@decidio/sdk 0.8.1 → 0.10.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/CHANGELOG.md CHANGED
@@ -5,6 +5,82 @@ the gate, signing, receipts and the verifier - is conformance-locked between the
5
5
  first-run surfaces (the fork, `--demo`, the code sign-in) are TypeScript-first and named as such in the parity
6
6
  allowlist. Each entry names what changed, what a reader must do on upgrade, and what proved it.
7
7
 
8
+ ## Unreleased
9
+
10
+ ## 0.10.0 — connect a coding assistant in one tap
11
+
12
+ - **`npx @decidio/sdk connect`** (cycle connect). A coding assistant sets Decidio up for you: it runs `connect`, which prints a
13
+ link and a short code (on a real terminal also a QR, and it opens the browser); the workspace owner opens the link signed in,
14
+ checks the code, picks who approves and taps Allow; `connect --wait` then returns by itself (exit 0 connected, 1 still
15
+ waiting - run it again, 3 refused or expired; within 90 s, under an assistant's 2-minute command limit). The machine gets an
16
+ ask-only credential - never a session - kept OUTSIDE the repository (the config dir, owner-only, keyed by the repository root
17
+ and the Decidio), and the hooks switch on in `.claude/settings.local.json` (`--team`: the shared `.claude/settings.json`).
18
+ No `.env`, nothing to paste, the word "agent" nowhere in the setup.
19
+ - **In a cloud session the hooks go in `.claude/settings.json`.** A cloud session reads only the repository's own settings file,
20
+ never `.claude/settings.local.json` (Claude Code's docs), so that is where they work. connect leaves it uncommitted and says to ask
21
+ the person before committing it (committed, every cloud session of the repository and every teammate's clone ask too). The
22
+ credential is that cloud machine's: a fresh one refuses destructive commands until it connects again.
23
+ - **The machine proves its key.** The start is signed with the machine's own key (the request-bound proof every signed
24
+ request carries, over the assistant, project, machine and cloud-or-local), so nobody can file a connect in another
25
+ machine's name; the server refuses a start without it. The connect checks the wire first (https, or a local address),
26
+ since the poll's answer carries the credential.
27
+ - **Credentials, in order:** the shell, then the folder's `.env`, then this repository's connection - all or nothing, so a
28
+ project with an `init` `.env` keeps exactly that identity. `disconnect` deletes this machine's token (gated commands are then
29
+ refused until you connect again; the machine's key stays, so reconnecting reuses its agent).
30
+ - **The hook's words** for an unconnected project now name `connect` and `connect --wait` (the plugin's skill says the same);
31
+ `hook install` with no credentials points at `connect` first.
32
+ - **The links the command line prints follow the API's own web app** (launch plan L1c). Staging's API maps to
33
+ `staging.decidioai.com`, production's (`mcp.decidioai.com`) to `app.decidioai.com`, and any other origin to itself.
34
+ `DECIDIO_APP_URL` wins only as a plain `http(s)` address, and the host line names it.
35
+ - **Release condition:** publish only after `staging.decidioai.com` serves staging's web app (docs/Deployment_Runbook.md
36
+ A1). Tell testers to run `github init` again to refresh their AGENTS.md section.
37
+
38
+ On upgrade: nothing to change - `init` projects keep their `.env`. The plugin pins 0.10.0. `connect` needs a Decidio that
39
+ serves `/connect/*` (the lab, from cycle connect's deploy). Proved by: cli-smoke.ts (356 → 378 checks: connect, its signed
40
+ start, wait, reconnect, disconnect, refused, expired, a cloud session, --team, beside the plugin, .env precedence, the hook
41
+ through a connection).
42
+
43
+ ## 0.9.0 — the coding-agent hook catches what agents type, protects itself, and tells the approver what was asked
44
+
45
+ - **Wider matching, from real agent runs.** `sql-destructive` counts a statement sent through an interpreter or runner
46
+ (`python3 db.py "drop table x"`, `python3 -c`, `node -e`, a here-doc), checked per segment so a commit message that
47
+ says "drop table" is not gated; options are read by name before `=` (`--force-with-lease=<ref>`); git's `-C`/`-c` are
48
+ skipped; a command handed to a shell (`bash -c`, `sh -c`, `pwsh -Command`) is matched inside, two shells deep; a file
49
+ the command runs is followed one level inside the project (shell scripts, SQL files, `package.json` scripts, DROP or
50
+ TRUNCATE in a program file run directly) - the request carries the matched line as `scriptLine` and the file's sha256
51
+ in its keyed scope, never the file.
52
+ - **`decidio-guard`.** Write, Edit and MultiEdit to `.claude/settings*.json`, `~/.claude/settings*.json` or the Decidio
53
+ plugin's files are gated when the file after the change drops or alters a Decidio hook, sets `disableAllHooks`, turns
54
+ the plugin off, or is not valid JSON (adding a permission or another hook is not); shell writes to those files,
55
+ `claude plugin disable|uninstall` naming Decidio and `hook install --cli` are gated whatever they write. The request
56
+ binds tool + path + the after-file's sha256. `hook install` writes PreToolUse and PostToolUse handlers for the three
57
+ tools, one `if` per path (Claude Code's `if` holds one rule and is tool-specific), and keys its merge on event +
58
+ matcher + `if`: a re-run keeps each handler once and upgrades an 0.8 install in place.
59
+ - **A policy `proceed` prints nothing** (exit 0; the state is kept, so posttool still records the execution). Claude Code
60
+ records a hook `allow` as the hook's decision in place of its own permission check; a company rule that something
61
+ needs no person now leaves that check in place. A person's approval of the exact call still prints `allow`.
62
+ - **Card context.** The request context gains `mode`, `repo`, `branch`, `commit` and the user's prompt (`asked`, and
63
+ `askedBefore` when the last prompt is under 40 characters; each through the redaction, at most 1,000 characters;
64
+ `askedLength`), read from the transcript's tail only for a gated call. `DECIDIO_HOOK_SHARE_PROMPT=0` leaves the prompt
65
+ out. The request commitment no longer covers these (they change between the ask and the retry), so a retry after the
66
+ user typed "continue" matches its approval instead of a 409.
67
+ - **An agent running `approvals approve|reject`** (with the person's stored sign-in, which the agent's shell inherits) is
68
+ denied by the hook itself in its usual spellings (`npx`, `decidio`, the installed or cached `cli.js`, `python -m decidio`,
69
+ `uvx`, inside `bash -c`, an option before the subcommand); listing, `hook wait` and `receipt` are untouched. A shell
70
+ check is not a complete close (the Backlog names the server-side one).
71
+ - The prompt reader skips transcript entries that are not the user's words (a background task's notification, bash-mode
72
+ input and output, a local command's output, a compaction summary); a slash command the user typed counts. The settings
73
+ guard finds an Edit's string the way Claude Code's Edit does - verbatim, else with curly and straight quotes alike - and
74
+ an empty `old_string` on an empty or whitespace-only file as the whole new file.
75
+ - File-tool denials say "edit" and "Nothing was written."; shell denials are unchanged.
76
+
77
+ On upgrade: re-run `npx @decidio/sdk@0.9.0 hook install` (or update the plugin once it pins 0.9.0). A gate older than
78
+ this release stores the new context keys as ordinary details. Proved by: `cli-smoke.ts` (336 → 355 checks; the gated table 42 → 89
79
+ commands, verbatim from the lab's runs; the noise table 24 → 46; the guard's effect, binding, posttool spend and install;
80
+ a mock gate that 409s a reused key with another commitment, where a retry after the prompt changed is ALLOWED; mutation
81
+ runs of each change), and 15 real Claude Code sessions against a loopback gate (every gated case stopped; a policy
82
+ approval left Claude's classifier deciding).
83
+
8
84
  ## 0.7.0 — approvals bind to the exact request they approved; receipts pin to a real trust anchor
9
85
 
10
86
  - **The receipt trust anchor is built in — nobody pastes an issuer DID, and nothing pins to a receipt's own
package/README.md CHANGED
@@ -75,8 +75,8 @@ npx @decidio/sdk init --unique my-agent # --unique appends a random suffix: co
75
75
  Every network command talks to the hosted sandbox by default (`verify` is offline) and announces its target host on
76
76
  stderr each run — set `DECIDIO_API_URL` (env or `.env`) to point at another Decidio instead.
77
77
 
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`
78
+ `init` signs you into your workspace (the 8-character code the CLI emails you — type it where you asked —
79
+ or, for an account made before sign-up became passwordless, its email + password), generates the agent's Ed25519 keypair **locally** (only the public `did:key`
80
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`.
81
81
  Every later command that talks to Decidio reads `.env` from this directory automatically (`verify` is
82
82
  offline and never does). Re-running init for an
@@ -727,7 +727,7 @@ that fits every other confirm error.
727
727
  `quickstart cleanup` works later too.
728
728
  - `init [agentId] [--unique] [--force] [--expires-days N]` — sign in, register, mint the agent token, write `.env` (+ `.gitignore` entries). Refuses to overwrite a *different* agent's key in this directory; `--force` backs the old `.env` up first.
729
729
  - `doctor` — config, connectivity, and what your current token can do
730
- - `approvals [--mine] [--json]` / `approvals approve|reject <id> [reason]` — the human side, from your terminal (the list is workspace-wide; `--mine` narrows it to this directory's agent)
730
+ - `approvals [--mine] [--json]` / `approvals approve|reject <id> [reason]` — the human side, from your terminal (the list is workspace-wide; `--mine` narrows it to this directory's agent). Run by a coding agent behind the hook, `approve|reject` is refused: a person decides, not the agent
731
731
  - `receipt <id> [--json]` — download a sealed decision's Authority Receipt to a `.json` file (`--json`: stdout is the JSON result only; the identities your access covers print only on a terminal, or in the JSON)
732
732
  - `verify [--issuer <did>] <file>...` — verify receipts offline (the canonical verifier; the standalone `@decidio/verify` package is the same implementation, for auditors who want zero Decidio dependency — the sdk depends on the verifier released alongside it, at the same version). Unknown flags are refused, never ignored. It pins to the trust anchor it prints first: `--issuer`, else the DID remembered for your Decidio at sign-in, else the built-in hosted-service issuer — never the receipt's own `issuer` field. Fully offline AND fully decoupled: it touches no network and reads no `.env` (only the issuer DIDs remembered in your profile) — a receipt verifies identically from any directory.
733
733
  - `tokens list [--mine]` / `tokens revoke <jti>` — see and kill agent tokens (values are never re-shown; the jti is the revocation handle). Each row carries its status — `active`, `expired`, or `REVOKED <date>` — so the list confirms the revocations it performs; re-revoking is an honest no-op (exit 1). The list is workspace-wide (a shared workspace shows every member's agents); `--mine` narrows to this directory's agent
@@ -738,6 +738,7 @@ that fits every other confirm error.
738
738
  - `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).
739
739
  - `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).
740
740
  - `dev [--target URL] [--port N]` — local relay for the resume webhook (operator setups)
741
+ - `hook install [--project] [--approver <email>]` / `hook rules` / `hook wait <id>` / `hook justify <id> "<why>"` — the Claude Code hook (0.8.0; widened in 0.9.0). `install` writes PreToolUse/PostToolUse hooks into `.claude/settings.local.json` (`--project`: `settings.json`): the shell calls, plus Write/Edit/MultiEdit on Claude Code's own settings and the Decidio plugin (the `decidio-guard` category: a change that takes Decidio's check away needs a person). The pretool hook fails closed (`onFailure: block`, a hold capped under its 150 s timeout). `hook rules` lists what is gated, including the files a command runs that are followed one level. For a gated call the hook sends Decidio the command (secrets redacted), its directory, category, subject and any matched script line; the permission mode; the repository, branch and commit; and the user's last prompt in the session (with the one before it when the last is under 40 characters; at most 1,000 characters each, secrets redacted) - shown to people allowed to read the request, never on Slack or Teams. `DECIDIO_HOOK_SHARE_PROMPT=0` leaves the prompt out. A policy's auto-approval prints nothing, so Claude Code's own permission check still decides; a person's approval of the exact call allows it once.
741
742
 
742
743
  The CLI's stream contract: **stdout carries results only** (safe to pipe — `--json` output is
743
744
  pure JSON); errors, usage shown on *error* paths, and the per-run target-host announcement go
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ export declare function appUrlFor(api: string, override?: string | undefined): string;
2
3
  export type ApprovalAnswer = "approve" | "reject" | "again";
3
4
  export declare function approvalAnswer(answer: unknown): ApprovalAnswer;
4
5
  /** The issuer store's key for a Decidio address: its origin (scheme + lowercased host + non-default port), computed by a
@@ -50,11 +51,42 @@ export type ParsedAdr = {
50
51
  export declare function parseAdr(text: string): ParsedAdr | null;
51
52
  export declare const AGENTS_MD_START = "<!-- decidio:start -->";
52
53
  export declare const AGENTS_MD_END = "<!-- decidio:end -->";
54
+ export type DeployRowView = {
55
+ decisionId: string;
56
+ dNumber: string | null;
57
+ repository: string;
58
+ environment: string;
59
+ sha: string;
60
+ ref: string;
61
+ state: string;
62
+ line: string;
63
+ checks: string | null;
64
+ requestedAt: string;
65
+ deployedAt: string | null;
66
+ runUrl: string | null;
67
+ recordUrl: string;
68
+ range: {
69
+ fromSha: string | null;
70
+ toSha: string;
71
+ compareUrl: string | null;
72
+ } | null;
73
+ };
74
+ /** PURE: one deploy as one line (plus its range line once deployed). */
75
+ export declare function deployLines(r: DeployRowView): string[];
76
+ /** PURE: what changed between two reads of the list (--watch) - a new deploy, or a deploy whose words moved. */
77
+ export declare function deployChanges(before: DeployRowView[], after: DeployRowView[]): string[];
78
+ /** PURE: a moment from the command line - an ISO date, "now", or a duration from now ("30m", "2h", "1d"). null: not one. */
79
+ export declare function parseWhen(s: string | undefined, now?: Date): Date | null;
80
+ /** PURE: the controls list, one line each. */
81
+ export declare function controlsLines(b: {
82
+ halts?: any[];
83
+ grants?: any[];
84
+ }): string[];
53
85
  /** Decidio's public GitHub Action (github.com/OmniTwin/decidio-actions): one action, step request or verify. It fetches the job's own
54
86
  * OIDC token (audience "decidio") - no repository secret - and fails the job in plain words when Decidio refuses. Pinned to the
55
- * v1.0.1 COMMIT, never a tag (invariant-guard 2026-10-20): the verify step enforces the approval in a production job with
87
+ * v1.3.1 COMMIT, never a tag (invariant-guard 2026-10-20): the verify step enforces the approval in a production job with
56
88
  * id-token: write, so a moved tag must not change it. Bump both pins together, with the release. */
57
- export declare const DECIDIO_ACTION = "OmniTwin/decidio-actions@c4506075600edef9576db821c4de756c300c15fe # v1.0.1";
89
+ export declare const DECIDIO_ACTION = "OmniTwin/decidio-actions@f0b43fcb8f8ac5e15d9ad43679e333ffa7038ae2 # v1.3.1";
58
90
  export declare const CHECKOUT_ACTION = "actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0";
59
91
  export declare function githubRequestWorkflow(api: string, environment: string, branch?: string, deployWorkflow?: string): string;
60
92
  /** The verify step and the checkout of the approved commit: the first steps of any deploy job Decidio starts. */