austere-axi 0.5.2 → 0.7.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.
package/README.md CHANGED
@@ -8,7 +8,11 @@
8
8
  [![node](https://img.shields.io/node/v/austere-axi)](https://www.npmjs.com/package/austere-axi)
9
9
  [![license](https://img.shields.io/npm/l/austere-axi)](LICENSE)
10
10
 
11
- Review pages for coding agents. The agent writes a short [OpenUI Lang](https://github.com/thesysdev/openui) program, not HTML. You open it in the browser, click on any part, and write a note. The agent gets each note with the name of the statement it points at. It edits only those statements, and the page updates in place.
11
+ Review pages for coding agents.
12
+
13
+ 1. The agent writes a short [OpenUI Lang](https://github.com/thesysdev/openui) program, not HTML.
14
+ 2. You open it in the browser, click any part, and write a note.
15
+ 3. The agent gets each note with the name of the statement it points at, edits only those statements, and the page updates in place.
12
16
 
13
17
  ![An agent writes a page, the reviewer clicks a table cell and a diagram node and sends two notes, and the page patches in place with the changed statements flashing](docs/demo.gif)
14
18
 
@@ -31,13 +35,13 @@ decide = Form("rollout", Buttons([Button("Send decision", @ToAssistant("Decision
31
35
  strategyField = FormControl("Rollout", RadioGroup("strategy", [RadioItem("Big bang", "", "bigbang"), RadioItem("Dual-write", "", "dualwrite")]))
32
36
  ```
33
37
 
34
- [`examples/session-migration.html`](examples/session-migration.html) is a full page (KPIs, diagram, table, chart, diff, decision form) in about 1,000 tokens.
38
+ A full page (KPIs, diagram, table, chart, diff, decision form) is about 1,000 tokens: [`examples/session-migration.html`](examples/session-migration.html).
35
39
 
36
- **Website:** [mandalorianbatman.github.io/austere-axi](https://mandalorianbatman.github.io/austere-axi/) has a [gallery](https://mandalorianbatman.github.io/austere-axi/gallery/) of example pages (postmortem, API review, PR summary, sprint plan) next to their source and token counts, and a [playground](https://mandalorianbatman.github.io/austere-axi/play/) where you edit OpenUI Lang and see the page with nothing installed.
40
+ **See it without installing:** the [gallery](https://mandalorianbatman.github.io/austere-axi/gallery/) shows example pages next to their source and token counts. The [playground](https://mandalorianbatman.github.io/austere-axi/play/) lets you edit OpenUI Lang and see the page live.
37
41
 
38
42
  ## Why
39
43
 
40
- The loop is the same as [lavish-axi](https://github.com/kunchenguid/lavish-axi). The difference: the agent never writes HTML, CSS or layout.
44
+ Same loop as [lavish-axi](https://github.com/kunchenguid/lavish-axi). The difference: the agent never writes HTML, CSS or layout.
41
45
 
42
46
  | | lavish-axi | austere-axi |
43
47
  |---|---|---|
@@ -47,17 +51,17 @@ The loop is the same as [lavish-axi](https://github.com/kunchenguid/lavish-axi).
47
51
  | Validation | Layout audit in the browser | `austere-axi check`, in Node, no browser |
48
52
  | Look | Different for each agent | One dark design |
49
53
 
50
- Fewer output tokens means faster pages and lower cost. From the [benchmark](docs/benchmark.md):
54
+ Fewer output tokens: faster pages, lower cost. From the [benchmark](docs/benchmark.md):
51
55
 
52
56
  <!-- bench:start -->
53
57
  Across 36 paired runs (6 review pages, 2 models), lavish-axi spent a median **3.2×** the output tokens of austere-axi to produce the same page, took **2.4×** as long and cost **2.5×** as much. Answering one reviewer note took **2.7×** the output tokens.
54
58
  <!-- bench:end -->
55
59
 
56
- The benchmark also shows where lavish-axi does better; read its caveats before quoting the numbers.
60
+ lavish-axi does better in some cases. Read the benchmark caveats before you quote the numbers.
57
61
 
58
62
  ## Install
59
63
 
60
- You need Node.js 22.18 or later.
64
+ Needs Node.js 22.18 or later.
61
65
 
62
66
  **Let your agent do it.** Install the setup skill, then ask your agent to "set up austere-axi":
63
67
 
@@ -67,35 +71,40 @@ npx skills add MandalorianBatman/austere-axi --skill austere-setup
67
71
 
68
72
  It checks Node, installs the `austere` skill, offers the optional items below, and opens a test page.
69
73
 
70
- **Or do it yourself.** Agents can run the CLI with no install:
74
+ **Or do it yourself.** Agents can run the CLI with no install (`-y` skips the npm prompt):
71
75
 
72
76
  ```sh
73
77
  npx -y austere-axi --help
74
78
  ```
75
79
 
76
- `-y` skips the npm prompt, so an agent does not stop on it. Optional:
80
+ Optional:
77
81
 
78
82
  ```sh
79
- npm install -g austere-axi # `austere-axi` command, no npx
83
+ npm install -g austere-axi # `austere-axi` command, no npx
80
84
  npx skills add MandalorianBatman/austere-axi --skill austere # tells agents when to make a page; adds /austere
81
85
  ```
82
86
 
83
- The `austere` skill is a stub that points at `--help` and `spec`, so it does not go out of date. A global install stays on its version until `npm update -g austere-axi`.
87
+ The `austere` skill only points at `--help` and `spec`, so it never goes stale. A global install stays on its version until `npm update -g austere-axi`.
84
88
 
85
- ## Install as a Claude Code plugin
89
+ ### Claude Code plugin
86
90
 
87
- The plugin bundles the `austere` skill (and `/austere`), the `austere-setup` skill and the MCP server below:
91
+ Bundles the `austere` skill (and `/austere`), the `austere-setup` skill and the MCP server:
88
92
 
89
93
  ```sh
90
94
  claude plugin marketplace add MandalorianBatman/austere-axi
91
95
  claude plugin install austere@austere-axi
92
96
  ```
93
97
 
94
- In a session: `/plugin marketplace add MandalorianBatman/austere-axi`, then `/plugin install austere@austere-axi`. The MCP server runs `npx -y austere-axi mcp`, so it needs Node 22.18 or later and npm access. `claude plugin update austere` picks up new skill text; the CLI comes from npm each time.
98
+ In a session: `/plugin marketplace add MandalorianBatman/austere-axi`, then `/plugin install austere@austere-axi`. The MCP server runs `npx -y austere-axi mcp` (needs Node 22.18+ and npm access). `claude plugin update austere` updates the skill text; the CLI comes from npm each time.
99
+
100
+ ### MCP
95
101
 
96
- ## Use with MCP
102
+ `austere-axi mcp` is an MCP server on stdio. Tools: `spec`, `check`, `open`, `poll`, `reply`, `patch`, `end`. Each one runs the CLI command of the same name, so the loop is the same as on the command line.
97
103
 
98
- `austere-axi mcp` is an MCP server on stdio. Its tools are `spec`, `check`, `open` (validate and open the review), `poll`, `reply`, `patch` and `end`. Each runs the CLI command of the same name and returns its output, so the review loop is the same as on the command line. `poll` returns `status: timeout` after 50 s by default, because many clients cancel longer tool calls; the agent polls again and nothing is lost. Pass `timeout_ms: 0` to wait until feedback arrives when your client allows long calls. `poll` and `reply` take the `cursor` a poll returned, as `--cursor` does.
104
+ - `poll` returns `status: timeout` after 50 s by default, because many clients cancel longer calls. The agent polls again; nothing is lost. `timeout_ms: 0` waits until feedback arrives.
105
+ - `poll` and `reply` take the `cursor` a poll returned, like `--cursor`.
106
+ - Pass absolute file paths. The server resolves relative paths against its own working directory, which depends on the client.
107
+ - No screen? Set `AUSTERE_AXI_NO_OPEN=1` in the server's `env` so `open` prints the link instead. Other [environment variables](#environment) work the same way.
99
108
 
100
109
  **Claude Desktop** (`claude_desktop_config.json`) and **Cursor** (`~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project):
101
110
 
@@ -110,7 +119,7 @@ In a session: `/plugin marketplace add MandalorianBatman/austere-axi`, then `/pl
110
119
  }
111
120
  ```
112
121
 
113
- **Codex** (`~/.codex/config.toml`):
122
+ **Codex** (`~/.codex/config.toml`), or `codex mcp add austere -- npx -y austere-axi mcp`:
114
123
 
115
124
  ```toml
116
125
  [mcp_servers.austere]
@@ -118,8 +127,6 @@ command = "npx"
118
127
  args = ["-y", "austere-axi", "mcp"]
119
128
  ```
120
129
 
121
- Or `codex mcp add austere -- npx -y austere-axi mcp`. On a machine with no screen, add `AUSTERE_AXI_NO_OPEN=1` to the server's `env` so `open` prints the link instead of launching a browser. The other [environment variables](#environment) work the same way. Pass absolute file paths: the server resolves relative ones against its own working directory, which depends on the client.
122
-
123
130
  ## Quick start
124
131
 
125
132
  ```sh
@@ -128,7 +135,7 @@ npx -y austere-axi plan.html # validate and open the page for review
128
135
  npx -y austere-axi poll plan.html # wait until the reviewer presses Send
129
136
  ```
130
137
 
131
- `--help` prints the file skeleton and the full loop.
138
+ `--help` prints the file skeleton and the loop (about 900 tokens); `help all` adds the rarer commands and the environment. The agent does not look at the page itself: the reviewer judges how it looks, and render failures come back through `poll`.
132
139
 
133
140
  ## How it works
134
141
 
@@ -136,18 +143,18 @@ npx -y austere-axi poll plan.html # wait until the reviewer presses Send
136
143
  agent writes plan.html ──► austere-axi plan.html ──► local server + browser review
137
144
  │ reviewer annotates, picks options,
138
145
  │ types, presses Send
139
- agent edits statements ◄── austere-axi poll plan.html ◄─┘ (long-poll returns the batch)
146
+ agent patches changes ◄── austere-axi poll plan.html ◄─┘ (long-poll returns the batch)
140
147
  │
141
148
  └─► page updates in place and flashes the changed statements
142
149
  ```
143
150
 
144
- - **A page is a plain `.html` file**: one runtime `<script>` tag and one `<script type="text/openui">` block. Opened directly, it loads the runtime from the CDN. Under review, the server serves a local copy.
145
- - **Components**: 58 stock OpenUI components, plus `Architecture` (`Node`, `Edge`, `Group`), `Diff` and `FileTree`. `Mermaid` and nine shadcn-style components are bundled [packs](#components-and-packs).
146
- - **Architecture diagrams** lay out automatically ([dagre](https://github.com/dagrejs/dagre)). The agent never writes coordinates. Each node and edge is a click target. `lanes` puts each group in a column; `tone` colors a box or line to show risk. Use Mermaid for sequences, state machines, ER and gantt.
147
- - **Annotations** go to the smallest named statement under the click, with detail where it exists: table row and column, `file:+line` in a diff, a diagram node, or selected text. The reviewer can move a note to a parent section.
148
- - **Changes**: after each agent turn the changed statements flash, and the bar shows `N changed`. That button is a toggle: on, it keeps the last turn's changes outlined until you switch it off, and each new turn replaces the outlines with its own changes.
149
- - **Buttons and forms** send feedback. `@ToAssistant("…")` sends a message with the form values. A button with no action sends its label.
150
- - **Phone review**: when Tailscale runs, the server also listens on the tailnet IP and prints a phone link. The link carries your owner key; keep it to yourself. The review UI has a mobile layout.
151
+ - **A page is a plain `.html` file:** one runtime `<script>` tag and one `<script type="text/openui">` block. Opened directly, it loads the runtime from the CDN. Under review, the server serves a local copy.
152
+ - **Components:** 58 stock OpenUI components, plus `Architecture` (`Node`, `Edge`, `Group`), `Diff` and `FileTree`. `Mermaid` and nine shadcn-style components come as bundled [packs](#components-and-packs).
153
+ - **Diagrams lay out automatically** ([dagre](https://github.com/dagrejs/dagre)); the agent never writes coordinates. Every node and edge is a click target. `lanes` puts each group in a column; `tone` colors a box or line to show risk. Use Mermaid for sequences, state machines, ER and gantt.
154
+ - **Notes** attach to the smallest named statement under the click, with detail where it exists: table row and column, `file:+line` in a diff, a diagram node, or selected text. The reviewer can move a note to a parent section.
155
+ - **Changes flash** after each agent turn, and the bar shows `N changed`. Click it to keep the last turn's changes outlined until you click again.
156
+ - **Buttons and forms send feedback.** `@ToAssistant("…")` sends a message with the form values. A button with no action sends its label.
157
+ - **Phone review:** with Tailscale running, the server also listens on the tailnet IP and prints a phone link. That link carries your owner key; keep it private. The review UI has a mobile layout.
151
158
 
152
159
  ## CLI
153
160
 
@@ -163,7 +170,7 @@ austere-axi close <file> close the review: no more notes until reopen
163
170
  austere-axi reopen <file> reopen an ended or closed review
164
171
  austere-axi login [file] single-use link that signs a browser in as you (SSH, --no-open)
165
172
  austere-axi feedback <file> feedback no agent has answered yet [--all] [--peek]
166
- austere-axi check <file> validate the program (no browser) [--fix]
173
+ austere-axi check <file> validate the program (no browser) [--fix] [--render: headless Chrome, text report]
167
174
  austere-axi patch <file> merge statements from stdin into the program (reports dropped unreferenced ones)
168
175
  austere-axi spec [group|pack...] component reference (~5.8k tokens; groups narrow it)
169
176
  austere-axi components ... list | search | show | add | remove | new | build
@@ -173,22 +180,22 @@ austere-axi share <file> --as "<name>" invite link for a teammate on your tail
173
180
  austere-axi share list|revoke see invites, cut one off
174
181
  austere-axi stop stop the background server
175
182
  austere-axi server --persist run the server in the foreground and never idle out
176
- austere-axi mcp MCP server on stdio (see Use with MCP)
183
+ austere-axi mcp MCP server on stdio (see MCP)
177
184
  ```
178
185
 
179
- Output is compact YAML for agents. Each `poll` item has its statement and that statement's line, so the agent does not need to read the file again.
186
+ Output is compact YAML for agents. Each `poll` item has its statement and that statement's line and source, so the agent does not read the file again. Agents revise with `patch` only: they send the changed statements, never the whole file.
180
187
 
181
- Session rules:
188
+ ### Session rules
182
189
 
183
- - One poll listens on a session at a time.
184
- - Every note is written to `~/.austere-axi/hub.db` before the page is told it was sent, so a note survives a crash or restart of the server.
185
- - Feedback sent while no agent listens waits for the next poll.
186
- - Feedback stays unread until the agent acknowledges it. `poll` prints a `cursor`. Passing it back (`--cursor n` on the next `poll` or `reply`), or replying at all, acknowledges everything up to it. A `poll` without either returns the same feedback again, so feedback lost on the way to the agent (a crash, a timed-out tool call) is delivered again. `--after n` replays from any point.
187
- - Ending a review (the agent's `end`, or **End review** in the page) means the agent stopped. The reviewer can still send notes. The next `poll` returns every note the agent has not acknowledged, including ones sent before the end or released from hold after it, and reopens the review. A batch the reviewer sent with end stays final.
188
- - Only the owner's **close** (`close <file>`, or **Close review** in the page) locks the composer. `reopen <file>` or **Reopen review** unlocks it.
189
- - A review the user ended or closed does not open again unless you pass `--reopen`.
190
- - If all review tabs stay closed, `poll` returns `browser_disconnected`: 10 s after a tab closes, or 5 min after a dropped connection (for example, a locked phone). The agent stops polling but does not end the review, so notes left later are picked up by the next `poll`.
191
- - `feedback <file>` returns notes no agent has answered, also from earlier days. Reading them acknowledges them; `reply` marks them answered.
190
+ - **One listener:** one `poll` listens on a session at a time.
191
+ - **No lost notes:** every note is written to `~/.austere-axi/hub.db` before the page is told it was sent, so it survives a server crash or restart. Feedback sent while no agent listens waits for the next poll.
192
+ - **Acknowledge with the cursor:** `poll` prints a `cursor`. Passing it back (`--cursor n` on the next `poll` or `reply`), or replying at all, acknowledges everything up to it. Without it, `poll` returns the same feedback again, so a crash or timed-out tool call loses nothing. `--after n` replays from any point.
193
+ - **End is soft:** ending a review (the agent's `end`, or **End review** in the page) means the agent stopped. The reviewer can still send notes. The next `poll` returns every unacknowledged note, including ones sent before the end or released from hold after it, and reopens the review. A batch the reviewer sent together with end stays final.
194
+ - **Close is hard:** only the owner's **close** (`close <file>`, or **Close review** in the page) locks the composer. `reopen <file>` or **Reopen review** unlocks it.
195
+ - A review the user ended or closed opens again only with `--reopen`.
196
+ - **Tabs closed:** `poll` returns `browser_disconnected` 10 s after the last review tab closes, or 5 min after a dropped connection (for example, a locked phone). The review stays open, so later notes reach the next `poll`.
197
+ - `feedback <file>` returns notes no agent has answered, from any day. Reading them acknowledges them; `reply` marks them answered.
198
+ - **Render failures reach the agent without a click:** bad Mermaid syntax, an unknown component or a component that throws. The owner's open review page sends them as a note from "Render check", once per page revision, and `poll` returns them as `render_error` items. With no review tab open, nothing renders, so nothing is sent. `check --render <file>` renders the page in headless Chrome (an installed Chrome, Chromium or Edge, or `CHROME_PATH`) at 1280 and 390 px and prints render errors and the statements that make the page scroll sideways, as text, with no screenshot. Agents run it only when the user asks for a check.
192
199
 
193
200
  ## Components and packs
194
201
 
@@ -207,24 +214,25 @@ austere-axi components add austere-pack-gantt --trust # npm name, folder, or .
207
214
  austere-axi check --fix plan.html # adds the pack tag to the page
208
215
  ```
209
216
 
210
- A page names its packs in the head, so it renders wherever you open it:
217
+ A page names its packs in the head, so it renders anywhere:
211
218
 
212
219
  ```html
213
220
  <meta name="austere-packs" content="austere-pack-gantt@1.2.0 sha384-…">
214
221
  ```
215
222
 
216
- - Under review, the server serves installed packs only while they match the hash in `~/.austere-axi/packs.json`. Opened directly, the page loads them from jsDelivr with Subresource Integrity. `export` inlines them.
223
+ - Under review, the server serves an installed pack only while it matches its hash in `~/.austere-axi/packs.json`. Opened directly, the page loads packs from jsDelivr with Subresource Integrity. `export` inlines them.
217
224
  - A pack component with no pack tag is a `missing-pack` error. `check --fix` repairs it.
218
- - Mermaid ships as a bundled pack. It loads only when the page calls `Mermaid(...)` and needs no tag.
219
- - The `shadcn` pack ships the same way. It adds `Progress`, `Avatar`, `AvatarGroup`, `Tooltip`, `HoverCard`, `Breadcrumb` (`BreadcrumbItem`), `Kbd`, `Skeleton`, `Calendar` and `Timeline` (`TimelineItem`). They are read-only. Find them with `austere-axi components search`.
225
+ - **Bundled packs** need no tag and load only when used:
226
+ - `Mermaid`.
227
+ - `shadcn`: `Progress`, `Avatar`, `AvatarGroup`, `Tooltip`, `HoverCard`, `Breadcrumb` (`BreadcrumbItem`), `Kbd`, `Skeleton`, `Calendar` and `Timeline` (`TimelineItem`). Read-only.
220
228
  ```
221
229
  rollout = Progress(64, "Rows migrated")
222
230
  freeze = Calendar("2026-11", ["2026-11-04 Code freeze"])
223
231
  ```
224
232
 
225
- **Trust.** Pack code runs inside your review pages, where notes and button presses also come from. Packs under `@austere-axi/` on npm install directly. Other npm packs and any `.tgz` need `--trust`; read them first. A local folder counts as your own. A page with third-party packs shows a warning, and `poll` tells the agent.
233
+ **Trust.** Pack code runs inside your review pages, where notes and button presses also come from. Packs under `@austere-axi/` on npm install directly. Other npm packs and any `.tgz` need `--trust`, so read them first. A local folder counts as your own. A page with third-party packs shows a warning, and `poll` tells the agent.
226
234
 
227
- **Write a pack:** [Writing a pack](docs/writing-a-pack.md) walks through one from scaffold to `npm publish`, using [austere-pack-gantt](https://github.com/MandalorianBatman/austere-pack-gantt), a Gantt chart pack built outside this repo, as the example. The short version:
235
+ **Write a pack:** [Writing a pack](docs/writing-a-pack.md) goes from scaffold to `npm publish`, with [austere-pack-gantt](https://github.com/MandalorianBatman/austere-pack-gantt) as the example. Short version:
228
236
 
229
237
  ```sh
230
238
  austere-axi components new austere-pack-milestones
@@ -232,7 +240,7 @@ cd austere-pack-milestones && npm install && npm run build
232
240
  austere-axi components add .
233
241
  ```
234
242
 
235
- Each component uses `austereComponent` from `austere-axi/pack`: a PascalCase name, zod props in positional order (append-only after you publish), a one-line description and an example. The build loads the pack in Node, so build only code you trust. Publish with `npm publish`.
243
+ Each component uses `austereComponent` from `austere-axi/pack`: a PascalCase name, zod props in positional order (append-only after you publish), a one-line description and an example. The build loads the pack in Node, so build only code you trust.
236
244
 
237
245
  ## Hub
238
246
 
@@ -245,13 +253,14 @@ austere-axi hub notes # every note, same for an agent: [words] [--project dir]
245
253
  austere-axi hub off # back to the plain session list; the database stays
246
254
  ```
247
255
 
248
- `/notes` lists every note on every page, newest first, with search, status and project filters. The home page shows which reviews are live and how much feedback the agent has answered.
256
+ The home page shows which reviews are live and how much feedback the agent answered. `/notes` lists every note, newest first, with search, status and project filters.
249
257
 
250
- Session files from versions before 0.5 (`~/.austere-axi/sessions/*.json`) are imported into `hub.db` on first start and renamed to `*.imported`. One server runs per state dir; a second one refuses to start. `server.lock` names the running server's pid and port; a lock whose server no longer answers on that port is stale and is taken over.
258
+ - Session files from before 0.5 (`~/.austere-axi/sessions/*.json`) are imported into `hub.db` on first start and renamed to `*.imported`.
259
+ - One server runs per state dir; a second one refuses to start. `server.lock` holds the running server's pid and port. If that server no longer answers on the port, the lock is stale and is taken over.
251
260
 
252
261
  ### Always on, behind a reverse proxy
253
262
 
254
- To serve review pages at a public address, run the server under a supervisor with `--persist` so it never idles out, and point the proxy at it:
263
+ Run the server under a supervisor with `--persist` so it never idles out, and point the proxy at it:
255
264
 
256
265
  ```ini
257
266
  # ~/.config/systemd/user/austere-axi.service
@@ -267,11 +276,13 @@ Restart=on-failure
267
276
  WantedBy=default.target
268
277
  ```
269
278
 
270
- `systemctl --user enable --now austere-axi`. Commands on the same machine use the running server; a newer CLI that replaces an older server starts the new one with `--persist` too. `AUSTERE_AXI_PERSIST=1` does the same as `--persist`. Read the reverse-proxy note in [Team review](#team-review) first: behind a proxy on the same machine every request looks local.
279
+ Then `systemctl --user enable --now austere-axi`. CLI commands on the same machine use the running server. A newer CLI that replaces an older server also starts it with `--persist`. `AUSTERE_AXI_PERSIST=1` is the same as `--persist`.
280
+
281
+ Read the [trust model](#team-review) first: behind a proxy on the same machine, every request looks local.
271
282
 
272
283
  ## Team review
273
284
 
274
- Teammates on your tailnet can review a page and leave notes under their own name. The server still runs on your machine. There are no accounts and no hosted service.
285
+ Teammates on your tailnet can review a page and leave notes under their own name. The server still runs on your machine. No accounts, no hosted service.
275
286
 
276
287
  ```sh
277
288
  austere-axi share plan.html --as "Ana" # prints a link; notes wait for you
@@ -280,20 +291,22 @@ austere-axi share list [--links] # who has a link
280
291
  austere-axi share revoke Ana # name or id; takes effect at once
281
292
  ```
282
293
 
283
- Send the link to the teammate. The link needs Tailscale (or `AUSTERE_AXI_HOST`); without one, `share` stops with an error.
294
+ Send the link to the teammate. It needs Tailscale (or `AUSTERE_AXI_HOST`); without one, `share` stops with an error.
284
295
 
285
- - **Hold (default):** a teammate's note waits in your side panel. Press **Send** on one note, or **Send all**. **Discard** removes it. Held notes are not in `poll` or `feedback`, so the agent does not see them until you send them. Each teammate can have up to 200 notes held at once; more are refused until you send or discard some.
296
+ - **Hold (default):** a teammate's note waits in your side panel. **Send** one note, **Send all**, or **Discard**. The agent does not see held notes (`poll` and `feedback` skip them) until you send them. Up to 200 held notes per teammate; more are refused until you send or discard some.
286
297
  - **Auto:** the note goes to the agent without your approval.
287
- - **Author:** `poll` and `feedback` show `from: <name>` on each teammate's note. Your own notes have no `from`. The hub Notes view shows the author and can filter on it.
288
- - **Reviewers can:** read the page and the conversation, and add notes. They cannot end the review, export, or send held notes.
298
+ - **Author:** `poll` and `feedback` show `from: <name>` on a teammate's note. Your own notes have no `from`. The hub Notes view shows the author and can filter on it.
299
+ - **Reviewers can** read the page and the conversation, and add notes. They cannot end the review, export, or send held notes.
289
300
 
290
- Trust model: anyone who has the link, and can reach your machine on the tailnet, can post notes under that name until you revoke it. Share links only with people you trust, and revoke when the review is done. The link holds a random 192-bit token. Opening it shows a "Continue as <name>" button; pressing it (a same-origin POST) trades the token for a cookie and the address bar drops it. A link opened from another site therefore cannot switch your browser into a reviewer.
301
+ **Trust model.** Anyone with the link who can reach your machine on the tailnet can post notes under that name until you revoke it. Share only with people you trust, and revoke when the review is done.
291
302
 
292
- A request without a valid invite or the owner key is refused, on loopback too. The hub and its Notes view are yours only. Your own phone link carries the owner key (`~/.austere-axi/owner.key`); do not forward it. To make all old phone links and owner cookies stop working, delete that file and restart the server. A reverse proxy on the same machine gets no owner rights from looking local.
303
+ - The link holds a random 192-bit token. Opening it shows a "Continue as <name>" button. Pressing it (a same-origin POST) trades the token for a cookie and removes it from the address bar, so a link opened from another site cannot turn your browser into a reviewer.
304
+ - A request without a valid invite or the owner key is refused, on loopback too. The hub and its Notes view are yours only. A reverse proxy on the same machine gets no owner rights from looking local.
305
+ - Your own phone link carries the owner key (`~/.austere-axi/owner.key`); do not forward it. To revoke all old phone links and owner cookies, delete that file and restart the server.
293
306
 
294
307
  ## GitHub Action
295
308
 
296
- Post an austere review page on every pull request. The action runs `austere-axi check` on the pages you commit, exports each one as a self-contained HTML file with `austere-axi export`, uploads them as a workflow artifact, and posts or updates **one** comment on the PR with a summary and a download link.
309
+ Posts an austere review page on every pull request: runs `austere-axi check` on the pages you commit, exports each with `austere-axi export`, uploads them as a workflow artifact, and posts or updates **one** PR comment with a summary and a download link.
297
310
 
298
311
  ```yaml
299
312
  # .github/workflows/austere.yml
@@ -326,12 +339,12 @@ jobs:
326
339
 
327
340
  Outputs: `ok` (`true` when every page passed) and `artifact-url`.
328
341
 
329
- - **A failing check fails the job.** The `check` output, with each error's statement and line, is in the log and in the job summary; pages that passed are still exported.
342
+ - **A failing check fails the job.** The log and the job summary show each error with its statement and line. Pages that passed are still exported.
330
343
  - **Fork PRs** get a read-only token, so the comment is skipped with a warning. The job summary and the artifact still carry the report.
331
- - **Sticky comment:** the action updates only a marked comment posted by its own token's account (a bot account for `github.token`). A comment someone else writes with the same marker is left alone, and the action posts its own.
344
+ - **Sticky comment:** the action updates only a marked comment posted by its own token's account (a bot account for `github.token`). If someone else posts a comment with the same marker, the action leaves it alone and posts its own.
332
345
  - **Triggers:** use `pull_request`. Do not use `pull_request_target` to run pages from a fork.
333
- - **Artifact:** GitHub zips it and requires a login to download it. Unzip and open the HTML in a browser; it works offline. A Pages preview is not part of the action.
334
- - The action lives in this repository, under `action/`, so one ref pins the action and the CLI contract it calls. `version` still picks the CLI.
346
+ - **Artifact:** GitHub zips it and needs a login to download. Unzip and open the HTML in a browser; it works offline. No Pages preview.
347
+ - The action lives in this repo under `action/`, so one ref pins both the action and the CLI contract it calls. `version` still picks the CLI.
335
348
 
336
349
  ## Environment
337
350
 
@@ -349,25 +362,33 @@ Outputs: `ok` (`true` when every page passed) and `artifact-url`.
349
362
  | `AUSTERE_AXI_IDLE_TIMEOUT_MS` | `1800000` | idle shutdown; `0` disables |
350
363
  | `AUSTERE_AXI_PERSIST` | | `1`: never idle out, as `server --persist` |
351
364
  | `AUSTERE_AXI_NPM_REGISTRY` | `npm_config_registry`, else npmjs | where `components add` gets packs |
365
+ | `CHROME_PATH` | an installed Chrome, Chromium or Edge | the browser `check --render` uses |
352
366
 
353
367
  ## Security
354
368
 
355
- The server binds to loopback, plus the Tailscale IP when Tailscale is up. It never binds to all interfaces.
369
+ The server binds to loopback, plus the Tailscale IP when Tailscale is up. Never to all interfaces.
370
+
371
+ **Loopback is not the owner.** Other OS users on the machine can reach loopback too. The owner key (`owner.key`) is what proves ownership.
372
+
373
+ - **API:** `/api/*` answers only loopback callers that send the owner key, and refuses browser requests. `/api/health` needs no key. It proves the server holds the key with an HMAC of its port and the CLI's nonce, so the CLI never talks to, or hands the key to, another program on its port.
374
+ - **Browser sign-in:** when the CLI opens your browser, it gets a single-use login code from the server and opens the page with it. The server swaps the code for the owner cookie and removes it from the address bar. The key never appears on a command line or in output. A code works once, for 60 seconds. Another local user who reads it from the process list in that window can use it first; your browser then shows "used already", and `austere-axi login` gives you a new one.
375
+ - **Other browsers:** a printed `url` works in a browser that already has the cookie. For any other browser (SSH with a forwarded port, `--no-open`, a second browser), run `austere-axi login [file]` and open the link it prints.
376
+ - **Owner cookie** is an HMAC of the key, not the key. Browsers share `127.0.0.1` cookies across ports, so a program on another local port that your browser visits can read it. With it, that program can open your pages and the hub as you in a browser, but cannot call `/api/*`, which needs the key.
377
+
378
+ **Files.** On macOS and Linux the state dir is set to mode 0700 on every run. New files in it (`owner.key`, `server.log`, and `hub.db`, which holds every note) are mode 0600. Files an older version created keep their old mode, behind the 0700 dir. On Windows, file modes do not apply; only your user profile's permissions protect the state dir.
379
+
380
+ **Requests.**
356
381
 
357
- - Loopback is not the owner: other OS users on the machine can reach it too. The owner key in `owner.key` is.
358
- - `/api/*` answers only loopback callers that send the owner key, and refuses browser requests. `/api/health` needs no key; it proves the server holds the key with an HMAC of its port and the CLI's nonce, so the CLI never talks to, or hands the key to, another program on its port.
359
- - A browser signs in with a single-use login code: when the CLI opens your browser, it asks the server for a code and opens the page with it; the server swaps it for the owner cookie and drops it from the address bar. The key is never on a command line or in printed output. A code works once and for 60 seconds. Another local user who reads it from the process list in that time can use it first; your browser then shows "used already", and `austere-axi login` gives you a new one.
360
- - A printed `url` works in a browser that already has the cookie. For any other browser (SSH with a forwarded port, `--no-open`, a second browser), run `austere-axi login [file]` and open the link it prints.
361
- - The owner cookie is an HMAC of the key, not the key. Browsers share `127.0.0.1` cookies across ports, so any program listening on another local port that your browser visits can read it. With it, that program can open your pages and the hub as you in a browser, but not call `/api/*`, which needs the key.
362
- - On macOS and Linux the state dir is made mode 0700 on every run, and new files in it (`owner.key`, `server.log`, and `hub.db`, which holds every note) are mode 0600. Files an older version created keep their old mode, behind the 0700 dir. On Windows, file modes do not apply: the state dir is protected only by your user profile's permissions.
363
382
  - `Host` headers must be on an allowlist (DNS-rebinding defense).
364
383
  - Browser routes that change state refuse a foreign `Origin`.
365
384
  - Invite tokens are random, compared in constant time, and kept out of the log. A reviewer link can only add notes to its own page. See [Team review](#team-review).
366
- - The page runs in a sandboxed iframe with no same-origin access. Packs run there too. Page files are also served with a `Content-Security-Policy: sandbox` header, so the page stays sandboxed when opened in a popup or its own tab. In review, links that open a new window are opened by the review page, and only if they are external `http(s):` or `mailto:` links. A popup the frame opens any other way stays sandboxed.
367
385
  - The server serves files only from the page's own directory, with no dotfiles.
368
- - Third-party packs need `--trust`. Installed packs must match their lockfile hash; CDN loads use Subresource Integrity.
369
386
 
370
- OpenUI's Node side has PostHog telemetry. The CLI sets `OPENUI_TELEMETRY_DISABLED=1`. The browser runtime sends no telemetry.
387
+ **Page sandbox.** The page runs in a sandboxed iframe with no same-origin access; packs run there too. Page files are also served with `Content-Security-Policy: sandbox`, so a page opened in a popup or its own tab stays sandboxed. In review, the review page opens new-window links only if they are external `http(s):` or `mailto:` links. A popup the frame opens any other way stays sandboxed.
388
+
389
+ **Packs.** Third-party packs need `--trust`. Installed packs must match their lockfile hash; CDN loads use Subresource Integrity.
390
+
391
+ **Telemetry.** OpenUI's Node side has PostHog telemetry; the CLI sets `OPENUI_TELEMETRY_DISABLED=1`. The browser runtime sends none.
371
392
 
372
393
  ## Not in v1
373
394
 
@@ -384,35 +405,9 @@ npm run build # dist/: runtime, packs, pack kit, Mermaid, spec, catalog, rev
384
405
  npm test
385
406
  npm run typecheck # tsc over src/cli, src/shared and the pack types
386
407
  node dist/cli.mjs examples/session-migration.html
387
- npm run site # _site/: the website (needs Chrome or CHROME_PATH); see site/ and examples/
388
408
  ```
389
409
 
390
- | Path | Contents |
391
- |---|---|
392
- | `src/runtime/components/` | Custom components (`Architecture` family, `Diff`, `FileTree`), one file each |
393
- | `src/runtime/stock.js` | Which stock components ship, which stay out of `spec`, their groups |
394
- | `docs/` | [Writing a pack](docs/writing-a-pack.md) |
395
- | `packs/` | Packs bundled with austere-axi (`mermaid`, `shadcn`) |
396
- | `src/runtime/pack-api.js`, `src/pack-kit/` | `austere-axi/pack` and the `components build` pipeline |
397
- | `src/shared/catalog.js` | Component catalog and prompt tiers |
398
- | `src/runtime/` | Library, annotation layer, review bridge, styles |
399
- | `src/chrome/` | Review UI around the page iframe |
400
- | `src/cli/` | CLI, server, sessions, `check`, `export`, packs, hub |
401
- | `action/` | The [GitHub Action](#github-action) (not published to npm) |
402
- | `skills/` | Published skills: `austere`, `austere-setup` |
403
- | `.claude-plugin/` | Claude Code marketplace and plugin manifest (in git, not in the npm package) |
404
-
405
- Maintainer guides (not published): [`AGENTS.md`](AGENTS.md), [`.agents/skills/add-component`](.agents/skills/add-component/SKILL.md) to add a component, [`.agents/skills/review-change`](.agents/skills/review-change/SKILL.md) before you commit. Tests enforce the component rules: every component has a group, `spec` stays in its token budget, released prop orders are append-only (`npm run signatures`), and every example passes `check`.
406
-
407
- Visual checks: `npm run gallery`, `npm run shot`, and `npm run test:browser` (real review loop in headless Chrome; needs Chrome or `CHROME_PATH`).
408
-
409
- Website: `site/` holds the hand-written pages and CSS, `examples/` feeds the gallery (add a file there and it appears with a generated thumbnail), `scripts/site.mjs` builds `_site/`, and `.github/workflows/pages.yml` deploys it to GitHub Pages on every push to `main`. Examples need a `<meta name="description">` for the gallery blurb. None of it is published to npm.
410
-
411
- Playground (`/play/`, source in `site/play/`): a textarea editor beside a sandboxed frame running `dist/austere.js`. It plays the part of the review chrome over the same `postMessage` bridge, so edits re-render in place and clicks arrive as the annotations `poll` prints. Problems come from `src/shared/check-program.js`, the same code as `austere-axi check`, bundled for the browser by `scripts/site.mjs` (with the spec, the component catalog and one template per example in `play/data.json`). The program travels in the URL hash (`#code=v1.<deflate-raw, base64url>`), so there is no server. `test/browser/play.browser.test.js` drives it in headless Chrome as part of `npm run test:browser`. Packs other than the bundled ones are not available there.
412
-
413
- Benchmark against lavish-axi: `npm run bench` (needs model keys; see [docs/benchmark.md](docs/benchmark.md)). `npm run bench:report` rebuilds its tables from the committed results.
414
-
415
- **CI** builds and tests every push to `main` and every pull request (Node 22, 24, 26 on Linux; Node 24 on Windows; browser suite). `action.yml` runs the GitHub Action on fixture pages (`test/fixtures/action/`). **Release:** bump `version` in `package.json`, push, and publish a GitHub Release. `publish.yml` tests again and publishes to npm with trusted publishing.
410
+ Repo layout, website, playground, benchmark, CI and release: [CONTRIBUTING.md](CONTRIBUTING.md).
416
411
 
417
412
  ## License
418
413