@alter-ai/cli 0.3.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +26 -188
  2. package/dist/cli.js +193 -44
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -1,208 +1,46 @@
1
- # `@alter-ai/cli` — the Alter Vault command-line interface
1
+ # `@alter-ai/cli`
2
2
 
3
- `alter` is a command-line client for the Alter Vault dev portal. It authenticates with a Personal Access Token (PAT) minted from the dashboard and exposes the same dashboard operations as scriptable commands.
3
+ Command-line client for the Alter Vault dev portal. Authenticates with a Personal Access Token (PAT) and exposes the dashboard's resource model as scriptable commands.
4
+
5
+ 📖 **Full docs:** [docs.alterauth.com/reference/cli](https://docs.alterauth.com/reference/cli)
4
6
 
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  npm install -g @alter-ai/cli
9
- # verify
10
11
  alter --version
11
12
  ```
12
13
 
13
- ## Quick start
14
-
15
- The fastest interactive sign-in is the browser-dance flow — `alter auth login` opens the dashboard, you click **Authorize**, and the CLI receives the freshly-minted token on a localhost listener:
16
-
17
- ```bash
18
- alter auth login
19
- # alter: opening browser at https://portal.alterauth.com/cli-auth
20
- # (waiting up to 2 minutes for you to approve)…
21
- # alter: signed in via browser-dance flow.
22
-
23
- alter auth status
24
- ```
25
-
26
- For headless / CI environments, mint a PAT manually from the dashboard (**Settings → Personal Access Tokens → New token**) and feed it to the CLI through one of the channels below:
27
-
28
- **Token file (recommended for local headless use):** keeps the value out of `process.argv` (visible to other users via `ps`) and out of shell history.
29
-
30
- ```bash
31
- umask 077 && echo "alter_pat_xxxxxxxxxxxxxxxxxxxxxxxx_yyyyyy" > ~/alter-pat.txt
32
- alter auth login --token-file ~/alter-pat.txt
33
- ```
34
-
35
- **Stdin:**
36
-
37
- ```bash
38
- pbpaste | alter auth login --token-stdin # macOS
39
- xclip -o -selection clipboard | alter auth login --token-stdin # Linux
40
- ```
41
-
42
- **Env var (CI):** skip persistence entirely. The SDK reads `ALTER_PAT` directly; it never lands in argv.
14
+ ## Sign in
43
15
 
44
16
  ```bash
45
- export ALTER_PAT="alter_pat_xxxxxxxxxxxxxxxxxxxxxxxx_yyyyyy"
46
- alter apps list
17
+ alter auth login # interactive browser flow
18
+ ALTER_PAT=alter_pat_... alter apps list # headless / CI
47
19
  ```
48
20
 
49
- **Inline `--token <pat>` is supported but discouraged** — the value lands in `process.argv` (visible to other local users via `ps aux`) and shell history. Use only on single-user machines and rotate afterwards.
50
-
51
21
  ## Commands
52
22
 
53
- The CLI mirrors the dashboard's resource model. Every namespace lives at `alter <namespace> <verb>`:
54
-
55
- ```
56
- auth login | status | logout Sign in / out + token introspection
57
- apps list | create | show | update | delete Manage applications
58
- keys list | mint | show | rotate | revoke | rename Manage runtime API keys
59
- agents list | create | show | update | revoke Managed-agent identities (+ mint-key, list-keys, revoke-key)
60
- providers list | list-catalog | create | show | update | delete OAuth provider integrations per app
61
- managed-secrets
62
- templates | list | show | create | delete | rotate | access | users |
63
- grants {list, list-for-agent, create, update, revoke} |
64
- groups {list, show} Managed-secret credentials, grants, and access (CRUD + autocomplete helpers)
65
- policy show-app View app-level policy (org-wide policy is dashboard-only)
66
- audit list | show | portal-actions | grant-events | traces Dev-portal audit log
67
- pats whoami Same as `auth status`, under the `pats` namespace
68
- link <app-id> | --status Pin an app to the current directory tree
69
- unlink Clear the workspace pin
70
- completion install | print Generate shell completions (bash/zsh/fish)
71
- self-update --to <v> | --dry-run Upgrade the CLI via npm
72
- sdk-passthrough request <method> <path> [...] Raw authenticated request (escape hatch for routes the CLI doesn't model)
73
- ```
74
-
75
- PAT lifecycle (mint, revoke) remains **dashboard-only** in v1 — a PAT cannot mint or revoke another PAT, including its own. Operators use the dashboard for those actions. Org-wide policy and identity-provider configuration are also dashboard-only per the destructive-action policy in CLAUDE.md.
76
-
77
- Every list / show command accepts `--output=json|table|jsonl` (default: `json` for pipelines, `table` for interactive). Object commands default to `json`. Use `--fields a,b,c` at the top level to project to specific keys (see [Field selection](#field-selection---fields)).
78
-
79
- ## Authentication
80
-
81
- The CLI resolves credentials in this order (highest precedence first):
82
-
83
- 1. **`ALTER_PAT` environment variable** — canonical CI / headless source.
84
- 2. **OS keychain** (macOS Keychain, Linux Secret Service / `gnome-keyring`, Windows Credential Manager) via the optional `keytar` native module. This is the default location after a successful `alter auth login`.
85
- 3. **Plaintext file** `~/.config/alter/auth.toml` (XDG-compliant, mode `0600`). Used as the fallback when `keytar` failed to build on the host (e.g. missing `libsecret-1-dev` on Linux). The CLI prints a warning at login time when it falls back to this path.
86
-
87
- The `--base-url` flag and the `ALTER_BASE_URL` environment variable both require an `https://` URL — non-HTTPS schemes (`http`, `file`, `gopher`, etc.) are rejected at login time so a misconfigured backend URL cannot exfiltrate the PAT in clear text or to an unintended target.
88
-
89
- If keytar isn't loading on your host, install the native build tools and re-install:
90
-
91
- ```bash
92
- # macOS
93
- xcode-select --install
94
- # Linux (Debian/Ubuntu — adjust for your distro)
95
- sudo apt install libsecret-1-dev gnome-keyring
96
- # Windows — install windows-build-tools or VS Build Tools
97
-
98
- npm install -g @alter-ai/cli
99
- ```
100
-
101
- ## Workspace config (`alter link`)
102
-
103
- If you work primarily on one app, run `alter link <app-id>` once in the project root. The CLI writes a tiny `.alter/config.yaml` that pins the default app for every subsequent `alter keys`, `alter agents`, `alter providers`, and `alter policy show-app` invocation in that directory tree. No more retyping the UUID.
104
-
105
- ```bash
106
- cd ~/code/my-product
107
- alter link app_abc123
108
- # alter: pinned app_id=app_abc123 in /Users/me/code/my-product/.alter/config.yaml
109
- # alter: appended `.alter/` to .gitignore so the pin isn't committed.
110
-
111
- # from anywhere in this tree, --app becomes optional:
112
- alter keys list
113
- alter agents create --name worker --type service
114
- alter policy show-app
115
-
116
- # show the current pin
117
- alter link --status
118
-
119
- # clear the pin
120
- alter unlink
121
23
  ```
122
-
123
- `app_id` precedence (highest first):
124
-
125
- 1. `--app <id>` on the command line
126
- 2. `ALTER_APP_ID` env var
127
- 3. The nearest `.alter/config.yaml` found by walking up from `process.cwd()`
128
- 4. Error: `no app selected` (exit code 2)
129
-
130
- Discovery walks up the directory tree until it finds an `.alter/` directory OR crosses your home directory (whichever comes first). The home-dir bound prevents a misplaced config in `$HOME` from silently pinning every shell.
131
-
132
- If your project lives in a git repo, `alter link` appends `.alter/` to `.gitignore` so the workspace pin doesn't get committed — same posture as `vercel link`. The pin is personal-to-the-checkout.
133
-
134
- ## Field selection (`--fields`)
135
-
136
- Narrow the JSON / JSONL output to specific top-level keys with `--fields a,b,c` at the top level — useful for scripts that only need one or two columns. Inert when paired with `--output=table` (the table columns are already a pre-defined slice). Missing fields render as `null` to keep the column visible.
137
-
138
- ```bash
139
- # List apps, keep only id + name
140
- alter apps list --fields id,name
141
- # [
142
- # { "id": "app_abc", "name": "demo" },
143
- # ...
144
- # ]
145
-
146
- # Single object — same projection rule
147
- alter apps show app_abc --fields id,name,environment
148
-
149
- # JSONL — one projected object per line
150
- alter audit list --fields timestamp,action --output=jsonl
24
+ auth login | status | logout
25
+ apps list | create | show | update | archive | unarchive | delete
26
+ keys list | mint | show | rotate | revoke | rename
27
+ agents list | create | show | update | revoke (+ mint-key, list-keys, revoke-key)
28
+ providers list | list-catalog | create | show | update | delete
29
+ managed-secrets list | show | create | rotate | delete | templates | access |
30
+ set-delegation-policy | users |
31
+ grants {list, list-for-agent, create, update, revoke} |
32
+ groups {list, show}
33
+ policy show-app
34
+ audit list | show | portal-actions | grant-events | traces
35
+ pats whoami
36
+ link / unlink pin an app to the current directory
37
+ completion install | print
38
+ self-update --to <v>
39
+ sdk-passthrough request <grant-id> --url <url>
151
40
  ```
152
41
 
153
- The `--fields` value is `,`-separated. Spaces around commas are ignored; spaces INSIDE a field name are rejected up front (catches the common `--fields 'id name'` typo with a clear error rather than silently dropping the second name).
154
-
155
- ## Exit codes
156
-
157
- `alter` returns a structured exit code so scripts can branch on the failure mode without parsing stderr text. Codes are stable contract — operators can rely on them across releases.
158
-
159
- | Code | Name | Meaning |
160
- |------|------------------|-----------------------------------------------------------------------------------------------|
161
- | 0 | OK | Command succeeded. |
162
- | 1 | ERROR | Generic runtime failure (uncategorized — including unknown SDK / network errors). |
163
- | 2 | USAGE | Bad flag, arg, or input format. Always paired with a stderr line naming the offending input. |
164
- | 3 | AUTH | Not signed in, or PAT revoked / expired. **Remediation:** re-run `alter auth login`. |
165
- | 4 | NOT_FOUND | Resource not found — 404 from the backend, or a referenced local file is missing. |
166
- | 5 | CONFLICT | 409 from the backend — most commonly a type-to-confirm mismatch or dependent-resource block. |
167
- | 6 | RATE_LIMIT | 429 from the backend — retry with backoff. |
168
- | 7 | FORBIDDEN | 403 from the backend — PAT is valid but lacks the required scope. **Remediation:** re-mint the PAT with broader scopes (or switch PATs); `alter auth login` alone does NOT help. |
169
- | 8 | CANCELLED | Operator declined an interactive prompt (type-to-confirm mismatch on a destructive action, "no" at a y/N gate). Distinct from `ERROR` (1) — the CLI did nothing wrong, the operator chose not to proceed. Pass `--yes` or `--confirm <name>` in CI to skip the prompt. |
170
-
171
- Example:
172
-
173
- ```bash
174
- # Probe whether an app exists without erroring on the not-found case.
175
- # Capture $? into a local variable BEFORE running anything else; ``$?``
176
- # is clobbered by every command, so a stray ``log_attempt`` between the
177
- # ``if`` and ``elif`` would silently break the not-found branch.
178
- alter apps show "$APP_ID" --output=json > /dev/null 2>&1
179
- status=$?
180
- if [ "$status" -eq 0 ]; then
181
- echo "app exists"
182
- elif [ "$status" -eq 4 ]; then
183
- echo "app not found"
184
- elif [ "$status" -eq 7 ]; then
185
- echo "PAT lacks dashboard_apps:read — re-mint with broader scopes"
186
- else
187
- echo "unexpected error" && exit 1
188
- fi
189
- ```
190
-
191
- Backend-thrown errors flow through `withClient` and get mapped from HTTP status to exit code automatically (401 → 3, 403 → 7, 404 → 4, 409 → 5, 429 → 6, everything else → 1). Validation errors raised by the CLI itself (e.g. malformed `--limit`) exit 2.
192
-
193
- ## Scope
194
-
195
- This CLI ships the full dev-portal command surface — `auth`, `apps`, `keys`, `agents`, `providers`, `managed-secrets`, `policy` (read-only at the app level), `audit`, `pats`, `link` / `unlink`, `completion`, `self-update`, and `sdk-passthrough` as a typed-route escape hatch. Backend routes are PAT-callable via `dashboard_*` scopes (see the [scope catalog](https://docs.alterauth.com/api-reference/scopes) for the full list).
196
-
197
- **Managed secrets — destructive verb tier:** `alter managed-secrets delete <secret-id>` cascade-revokes every grant and delegation tied to the secret, removes the stored credential from secret storage, and writes cascade audit log entries — irrecoverable. The route is gated by `dashboard_secrets:delete` (NOT bundled into `:write` or `:admin`) AND requires `?confirm=<slug>` matching the target secret's slug. The CLI prompts interactively when stdin is a TTY; CI must pass `--confirm <slug>` explicitly. Mirrors `alter apps delete`. Soft-delete operations on grants (`grants revoke`) use the recoverable `:write` tier.
198
-
199
- **Managed secrets — credential intake:** `create` and `rotate` accept the credential value through three channels, in decreasing safety order: `--credential-value -` reads one line from stdin (preferred for CI piping), `--credential-value @/path/to/file` reads from a file, and `--credential-value <value>` accepts the value inline with a stderr warning about shell-history leakage. Multi-field templates (those whose backend Pydantic model requires more than a single primary credential string) take `--credentials @file.json` (a JSON object of string fields, takes precedence) or repeated `--credential-field key=value` flags.
200
-
201
- **Dashboard-only operations** (intentional, not scope gaps):
42
+ All commands accept `--output=json|table|jsonl` and `--fields a,b,c`. See [scripting](https://docs.alterauth.com/reference/cli/scripting) for exit codes and CI patterns, and [authentication](https://docs.alterauth.com/reference/cli/authentication) for token storage details.
202
43
 
203
- - PAT mint / revoke — a PAT cannot manage another PAT, including its own.
204
- - Org-wide key policy (`/organizations/current/key-policy`) — reading the response body is a security-posture fingerprint, so the route refuses PAT auth on both reads and writes.
205
- - Identity-provider configuration — affects every grant in the org; never settable by a single scripted call.
206
- - App-level policy *writes* / *deletes* — only `policy show-app` is exposed by the CLI; mutations remain dashboard-only until a CLI use case emerges.
44
+ ## License
207
45
 
208
- When the CLI doesn't model a route you need, drop down to `alter sdk-passthrough request <METHOD> <path>` — it forwards the request with the current PAT and prints the raw JSON.
46
+ MIT
package/dist/cli.js CHANGED
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli.ts
4
- import { Command as Command14 } from "commander";
5
-
6
3
  // src/exit-codes.ts
7
4
  var EXIT_ERROR = 1;
8
5
  var EXIT_USAGE = 2;
@@ -176,7 +173,7 @@ async function maybePrintUpdateBanner(currentVersion, argv2 = process.argv.slice
176
173
  // package.json
177
174
  var package_default = {
178
175
  name: "@alter-ai/cli",
179
- version: "0.3.0",
176
+ version: "0.3.2",
180
177
  description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
181
178
  type: "module",
182
179
  bin: {
@@ -192,7 +189,9 @@ var package_default = {
192
189
  dev: "tsx src/cli.ts",
193
190
  test: "vitest run",
194
191
  "test:watch": "vitest",
195
- typecheck: "tsc --noEmit",
192
+ "docs:check": "tsx scripts/check-docs-drift.ts",
193
+ "docs:print": "tsx scripts/check-docs-drift.ts --print",
194
+ typecheck: "tsc --noEmit && tsc -p tsconfig.scripts.json",
196
195
  lint: "eslint src/ tests/ --ext .ts",
197
196
  format: "prettier --write 'src/**/*.ts' 'tests/**/*.ts'"
198
197
  },
@@ -224,6 +223,9 @@ var package_default = {
224
223
  }
225
224
  };
226
225
 
226
+ // src/program.ts
227
+ import { Command as Command14 } from "commander";
228
+
227
229
  // src/commands/agents.ts
228
230
  import { Command } from "commander";
229
231
 
@@ -6227,6 +6229,21 @@ var Agent = class _Agent {
6227
6229
  async connect(options) {
6228
6230
  return this.#client.connect(options);
6229
6231
  }
6232
+ /**
6233
+ * Poll a Connect session to completion. See
6234
+ * {@link _VaultClient.pollConnectSession} for the full signature.
6235
+ */
6236
+ async pollConnectSession(...args) {
6237
+ return this.#client.pollConnectSession(...args);
6238
+ }
6239
+ /**
6240
+ * Mint a recovery Connect session from a typed error. See
6241
+ * {@link _VaultClient.createConnectSessionForError} for the full
6242
+ * signature.
6243
+ */
6244
+ async createConnectSessionForError(...args) {
6245
+ return this.#client.createConnectSessionForError(...args);
6246
+ }
6230
6247
  // ── Delegation self-revoke ─────────────────────────────────────────────
6231
6248
  /**
6232
6249
  * Opt out of this agent's delegation on an OAuth grant.
@@ -6406,6 +6423,21 @@ var App = class _App {
6406
6423
  async connect(options) {
6407
6424
  return this.#client.connect(options);
6408
6425
  }
6426
+ /**
6427
+ * Poll a Connect session to completion. See
6428
+ * {@link _VaultClient.pollConnectSession} for the full signature.
6429
+ */
6430
+ async pollConnectSession(...args) {
6431
+ return this.#client.pollConnectSession(...args);
6432
+ }
6433
+ /**
6434
+ * Mint a recovery Connect session from a typed error. See
6435
+ * {@link _VaultClient.createConnectSessionForError} for the full
6436
+ * signature.
6437
+ */
6438
+ async createConnectSessionForError(...args) {
6439
+ return this.#client.createConnectSessionForError(...args);
6440
+ }
6409
6441
  // ── Auth (operator-only) ───────────────────────────────────────────────
6410
6442
  async authenticate(options) {
6411
6443
  return this.#client.authenticate(options);
@@ -6839,7 +6871,7 @@ var DEFAULT_BASE_URL = "https://backend.alterauth.com";
6839
6871
  var PAT_API_PREFIX = "/api/v1/dev-portal";
6840
6872
  var HTTP_ERROR_THRESHOLD = 400;
6841
6873
  var DEFAULT_TIMEOUT_MS = 3e4;
6842
- var CLI_VERSION = "0.3.0";
6874
+ var CLI_VERSION = "0.3.2";
6843
6875
  var USER_AGENT = buildUserAgent();
6844
6876
  function buildUserAgent() {
6845
6877
  let osTag = "";
@@ -7770,6 +7802,31 @@ var ManagedSecretsNamespace = class {
7770
7802
  );
7771
7803
  return expectDict(body, "managed_secrets.rotate", 200);
7772
7804
  }
7805
+ /**
7806
+ * Set the per-secret user → agent delegation policy. Requires
7807
+ * ``dashboard_secrets:write``. Controls whether a group-typed grant on
7808
+ * this secret may be delegated to an agent (``allow_group_source``) and
7809
+ * the per-delegation TTL ceiling (``max_delegation_ttl_days``).
7810
+ *
7811
+ * REPLACE semantics — the backend overwrites the whole policy object,
7812
+ * so the command always sends the full intended state (omitting
7813
+ * ``max_delegation_ttl_days`` lets the backend apply its 90-day default).
7814
+ */
7815
+ async setDelegationPolicy(appId, secretId, options) {
7816
+ const app = encodePathParam(appId, "appId");
7817
+ const secret = encodePathParam(secretId, "secretId");
7818
+ const payload = filterUndefined({
7819
+ allow_group_source: options.allow_group_source,
7820
+ max_delegation_ttl_days: options.max_delegation_ttl_days
7821
+ });
7822
+ const body = await this.#client._call(
7823
+ "PUT",
7824
+ `/apps/${app}/managed-secrets/${secret}/delegation-policy`,
7825
+ "managed_secrets.set_delegation_policy",
7826
+ { jsonBody: payload }
7827
+ );
7828
+ return expectDict(body, "managed_secrets.set_delegation_policy", 200);
7829
+ }
7773
7830
  /**
7774
7831
  * List grants on a managed secret. Requires ``dashboard_secrets:read``.
7775
7832
  *
@@ -10686,7 +10743,7 @@ function buildKeysCommand() {
10686
10743
  }
10687
10744
  );
10688
10745
  keys.command("rotate").description(
10689
- "Rotate a key (new plaintext returned ONCE; old key enters grace until revoke). Requires dashboard_keys:admin scope."
10746
+ "Rotate a key (new plaintext returned ONCE; old key enters grace until revoke). Requires dashboard_keys:write scope."
10690
10747
  ).option("--app <app-id>", "App ID. Falls back to ALTER_APP_ID env or .alter/config.yaml").requiredOption("--key <key-id>", "Key ID", parseUuidArgument("--key")).option(
10691
10748
  "--scopes <list>",
10692
10749
  "Optional new scope set (defaults to the key's current scopes)"
@@ -11695,7 +11752,14 @@ function buildManagedSecretsCommand() {
11695
11752
  ).option(
11696
11753
  "--injection-rule <@file.json>",
11697
11754
  "Path-prefixed JSON file containing the additional_injections array"
11698
- ).option("--label <label>", "Base-grant display label").option("--account-identifier <id>", "Optional account identifier metadata").option("--account-display-name <name>", "Optional account display name metadata").option("--output <format>", "Output format: json|table (default: json)", "json").action(
11755
+ ).option("--label <label>", "Base-grant display label").option("--account-identifier <id>", "Optional account identifier metadata").option("--account-display-name <name>", "Optional account display name metadata").option(
11756
+ "--allow-group-delegation",
11757
+ "Allow members of a group-typed grant on this secret to delegate it to an agent (default: off)"
11758
+ ).option(
11759
+ "--max-delegation-ttl-days <days>",
11760
+ "Cap on a single delegation's lifetime, in days (1..1825, default 90)",
11761
+ parseBoundedInt("--max-delegation-ttl-days", 1, 5 * 365)
11762
+ ).option("--output <format>", "Output format: json|table (default: json)", "json").action(
11699
11763
  async (options) => {
11700
11764
  const format = coerceOutputFormat(options.output);
11701
11765
  const appId = resolveAppIdOrExit(options.app);
@@ -11718,7 +11782,9 @@ function buildManagedSecretsCommand() {
11718
11782
  "injectionRule",
11719
11783
  "label",
11720
11784
  "accountIdentifier",
11721
- "accountDisplayName"
11785
+ "accountDisplayName",
11786
+ "allowGroupDelegation",
11787
+ "maxDelegationTtlDays"
11722
11788
  ].filter((k) => {
11723
11789
  if (k === "credentialType") return options.credentialType !== "bearer_token";
11724
11790
  return options[k] !== void 0;
@@ -11804,6 +11870,16 @@ function buildManagedSecretsCommand() {
11804
11870
  if (options.accountDisplayName !== void 0) {
11805
11871
  body.account_display_name = options.accountDisplayName;
11806
11872
  }
11873
+ if (options.allowGroupDelegation !== void 0 || options.maxDelegationTtlDays !== void 0) {
11874
+ const delegationPolicy = {};
11875
+ if (options.allowGroupDelegation !== void 0) {
11876
+ delegationPolicy.allow_group_source = options.allowGroupDelegation;
11877
+ }
11878
+ if (options.maxDelegationTtlDays !== void 0) {
11879
+ delegationPolicy.max_delegation_ttl_days = options.maxDelegationTtlDays;
11880
+ }
11881
+ body.delegation_policy = delegationPolicy;
11882
+ }
11807
11883
  await withClient(async (client) => {
11808
11884
  const row = await client.managedSecrets.create(
11809
11885
  resolveAppIdOrExit(options.app),
@@ -11813,6 +11889,29 @@ function buildManagedSecretsCommand() {
11813
11889
  });
11814
11890
  }
11815
11891
  );
11892
+ root.command("set-delegation-policy <secret-id>").description(
11893
+ "Set the user \u2192 agent delegation policy on a managed secret. REPLACES the current policy (omitting --allow-group-delegation turns group delegation OFF). Requires dashboard_secrets:write."
11894
+ ).option("--app <app-id>", "App ID. Falls back to ALTER_APP_ID env or .alter/config.yaml").option(
11895
+ "--allow-group-delegation",
11896
+ "Allow members of a group-typed grant on this secret to delegate it to an agent (default: off)"
11897
+ ).option(
11898
+ "--max-delegation-ttl-days <days>",
11899
+ "Cap on a single delegation's lifetime, in days (1..1825; omit for the 90-day default)",
11900
+ parseBoundedInt("--max-delegation-ttl-days", 1, 5 * 365)
11901
+ ).option("--output <format>", "Output format: json|table (default: json)", "json").action(
11902
+ async (rawSecretId, options) => {
11903
+ const format = coerceOutputFormat(options.output);
11904
+ const appId = resolveAppIdOrExit(options.app);
11905
+ const secretId = parseUuidArgument("<secret-id>")(rawSecretId);
11906
+ await withClient(async (client) => {
11907
+ const row = await client.managedSecrets.setDelegationPolicy(appId, secretId, {
11908
+ allow_group_source: options.allowGroupDelegation === true,
11909
+ max_delegation_ttl_days: options.maxDelegationTtlDays
11910
+ });
11911
+ emit(format, row);
11912
+ });
11913
+ }
11914
+ );
11816
11915
  root.command("delete <secret-id>").description(
11817
11916
  "Cascade-delete a managed secret. Requires dashboard_secrets:delete scope (NOT bundled into :write). Cascade-revokes grants + delegations + audit anchors and vault-deletes the credential."
11818
11917
  ).option("--app <app-id>", "App ID").option(
@@ -12092,6 +12191,46 @@ function collectRedirectUris(value, previous = []) {
12092
12191
  const next = value.split(",").map((s) => s.trim()).filter(Boolean);
12093
12192
  return [...previous, ...next];
12094
12193
  }
12194
+ function sanitizeStderrText(value) {
12195
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
12196
+ }
12197
+ function surfaceProviderResponse(row) {
12198
+ if (typeof row !== "object" || row === null) return;
12199
+ const r = row;
12200
+ const preflight = r.preflight;
12201
+ if (preflight && typeof preflight === "object") {
12202
+ const status = preflight.status;
12203
+ const msg = typeof preflight.message === "string" ? sanitizeStderrText(preflight.message) : "";
12204
+ if (status === "passed") {
12205
+ process.stderr.write("alter: preflight: passed\n");
12206
+ } else if (status === "failed") {
12207
+ process.stderr.write(
12208
+ `alter: preflight: failed${msg ? ` \u2014 ${msg}` : ""}
12209
+ `
12210
+ );
12211
+ } else if (status === "inconclusive") {
12212
+ process.stderr.write(
12213
+ `alter: preflight: inconclusive${msg ? ` \u2014 ${msg}` : ""} (save was allowed)
12214
+ `
12215
+ );
12216
+ }
12217
+ }
12218
+ const bc = r.breaking_changes;
12219
+ if (Array.isArray(bc) && bc.length > 0) {
12220
+ process.stderr.write(
12221
+ `alter: warning: ${bc.length} breaking change(s) \u2014 ${sanitizeStderrText(JSON.stringify(bc))}
12222
+ `
12223
+ );
12224
+ }
12225
+ const warning = r.warning;
12226
+ if (typeof warning === "string") {
12227
+ const sanitized = sanitizeStderrText(warning);
12228
+ if (sanitized.length > 0) {
12229
+ process.stderr.write(`alter: warning: ${sanitized}
12230
+ `);
12231
+ }
12232
+ }
12233
+ }
12095
12234
  function buildProvidersCommand() {
12096
12235
  const providers = new Command11("providers").description(
12097
12236
  "Manage OAuth provider configs"
@@ -12182,6 +12321,7 @@ function buildProvidersCommand() {
12182
12321
  appId,
12183
12322
  body
12184
12323
  );
12324
+ surfaceProviderResponse(row);
12185
12325
  emit(format, row);
12186
12326
  });
12187
12327
  return;
@@ -12221,6 +12361,7 @@ function buildProvidersCommand() {
12221
12361
  scopes,
12222
12362
  redirect_uris: redirects
12223
12363
  });
12364
+ surfaceProviderResponse(row);
12224
12365
  emit(format, row);
12225
12366
  });
12226
12367
  }
@@ -12267,6 +12408,7 @@ function buildProvidersCommand() {
12267
12408
  options.provider,
12268
12409
  body
12269
12410
  );
12411
+ surfaceProviderResponse(row);
12270
12412
  emit(format, row);
12271
12413
  });
12272
12414
  return;
@@ -12293,6 +12435,7 @@ function buildProvidersCommand() {
12293
12435
  redirect_uris: redirects,
12294
12436
  status: providerStatus
12295
12437
  });
12438
+ surfaceProviderResponse(row);
12296
12439
  emit(format, row);
12297
12440
  });
12298
12441
  }
@@ -12565,6 +12708,46 @@ function buildSelfUpdateCommand() {
12565
12708
  });
12566
12709
  }
12567
12710
 
12711
+ // src/program.ts
12712
+ function buildProgram() {
12713
+ const program2 = new Command14();
12714
+ program2.name("alter").description("Alter Vault command-line interface").version(package_default.version);
12715
+ program2.option(
12716
+ "--fields <list>",
12717
+ "Comma-separated top-level keys to keep in JSON output (e.g. ``--fields id,name``). Inert with --output=table."
12718
+ );
12719
+ program2.hook("preAction", (thisCommand) => {
12720
+ setGlobalFields(void 0);
12721
+ const raw = thisCommand.opts().fields;
12722
+ if (raw !== void 0) {
12723
+ try {
12724
+ setGlobalFields(parseFieldsList(raw));
12725
+ } catch (e) {
12726
+ process.stderr.write(
12727
+ `alter: ${e instanceof Error ? e.message : String(e)}
12728
+ `
12729
+ );
12730
+ process.exit(EXIT_USAGE);
12731
+ }
12732
+ }
12733
+ });
12734
+ program2.addCommand(buildAuthCommand());
12735
+ program2.addCommand(buildAppsCommand());
12736
+ program2.addCommand(buildKeysCommand());
12737
+ program2.addCommand(buildAgentsCommand());
12738
+ program2.addCommand(buildProvidersCommand());
12739
+ program2.addCommand(buildManagedSecretsCommand());
12740
+ program2.addCommand(buildPolicyCommand());
12741
+ program2.addCommand(buildAuditCommand());
12742
+ program2.addCommand(buildPatsCommand());
12743
+ program2.addCommand(buildLinkCommand());
12744
+ program2.addCommand(buildUnlinkCommand());
12745
+ program2.addCommand(buildCompletionCommand());
12746
+ program2.addCommand(buildSdkPassthroughCommand());
12747
+ program2.addCommand(buildSelfUpdateCommand());
12748
+ return program2;
12749
+ }
12750
+
12568
12751
  // src/cli.ts
12569
12752
  var COMMANDER_USAGE_CODES = /* @__PURE__ */ new Set([
12570
12753
  "commander.missingArgument",
@@ -12628,41 +12811,7 @@ function rewriteLegacySelfUpdateVersionFlag(argv2) {
12628
12811
  }
12629
12812
  return rewritten;
12630
12813
  }
12631
- var program = new Command14();
12632
- program.name("alter").description("Alter Vault command-line interface").version(package_default.version);
12633
- program.option(
12634
- "--fields <list>",
12635
- "Comma-separated top-level keys to keep in JSON output (e.g. ``--fields id,name``). Inert with --output=table."
12636
- );
12637
- program.hook("preAction", (thisCommand) => {
12638
- setGlobalFields(void 0);
12639
- const raw = thisCommand.opts().fields;
12640
- if (raw !== void 0) {
12641
- try {
12642
- setGlobalFields(parseFieldsList(raw));
12643
- } catch (e) {
12644
- process.stderr.write(
12645
- `alter: ${e instanceof Error ? e.message : String(e)}
12646
- `
12647
- );
12648
- process.exit(EXIT_USAGE);
12649
- }
12650
- }
12651
- });
12652
- program.addCommand(buildAuthCommand());
12653
- program.addCommand(buildAppsCommand());
12654
- program.addCommand(buildKeysCommand());
12655
- program.addCommand(buildAgentsCommand());
12656
- program.addCommand(buildProvidersCommand());
12657
- program.addCommand(buildManagedSecretsCommand());
12658
- program.addCommand(buildPolicyCommand());
12659
- program.addCommand(buildAuditCommand());
12660
- program.addCommand(buildPatsCommand());
12661
- program.addCommand(buildLinkCommand());
12662
- program.addCommand(buildUnlinkCommand());
12663
- program.addCommand(buildCompletionCommand());
12664
- program.addCommand(buildSdkPassthroughCommand());
12665
- program.addCommand(buildSelfUpdateCommand());
12814
+ var program = buildProgram();
12666
12815
  applyExitOverride(program);
12667
12816
  var argv = rewriteLegacySelfUpdateVersionFlag(process.argv);
12668
12817
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alter-ai/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Command-line interface for the Alter Vault dev portal — scripted dashboard automation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,9 @@
16
16
  "dev": "tsx src/cli.ts",
17
17
  "test": "vitest run",
18
18
  "test:watch": "vitest",
19
- "typecheck": "tsc --noEmit",
19
+ "docs:check": "tsx scripts/check-docs-drift.ts",
20
+ "docs:print": "tsx scripts/check-docs-drift.ts --print",
21
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
20
22
  "lint": "eslint src/ tests/ --ext .ts",
21
23
  "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'"
22
24
  },