@intutic/cli 2.8.0 → 2.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @intutic/cli
2
2
 
3
- > AI governance control plane for developer workspaces.
3
+ > AI governance for developer workspaces.
4
4
 
5
5
  Intutic intercepts agent-to-LLM traffic, enforces SOPs, detects anomalies, and tracks costs — all without changing how your team codes.
6
6
 
@@ -21,6 +21,7 @@ intutic start # Run the proxy standalone — no account needed
21
21
  intutic traces list # See governance verdicts
22
22
  ```
23
23
 
24
+ <!-- UNRELEASED:cloud
24
25
  **With a control plane:**
25
26
 
26
27
  ```bash
@@ -29,6 +30,7 @@ intutic init # Detect harnesses, record them for sync
29
30
  intutic connect # Start sync daemon — writes harness rules and gates
30
31
  intutic traces list # See governance verdicts
31
32
  ```
33
+ -->
32
34
 
33
35
  ## Commands
34
36
 
@@ -36,15 +38,17 @@ intutic traces list # See governance verdicts
36
38
  |---------|-------------|
37
39
  | `intutic init` | Initialize workspace — detect harnesses, record them in `~/.intutic/config.json` |
38
40
  | `intutic start` | Start the proxy standalone — no account or control plane needed (`--port`, `--upstream-url`) |
39
- | `intutic login` | Authenticate with control plane (`--api-key`, `--control-plane-url`, `--dev`) |
40
- | `intutic logout` | Clear stored credentials |
41
41
  | `intutic status` | Show workspace status — auth, harnesses, sync state |
42
- | `intutic whoami` | Show current authenticated identity |
43
- | `intutic connect` | Start sync daemon — bidirectional config sync (`--interval`) |
44
42
  | `intutic disconnect` | Undo `intutic connect` — restore the harness configs it changed, remove its services, log out (`--harness`, `--dry-run`, `--keep-login`) |
45
43
  | `intutic doctor` | Diagnose workspace health — proxy, auth, daemon, configs, logs |
46
44
  | `intutic traces list` | List execution traces (`--limit`, `--since`, `--action` with a control plane, `--verdict` without one, `--model`, `--json`) |
47
45
  | `intutic traces inspect <id>` | Show full detail of a single trace |
46
+ <!-- UNRELEASED:cloud
47
+ | `intutic login` | Authenticate with control plane (`--api-key`, `--control-plane-url`, `--dev`) |
48
+ | `intutic logout` | Clear stored credentials |
49
+ | `intutic whoami` | Show current authenticated identity |
50
+ | `intutic connect` | Start sync daemon — bidirectional config sync (`--interval`) |
51
+ -->
48
52
 
49
53
  ## Supported Harnesses
50
54
 
@@ -98,6 +102,7 @@ Credentials are stored at `~/.intutic/credentials.json` (mode `0600`).
98
102
  | `INTUTIC_DEV` | `1` targets the local control plane (`http://localhost:3001`), like `--dev` |
99
103
  | `INTUTIC_PROXY_URL` | Local proxy URL; its port is the proxy port (default `4000`) |
100
104
 
105
+ <!-- UNRELEASED:cloud
101
106
  For non-interactive auth, use `intutic login --api-key vk_...`.
102
107
 
103
108
  ### Development Mode
@@ -109,6 +114,7 @@ intutic login --dev
109
114
  intutic connect --dev
110
115
  intutic traces list --dev
111
116
  ```
117
+ -->
112
118
 
113
119
  ## Documentation
114
120
 
package/dist/cli.js CHANGED
@@ -9,8 +9,9 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- import { Command } from 'commander';
12
+ import { Command, Option } from 'commander';
13
13
  import { createRequire } from 'node:module';
14
+ import { CLOUD_RELEASED, CONTROL_PLANE_PRODUCT_RELEASED, HOSTED_CONTROL_PLANE_URL, SELF_HOST_RELEASED, } from '@intutic/shared-types';
14
15
  import { DEFAULT_SANDBOX_IMAGE } from './lib/sandbox/image.js';
15
16
  // Read the version from package.json rather than repeating it here. The literal
16
17
  // that used to live below said 1.6.0 for three releases running, so
@@ -20,13 +21,37 @@ import { DEFAULT_SANDBOX_IMAGE } from './lib/sandbox/image.js';
20
21
  // but JSON imports still need an assertion on older runtimes.
21
22
  const { version } = createRequire(import.meta.url)('../package.json');
22
23
  const program = new Command();
24
+ // Release flags (packages/shared-types/src/release.ts). A command that only
25
+ // works against a control plane, or only in the self-hosted distribution, is
26
+ // left out of help until that product is released. Hidden is all it is: the
27
+ // command still parses and runs, so staging and existing users are unaffected.
28
+ const needsControlPlane = { hidden: !CONTROL_PLANE_PRODUCT_RELEASED };
29
+ const needsSelfHost = { hidden: !SELF_HOST_RELEASED };
30
+ /** An option of a visible command that only does anything against a control plane. */
31
+ function controlPlaneOption(flags, description) {
32
+ const option = new Option(flags, description);
33
+ return CONTROL_PLANE_PRODUCT_RELEASED ? option : option.hideHelp();
34
+ }
35
+ // A command that needs a control plane, run with none configured, throws
36
+ // NoControlPlaneError (config/paths.ts) from an async action. Say that plainly
37
+ // rather than as an unhandled rejection with a stack; anything else still
38
+ // crashes as before.
39
+ process.on('unhandledRejection', (err) => {
40
+ if (err instanceof Error && err.name === 'NoControlPlaneError') {
41
+ console.error(err.message);
42
+ process.exit(1);
43
+ }
44
+ throw err;
45
+ });
23
46
  // Positional options: `--version` and `--help` belong to the program only when
24
47
  // they come before a subcommand. Without this, commander matches program
25
48
  // options anywhere on the line, so a subcommand option named `--version` would
26
49
  // print the CLI version and exit before its command ever saw it.
27
50
  program
28
51
  .name('intutic')
29
- .description('Intutic CLI — AI governance control plane for developer workspaces')
52
+ .description(CONTROL_PLANE_PRODUCT_RELEASED
53
+ ? 'Intutic CLI — AI governance control plane for developer workspaces'
54
+ : 'Intutic CLI — open-core AI governance for developer workspaces: a local proxy, gates and policy for coding agents')
30
55
  .version(version)
31
56
  .enablePositionalOptions();
32
57
  program
@@ -49,7 +74,7 @@ program
49
74
  await runSetup(opts);
50
75
  });
51
76
  const judgeCmd = program
52
- .command('judge')
77
+ .command('judge', needsSelfHost)
53
78
  .description('Configure the LLM-as-judge path');
54
79
  judgeCmd
55
80
  .command('configure')
@@ -60,7 +85,7 @@ judgeCmd
60
85
  await runJudgeConfigure(opts);
61
86
  });
62
87
  program
63
- .command('login')
88
+ .command('login', needsControlPlane)
64
89
  .description('Authenticate with the Intutic control plane')
65
90
  .option('--api-key <key>', 'Authenticate with an API key (vk_*)')
66
91
  .option('--control-plane-url <url>', 'Control plane to log in to (e.g. a self-hosted one); saved for every later command')
@@ -80,7 +105,7 @@ program
80
105
  await runDisconnect({ harness: opts.harness, dryRun: opts.dryRun, keepLogin: opts.keepLogin });
81
106
  });
82
107
  program
83
- .command('logout')
108
+ .command('logout', needsControlPlane)
84
109
  .description('Clear stored credentials')
85
110
  .action(async () => {
86
111
  const { runLogout } = await import('./commands/logout.js');
@@ -111,7 +136,9 @@ program
111
136
  });
112
137
  const budgetCmd = program
113
138
  .command('budget')
114
- .description('Check remaining daily/monthly budget and list active loops; set workspace, key and member budgets')
139
+ .description(CONTROL_PLANE_PRODUCT_RELEASED
140
+ ? 'Check remaining daily/monthly budget and list active loops; set workspace, key and member budgets'
141
+ : 'Check the local spending cap and what this machine has spent today')
115
142
  .option('--dev', 'Use local control plane (http://localhost:3001)')
116
143
  .option('--watch', 'Continuously print machine-local and workspace spend, one line per tick')
117
144
  .option('--interval <seconds>', 'Tick interval in seconds for --watch', String(5))
@@ -125,7 +152,7 @@ const budgetCmd = program
125
152
  await runBudget(opts);
126
153
  });
127
154
  budgetCmd
128
- .command('set')
155
+ .command('set', needsControlPlane)
129
156
  .description("Set the workspace's daily and monthly caps, alert threshold and whether each cap refuses requests (OWNER, ADMIN)")
130
157
  .option('--daily <usd>', 'Daily cap in USD (0: no daily cap; default: back to the $100 default)')
131
158
  .option('--monthly <usd>', 'Monthly cap in USD (0: no monthly cap; default: back to the $500 default)')
@@ -141,7 +168,7 @@ budgetCmd
141
168
  await runBudgetSet(cmd.optsWithGlobals());
142
169
  });
143
170
  budgetCmd
144
- .command('keys')
171
+ .command('keys', needsControlPlane)
145
172
  .description("Every virtual key's spend budgets, rate limit and spend today and this month")
146
173
  .option('--json', 'Output as JSON')
147
174
  .option('--dev', 'Use local control plane (http://localhost:3001)')
@@ -150,7 +177,7 @@ budgetCmd
150
177
  await runBudgetKeys(cmd.optsWithGlobals());
151
178
  });
152
179
  budgetCmd
153
- .command('key <keyId>')
180
+ .command('key <keyId>', needsControlPlane)
154
181
  .description("Set a virtual key's spend budgets and rate limit (OWNER, ADMIN)")
155
182
  .option('--daily <usd>', 'Day budget in USD, or none to remove it')
156
183
  .option('--monthly <usd>', 'Month budget in USD, or none to remove it')
@@ -165,7 +192,7 @@ budgetCmd
165
192
  await runBudgetKey(keyId, cmd.optsWithGlobals());
166
193
  });
167
194
  budgetCmd
168
- .command('members')
195
+ .command('members', needsControlPlane)
169
196
  .description("The default member budget, and each member's budgets and spend (Biz Org+)")
170
197
  .option('--json', 'Output as JSON')
171
198
  .option('--dev', 'Use local control plane (http://localhost:3001)')
@@ -174,7 +201,7 @@ budgetCmd
174
201
  await runBudgetMembers(cmd.optsWithGlobals());
175
202
  });
176
203
  budgetCmd
177
- .command('member <memberId>')
204
+ .command('member <memberId>', needsControlPlane)
178
205
  .description("Set a member's spend budgets, or the default member budget with `default` (OWNER, ADMIN; Biz Org+)")
179
206
  .option('--daily <usd>', 'Day budget in USD, or none to remove it')
180
207
  .option('--monthly <usd>', 'Month budget in USD, or none to remove it')
@@ -187,7 +214,7 @@ budgetCmd
187
214
  await runBudgetMember(memberId, cmd.optsWithGlobals());
188
215
  });
189
216
  program
190
- .command('predict-cost')
217
+ .command('predict-cost', needsControlPlane)
191
218
  .description('Pre-flight cost estimate for a prompt/task before it runs (requires `intutic login`). ' +
192
219
  "Estimates output tokens from the workspace's recorded usage and prices them with the same " +
193
220
  'rate table as every other cost figure the control plane reports.')
@@ -201,7 +228,7 @@ program
201
228
  await runPredictCost(opts);
202
229
  });
203
230
  const sopsCmd = program
204
- .command('sops')
231
+ .command('sops', needsControlPlane)
205
232
  .description('Manage local and global SOP rules');
206
233
  sopsCmd
207
234
  .command('push <name>')
@@ -249,7 +276,7 @@ sopsCmd
249
276
  });
250
277
  // ── SOP Optimizer approval mode ──────────────────────────────────────────
251
278
  const optimizerCmd = program
252
- .command('optimizer')
279
+ .command('optimizer', needsControlPlane)
253
280
  .description("Read and set the SOP Optimizer's approval mode (whether it may auto-apply strengthening proposals)");
254
281
  optimizerCmd
255
282
  .command('settings')
@@ -273,9 +300,9 @@ optimizerCmd
273
300
  });
274
301
  const policyCmd = program
275
302
  .command('policy')
276
- .description('WASM rules and the policy snapshot');
303
+ .description(CONTROL_PLANE_PRODUCT_RELEASED ? 'WASM rules and the policy snapshot' : 'WASM rules: compile, test and install them locally');
277
304
  policyCmd
278
- .command('snapshot')
305
+ .command('snapshot', needsControlPlane)
279
306
  .description('Compile workspace policy to ~/.intutic/hooks/policy-snapshot.rules — the file every gate reads.\n' +
280
307
  '\n' +
281
308
  ' Does only that. \'intutic connect\' writes it too, but as one step of\n' +
@@ -298,10 +325,10 @@ policyCmd
298
325
  .command('compile')
299
326
  .description('Compile an AssemblyScript rule to WASM (wraps asc)')
300
327
  .option('--src <path>', 'Rule source entry file (default: assembly/index.ts)')
301
- .option('--out <path>', 'Output .wasm path (default: build/rule.wasm, or build/<candidate>.wasm with --candidate)')
328
+ .option('--out <path>', `Output .wasm path (default: build/rule.wasm${CONTROL_PLANE_PRODUCT_RELEASED ? ', or build/<candidate>.wasm with --candidate' : ''})`)
302
329
  .option('--debug', 'Include debug info and source maps')
303
- .option('--candidate <id>', 'Compile a rule candidate from its source of record on the control plane (fetched and hash-verified; not with --src)')
304
- .option('--upload', 'With --candidate: upload the bundle to the candidate with its source hash and print the gate results')
330
+ .addOption(controlPlaneOption('--candidate <id>', 'Compile a rule candidate from its source of record on the control plane (fetched and hash-verified; not with --src)'))
331
+ .addOption(controlPlaneOption('--upload', 'With --candidate: upload the bundle to the candidate with its source hash and print the gate results'))
305
332
  .option('--dev', 'Use local control plane (http://localhost:3001)')
306
333
  .action(async (opts) => {
307
334
  const { runPolicyCompile } = await import('./commands/policy.js');
@@ -325,7 +352,7 @@ policyCmd
325
352
  await runPolicyListLocal();
326
353
  });
327
354
  policyCmd
328
- .command('replay <ruleId>')
355
+ .command('replay <ruleId>', needsControlPlane)
329
356
  .description("Replay a rule (installed or not) against this workspace's own recent traffic and report " +
330
357
  'what it would have done, without touching enforcement.')
331
358
  .option('--limit <n>', 'Max sampled contexts to replay against (default 500, capped at 5000)')
@@ -371,7 +398,7 @@ rulesCmd
371
398
  // `intutic sops` is files on disk; this namespace is the cited guardrails
372
399
  // extracted from policy documents and their shadow -> promote lifecycle.
373
400
  const guardrailsCmd = program
374
- .command('guardrails')
401
+ .command('guardrails', needsControlPlane)
375
402
  .description('Policy documents, cited clauses and the guardrails compiled from them');
376
403
  const guardrailsSourcesCmd = guardrailsCmd
377
404
  .command('sources')
@@ -614,7 +641,7 @@ guardrailsCmd
614
641
  await runGuardrailsPull(opts);
615
642
  });
616
643
  program
617
- .command('whoami')
644
+ .command('whoami', needsControlPlane)
618
645
  .description('Show current authenticated identity')
619
646
  .option('--dev', 'Use local control plane (http://localhost:3001)')
620
647
  .action(async (opts) => {
@@ -657,7 +684,7 @@ for (const [action, desc] of [
657
684
  });
658
685
  }
659
686
  enforceCmd
660
- .command('report')
687
+ .command('report', needsControlPlane)
661
688
  .description('Report locally recorded enforcement state (firewall/CA-trust/system-hooks) to the control plane. ' +
662
689
  'No privilege needed — for when apply/remove ran elevated and could not reach stored credentials.')
663
690
  .action(async () => {
@@ -670,7 +697,7 @@ enforceCmd
670
697
  // tools/scripts/intutic-enterprise-install.sh, an unrelated air-gapped
671
698
  // docker-compose installer. Does not touch the host firewall; see `enforce`.
672
699
  const enterpriseCmd = program
673
- .command('enterprise')
700
+ .command('enterprise', needsControlPlane)
674
701
  .description('Enterprise fleet rollout: CA trust, Cursor system hooks, MDM manifests');
675
702
  enterpriseCmd
676
703
  .command('install')
@@ -709,7 +736,7 @@ envCmd
709
736
  await runEnvClear();
710
737
  });
711
738
  program
712
- .command('connect')
739
+ .command('connect', needsControlPlane)
713
740
  .description('Start sync daemon — bidirectional config sync with control plane (requires an account)')
714
741
  .option('--dev', 'Use local control plane (http://localhost:3001)')
715
742
  .option('--interval <ms>', 'Poll interval in milliseconds', '30000')
@@ -721,7 +748,7 @@ program
721
748
  await runConnect(opts);
722
749
  });
723
750
  program
724
- .command('sync-context')
751
+ .command('sync-context', needsControlPlane)
725
752
  .description('Record the Git branch and commit in .intutic/git-context.json, which a running `intutic connect` reports')
726
753
  .option('--git', 'Read the branch and commit from the repository (for any not given below)')
727
754
  .option('--branch <name>', 'Current Git branch name')
@@ -731,7 +758,7 @@ program
731
758
  await runSyncContext(opts);
732
759
  });
733
760
  program
734
- .command('decisions-log-refresh')
761
+ .command('decisions-log-refresh', needsControlPlane)
735
762
  .description('One-shot refresh of the governed decisions log (.intutic/DECISIONS.md, and the recent ' +
736
763
  'entries in each governed harness\'s instructions file) — no-ops if decisionsLogEnabled is off. Invoked by the optional ' +
737
764
  'post-merge Git hook `intutic init` installs; safe to run manually.')
@@ -833,7 +860,7 @@ traces
833
860
  });
834
861
  // ── Findings (detector_findings adjudication) ───────────────────────────────
835
862
  const findings = program
836
- .command('findings')
863
+ .command('findings', needsControlPlane)
837
864
  .description('Query and adjudicate detector findings — the TP/FP labeling surface');
838
865
  findings
839
866
  .command('list')
@@ -880,7 +907,7 @@ findings
880
907
  });
881
908
  // ── Governance incidents ───────────────────────────────────────────────────
882
909
  const incidentsCmd = program
883
- .command('incidents')
910
+ .command('incidents', needsControlPlane)
884
911
  .description('Governance incidents, as on Findings › Incidents (OWNER, ADMIN, EM)');
885
912
  incidentsCmd
886
913
  .command('list')
@@ -907,7 +934,7 @@ incidentsCmd
907
934
  });
908
935
  // ── Trace integrity ────────────────────────────────────────────────────────
909
936
  const integrity = program
910
- .command('integrity')
937
+ .command('integrity', needsControlPlane)
911
938
  .description('Verify sealed trace roots — list, re-derive, walk the root chain, and walk the config snapshot chain');
912
939
  integrity
913
940
  .command('roots')
@@ -950,7 +977,7 @@ integrity
950
977
  });
951
978
  // ── Routing reports ─────────────────────────────────────────────────────────
952
979
  const routing = program
953
- .command('routing')
980
+ .command('routing', needsControlPlane)
954
981
  .description('Read-only reports for the routing subsystem');
955
982
  routing
956
983
  .command('adoption-report')
@@ -989,7 +1016,7 @@ function defineDaemonInstall(cmd) {
989
1016
  ' Use \'intutic daemon stop\' or \'intutic daemon uninstall\'.')
990
1017
  .option('--workspace-id <id>', 'Workspace ID (e.g. wk_xxxx) — required unless --proxy')
991
1018
  .option('--api-key <key>', 'Workspace API key (e.g. vk_xxxx) — required unless --proxy')
992
- .option('--control-plane-url <url>', 'Control plane the daemon connects to (default: $INTUTIC_CONTROL_PLANE_URL, then a workspace set up with `intutic init --dev`, then the URL saved by `intutic login`, then https://api.intutic.ai)')
1019
+ .option('--control-plane-url <url>', `Control plane the daemon connects to (default: $INTUTIC_CONTROL_PLANE_URL, then a workspace set up with \`intutic init --dev\`, then the URL saved by \`intutic login\`${CLOUD_RELEASED ? `, then ${HOSTED_CONTROL_PLANE_URL}` : ''})`)
993
1020
  .option('--binary-path <path>', 'Path to intutic CLI binary (defaults to current process); with --proxy, absolute path to intutic-proxy')
994
1021
  .option('--dry-run', 'Print what would be done without writing files')
995
1022
  .option('--system', 'Install as a system-level service (LaunchDaemon on macOS, systemd system unit on Linux)')
@@ -1122,7 +1149,7 @@ skillCmd
1122
1149
  });
1123
1150
  // ── Loop commands ──────────────────────────────────────────────────────────
1124
1151
  const loopCmd = program
1125
- .command('loop')
1152
+ .command('loop', needsControlPlane)
1126
1153
  .description('Manage and execute recursive agent loops with budget limits');
1127
1154
  loopCmd
1128
1155
  .command('start')
@@ -1190,7 +1217,7 @@ loopCmd
1190
1217
  // module doc for why `intutic loop review` was never the right command for
1191
1218
  // this — it addresses a different id space (Loop Runs, not decisions).
1192
1219
  const decisionCmd = program
1193
- .command('decision')
1220
+ .command('decision', needsControlPlane)
1194
1221
  .description('Approve or reject a decision (e.g. a review_before hold) held for human review');
1195
1222
  decisionCmd
1196
1223
  .command('approve <holdId>')
@@ -1216,7 +1243,7 @@ decisionCmd
1216
1243
  // hand-written curl against services/control-plane/src/routes/gateways.ts —
1217
1244
  // see commands/gateway.ts's module doc.
1218
1245
  const gatewayCmd = program
1219
- .command('gateway')
1246
+ .command('gateway', needsControlPlane)
1220
1247
  .description('Manage self-hosted gateway registrations (Docker / Kubernetes / bare metal)');
1221
1248
  gatewayCmd
1222
1249
  .command('register')
@@ -1314,7 +1341,7 @@ gatewayCmd
1314
1341
  // Provision a workspace's own upstream provider keys — the BYO-key wizard's
1315
1342
  // API, previously dashboard-only. See commands/credentials.ts's module doc.
1316
1343
  const credentialsCmd = program
1317
- .command('credentials')
1344
+ .command('credentials', needsControlPlane)
1318
1345
  .description('Manage this workspace\'s own upstream provider API keys (BYO-key)');
1319
1346
  credentialsCmd
1320
1347
  .command('list')
@@ -1358,7 +1385,7 @@ credentialsCmd
1358
1385
  // "CLI mints, dashboard audits": this is the mint/inspect half. See
1359
1386
  // commands/attenuate.ts's module doc.
1360
1387
  const attenuateCmd = program
1361
- .command('attenuate')
1388
+ .command('attenuate', needsControlPlane)
1362
1389
  .description('Attenuate one of your API keys to a child key with narrower scopes (and an optional TTL)')
1363
1390
  .option('--parent-key <keyId>', 'Parent API key ID to attenuate')
1364
1391
  .option('--caps <scopes>', 'Comma-separated API key scopes for the child key (e.g. gateway:use,traces:read); each must be granted by the parent key')
@@ -1378,7 +1405,7 @@ attenuateCmd
1378
1405
  });
1379
1406
  // ── API keys and their scopes ────────────────────────────────────────────
1380
1407
  const keysCmd = program
1381
- .command('keys')
1408
+ .command('keys', needsControlPlane)
1382
1409
  .description('List, create, re-scope and revoke your API keys');
1383
1410
  keysCmd
1384
1411
  .command('list')
@@ -1423,7 +1450,7 @@ keysCmd
1423
1450
  });
1424
1451
  // ── Org signup + team management ─────────────────────────────────────────
1425
1452
  const orgCmd = program
1426
- .command('org')
1453
+ .command('org', needsControlPlane)
1427
1454
  .description('Create and manage orgs');
1428
1455
  orgCmd
1429
1456
  .command('create')
@@ -1438,7 +1465,7 @@ orgCmd
1438
1465
  await runOrgCreate(opts);
1439
1466
  });
1440
1467
  const teamCmd = program
1441
- .command('team')
1468
+ .command('team', needsControlPlane)
1442
1469
  .description('Manage teams and workspaces under an org');
1443
1470
  teamCmd
1444
1471
  .command('list')
@@ -1487,7 +1514,7 @@ teamCmd
1487
1514
  // route. Previously dashboard-only. See commands/apiCommand.ts for the shape
1488
1515
  // they share.
1489
1516
  const settingsCmd = program
1490
- .command('settings')
1517
+ .command('settings', needsControlPlane)
1491
1518
  .description('Read and change workspace settings (MCP policy, budgets, group policy, config content upload, ...)');
1492
1519
  settingsCmd
1493
1520
  .command('get [key]')
@@ -1509,7 +1536,7 @@ settingsCmd
1509
1536
  await runSettingsSet(key, value, opts);
1510
1537
  });
1511
1538
  const mcpCmd = program
1512
- .command('mcp')
1539
+ .command('mcp', needsControlPlane)
1513
1540
  .description('Review the MCP servers the workspace\'s MCP proxies have seen, and decide on them');
1514
1541
  mcpCmd
1515
1542
  .command('list')
@@ -1550,7 +1577,7 @@ for (const [action, enabled, desc] of [
1550
1577
  });
1551
1578
  }
1552
1579
  const notificationsCmd = program
1553
- .command('notifications')
1580
+ .command('notifications', needsControlPlane)
1554
1581
  .description('Manage notification rules: which events go to Slack, email, a webhook or PagerDuty');
1555
1582
  function ruleOptions(cmd) {
1556
1583
  return cmd
@@ -1612,7 +1639,7 @@ notificationsCmd
1612
1639
  await runNotificationsRotateSecret(ruleId, opts);
1613
1640
  });
1614
1641
  const siemCmd = program
1615
- .command('siem')
1642
+ .command('siem', needsControlPlane)
1616
1643
  .description('Manage SIEM export destinations and the event sources each receives');
1617
1644
  function destinationOptions(cmd) {
1618
1645
  return cmd
@@ -1687,7 +1714,7 @@ siemCmd
1687
1714
  await runSiemRotateSecret(destinationId, opts);
1688
1715
  });
1689
1716
  const tasksCmd = program
1690
- .command('tasks')
1717
+ .command('tasks', needsControlPlane)
1691
1718
  .description('Manage task-management connections (Jira Cloud, Linear, PagerDuty) that attach ticket context to sessions');
1692
1719
  tasksCmd
1693
1720
  .command('list')
@@ -1732,7 +1759,7 @@ tasksCmd
1732
1759
  await runTasksRemove(connectionId, opts);
1733
1760
  });
1734
1761
  const complianceCmd = program
1735
- .command('compliance')
1762
+ .command('compliance', needsControlPlane)
1736
1763
  .description('Compliance framework reports and the evidence archive');
1737
1764
  complianceCmd
1738
1765
  .command('coverage <framework_id>')
@@ -1777,7 +1804,7 @@ complianceCmd
1777
1804
  await runComplianceVerify(file, opts);
1778
1805
  });
1779
1806
  const usageCmd = program
1780
- .command('usage')
1807
+ .command('usage', needsControlPlane)
1781
1808
  .description('LLM usage across the fleet, by member, team, branch, commit or pull request');
1782
1809
  for (const [view, handler, desc] of [
1783
1810
  ['members', 'runUsageMembers', 'Usage per member'],
@@ -1808,7 +1835,7 @@ usageCmd
1808
1835
  await runUsagePullRequests(opts);
1809
1836
  });
1810
1837
  const githubWebhookCmd = program
1811
- .command('github')
1838
+ .command('github', needsControlPlane)
1812
1839
  .description('GitHub integration for cost per pull request')
1813
1840
  .command('webhook')
1814
1841
  .description('The pull-request webhook: payload URL and signing secret');
@@ -1831,7 +1858,7 @@ githubWebhookCmd
1831
1858
  await runGithubWebhookRotateSecret(opts);
1832
1859
  });
1833
1860
  const inventoryCmd = program
1834
- .command('inventory')
1861
+ .command('inventory', needsControlPlane)
1835
1862
  .description('AI harnesses, MCP servers and skills on connected developer machines, governed or not');
1836
1863
  inventoryCmd
1837
1864
  .command('summary')
@@ -1893,7 +1920,7 @@ inventoryCmd
1893
1920
  await runInventoryDisconnects(opts);
1894
1921
  });
1895
1922
  program
1896
- .command('gate-liveness')
1923
+ .command('gate-liveness', needsControlPlane)
1897
1924
  .description('Whether each installed harness\'s gate is reporting, and any open silent-gate alert')
1898
1925
  .option('--json', 'Output as JSON')
1899
1926
  .option('--dev', 'Use local control plane (http://localhost:3001)')