@decidio/sdk 0.6.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ Release notes for the client SDK. The Python package `decidio` ships the same ch
4
4
  version (the two are conformance-locked twins). Each entry names what changed, what a reader must do
5
5
  on upgrade, and what proved it.
6
6
 
7
+ ## 0.6.1 — the first-run fork, and the local demo's loose ends
8
+
9
+ Two independent first-run reviews of 0.6.0 (grades B / A− overall) agreed on one thing: the hosted tour and the
10
+ verifier earn trust, and the new local demo had polish debts. This release pays them and adds the one question a
11
+ stranger's first minute was missing (founder ruling 2026-09-06).
12
+
13
+ - **One question, then the thing you picked.** A bare `npx @decidio/sdk quickstart` (or a bare `npx @decidio/sdk`) on
14
+ a terminal with no sign-in asks: do you have a Decidio invite? `n` runs the local demo; `y` signs you in (the emailed
15
+ link) and asks what next: the two-minute tour, or wiring up your own agent (`init`). Every answer prints the exact
16
+ command it stands for, so the questions teach their own flags and you never need them twice. Nothing is stored;
17
+ Enter is not an answer; scripts and CI never see a question (they get today's text, which now also names `--demo`).
18
+ A signed-in `quickstart` runs straight away; the bare `npx @decidio/sdk` for a signed-in user skips the invite
19
+ question and asks only "what next?" - the bare command always leads somewhere, usage is a `--help` away. After
20
+ the sign-in step, the tour does not ask you to sign in again.
21
+ - **Your agent's name has a real default.** `init` with no name used `my-agent`; it now uses this folder's name (or
22
+ the package name), slugified, and the fork explains what the name is for ("Requested by …", the key `.env` is
23
+ written under). Any name still works.
24
+ - **A mark, not a banner:** the title lines carry the app's ✦ beside the name.
25
+ - **Headless `--demo` leaves nothing behind** (review: Medium). The stop at the human moment used to exit inside the
26
+ tour, skipping the wrapper that removes the temporary folder - the mock key, mock token and journal stayed in the
27
+ temp dir, and the message pointed at the hosted tour's remedies (`approvals approve`, `quickstart cleanup`), which
28
+ cannot act on a stand-in that lives only in that process. The stop now returns through the wrapper, and says the
29
+ demo's truth: nothing executed, nothing kept, re-run interactively or with `--auto-approve-demo`.
30
+ - **The demo receipt carries what the terminal claims** (review: Medium). Its canonical record now holds the real
31
+ amount and currency, the reason the decision arrived with (the automated flag names itself there, exactly as it does
32
+ in a hosted record's reason), and how it was decided (terminal, page, or the flag). The party stays tokenized, as
33
+ the real seal keeps it.
34
+ - **`--demo --keep-agent` is refused** (both reviews: Low) - nothing of the demo's can be kept, and the flag used to
35
+ be accepted while the opposite happened. Exit 2, with the reason.
36
+ - **A receipt the demo could not copy home is not a completed tour** (retest: Low): the temporary folder is kept and
37
+ named, and the run exits non-zero, instead of deleting the only copy and exiting 0.
38
+ - **Words that were not true** (review: Low): the demo's Target line no longer says "nothing hosted" while the web
39
+ door uses a hosted relay; the automated line names the demo's mock credential, not "your session credential"; a
40
+ repeat run's ④ frame names the suffixed file that actually lands; the README no longer calls the sandbox "one shared
41
+ lab workspace" in two places while saying the opposite in a third.
42
+
7
43
  ## 0.6.0 — `quickstart --demo`: the whole tour on your machine, no account
8
44
 
9
45
  An uninvited developer could not run the tour at all: a workspace needs an invite (no open signup, by ruling). A
package/README.md CHANGED
@@ -11,6 +11,9 @@ is the two-minute tour, the sign-in and the token admin around it. Install, wrap
11
11
  ```bash
12
12
  npx @decidio/sdk quickstart # against your workspace (you need an invite - request one below)
13
13
  npx @decidio/sdk quickstart --demo # no invite yet? the same tour on this machine, against a local stand-in of the API
14
+ # Not sure? The bare command asks: with no sign-in on this computer it asks whether you have an
15
+ # invite (n → the local demo; y → sign in, then the tour or your own agent). Every answer prints
16
+ # the command it stands for, so you never need the questions twice.
14
17
  ```
15
18
 
16
19
  One command, guided: it registers a fresh agent (key generated locally), gates a demo action
@@ -137,8 +140,8 @@ the agent's floor-limited token — it cannot approve anything):
137
140
 
138
141
  ```bash
139
142
  npx @decidio/sdk approvals --mine # see it pending (--mine narrows to THIS agent; the
140
- # sandbox is one shared lab workspace, so the bare
141
- # command lists every member's pending decisions)
143
+ # workspace is yours alone unless you invite others,
144
+ # so the bare command lists every pending decision in it)
142
145
  npx @decidio/sdk approvals approve <decisionId>
143
146
  ```
144
147
 
@@ -747,7 +750,7 @@ the next reader grades a promise, not a moving target:
747
750
 
748
751
  ## Using this for real?
749
752
 
750
- The hosted sandbox is synthetic and shared. Production workspaces are per-tenant, with your own
753
+ The hosted sandbox is synthetic data in a workspace of your own. Production workspaces are per-tenant too, with your own
751
754
  issuer key and receipts that verify offline against it.
752
755
 
753
756
  [decidioai.com](https://decidioai.com) · [Developer docs](https://decidioai.com/developers/) · [Request access](https://decidioai.com/#early-access)
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  export type ApprovalAnswer = "approve" | "reject" | "again";
3
3
  export declare function approvalAnswer(answer: unknown): ApprovalAnswer;
4
+ /** The agent id `init` uses when none is given: this folder's name (or the package name), slugified. "my-agent" told a
5
+ * tester nothing; the folder they are in is the agent they are building (founder ruling 2026-09-06). */
6
+ export declare function defaultAgentId(dir?: string): string;
4
7
  /** `decidio-receipt-<id>.json`, with every character outside [A-Za-z0-9_-] escaped as `~` + hex code point, so two
5
8
  * distinct decision ids can never share a file (first-run review of 0.4.1: dots were STRIPPED, `a.b` and `ab`
6
9
  * collided). `~` is outside the allowed set, so the escape is unambiguous. Exported for the smokes. */
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { readFileSync, writeFileSync, existsSync, appendFileSync, chmodSync, rmSync, openSync, writeSync, fsyncSync, closeSync, renameSync, mkdirSync, mkdtempSync, readdirSync, copyFileSync } from "node:fs";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { homedir, tmpdir } from "node:os";
5
- import { join as joinPath, resolve as resolvePath, sep as pathSep } from "node:path";
5
+ import { join as joinPath, resolve as resolvePath, sep as pathSep, basename } from "node:path";
6
6
  import { createServer } from "node:http";
7
7
  import { execFileSync, spawnSync } from "node:child_process";
8
8
  import { createInterface } from "node:readline";
@@ -416,6 +416,7 @@ const paint = (code, s) => (TTY_OUT ? `\x1b[${code}m${s}\x1b[0m` : s);
416
416
  const bold = (s) => paint("1", s);
417
417
  const dim = (s) => paint("2", s);
418
418
  const link = (url) => (TTY_OUT ? `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\` : url);
419
+ const brand = () => `${paint("38;5;178", "✦")} ${paint("38;5;29", bold("Decidio"))}`;
419
420
  const FRAME_WIDTH = 84;
420
421
  const visibleLength = (s) => s.replace(/\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\/g, "").length;
421
422
  function frameWidth(rows) {
@@ -608,12 +609,79 @@ async function acquireSession(purpose) {
608
609
  if (!process.stdin.isTTY) {
609
610
  err(`✘ ${purpose} needs a Decidio workspace sign-in, and this terminal can't prompt for one.`);
610
611
  err(` Fix: run \`npx @decidio/sdk login\` in an interactive terminal first (the sign-in is stored for this terminal),`);
611
- err(` or set DECIDIO_SESSION_TOKEN. To mint one by hand: request a sign-in link, then`);
612
+ err(` or set DECIDIO_SESSION_TOKEN - or, with no account at all: npx @decidio/sdk quickstart --demo. To mint one by hand: request a sign-in link, then`);
612
613
  err(` curl -X POST ${API}/demo/login-consume -H "content-type: application/json" -d '{"token":"<login value>"}' # the response's "token"`);
613
614
  return null;
614
615
  }
615
616
  return loginFlow(purpose);
616
617
  }
618
+ export function defaultAgentId(dir = process.cwd()) {
619
+ let name = "";
620
+ try {
621
+ const pkg = JSON.parse(readFileSync(joinPath(dir, "package.json"), "utf8"));
622
+ if (typeof pkg?.name === "string")
623
+ name = pkg.name.replace(/^@[^/]+\//, "");
624
+ }
625
+ catch { }
626
+ if (!name)
627
+ name = basename(dir);
628
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
629
+ return slug || "my-agent";
630
+ }
631
+ async function firstRunFork(signedInAs = null) {
632
+ const pick = async (prompt, ok) => {
633
+ let a = "";
634
+ for (let i = 0; i < 3 && !ok.test(a); i++)
635
+ a = (await ask(i === 0 ? " › " : ` Please answer ${prompt}: `)).trim().toLowerCase();
636
+ return ok.test(a) ? a : "";
637
+ };
638
+ log("");
639
+ if (signedInAs !== null) {
640
+ log(` Signed in${signedInAs ? ` as ${signedInAs}` : ""} on this computer.`);
641
+ }
642
+ else {
643
+ log(` Do you have a Decidio invite? ${dim("[y/n]")}`);
644
+ log(` ${bold("n")} the local demo on this machine, no account ${dim("npx @decidio/sdk quickstart --demo")}`);
645
+ log(` ${bold("y")} sign in to your workspace ${dim("npx @decidio/sdk login")}`);
646
+ const a = await pick("y or n", /^[yn]$/);
647
+ if (!a) {
648
+ log(` No answer — nothing was done. Run either command above when you are ready.`);
649
+ return { kind: "stop", code: 1 };
650
+ }
651
+ if (a === "n") {
652
+ log(` → running: npx @decidio/sdk quickstart --demo`);
653
+ log("");
654
+ return { kind: "demo" };
655
+ }
656
+ log(` → running: npx @decidio/sdk login`);
657
+ const token = await loginFlow("Signing in");
658
+ if (!token)
659
+ return { kind: "stop", code: 1 };
660
+ }
661
+ log("");
662
+ log(` What next? ${dim("[1/2]")}`);
663
+ log(` ${bold("1")} the two-minute tour against your workspace ${dim("npx @decidio/sdk quickstart")}`);
664
+ log(` ${bold("2")} wire up your own agent now (register, token, .env) ${dim("npx @decidio/sdk init <agentId>")}`);
665
+ const b = await pick("1 or 2", /^[12]$/);
666
+ if (!b) {
667
+ log(` No answer — you are signed in; run either command above when you are ready.`);
668
+ return { kind: "stop", code: 0 };
669
+ }
670
+ if (b === "1") {
671
+ log(` → running: npx @decidio/sdk quickstart ${dim("(signed in - it will not ask again)")}`);
672
+ log("");
673
+ return { kind: "tour" };
674
+ }
675
+ const def = defaultAgentId();
676
+ log(` Your agent needs a name. It is how the workspace shows the request ("Requested by ${def.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}")`);
677
+ log(` and the key .env is written under. Any name works; Enter takes the folder's.`);
678
+ const typed = (await ask(` agent id [${def}]: `)).trim();
679
+ const agentId = typed || def;
680
+ log(` → running: npx @decidio/sdk init ${agentId} ${dim("(next time, skip the questions: run that directly)")}`);
681
+ log("");
682
+ return { kind: "init", agentId };
683
+ }
684
+ const forkApplies = () => !!(process.stdin.isTTY && process.stdout.isTTY) && !process.env.DECIDIO_SESSION_TOKEN && !loadSession();
617
685
  async function cmdLogin(emailArg) {
618
686
  try {
619
687
  assertSafeTransport(API, true);
@@ -994,7 +1062,10 @@ async function cmdApprovals(sub, id, reason, flags = {}) {
994
1062
  return 0;
995
1063
  }
996
1064
  export function receiptFileName(id) {
997
- return `decidio-receipt-${id.replace(/[^A-Za-z0-9_-]/g, (c) => "~" + c.codePointAt(0).toString(16).padStart(2, "0"))}.json`;
1065
+ const base = `decidio-receipt-${id.replace(/[^A-Za-z0-9_-]/g, (c) => "~" + c.codePointAt(0).toString(16).padStart(2, "0"))}.json`;
1066
+ if (DEMO_ORIGIN && DEMO_STAMP && existsSync(joinPath(DEMO_ORIGIN, base)))
1067
+ return base.replace(/\.json$/, `.${DEMO_STAMP}.json`);
1068
+ return base;
998
1069
  }
999
1070
  function writeReceiptFile(name, bytes, say = log) {
1000
1071
  if (existsSync(name)) {
@@ -1155,17 +1226,28 @@ function valuedOpt(args, flag) {
1155
1226
  const eq = args.find((x) => x.startsWith(flag + "="));
1156
1227
  return eq ? eq.slice(flag.length + 1) : undefined;
1157
1228
  }
1229
+ let DEMO_ORIGIN = null;
1230
+ let DEMO_STAMP = null;
1158
1231
  async function cmdQuickstart(opts = {}) {
1159
1232
  if (!opts.demo)
1160
1233
  return cmdQuickstartInner(opts);
1234
+ if (opts.keepAgent) {
1235
+ err(`✘ --keep-agent has nothing to keep under --demo: the demo's agent and token live in a temporary folder that is removed at the end (only the receipt stays).`);
1236
+ err(` Drop --keep-agent, or run the hosted tour (npx @decidio/sdk quickstart) where --keep-agent keeps a real agent + token.`);
1237
+ return 2;
1238
+ }
1161
1239
  const relayBase = SHELL_ENV.DECIDIO_DEMO_RELAY_URL === undefined ? "https://decidio-api.onrender.com" : (SHELL_ENV.DECIDIO_DEMO_RELAY_URL || null);
1162
1240
  const gate = await startDemoGate({ relayBase });
1163
1241
  enterDemo(gate.url);
1164
1242
  const origin = process.cwd();
1165
1243
  const work = mkdtempSync(joinPath(tmpdir(), "decidio-demo-"));
1244
+ DEMO_ORIGIN = origin;
1245
+ DEMO_STAMP = String(Date.now());
1166
1246
  process.chdir(work);
1247
+ let code = 1;
1248
+ let copyFailed = false;
1167
1249
  try {
1168
- return await cmdQuickstartInner(opts);
1250
+ code = await cmdQuickstartInner(opts);
1169
1251
  }
1170
1252
  finally {
1171
1253
  process.chdir(origin);
@@ -1180,12 +1262,17 @@ async function cmdQuickstart(opts = {}) {
1180
1262
  log(` receipt kept here: ${target.slice(origin.length + 1)} ${dim("(the demo's agent, token and files lived in a temporary folder, now removed)")}`);
1181
1263
  }
1182
1264
  catch (e) {
1183
- err(` ⚠ could not copy the receipt into this folder (${e?.message ?? e}) — it is at ${joinPath(work, f)}`);
1265
+ copyFailed = true;
1266
+ err(` ✘ could not copy the receipt into this folder (${e?.message ?? e}) — the temporary folder is KEPT so the receipt is not lost: ${joinPath(work, f)}`);
1184
1267
  }
1185
1268
  }
1186
- rmSync(work, { recursive: true, force: true });
1269
+ if (!copyFailed)
1270
+ rmSync(work, { recursive: true, force: true });
1187
1271
  await gate.close();
1272
+ DEMO_ORIGIN = null;
1273
+ DEMO_STAMP = null;
1188
1274
  }
1275
+ return copyFailed ? (code || 1) : code;
1189
1276
  }
1190
1277
  async function cmdQuickstartInner(opts = {}) {
1191
1278
  const t0 = Date.now();
@@ -1194,9 +1281,9 @@ async function cmdQuickstartInner(opts = {}) {
1194
1281
  err(`✘ --phone takes app (default: the QR opens the installed app when there is one) or web (the QR opens the phone web rendering, always).`);
1195
1282
  return 2;
1196
1283
  }
1197
- log(bold("Decidio quickstart") + " — gate an action, route it to a human, execute on approval, hold the proof.");
1284
+ log(brand() + bold(" quickstart") + " — gate an action, route it to a human, execute on approval, hold the proof.");
1198
1285
  if (DEMO)
1199
- log(` Target: LOCAL DEMO — an in-process stand-in for the Decidio API on this machine (${API}); nothing hosted, nothing kept`);
1286
+ log(` Target: LOCAL DEMO — an in-process stand-in for the Decidio API on this machine (${API}). Only the web/phone door touches a hosted relay (one word, no account); nothing is kept but the receipt`);
1200
1287
  else
1201
1288
  log(` Target: ${API}${process.env.DECIDIO_API_URL ? "" : dim(" (the hosted synthetic sandbox — set DECIDIO_API_URL to use your own)")}`);
1202
1289
  hostAnnounced = true;
@@ -1458,26 +1545,52 @@ async function cmdQuickstartInner(opts = {}) {
1458
1545
  verdict = "again";
1459
1546
  if (verdict === "again" && !(earlyError instanceof DecidioTimeoutError)) {
1460
1547
  log(`③ No decision was made (no y or n answer), so the tour stops here rather than guess.`);
1461
- log(` The request is still ROUTED and waiting in your queue - approve or reject it there, or re-run the tour.`);
1462
- log(` NOTE: this tour's credentials are still live in ${envFile} (a 7-day token + the agent's key). Once you`);
1463
- log(` have decided - or if you abandon the tour - remove them: npx @decidio/sdk quickstart cleanup`);
1548
+ if (DEMO) {
1549
+ log(` Nothing executed and no receipt was written. Nothing is kept: the demo's agent, token and folder go now.`);
1550
+ log(` Re-run and answer y or n (or use the web page / QR).`);
1551
+ }
1552
+ else {
1553
+ log(` The request is still ROUTED and waiting in your queue - approve or reject it there, or re-run the tour.`);
1554
+ log(` NOTE: this tour's credentials are still live in ${envFile} (a 7-day token + the agent's key). Once you`);
1555
+ log(` have decided - or if you abandon the tour - remove them: npx @decidio/sdk quickstart cleanup`);
1556
+ }
1464
1557
  log("");
1465
1558
  sessionReminder();
1466
- process.exit(3);
1559
+ currentRl?.close();
1560
+ stdinReadAbandoned = true;
1561
+ return 3;
1467
1562
  }
1468
1563
  if (earlyError instanceof DecidioTimeoutError) {
1469
1564
  log(`③ the tour's blocking call gave up after 5 minutes while waiting for your answer, so your answer is NOT posted:`);
1470
- log(` the request is still ROUTED in your queue - decide it there, or re-run the tour and answer within 5 minutes.`);
1471
- log(` NOTE: this tour's credentials are still live in ${envFile} - remove them: npx @decidio/sdk quickstart cleanup`);
1565
+ if (DEMO)
1566
+ log(` the demo ends here; nothing is kept (the demo's agent, token and folder go now). Re-run and answer within 5 minutes.`);
1567
+ else {
1568
+ log(` the request is still ROUTED in your queue - decide it there, or re-run the tour and answer within 5 minutes.`);
1569
+ log(` NOTE: this tour's credentials are still live in ${envFile} - remove them: npx @decidio/sdk quickstart cleanup`);
1570
+ }
1472
1571
  log("");
1473
1572
  sessionReminder();
1474
- process.exit(3);
1573
+ currentRl?.close();
1574
+ stdinReadAbandoned = true;
1575
+ return 3;
1475
1576
  }
1476
1577
  }
1477
1578
  else if (opts.autoApproveDemo) {
1478
- log("③ --auto-approve-demo: approving with your session credential. This is AUTOMATED authority");
1579
+ log(`③ --auto-approve-demo: approving with ${DEMO ? "the demo's mock credential (no account is involved)" : "your session credential"}. This is AUTOMATED authority`);
1479
1580
  log(" (a flag you passed, not a human decision in the moment) — fine for CI, not the real story.");
1480
1581
  }
1582
+ else if (DEMO) {
1583
+ log("③ This terminal can't put a human in the loop, and an approval IS a human act — the demo stops here");
1584
+ log(" rather than approve its own request. Nothing executed, no receipt was written, and nothing is kept:");
1585
+ log(" the demo's agent, token and working folder go now (the stand-in API lives only in this process).");
1586
+ log("");
1587
+ log(" Re-run in an interactive terminal and answer y or n there, on the web page or by QR — or, for CI:");
1588
+ log(" npx @decidio/sdk quickstart --demo --auto-approve-demo (the tour approves itself under that explicit label)");
1589
+ log("");
1590
+ log(" (exit 3 = the demo stopped at the human moment; deliberately not reported as a completed tour.)");
1591
+ stdinReadAbandoned = true;
1592
+ return 3;
1593
+ }
1481
1594
  else {
1482
1595
  log("③ This terminal can't put a human in the loop, and an approval IS a human act — the tour");
1483
1596
  log(" stops here rather than approve its own request. The decision is routed and waiting —");
@@ -1499,7 +1612,8 @@ async function cmdQuickstartInner(opts = {}) {
1499
1612
  log(` or if you abandon the tour — remove them: npx @decidio/sdk quickstart cleanup`);
1500
1613
  log("");
1501
1614
  sessionReminder();
1502
- process.exit(3);
1615
+ stdinReadAbandoned = true;
1616
+ return 3;
1503
1617
  }
1504
1618
  const rejected = verdict === "reject";
1505
1619
  if (rejected) {
@@ -1644,7 +1758,7 @@ async function cmdQuickstartInner(opts = {}) {
1644
1758
  const secs = Math.round((Date.now() - t0) / 1000);
1645
1759
  block("✔", `Done in ${secs}s`, [
1646
1760
  `${bold("gated")} payInvoice $86,000 — ROUTED to a human as ${decisionId}`,
1647
- `${bold("decided")} ${decidedOutside ? "approved in the web app (or on your phone)" : opts.autoApproveDemo ? "approved by --auto-approve-demo (automated, and labeled so on the record)" : "approved by you, here"}`,
1761
+ `${bold("decided")} ${decidedOutside ? "approved in the web app (or on your phone)" : opts.autoApproveDemo ? `approved by --auto-approve-demo (automated — the ${DEMO ? "receipt's reason says so" : "record's reason says so"})` : "approved by you, here"}`,
1648
1762
  `${bold("executed")} your function ran once, AFTER approval`,
1649
1763
  `${bold("receipt")} VALID — verified offline, pinned to ${DEMO ? "this demo's own key" : "your workspace issuer"} (④ above)`,
1650
1764
  dim("gated → routed to a human → approved → executed once → a receipt you can verify offline"),
@@ -1968,7 +2082,7 @@ function cmdDev(target, port) {
1968
2082
  }).listen(port, "127.0.0.1");
1969
2083
  return 0;
1970
2084
  }
1971
- const USAGE = `decidio — the Decidio SDK command line
2085
+ const USAGE = `✦ Decidio — the SDK command line
1972
2086
 
1973
2087
  The wrapper IS the SDK: guard.protect(fn, describe) ships in this package (import { guard } from "@decidio/sdk").
1974
2088
  This command line is the tour, the sign-in, and the token admin around it.
@@ -1977,6 +2091,10 @@ const USAGE = `decidio — the Decidio SDK command line
1977
2091
 
1978
2092
  npx @decidio/sdk quickstart
1979
2093
 
2094
+ With no sign-in on this computer it first asks ONE question - do you have an invite? - and, if you do,
2095
+ what next: the tour, or wiring up your own agent. Every answer shows the command it stands for. The bare
2096
+ npx @decidio/sdk on a terminal asks the same (skipping the invite question when you are signed in).
2097
+
1980
2098
  It gates a demo action, routes it to YOU for approval, executes on your yes, and hands you a
1981
2099
  cryptographically verifiable receipt of the DECISION (execution evidence then accrues on the
1982
2100
  hosted record — the receipt seals who authorized what, not what ran afterward). Guided and
@@ -1995,7 +2113,7 @@ THE TOUR
1995
2113
  npx @decidio/sdk quickstart --auto-approve-demo headless tour; cleans up after itself
1996
2114
  npx @decidio/sdk quickstart --demo the same tour against a LOCAL stand-in of the API — no account needed; the
1997
2115
  web/phone door is a hosted demo page; the receipt is the one thing kept
1998
- (add --keep-agent to KEEP the tour agent + token)
2116
+ (--keep-agent is refused here: nothing of the demo's can be kept)
1999
2117
 
2000
2118
  SIGN IN (once)
2001
2119
  npx @decidio/sdk login [email] sign in ONCE: the CLI emails you a link, you paste it; the sign-in
@@ -2005,6 +2123,8 @@ SIGN IN (once)
2005
2123
 
2006
2124
  YOUR AGENT AND ITS TOKENS
2007
2125
  npx @decidio/sdk init [agentId] sign in, register the agent, mint its API token, write .env
2126
+ (agentId defaults to this folder's name; any name works - it is how the
2127
+ workspace shows the request and the key .env is written under)
2008
2128
  --unique append a random suffix (collision-proof samples)
2009
2129
  --force replace a different agent already in this .env
2010
2130
  (its .env is backed up first)
@@ -2147,6 +2267,16 @@ async function main(argv) {
2147
2267
  log(VERSION);
2148
2268
  return 0;
2149
2269
  }
2270
+ if (!cmd && process.stdin.isTTY && process.stdout.isTTY) {
2271
+ log(brand() + " — gate an action, route it to a human, execute on approval, hold the proof. " + dim("(npx @decidio/sdk --help for every command)"));
2272
+ const stored = process.env.DECIDIO_SESSION_TOKEN ? "" : loadSession()?.email ?? null;
2273
+ const pick = await firstRunFork(process.env.DECIDIO_SESSION_TOKEN ? "" : stored);
2274
+ if (pick.kind === "stop")
2275
+ return pick.code;
2276
+ if (pick.kind === "init")
2277
+ return cmdInit(pick.agentId, {});
2278
+ return cmdQuickstart({ demo: pick.kind === "demo" });
2279
+ }
2150
2280
  if (!cmd || wantsHelp(cmd)) {
2151
2281
  log(USAGE);
2152
2282
  return 0;
@@ -2257,7 +2387,18 @@ async function main(argv) {
2257
2387
  return 1;
2258
2388
  return quickstartCleanup(session, envFile);
2259
2389
  }
2260
- return cmdQuickstart({ demo: argsIn.includes("--demo"), autoApproveDemo: argsIn.includes("--auto-approve-demo"), keepAgent: argsIn.includes("--keep-agent"), explain: argsIn.includes("--explain"), phone: valuedOpt(argsIn, "--phone") });
2390
+ const qopts = { demo: argsIn.includes("--demo"), autoApproveDemo: argsIn.includes("--auto-approve-demo"), keepAgent: argsIn.includes("--keep-agent"), explain: argsIn.includes("--explain"), phone: valuedOpt(argsIn, "--phone") };
2391
+ if (!qopts.demo && !qopts.autoApproveDemo && forkApplies()) {
2392
+ log(brand() + bold(" quickstart") + " — gate an action, route it to a human, execute on approval, hold the proof.");
2393
+ const pick = await firstRunFork();
2394
+ if (pick.kind === "stop")
2395
+ return pick.code;
2396
+ if (pick.kind === "init")
2397
+ return cmdInit(pick.agentId, {});
2398
+ if (pick.kind === "demo")
2399
+ qopts.demo = true;
2400
+ }
2401
+ return cmdQuickstart(qopts);
2261
2402
  }
2262
2403
  case "init": {
2263
2404
  const unique = argsIn.includes("--unique");
@@ -2270,7 +2411,7 @@ async function main(argv) {
2270
2411
  }
2271
2412
  const ddIdx = argsIn.indexOf("--");
2272
2413
  const nameArg = ddIdx >= 0 ? argsIn[ddIdx + 1] : argsIn.find((x, i) => !x.startsWith("-") && !(expIdx >= 0 && i === expIdx + 1));
2273
- let name = nameArg ?? process.env.DECIDIO_AGENT_ID ?? "my-agent";
2414
+ let name = nameArg ?? (process.env.DECIDIO_AGENT_ID || defaultAgentId());
2274
2415
  if (unique)
2275
2416
  name = `${name}-${randomBytes(2).toString("hex")}`;
2276
2417
  return cmdInit(name, { force: argsIn.includes("--force"), expiresDays: expRaw ? Number(expRaw) : undefined });
package/dist/demo-gate.js CHANGED
@@ -35,6 +35,8 @@ export async function startDemoGate(opts = {}) {
35
35
  const issuerDid = "did:key:z" + b58(Buffer.concat([Buffer.from([0xed, 0x01]), issuerRaw]));
36
36
  const sealedAt = new Date().toISOString();
37
37
  let verdict = null;
38
+ let decidedBy = "the human at the terminal";
39
+ let lastReason = "quickstart";
38
40
  let relayCode = null;
39
41
  let relayOk = null;
40
42
  const relayFetch = async (path, init = {}) => {
@@ -58,7 +60,7 @@ export async function startDemoGate(opts = {}) {
58
60
  }
59
61
  };
60
62
  const issueReceipt = (kind) => {
61
- const canonical = `{"accountableParty":"[PERSON_1]","action":"payInvoice","amount":"[AMOUNT_2]","recordKind":"${kind}","recordSeries":"local-demo"}`;
63
+ const canonical = jcsCanonicalize({ accountableParty: "[PERSON_1]", action: "payInvoice", amount: 86000, currency: "USD", decidedBy, reason: lastReason, recordKind: kind, recordSeries: "local-demo" });
62
64
  const contentHash = createHash("sha256").update(canonical, "utf8").digest("hex");
63
65
  const context = ["https://www.w3.org/ns/credentials/v2"];
64
66
  const doc = {
@@ -113,14 +115,21 @@ export async function startDemoGate(opts = {}) {
113
115
  if (path.startsWith("/agent/status/")) {
114
116
  if (!verdict && relayCode) {
115
117
  const r = await relayFetch(`/demo/relay/${relayCode}`);
116
- if (r && (r.verdict === "approve" || r.verdict === "reject"))
118
+ if (r && (r.verdict === "approve" || r.verdict === "reject")) {
117
119
  verdict = r.verdict;
120
+ decidedBy = "the demo page (web or phone), through the relay";
121
+ lastReason = "decided on the demo page";
122
+ }
118
123
  }
119
124
  return send(verdict === "reject" ? { state: "rejected", reason: "quickstart: rejected by the approver" } : { state: verdict === "approve" ? "approved" : "pending" });
120
125
  }
121
126
  if (/^\/api\/decisions\/[^/]+\/(approve|reject)$/.test(path)) {
122
127
  const already = verdict !== null;
123
128
  verdict = path.endsWith("/reject") ? "reject" : "approve";
129
+ if (!already) {
130
+ lastReason = typeof body?.reason === "string" && body.reason ? body.reason : "quickstart";
131
+ decidedBy = /automated/i.test(lastReason) ? "automated: the --auto-approve-demo flag (not a human in the moment)" : "the human at the terminal";
132
+ }
124
133
  if (relayCode && !already)
125
134
  void relayFetch(`/demo/relay/${relayCode}`, { method: "PUT", body: JSON.stringify({ verdict }) });
126
135
  return send({ ok: true, decisionId: DECISION_ID, sealed: true, ...(already ? { alreadyResolved: true } : {}) });
@@ -156,6 +165,6 @@ export async function startDemoGate(opts = {}) {
156
165
  url: `http://127.0.0.1:${port}`,
157
166
  issuerDid,
158
167
  relayReachable: () => relayOk,
159
- close: () => new Promise((r) => server.close(() => r())),
168
+ close: () => new Promise((r) => { server.closeAllConnections(); server.close(() => r()); }),
160
169
  };
161
170
  }
package/openapi.yaml CHANGED
@@ -5,7 +5,7 @@
5
5
  openapi: 3.1.0
6
6
  info:
7
7
  title: Decidio Agent Gate
8
- version: 0.6.0 # tracks the SDK release - checked by packages/conformance/manifests.mjs
8
+ version: 0.6.1 # tracks the SDK release - checked by packages/conformance/manifests.mjs
9
9
  description: >
10
10
  Authorize an AI-agent action (proceed | route | block), confirm the captured outcome, and
11
11
  receive the signed resume webhook when a routed action is decided. Every outcome seals a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decidio/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "One-line approval gate for AI-agent actions. Wrap a risky tool call with guard.protect(); Decidio's policy engine decides proceed | route | block, a human approves routed actions, and every outcome is sealed as a verifiable Authority Receipt.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -84,7 +84,7 @@
84
84
  "prepublishOnly": "npm run typecheck && npm run build && node ../publish-boundary/check.mjs --only @decidio/sdk"
85
85
  },
86
86
  "dependencies": {
87
- "@decidio/verify": "0.6.0"
87
+ "@decidio/verify": "0.6.1"
88
88
  },
89
89
  "author": "OmniTwin Technologies Inc.",
90
90
  "bugs": {