acrylblends 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.d.ts +15 -1
  2. package/dist/index.js +13 -7
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/api/summary.md +1 -1
  5. package/docs/framework/guides/bring-your-own-tool.md +2 -0
  6. package/docs/framework/guides/production.md +20 -0
  7. package/docs/framework/workshop/README.md +177 -274
  8. package/docs/framework/workshop/facilitator.md +107 -84
  9. package/lib/{audit-Dw-siYTp.d.ts → audit-BFpULBCj.d.ts} +15 -3
  10. package/lib/cordis/door.d.ts +3 -1
  11. package/lib/cordis/door.js +11 -1
  12. package/lib/cordis/state.d.ts +3 -2
  13. package/lib/cordis/state.js +370 -6
  14. package/lib/db/conformance.d.ts +6 -2
  15. package/lib/db/conformance.js +14 -2
  16. package/lib/door/host.d.ts +1 -1
  17. package/lib/door/host.js +1 -1
  18. package/lib/door/page.d.ts +10 -0
  19. package/lib/door/page.js +29 -7
  20. package/lib/kv/conformance.d.ts +6 -2
  21. package/lib/kv/conformance.js +14 -2
  22. package/lib/{snapshot-text-CBi3EMnj.js → snapshot-text-BwM6T2nG.js} +37 -5
  23. package/lib/vitest-binding-CctNu3jT.d.ts +15 -0
  24. package/lib/vitest-binding-CrH7rKg2.js +17 -0
  25. package/package.json +1 -5
  26. package/plugins/acryl-door/src/host/channel.ts +43 -5
  27. package/plugins/acryl-door/src/host/door.ts +5 -0
  28. package/plugins/acryl-door/src/host/page-routes.ts +1 -1
  29. package/plugins/acryl-door/src/host/page-socket.ts +1 -1
  30. package/plugins/acryl-door/src/page/channel.ts +8 -0
  31. package/plugins/acryl-door/src/page/driver/driver.ts +17 -3
  32. package/plugins/acryl-door/src/page/index.ts +6 -1
  33. package/plugins/acryl-door/src/page/indicator.ts +14 -6
  34. package/plugins/acryl-state/plugin.json +4 -3
  35. package/plugins/acryl-state/src/client-source.ts +244 -0
  36. package/plugins/acryl-state/src/service.ts +33 -6
  37. package/plugins/acryl-state/src/socket.ts +96 -0
  38. package/plugins/agent-panel/client/main.ts +105 -55
  39. package/plugins/agent-panel/index.ts +42 -5
  40. package/plugins/desktop/main.ts +5 -1
  41. package/plugins/instance/index.ts +6 -3
  42. package/templates/agent/AGENTS.md +3 -1
  43. package/templates/agent/src/routes.ts +38 -5
  44. package/templates/agent/test/app.test.ts +38 -2
  45. package/templates/chat/src/main.ts +1 -6
  46. package/templates/nodejs/src/main.ts +1 -6
  47. package/templates/notes/src/main.ts +1 -6
@@ -1,191 +1,164 @@
1
- # From nothing to your own app: the ACRYL Blends workshop
1
+ # An app that builds and drives itself: the ACRYL Blends workshop
2
2
 
3
- Ninety minutes, starting from an empty folder. **Acryl** is a small web framework built on Web Standards: a handler takes a `Request` and returns a
4
- `Response`, exactly as in the browser. **ACRYL Blends** puts that framework inside an app you own: a folder whose `blend.yaml` lists its plugins, whose
5
- `plugins/` folder holds their code, and which runs in a browser or in a desktop window. A coding agent can change the app while it runs. By the end you
6
- will have built an app from the blank template, added a feature to it with an agent, opened it as a desktop window, upgraded it, and published it as a
7
- starter other people can pull from the registry.
3
+ Ninety minutes, from an empty folder to an app whose interface a coding agent built from inside it, then drove the way a person would: it opens the
4
+ sidebar, ticks a card, changes a setting. The same app then runs in a desktop window, where the agent drives that too, and you publish it as a starter
5
+ other people can pull. **Acryl** is the small web framework underneath (a handler takes a `Request` and returns a `Response`); **ACRYL Blends** is the app
6
+ around it: a folder of Cordis plugins with a `blend.yaml`, running on your machine, with an agent living inside its panel.
8
7
 
9
- Every command below is copy-pasteable and was run on the published packages. Each step says how long it takes; the checkpoints tell you whether you
10
- are on time.
8
+ Two things make this workshop different from clicking through a tutorial: you write very little code (the agent writes the interface, you direct and
9
+ edit it), and the agent is not a sidebar chat about the code, it is a hand on the app.
11
10
 
12
- ## What you need (10 minutes)
11
+ This guide is for **acrylblends 1.0.10 or newer**; `npm create acrylblends@latest` gives you that.
13
12
 
14
- | | macOS | Windows | Linux |
15
- | --- | --- | --- | --- |
16
- | Node.js 22.19 or newer (24 LTS or 26 tested) | `node -v` | `node -v` | `node -v` |
17
- | npm, which comes with Node | `npm -v` | `npm -v` | `npm -v` |
18
- | git | `git --version` | `git --version` | `git --version` |
19
- | a coding agent, logged in | `codex login status` or `claude --version` | same | same |
20
- | a GitHub account and the `gh` CLI, logged in | `gh auth status` | same | same |
13
+ ## 0. Before you start (10 minutes)
21
14
 
22
- Install what is missing: Node from <https://nodejs.org> (the LTS), git from <https://git-scm.com>, the GitHub CLI (`brew install gh`, `winget
23
- install GitHub.cli`, or <https://cli.github.com>), and one agent with `npm i -g @openai/codex` (then `codex login` once) or Claude Code. Run
24
- `gh auth login` once, in any repository: step 10 publishes your app with a pull request. No prior knowledge of ACRYL, Cordis or Blends is needed;
25
- every new word is explained where it appears.
26
-
27
- Make a folder and open a terminal in it:
15
+ Run this first. It checks everything below and prints one line per check, with the exact command for anything missing:
28
16
 
29
17
  ```sh
30
- mkdir ~/acryl-workshop && cd ~/acryl-workshop
18
+ npx acrylblends@latest preflight
31
19
  ```
32
20
 
33
- ## The path
21
+ It ends with `Ready for the workshop.`, or with how many lines say `FIX`: run each `FIX` line, then the preflight again. `NOTE` lines are things the
22
+ first blocks do without (`gh` is only needed in block 5).
34
23
 
35
- | Minutes | Step | What you get |
36
- | --- | --- | --- |
37
- | 0-10 | Before you start | tools installed and checked |
38
- | 10-15 | 1. Create and run | a blank app answering in a browser |
39
- | 15-22 | 2. Change one thing | a route of your own, live, without a restart |
40
- | 22-30 | 3. Tests and the typecheck | the app proves itself |
41
- | 30-38 | 4. Data: JSON, then SQLite *(optional)* | a counter kept in a file, then in SQL |
42
- | 38-45 | 5. A desktop window | the same app as a window |
43
- | 45-53 | 6. Pull a starter from the registry | someone else's app in your folder |
44
- | 53-60 | 7. Create from a template | the six starters, one table, one run |
45
- | 60-75 | 8. An agent builds a feature | your app changed by the agent, still running |
46
- | 75-82 | 9. Upgrade | the app moved to the current framework |
47
- | 82-90 | 10. Publish it | your app as a registry starter |
48
-
49
- > **Checkpoint 15 minutes:** your app answers in a browser. Without that, read the "Stuck?" line of step 1.
50
- > **Checkpoint 45 minutes:** your app is open in a desktop window. Behind? Step 4 is the optional one; skip it and carry on.
51
- > **Checkpoint 75 minutes:** the agent's page is live. Still waiting? Give it five more minutes, then skip step 9.
52
-
53
- ## 1. Create and run (5 minutes)
24
+ | | macOS | Windows | Linux |
25
+ | --- | --- | --- | --- |
26
+ | Node.js 22.19 or newer (24 and 26 work) | `node -v` | `node -v` | `node -v` |
27
+ | git | `git --version` | `git --version` | `git --version` |
28
+ | one coding agent, logged in | `codex login status` or `claude --version` or `pi --version` | same | same |
29
+ | GitHub account and `gh`, for the last block | `gh auth status` | same | same |
54
30
 
55
- ```sh
56
- npm create acrylblends@latest my-hero blank
57
- cd my-hero
58
- npm install
59
- npm run dev
60
- ```
31
+ Install what is missing: Node from <https://nodejs.org> (the LTS), git from <https://git-scm.com>, the GitHub CLI from <https://cli.github.com> (`gh auth
32
+ login` once), and one agent:
61
33
 
62
- You should see:
34
+ - **Codex**: `npm i -g @openai/codex`, then `codex login`. If a run answers `model_not_found`, your account does not have the default model: pick one
35
+ your account supports (`/model` inside codex, or `model = "..."` in `~/.codex/config.toml`).
36
+ - **Claude Code**: <https://claude.com/claude-code>, then run `claude` once to log in.
37
+ - **pi with DeepSeek**: `npm i -g @earendil-works/pi-coding-agent`, and set your DeepSeek key in the terminal you will start the app from (the panel
38
+ starts pi from the app, so pi sees that terminal's environment). The preflight checks the key: `PASS agent pi (DeepSeek key found)`.
63
39
 
64
- ```
65
- created my-hero (blank) in /Users/you/acryl-workshop/my-hero: … files, plugins instance, acryl-router, acryl-node-server, manifest, save
40
+ | macOS and Linux | Windows PowerShell | Windows cmd.exe |
41
+ | --- | --- | --- |
42
+ | `export DEEPSEEK_API_KEY=sk-...` | `$env:DEEPSEEK_API_KEY="sk-..."` | `set DEEPSEEK_API_KEY=sk-...` |
66
43
 
67
- Next:
68
- cd my-hero
69
- npm install
70
- npm run dev
71
- ```
44
+ No prior knowledge of ACRYL, Cordis or Blends is needed; every new word is explained where it appears.
72
45
 
73
- then, from `npm run dev`:
46
+ Make the workshop folder:
74
47
 
75
- ```
76
- my-hero listening on http://127.0.0.1:3080 (data in /Users/you/acryl-workshop/my-hero/.acrylblends)
77
- ```
48
+ | macOS and Linux | Windows PowerShell | Windows cmd.exe |
49
+ | --- | --- | --- |
50
+ | `mkdir ~/acryl-workshop && cd ~/acryl-workshop` | `mkdir ~/acryl-workshop` then `cd ~/acryl-workshop` | `mkdir %USERPROFILE%\acryl-workshop` then `cd %USERPROFILE%\acryl-workshop` |
78
51
 
79
- Open that address: `{"app":"my-hero","ok":true}`. `/health` answers `{"ok":true}` too. Leave the terminal running.
52
+ **On Windows.** Windows PowerShell 5.1 (the one Windows ships) has no `&&`: where this guide joins two commands with `&&`, type them as two lines, or join
53
+ them with `;`. PowerShell 7 (`pwsh`) accepts `&&`. In cmd.exe, `Ctrl-C` asks `Terminate batch job (Y/N)?`: answer `Y`.
80
54
 
81
- **Stuck?** A busy port gets the next free one: use the address the app printed. The first `npm create` downloads the initializer, so give it a few
82
- seconds.
55
+ **The agent setting that matters all day.** The app's agent is configured to act rather than to describe: when you ask for something, it writes the
56
+ files, installs the plugin and clicks through the result itself. If your agent stops to ask "shall I proceed?", answer "do it now" and it continues; that
57
+ one sentence is the difference between watching and building.
83
58
 
84
- ## 2. Change one thing (7 minutes)
59
+ ## The path
85
60
 
86
- Open `src/routes.ts`. It builds an `Acryl` app and mounts it on the router. Add one line to that chain, just above `.get('/health', …)`, and keep the
87
- indentation of the lines around it:
61
+ | Minutes | Block | What you get |
62
+ | --- | --- | --- |
63
+ | 0-10 | 0. Before you start, 1. The core in one breath | a checked machine, and an app with a route of your own, answering in a browser |
64
+ | 10-40 | 2. The agent builds the interface | a board page with cards, a sidebar and live counts, written by the agent from inside |
65
+ | 40-60 | 3. The agent drives the app | the agent opening the sidebar and ticking a card while you watch |
66
+ | 60-75 | 4. The same app in a window | the app and its agent in a desktop window, driven there too, with its menu and settings |
67
+ | 75-90 | 5. Deploy it, then publish it | the app running as a server, then listed in the registry |
88
68
 
89
- ```ts
90
- .get('/hello', (c) => c.json({ hello: 'workshop' }))
69
+ > **Checkpoint 10 minutes:** your route answers in the browser.
70
+ > **Checkpoint 40 minutes:** the agent's board is on screen.
71
+ > **Checkpoint 60 minutes:** the agent has clicked through the page by itself.
72
+ > **Checkpoint 75 minutes:** the same app is in a desktop window.
73
+
74
+ ## 1. The core in one breath (10 minutes)
75
+
76
+ ```sh
77
+ npm create acrylblends@latest my-app agent
78
+ cd my-app
79
+ npm install
80
+ npm run dev
91
81
  ```
92
82
 
93
- Save. The terminal prints `Change detected … Restarting 'src/main.ts'`, and `http://127.0.0.1:3080/hello` answers `{"hello":"workshop"}`. No restart
94
- command, no build step: the app watches its own source.
83
+ Open the address it prints. You get a small app with a button in the corner, "Ask the agent": it opens the panel, the agent that will build and drive
84
+ this app. It is the `agent` template.
95
85
 
96
- **Stuck?** No message means the file is not where the app watches; the message names the path it saw. A red error names the file and line.
86
+ ![The app's first page with the agent panel open on the right; the page follows the system theme, dark here](/docs-assets/workshop/s1-first-page.webp)
97
87
 
98
- ## 3. Tests and the typecheck (8 minutes)
88
+ The page follows your system's light or dark setting, so on a dark-mode machine it is dark as above. For the next two minutes forget the panel: an
89
+ Acryl app is a function from a request to a response.
99
90
 
100
- Stop the server with `Ctrl-C`, then:
91
+ Open `src/routes.ts` and add one line to the chain, just above `.get('/health', …)`, keeping the indentation of the lines around it:
101
92
 
102
- ```sh
103
- npm test
104
- npm run check
93
+ ```ts
94
+ .get('/hello', (c) => c.json({ hello: 'workshop' }))
105
95
  ```
106
96
 
107
- `npm test` ends with `ℹ pass 5`, `ℹ fail 0`. `npm run check` prints nothing when the types are clean.
97
+ Save: the terminal prints `Restarting 'src/main.ts'` (Node 24 and 26 print `Change detected …` on the line before it; Node 22 prints only the
98
+ `Restarting` line), and `/hello` answers `{"hello":"workshop"}`. Stop the server (`Ctrl-C`; in cmd.exe answer `Y`), run `npm test` and `npm run check`:
99
+ both pass (`npm test` reports 14 passing tests). That is the whole core: a route, a test, a typecheck.
108
100
 
109
- Now add a test for your route in `test/`: copy the style of the existing test file, request `/hello` and expect `{"hello":"workshop"}`. `npm test`
110
- again; a failure names the file and line.
101
+ **Stuck?** A busy port means the app printed the next free one; use that address. `npm test` failing on your line names the file and the type.
111
102
 
112
- **Stuck?** `npm run check` complaining about your new line is the typechecker doing its job; the message says which type it expected.
103
+ ## 2. The agent builds the interface (30 minutes)
113
104
 
114
- ## 4. Data: JSON, then SQLite (8 minutes, optional)
115
-
116
- An app keeps data through a **port**: a small interface a plugin provides. The smallest is `kv`, a JSON file in the app's own folder.
105
+ Start the app (on pi, in the terminal where `DEEPSEEK_API_KEY` is set):
117
106
 
118
107
  ```sh
119
- npx acrylblends add acryl-kv-json
120
- npm install
108
+ npm run dev
121
109
  ```
122
110
 
123
- `add` copies the plugin into `plugins/` and prints how to mount it. Three edits:
111
+ Open the address it prints and click "Ask the agent". Ask it, in one message:
124
112
 
125
- 1. `blend.yaml`, before `- id: server`: add the provider row (the row's id is yours to pick; the name is the plugin):
113
+ > Do it now, without asking for approval. Build a board page at /board: three columns (backlog, doing, done) with cards that have a checkbox to mark
114
+ > them done, a sidebar with filters (all, mine, done) and a count on each filter, a button that opens and closes the sidebar, and a header button
115
+ > "New card" that adds one. Use the app's own state so the counts update live. Build it as a plugin in plugins/, install it into the running app, then
116
+ > go to /board yourself and check it looks right, and tell me what you saw.
126
117
 
127
- ```yaml
128
- - id: kv
129
- name: acryl-kv-json
130
- ```
118
+ What happens next is the workshop: the agent reads `AGENTS.md` and `docs/framework` (both are in your app), writes a Cordis plugin under `plugins/`,
119
+ installs it into the running app as an extension (`.acryl/bin/acryl plugin add plugins/board`), and goes to `/board` through the door to look at what it
120
+ built. The panel shows `Working... m:ss` while it runs; with pi the build took about six minutes. The panel is on every page of the app, so when the
121
+ agent moves the page to `/board`, the panel comes along and keeps following the run.
131
122
 
132
- 2. `src/app.ts`: import the plugin and register it under that name:
123
+ ![The panel while the agent builds: the request, then Working... 0:04 below it](/docs-assets/workshop/s2-panel-building.webp)
133
124
 
134
- ```ts
135
- import * as kvJson from '../plugins/acryl-kv-json/src/index.ts'
136
- ```
125
+ ![The board the agent built: three columns with cards, and a sidebar with filters and counts](/docs-assets/workshop/s3-board.webp)
137
126
 
138
- ```ts
139
- export const registry = { 'acryl-router': router, 'acryl-node-server': nodeServer, 'acryl-kv-json': kvJson, routes }
140
- ```
127
+ Then make it yours: open the plugin it wrote, change a colour or a label, save, and watch the app reload. The code is in your folder and belongs to you;
128
+ the agent wrote it, you own it. Every agent builds a slightly different board: yours may need a title typed before "New card" adds one, or keep the
129
+ sidebar always open.
141
130
 
142
- 3. `src/routes.ts`: declare the port and count visits in your route:
131
+ **Stuck?** If the agent describes the plan and stops, tell it "do it now, without asking for approval". An app made with an older release gets the
132
+ current panel and fixes with `npx acrylblends@latest upgrade`.
143
133
 
144
- ```ts
145
- import type {} from '../plugins/acryl-kv/src/cordis.ts'
146
- ```
134
+ ## 3. The agent drives the app (20 minutes)
147
135
 
148
- ```ts
149
- inject: ['router', 'kv'],
150
- ```
136
+ The same agent, in the same panel, now uses the door: a channel into the running page with `look` (a snapshot of what is on screen, with refs) and
137
+ `act` (click, type, select, press, scroll, wait, and `navigate` or `reload` the page).
151
138
 
152
- ```ts
153
- .get('/hello', async (c) => {
154
- const visits = await ctx.kv.update('visits', (n) => (n ?? 0) + 1)
155
- return c.json({ hello: 'workshop', visits })
156
- })
157
- ```
139
+ Ask it:
158
140
 
159
- `npm run dev` again and open `/hello` twice: `visits` goes 1, 2. The file is `my-hero/.acrylblends/kv.json`.
141
+ > Do it now, without asking for approval. On /board, use the door to open the sidebar, tick one card with its checkbox, set the filter to done, then
142
+ > look again and tell me what the counts say.
160
143
 
161
- The relational port is `db`, with numbered SQL migrations:
144
+ Watch the page: the card ticks, the filter changes, the counts move. While each action runs, a bar at the bottom of the page says `Agent is driving:
145
+ clicking` (or looking, typing) with a Stop button; it shows only for that moment, so most of the time you see the page change and the panel's `Working...`
146
+ line. The panel's own Stop button ends the whole run. Every action is a line in `.acryl/audit/` afterwards. With pi the drive was six door calls in
147
+ under two minutes, most of it the model thinking.
162
148
 
163
- ```sh
164
- npx acrylblends add acryl-db-sqlite
165
- mkdir migrations
166
- printf 'CREATE TABLE visits (id INTEGER PRIMARY KEY, at TEXT NOT NULL);\n' > migrations/001-visits.sql
167
- npm install
168
- ```
149
+ Then ask it to check its own work in the way a person would:
169
150
 
170
- A row in `blend.yaml` (`- id: db`, `name: acryl-db-sqlite`), then the migration folder in `src/app.ts`, where the app is composed: the path is set
171
- there, not in the manifest, because a relative path in the manifest has no folder to resolve against.
151
+ > Do it now, without asking for approval. Click "New card" twice, look at the board, and tell me whether the counts agree.
172
152
 
173
- ```ts
174
- import * as dbSqlite from '../plugins/acryl-db-sqlite/src/index.ts'
175
- // in the registry: 'acryl-db-sqlite': dbSqlite
176
- const projectRoot = fileURLToPath(new URL('..', import.meta.url))
177
- // in the compose options next to server:
178
- db: { migrations: join(projectRoot, 'migrations') },
179
- ```
153
+ If your board needs a title before "New card" adds a card, the agent says so, and the counts agree because nothing was added; ask it to type a title
154
+ first if you want to see them move.
180
155
 
181
- Then `inject: ['router', 'kv', 'db']` with `import type {} from '../plugins/acryl-db/src/cordis.ts'`, and in the route
182
- `await ctx.db.run('INSERT INTO visits (at) VALUES (?)', new Date().toISOString())` before the reply. The database is `my-hero/.acrylblends/data.sqlite`,
183
- and the migrations run at start, in order.
156
+ **Stuck?** If the agent says it cannot see a page, reload the app's tab: the page connects to the door when it loads.
184
157
 
185
- **Stuck?** `migrations "migrations" is relative, but this app has no file base URL` means the app has `acrylblends` older than 1.0.3, where a relative path in `blend.yaml` was not read
186
- from the app folder; move it into `src/app.ts` as above, or update (`npx acrylblends@latest upgrade`). "no such table" means the file is not in `migrations/` at the app root.
158
+ ## 4. The same app in a desktop window (15 minutes)
187
159
 
188
- ## 5. A desktop window (7 minutes)
160
+ **First close the browser tab of the app.** While a browser tab of the app is open, the agent's page actions can go to that tab instead of the
161
+ window (the door picks a page when the agent does not name a window), and you would watch an unchanged window. Keep `npm run dev` running.
189
162
 
190
163
  ```sh
191
164
  npx acrylblends add desktop
@@ -193,189 +166,119 @@ npm install
193
166
  npm run desktop
194
167
  ```
195
168
 
196
- A window opens, showing the same routes and the same data. The app's own code never imports Electron; every Electron call lives in `plugins/desktop`,
197
- and the window's browser data stays inside `my-hero/.acrylblends/electron`. Close the window when you are done (`Cmd-Q`, `Alt-F4`).
198
-
199
- **Stuck?** The first `npm run desktop` downloads Electron (about 200 MB). No display (a bare server)? Skip this step; nothing later needs it.
169
+ The first `npm run desktop` downloads Electron (about 200 MB), then the window attaches to the running server and says so:
200
170
 
201
- ## 6. Pull a starter from the registry (8 minutes)
202
-
203
- The registry is where people publish starters: <https://acrylblends.github.io/blends> (the starters written for this framework say
204
- `runtime: acrylblends`; older ACRYL app starters are refused with a line saying so). Pull one by id, straight into a new folder:
205
-
206
- ```sh
207
- cd ~/acryl-workshop
208
- npm create acrylblends@latest from-the-registry -- --from <starter-id>
209
- cd from-the-registry
210
- npm install
211
- npm run dev
171
+ ```text
172
+ desktop: attached to the app already running from this folder at http://127.0.0.1:3288 (its server keeps running when the window closes)
173
+ desktop: windows and menu are available to the door (acryl window list, acryl menu list)
212
174
  ```
213
175
 
214
- The same command takes a git repository or a folder: `--from github:you/starter` or `--from ../a-template`. You get a complete app someone else
215
- published, with its plugins, its dependencies and its `blend.yaml`, in your folder and yours to change. Nothing is installed for you: the starter's
216
- `Next:` block asks for the same `npm install` as step 1.
176
+ A window opens on the same app, from the same server: your board, your sidebar, your panel. First drive the page in the window the way you did in block
177
+ 3: open the panel there and ask the agent to open the sidebar and change a filter. Look and act work the same way through the window.
217
178
 
218
- For the workshop's own registry, name it:
179
+ ![The agent driving the board inside the desktop window: the Done filter it just set, and the panel's Working... line](/docs-assets/workshop/s4-agent-drives-window.webp)
219
180
 
220
- ```sh
221
- npm create acrylblends@latest from-the-workshop -- --from <id> --registry github:acryldev/workshop-2026-11-20
222
- ```
181
+ Then hand the agent the window itself, not only the page inside it. Ask:
223
182
 
224
- Starters for this workshop: `acrylblends.notes-desktop` (notes with a desktop window), `acrylblends.chat`, `acrylblends.agent`.
183
+ > List the windows that are open, open a second one with the board in it, open the app's settings from the menu, and switch the appearance to dark.
225
184
 
226
- **Stuck?** The `--` before `--from` is npm's, not ours: without it npm swallows the flag. A refused id prints its reason first (`error: acryl.gtd is an
227
- ACRYL app starter: open it in the ACRYL app`) and then npm's own error block; read the first lines, not the last.
185
+ The agent lists the windows, opens a second one, clicks the menu's Settings item, and changes the app's appearance on the settings page; each shows on
186
+ screen as it happens. The Settings item is in the application menu on macOS (`my-app > Settings...`, `Cmd+,`) and the first item of the File menu on
187
+ Windows and Linux (`Ctrl+,`); it opens `/settings` in a window of its own.
228
188
 
229
- ## 7. Create from a template (7 minutes)
189
+ The appearance is saved for the whole app. The app's own pages (`/`, `/settings`) use it; a window that is already open picks it up when it reloads, so
190
+ ask the agent "reload window 1 and look at it" to see the change there. On a machine already in dark mode, dark looks like before: pick Light to see
191
+ it change. A page the agent built (the board) follows the system theme unless you ask the agent to follow the app's appearance too.
230
192
 
231
- Every app begins from one of six templates, the same list in `npm create acrylblends` and `acrylblends init`:
193
+ ![Two desktop windows: window 1 on the app's page in the dark appearance, and the Settings window showing Saved: Dark](/docs-assets/workshop/s5-two-windows.webp)
232
194
 
233
- | Template | What you get |
234
- | --- | --- |
235
- | `blank` | the router, the server and one route: what you just built |
236
- | `nodejs` | a page rendered with JSX and a client component, without a bundler |
237
- | `notes` | a multi-user notes app on the `db` port: sign-up, sessions, migrations, search, a typed client |
238
- | `chat` | an answer streamed over server-sent events from an OpenAI-compatible API, history in SQLite |
239
- | `todo` | a to-do list on the router and live state |
240
- | `agent` | the blank app with an offline agent that writes and installs plugins into the running app |
195
+ **Tip.** `navigate` answers with the status: a `404` means the app does not serve that path, and you can navigate straight back to one it does.
241
196
 
242
- Try the one with a database and accounts, in a window:
197
+ Close the windows when you are done. The last window does not close through the door without `--force` (closing it ends the desktop app), so close it
198
+ yourself or tell the agent it may.
243
199
 
244
- ```sh
245
- cd ~/acryl-workshop
246
- npm create acrylblends@latest notes-app notes
247
- cd notes-app && npm install
248
- npx acrylblends add desktop && npm install && npm run desktop
249
- ```
250
-
251
- Sign up, add a note, close the window; run `npm run dev` and the note is still there, because it lives in `notes-app/.acrylblends/data.sqlite`.
200
+ **Stuck?** On Ubuntu 23.10+ the launch starts with `--no-sandbox` and says why; the AppArmor route is in
201
+ `docs/framework/getting-started/desktop.md`. If the agent reports success but the window did not change, a browser tab of the app is still open: close
202
+ it and ask again.
252
203
 
253
- **Stuck?** The sign-in page is at `/login`; the root redirects there. Delete the folder when you are done with it.
204
+ ## 5. Deploy it, then publish it (15 minutes)
254
205
 
255
- ## 8. An agent builds a feature (15 minutes)
256
-
257
- The agent panel turns a coding agent you already use into a chat in the corner of the page. It needs the plugins the `agent` template ships (its model,
258
- tools and memory), so this step builds a second, small app from that template; it is the app you publish in step 10.
206
+ **Deploy the app without its agent.** The agent, the panel and the terminal driver are development tools: with `NODE_ENV=production` they start
207
+ nothing, while the app's own routes, plugins, state and database run as always. That is the app you put on a server. Stop `npm run dev` first.
259
208
 
260
209
  ```sh
261
- cd ~/acryl-workshop
262
- npm create acrylblends@latest agent-app agent
263
- cd agent-app
264
- npm install
265
- npx acrylblends add agent-panel driver-pty
266
- npx acrylblends add desktop
267
- npm install
268
- npm run dev
210
+ NODE_ENV=production npm start
269
211
  ```
270
212
 
271
- `add agent-panel` adds the plugin rows the panel needs and registers it for you, and prints what it changed. (On a `blank` app it refuses and says
272
- which rows are missing: that is why this step uses the `agent` template.) Open the page, click the panel button in the corner, pick `codex` (or
273
- `claude`, or `pi` when pi has its DeepSeek login in your home folder), and ask:
213
+ (The command is the app's own `start` script; on Windows, `set NODE_ENV=production&& npm start` in cmd, or `$env:NODE_ENV="production"; npm start` in
214
+ PowerShell.) It prints `extensions (read-only): board`: the board the agent installed runs, and nothing can install or remove a plugin.
274
215
 
275
- > Add a page at /board that lists the app's tasks, newest first, and a test for it.
216
+ That is the same app on any Node host: copy the folder (or clone the repository the app was saved to), run `npm install --omit=dev`, and start it with
217
+ `NODE_ENV=production`. On its own the app listens on `127.0.0.1` at a port chosen from its folder; a server must say where to listen. The app reads
218
+ `ACRYLBLENDS_HOST` and `ACRYLBLENDS_PORT` (not `PORT`):
276
219
 
277
- The agent reads `AGENTS.md`, reads `docs/framework` (which is in your app), edits your files, runs your tests, and the app reloads itself. It takes a
278
- few minutes; the panel shows each step, and has a stop button. Open `/board` when it is done.
279
-
280
- A terminal agent works too: choose `pty:codex` (or `pty:claude`, `pty:pi`) for its normal interactive session inside the app folder, where it can use
281
- the door (`.acryl/bin/acryl`) to look at the page it built.
282
-
283
- **Stuck?** The panel uses your own agent account: `codex login` (or `claude`) in a terminal first. Your own agent settings travel with it: skills or hooks of your own can make it stop and ask for approval instead of acting, so write the task as "do it now, without asking for approval", or start the agent from a clean configuration (a fresh `CODEX_HOME`, or no personal skills). If `npm install` warns that npm blocked
284
- `node-pty`'s install script, ignore it: prebuilt binaries ship. Only if the panel lists no `pty:` terminal, run `npm approve-scripts node-pty` and
285
- `npm rebuild node-pty` (the `add` output says the same).
220
+ | macOS and Linux | Windows PowerShell | Windows cmd.exe |
221
+ | --- | --- | --- |
222
+ | `ACRYLBLENDS_HOST=0.0.0.0 ACRYLBLENDS_PORT=8080 NODE_ENV=production npm start` | `$env:ACRYLBLENDS_HOST="0.0.0.0"; $env:ACRYLBLENDS_PORT="8080"; $env:NODE_ENV="production"; npm start` | `set ACRYLBLENDS_HOST=0.0.0.0&& set ACRYLBLENDS_PORT=8080&& set NODE_ENV=production&& npm start` |
286
223
 
287
- ## 9. Upgrade (7 minutes)
224
+ Nothing in the app assumes ACRYL's own infrastructure: it is one Node process, one port, and its data in `.acrylblends/` next to it.
288
225
 
289
- Your app carries its framework plugins as source, so a new release moves them in place and leaves your code alone. Run the tool by its full name:
290
- **`npx acrylblends@latest`**, because an app's own copy of `acrylblends` shadows the bare command and an older copy has no `upgrade` at all.
226
+ **Publish it as a starter.** Your app already is one: `blend.yaml` names it, `plugins/` holds its code.
291
227
 
292
228
  ```sh
293
- cd ~/acryl-workshop/my-hero
294
- npx acrylblends@latest upgrade --dry-run
295
- npx acrylblends@latest upgrade
296
- npm install && npm test
229
+ cd ~/acryl-workshop
230
+ gh repo fork acryldev/workshop-2026-11-20 --clone
231
+ cd workshop-2026-11-20
232
+ node scripts/make-entry.mjs ../my-app <your-github-login>.my-app --description "the board my agent built"
233
+ git add blends && git commit -m "add <your-github-login>.my-app"
234
+ git push
235
+ gh pr create --fill --repo acryldev/workshop-2026-11-20
297
236
  ```
298
237
 
299
- The dry run lists what would be replaced; the real run replaces untouched files and stops with a diff if you edited one (move your change into a plugin
300
- of your own, or use `--force`, which backs the edited copies up first). An app made by this version is already current, so both commands answer
301
- `everything is up to date`: that is the mechanism working, and an app made by an earlier release lists what it would move. Your tests still pass
302
- afterwards.
303
-
304
- ## 10. Publish it, and the hero moment (8 minutes)
305
-
306
- The app you publish is `agent-app`, the one the agent changed in step 8 (keep `my-hero` too: it is yours, saved the same way). It is already a
307
- **starter**: `blend.yaml` names it, lists its rows, and `plugins/` holds its code. Two parts make it public: your own copy, then the registry.
308
-
309
- **Your own copy first.** `npm run save` commits the app and pushes it to its remote (it refuses a file that looks like a secret, and refuses to push a
310
- private app to a public remote). Create an empty repository on your git host, set it as `origin`, run `npm run save`.
311
-
312
- **The registry.** Publishing is a pull request to the workshop registry, `github.com/acryldev/workshop-2026-11-20`, and it is four commands.
313
-
314
- 1. Fork and clone it, then let its script build the entry from your app:
315
-
316
- ```sh
317
- cd ~/acryl-workshop
318
- gh repo fork acryldev/workshop-2026-11-20 --clone
319
- cd workshop-2026-11-20
320
- node scripts/make-entry.mjs ../agent-app <your-github-login>.agent-app --description "the app I built in the workshop"
321
- ```
238
+ (On Windows PowerShell 5.1, `cd ~/acryl-workshop` works as written; in cmd.exe use `cd %USERPROFILE%\acryl-workshop`. Type the `git add` and `git
239
+ commit` as two lines, or join them with `;`.)
322
240
 
323
- `make-entry` copies the app into `blends/<your-github-login>.agent-app/` without `node_modules`, `.git` or the app's data, marks its `blend.yaml`
324
- public and licensed under that id, and writes the `acryl.template.json` that makes it pullable. The entry is the whole app: its dependencies come with
325
- it, so whoever pulls it gets the desktop shell without adding it again. The id is lowercase, `<login>.<name>` with letters, digits and dashes.
241
+ A check runs on the pull request and merges it: your starter is listed in about two minutes, and anyone can start from it with
242
+ `npm create acrylblends@latest another-app -- --from <your-github-login>.my-app --registry github:acryldev/workshop-2026-11-20`.
326
243
 
327
- 2. Commit, push and open the pull request:
244
+ ![The workshop registry's page, listing the starters other people published](/docs-assets/workshop/s6-registry.webp)
328
245
 
329
- ```sh
330
- git add blends && git commit -m "add <your-github-login>.agent-app"
331
- git push
332
- gh pr create --fill --repo acryldev/workshop-2026-11-20
333
- ```
246
+ As an aside, for an app made before this release: `npx acrylblends@latest upgrade` moves the framework plugins it carries to the installed version,
247
+ leaving your own code alone (an app you made today answers "everything is up to date").
334
248
 
335
- 3. A check runs on the pull request, and when it passes the pull request is merged: your starter is listed, usually well under two minutes. If it
336
- refuses, it comments what to change; fix, commit, push again and it re-checks. To take an app down, open a pull request that removes the folder:
337
- `git rm -r blends/<your-github-login>.agent-app`.
249
+ **Stuck?** The registry's check comments what to change; fix, commit, push again. `gh auth status` not logged in means `gh auth login`. A production app
250
+ with no agent is correct: that is the design, not a missing piece.
338
251
 
339
- Anyone can then start from it, in the browser or in a window:
340
-
341
- ```sh
342
- npm create acrylblends@latest my-app -- --from <your-github-login>.agent-app --registry github:acryldev/workshop-2026-11-20
343
- ```
252
+ ## Browser only (WebBoxes.ai) TODO(M9)
344
253
 
345
- **Stuck?** The check's comment is the answer: read it, fix, push again. An id with a capital or a dot inside a part is refused by `make-entry`; use
346
- lowercase `<login>.<name>`. `gh: not found` means the GitHub CLI is not installed, and `gh auth status` saying not logged in means `gh auth login`.
254
+ No local install? The same app, and the same agent building and driving it, will run as a hosted workspace: open it in a browser, pick a starter, and work
255
+ in the same folders. Nothing to install, nothing to keep on your machine.
347
256
 
348
- > **The hero moment.** An app you created, with a page an agent wrote for it, running as a desktop window, saved in your own repository, and published
349
- > as a starter in the registry so the next person can pull it with one command. Along the way you built a second app from blank, a route, a test, a JSON
350
- > store and a SQLite database. Note what you did not write: the router, the server, the JSON file, the SQLite migrations, the window, the page.
257
+ The hosted workspace is milestone M9; until it is live this section is a stub. When it is, it says the address, the sign-in it needs, and the two or three
258
+ clicks that replace block 1 here - and the one-click WebBoxes.ai deploy of an app from block 5, which is the same app on their infrastructure instead of
259
+ yours.
351
260
 
352
261
  ## Where next
353
262
 
354
- - **Read the docs you already have**: every app carries `docs/framework`, the same pages as <https://acrylblends.github.io/docs>. Start with
355
- `cordis/acryl-in-a-cordis-app.md`, then `guides/data.md`, then `guides/production.md` (what runs in production, and what does not).
356
- - **Swap a provider by editing one row**: SQLite to Postgres (`npx acrylblends add acryl-db-postgres`), the browser to a window (`desktop`), one agent
357
- to another (the panel's driver list).
358
- - **Teach your agent**: `AGENTS.md` in your app is what it reads first; add your team's conventions there and the next run follows them.
263
+ - **Read the docs you already have**: every app carries `docs/framework`, the same pages as <https://acrylblends.github.io/docs>. The door's verbs are in
264
+ `agent/`, the data ports in `guides/data.md`, and `guides/production.md` says exactly what runs on a server.
265
+ - **Teach the agent your app**: `AGENTS.md` is what it reads first; add your team's conventions there and it follows them in the next run.
266
+ - **Swap a provider by editing one row**: SQLite to Postgres, the browser to a window, one agent to another in the panel's driver list.
359
267
  - **Bring your own tool**: <https://acrylblends.github.io/docs/guides/bring-your-own-tool> wraps an outside library as a provider of a port.
360
268
 
361
- ## Browser only (WebBoxes.ai) TODO(M9)
362
-
363
- No local install? The same app, and the same agent, will run as a hosted workspace: open it in a browser, pick a starter, and work in the same three
364
- folders. Nothing to install, nothing to keep on your machine.
365
-
366
- The hosted workspace is milestone M9 and is due before the workshop, so this section is a stub until it is live: write the address, the sign-in it
367
- needs, and the two or three clicks that replace steps 1-2 here, then let the local steps stand for anyone who wants the app on their own disk.
368
-
369
269
  ---
370
270
 
371
271
  ## If something goes wrong
372
272
 
373
273
  | Symptom | Do |
374
274
  | --- | --- |
275
+ | The preflight prints `FIX` lines | run each one, then `npx acrylblends@latest preflight` again |
375
276
  | `npm create` fails or hangs | check the network; the facilitator has prepared apps |
376
277
  | A port is taken | use the address the app printed |
377
- | The agent cannot log in | `codex login` / `claude` in a terminal, then retry in the panel |
378
- | You want to start over | delete your app folder and repeat step 1; nothing lives outside it |
278
+ | The agent cannot log in | `codex login` / `claude` / `pi` in a terminal, then retry in the panel |
279
+ | Codex answers `model_not_found` | pick a model your account supports: `/model` in codex, or `model = "..."` in `~/.codex/config.toml` |
280
+ | pi does not start or has no key | set `DEEPSEEK_API_KEY` in the terminal that runs `npm run dev`, restart it, and run the preflight |
281
+ | The agent plans and stops | reply "do it now"; the config asks it to act, but a model sometimes waits |
282
+ | `&&` is an error in PowerShell | Windows PowerShell 5.1: type the two commands as two lines, or join them with `;` |
283
+ | The agent says it acted, but the desktop window did not change | close the app's browser tab, then ask again |
379
284
  | Anything else | the **Stuck?** line of the step, then your facilitator |
380
-
381
- Behind at a checkpoint? Skip step 4 (data) first, then the second app in step 7. Steps 1-3, 5, 8-10 are the spine.