sign-off-axi 0.3.6 → 0.4.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/README.md +351 -151
  2. package/dist/cli.js +352 -48
  3. package/dist/contract.js +204 -46
  4. package/dist/store.js +97 -12
  5. package/fixtures/contract/README.md +2 -2
  6. package/fixtures/contract/ask.v1/invalid-default_if_silent-missing.json +1 -0
  7. package/fixtures/contract/ask.v1/invalid-link-c1-control.json +1 -1
  8. package/fixtures/contract/ask.v1/invalid-merges-own-key.json +1 -0
  9. package/fixtures/contract/ask.v1/invalid-options-missing-letter.json +1 -0
  10. package/fixtures/contract/ask.v1/invalid-options-not-an-answer.json +1 -0
  11. package/fixtures/contract/ask.v1/invalid-options-outcome-missing.json +1 -0
  12. package/fixtures/contract/ask.v1/invalid-options-yes-label.json +1 -0
  13. package/fixtures/contract/ask.v1/invalid-options-yes-no-for-letters.json +1 -0
  14. package/fixtures/contract/ask.v1/invalid-recommend-unknown.json +1 -1
  15. package/fixtures/contract/ask.v1/invalid-recommend_reason-missing.json +1 -0
  16. package/fixtures/contract/ask.v1/invalid-recommend_value-missing.json +1 -1
  17. package/fixtures/contract/ask.v1/invalid-recommend_value-repeated.json +1 -1
  18. package/fixtures/contract/ask.v1/invalid-silent_by-not-utc.json +1 -0
  19. package/fixtures/contract/ask.v1/invalid-title-id-word.json +1 -0
  20. package/fixtures/contract/ask.v1/invalid-title-not-a-question.json +1 -0
  21. package/fixtures/contract/ask.v1/invalid-title-robot-prefix.json +1 -0
  22. package/fixtures/contract/ask.v1/invalid-title-too-long.json +1 -1
  23. package/fixtures/contract/ask.v1/invalid-title-too-many-words.json +1 -0
  24. package/fixtures/contract/ask.v1/invalid-title-unpaired-surrogate.json +1 -1
  25. package/fixtures/contract/ask.v1/invalid-why-missing.json +1 -0
  26. package/fixtures/contract/ask.v1/valid-pick-one.json +25 -6
  27. package/fixtures/contract/ask.v1/valid-yes-no.json +1 -1
  28. package/fixtures/contract/plan.v1/invalid-id-repeated.json +1 -0
  29. package/fixtures/contract/plan.v1/invalid-options-unknown.json +1 -0
  30. package/fixtures/contract/plan.v1/invalid-question-over-12-words.json +1 -0
  31. package/fixtures/contract/plan.v1/invalid-question-two-questions.json +1 -0
  32. package/fixtures/contract/plan.v1/invalid-recommend-missing.json +1 -0
  33. package/fixtures/contract/plan.v1/invalid-sections-missing.json +1 -0
  34. package/fixtures/contract/plan.v1/invalid-view-missing.json +1 -0
  35. package/fixtures/contract/plan.v1/page/index.html +42 -0
  36. package/fixtures/contract/plan.v1/valid-full.json +45 -0
  37. package/fixtures/contract/plan.v1/valid-minimal.json +13 -0
  38. package/package.json +2 -1
  39. package/web/demo/media/explainer.mp4 +0 -0
  40. package/web/demo/media/proof-dark.svg +12 -0
  41. package/web/demo/media/proof-light.svg +12 -0
  42. package/web/demo/mocks/a.html +26 -0
  43. package/web/demo/mocks/b.html +33 -0
  44. package/web/demo/mocks/c.html +29 -0
  45. package/web/demo/seed.json +116 -0
  46. package/web/demo/shim.js +180 -0
  47. package/web/index.html +173 -40
package/README.md CHANGED
@@ -1,222 +1,422 @@
1
+ <p align="center"><b>▶ <a href="https://umeranjum17.github.io/sign-off/">Try it live</a></b> — the real board on demo data: tap an answer and watch it save. No install, nothing leaves your browser.</p>
2
+
3
+ <p align="center">
4
+ <img src="docs/media/demo-tap.gif" width="300" alt="Tap Yes on a demo question, see it saved, and read the saved answer back">
5
+ </p>
6
+
1
7
  <p align="center">
2
8
  <img src="docs/logo.svg" width="72" height="72" alt="sign-off logo">
3
9
  </p>
4
10
 
5
11
  <h1 align="center">sign-off</h1>
6
12
 
7
- <p align="center"><b>Answer your agents' yes/no and pick-one questions in one click, from a page on your own machine.</b></p>
13
+ <p align="center"><b>One calm page for every question the programs working for you need answered.</b><br>Yes or no, pick-one, mock-ups, proof photos, short videos, sign-in links: each lands on a board on your own machine, you answer with one tap, and the answer is saved for the asker to read.</p>
8
14
 
9
15
  <p align="center">
10
- <img src="docs/flow-ask.svg" width="240" alt="1. Agent asks: Keep the digest?">
11
- <img src="docs/flow-answer.svg" width="240" alt="2. You click Yes or No on the page.">
12
- <img src="docs/flow-receipt.svg" width="240" alt="3. Agent reads your saved answer: yes.">
16
+ <picture>
17
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/hero-dark.gif">
18
+ <img src="docs/media/hero.gif" width="420" alt="Ask, tap, answer: an agent raises a yes/no question, you tap Yes on your phone, the agent reads the saved answer">
19
+ </picture>
13
20
  </p>
14
21
 
15
- <p align="center">
16
- <img src="docs/media/desktop-light-list.png" width="860" alt="Light desktop: a pick-one question with Today, Friday and Next week, two Yes/No questions, and the weekly digest answered Yes with a note">
17
- </p>
22
+ ## One-minute quick start
18
23
 
19
- ## Why
24
+ ```sh
25
+ npm install -g sign-off-axi
26
+ sign-off-axi raise \
27
+ --title "Keep the weekly \
28
+ digest?" \
29
+ --why "The paid plan \
30
+ renews Monday." \
31
+ --outcome yes="It keeps \
32
+ going out." \
33
+ --outcome no="It stops \
34
+ this week." \
35
+ --recommend yes \
36
+ --recommend-reason "Readers \
37
+ still open it." \
38
+ --default yes
39
+ sign-off-axi open
40
+ ```
20
41
 
21
- Agents stop and ask: ship this, keep that, turn this on? Those questions scatter across terminals and chats, and an answer typed in the wrong place gets lost.
42
+ Open the printed URL, click **Yes**, then read the answer back the way an
43
+ agent would:
22
44
 
23
- sign-off puts them on one calm list. The agent writes a small JSON file, you inspect the linked evidence, click **Yes** or **No** (or one of up to six choices the agent offers), and the answer is saved next to the file for the agent to read. Add an optional short note if the agent should know why; the note never blocks the click. It runs on your own machine (`127.0.0.1`, or one private address you name with `--bind`): no account, no hosting.
45
+ ```sh
46
+ sign-off-axi answers \
47
+ --key keep-the-weekly-digest
48
+ ```
24
49
 
25
- Your answer survives reloads and restarts; you can change it with one more click. Recommendations and silent defaults are shown, never applied for you.
50
+ That is the whole loop: the agent raises, you tap, the agent reads.
51
+ Every question says why it is asked, what each answer does, which one the
52
+ agent recommends and why, and what happens if you stay quiet; a question
53
+ that does not is refused before it reaches you (see
54
+ [the question standard](docs/contract.md#ask-schema-1)). The
55
+ board is the default one (`~/.sign-off/decisions.json`); name a file after
56
+ any command to use another board.
26
57
 
27
- ## Quick start
58
+ ## What you can ask
28
59
 
29
- ### Agent skill (recommended)
60
+ One line on what the agent runs, one screenshot of what you see. The full
61
+ reference for each kind lives in [docs/question-kinds.md](docs/question-kinds.md);
62
+ the message rules are in [docs/contract.md](docs/contract.md).
30
63
 
31
- sign-off works with whichever coding agent you use; it names no vendor, model or account, and calls no model itself. In a project folder:
64
+ ### Yes or no
32
65
 
33
66
  ```sh
34
- npx -y sign-off-axi init
67
+ sign-off-axi raise \
68
+ --title "Keep the weekly \
69
+ digest?" \
70
+ --about "Open rate held \
71
+ steady at about a third \
72
+ for two months." \
73
+ --why "The paid plan \
74
+ renews Monday." \
75
+ --outcome yes="It keeps \
76
+ going out each Monday." \
77
+ --outcome no="It stops \
78
+ after this week." \
79
+ --recommend yes \
80
+ --recommend-reason "Readers \
81
+ still open it." \
82
+ --default yes \
83
+ --silent-by 2026-10-19T09:00Z
35
84
  ```
36
85
 
37
- `init` writes the repo setup (below) and installs the sign-off skill for every coding agent whose command is on your `PATH`. Detection reads nothing else (no logins, no network); the install then fetches the skill from this repository. Pick agents yourself with `--agent` (one name, or several joined by commas); the choice is saved as `agent:` in `.sign-off/config.yaml`, and `sign-off-axi doctor` then prints one line per agent saying whether its skill is installed, with the install command when it is not.
86
+ <picture>
87
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-yes-no-dark.webp">
88
+ <img src="docs/media/kind-yes-no.webp" alt="A yes/no question with the recommended Yes marked">
89
+ </picture>
38
90
 
39
- | Agent | `--agent` name | Detected by | Skill folder in the project |
40
- |---|---|---|---|
41
- | Claude Code | `claude-code` | `claude` | `.claude/skills/` |
42
- | Codex | `codex` | `codex` | `.agents/skills/` |
43
- | OpenCode | `opencode` | `opencode` | `.agents/skills/` |
44
- | Pi | `pi` | `pi` | `.agents/skills/` |
45
- | Gemini CLI | `gemini-cli` | `gemini` | `.agents/skills/` |
91
+ *A yes/no ask. The recommended answer is marked.*
46
92
 
47
- The install is the multi-agent [`npx skills`](https://github.com/vercel-labs/skills) installer for the [Agent Skills](https://agentskills.io) format, so you can also run it by hand, for any agent it supports:
93
+ ### Pick one, with a recommendation
48
94
 
49
95
  ```sh
50
- npx skills add umeranjum17/sign-off --skill sign-off-axi -a <agent> # add -g to install for all your projects
96
+ sign-off-axi raise \
97
+ --title "When should \
98
+ the new settings \
99
+ page go out?" \
100
+ --about "No new \
101
+ bug reports for three \
102
+ weeks." \
103
+ --why "Support is fully \
104
+ staffed on Friday." \
105
+ --option today=Today \
106
+ --option friday=Friday \
107
+ --option next-week="Next \
108
+ week" \
109
+ --outcome today="It goes \
110
+ out now." \
111
+ --outcome friday="It goes \
112
+ out with support on hand." \
113
+ --outcome next-week="It \
114
+ waits a week." \
115
+ --recommend friday \
116
+ --recommend-reason "Support \
117
+ can answer questions." \
118
+ --default next-week
51
119
  ```
52
120
 
53
- That is the entire setup - no npm install needed.
54
- The skill teaches your agent to run sign-off through `npx -y sign-off-axi`, so the CLI comes along on demand.
55
- It stays a short stub and sends the agent to `npx -y sign-off-axi --help` for current instructions, so an installed copy cannot go stale against a newer CLI.
56
- Its frontmatter also includes Hermes Agent metadata, so Hermes-compatible harnesses can categorize and surface it as a first-class productivity skill.
121
+ <picture>
122
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-pick-one-dark.webp">
123
+ <img src="docs/media/kind-pick-one.webp" alt="A pick-one question with three options and Friday recommended">
124
+ </picture>
125
+
126
+ *Pick one of three. Friday is recommended; silence means next week.*
127
+
128
+ ### Design pick, with mocks A/B/C
129
+
130
+ ```sh
131
+ sign-off-axi post \
132
+ --type design \
133
+ --title "Pick the settings \
134
+ layout" \
135
+ --option a="Layout A" \
136
+ --option b="Layout B" \
137
+ --option c="Layout C" \
138
+ --mock a=mocks/a.html \
139
+ --mock b=mocks/b.html \
140
+ --mock c=mocks/c.html \
141
+ --tradeoff "A is fastest \
142
+ to tap; B shows both \
143
+ choices; C fits small \
144
+ screens." \
145
+ --recommend b
146
+ ```
147
+
148
+ <picture>
149
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-design-dark.webp">
150
+ <img src="docs/media/kind-design.webp" alt="Three mock-ups side by side in phone frames with Pick buttons, Layout B recommended">
151
+ </picture>
152
+
153
+ *Three layouts side by side. Layout B is recommended.*
154
+
155
+ ### Proof update, with Pass / Fail / Needs work
57
156
 
58
- Then ask in plain words - "ask me whether to keep the weekly digest and wait for my answer" - and the agent loads the skill on its own when it recognizes the task. Agents that expose skills as slash commands also take `/sign-off-axi should we keep the weekly digest?`.
157
+ ```sh
158
+ sign-off-axi post \
159
+ --type evidence \
160
+ --title "Settings page \
161
+ loads in a second" \
162
+ --about "Umer opens \
163
+ the new page on a phone." \
164
+ --media shot-light.png \
165
+ --media shot-dark.png
166
+ ```
167
+
168
+ <picture>
169
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-evidence-dark.webp">
170
+ <img src="docs/media/kind-evidence.webp" alt="Evidence update 1 with two captures and Pass, Fail, Needs work buttons">
171
+ </picture>
59
172
 
60
- ### Your answer reaches the agent that asked
173
+ *Proof of a fix, with two captures. Pass, Fail, or Needs work.*
61
174
 
62
- Every item records who filed it, and that agent gets your answer as an event, by itself:
175
+ ### Explainer video, with chapters
63
176
 
64
177
  ```sh
65
- npx -y sign-off-axi raise --title "Keep the weekly digest?" --filer my-agent # or export SIGN_OFF_FILER=my-agent
66
- npx -y sign-off-axi wait --filer my-agent # blocks until you answer, then prints it
178
+ sign-off-axi post \
179
+ --type explainer \
180
+ --title "Is the new \
181
+ sign-in clear?" \
182
+ --about "Two steps: \
183
+ password, then a code." \
184
+ --media explainer.mp4 \
185
+ --chapter 0:00=Start \
186
+ --chapter 0:02="The tap"
67
187
  ```
68
188
 
69
- `wait --filer` returns only answers to that filer's items, each once, including one given before it started waiting; the card then shows **Seen by agent**. With no `--filer` or `SIGN_OFF_FILER`, a post saves the Claude Code session id when there is one, and the post prints the exact `wait` command to run. An agent that cannot block can be woken instead: an `on_answer:` line in the `config.yaml` beside the board names a command the board runs for every answer (below).
189
+ <picture>
190
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-explainer-dark.webp">
191
+ <img src="docs/media/kind-explainer.webp" alt="An explainer card with a video player, chapter buttons and Yes/No">
192
+ </picture>
70
193
 
71
- ### Repo setup
194
+ *A short video with chapters, then Yes or No.*
72
195
 
73
- In a project folder, `sign-off-axi init` writes `.sign-off/` (config with the
74
- board, port and agent choice, an empty board, `mocks/` and `evidence/` folders,
75
- and a `.gitignore` keeping media and answer files out of git) and
76
- `sign-off-axi doctor` checks it: node version, config, board, answer files,
77
- a lock a crashed command left behind, gitignore lines, folders, the dashboard
78
- port, the `hosts:` names and each agent's skill. Each
79
- failure prints one `help:` line with its fix, and doctor exits 1; a clean run
80
- exits 0. `open` and `serve` use the config's `port:` unless `--port` is given;
81
- for a symlinked board they read the `config.yaml` beside the link, as doctor does. With no folder, `doctor` checks the nearest setup the same way a bare command finds its board.
82
- Re-running `init` keeps files that are already there, adds the missing ones (so
83
- it repairs an `init` that was cut short) and skips agents that already have the
84
- skill. Mock files go under
85
- `.sign-off/mocks/` and are named board-relative in `post` (for example
86
- `mocks/a.html`). The skill tells the agent how to make them: 3 variants that
87
- differ in structure, a short trade-off and one recommendation, posted, waited
88
- on and read back.
196
+ ### Explainer page, to read
197
+
198
+ ```sh
199
+ sign-off-axi report init \
200
+ .sign-off/mocks/sign-in
201
+ d=mocks/sign-in
202
+ sign-off-axi post \
203
+ --type report \
204
+ --title "Sign-in now \
205
+ takes 2 steps" \
206
+ --about "Why the code \
207
+ step moved first." \
208
+ --view $d/index.html \
209
+ --preview $d/preview.svg
210
+ ```
89
211
 
90
- Boards written by 0.1.0 (`"schema": 1`, including `~/.sign-off/decisions.json`)
91
- still read, with their saved answers, and are upgraded on the first write that
92
- changes them. The format is in [docs/file-formats.md](docs/file-formats.md).
212
+ <picture>
213
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-report-dark.webp">
214
+ <img src="docs/media/kind-report.webp" alt="An explainer page: the answer in one sentence, a diagram, steps to tap through, folded details">
215
+ </picture>
93
216
 
94
- ### Manual CLI
217
+ *An explainer page to tap through and read.*
95
218
 
96
- The skill is the recommended path, but you can also run the board by hand from this repository (Node 22.5 or newer):
219
+ ### Diagram
97
220
 
98
221
  ```sh
99
- # 1. Build
100
- git clone https://github.com/umeranjum17/sign-off && cd sign-off && npm install && npm run build
222
+ sign-off-axi post \
223
+ --type diagram \
224
+ --title "Is this the \
225
+ sign-in flow?" \
226
+ --about "Password first, \
227
+ then a code, then in." \
228
+ --view flow.svg \
229
+ --recommend yes
230
+ ```
231
+
232
+ <picture>
233
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-diagram-dark.webp">
234
+ <img src="docs/media/kind-diagram.webp" alt="A question shown with a three-box flow diagram at card width">
235
+ </picture>
101
236
 
102
- # 2. Open the page with the demo questions (copy them so the answers stay out of the repo)
103
- cp fixtures/demo-decisions.json /tmp/decisions.json
104
- node dist/cli.js serve /tmp/decisions.json # prints http://127.0.0.1:4410/
105
- # Optional: add --evidence-root /path/to/project-store to read proof records/media (the root containing .done-done/)
237
+ *A question drawn as a three-box flow.*
106
238
 
107
- # 3. Click an answer, then read it back the way an agent would
108
- node dist/cli.js answers /tmp/decisions.json # answers[1]{key,answer,at}: weekly-digest,yes,...
239
+ ### Interactive page
240
+
241
+ ```sh
242
+ sign-off-axi post \
243
+ --type interactive \
244
+ --title "How many \
245
+ testers approved?" \
246
+ --about "Tap the counter \
247
+ to add yourself." \
248
+ --view counter.html \
249
+ --option yes=Yes \
250
+ --option no="Needs \
251
+ work" \
252
+ --recommend yes
109
253
  ```
110
254
 
111
- ## Commands
255
+ <picture>
256
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-interactive-dark.webp">
257
+ <img src="docs/media/kind-interactive.webp" alt="A question with a sandboxed counter page to tap">
258
+ </picture>
112
259
 
113
- The CLI is `sign-off-axi`, an [AXI](https://axi.md): agents drive it from a shell with no setup. It prints compact [TOON](https://toonformat.dev) on stdout with next-step hints, and every command takes `--help`.
114
- sign-off itself calls no model and inherits the calling agent's model routing.
260
+ *A live counter to tap, in a sandboxed page.*
261
+
262
+ ### Sign-in link
115
263
 
264
+ ```sh
265
+ sign-off-axi post \
266
+ --type signin \
267
+ --service "npm" \
268
+ --link \
269
+ https://example.com/abc123 \
270
+ --unlocks "Publish \
271
+ the release" \
272
+ --waits "Umer" \
273
+ --expires 15m \
274
+ --key npm-login
116
275
  ```
117
- sign-off-axi list the decisions waiting on the user (same as list)
118
- sign-off-axi raise --title "<question>" [--key <key>] [--about <text>] [--detail <text>]
119
- [--option <value>=<label>]... [--recommend <value>] [--default <value>] [--close done|release]
120
- sign-off-axi raise --from <decisions.json> add every decision in a decisions file (a new item of a contract kind carries `contract`, `kind` and `schema`: see docs/contract.md)
121
- sign-off-axi post --kind <kind> --from <message.json|-> post one contract message, from a file or stdin
122
- sign-off-axi post --type design --title "<question>" [--option <value>=<label>]... [--mock <value>=<path>]...
123
- [--tradeoff <text>] [--recommend <value>] ask for a pick between mocks, shown side by side
124
- (--type decision is the same as raise; raise stays its alias)
125
- sign-off-axi post --type evidence --title "<what this shows>" --about "<short note>" --media <file>... [--link <commit or URL>]
126
- post the next numbered evidence update for a pass/fail/needs-work verdict
127
- sign-off-axi post --type diagram|interactive --title "<question>" --view <path> [--option <value>=<label>]...
128
- ask with a diagram image, or a sandboxed interactive .html page
129
- sign-off-axi post --type explainer --title "<question>" --media <video> [--about <summary>] [--chapter <m:ss>=<title>]...
130
- ask with a narrated video the agent made with free local tools
131
- sign-off-axi report init <folder> write a report page to fill: the answer and a picture, a diagram, steps to tap, the detail folded
132
- sign-off-axi post --type report --title "<the answer>" --about "<why it matters>" --view <page.html> --preview <picture>
133
- post that page as a report: a card with its picture that opens full screen; text alone is refused
134
- sign-off-axi init [dir] [--agent auto|<name>] write the repo setup under <dir>/.sign-off/ (config, board, mocks/ and evidence/ folders)
135
- and install the skill for each chosen coding agent (auto: each one on PATH)
136
- sign-off-axi doctor [dir] check that setup and each agent's skill; every failure names its fix
137
- sign-off-axi validate <message.json>... [--kind <kind>] [--expect-invalid] check ask, report, evidence, gate and finding messages against the contract (docs/contract.md) without posting
138
- sign-off-axi open [--port 4410] [--bind <address>] [--evidence-root <dir>] start the board in the background; prints its URL and pid
139
- sign-off-axi status is the board open started for this file running? its URL and pid
140
- sign-off-axi stop stop that board
141
- sign-off-axi serve [--port 4410] [--bind <address>] [--evidence-root <dir>] run the board in the foreground (--port 0 picks a free port)
142
- sign-off-axi answers [--key <key>] the latest answer per decision, and how many still wait
143
- sign-off-axi wait [--filer <id>] [--key <key>] [--timeout 600] [--events (needs --filer)] block until the user answers, then print it like answers; --events prints one JSON event line per answer and round (docs/contract.md section 7)
144
- (--filer: only that filer's items, each marked taken; without --key each answer once)
145
- sign-off-axi poll [--timeout 600] block until the user comments or queues a prompt, then print each new one once
146
- sign-off-axi export [--out <file.html>] write a frozen read-only copy of the board as one HTML file
147
- ```
148
-
149
- - The board is the one you name right after the command (`sign-off-axi answers ./decisions.json`). With none, it is the nearest `.sign-off/` board that `init` set up, found by the walk described in [Test runs never touch your board](#test-runs-never-touch-your-board); otherwise the default board there (`~/.sign-off/decisions.json`, or under `SIGN_OFF_HOME`). Answers are saved next to it.
150
- - Next-step commands in the output name paths under the working folder relative to it, and say `npx -y sign-off-axi` when sign-off-axi runs through `npx`.
151
- - `post --type design` asks for a pick between up to 6 mock options, each with an optional `--mock <value>=<path>` file (`.html`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.webp`, 5 MB or less) shown as a tall phone frame above its button (the recommended one first; on a phone the frames swipe sideways, the next one peeking in, and a tap opens one full size), plus a `--tradeoff` line and a `--recommend` badge. Mock paths are relative to the board file's folder and never escape it. One `.html` page can hold every variant: `--mock a=mocks/<key>/index.html?variant=a` loads the page with `?variant=a`. An `.html` mock or view loads the css, scripts, images and fonts beside it by relative name (`style.css`, `img/dot.png`), from its own folder only, never anything above it. The board adds a small pointing script to each `.html` mock: once the board turns it on for that frame, a tap on a part of the mock is kept from the mock and named back to the board as a path to that element, its tag and its words; it passes only text and gives the mock no reach into the board. With **Comment on the mocks** on, a tap opens a comment on that element: one per element, listed under the mocks with Edit and Delete (words typed but not yet saved stay with their element while the page is open, so tapping another element and back keeps them; a reload drops them), kept on the board and shown, until it is sent, only on the exact mock file it was made on. The comments go to the agent with the next answer on that card (a pick, Other answer or **Another round**, which may then have no note), each with its element, in `wait` and `answers` (`comments`); once sent they are listed as sent and can no longer change. An option with no mock, or whose file is gone, says "No mock for this option". `post --type decision` (the default) is exactly `raise`, and `raise` stays as its alias.
152
- - `post --type evidence` posts the next numbered update (`update-1`, `update-2`, ...) with 1 to 12 screenshots or recordings (`.png`, `.jpg`, `.jpeg`, `.webp`, `.mp4`, `.webm`, up to 50 MB each), a short note and an optional commit or PR link. The files are copied into `evidence/<number>/` next to the board, so the board is the store: no hosted service. The page shows the updates newest first; each file is served only while its size and SHA-256 still match what was posted, and recordings play inline on a phone. The person gives a verdict, **Pass**, **Fail** or **Needs work**, with an optional note, read back with `answers` or `wait --key update-<number>`.
153
- - `post --type diagram` and `post --type interactive` show a question with a picture: `--view <path>` names one board-relative file, an image (`.svg`, `.png`, `.jpg`, `.jpeg`, `.webp`) drawn at card width for a diagram, or one self-contained `.html` page that runs in a sandboxed frame for interactive. Every mock and view is served under a Content-Security-Policy with no `fetch`, no remote files and no access to the dashboard or its tab. They answer like a decision.
154
- - `post --type report` posts an explainer page the user reads, with no answer: something they asked to have explained, or a concept, design or trade-off they must understand. `sign-off-axi report init <folder>` writes the page to fill, one self-contained `.html` file, phone first, light and dark: the answer in one plain sentence with a picture and an as-of line naming the date the facts were true, then a diagram, then steps the reader taps through, then the detail folded. Reports and explainers lists each one as a card with its picture; a tap opens the page over the whole board, on a phone and a desk alike, with **‹ Reports** (or Escape) to go back. A report of text only is refused with the command that makes the page; one stored before reports had pages still loads, folded under **Notes**. Proof that something works is evidence, and a merge or a status note is neither.
155
- - `post --type explainer` shows a question with a narrated video the agent made itself (stills plus speech from a free local engine such as piper or espeak-ng, joined with ffmpeg; the skill has the recipe). `--media` names one `.mp4` or `.webm`, copied into `evidence/<number>/` and served hash-checked like evidence media; it plays on the card with its own controls, at phone width too. `--about` is a short summary and each `--chapter m:ss=<title>` is a button that plays the video from there. sign-off itself never makes the video: it calls no speech engine, no ffmpeg and no network service.
156
- - The person can also tap **Another round** inside **Other answer** (with the typed note), which wakes a waiting agent the same way an answer does; the agent reads it back in `answers --json` under `rounds`, with any comments on mock elements it carried.
157
- - **Dismiss** (the × on an item in a list, **Dismiss** under Needs you first and beside **Skip for now** in Triage, or **Clear all done** on Answered) hides items from the board, Today included; **Undo** in the toast brings them back, and so does **Restore** under **Dismissed (N)** at the end of Needs you first and Triage. `list` counts a dismissed item as neither waiting nor answered, as the board does (`list --full` says `dismissed` for an unanswered one and still its answer for an answered one), and `answers` does not count it as waiting; the agent is not told, so an ask it waits on keeps waiting, and `answers` still returns a dismissed answer. The storage is in [docs/file-formats.md](docs/file-formats.md).
158
- - The person can comment on any item (**Send as a comment**, also inside **Other answer**: each card has that one place to type) and queue a free-text prompt for the agent (**Ask the agent**). `poll` blocks until one arrives and prints every new one once (`--json` prints `{"messages":[{"id","kind","key"?,"text","at"}]}`); the page then shows it as read by the agent. Typed notes that are not sent yet stay in the browser across reloads; everything sent is saved next to the board and survives a restart.
159
- - The board runs on your own machine only: no hosted service, no recurring cost. It listens on `127.0.0.1` unless `--bind <address>` (or `bind:` in the `config.yaml` that `init` writes) names one private address of this machine, such as its VPN address, so a phone or tablet on your own network can open it. A `hosts:` line in that config names private DNS names that lead to the bind address, such as its mesh VPN name (commas between them), so the board also opens by name; `doctor` checks each one resolves to the bind address. All interfaces and public addresses are refused. Beyond `127.0.0.1` the board makes an access token once (`decisions.token`, mode 600, next to the board), `open` prints it in the link, and every request without it is refused; the link sets a cookie, so the page and its media work after the first load. Delete the token file to shut old links out at once, then `stop` and `open` for a new one.
160
- - `export` writes one self-contained HTML file (`decisions.export.html` by default): every item, answer, note and another-round request with its local mocks and media inlined; comments and queued prompts are left out. `--out` must end in `.html`, so a wrong path never overwrites the board. It opens from disk with no server, is read-only, and makes no network request. A mock page is inlined on its own, so the css and images beside it show only on the running board.
161
- - With no `--option` a question is Yes/No; give 2 to 6 `--option` flags for a pick-one question (`--option friday=Friday --option next-week="Next week"`). `--recommend` and `--default` name one of its values. Listing options needs `--recommend` (on `raise`, `post` and `--from` files): the board shows that option first and marks it; a plain Yes/No may leave it out. Each answer is one tap, with an Undo for a few seconds after it (an Undo is saved as an answer change). Typing is last: every question with options, a design's Picks included, has a text box folded behind **Other answer** (none when an option is valued `text`); the words are saved as the answer `text`, with the same Undo.
162
- - `list` and `answers` take `--full` for every field and `--json` for JSON (`answers --json` prints `{"<key>":{"answer","label","close","at"}}`, plus `"note"` when the user wrote one). `answers` adds a note column when any shown answer has a note.
163
- - Raising a question that is already on the board is a no-op that reports its status, so an agent can re-run it safely. `--key` defaults to a slug of the title.
164
- - Each raised or posted item records when it was filed (`filed`), so Triage shows `Waiting since 09:15` and the Waiting on you tile `Oldest 09:15`; `post --replace` keeps an item's first time.
165
- - `open` is a no-op when the same board is already open on the same address, and fails with `CONFLICT` when it runs on another one. `--evidence-root` is the folder holding `.done-done/`, for decisions that link run evidence.
166
- - `--filer <id>` on `post` and `raise` saves who filed the item (an agent session id or any 1-128 of `A-Z a-z 0-9 . _ -`; `SIGN_OFF_FILER` sets it for every command, and a post with neither saves `CLAUDE_CODE_SESSION_ID` when it is set). `wait --filer <id>` then returns only answers to that filer's items it has not taken yet (with `--key`, that key's answer even if taken), marks each taken (`decisions.seen.jsonl`) and the card shows **Seen by agent**; it fails with `NOT_FOUND` when none of that filer's items still needs an answer. The page never shows the filer id.
167
- - `on_answer: <command> [args]` in the `config.yaml` beside the board runs that command for every saved answer, read when the board starts. It runs with no shell, in the board's folder, and gets the answer as one JSON line on stdin (`{"event":"answer","contract":1,"kind","schema","key","answer","label","close","at","note"?,"title"?,"filer"?,"comments"?}`, where `kind` and `schema` are the item's contract kind, or `ask` and 1 for a board-format item, and `comments`, present when the answer carried comments on mock elements, lists each as `id`, `mock`, `hash`, `ref`, `tag`, `words` and `text`); the answer is never put into its arguments, so a typed note cannot inject a command. A failure is logged and never blocks the answer. With no shell, `|`, `>` and `&&` are passed as plain arguments, so put a pipeline in a script and name the script; a relative path such as `./hook.sh` is found from the board's folder. `doctor` checks that the command can run.
168
- - `post --from <file> --replace --filer <id>` lets several programs feed one board: it replaces only that filer's items (and items with no filer that the file names by key), keeps every other item, the board's other top-level fields and all answers, and an empty items list removes that filer's items. A contract message that changed is checked against its kind's rules again, so one that no longer matches is refused with the board left as it was; an answered contract key the feed dropped cannot come back, so post it under a new key (a design, diagram, interactive or explainer item that comes back keeps its answer).
169
- - `wait` replaces a polling loop. With `--key` it returns at once if that decision is already answered, and waits for a key that is not raised yet; without it, it waits for the next answer to any decision that still needs an answer and fails with `NOT_FOUND` when nothing needs one. It wakes on each save, gives up after `--timeout` seconds (600, ten minutes, by default) with code `TIMEOUT` and exit 1, and takes `--full` and `--json` like `answers`. Ctrl-C stops it without changing anything.
170
- - `open` records the board's pid, port and address next to the board file (`decisions.board.pid`). `status` and `stop` read it and only act on a pid that really serves that board, so no one has to guess a pid to kill. `open` on a board already served by `serve` records that server, so `stop` can close it too.
171
-
172
- ```
173
- $ sign-off-axi raise --title "Keep the weekly digest?" --recommend yes
174
- board: ~/.sign-off/decisions.json
175
- raised[1]{key,status,title}:
176
- keep-the-weekly-digest,raised,Keep the weekly digest?
177
- help[2]:
178
- Run `sign-off-axi open` so the user can answer
179
- Run `sign-off-axi answers --key keep-the-weekly-digest` to read the answer
180
- ```
181
-
182
- Exit codes: `0` ok, including no-ops; `1` the request cannot be done (a key that already holds a different question, a busy port); `2` usage or input error (the message names the item, key and field). Errors print on stdout as `error:`, `code:` and `help:` lines.
183
276
 
184
- Write your own questions using the [file-format reference](docs/file-formats.md).
277
+ Later, when it worked:
278
+
279
+ ```sh
280
+ sign-off-axi signin done \
281
+ --key npm-login
282
+ ```
283
+
284
+ <picture>
285
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-signin-dark.webp">
286
+ <img src="docs/media/kind-signin.webp" alt="A sign-in card on top with an Open button, what it unlocks, who waits and the time left">
287
+ </picture>
288
+
289
+ *A sign-in card: what it unlocks, who waits, the time left.*
290
+
291
+ ### Release gate
292
+
293
+ ```sh
294
+ sign-off-axi post \
295
+ --kind gate \
296
+ --from gate.json
297
+ ```
298
+
299
+ A `gate.json` holds a run for the user: Approve or Fix first.
300
+
301
+ <picture>
302
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-gate-dark.webp">
303
+ <img src="docs/media/kind-gate.webp" alt="A gate question with Approve recommended and Fix first">
304
+ </picture>
305
+
306
+ *A held run: Approve it or Fix first.*
307
+
308
+ ### Retro finding
309
+
310
+ ```sh
311
+ sign-off-axi post \
312
+ --kind finding \
313
+ --from finding.json
314
+ ```
315
+
316
+ A `finding.json` asks the user to Apply or Skip a proposed fix.
317
+
318
+ <picture>
319
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/kind-finding-dark.webp">
320
+ <img src="docs/media/kind-finding.webp" alt="A finding question with Apply recommended and Skip">
321
+ </picture>
322
+
323
+ *A retro fix to Apply or Skip.*
324
+
325
+ ## On your phone
326
+
327
+ The same board on a phone, light and dark.
328
+
329
+ <picture>
330
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/phone-dark.webp">
331
+ <img src="docs/media/phone-light.webp" width="320" alt="Board summary on a phone in light mode">
332
+ </picture>
333
+
334
+ *The summary, the waiting list, and the answers, in a phone frame.*
185
335
 
186
336
  ## On your screen
187
337
 
188
- Real demo questions, with one Yes and its note already saved. Desktop shots are cropped to the list, not redrawn.
338
+ The waiting list and the summary at desk width, light and dark.
189
339
 
190
- **Desktop · dark**
340
+ <picture>
341
+ <source media="(prefers-color-scheme: dark)" srcset="docs/media/desktop-dark.webp">
342
+ <img src="docs/media/desktop-light.webp" alt="Board summary at desk width in light mode">
343
+ </picture>
191
344
 
192
- <img src="docs/media/desktop-dark-list.png" width="860" alt="Dark desktop: the same question list, with the saved Yes highlighted in purple and the note shown under it">
345
+ *Everything in one glance: waiting, evidence, explainers, answered.*
193
346
 
194
- **Phone · light and dark**
347
+ ## How agents use it
195
348
 
196
- <p>
197
- <img src="docs/media/phone-light.png" width="300" alt="Decisions page on a phone in light mode">
198
- <img src="docs/media/phone-dark.png" width="300" alt="Decisions page on a phone in dark mode">
199
- </p>
349
+ Every item records who filed it, and that agent gets the answer as an
350
+ event, by itself: the agent raises with `--filer my-agent` and blocks on
351
+ `sign-off-axi wait --filer my-agent`, which returns only that filer's
352
+ answers, each once, and the card shows **Seen by agent**. An agent that
353
+ cannot block can be woken instead: an `on_answer:` line in the
354
+ `config.yaml` beside the board names a command the board runs for every
355
+ answer. The full reference, with the worked example, is in
356
+ [docs/commands.md](docs/commands.md).
357
+
358
+ Every command takes `--help` and prints compact output with next-step hints.
359
+ sign-off itself calls no model and inherits the calling agent's model routing.
360
+
361
+ ## Install
362
+
363
+ ```sh
364
+ npm install -g sign-off-axi
365
+ ```
366
+
367
+ In a project folder, one command sets up `.sign-off/` and installs the
368
+ skill for each coding agent on your `PATH`:
369
+
370
+ ```sh
371
+ npx -y sign-off-axi init
372
+ ```
373
+
374
+ ## Docs and reference
375
+
376
+ - [Commands](docs/commands.md): every command, the agent setup, exit codes.
377
+ - [Question kinds](docs/question-kinds.md): the reference for each kind.
378
+ - [Contract](docs/contract.md): the message rules agents post by.
379
+ - [File formats](docs/file-formats.md): board and message files.
200
380
 
201
381
  ## Development
202
382
 
383
+ Builds first, then runs the integration journeys against the real CLI:
384
+
203
385
  ```sh
204
- npm test # builds, then runs the integration journeys against the real CLI and server
205
- npm run build:skill # regenerates skills/sign-off-axi/SKILL.md from src/skill.ts (CI runs it with -- --check)
206
- npm pack # builds the publishable sign-off-axi tarball
386
+ npm test
387
+ npm run build:skill
388
+ npm run build:demo
389
+ npm pack
207
390
  ```
208
391
 
392
+ - `npm test`: full journeys
393
+ - `npm run build:skill`: skill stub
394
+ - `npm run build:demo`: demo site (Try it live)
395
+ - `npm pack`: tarball
396
+
209
397
  A release is a merged version-bump PR: CI publishes the new version to npm.
210
398
 
211
399
  ### Test runs never touch your board
212
400
 
213
- With no board named, a bare command uses the nearest `.sign-off/` that `init` set up, looking from the working folder up to its repo root (the folder holding `.git`) or up to HOME, but never HOME's own `.sign-off/`. The working folder's own `.sign-off/` is always used. A folder above it is searched only when that climb reaches a repo root or HOME, so a `.sign-off/` above a folder with neither (outside HOME, no `.git`) is never used, and the account's own home is never used. Otherwise the default board is `~/.sign-off/decisions.json`, under `SIGN_OFF_HOME` when it is set (it takes HOME's place for the default board and for where the climb stops), else under HOME.
214
-
215
- A test or agent run that must not reach your own board sets HOME and every `XDG_*` folder to one fresh temporary folder, or sets `SIGN_OFF_HOME` to one. A board file named on the command line always wins, so name only a board inside the temporary folder, and spell it out in full: a `~` is expanded by the calling shell with its own HOME before `HOME=...` takes effect. Run the command from the temporary folder to keep project boards out too.
401
+ With no board named, a bare command uses the nearest `.sign-off/` that
402
+ `init` set up, looking from the working folder up to its repo root (the
403
+ folder holding `.git`) or up to HOME, but never HOME's own `.sign-off/`.
404
+ Otherwise the default board is `~/.sign-off/decisions.json`, under
405
+ `SIGN_OFF_HOME` when it is set. A test or agent run that must not reach your
406
+ own board sets HOME and every `XDG_*` folder to one fresh temporary folder,
407
+ or sets `SIGN_OFF_HOME` to one. A board file named on the command line
408
+ always wins.
216
409
 
217
410
  ```sh
218
411
  tmp=$(mktemp -d)
219
- (cd "$tmp" && SIGN_OFF_HOME="$tmp" sign-off-axi raise --title "Probe?") # lands in $tmp/.sign-off/decisions.json
412
+ export SIGN_OFF_HOME="$tmp"
413
+ (cd "$tmp" &&
414
+ sign-off-axi raise \
415
+ --title "Probe?")
220
416
  ```
221
417
 
222
- Proof lives in [`.done-done.yaml`](.done-done.yaml): the `install`, `test` and `skill` journeys re-run every check the CI gate enforces, and CI calls them through `done-done run`.
418
+ Proof lives in [`.done-done.yaml`](.done-done.yaml): the `install`, `test`
419
+ and `skill` journeys re-run every check the CI gate enforces, and CI calls
420
+ them through `done-done run`.
421
+
422
+ Write your own questions using the [file-format reference](docs/file-formats.md).