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 +96 -101
- package/dist/austere.js +126 -126
- package/dist/catalog.json +1 -1
- package/dist/chrome/chrome.js +5 -5
- package/dist/cli.mjs +837 -462
- package/dist/pack-kit.mjs +23 -23
- package/dist/packs/mermaid/catalog.json +3 -3
- package/dist/packs/mermaid/pack.js +4 -4
- package/dist/packs/shadcn/catalog.json +3 -3
- package/dist/packs/shadcn/pack.js +5 -5
- package/dist/prompt.md +1 -1
- package/package.json +17 -14
- package/skills/austere/SKILL.md +2 -0
- package/types/pack-kit.d.ts +4 -3
- package/types/pack.d.ts +27 -8
package/README.md
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/austere-axi)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
|
-
Review pages for coding agents.
|
|
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
|

|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
Optional:
|
|
77
81
|
|
|
78
82
|
```sh
|
|
79
|
-
npm install -g austere-axi
|
|
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
|
|
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
|
-
|
|
89
|
+
### Claude Code plugin
|
|
86
90
|
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
145
|
-
- **Components
|
|
146
|
-
- **
|
|
147
|
-
- **
|
|
148
|
-
- **Changes
|
|
149
|
-
- **Buttons and forms
|
|
150
|
-
- **Phone review
|
|
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
|
|
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
|
|
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
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
219
|
-
-
|
|
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
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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`.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
288
|
-
- **Reviewers can
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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`).
|
|
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
|
|
334
|
-
- The action lives in this
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|