@decidio/sdk 0.1.1 → 0.1.2
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 +3 -2
- package/dist/cli.js +47 -10
- package/dist/index.js +7 -4
- package/openapi.yaml +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,10 @@ One line to put a human-or-policy approval gate in front of any AI-agent action
|
|
|
4
4
|
|
|
5
5
|
## Quickstart — seven steps to your first sealed receipt
|
|
6
6
|
|
|
7
|
-
Under 30 minutes, no help needed. You'll need Node 20+, a terminal, and a Decidio workspace
|
|
7
|
+
Under 30 minutes, no help needed. You'll need Node 20.6+, a terminal, and a Decidio workspace
|
|
8
8
|
(request one at [decidioai.com](https://decidioai.com/#early-access) — the hosted sandbox is
|
|
9
|
-
synthetic data, safe to experiment in).
|
|
9
|
+
synthetic data, safe to experiment in). Shell commands below are Bash; on PowerShell, `export
|
|
10
|
+
NAME=value` becomes `$env:NAME="value"`.
|
|
10
11
|
|
|
11
12
|
**1. Install**
|
|
12
13
|
|
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,16 @@ import { readFileSync, writeFileSync, existsSync, appendFileSync } from "node:fs
|
|
|
22
22
|
import { randomBytes } from "node:crypto";
|
|
23
23
|
import { createServer } from "node:http";
|
|
24
24
|
import { createInterface } from "node:readline";
|
|
25
|
+
import { createRequire } from "node:module";
|
|
25
26
|
import { generateKeypair } from "./signing.js";
|
|
27
|
+
const VERSION = (() => {
|
|
28
|
+
try {
|
|
29
|
+
return createRequire(import.meta.url)("../package.json").version;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
26
35
|
// ---- environment: load the CWD .env FIRST (Codex finding: init writes .env, but the next
|
|
27
36
|
// command ignored it and pointed at localhost). Process env always wins — .env only fills gaps.
|
|
28
37
|
function readEnvFile() {
|
|
@@ -239,25 +248,31 @@ async function cmdInit(agentId) {
|
|
|
239
248
|
if (workspaceId)
|
|
240
249
|
envOut.DECIDIO_WORKSPACE_ID = workspaceId;
|
|
241
250
|
upsertEnv(envOut);
|
|
242
|
-
log(`✔ wrote ${ENV_PATH} — every decidio command
|
|
251
|
+
log(`✔ wrote ${ENV_PATH} — every decidio command reads it from this directory (your script loads it with node --env-file=.env)`);
|
|
243
252
|
ensureGitignore();
|
|
244
253
|
if (privateKey)
|
|
245
254
|
log(` (the agent's private signing key is in there — for production, move it to your secret manager)`);
|
|
246
255
|
log("");
|
|
247
256
|
log("Next: protect one function and watch it route. Two steps:");
|
|
248
257
|
log("");
|
|
249
|
-
log(" 1. Save this as quickstart.mjs:");
|
|
258
|
+
log(" 1. Save this as quickstart.mjs (the same snippet as the README's step 3):");
|
|
250
259
|
log("");
|
|
251
260
|
log(` import { guard } from "@decidio/sdk";`);
|
|
252
|
-
log(
|
|
253
|
-
log(` const
|
|
254
|
-
log(
|
|
255
|
-
log(`
|
|
256
|
-
log(`
|
|
261
|
+
log("");
|
|
262
|
+
log(` const payInvoiceRaw = async (invoice) => ({ paid: true, id: invoice.id }); // your real action`);
|
|
263
|
+
log("");
|
|
264
|
+
log(` const payInvoice = guard.protect(`);
|
|
265
|
+
log(` payInvoiceRaw,`);
|
|
266
|
+
log(` (invoice) => ({ action: "payInvoice", amount: invoice.amount, scope: "Invoice" }),`);
|
|
267
|
+
log(` { mode: "blocking" }, // blocking = watch it happen live; durable is the production mode`);
|
|
268
|
+
log(` );`);
|
|
269
|
+
log("");
|
|
270
|
+
log(` const result = await payInvoice({ id: "INV-2026-001", amount: 86_000 });`);
|
|
271
|
+
log(` console.log("executed after approval:", result);`);
|
|
257
272
|
log("");
|
|
258
273
|
log(" 2. Run it, approve it, watch it complete:");
|
|
259
274
|
log("");
|
|
260
|
-
log(` node --env-file=.env quickstart.mjs # routes to a human
|
|
275
|
+
log(` node --env-file=.env quickstart.mjs # routes to a human, prints the decision id, waits`);
|
|
261
276
|
log(` npx @decidio/sdk approvals # (second terminal) see it pending`);
|
|
262
277
|
log(` npx @decidio/sdk approvals approve <id> # approve -> your function runs -> sealed`);
|
|
263
278
|
log("");
|
|
@@ -287,8 +302,18 @@ async function cmdDoctor() {
|
|
|
287
302
|
const rec = await api("/api/records");
|
|
288
303
|
if (rec.status >= 200 && rec.status < 300)
|
|
289
304
|
log("✔ token scope: session/admin (init, approvals and receipt work from this env)");
|
|
290
|
-
else if (rec.status === 401)
|
|
291
|
-
|
|
305
|
+
else if (rec.status === 401) {
|
|
306
|
+
// A 401 off the agent floor could also mean an INVALID token (Codex catch: don't
|
|
307
|
+
// false-green expiry). Disambiguate with a floor probe: a valid agent token gets 404 for an
|
|
308
|
+
// unknown decision (authorized, not found); an invalid one gets 401 again.
|
|
309
|
+
const probe = await api("/agent/status/doctor-scope-probe");
|
|
310
|
+
if (probe.status === 404)
|
|
311
|
+
log("✔ token scope: agent floor — correct for the runtime SDK (approvals/receipt will ask you to sign in)");
|
|
312
|
+
else if (probe.status === 401)
|
|
313
|
+
log("✘ token: REJECTED on the agent floor too — it's invalid or expired. Re-run `npx @decidio/sdk init` to mint a fresh one.");
|
|
314
|
+
else
|
|
315
|
+
log(`⚠ token scope: agent-floor probe returned ${probe.status} — inconclusive`);
|
|
316
|
+
}
|
|
292
317
|
else
|
|
293
318
|
log(`⚠ token scope: unexpected ${rec.status} from /api/records`);
|
|
294
319
|
log(ok ? "\ndoctor: all green." : "\ndoctor: fix the ✘ items above, then re-run `npx @decidio/sdk doctor`.");
|
|
@@ -386,6 +411,13 @@ async function cmdReceipt(id) {
|
|
|
386
411
|
const name = `decidio-receipt-${id.replace(/[^A-Za-z0-9_-]/g, "")}.json`;
|
|
387
412
|
writeFileSync(name, JSON.stringify(r.body.credential, null, 2) + "\n");
|
|
388
413
|
log(`✔ wrote ${name}`);
|
|
414
|
+
// The credential seals at DECISION time (immutable), so execution evidence that arrived later
|
|
415
|
+
// lives on the record — surface the CURRENT tier here so the reader doesn't need a second
|
|
416
|
+
// lookup to see it (Codex catch).
|
|
417
|
+
const detail = await api(`/api/records/${encodeURIComponent(id)}`, {}, bearer);
|
|
418
|
+
const conf = detail.body?.record?.confirmation;
|
|
419
|
+
if (conf?.status)
|
|
420
|
+
log(` Sealed credential: the authority decision (its confirmation field reads "pending" forever — immutable). Current record evidence tier: ${conf.status}${conf.confirmed ? " (confirmed)" : ""}.`);
|
|
389
421
|
log(` Verify it offline — no Decidio account or network needed:`);
|
|
390
422
|
log(` npx @decidio/verify ${name}`);
|
|
391
423
|
log(` To also prove WHO issued it, pin your workspace's issuer DID (shown in your app's record verify panel):`);
|
|
@@ -428,11 +460,16 @@ const USAGE = `decidio — the Decidio SDK command line
|
|
|
428
460
|
npx @decidio/sdk approvals reject <id> [reason]
|
|
429
461
|
npx @decidio/sdk receipt <id> download a sealed decision's Authority Receipt (.json)
|
|
430
462
|
npx @decidio/sdk dev [--target URL] [--port N] local relay for the resume webhook
|
|
463
|
+
npx @decidio/sdk --version print the CLI version
|
|
431
464
|
|
|
432
465
|
Docs: the README that shipped with this package (quickstart, durable resume, adapters).`;
|
|
433
466
|
async function main(argv) {
|
|
434
467
|
const [cmd, a, b, ...rest] = argv.slice(2);
|
|
435
468
|
const wantsHelp = (s) => s === "help" || s === "--help" || s === "-h";
|
|
469
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
470
|
+
log(VERSION);
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
436
473
|
if (!cmd || wantsHelp(cmd)) {
|
|
437
474
|
log(USAGE);
|
|
438
475
|
return 0;
|
package/dist/index.js
CHANGED
|
@@ -122,13 +122,16 @@ export function withApproval(fn, describe, config) {
|
|
|
122
122
|
throw new DecidioBlockedError(auth.reason ?? "blocked by policy", auth.decisionId);
|
|
123
123
|
// 2. routed → either SUSPEND for async resume (durable) or BLOCK-poll (interactive)
|
|
124
124
|
if (auth.decision === "route") {
|
|
125
|
-
//
|
|
126
|
-
//
|
|
125
|
+
// ALWAYS hand back the decision id — an agent that suspends and says only "waiting" leaves
|
|
126
|
+
// the operator hunting through a queue, and an agent that says NOTHING (the pre-0.1.2 bug:
|
|
127
|
+
// the print was gated on appUrl, which init never writes) leaves them staring at a silent
|
|
128
|
+
// terminal. The Codex acceptance run failed exactly this claim; the id line is unconditional
|
|
129
|
+
// now, with the deep link when appUrl is configured and the CLI command when not.
|
|
127
130
|
const approvalUrl = decisionUrl(config.appUrl, auth.decisionId);
|
|
128
131
|
if (config.onPending)
|
|
129
132
|
config.onPending({ decisionId: auth.decisionId, receiptId: auth.receiptId, url: approvalUrl });
|
|
130
|
-
else
|
|
131
|
-
console.log(`[decidio] ${ctx.action} is awaiting approval — approve it at ${approvalUrl}`);
|
|
133
|
+
else
|
|
134
|
+
console.log(`[decidio] ${ctx.action} is awaiting approval (decision ${auth.decisionId}) — approve it ${approvalUrl ? `at ${approvalUrl}` : `in your Decidio queue, or: npx @decidio/sdk approvals approve ${auth.decisionId}`}`);
|
|
132
135
|
// Durable transport: park the args agent-side, register the raw fn so a (cold)
|
|
133
136
|
// resume can replay it, and SUSPEND. The process may exit now; the controller's
|
|
134
137
|
// webhook handler or poll worker re-executes THIS fn when a human approves.
|
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.1.
|
|
8
|
+
version: 0.1.2
|
|
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
|
|
@@ -154,7 +154,10 @@ paths:
|
|
|
154
154
|
"404": { description: unknown decision }
|
|
155
155
|
/agent/confirm:
|
|
156
156
|
post:
|
|
157
|
-
summary:
|
|
157
|
+
summary: >
|
|
158
|
+
Report the captured execution outcome — recorded as the RECORD's evidence tier
|
|
159
|
+
(application_confirmed). The credential sealed at decision time is immutable; evidence
|
|
160
|
+
accrues on the record, never inside the sealed bytes.
|
|
158
161
|
requestBody:
|
|
159
162
|
required: true
|
|
160
163
|
content:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decidio/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|