Repository navigation
Releases
acrylblends 1.1.0
1.1.0
A page follows its state over one WebSocket, so eight windows of a board no longer use up the browser's connections; a panel left open while the app restarts keeps working; and the five things the final macOS walk of the workshop guide found: the agent drives the page that asked, the app honours PORT and HOST, the appearance reaches every open window at once and the pages the agent builds, and the "Agent is driving" bar stays up for the whole run.
Added
-
watch(key, onValue)of/api/state/client.js. A cell ofctx.stateis followed in a page withimport { watch } from '/api/state/client.js' const stop = watch('board.cards', (cards) => render(cards))
The callback gets the value now and after every change, as the JSON value the server holds (a string stays a string);
stop()ends it. A third argument,(error) => ..., is told of a key that does not exist; without it the console gets one warning. One WebSocket (GET /api/state/ws) carries every key of the page. A WebSocket is not one of the six connections a browser allows to one host, and an event stream per key is: three keys in eight windows were 24 streams, and the windows past the sixth loaded only partly (found on macOS with 8 board windows). -
The client retries a lost socket after a delay that grows from 250 ms to ten seconds, subscribes every key again and does not repeat a value the page already has. A server that is silent for 45 seconds counts as gone (it writes a ping every 15). A page that is hidden for good lets its connections go, because the back/forward cache would keep them open, and opens them again when it comes back.
-
Where the WebSocket cannot open (no
wspackage, a server that does not upgrade, a proxy that does not pass it) each key is followed by its event stream instead, with one console warning.GET /api/state/<key>/events?json=1sends every value as JSON; without it a string is sent as it is, as before. -
Who may open it. The upgrade needs a loopback peer on the app's own port and a loopback
Host; theOrigin, when the request has one, must be exactly the app's own origin andSec-Fetch-Siteabsent orsame-origin. A program on this machine that sends noOrigin(a script, a test) is let in, as it is for the JSON and stream routes; a page of another origin gets 403, and so does a request that names another host (a name that only resolves to this machine, DNS rebinding), with or without anOrigin. -
wsis now a dependency of theacryl-stateplugin (it was only the door's development dependency), so an app that follows state in production has it. It is loaded when the first socket is wanted. -
The agent template's
AGENTS.mdtells the agent to follow state withwatchand not with anEventSource. -
Gates:
verify:doorsection G holds all six connections of a page in real Chrome and checks thatwatchstill delivers every change of a key;verify:desktopopens eight board windows that each follow three keys and checks that they all load and show each new count.
Fixed
- A page left open while the app restarts keeps working. The panel's token belongs to one start of the app, and in development
node --watchrestarts the app at every change of a file, the guide's first edit included: a page that stayed open sent the old token, the socket closed with 4401 and the panel said "the panel was not accepted (reload the page and try again)". The server now saysacceptedonce it has taken the token; when the page is refused (4401 on the socket, 403 on the stream) it asks for a new session and tries once more without the person seeing anything, and only a second refusal is shown. The terminal driver takes a fresh session when it opens too. The token stays per start (a token kept across starts would give a leaked one a longer life, and the retry is needed anyway). The door's page channel and the state socket never had a token and reconnect by themselves. Gate:verify:doorsection K restarts the app under an open page in real Chrome and checks that the same document asks the agent, is looked at through the door and follows a state key again, with no reload; with the retry switched off it fails with the message above.
Fixed (found by the macOS walk of 1.0.10)
- The agent drives the page whose panel asked. With a browser tab left open from an earlier step and a desktop window, a question asked in the window's panel sent every door call to the tab: the window stayed where it was, the hidden tab moved, and the agent reported success. The panel now tells the door which page asked, and until the run ends the calls that name no window go to that page (a page that reloads or navigates during its run stays the one; an explicit
--windowstill picks its own). The asking page comes before the page the door last sent somewhere and before the page in front. Gates:verify:doorsection H holds a second tab in front and asks from the panel's tab; unit tests in the door's channel and the panel's socket. PORTandHOSTare honoured. Theagentandblanktemplates ignoredPORT(PORT=3299still listened on the app's own number 3991). The instance readsACRYLBLENDS_PORT, thenPORT, andACRYLBLENDS_HOST, thenHOST, for every template (the three templates that mapped them inmain.tsno longer need to).docs/framework/guides/production.mdhas the four variables and a real-server example.- The appearance reaches every open window, and the pages the agent builds. The Settings page saved the choice to the browser's storage and only a reload applied it elsewhere. The template now serves
/appearance.js: one script tag in a page's head applies the saved choice before the page paints, applies a change made in another window at once, and defines--bg,--fg,--muted,--surface,--borderand--accentfor the system's light and dark and for the person's choice. The home and settings pages use it, and the template'sAGENTS.mdtells the agent to put the tag in the pages it builds and colour them with the properties (the board the agent built used the system's colours and ignored the choice). Gate:verify:doorsection J changes the appearance in one tab of real Chrome and reads the other tab's background colour. - The "Agent is driving" bar stays up for the whole run. It showed only while one door call was in flight (a run is mostly the agent thinking: the walk saw it in none of 33 frames). It is now up from the start of a run asked for from that page's panel to its end, says "working on your request" between calls, and its Stop ends the run as well as the page's control (a new question allows the agent again). Gate:
verify:doorsection I, with an agent tool that takes three seconds and calls nothing.
Changed
- The names
wsandclient.jsare routes under/api/state, so a state cell cannot be called either (ctx.state.cell('ws', 0)throws, saying so). No other key changes.
Upgrading
Run npx acrylblends upgrade in an app made before 1.1.0, then npm install. That takes the new acryl-state (and moves its ws dependency from devDependencies to dependencies, so npm install --omit=dev keeps it), the door, the agent panel and the instance (PORT and HOST). It does not touch your own files, so two things of this release need a copy from a fresh app (npx acrylblends init fresh --template agent): src/routes.ts (the /appearance.js route and the pages that use it) and AGENTS.md (what the agent is told about the appearance, watch, and the stale panel). The event stream routes are as they were: a page that opens its own EventSource still works, and still holds a connection for as long as it is open, so move it to watch.
acrylblends 1.0.10
1.0.10
A production install no longer carries vitest.
Fixed
-
acrylblendsno longer declaresvitestas a peer dependency. It was declared (optional) for the two conformance suites a provider of a port passes,acrylblends/db/conformanceandacrylblends/kv/conformance; pnpm resolves an optional peer wherever the workspace has the package, sopnpm deploy --prodof an app pulled vitest, vite, rolldown, lightningcss and their native files into the production closure (about 30 MB, and files of the wrong platform for an archive built on another system). The suites now run on the vitest the provider's test hands them as a third argument, so the package imports no vitest at all (acrylblends/testingnever did) and a suite can never see a second copy of vitest that is not the one running the test. -
A new gate,
pnpm run verify:vitest-peer, checks the packed package declares no vitest and no file of it imports vitest, thatpnpm install --prodof a project that depends on it has no vitest, vite, rolldown or lightningcss in its closure even with vitest in the workspace beside it, and that a provider's project with its own vitest runs the kv conformance suite fromacrylblends/kv/conformanceunder pnpm. It fails on 1.0.9. -
window listnames the path a window was opened on until its page reports its own address (it showed an empty path for a window still loading), and the timeout text adds "a page busy running a long script" to the causes it names.
Upgrading
Nothing to change for an app. A provider's test that imports describeKvConformance or describeDbConformance from the package now passes vitest as a third argument:
import * as vitest from 'vitest'
import { describeKvConformance } from 'acrylblends/kv/conformance'
describeKvConformance('my-provider', open, vitest)Without it the call throws, saying what to pass. The copy that npx acrylblends add kv --suite (or db --suite) puts in an app, and the plugins' own suites in this repository, are unchanged: they import vitest from the app, and take two arguments.
acrylblends 1.0.9
1.0.9
Hardening after the first walks of 1.0.8's WebSocket channel, and a panel bug a CI run found.
Fixed
- A message typed as the page loads is no longer dropped. The panel asks the server for the latest run when a page loads, and over its socket "there is none" takes a round trip; a message typed in that moment found the panel believing it was busy following a run, and was ignored (the text stayed in the box, nothing was asked). A panel is busy only once there is a run to show. A real-browser case delays that answer and fails on 1.0.8's client; it is how a CI run (Node 26, a slow runner) lost a message.
- The "Working... m:ss" timer counts from the run's start, not from when the page loaded (a page that loads during a long run showed 0:05 on a run 25 minutes old).
- A look right after a navigate or reload waits for the page to register again (up to two seconds) instead of failing with a timeout, and a read-only call that no page acknowledged gets one more try when a page registered while it waited.
- The "no page answered" message names the real causes (a background tab, a page still loading, a page holding the browser's six connections with event streams of its own) so an agent no longer reloads and resizes windows to chase one that does not apply.
- A new window never lands exactly on another, even on a small screen. 1.0.8's cascade wrapped to the screen's corner when the window did not fit, and on a 1024x768 screen that is where the first window already was (found by the new Electron gate on a Windows runner). The place is now the first free one: a step down and right, then from the corner, and on a screen no bigger than the window the step is kept (the window may run a little past the edge).
window listshows each window's place (1100x760@120,80), and the real-Electron gate asserts that a window opened beside another does not stack on it (1.0.8's cascade).
Upgrading
npx acrylblends@latest upgrade brings the door, the panel and the desktop shell.
acrylblends 1.0.8
1.0.8
Two windows of the desktop app, and the Settings the workshop's menu step needs.
Two live windows no longer stall
- The cause. A browser lets a page hold six connections to one host, across all the windows of the app, and the door's channel, the panel's follow of a run and a board's own state were each an event stream held for as long as the page was open: two windows made six, and then every request of either window (the door's answers, Stop) queued forever while the server answered
/healthin a millisecond. 1.0.7's fix covered pages the browser kept after they were left, not two live ones. - The fix. The door's page channel and the panel's runs now travel over a WebSocket, which is not one of the six: a page opens it at once however many streams are open, and the answers go up the same socket (a request would queue). The event stream stays as the fallback (a server without the
wspackage, a proxy that cannot upgrade), and the page says so once in the console. A WebSocket has no CORS, so the upgrade is refused unless itsOriginis exactly the app's own (a page on another port, a web site, a program that sends none are all refused, with tests); the panel's socket also needs its token as the first frame.wsis a development dependency of the door, so new apps have it. Checked in a real Chrome with six connections held (the door and the panel still answer; kept to the stream, the look fails in seconds with a reason) and in a real Electron with two windows that hold six streams between them. - Streams are the app's to count too. The agent template's
AGENTS.mdtells the in-app agent to keep to two event streams a page; the state client still opens one per cell (a ticket on the 1.1 list), and the panel's width over a page's fixed elements is another.
Desktop
- A Settings item. The
agenttemplate serves a/settingspage (the appearance: system, light or dark, kept in the browser) and the desktop app's menu has a Settings item (Cmd+, on macOS, in the application menu; Ctrl+, in File elsewhere) that opens it in a window, or focuses the window that shows it.acryl menu click --path "File>Settings..."does the same; an app without a/settingspage gets no item. The real-Electron gate presses it and changes the setting through the door. window opencascades. A new window sits a step down and to the right of the one it was opened from, not exactly on top.
Small
- The panel's disabled buttons look disabled (Send kept its dark style while a run went).
- The preflight names what each agent needs (
pi (DeepSeek key found),codex (be logged in: codex login)). - The panel's refusal of a driver says why: a terminal driver (pick it in the panel), an agent that is not on the PATH, pi without a key.
Upgrading
npx acrylblends@latest upgrade brings the door, the panel and the desktop shell; run npm install for ws. The agent template's src/routes.ts (the /settings page) and AGENTS.md are your own files: copy them from a fresh npm create acrylblends@latest my-app agent if you want them.
acrylblends 1.0.7
1.0.7
The fixes from the first walk of the workshop on Windows, and one for every machine: a deployed app keeps what the agent built.
Before the workshop
npx acrylblends@latest preflightchecks a machine before the first command and needs no app folder: Node 22.19+, npm, git, a coding agent the panel would offer (claude,codex, orpiwith a DeepSeek key), a free port in the apps' range (so a firewall or security tool that blocks loopback shows up here), andghfor the publish step; on Windows also the PowerShell version (5.1 needs the guide's PowerShell lines) and long paths. One line per check:PASSwith what was found,FIXwith the exact command or address,NOTEfor what the first blocks do without; exit 1 while anything needs a fix. (An older Node gets the bin's own plain refusal before anything else.)
Deploy
- Production serves the extensions the agent installed. With
NODE_ENV=productiontheagenttemplate left theextensionsrow out (1.0.1), so an app whose pages were extensions, such as the workshop's board, answered 404 for them once deployed. The row now starts again, read-only: the extension folders ofplugins/(committed with the app) are loaded once from where they are; install, update, remove, reload and approve are refused;/ask,/extensions*and/tasks*stay unmounted. The start printsextensions (read-only): board, orextensions: none found in plugins/, and aWARNINGwhen.acrylblends/extensions.jsonrecords an extension whose folderplugins/lacks.verify:productionbuilds one in development and serves it in production. An app made before 1.0.7 keeps its ownsrc/app.ts: copy the production switch from a freshnpm create acrylblends@latest my-app agent(orupgradethe extensions plugin and setreadOnly: truefor it in production).
The door
- Git Bash on Windows.
acryl act navigate --path /nopearrived asC:/Program Files/Git/nope(MSYS rewrites an argument that looks like a Unix path before a native program sees it), so the agent of every attendee whose shell is Git Bash (pi's is) could not navigate. The shim now exportsMSYS_NO_PATHCONVandMSYS2_ARG_CONV_EXCL, and the client turns a--pathunder the Git folder back into the path typed, with a warning. A test runs the shim through a realsh(and, on Windows, shows that MSYS rewrites the same argument without the exports). - Two tabs of the app no longer make a look wait 30 s. A page that was left stayed alive in the browser's back/forward cache with its event stream open; a browser allows six connections per host, so after a few navigations the live page's own answers queued behind dead pages. A page now lets go of its stream when it is left and reopens it if it is restored. The door also chooses the page that answers the same way every time (the page it just sent somewhere, then the one in front, then the most recent); pages acknowledge a call, a read-only call (
look,act wait) to a page that does not acknowledge in 3 s goes to the next page and puts the silent one last, and when none answers it says so and what to do, instead of waiting 30 s.verify:doordrives two real tabs. - A field is a secret by what it is. Any text field whose label contained "card" was hidden from the agent, so it could not type the title of a card on a board. A password input, an
autocompleteof a credential or a payment detail, a value in the shape of a key or token, and labels that say so in plain words (password, API key, card number, CVV, IBAN, ...) are hidden; "Card title" is not.
The panel
- It sits beside the page: the page gets narrower by the panel's width while the panel is open, so the header and right-hand columns stay in view.
- It renders the agent's answer as Markdown (headings, lists, code, bold, italic, links), safely: the answer is parsed to a tree and built with
createElementandtextContent, so markup, scripts andjavascript:links stay text. - It says it is working, with the time elapsed, while a run is going: a coding agent started headless prints nothing for minutes.
Upgrading
npx acrylblends@latest upgrade brings the door, the panel and the extensions plugin. The agent template's src/app.ts and AGENTS.md are your own files; copy their production switch and the "Drive the app" paragraph from a fresh npm create acrylblends@latest my-app agent.
acrylblends 1.0.6
1.0.6
The desktop app's windows and menu are door verbs: an agent that lives in the app can open a window, drive it, resize it and press a menu item, and cannot end its own session by accident.
Windows and menu (desktop app)
acryl window list|open|focus|close|resizeandacryl menu list|click.window open --path /settingsopens a new window on a path of the app and answers its id;menu click --path "File>New Note"presses a menu item by the labelsmenu listprints. They are in the CLI, MCP and the in-app agent's tools like every verb. Settings are a path of the app (build them at/settings) or a menu item.--window <id>onlookandactdrives the page of that window instead of the page used last. Each window tells the door which it is (anAcrylWindow/<id>suffix of its user agent), so it works for windows the agent opened itself and for the first one.- It works when the window is attached to
npm run dev. The door lives in the server, which is another process then. The Electron main process connects to the door the way a page does (over/api/acryl-door/shell/*, which needs the run's secret, so no page can open it) and follows the server across restarts. - The session cannot be ended by accident.
window closeon the last window, and a menu item that ends the app (Quit, Close Window on the last window), are refused unless the call says--force;menu listmarks them. Every call is in.acryl/audit/ui-control.jsonlwith the window id. With no desktop app attached the verbs answer one line:no desktop window is attached; run npm run desktop. - A new gate,
pnpm run verify:desktop, runs all of it in a real Electron (attached to a dev server, the app's own server, a dev restart, the guards); CI runs it on Linux under a virtual display and on Windows. Design: the Electron adapter.
The agent template
AGENTS.mdtells the in-app agent how to drive the app it builds (look,act, a path the app does not serve answers404 at /nopeand the window shows a 404 page, the window and menu verbs, the--forcecaution) and to build settings as a page at/settings.
The package
- A clean tarball. 1.0.5 was packed from a working tree that held 95 stray
plugins/acryl-router/src/**/*.d.tsfiles (declarations beside their sources; inert, but every new app received them). 1.0.6 is packed from a fresh clone of the release commit, andverify:tarballnow fails on a declaration file beside its source. packages/cli/lib(build output) is no longer tracked in the repository; a fresh clone builds and packs the same tarball.
Upgrading
npx acrylblends@latest upgrade brings the door, the router and the desktop shell. An agent-template app's own AGENTS.md is its own file: copy the "Drive the app you build" paragraph from a fresh npm create acrylblends@latest my-app agent if you want the in-app agent to know the new verbs.
acrylblends 1.0.5
1.0.5
The first-run fixes from the workshop guide's walk on 1.0.4, and the app's own agent driving the page with a real model.
The agent template
- A new app from the
agenttemplate has the agent panel.npm create acrylblends@latest my-app agentanswers/api/agent-panel/sessionwith noaddstep;add driver-ptyandadd driver-acpmount after the panel's row. (An agent-template app made earlier gets it withnpx acrylblends add agent-panel.) The panel starts nothing in production. test/builder.test.tsbuilds a plugin with a name and a path of its own (builder-probe-<random>), not/hello. A route a person adds at/hello(the workshop's first route) shadowed the plugin the test installs, and the template's ownnpm testfailed on 1.0.4. The packed-install gate now adds/hello,/hiand/apito the agent template's routes and runs its tests.- The default step budget is 12 (it was 6: looking, pressing and looking again used the six before the answer).
The panel
- The tool lines say what the tool is doing. A coding agent started in the folder reports its own log; the panel showed the driver's name once per event ("codex, codex, codex"). It now shows the command (
codex: $ npm test) or the sentence, drops the agent's bookkeeping (hooks, token counts, the output of a command that succeeded), and shows a failure; the app's own agent shows the tool and its arguments. - The panel's script is added to every HTML page the app answers, including the pages of a plugin that loads at boot (it used the late-mount way and was missing after a restart). It uses
ctx.router.wrap, like the door. A gate now checks the door's and the panel's scripts on a page of a plugin installed live, one loaded at boot, and one mounted as a row ofblend.yamlahead of both.
The door and the app's own agent
- The door's verbs reach a real model with their schemas. The tools registry takes an optional JSON Schema per tool (
parameters), the openai plugin sends it, and the door registersdoor_lookanddoor_actwith the schema MCP lists, so a model knows anactneeds itsop. With a DeepSeek key in the server's environment (ACRYLBLENDS_MODEL_URL,ACRYLBLENDS_MODEL,ACRYLBLENDS_MODEL_KEY) the panel's built-in agent looks at the open page and presses its buttons: no Codex or Claude login needed. Recorded in the evidence page.
Desktop
npm run desktopwhilenpm run devruns attaches to the running server instead of failing with "another process serves the door": the window opens on the dev server found through.acryl/door.json(only a live process on a loopback address that answers), prints a line saying so, and closing the window leaves that server running.
Rows, extensions and npm run dev
- A row can import the framework by name. A plugin mounted as a row of
blend.yaml(imported bysrc/app.tsbefore anything runs) that importsacrylblends,acrylblends/body-limitoracryl-routerused to stop the app withCannot find package 'acrylblends', even afternpm install acrylblends. The preload every script uses (plugins/acryl-node-server/src/register.ts) now resolves those names the way the extensions do, for the app's own files.verify:doormounts such a row and fails on 1.0.4. - The agent template's
AGENTS.mdsays what to build: a feature written while the app runs is an extension (plugins/<name>/, installed with.acryl/bin/acryl plugin add plugins/<name>); a row ofblend.yamlonly when the person asks for one. - A live install no longer fights
npm run dev. The dev script's watcher saw the staged copy of an extension being replaced and restarted the app halfway through an install or a reload (a loop, with the door answering "no running app"). An unchanged extension reuses its staged copy, and nothing staged is deleted while the watcher runs (a start without it prunes). A new gate,verify:dev-install, installs, reloads and restarts undernpm run dev. - A mistyped path no longer strands the window.
act navigate --path /nopeused to leave the window on a bare "404 Not Found" with no door script, and every later call answeredno-windowuntil a person opened a real page. In development the 404 of a page request is now a small HTML page (it names the path and links back) that carries the door's script, solook,act clickandact navigatekeep working;act navigatesays what the app answered (404 at /nope,200 at /p1).verify:doornavigates to a missing path, looks, clicks the way back and navigates on. - The agent template's home page says the panel is on the page (it still told you to add it).
Upgrading
npx acrylblends@latest upgrade brings every plugin copied into an existing app (the door, the router, the panel, the extensions, the preload, the agent, the desktop shell). An agent-template app's own test/builder.test.ts, blend.yaml and src/app.ts are its own files: copy them from a fresh npm create acrylblends@latest my-app agent if a route of yours shadows the old test or you want the panel row.
acrylblends 1.0.4
1.0.4
An agent that builds a page can now look at it and click through it in the same run. For the workshop's "built from the inside, then driven" demo: the page an agent builds is seen by the door, the page refreshes when the app changes, and the panel keeps the run alive across those refreshes.
The door
act reloadandact navigate --path /settings(CLI, MCP and the built-in chat: one verb table). They answer first, move the page, and then wait up to 15 s for the new page to connect to the door, so the nextlooksees it. A path must start with one/and name no other site; a path whose page does not load the door fails with atimeoutthat says so.- The open pages reload after a live plugin change (
plugin add,plugin remove,plugin reload, except for a plugin that was already as it should be); the verb answers once they are back.reloadOnChange: falsein the door row turns it off. - The door's page script is added to every HTML page in development, including the pages of a plugin that loaded at boot, so a page an agent writes can be looked at and driven with no tag. Production is untouched (it has no door).
- The
.acryl/bin/acrylcommand uses a real Node when the app runs in Electron (anodeof 22.19 or newer on the PATH), so each call no longer prints a code-signing complaint on macOS.
The router
ctx.router.wrap(middleware)runs a middleware around every request, whenever the plugin that asks was mounted; a middleware in an ordinary mount wraps only the routes mounted after it. It goes away with the calling plugin.
The agent panel
- A run belongs to the app, not to the page. A page that reloads, closes or moves no longer cancels the agent: the next page that loads shows the question and the events so far (
GET /api/agent-panel/run) and follows the run to its end. Only Stop (POST /api/agent-panel/stop) cancels it. A run that ended before a page loaded is shown there, and does not reload it again.
Docs
- The agent docs (
AGENTS.md, the app guide) say thatlookandactare how you check your own UI work, and nameact reloadandact navigate. The workshop pages say to run the agent from a clean configuration and to word tasks as "do it now, without asking for approval". The recorded run is in Build, then check it through the door.
Upgrading
An app made by an earlier release gets all of it with npx acrylblends@latest upgrade (the door, the router and the panel are library plugins). An app keeps working without it.
acrylblends 1.0.3
1.0.3
Fixes from the workshop rehearsals, and a correctness fix that matters on the Node most Mac users now have.
Correctness
- Node 26. A request body that arrived complete but whose connection closed before the app read it could be cut to its first chunk on Node 26 (Homebrew's current
node): the adapter read the stream's buffer once, and Node 26 returns only the first chunk of a destroyed stream's buffer in oneread(). It now reads until the stream says there is no more, which is the contract ofReadable#read()on every version. Node 26 joins the CI matrix (the fullcheckon Linux, and the framework packages on Linux, macOS and Windows).
Starting an app
- A relative path in a
blend.yamlrow is read from the app's folder.config: { migrations: migrations }(andpath: data/app.sqlite) on theacryl-db-sqliterow, andpathonacryl-kv-json, used to stop the app with "no file base URL". Theadd dbhint and the data guide show the row. create-acrylblendstakes a GitHub address pasted from the browser as a source or a registry (https://github.com/owner/repo, with.gitor/tree/<ref>), likegithub:owner/repo.
Behind the release
- Two flaky tests were fixed at the root: the node-server spec served a fixture that a ported test makes unreadable for one test while another worker read it, and the PTY gate read the audit log before the line that follows the process's death.
- The packed-install gate now runs the refusal of an ACRYL starter through
npm exec, and an app that mounts the database with a relative migrations path (the row and its registry entry written the way the hint says).
acrylblends 1.0.2
1.0.2
The release for people starting from nothing: every friction found by walking the published 1.0.0 as a newcomer on macOS and Linux, fixed, plus one more production default.
Security
The chat template is closed in production. Anyone who could reach a deployed chat app spent the owner's model key. With NODE_ENV=production every route but GET /health now answers 503 until you choose: CHAT_ACCESS_TOKEN (the browser asks for a login: any user name, the token as the password), or ACRYL_PRODUCTION_KEEP=chat to make it public on purpose, which prints a WARNING at start and limits one address to CHAT_RATE_PER_MINUTE messages a minute (default 20). Development is unchanged. An app made by 1.0.0 or 1.0.1 keeps its own src/routes.ts: copy src/access.ts, src/chat-app.ts and src/routes.ts from a fresh acrylblends init my-app --template chat, or put your own login in front. See What runs in production.
Start from the registry, from npm alone
npm create acrylblends@latest my-app -- --from <id>makes an app from a registry starter (npm needs the--before the options).--fromalso takesgithub:owner/repo,@scope/name, a.tgzand a template folder;--registry github:owner/repo(or a folder) reads another registry. Nothing is installed or run, the manifest of the new app is its own (private, named after its folder, no licence of the starter's), and the secrets scan runs first.- Registry entries declare their runtime. The index now carries
runtime: acrylblends | acryl. A starter for the ACRYL app (acryl.gtdand the otheracryl.*Blueprints) is refused with one line: it is an ACRYL app starter, open it in the ACRYL app.create-acrylblendsnever writes disabled rows. - Three starters that work today:
acrylblends.notes-desktop(the notes app with a desktop window),acrylblends.chatandacrylblends.agent, made from the templates by the release routine. acrylblends.github.io/blends shows the runtime as a badge and filters on it.
The newcomer path
acrylblends add agent-panelmounts itself. It adds the panel's row and its registration (anddriver-ptyanddriver-acpafter it), or refuses before copying anything, naming the rows the app lacks (agent,extensions) and where they come from (theagenttemplate). A blank app no longer ends in a silent 404.piis offered in the panel when it has a DeepSeek key in the environment or its own stored login (~/.pi/agent/auth.json). The docs say that the panel's coding agents use the login of the person who runs the app.node-pty:add driver-ptyexplains the npm 11 / pnpm warning about its install script (the prebuilt binaries work without it; the exact approve and rebuild commands are printed and documented).todoprints the URL it serves (/todo),create-acrylblendsprints itsNext:block once, andinit --desktopsays what the desktop shell added and why.npx acrylblends@latest upgradeis the documented form, with the reason: an app's own older copy shadows a barenpx acrylblendsand has noupgradeverb (it prints its help and exits 0, or says "unknown command" and exits 2).- Node 22.19 or newer is checked first. The two commands and every new app (
scripts/check-node.cjs, run beforedev,start,testandcheck) stop an older Node with "Node 22.19+ needed, you have 18.x: install it from nodejs.org or with nvm". - Linux, Ubuntu 23.10 and later:
npm run desktopno longer aborts at Chromium's sandbox. Wherekernel.apparmor_restrict_unprivileged_userns=1, and only there, it starts Electron with--no-sandboxand prints one line saying why; the desktop page says how to keep the sandbox with an AppArmor profile.
Things to know
- A registry starter is a whole app, about 2.6 MB with its plugin copies, so a registry grows by that much per entry.
acrylblends upgradestill does not edit an app's ownsrc/files (the second pass is planned).