Skip to content

Releases: iMathieuB/publii-agent-toolkit

v1.3.6

Choose a tag to compare

@iMathieuB iMathieuB released this 22 Oct 05:56

Changed

  • soumission-sitemaps adds the site to Bing by hand, with DNS verification, and no longer uses the import from Google Search Console: the import fails when the account signed in to Bing is not the one that owns the Search Console property, which only shows at the end (seen while recording on 2026-10-21), and it gives Bing access to Search Console data. The manual add worked on the first try.
  • site-publii-cloudflare prefers Claude in Chrome, which drives the person's own Chrome with its sessions already open, over the app's built-in browser, which starts signed out of everything.
  • formulaire-contact-cloudflare now goes through Cloudflare's MCP server first, in one execute call that needs neither Node.js nor Wrangler: Turnstile widget (managed, no clearance), Worker uploaded as a module with its plain-text, send_email and secret_text bindings, then the route, returning the site key, Worker name, bindings and route but never the secret (tried on 2026-10-21 on a machine without Node.js); Wrangler becomes the fallback. It starts with a one-call survey of the zone (DNS and MX records, Email Routing, verified addresses, existing widgets, Workers and routes) and asks only what is left, checks the Worker with three requests that send no email (405, 400, 403), makes the thank-you page noindex, follow with a link back to the home page, warns that Publii's visual editor can strip the form or the Turnstile script, and splits the test: the agent fills in the form and checks the consent box, the person clicks Turnstile and Send, since Turnstile stopped the agent's own submission in a rehearsal. decouvrabilite-google-et-ia notes that on Publii's default theme the page title is the H1, so the page text starts with the answer paragraph and no <h1>.
  • site-publii-cloudflare, from the same rehearsal's notes: no manual first sync (the person pastes the keys, saves and closes Publii; the agent's first desktop-sync publishes the content and tests the keys, so an empty theme never goes online); the exact /mcp wording (/mcp cloudflare is rejected); both sign-ins asked for at the start; a one-call Cloudflare survey; www added by default (AAAA 100:: proxied and a 301 dynamic redirect); the direct R2 token page and the clicks that need checking; the page title as the H1; internal links as #INTERNAL_LINK#/page/<id> in a second batch; four grouped consents; where to read AGENTS.md, which the pip package does not include; hero.jpg as the home image's name; an accent check on the published pages.

v1.3.5: accented text on stdin, garbled-text guard, centred titles

Choose a tag to compare

@iMathieuB iMathieuB released this 22 Oct 00:50

Fixed

  • Accented text sent on stdin arrived garbled on Windows. The CLI read stdin with sys.stdin.read(), which Python decodes with the Windows code page (cp1252) unless UTF-8 mode is on, so the "débutants" an agent sends as UTF-8 was stored as "débutants" and showed in the published page titles. Found in a rehearsal on 2026-10-21. Stdin is now read as bytes and decoded as UTF-8 (a UTF-8 or UTF-16 byte-order mark is honoured, and the code page is used only for bytes that are not valid UTF-8). A batch can also be passed as @file.json. The JSON the CLI prints is now plain ASCII with \u escapes, the same bytes in every console code page.
  • Garbled text is refused before anything is written. Every command's strings are checked for words that read as UTF-8 decoded as cp1252: such a word encodes back to cp1252 bytes forming valid UTF-8, which a genuine accented word never does, so real French, Portuguese and symbols such as € or → pass. The error says how to resend the batch.

Added

  • set_theme_style takes title_alignment (the theme's alignHero: center centres the header of every page).
  • The site-publii-cloudflare skill watches the browser tab while the person signs in (Cloudflare, Google, Bing) and carries on as soon as the signed-in page appears, instead of asking to be told; it centres page titles by default, and explains how to pass batches as UTF-8.

v1.3.4: S3 settings filled in, ready-to-publish images, a presentable look

Choose a tag to compare

@iMathieuB iMathieuB released this 22 Oct 00:15

Added

  • set_s3_deployment: fills Publii's "S3 compatible storage" server settings, all but the two keys, so the site owner only pastes the Access ID and the Secret key. It takes website_url, endpoint (without the bucket), bucket and region (default auto), and writes the values Publii itself writes for Cloudflare R2: domain, deployment.protocol s3, and under deployment.s3 customProvider true, provider custom, endpoint, bucket, an empty region and customRegion. These were read from a site saved in Publii 0.48.1 and one saved in 0.47.6 on 2026-10-21, with the same result in both. Prefix, ACL and the Cache-Control headers are left to Publii's defaults. The keys are refused under any of their usual names, keys already filled are kept, and a site set to another server type is refused. Asked for while preparing a video demonstration: the agent had created the bucket and knew every value but the keys, and the owner was retyping them by hand.
  • The site-publii-cloudflare skill uses it: once the bucket, its domain and the rewrite rule are in place, the agent fills the server settings (step 2.5), and the person creates the R2 token in the dashboard and pastes the two keys (step 2.6). The full table stays as the way to do it with an older toolkit.
  • The site-publii-cloudflare skill proposes a hero image for the home page, for the person to approve: what it shows and why, its format, the alt text, and a prompt to paste in the person's own image generator (or keywords for a free photo library). The agent does not drive an image generator in the browser on the person's behalf, since such services often forbid it (OpenAI's terms rule out "automatically or programmatically extract data or Output"). The approved image goes in with add_media as the page's featured image, which the default theme shows as the hero, and the toolkit makes its smaller copies (below).
  • add_media makes the theme's smaller copies of a post image, so the site can be published without a trip to Publii's "Regenerate thumbnails". Publii writes a copy per width the theme declares when someone adds an image in the app, and the theme's srcset points at them; an image copied in by the toolkit had none, so the srcset pointed at missing files. New module images.py reads the widths from the theme's config.json (files.responsiveImages, featuredImages for a featured image, contentImages otherwise) and the quality from the site settings, and writes responsive/<name>-<size> with the height rounded from the ratio and nothing enlarged. On 2026-10-21, for the same 2752 x 1536 photo, Publii 0.48.1 and the toolkit gave the same six names and the same dimensions, and file sizes within 3 %. The copies are staged in the same transaction as the image; a copy already on disk (Publii's own) is kept; sizes with a fixed height (crops) are left to Publii and listed. Needs the new optional images extra (Pillow); without it, the image goes in as before and responsive.note says why no copies were made. GIF, SVG, logos and sites with WebP conversion turned on get no copies.
  • set_theme_style: a presentable default look. It sets the theme's primary colour, colour scheme, fonts and footer text, each checked against the options the active theme declares, and with menu_style: "pro" adds a menu style for Publii's default theme: a line under the header, bolder entries underlined in the primary colour on hover and on the current page, and the last entry as a rounded button. The style is a block between markers in input/config/custom-css.css (the file of Publii's Tools, Custom CSS), rewritten on each call and removed with "none"; the rest of the file is kept. A primary colour too light for white text (under 3:1) is refused. PubliiTransaction gains set_custom_css, staged and swapped after the commit like the JSON files. Checked by rendering the demonstration site's home page with the block in headless Chrome.
  • Skills: site-publii-cloudflare now reads a site information sheet (fiche_site.md or similar) before writing and asks only what it leaves open, in one message, with announced defaults (visual editor, no cookie banner without trackers, the only logo in the folder); tells the person up front that the app may ask for permissions along the way; uses Cloudflare's MCP server first and the browser only for what the API cannot do, opening the right page itself; for the R2 token, fills in the creation page (name, Object Read & Write, the one bucket) for the person to click Create, then stops reading or capturing the page until the keys are pasted in Publii; and proposes a look with set_theme_style (colour from the logo, light scheme for a dark logo, theme fonts, footer text, the menu style). formulaire-contact-cloudflare warns that the Claude app's automatic permission mode can hold the Turnstile widget, the Worker and its route until the person gives explicit consent in the conversation (seen in a rehearsal on 2026-10-21 and first blamed on Cloudflare), asks for that consent in one message before creating them, and prefers the MCP server for Email Routing and Turnstile.
  • get_site_settings now has a deployment summary: protocol, website address and, for S3, endpoint, bucket, region and keys_set, a yes or no that never carries a key.

Fixed

  • site-publii-cloudflare skill: connecting Cloudflare from the Code tab of the Claude desktop app. The skill told the agent to have the person add Cloudflare's MCP server as a connector. On 2026-10-21 (desktop app 3.1623.1, Claude Code 2.1.304), a custom mcp.cloudflare.com connector added and authorized in claude.ai never reached a Code tab session, even a new one. The skill now checks the server's state and, when it is missing, adds it at user scope with claude mcp add --transport http --scope user cloudflare https://mcp.cloudflare.com/mcp (the bundled claude.exe lives under %APPDATA%\Claude\claude-code\, outside the PATH). The person then signs in with /mcp. It also warns that the older "Cloudflare Developer Platform" connector, which Cloudflare has deprecated, has no tools for bucket domains or rules, says that Wrangler cannot set the rewrite rule, and asks the agent to offer before driving the Cloudflare dashboard in the browser instead of the server.
  • The R2 activation step now gives the dashboard labels read on 2026-10-20, in place of a placeholder.
  • formulaire-contact-cloudflare: the consent checkbox was invisible on Publii's default theme. "Simple" hides native checkboxes (opacity:0; position:absolute) and draws its own in the label that follows (input[type=checkbox]+label:before). The form fragment put the checkbox inside its label, so no box was drawn: the field could still be ticked by clicking the text, but nobody could see it, and without it the Worker refuses the message. The fragment now puts the checkbox first and its <label for="consentement"> right after, which also renders a normal checkbox on themes that do not restyle it. Found on 2026-10-21 while rehearsing the demonstration.

v1.3.3: publishing with Publii 0.48.1

Choose a tag to compare

@iMathieuB iMathieuB released this 20 Oct 20:50

Found by running the five-page example from AGENTS.md end to end on a fresh Windows 11 virtual machine with Publii 0.48.1, the version getpublii.com now offers: install without Git, build the site with one plan and one apply, then desktop-sync --apply to an S3 endpoint (a local S3 emulator standing in for Cloudflare R2, so nothing went online).

Fixed

  • Enabling the cookie banner made Publii stop on a dialog at every launch, which blocked desktop-sync. Publii reads an enabled banner without advanced.gdpr.settingsVersion as a pre-0.40 configuration and opens "We've made significant changes to the Privacy Settings" each time the site loads. update_site_settings now writes settingsVersion: "v2" when it touches the banner and the marker is empty, as Publii's own Save settings does. The defect dates from 1.3.0.
  • desktop-sync refuses such a site before launching Publii, dry run included, and says how to clear it: open Site settings in Publii and click Save settings once.
  • desktop-sync publishes with Publii 0.48.1. The site header and the modal's "Sync your website" are now Buttons, and the success modal closes with "Close". Both forms are accepted, with the same guards as before.

Changed

  • Install from the GitHub .zip address, which needs no Git. AGENTS.md and the site-publii-cloudflare skill cover the Visual C++ Redistributable that pywinauto needs on a fresh Windows, and give the exact Publii 0.48.1 labels of the S3 server form.

The four skills are attached as ZIPs for upload to claude.ai. Full details in CHANGELOG.md.

v1.3.2: a contact form skill

Choose a tag to compare

@iMathieuB iMathieuB released this 20 Oct 15:30

A fourth skill for Claude: a contact or waiting-list form for a static site on Cloudflare. No code changes; the Python package is the same as 1.3.1.

What's new

formulaire-contact-cloudflare.zip adds a form to the Contact page of a static site hosted on Cloudflare, such as a Publii site on R2. A small Worker receives the form, checks Turnstile and emails each message to a verified address of the Cloudflare account. It keeps no copy. The skill also updates the privacy policy. It needs an agent that runs commands on the computer, such as Claude Code, and Node.js for Wrangler.

It was tried on a real domain before this release. The wrangler.toml template deployed as is, the form's route answered before the site on the same hostname, and the first message went to the spam folder. The skill now tells the owner to look there and mark the sender as safe.

The four ZIPs below upload as is to claude.ai (Customize, Skills, Upload a skill), one at a time. The three from 1.3.1 have not changed.

Details in CHANGELOG.md.

Install

pip install "publii-agent-toolkit[desktop] @ git+https://github.com/iMathieuB/publii-agent-toolkit.git@v1.3.2"

The [desktop] extra (publishing) is Windows only.

v1.3.1: build a site from a fresh Publii project, plus three skills

Choose a tag to compare

@iMathieuB iMathieuB released this 16 Oct 14:03

An agent can now build a site from a brand-new Publii project, and three skills for Claude come with the toolkit.

Since 1.2.0

1.3.0: build a site from a fresh Publii project. create_page, create_menu, add_media, update_site_settings (a closed whitelist: site name, description, logo, front page, cookie banner, privacy policy page) and get_site_settings. A single plan and a single apply can create the pages, the menus, the logo and the settings of a five-page site. apply refuses while Publii is open and returns the backup folder as result.backup.

1.3.1: one fix, three skills.

  • Every read left a connection open on db.sqlite until garbage collection, which on Windows can block replacing the file. Present since 1.0.0, now closed, with a regression test.
  • skills/: three skills for Claude, in French, for small-business owners in Quebec. The ZIPs below upload as is to claude.ai (Customize, Skills, Upload a skill), one at a time.
    • site-publii-cloudflare.zip: puts a five-page Publii site online on Cloudflare R2 with its own domain, using this toolkit. Needs an agent that runs commands on the computer, such as Claude Code.
    • decouvrabilite-google-et-ia.zip: writes a home page and service pages that search engines and AI assistants can read. Works anywhere, including a free claude.ai account.
    • soumission-sitemaps.zip: checks the sitemap, then walks through Google Search Console and Bing Webmaster Tools.

Details in CHANGELOG.md.

Install

pip install "publii-agent-toolkit[desktop] @ git+https://github.com/iMathieuB/publii-agent-toolkit.git@v1.3.1"

The [desktop] extra (publishing) is Windows only.

v1.2.0: reliable multi-site publishing, distinct backups, menu renames

Choose a tag to compare

@iMathieuB iMathieuB released this 08 Oct 14:33

Four defects, each found in production use. The first two were found by driving the desktop module against six real sites instead of two, and both made it report success while leaving a site unpublished, which is the one failure mode a publishing tool must not have. The third left Publii open and the database locked after a launch that never exposed its controls. The fourth was in the repository: two transactions in the same second shared one backup folder.

Fixed

  • A sync could only ever publish one site per launch. Publii raises a "Your website is now in sync" modal when a sync completes, and that modal covers the whole interface: while it is up, neither the site switcher nor the "Sync your website" link exists in the accessibility tree. The driver never dismissed it, so the second site in a batch failed with "site is not in the Publii list", a message that points at selection and hides the real cause. The driver now acknowledges the modal before moving on, and only clicks that "OK" when a known completion string is present or the sidebar link has disappeared, because "OK" alone is too ordinary a label to trust.

  • Two projects sharing a display name could not be told apart. Nothing stops two Publii projects from carrying the same display name, and it is the normal case for a bilingual site whose brand does not translate. The site list shows nothing but that name, so selecting by name published whichever entry came first and reported success, leaving the other project behind and looking like it had worked, since the verification read the same name back.

  • A failed launch left Publii open. sync() launched Publii and waited for its accessibility tree before entering the try whose finally closes it. When the tree never filled (seen with the screen locked during a remote session: Publii exposed 9 empty containers), _wait_for_tree raised and Publii stayed open, holding the database lock, so the next run refused to start until someone closed it by hand. Launch and tree wait now sit inside the try.

  • Two transactions in the same second shared one backup. The backup folder under input_backup/ is named by the UTC time to the second, and it was created with exist_ok=True. Two passes run back to back wrote into the same folder, and the second copy of db.sqlite replaced the first, so the state before the first pass was no longer on disk. A folder that already exists now gets a -2, -3… suffix instead of being reused.

  • The README told you to pip install a package that is not on PyPI. It now installs from GitHub.

Added

  • update_menu_item, as a JSON command and as PubliiTransaction.update_menu_item(): renames a menu entry (its label, or its title) in place. Removing an entry and adding a copy moved it to the end of its level, which made a rename impossible without reordering the menu. The command refuses to change an entry's target: pointing it elsewhere stays a remove and an add, so the integrity check sees the new link.
  • PubliiDesktop.plan() groups the requested projects by display name and refuses to act when only part of a same-named group is requested: syncing one of two identical entries means possibly publishing the other without being asked to.
  • PubliiDesktop.directories_by_display_name() replaces the collapsing display_names() lookup, which silently lost one of any two projects sharing a name. display_names() remains for membership tests.
  • Same-named groups are published by walking the list positions, and which directory sat behind which position is worked out afterwards, by reading which syncDate moved. No assumption is made about Publii's display order. Publishing one project twice costs nothing (the content being identical, Publii uploads no file), whereas a guess about ordering costs a project left unpublished.
  • Tests: tests/test_desktop_plan.py, seven tests over the grouping and refusal logic; two tests for update_menu_item (a rename keeps the entry's position; a retarget, an empty update or an unknown id is refused); one test for distinct backups within the same second.

Install:

pip install "git+https://github.com/iMathieuB/publii-agent-toolkit.git@v1.2.0"

v1.1.0 — Publishing without the click

Choose a tag to compare

@iMathieuB iMathieuB released this 08 Sep 02:13

Added

  • publii_toolkit.desktop — drives the Publii desktop application to render
    and publish a site, closing the last manual step in an otherwise scriptable
    pipeline. Publii has no CLI, so rendering and syncing were interface actions
    that a human had to perform between two automated steps.
  • CLI commands desktop-status and desktop-sync, emitting the same JSON shape
    as the rest of the tool.
  • Optional desktop extra: pip install "publii-agent-toolkit[desktop]". The
    core package keeps pydantic as its only runtime dependency.

How the desktop driver works

Publii is an Electron application, and Chromium only builds its accessibility
tree when something asks for it. Launched normally, Publii exposes nine empty
panes to Windows UI Automation and not one button. Launched with
--force-renderer-accessibility it exposes the whole interface by name. The
driver therefore always launches Publii itself rather than attaching to a window
that may have started without the flag.

Success is read from disk, never inferred from a click. Publii writes syncDate
into input/config/site.config.json when a sync completes; the driver compares
that value before and after and only reports success when it changes.

Safety

A sync publishes to the live site, and the transaction backups this toolkit
takes protect input/ — they do not undo a publication. The driver therefore
defaults to a dry run, refuses to start while Publii is already open, refuses to
click any element whose label contains "delete" (the site list places a "Delete
website" link beside every entry), and re-checks the selected site name
immediately before publishing.

Known trap, handled

The sidebar link and the confirmation modal's button carry the identical label
"Sync your website". Only the control type separates them: Hyperlink in the
sidebar, Text in the modal. Clicking the sidebar element twice reopens the
modal and publishes nothing, while looking like a click that worked. The driver
matches on control type and waits for the modal title before confirming.