sign-off-axi 0.1.0 → 0.3.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
@@ -20,7 +20,7 @@
20
20
 
21
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.
22
22
 
23
- sign-off puts them on one calm list. The agent writes a small JSON file, you inspect the linked done-done run 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 only on `127.0.0.1`: no account, no hosting.
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.
24
24
 
25
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.
26
26
 
@@ -28,10 +28,26 @@ Your answer survives reloads and restarts; you can change it with one more click
28
28
 
29
29
  ### Agent skill (recommended)
30
30
 
31
- Install the sign-off skill in the [Agent Skills](https://agentskills.io) format with [`npx skills`](https://github.com/vercel-labs/skills):
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:
32
32
 
33
33
  ```sh
34
- npx skills add umeranjum17/sign-off --skill sign-off-axi
34
+ npx -y sign-off-axi init
35
+ ```
36
+
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.
38
+
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/` |
46
+
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:
48
+
49
+ ```sh
50
+ npx skills add umeranjum17/sign-off --skill sign-off-axi -a <agent> # add -g to install for all your projects
35
51
  ```
36
52
 
37
53
  That is the entire setup - no npm install needed.
@@ -39,15 +55,41 @@ The skill teaches your agent to run sign-off through `npx -y sign-off-axi`, so t
39
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.
40
56
  Its frontmatter also includes Hermes Agent metadata, so Hermes-compatible harnesses can categorize and surface it as a first-class productivity skill.
41
57
 
42
- Then, in agents that expose skills as slash commands (Claude Code, for example), invoke it directly:
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?`.
43
59
 
44
- ```
45
- /sign-off-axi should we keep the weekly digest?
46
- ```
60
+ ### Your answer reaches the agent that asked
47
61
 
48
- Or just 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.
62
+ Every item records who filed it, and that agent gets your answer as an event, by itself:
49
63
 
50
- By default the skill lands in the current project's skills directory (`.claude/skills/`, for example); add `-g` to install it for all projects (`~/.claude/skills/`).
64
+ ```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
67
+ ```
68
+
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).
70
+
71
+ ### Repo setup
72
+
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.
89
+
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).
51
93
 
52
94
  ### Manual CLI
53
95
 
@@ -74,23 +116,54 @@ sign-off itself calls no model and inherits the calling agent's model routing.
74
116
  ```
75
117
  sign-off-axi list the decisions waiting on the user (same as list)
76
118
  sign-off-axi raise --title "<question>" [--key <key>] [--about <text>] [--detail <text>]
77
- [--option <value>=<label>]... [--recommend <value>] [--default <value>] [--close done|release]
78
- sign-off-axi raise --from <decisions.json> add every decision in a decisions file
79
- sign-off-axi open [--port 4410] [--evidence-root <dir>] start the board in the background; prints its URL and pid
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 init [dir] [--agent auto|<name>] write the repo setup under <dir>/.sign-off/ (config, board, mocks/ and evidence/ folders)
132
+ and install the skill for each chosen coding agent (auto: each one on PATH)
133
+ sign-off-axi doctor [dir] check that setup and each agent's skill; every failure names its fix
134
+ sign-off-axi validate <message.json>... [--kind <kind>] [--expect-invalid] check ask and report messages against the contract (docs/contract.md) without posting
135
+ sign-off-axi open [--port 4410] [--bind <address>] [--evidence-root <dir>] start the board in the background; prints its URL and pid
80
136
  sign-off-axi status is the board open started for this file running? its URL and pid
81
137
  sign-off-axi stop stop that board
82
- sign-off-axi serve [--port 4410] [--evidence-root <dir>] run the board in the foreground (--port 0 picks a free port)
138
+ sign-off-axi serve [--port 4410] [--bind <address>] [--evidence-root <dir>] run the board in the foreground (--port 0 picks a free port)
83
139
  sign-off-axi answers [--key <key>] the latest answer per decision, and how many still wait
84
- sign-off-axi wait [--key <key>] [--timeout 600] block until the user answers, then print it like answers
140
+ sign-off-axi wait [--filer <id>] [--key <key>] [--timeout 600] block until the user answers, then print it like answers
141
+ (--filer: only that filer's items; without --key each answer once)
142
+ sign-off-axi poll [--timeout 600] block until the user comments or queues a prompt, then print each new one once
143
+ sign-off-axi export [--out <file.html>] write a frozen read-only copy of the board as one HTML file
85
144
  ```
86
145
 
87
- - The board is `~/.sign-off/decisions.json` unless you name a file right after the command (`sign-off-axi answers ./decisions.json`). Answers are saved next to it.
88
- - 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.
146
+ - 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.
147
+ - 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`.
148
+ - `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`) 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, kept on the board and shown only on the exact mock file it was made on. 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.
149
+ - `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>`.
150
+ - `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.
151
+ - `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.
152
+ - 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`.
153
+ - **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. The agent is not told, so an ask it waits on keeps waiting. The storage is in [docs/file-formats.md](docs/file-formats.md).
154
+ - 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.
155
+ - 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.
156
+ - `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.
157
+ - 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.
89
158
  - `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.
90
159
  - 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.
91
- - `open` is a no-op when the same board is already open. `--evidence-root` is the folder holding `.done-done/`, for decisions that link run evidence.
160
+ - 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.
161
+ - `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.
162
+ - `--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, 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 is answered or waiting. The page never shows the filer id.
163
+ - `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 (`{"key","answer","label","close","at","note"?,"title"?,"filer"?}`); 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.
164
+ - `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.
92
165
  - `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 still waiting and fails with `NOT_FOUND` when nothing is. 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.
93
- - `open` records the board's pid and port 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.
166
+ - `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.
94
167
 
95
168
  ```
96
169
  $ sign-off-axi raise --title "Keep the weekly digest?" --recommend yes
@@ -129,4 +202,15 @@ npm run build:skill # regenerates skills/sign-off-axi/SKILL.md from sr
129
202
  npm pack # builds the publishable sign-off-axi tarball
130
203
  ```
131
204
 
205
+ ### Test runs never touch your board
206
+
207
+ 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.
208
+
209
+ 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.
210
+
211
+ ```sh
212
+ tmp=$(mktemp -d)
213
+ (cd "$tmp" && SIGN_OFF_HOME="$tmp" sign-off-axi raise --title "Probe?") # lands in $tmp/.sign-off/decisions.json
214
+ ```
215
+
132
216
  Browser proof lives in [`.agents/skills/verify-sign-off`](.agents/skills/verify-sign-off/SKILL.md).