@decidio/sdk 0.6.1 → 0.6.3
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 +39 -3
- package/README.md +8 -7
- package/dist/cli.js +97 -37
- package/openapi.yaml +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
# Changelog — @decidio/sdk
|
|
2
2
|
|
|
3
|
-
Release notes for the client SDK. The Python package `decidio` ships the same
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Release notes for the client SDK. The Python package `decidio` ships under the same version; the core -
|
|
4
|
+
the gate, signing, receipts and the verifier - is conformance-locked between the two, while the command line's
|
|
5
|
+
first-run surfaces (the fork, `--demo`, the code sign-in) are TypeScript-first and named as such in the parity
|
|
6
|
+
allowlist. Each entry names what changed, what a reader must do on upgrade, and what proved it.
|
|
7
|
+
|
|
8
|
+
## 0.6.3 — the two 0.6.2 reviews' small items
|
|
9
|
+
|
|
10
|
+
- **`DECIDIO_DEBUG_INPUT=1` no longer prints what you type** (review: Medium). The 0.6.2 trace hex-dumped every stdin
|
|
11
|
+
byte - passwords, codes and links, muted prompt or not. It now prints the SHAPE only: control bytes named (`\r`, `\n`,
|
|
12
|
+
ESC), every printable byte as `x`. A newline problem is visible in the shape alone.
|
|
13
|
+
- Four strings still described the sign-in as an emailed link to paste; they say the code now. The CHANGELOG's "twins"
|
|
14
|
+
line overstated CLI parity and now says exactly what is locked and what is TypeScript-first. Two README headings
|
|
15
|
+
still said "0.4". The offline local demo no longer prints "the terminal follows the web app or your phone" when it
|
|
16
|
+
has just said there are no such doors. `receipt --json` help says it prints a summary; the credential is the file.
|
|
17
|
+
|
|
18
|
+
## 0.6.2 — the fork after its first real run
|
|
19
|
+
|
|
20
|
+
Founder UAT of 0.6.1 on a Windows console, same day.
|
|
21
|
+
|
|
22
|
+
- **Prompts pause.** On the founder's cmd.exe the agent-id prompt "took the folder's" name without waiting. The
|
|
23
|
+
terminal driver could not reproduce it, so this is defence plus instrumentation: one readline now lives for the whole
|
|
24
|
+
command (a console's `\r\n` can no longer split across two questions - the previous per-question interfaces could
|
|
25
|
+
hand the second byte to the next prompt as Enter), and `DECIDIO_DEBUG_INPUT=1` prints the raw bytes stdin delivers, so
|
|
26
|
+
a recurrence names its cause instead of a theory.
|
|
27
|
+
- **"What next?" has a way out:** `3 sign out of this computer (npx @decidio/sdk logout)`.
|
|
28
|
+
- **Option 2 in a folder that already holds an agent** says so before asking for a name (with cleanup / a new folder /
|
|
29
|
+
`--force` as the ways out), instead of `init` refusing afterwards with text that read like a prompt.
|
|
30
|
+
- **The bare command is update-gated** on a terminal, so a cached old npx copy announces the newer version instead of
|
|
31
|
+
quietly showing an old first screen; `npx @decidio/sdk` is enough, `@latest` is not needed.
|
|
32
|
+
- **Sign in by CODE, not by paste.** When the command line asks for a sign-in, the email it triggers carries an
|
|
33
|
+
8-character code and nothing to click; you type the code into the terminal that asked (dashes, case and an O for a 0
|
|
34
|
+
are forgiven). Three misses and the code is dead. The web app's sign-in screen keeps its link email unchanged; two
|
|
35
|
+
flows, each with exactly one action. Why this shape and not "click the link and the terminal continues": that design
|
|
36
|
+
moves a session to a DIFFERENT device than the one that clicked, which is a new attack class the privacy council had
|
|
37
|
+
to harden (docs/Decidio_CLI_Signin_Wait_Spec.md, retired); a code travels from the inbox to the device you are
|
|
38
|
+
sitting at, and an attacker who triggers the email gains nothing. A pasted link is still accepted, quietly.
|
|
39
|
+
- **`npx decidio`.** A new npm package named `decidio` holds one file: it resolves the `@decidio/sdk` it pins (same
|
|
40
|
+
version, released in lockstep) and runs that CLI. `npx decidio …` is `npx @decidio/sdk …` with fewer keystrokes; the
|
|
41
|
+
update gate names whichever package you came through. Not something to import - the SDK stays `@decidio/sdk`.
|
|
6
42
|
|
|
7
43
|
## 0.6.1 — the first-run fork, and the local demo's loose ends
|
|
8
44
|
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ is the two-minute tour, the sign-in and the token admin around it. Install, wrap
|
|
|
9
9
|
## The two-minute tour
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
+
npx decidio # the short form: asks one question, then does the thing you picked
|
|
12
13
|
npx @decidio/sdk quickstart # against your workspace (you need an invite - request one below)
|
|
13
14
|
npx @decidio/sdk quickstart --demo # no invite yet? the same tour on this machine, against a local stand-in of the API
|
|
14
15
|
# Not sure? The bare command asks: with no sign-in on this computer it asks whether you have an
|
|
@@ -22,7 +23,7 @@ you approve right there (or from your Decidio app), executes on approval, then d
|
|
|
22
23
|
sealed Authority Receipt and verifies it offline in front of you, pinned to your workspace's
|
|
23
24
|
issuer. It mints a **7-day** tour token (`init` mints 90-day tokens — different jobs, different
|
|
24
25
|
lifetimes) and ends by offering to clean itself up (`quickstart cleanup` works later too).
|
|
25
|
-
Sign in once — `npx @decidio/sdk login`, or when the tour asks: the CLI emails you
|
|
26
|
+
Sign in once — `npx @decidio/sdk login`, or when the tour asks: the CLI emails you an 8-character code, you type it, and
|
|
26
27
|
the sign-in is kept in your profile (owner-only) for 30 days, or until this terminal closes, `logout`, or
|
|
27
28
|
`quickstart cleanup`. At the human step the tour offers three doors: answer here, open the decision in
|
|
28
29
|
the web app (a one-time link, signed in for you), or scan the QR with your phone — whichever answers
|
|
@@ -74,8 +75,8 @@ npx @decidio/sdk init --unique my-agent # --unique appends a random suffix: co
|
|
|
74
75
|
Every network command talks to the hosted sandbox by default (`verify` is offline) and announces its target host on
|
|
75
76
|
stderr each run — set `DECIDIO_API_URL` (env or `.env`) to point at another Decidio instead.
|
|
76
77
|
|
|
77
|
-
`init` signs you into your workspace (email + password, or
|
|
78
|
-
|
|
78
|
+
`init` signs you into your workspace (email + password, or the 8-character code the CLI emails you —
|
|
79
|
+
type it where you asked), generates the agent's Ed25519 keypair **locally** (only the public `did:key`
|
|
79
80
|
is sent), registers the agent, mints its **floor-limited API token** (limited to the agent runtime — request authorization, poll status, report signed confirmation; it cannot administer the workspace or approve anything — and your workspace session never touches the agent's disk), and writes `.env`.
|
|
80
81
|
Every later command that talks to Decidio reads `.env` from this directory automatically (`verify` is
|
|
81
82
|
offline and never does). Re-running init for an
|
|
@@ -91,7 +92,7 @@ agent name — if it's already taken in your workspace, `init` stops cleanly and
|
|
|
91
92
|
fresh one (or pass `--unique` to auto-append a random suffix — collision-proof for shared
|
|
92
93
|
workspaces and CI). Interactive terminals sign in once (`npx @decidio/sdk login` — stored owner-only in your
|
|
93
94
|
profile, bound to this terminal window, 30 days). Headless terminal (no prompts possible)? Export `DECIDIO_SESSION_TOKEN` first:
|
|
94
|
-
request a sign-in link, then exchange its `?login=` value at `POST /demo/login-consume` — the
|
|
95
|
+
request a sign-in link from the web app's sign-in screen, then exchange its `?login=` value at `POST /demo/login-consume` — the
|
|
95
96
|
response's `token` is your session token (the CLI prints these exact instructions when it
|
|
96
97
|
can't prompt). That session token is your workspace login, not the agent's credential: unset it
|
|
97
98
|
before launching agent code from the same shell, because child processes inherit exported
|
|
@@ -659,7 +660,7 @@ that fits every other confirm error.
|
|
|
659
660
|
- **The token recovery journal** (`.decidio-token-journal`, owner-only, gitignored; ids and timestamps, never a token value; append-only by the CLI's behaviour — not OS-enforced or tamper-evident). One JSON row per event: `minted` (a token was issued, written BEFORE its credential file), `retired how=persisted` (the credential file now holds it - the token is live and that file is its handle; not revoked), `retired how=revoked` (revoked server-side), `probe` (a durability check). `doctor` names rows still outstanding; `quickstart cleanup` sweeps them.
|
|
660
661
|
- `quickstart --demo` — the same tour with no account: an in-process stand-in of the API on 127.0.0.1 (shipped in this package) answers every door, a key generated for the run signs a real receipt (its content says `local-demo`; pinning it to the hosted issuer fails), and the web/phone door is a hosted demo decision page reached through a one-word relay (no account, no record, ten minutes). Your folder gains exactly one file, the receipt. Offline, the tour is terminal-only and says so.
|
|
661
662
|
- `quickstart --phone web` — the QR at the human step opens the phone web rendering even when the Decidio app is installed (by default it opens the app when there is one).
|
|
662
|
-
- `login [email]` / `logout` — sign in ONCE (the CLI emails you
|
|
663
|
+
- `login [email]` / `logout` — sign in ONCE (the CLI emails you an 8-character code; you type it) and forget that sign-in on this computer (the session token itself stays valid until it expires (up to 30 days) — there is no server-side revocation yet). The session is stored in your user profile, owner-only, bound to the terminal window that created it, for 30 days; `DECIDIO_SESSION_TOKEN` in the environment outranks it (headless shells).
|
|
663
664
|
- `dev [--target URL] [--port N]` — local relay for the resume webhook (operator setups)
|
|
664
665
|
|
|
665
666
|
The CLI's stream contract: **stdout carries results only** (safe to pipe — `--json` output is
|
|
@@ -702,7 +703,7 @@ Every error raised after a decision exists carries the `decisionId`, so a suspen
|
|
|
702
703
|
timed-out action is always findable in
|
|
703
704
|
your queue.
|
|
704
705
|
|
|
705
|
-
## What
|
|
706
|
+
## What the SDK does not yet claim
|
|
706
707
|
|
|
707
708
|
The retest that graded 0.3.3 *A- for the core and the shipped stores* and the first-run reviews of
|
|
708
709
|
0.3.3 and 0.3.5 agree on what is left, and none of it is a defect a patch release closes. Stated here so
|
|
@@ -728,7 +729,7 @@ the next reader grades a promise, not a moving target:
|
|
|
728
729
|
|
|
729
730
|
## Compatibility and support
|
|
730
731
|
|
|
731
|
-
- **Stable
|
|
732
|
+
- **Stable surface:** `guard.protect` / `withApproval` and their options; the durable resume
|
|
732
733
|
controller (`createDecidioResume`: `park`, `resolve`, `reconcile`, `reconfirm`, `recover`, the worker
|
|
733
734
|
and both webhook handlers); the `PendingStore` contract and both shipped stores, including the on-disk
|
|
734
735
|
revision-chain layout; `EvidenceOutcome` (a root export) and `ResumeStatus` / `RecoveryState` (on the
|
package/dist/cli.js
CHANGED
|
@@ -467,22 +467,44 @@ export function approvalAnswer(answer) {
|
|
|
467
467
|
return "reject";
|
|
468
468
|
return "again";
|
|
469
469
|
}
|
|
470
|
+
let sharedRl = null;
|
|
471
|
+
let sharedRlOpen = false;
|
|
472
|
+
function closeSharedRl() { if (sharedRlOpen) {
|
|
473
|
+
sharedRlOpen = false;
|
|
474
|
+
sharedRl?.close();
|
|
475
|
+
} }
|
|
470
476
|
function ask(question, mute = false) {
|
|
471
477
|
if (process.stdin.readableEnded || process.stdin.destroyed)
|
|
472
478
|
return Promise.resolve("");
|
|
473
479
|
return new Promise((resolve) => {
|
|
474
|
-
|
|
480
|
+
if (!sharedRl || !sharedRlOpen) {
|
|
481
|
+
sharedRl = createInterface({ input: process.stdin, output: process.stderr, terminal: true, crlfDelay: Infinity });
|
|
482
|
+
sharedRlOpen = true;
|
|
483
|
+
sharedRl.once("close", () => { sharedRlOpen = false; });
|
|
484
|
+
}
|
|
485
|
+
const rl = sharedRl;
|
|
475
486
|
currentRl = rl;
|
|
487
|
+
let restore = null;
|
|
476
488
|
if (mute) {
|
|
477
489
|
const anyRl = rl;
|
|
478
490
|
const write = anyRl._writeToOutput?.bind(rl);
|
|
479
491
|
anyRl._writeToOutput = (s) => { if (s.includes(question))
|
|
480
492
|
write?.(s); };
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
493
|
+
restore = () => { anyRl._writeToOutput = write; };
|
|
494
|
+
}
|
|
495
|
+
const onClose = () => { if (currentRl === rl)
|
|
496
|
+
currentRl = null; restore?.(); resolve(""); };
|
|
497
|
+
rl.once("close", onClose);
|
|
498
|
+
rl.question(question, (answer) => { rl.off("close", onClose); if (currentRl === rl)
|
|
499
|
+
currentRl = null; restore?.(); if (mute)
|
|
500
|
+
process.stderr.write("\n"); resolve(answer.trim()); });
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (process.env.DECIDIO_DEBUG_INPUT && process.stdin.isTTY) {
|
|
504
|
+
process.stdin.on("data", (c) => {
|
|
505
|
+
const b = Buffer.from(c);
|
|
506
|
+
const shape = [...b].map((x) => x === 0x0d ? "\\r" : x === 0x0a ? "\\n" : x === 0x1b ? "ESC" : x < 0x20 ? `^${String.fromCharCode(x + 64)}` : x === 0x7f ? "DEL" : "x").join("");
|
|
507
|
+
process.stderr.write(`[stdin ${b.length}B ${shape}]\n`);
|
|
486
508
|
});
|
|
487
509
|
}
|
|
488
510
|
function terminalId() {
|
|
@@ -557,7 +579,7 @@ async function loginFlow(purpose, emailArg) {
|
|
|
557
579
|
log(" ✘ no email given - re-run when you have it.");
|
|
558
580
|
return null;
|
|
559
581
|
}
|
|
560
|
-
const password = await ask(" password (Enter = emailed sign-in
|
|
582
|
+
const password = await ask(" password (Enter = emailed sign-in code): ", true);
|
|
561
583
|
if (password) {
|
|
562
584
|
const r = await api("/demo/login", { method: "POST", body: JSON.stringify({ email, password }) }, "");
|
|
563
585
|
if (r.status >= 200 && r.status < 300 && r.body?.token) {
|
|
@@ -567,29 +589,37 @@ async function loginFlow(purpose, emailArg) {
|
|
|
567
589
|
}
|
|
568
590
|
log(` ✘ password sign-in failed (${r.status})${r.body?.error ? ` — ${r.body.error}` : ""}. No problem — the emailed link works for every account:`);
|
|
569
591
|
}
|
|
570
|
-
const req = await api("/demo/login-link", { method: "POST", body: JSON.stringify({ email }) }, "");
|
|
592
|
+
const req = await api("/demo/login-link", { method: "POST", body: JSON.stringify({ email, code: true }) }, "");
|
|
571
593
|
if (!(req.status >= 200 && req.status < 300)) {
|
|
572
594
|
log(` ✘ could not request a sign-in email (${req.status})${req.body?.error ? ` — ${req.body.error}` : ""}. Check the address and re-run.`);
|
|
573
595
|
return null;
|
|
574
596
|
}
|
|
575
|
-
log(` ✔ sign-in email
|
|
576
|
-
log(dim(` invite-only lab:
|
|
597
|
+
log(` ✔ sign-in email sent to ${email} — it holds an 8-character code (single-use, 15 minutes). Check spam too.`);
|
|
598
|
+
log(dim(` invite-only lab: an email arrives only for an invited address — not invited yet? request access: decidioai.com/#early-access`));
|
|
577
599
|
log(dim(` or see the whole story on this machine right now, no account: npx @decidio/sdk quickstart --demo`));
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
600
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
601
|
+
const typed = await ask(" code from the email: ");
|
|
602
|
+
if (!typed) {
|
|
603
|
+
log(" ✘ nothing typed — re-run when you have the email.");
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
const m = typed.match(/[?&]login=([^&\s]+)/);
|
|
607
|
+
const isLink = !!m || /^[0-9a-f]{32,}$/i.test(typed);
|
|
608
|
+
const body = isLink ? { token: m ? decodeURIComponent(m[1]) : typed } : { token: "c." + typed.toUpperCase().replace(/[^0-9A-Z]/g, "").replace(/O/g, "0").replace(/[IL]/g, "1").slice(0, 8), email };
|
|
609
|
+
const r = await api("/demo/login-consume", { method: "POST", body: JSON.stringify(body) }, "");
|
|
610
|
+
if (r.status >= 200 && r.status < 300 && r.body?.token) {
|
|
611
|
+
const s = saveSession(String(r.body.token), email);
|
|
612
|
+
log(` ✔ signed in as ${email} until ${s.expiresAt.slice(0, 10)}${s.terminal ? "" : dim(" (no terminal id here: the sign-in ends on logout or cleanup, not on close)")}`);
|
|
613
|
+
return s.token;
|
|
614
|
+
}
|
|
615
|
+
if (isLink) {
|
|
616
|
+
log(` ✘ that link didn't work (${r.status})${r.body?.error ? ` — ${r.body.error}` : ""}. Sign-in links are single-use and expire in 15 minutes — request a fresh one and re-run.`);
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
if (attempt < 3)
|
|
620
|
+
log(` ✘ that code didn't work — check the email and try again (${3 - attempt} ${3 - attempt === 1 ? "try" : "tries"} left)`);
|
|
590
621
|
}
|
|
591
|
-
log(` ✘ that
|
|
592
|
-
log(` Sign-in links are single-use and expire in 15 minutes — request a fresh one and re-run.`);
|
|
622
|
+
log(` ✘ three misses — that code is now dead. Re-run to get a fresh email.`);
|
|
593
623
|
return null;
|
|
594
624
|
}
|
|
595
625
|
async function acquireSession(purpose) {
|
|
@@ -659,12 +689,13 @@ async function firstRunFork(signedInAs = null) {
|
|
|
659
689
|
return { kind: "stop", code: 1 };
|
|
660
690
|
}
|
|
661
691
|
log("");
|
|
662
|
-
log(` What next? ${dim("[1/2]")}`);
|
|
692
|
+
log(` What next? ${dim("[1/2/3]")}`);
|
|
663
693
|
log(` ${bold("1")} the two-minute tour against your workspace ${dim("npx @decidio/sdk quickstart")}`);
|
|
664
694
|
log(` ${bold("2")} wire up your own agent now (register, token, .env) ${dim("npx @decidio/sdk init <agentId>")}`);
|
|
665
|
-
|
|
695
|
+
log(` ${bold("3")} sign out of this computer ${dim("npx @decidio/sdk logout")}`);
|
|
696
|
+
const b = await pick("1, 2 or 3", /^[123]$/);
|
|
666
697
|
if (!b) {
|
|
667
|
-
log(` No answer — you are signed in; run
|
|
698
|
+
log(` No answer — you are signed in; run any command above when you are ready.`);
|
|
668
699
|
return { kind: "stop", code: 0 };
|
|
669
700
|
}
|
|
670
701
|
if (b === "1") {
|
|
@@ -672,6 +703,21 @@ async function firstRunFork(signedInAs = null) {
|
|
|
672
703
|
log("");
|
|
673
704
|
return { kind: "tour" };
|
|
674
705
|
}
|
|
706
|
+
if (b === "3") {
|
|
707
|
+
log(` → running: npx @decidio/sdk logout`);
|
|
708
|
+
log("");
|
|
709
|
+
return { kind: "logout" };
|
|
710
|
+
}
|
|
711
|
+
const here = readEnvFile();
|
|
712
|
+
if (["DECIDIO_AGENT_KEY", "DECIDIO_API_TOKEN"].some((k) => (here[k] ?? "").trim()) && here.DECIDIO_AGENT_ID) {
|
|
713
|
+
const tour = /^quickstart-/.test(here.DECIDIO_AGENT_ID);
|
|
714
|
+
log(` This folder's .env already holds an agent (${tour ? "the tour's, " : ""}"${here.DECIDIO_AGENT_ID}"). A new agent here would overwrite its key.`);
|
|
715
|
+
log(` Either: start in a new folder ${dim("mkdir <name> && cd <name> && npx @decidio/sdk")}`);
|
|
716
|
+
if (tour)
|
|
717
|
+
log(` or: clear the tour's agent first ${dim("npx @decidio/sdk quickstart cleanup")} then run this again`);
|
|
718
|
+
log(` or: replace it, backing .env up ${dim("npx @decidio/sdk init <name> --force")}`);
|
|
719
|
+
return { kind: "stop", code: 1 };
|
|
720
|
+
}
|
|
675
721
|
const def = defaultAgentId();
|
|
676
722
|
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
723
|
log(` and the key .env is written under. Any name works; Enter takes the folder's.`);
|
|
@@ -721,9 +767,8 @@ async function cmdInit(agentId, opts = {}) {
|
|
|
721
767
|
err(`✘ this directory's .env already holds the key for a DIFFERENT agent ("${existingEnv.DECIDIO_AGENT_ID}").`);
|
|
722
768
|
err(` Registering "${agentId}" here would overwrite that key — and a private key that is`);
|
|
723
769
|
err(` overwritten is gone for good (it never left this machine). Nothing was written.`);
|
|
724
|
-
err(`
|
|
725
|
-
err(`
|
|
726
|
-
err(` 2. Replace the old agent here, backing its .env up first: npx @decidio/sdk init ${agentId} --force`);
|
|
770
|
+
err(` Either: a new directory (cleanest): mkdir ${agentId} && cd ${agentId} && npx @decidio/sdk init ${agentId}`);
|
|
771
|
+
err(` or: replace the old agent here, backing its .env up first: npx @decidio/sdk init ${agentId} --force`);
|
|
727
772
|
return 1;
|
|
728
773
|
}
|
|
729
774
|
const { did: localDid, privateKeyBase64: localKey } = generateKeypair();
|
|
@@ -1513,7 +1558,8 @@ async function cmdQuickstartInner(opts = {}) {
|
|
|
1513
1558
|
}
|
|
1514
1559
|
else if (doorLink)
|
|
1515
1560
|
rows.push(`${bold("your phone")} ${dim("the link above works on a phone too (no QR came back this time)")}`);
|
|
1516
|
-
|
|
1561
|
+
if (!DEMO || doorLink)
|
|
1562
|
+
rows.push(dim("whichever answers first wins — the terminal follows the web app or your phone."));
|
|
1517
1563
|
block("③", "Your call", rows, "choice");
|
|
1518
1564
|
let settledState = null;
|
|
1519
1565
|
const settledP = running.then(() => { settledState = "approved"; }, (e) => { settledState = e instanceof DecidioRejectedError ? "rejected" : "other"; });
|
|
@@ -2116,7 +2162,7 @@ THE TOUR
|
|
|
2116
2162
|
(--keep-agent is refused here: nothing of the demo's can be kept)
|
|
2117
2163
|
|
|
2118
2164
|
SIGN IN (once)
|
|
2119
|
-
npx @decidio/sdk login [email] sign in ONCE: the CLI emails you
|
|
2165
|
+
npx @decidio/sdk login [email] sign in ONCE: the CLI emails you an 8-character code, you type it; the sign-in
|
|
2120
2166
|
is kept on this computer 30 days, or until this terminal closes, logout, or cleanup
|
|
2121
2167
|
npx @decidio/sdk logout forget the stored sign-in on this computer (the token itself stays valid
|
|
2122
2168
|
until it expires — there is no server-side revocation yet)
|
|
@@ -2145,7 +2191,9 @@ APPROVALS AND RECEIPTS
|
|
|
2145
2191
|
If it starts with a dash, put it after -- or use --reason:
|
|
2146
2192
|
... approve <id> -- "-50% cut, per the board"
|
|
2147
2193
|
... approve <id> --reason "-50% cut, per the board"
|
|
2148
|
-
npx @decidio/sdk receipt <id> [--json] download a sealed decision's Authority Receipt
|
|
2194
|
+
npx @decidio/sdk receipt <id> [--json] download a sealed decision's Authority Receipt to a .json FILE; --json prints
|
|
2195
|
+
a summary to stdout (file name, evidence, issuer, the identities you may see) -
|
|
2196
|
+
the credential itself is the file
|
|
2149
2197
|
npx @decidio/sdk verify --issuer <did> <receipt.json> [more...] verify receipts OFFLINE, pinned to your
|
|
2150
2198
|
workspace's issuer (--allow-unpinned: structural check only)
|
|
2151
2199
|
|
|
@@ -2157,7 +2205,8 @@ NOTES
|
|
|
2157
2205
|
Every NETWORK command talks to the hosted sandbox (https://decidio-api.onrender.com) unless
|
|
2158
2206
|
DECIDIO_API_URL says otherwise; the target host is announced on stderr each run. "verify" is
|
|
2159
2207
|
fully offline — it touches no network and reads no .env.
|
|
2160
|
-
Update check: network commands ask the public npm registry once a day
|
|
2208
|
+
Update check: network commands (and the bare command on a terminal) ask the public npm registry once a day
|
|
2209
|
+
whether a newer version exists. If so,
|
|
2161
2210
|
an interactive run asks "Upgrade now? [y/n]" first (y upgrades the way you installed - global, or in the
|
|
2162
2211
|
project for a local install; under npx it prints the @latest form - and re-runs your command; n exits); a headless
|
|
2163
2212
|
run prints the notice and continues (DECIDIO_REQUIRE_LATEST=1 makes it exit). DECIDIO_NO_UPDATE_CHECK=1 skips
|
|
@@ -2212,7 +2261,8 @@ async function latestPublished() {
|
|
|
2212
2261
|
}
|
|
2213
2262
|
}
|
|
2214
2263
|
async function updateGate(cmd, argv) {
|
|
2215
|
-
|
|
2264
|
+
const gatedHere = cmd ? GATED_CMDS.has(cmd) : !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
2265
|
+
if (!gatedHere || SHELL_ENV.DECIDIO_NO_UPDATE_CHECK || SHELL_ENV.DECIDIO_UPDATE_REEXEC || VERSION === "unknown" || VERSION.includes("-"))
|
|
2216
2266
|
return null;
|
|
2217
2267
|
const latest = await latestPublished();
|
|
2218
2268
|
if (!latest || !semverNewer(latest, VERSION))
|
|
@@ -2228,7 +2278,9 @@ async function updateGate(cmd, argv) {
|
|
|
2228
2278
|
const cwd = norm(process.cwd());
|
|
2229
2279
|
return cwd === norm(root) || cwd.startsWith(norm(root) + pathSep) ? root : null;
|
|
2230
2280
|
})();
|
|
2231
|
-
const
|
|
2281
|
+
const viaAlias = /[\\/]decidio$/.test(argv[1] ?? "");
|
|
2282
|
+
const pkgName = viaAlias ? "decidio" : "@decidio/sdk";
|
|
2283
|
+
const upgradeCmd = underNpx ? `npx ${pkgName}@latest ${argv.slice(2).join(" ")}` : localRoot ? `npm i ${pkgName}@latest` : `npm i -g ${pkgName}@latest`;
|
|
2232
2284
|
const notice = `${latest} is available (you have ${VERSION}).`;
|
|
2233
2285
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
2234
2286
|
err(`⚠ ${notice} Upgrade: ${upgradeCmd}`);
|
|
@@ -2252,7 +2304,7 @@ async function updateGate(cmd, argv) {
|
|
|
2252
2304
|
return null;
|
|
2253
2305
|
}
|
|
2254
2306
|
log(` upgrading: ${upgradeCmd}${localRoot ? ` (in ${localRoot})` : ""}`);
|
|
2255
|
-
const up = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", localRoot ? ["i",
|
|
2307
|
+
const up = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", localRoot ? ["i", `${pkgName}@latest`] : ["i", "-g", `${pkgName}@latest`], { stdio: "inherit", shell: process.platform === "win32", env: SHELL_ENV, ...(localRoot ? { cwd: localRoot } : {}) });
|
|
2256
2308
|
if (up.status !== 0) {
|
|
2257
2309
|
err(`✘ the upgrade did not complete (exit ${up.status ?? "?"}) — run it by hand: ${upgradeCmd}`);
|
|
2258
2310
|
return 1;
|
|
@@ -2268,11 +2320,16 @@ async function main(argv) {
|
|
|
2268
2320
|
return 0;
|
|
2269
2321
|
}
|
|
2270
2322
|
if (!cmd && process.stdin.isTTY && process.stdout.isTTY) {
|
|
2323
|
+
const gatedBare = await updateGate(cmd, argv);
|
|
2324
|
+
if (gatedBare !== null)
|
|
2325
|
+
return gatedBare;
|
|
2271
2326
|
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
2327
|
const stored = process.env.DECIDIO_SESSION_TOKEN ? "" : loadSession()?.email ?? null;
|
|
2273
2328
|
const pick = await firstRunFork(process.env.DECIDIO_SESSION_TOKEN ? "" : stored);
|
|
2274
2329
|
if (pick.kind === "stop")
|
|
2275
2330
|
return pick.code;
|
|
2331
|
+
if (pick.kind === "logout")
|
|
2332
|
+
return cmdLogout();
|
|
2276
2333
|
if (pick.kind === "init")
|
|
2277
2334
|
return cmdInit(pick.agentId, {});
|
|
2278
2335
|
return cmdQuickstart({ demo: pick.kind === "demo" });
|
|
@@ -2393,6 +2450,8 @@ async function main(argv) {
|
|
|
2393
2450
|
const pick = await firstRunFork();
|
|
2394
2451
|
if (pick.kind === "stop")
|
|
2395
2452
|
return pick.code;
|
|
2453
|
+
if (pick.kind === "logout")
|
|
2454
|
+
return cmdLogout();
|
|
2396
2455
|
if (pick.kind === "init")
|
|
2397
2456
|
return cmdInit(pick.agentId, {});
|
|
2398
2457
|
if (pick.kind === "demo")
|
|
@@ -2497,6 +2556,7 @@ const invokedAsProgram = (() => {
|
|
|
2497
2556
|
})();
|
|
2498
2557
|
if (invokedAsProgram)
|
|
2499
2558
|
main(process.argv).then(async (code) => {
|
|
2559
|
+
closeSharedRl();
|
|
2500
2560
|
if (code !== 0)
|
|
2501
2561
|
process.exitCode = code;
|
|
2502
2562
|
if (stdinReadAbandoned) {
|
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.
|
|
8
|
+
version: 0.6.3 # 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.
|
|
3
|
+
"version": "0.6.3",
|
|
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.
|
|
87
|
+
"@decidio/verify": "0.6.3"
|
|
88
88
|
},
|
|
89
89
|
"author": "OmniTwin Technologies Inc.",
|
|
90
90
|
"bugs": {
|