@figs-so/cli 0.6.0 → 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.
Files changed (3) hide show
  1. package/SPEC.md +1 -0
  2. package/figs.mjs +42 -11
  3. package/package.json +1 -1
package/SPEC.md CHANGED
@@ -134,6 +134,7 @@ primitive** — the agent reached the edge of its autonomy.
134
134
  | `found` | string | | What the agent found / why it's stuck. |
135
135
  | `need` | string | | What it needs from the human. |
136
136
  | `options` | string[] | | Candidate resolutions — **short, stable, quotable** strings: an answer references one *verbatim* (see [§6.2](#62-resolution--how-an-ask-closed)). On a **sign-off** they are **answer paths** — qualified verdicts the human's verdict can cite verbatim alongside approve/request-changes (e.g. `"Approved — file the 15 ready charges"`). |
137
+ | `onApprove` | string[] | | **Sign-off only.** The ordered steps approval sets in motion — **an approval authorizes exactly these stated steps, in order** (e.g. `"Post the 8 journal entries to SAP"`, `"Email the filing to Acme"`); flag anything irreversible in the step itself. This is the agent's **declared intent, not a bound plan** — readers present it as the agent's claim. Invalid on other types: a *needs-decision* has no approval; there, the chosen option carries the next step. |
137
138
  | `details` | `{ l, v }[]` | | Labelled facts (e.g. amount at risk). |
138
139
  | `refs` | `{ label, artifact? }[]` | | Pointers to artifacts that back the ask. |
139
140
  | `resolution` | string \| `Resolution` | | The agent's account of the close ([§6.2](#62-resolution--how-an-ask-closed)). A bare string is shorthand for `{ "note": … }`. |
package/figs.mjs CHANGED
@@ -134,27 +134,31 @@ const COMMANDS = {
134
134
  ask: {
135
135
  args: "<type> --title <text> [options]",
136
136
  flags: [
137
- "--id", "--title", "--need", "--found", "--option", "--detail", "--attach",
138
- "--to", "--unit", "--run", "--stdin", "--no-push",
137
+ "--id", "--title", "--need", "--found", "--option", "--on-approve", "--detail",
138
+ "--attach", "--to", "--unit", "--run", "--stdin", "--no-push",
139
139
  ],
140
140
  desc: "raise an ask — one self-contained line in asks.jsonl, pushed so a human sees it",
141
141
  more: [
142
142
  "<type> = the answer contract: needs-decision (give me an answer) ·",
143
143
  "sign-off (give me a verdict) · fyi (no answer — a for-the-record note).",
144
- "Make it self-contained — a future session with zero context (or another human)",
145
- "must be able to act from this record alone: --found (what you saw), --need (what",
146
- "you need), --option (repeatable; short, stable, quotable — answers cite one",
147
- "verbatim), --detail 'Label=Value' (repeatable), --attach <file> (repeatable;",
148
- "for sign-offs attach the exact content for review + a brief: what to do once",
149
- "approved and what it requires).",
144
+ "Two strangers read every ask — a human deciding, a future session acting;",
145
+ "the record must carry everything both need: --found (what you saw), --need",
146
+ "(what you need), --option (repeatable; short, stable, quotable — answers cite",
147
+ "one verbatim; the option is the label, context goes in --found/--detail),",
148
+ "--detail 'Label=Value' (repeatable), --attach <file> (repeatable; a verdict",
149
+ "blesses what the ask carries — attach the exact content for review + a brief:",
150
+ "what to do once approved and what it requires).",
150
151
  "On a sign-off, --option entries are answer paths — the human's verdict can",
151
- "cite one verbatim ('Approved — file the 15 ready charges').",
152
+ "cite one verbatim ('Approved — file the 15 ready charges') — and",
153
+ "--on-approve '<step>' (repeatable, ordered; sign-off only) states what",
154
+ "approval sets in motion: an approval authorizes exactly the steps you stated.",
155
+ "Flag anything irreversible in the step itself.",
152
156
  "--run <run-id> links the run this came out of — explicit id only (other",
153
157
  "sessions may report concurrently; `figs report` prints the id it wrote).",
154
158
  "--stdin reads a full JSON object instead of flags (long texts; attachments still via --attach).",
155
159
  "Single-quote prose values ('…') — double quotes let your shell eat $ amounts.",
156
160
  ],
157
- eg: "figs ask sign-off --title 'Send 10 payment reminders' --attach ./previews.html --run recon-2026-06",
161
+ eg: "figs ask sign-off --title 'Send 10 payment reminders' --attach ./previews.html --on-approve 'Send the 10 reminder emails' --on-approve 'Mark the invoices chased' --run recon-2026-06",
158
162
  },
159
163
  inbox: {
160
164
  args: "[<ask-id>] [--json]",
@@ -346,6 +350,15 @@ function validateAsk(a) {
346
350
  if (a.options !== undefined && (!Array.isArray(a.options) || a.options.some((o) => typeof o !== "string"))) {
347
351
  issues.push(`${label}.options: must be an array of short, quotable strings`)
348
352
  }
353
+ if (a.onApprove !== undefined) {
354
+ if (!Array.isArray(a.onApprove) || a.onApprove.some((s) => typeof s !== "string")) {
355
+ issues.push(`${label}.onApprove: must be an array of strings — the ordered steps approval sets in motion`)
356
+ } else if (a.type !== "sign-off") {
357
+ issues.push(
358
+ `${label}.onApprove: sign-off only — it is the approval contract; a ${a.type ?? "non-sign-off ask"} has no approval (the chosen option carries the next step)`,
359
+ )
360
+ }
361
+ }
349
362
  if (a.details !== undefined && (!Array.isArray(a.details) || a.details.some((d) => !d || typeof d.l !== "string"))) {
350
363
  issues.push(`${label}.details: must be [{ "l": "Label", "v": "Value" }]`)
351
364
  }
@@ -1082,7 +1095,12 @@ function nextMove(a) {
1082
1095
  if (last.kind === "verdict" && last.verdict === "changes_requested") {
1083
1096
  return `revise, then re-raise on the same id: figs ask ${a.type} --id ${a.id} --title '…' …`
1084
1097
  }
1085
- return `act on the answer (real work → figs report it under its own --id), then: figs resolve ${a.id} --chosen '…'`
1098
+ if (last.chosen) {
1099
+ // Pre-fill the cited option verbatim — the note is substance for --note, never for --chosen.
1100
+ const note = last.text ? ` --note '…'` : ""
1101
+ return `act on the answer (real work → figs report it under its own --id), then: figs resolve ${a.id} --chosen '${last.chosen}'${note}`
1102
+ }
1103
+ return `act on the answer (real work → figs report it under its own --id), then: figs resolve ${a.id} --note '…'`
1086
1104
  }
1087
1105
 
1088
1106
  /** Restore an ask's refs into artifacts/ — hash-verified; never clobbers. */
@@ -1366,6 +1384,13 @@ async function askCmd() {
1366
1384
  )
1367
1385
  }
1368
1386
  }
1387
+ const onApprove = flagAll("--on-approve")
1388
+ if (onApprove.length) ask.onApprove = onApprove
1389
+ if (ask.onApprove?.length && ask.type !== "sign-off") {
1390
+ die(
1391
+ `--on-approve is the approval contract — sign-off only. A ${ask.type} has no approval; the chosen option carries the next step (put it in the --option text)`,
1392
+ )
1393
+ }
1369
1394
  const details = flagAll("--detail").map((d) => {
1370
1395
  const i = d.indexOf("=")
1371
1396
  if (i < 1) die(`--detail must be "Label=Value", got "${d}"`)
@@ -1388,11 +1413,17 @@ async function askCmd() {
1388
1413
  "figs: ! tip: a sign-off reviews best with attachments — the exact content to approve, plus a brief (what to do once approved + what it requires). Add --attach <file>",
1389
1414
  )
1390
1415
  }
1416
+ if (ask.type === "sign-off" && !ask.onApprove?.length) {
1417
+ console.warn(
1418
+ "figs: ! tip: state what approval sets in motion — --on-approve '<step>' (repeatable, ordered); an approver shouldn't have to guess what approve causes",
1419
+ )
1420
+ }
1391
1421
  warnEatenDollar(
1392
1422
  ask.title,
1393
1423
  ask.found,
1394
1424
  ask.need,
1395
1425
  ask.options ?? [],
1426
+ ask.onApprove ?? [],
1396
1427
  (ask.details ?? []).flatMap((d) => [d.l, d.v]),
1397
1428
  )
1398
1429
  const issues = validateAsk(ask)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@figs-so/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Figs CLI — publish your AI agent's state to Figs (figs.so). Run by the agent.",
5
5
  "type": "module",
6
6
  "bin": {