@intutic/cli 1.10.1 → 2.1.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.
Files changed (177) hide show
  1. package/dist/cli.js +120 -128
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/attenuate.d.ts.map +1 -1
  4. package/dist/commands/attenuate.js +4 -2
  5. package/dist/commands/attenuate.js.map +1 -1
  6. package/dist/commands/budget.d.ts.map +1 -1
  7. package/dist/commands/budget.js +2 -2
  8. package/dist/commands/budget.js.map +1 -1
  9. package/dist/commands/connect.d.ts.map +1 -1
  10. package/dist/commands/connect.js +20 -9
  11. package/dist/commands/connect.js.map +1 -1
  12. package/dist/commands/doctor.d.ts +19 -1
  13. package/dist/commands/doctor.d.ts.map +1 -1
  14. package/dist/commands/doctor.js +33 -41
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/enforce.d.ts +10 -0
  17. package/dist/commands/enforce.d.ts.map +1 -1
  18. package/dist/commands/enforce.js +36 -0
  19. package/dist/commands/enforce.js.map +1 -1
  20. package/dist/commands/exec.d.ts +7 -8
  21. package/dist/commands/exec.d.ts.map +1 -1
  22. package/dist/commands/exec.js +59 -60
  23. package/dist/commands/exec.js.map +1 -1
  24. package/dist/commands/gateway.d.ts.map +1 -1
  25. package/dist/commands/gateway.js +3 -1
  26. package/dist/commands/gateway.js.map +1 -1
  27. package/dist/commands/guardrails.d.ts.map +1 -1
  28. package/dist/commands/guardrails.js +18 -1
  29. package/dist/commands/guardrails.js.map +1 -1
  30. package/dist/commands/init.d.ts +5 -2
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +36 -22
  33. package/dist/commands/init.js.map +1 -1
  34. package/dist/commands/install-daemon.d.ts +1 -1
  35. package/dist/commands/install-daemon.d.ts.map +1 -1
  36. package/dist/commands/install-daemon.js +5 -3
  37. package/dist/commands/install-daemon.js.map +1 -1
  38. package/dist/commands/integrity.d.ts +31 -2
  39. package/dist/commands/integrity.d.ts.map +1 -1
  40. package/dist/commands/integrity.js +109 -3
  41. package/dist/commands/integrity.js.map +1 -1
  42. package/dist/commands/judge.d.ts +8 -4
  43. package/dist/commands/judge.d.ts.map +1 -1
  44. package/dist/commands/judge.js +41 -21
  45. package/dist/commands/judge.js.map +1 -1
  46. package/dist/commands/login.d.ts +1 -0
  47. package/dist/commands/login.d.ts.map +1 -1
  48. package/dist/commands/login.js +16 -1
  49. package/dist/commands/login.js.map +1 -1
  50. package/dist/commands/setup.d.ts +1 -2
  51. package/dist/commands/setup.d.ts.map +1 -1
  52. package/dist/commands/setup.js +11 -68
  53. package/dist/commands/setup.js.map +1 -1
  54. package/dist/commands/skill.d.ts +1 -0
  55. package/dist/commands/skill.d.ts.map +1 -1
  56. package/dist/commands/skill.js +17 -3
  57. package/dist/commands/skill.js.map +1 -1
  58. package/dist/commands/start.d.ts.map +1 -1
  59. package/dist/commands/start.js +5 -1
  60. package/dist/commands/start.js.map +1 -1
  61. package/dist/commands/syncContext.d.ts.map +1 -1
  62. package/dist/commands/syncContext.js +15 -5
  63. package/dist/commands/syncContext.js.map +1 -1
  64. package/dist/config/paths.d.ts +25 -3
  65. package/dist/config/paths.d.ts.map +1 -1
  66. package/dist/config/paths.js +47 -7
  67. package/dist/config/paths.js.map +1 -1
  68. package/dist/harness/agentFramework.d.ts +25 -0
  69. package/dist/harness/agentFramework.d.ts.map +1 -0
  70. package/dist/harness/agentFramework.js +37 -0
  71. package/dist/harness/agentFramework.js.map +1 -0
  72. package/dist/harness/agentcore.d.ts +6 -3
  73. package/dist/harness/agentcore.d.ts.map +1 -1
  74. package/dist/harness/agentcore.js +7 -4
  75. package/dist/harness/agentcore.js.map +1 -1
  76. package/dist/harness/aiSdkHarness.d.ts +1 -1
  77. package/dist/harness/aiSdkHarness.js +1 -1
  78. package/dist/harness/aiSdkWorkflow.d.ts +1 -1
  79. package/dist/harness/aiSdkWorkflow.d.ts.map +1 -1
  80. package/dist/harness/aiSdkWorkflow.js +5 -3
  81. package/dist/harness/aiSdkWorkflow.js.map +1 -1
  82. package/dist/harness/aider.d.ts +4 -3
  83. package/dist/harness/aider.d.ts.map +1 -1
  84. package/dist/harness/aider.js +7 -6
  85. package/dist/harness/aider.js.map +1 -1
  86. package/dist/harness/antigravity.d.ts +4 -2
  87. package/dist/harness/antigravity.d.ts.map +1 -1
  88. package/dist/harness/antigravity.js +14 -12
  89. package/dist/harness/antigravity.js.map +1 -1
  90. package/dist/harness/cline.d.ts +10 -4
  91. package/dist/harness/cline.d.ts.map +1 -1
  92. package/dist/harness/cline.js +39 -32
  93. package/dist/harness/cline.js.map +1 -1
  94. package/dist/harness/codex.d.ts +10 -4
  95. package/dist/harness/codex.d.ts.map +1 -1
  96. package/dist/harness/codex.js +28 -26
  97. package/dist/harness/codex.js.map +1 -1
  98. package/dist/harness/continue.d.ts +8 -5
  99. package/dist/harness/continue.d.ts.map +1 -1
  100. package/dist/harness/continue.js +27 -63
  101. package/dist/harness/continue.js.map +1 -1
  102. package/dist/harness/detector.d.ts +1 -1
  103. package/dist/harness/detector.d.ts.map +1 -1
  104. package/dist/harness/detector.js +3 -1
  105. package/dist/harness/detector.js.map +1 -1
  106. package/dist/harness/dsh.d.ts +2 -2
  107. package/dist/harness/dsh.js +3 -3
  108. package/dist/harness/dsh.js.map +1 -1
  109. package/dist/harness/eve.d.ts +1 -1
  110. package/dist/harness/eve.js +1 -1
  111. package/dist/harness/githubCopilot.d.ts +4 -2
  112. package/dist/harness/githubCopilot.d.ts.map +1 -1
  113. package/dist/harness/githubCopilot.js +10 -2
  114. package/dist/harness/githubCopilot.js.map +1 -1
  115. package/dist/harness/jsSdkGatedAdapter.d.ts.map +1 -1
  116. package/dist/harness/jsSdkGatedAdapter.js +4 -3
  117. package/dist/harness/jsSdkGatedAdapter.js.map +1 -1
  118. package/dist/harness/langgraph.js +4 -4
  119. package/dist/harness/langgraph.js.map +1 -1
  120. package/dist/harness/n8n.d.ts +5 -2
  121. package/dist/harness/n8n.d.ts.map +1 -1
  122. package/dist/harness/n8n.js +12 -3
  123. package/dist/harness/n8n.js.map +1 -1
  124. package/dist/harness/openWebUI.d.ts +7 -2
  125. package/dist/harness/openWebUI.d.ts.map +1 -1
  126. package/dist/harness/openWebUI.js +15 -4
  127. package/dist/harness/openWebUI.js.map +1 -1
  128. package/dist/harness/opencode.d.ts +3 -2
  129. package/dist/harness/opencode.d.ts.map +1 -1
  130. package/dist/harness/opencode.js +3 -2
  131. package/dist/harness/opencode.js.map +1 -1
  132. package/dist/harness/openhands.d.ts +3 -2
  133. package/dist/harness/openhands.d.ts.map +1 -1
  134. package/dist/harness/openhands.js +27 -26
  135. package/dist/harness/openhands.js.map +1 -1
  136. package/dist/harness/rooCode.d.ts +6 -5
  137. package/dist/harness/rooCode.d.ts.map +1 -1
  138. package/dist/harness/rooCode.js +9 -12
  139. package/dist/harness/rooCode.js.map +1 -1
  140. package/dist/harness/sdkGatedAdapter.d.ts.map +1 -1
  141. package/dist/harness/sdkGatedAdapter.js +4 -3
  142. package/dist/harness/sdkGatedAdapter.js.map +1 -1
  143. package/dist/harness/trueforge.js +2 -2
  144. package/dist/harness/trueforge.js.map +1 -1
  145. package/dist/harness/types.d.ts.map +1 -1
  146. package/dist/harness/types.js +3 -0
  147. package/dist/harness/types.js.map +1 -1
  148. package/dist/harness/windsurf.d.ts +4 -2
  149. package/dist/harness/windsurf.d.ts.map +1 -1
  150. package/dist/harness/windsurf.js +12 -5
  151. package/dist/harness/windsurf.js.map +1 -1
  152. package/dist/lib/localProxy.d.ts +19 -0
  153. package/dist/lib/localProxy.d.ts.map +1 -0
  154. package/dist/lib/localProxy.js +33 -0
  155. package/dist/lib/localProxy.js.map +1 -0
  156. package/dist/lib/onboarding.d.ts +1 -1
  157. package/dist/lib/onboarding.d.ts.map +1 -1
  158. package/dist/lib/onboarding.js +21 -27
  159. package/dist/lib/onboarding.js.map +1 -1
  160. package/dist/lib/sandbox/image.d.ts +27 -0
  161. package/dist/lib/sandbox/image.d.ts.map +1 -0
  162. package/dist/lib/sandbox/image.js +54 -0
  163. package/dist/lib/sandbox/image.js.map +1 -0
  164. package/dist/lib/sandbox/oci.d.ts +1 -0
  165. package/dist/lib/sandbox/oci.d.ts.map +1 -1
  166. package/dist/lib/sandbox/oci.js +4 -0
  167. package/dist/lib/sandbox/oci.js.map +1 -1
  168. package/dist/lib/sandbox/types.d.ts +5 -0
  169. package/dist/lib/sandbox/types.d.ts.map +1 -1
  170. package/package.json +6 -5
  171. package/resources/sandbox/Dockerfile +30 -0
  172. package/resources/sandbox/entrypoint.sh +108 -0
  173. package/resources/sandbox/firecracker-launch.sh +120 -0
  174. package/dist/harness/vscodeSettingsWriter.d.ts +0 -40
  175. package/dist/harness/vscodeSettingsWriter.d.ts.map +0 -1
  176. package/dist/harness/vscodeSettingsWriter.js +0 -116
  177. package/dist/harness/vscodeSettingsWriter.js.map +0 -1
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { Command } from 'commander';
14
14
  import { createRequire } from 'node:module';
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
17
18
  // `intutic --version` reported a version the user did not have — and any check
@@ -20,14 +21,22 @@ import { createRequire } from 'node:module';
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
+ // Positional options: `--version` and `--help` belong to the program only when
25
+ // they come before a subcommand. Without this, commander matches program
26
+ // options anywhere on the line, so `intutic policy rollback <id> --version 2`
27
+ // printed the CLI version and exited before `policy rollback` ever saw its own
28
+ // required `--version`.
23
29
  program
24
30
  .name('intutic')
25
31
  .description('Intutic CLI — AI governance control plane for developer workspaces')
26
- .version(version);
32
+ .version(version)
33
+ .enablePositionalOptions();
27
34
  program
28
35
  .command('init')
29
- .description('Initialize workspace — detect harnesses, configure sync')
36
+ .description('Initialize workspace — detect harnesses and record them in ~/.intutic/config.json')
30
37
  .option('--dev', 'Use local control plane (http://localhost:3001)')
38
+ .option('--git-hooks', 'Install the Intutic Git hooks without asking')
39
+ .option('--no-git-hooks', 'Skip the Git hooks without asking (the default when stdin is not a terminal)')
31
40
  .action(async (opts) => {
32
41
  const { runInit } = await import('./commands/init.js');
33
42
  await runInit(opts);
@@ -35,7 +44,7 @@ program
35
44
  // ── Cohort setup wizard (LLD #70) ────────────────────────────────────────
36
45
  program
37
46
  .command('setup')
38
- .description('Guided setup: detect harnesses, configure a provider credential, verify it, and (optionally) a judge model')
47
+ .description('Guided setup: detect harnesses, configure a provider credential, and verify it')
39
48
  .option('--dev', 'Use local control plane (http://localhost:3001)')
40
49
  .action(async (opts) => {
41
50
  const { runSetup } = await import('./commands/setup.js');
@@ -46,7 +55,7 @@ const judgeCmd = program
46
55
  .description('Configure the LLM-as-judge path');
47
56
  judgeCmd
48
57
  .command('configure')
49
- .description('Generate local artifacts (litellm_config.yaml, env, Helm values) for an on-prem judge — never a remote API call')
58
+ .description('Generate local artifacts (litellm_config.yaml, env, Helm values) for an on-prem judge on a self-hosted model — never a remote API call')
50
59
  .option('--out <path>', 'Where to write litellm_config.yaml', './litellm_config.yaml')
51
60
  .action(async (opts) => {
52
61
  const { runJudgeConfigure } = await import('./commands/judge.js');
@@ -56,6 +65,7 @@ program
56
65
  .command('login')
57
66
  .description('Authenticate with the Intutic control plane')
58
67
  .option('--api-key <key>', 'Authenticate with an API key (vk_*)')
68
+ .option('--control-plane-url <url>', 'Control plane to log in to (e.g. a self-hosted one); saved for every later command')
59
69
  .option('--dev', 'Use local control plane (http://localhost:3001)')
60
70
  .action(async (opts) => {
61
71
  const { runLogin } = await import('./commands/login.js');
@@ -108,9 +118,9 @@ program
108
118
  });
109
119
  program
110
120
  .command('predict-cost')
111
- .description('Pre-flight cost estimate for a prompt/task before it runs (POST /api/v1/predict-cost). ' +
112
- 'Estimates output tokens from the workspace baseline and prices with the same rate table ' +
113
- 'every other cost figure in the control plane uses.')
121
+ .description('Pre-flight cost estimate for a prompt/task before it runs (requires `intutic login`). ' +
122
+ "Estimates output tokens from the workspace's recorded usage and prices them with the same " +
123
+ 'rate table as every other cost figure the control plane reports.')
114
124
  .requiredOption('--model <model>', 'Model to estimate against (e.g. claude-sonnet-4-5)')
115
125
  .option('--task-type <type>', 'Task type used to pick the baseline bucket', 'coding')
116
126
  .option('--tokens <n>', 'Input token count (mutually exclusive with --file)')
@@ -128,7 +138,7 @@ sopsCmd
128
138
  .command('push <name>')
129
139
  .description('Push a local offline SOP folder to the central workspace')
130
140
  .option('--dev', 'Use local control plane (http://localhost:3001)')
131
- .option('--org', "Push as an org-wide floor (LLD #65 follow-up) instead of a workspace SOP -- applies to every workspace under the caller's org, in addition to that workspace's own SOPs")
141
+ .option('--org', "Push as an org-wide floor instead of a workspace SOP -- applies to every workspace under the caller's org, in addition to that workspace's own SOPs")
132
142
  .action(async (name, opts) => {
133
143
  const { runSopsPush } = await import('./commands/sops.js');
134
144
  await runSopsPush(name, opts);
@@ -152,7 +162,7 @@ sopsCmd
152
162
  });
153
163
  sopsCmd
154
164
  .command('org-list')
155
- .description("List org-wide SOP floors for the caller's own org (LLD #65 follow-up)")
165
+ .description("List org-wide SOP floors for the caller's own org")
156
166
  .option('--dev', 'Use local control plane (http://localhost:3001)')
157
167
  .action(async (opts) => {
158
168
  const { runSopsOrgList } = await import('./commands/sops.js');
@@ -349,7 +359,7 @@ guardrailsCmd
349
359
  .command('list')
350
360
  .description('List guardrails with their status and shadow evidence')
351
361
  .option('--status <status>', 'PROPOSED | SHADOW | ENFORCING | REJECTED | RETIRED')
352
- .option('--target <target>', 'hook_rule | sop_front_matter | wasm_rule')
362
+ .option('--target <target>', 'hook_rule | sop_front_matter | wasm_rule | workspace_setting')
353
363
  .option('--doc <docId>', 'Only guardrails cited from this document')
354
364
  .option('--limit <n>', 'Max rows (default 50, capped at 200)')
355
365
  .option('--json', 'Output as JSON')
@@ -378,8 +388,8 @@ guardrailsCmd
378
388
  });
379
389
  guardrailsCmd
380
390
  .command('promote <guardrailId>')
381
- .description('Promote a shadow guardrail to enforcing once the server says it is ready')
382
- .option('--acknowledge-no-traffic', 'Promote a rule that no observed traffic exercised')
391
+ .description('Promote a shadow guardrail to enforcing once the server says it is ready (an egress allow list is applied from proposed)')
392
+ .option('--acknowledge-no-traffic', 'Promote a rule that no observed traffic exercised, or apply an egress allow list, which has no shadow evidence')
383
393
  .option('--json', 'Output as JSON')
384
394
  .option('--dev', 'Use local control plane (http://localhost:3001)')
385
395
  .action(async (guardrailId, opts) => {
@@ -475,7 +485,7 @@ program
475
485
  program
476
486
  .command('start')
477
487
  .description('Start the proxy standalone — no account or control plane needed')
478
- .option('--port <port>', 'Proxy port', '4000')
488
+ .option('--port <port>', 'Proxy port (default: the port of $INTUTIC_PROXY_URL, else 4000)')
479
489
  .option('--valkey-port <port>', 'Valkey port', '6379')
480
490
  .option('--upstream-url <url>', 'Upstream LLM provider base URL')
481
491
  .action(async (opts) => {
@@ -531,7 +541,7 @@ enterpriseCmd
531
541
  .option('--mdm-output-dir <dir>', 'Directory to write generated MDM manifests to', './intutic-mdm')
532
542
  .option('--skip-ca', 'Skip system-wide CA trust installation')
533
543
  .option('--skip-hooks', 'Skip system-level Cursor hooks installation')
534
- .option('--dev', 'Use local control plane (http://localhost:3001) when resolving workspace context')
544
+ .option('--dev', 'Send the device enforcement report to the local control plane (http://localhost:3001)')
535
545
  .option('--cli-binary-path <path>', 'Absolute path to the intutic CLI binary on TARGET machines, embedded in the firewall-deployment manifests (default: /usr/local/bin/intutic — override if your fleet installs elsewhere)')
536
546
  .action(async (opts) => {
537
547
  const { runEnterpriseInstall } = await import('./commands/enterprise.js');
@@ -542,7 +552,7 @@ const envCmd = program
542
552
  .description('Persist or clear the proxy base-URL environment variables');
543
553
  envCmd
544
554
  .command('persist')
545
- .description('Write ANTHROPIC_BASE_URL / OPENAI_BASE_URL at the OS level so they survive a restart.\n' +
555
+ .description('Write ANTHROPIC_BASE_URL / OPENAI_BASE_URL at the OS level so new shells and apps pick them up.\n' +
546
556
  ' macOS: launchctl setenv Linux: ~/.bashrc Windows: setx\n' +
547
557
  '\n' +
548
558
  ' Opt-in on purpose: on macOS this reaches every GUI app you launch afterwards.\n' +
@@ -573,8 +583,8 @@ program
573
583
  });
574
584
  program
575
585
  .command('sync-context')
576
- .description('Sync Git context metadata to the local daemon')
577
- .option('--git', 'Sync Git branch and commit information')
586
+ .description('Record the Git branch and commit in .intutic/git-context.json, which a running `intutic connect` reports')
587
+ .option('--git', 'Read the branch and commit from the repository (for any not given below)')
578
588
  .option('--branch <name>', 'Current Git branch name')
579
589
  .option('--commit <hash>', 'Current Git commit SHA')
580
590
  .action(async (opts) => {
@@ -585,7 +595,7 @@ program
585
595
  .command('decisions-log-refresh')
586
596
  .description('One-shot refresh of the governed decisions log (.intutic/DECISIONS.md + the claude-code ' +
587
597
  'harness config section) — no-ops if decisionsLogEnabled is off. Invoked by the optional ' +
588
- 'post-merge hook (see gitHooks.ts); safe to run manually.')
598
+ 'post-merge Git hook `intutic init` installs; safe to run manually.')
589
599
  .action(async () => {
590
600
  const { refreshDecisionsLog } = await import('./lib/decisionsLogRefresh.js');
591
601
  const result = await refreshDecisionsLog(process.cwd());
@@ -602,7 +612,7 @@ program
602
612
  // (LLD #63 §6): cap-drop, no-new-privileges, read-only rootfs, resource caps,
603
613
  // and a default-deny egress firewall the agent cannot undo.
604
614
  .option('--sandbox [kind]', 'Run the agent in an isolated sandbox (kind: oci | firecracker; default oci)')
605
- .option('--sandbox-image <image>', 'Sandbox image (must contain the agent + nftables + capsh)', 'intutic/sandbox:latest')
615
+ .option('--sandbox-image <image>', 'Sandbox image (must contain the agent + nftables + capsh); the default is built locally on first use', DEFAULT_SANDBOX_IMAGE)
606
616
  .option('--sandbox-memory <size>', 'Sandbox memory cap (e.g. 2g)', '2g')
607
617
  .option('--sandbox-cpus <n>', 'Sandbox CPU cap', '2')
608
618
  .option('--sandbox-pids <n>', 'Sandbox max process count', '512')
@@ -658,7 +668,7 @@ const findings = program
658
668
  findings
659
669
  .command('list')
660
670
  .description('List detector findings for the workspace')
661
- .option('--unadjudicated', "Only show findings nobody has ruled on yet (the route's own default is to show all)")
671
+ .option('--unadjudicated', 'Only show findings nobody has ruled on yet (default: all findings)')
662
672
  .option('--detector <id>', 'Filter by detector_id (e.g. response_injection:override-instructions)')
663
673
  .option('--limit <n>', 'Max rows to return (default: 100, max: 500)')
664
674
  .option('--json', 'Output as JSON instead of table')
@@ -690,8 +700,8 @@ findings
690
700
  findings
691
701
  .command('echo-report')
692
702
  .description('Response-injection echo measurement report — per-pattern false-positive rate')
693
- .option('--since <date>', 'ISO date — window start (route default: trailing 7 days)')
694
- .option('--until <date>', 'ISO date — window end (route default: now)')
703
+ .option('--since <date>', 'ISO date — window start (default: 7 days before --until)')
704
+ .option('--until <date>', 'ISO date — window end (default: now)')
695
705
  .option('--json', 'Output as JSON instead of table')
696
706
  .option('--dev', 'Use local control plane (http://localhost:3001)')
697
707
  .action(async (opts) => {
@@ -715,6 +725,7 @@ integrity
715
725
  integrity
716
726
  .command('verify <root_id>')
717
727
  .description('Re-derive a root from the live traces and check its signature (exit 1 on mismatch)')
728
+ .option('--against <file>', 'Also compare with the copy of this root mirrored to your own bucket (BYOC)')
718
729
  .option('--json', 'Output as JSON instead of a report')
719
730
  .option('--dev', 'Use local control plane (http://localhost:3001)')
720
731
  .action(async (rootId, opts) => {
@@ -747,7 +758,7 @@ const routing = program
747
758
  routing
748
759
  .command('adoption-report')
749
760
  .description('Mirror-test adoption report for one candidate model — win/loss/tie, fault-rate delta, ' +
750
- 'cost delta, latency delta (Phase 7b: GET /api/v1/routing/mirror-adoption-report). ' +
761
+ 'cost delta, latency delta. ' +
751
762
  'A reported signal for human review, not an automatic gate.')
752
763
  .requiredOption('--candidate-model <model>', 'The mirror-tested candidate model to report on')
753
764
  .option('--json', 'Output as JSON instead of a report')
@@ -760,80 +771,93 @@ routing
760
771
  const daemon = program
761
772
  .command('daemon')
762
773
  .description('Manage the Intutic sync-daemon, MCP daemon and standalone proxy system services (LaunchAgent / systemd)');
763
- daemon
764
- .command('install')
765
- .alias('install-daemon') // top-level alias for discoverability
766
- .description('Install sync-daemon as a system service (auto-starts on login, restarts on any exit).\n' +
767
- ' macOS: ~/Library/LaunchAgents/ai.intutic.sync-daemon.plist (KeepAlive: true)\n' +
768
- ' Linux: ~/.config/systemd/user/intutic-sync-daemon.service (Restart=always)\n' +
769
- '\n' +
770
- ' --proxy installs the standalone intutic-proxy binary instead (no workspace or key needed):\n' +
771
- ' macOS: ~/Library/LaunchAgents/ai.intutic.proxy.plist\n' +
772
- ' Linux: ~/.config/systemd/user/intutic-proxy.service\n' +
773
- '\n' +
774
- ' NOTE: To stop the daemon you MUST use \'intutic daemon uninstall\' or \'launchctl unload\'.\n' +
775
- ' \'intutic disconnect\' alone will NOT stop a daemon-installed service. (TD-154)')
776
- .option('--workspace-id <id>', 'Workspace ID (e.g. wk_xxxx) — required unless --proxy')
777
- .option('--api-key <key>', 'Workspace API key (e.g. vk_xxxx) — required unless --proxy')
778
- .option('--control-plane-url <url>', 'Control plane URL', 'https://api.intutic.ai')
779
- .option('--binary-path <path>', 'Path to intutic CLI binary (defaults to current process); with --proxy, absolute path to intutic-proxy')
780
- .option('--dry-run', 'Print what would be done without writing files')
781
- .option('--system', 'Install as a system-level service (LaunchDaemon on macOS, systemd system unit on Linux)')
782
- .option('--mcp', 'Install the MCP proxy daemon instead of the sync-daemon')
783
- .option('--proxy', 'Install the standalone intutic-proxy binary as a service (TD-465)')
784
- .option('--port <port>', 'With --proxy: proxy listen port', '4000')
785
- .option('--valkey-url <url>', 'With --proxy: Valkey URL to attach to; omit to run standalone (INTUTIC_STANDALONE=1)')
786
- .option('--upstream-url <url>', 'With --proxy: upstream LLM provider base URL')
787
- .action(async (opts, cmd) => {
788
- if (opts.proxy) {
789
- // The standalone proxy has no control plane, so no workspace or key.
790
- // Its unit carries the same environment `intutic start` sets, fixed at
791
- // install time rather than probed at launch (TD-465).
792
- const { installProxyService } = await import('./commands/install-daemon.js');
793
- await installProxyService({
794
- port: opts.port,
795
- valkeyUrl: opts.valkeyUrl,
796
- upstreamUrl: opts.upstreamUrl,
774
+ // `install` and `uninstall` are also reachable as the top-level shortcuts
775
+ // `intutic install-daemon` / `intutic uninstall-daemon`. A commander alias only
776
+ // renames a command within its own parent, so each verb is defined once here
777
+ // and registered in both places. The shortcuts used to be separate copies, and
778
+ // they drifted: `install-daemon` had no --mcp, --proxy or proxy options and
779
+ // demanded a workspace and key even for the standalone proxy, while the docs
780
+ // called it a shortcut for `daemon install`.
781
+ function defineDaemonInstall(cmd) {
782
+ return cmd
783
+ .description('Install sync-daemon as a system service (auto-starts on login, restarts on any exit).\n' +
784
+ ' macOS: ~/Library/LaunchAgents/ai.intutic.sync-daemon.plist (KeepAlive: true)\n' +
785
+ ' Linux: ~/.config/systemd/user/intutic-sync-daemon.service (Restart=always)\n' +
786
+ '\n' +
787
+ ' --proxy installs the standalone intutic-proxy binary instead (no workspace or key needed):\n' +
788
+ ' macOS: ~/Library/LaunchAgents/ai.intutic.proxy.plist\n' +
789
+ ' Linux: ~/.config/systemd/user/intutic-proxy.service\n' +
790
+ '\n' +
791
+ ' The service restarts whenever its process exits, so killing the process does not stop it.\n' +
792
+ ' Use \'intutic daemon stop\' or \'intutic daemon uninstall\'.')
793
+ .option('--workspace-id <id>', 'Workspace ID (e.g. wk_xxxx) — required unless --proxy')
794
+ .option('--api-key <key>', 'Workspace API key (e.g. vk_xxxx) — required unless --proxy')
795
+ .option('--control-plane-url <url>', 'Control plane the daemon connects to (default: $INTUTIC_CONTROL_PLANE_URL, then the URL saved by `intutic login`, then https://api.intutic.ai)')
796
+ .option('--binary-path <path>', 'Path to intutic CLI binary (defaults to current process); with --proxy, absolute path to intutic-proxy')
797
+ .option('--dry-run', 'Print what would be done without writing files')
798
+ .option('--system', 'Install as a system-level service (LaunchDaemon on macOS, systemd system unit on Linux)')
799
+ .option('--mcp', 'Install the MCP proxy daemon instead of the sync-daemon')
800
+ .option('--proxy', 'Install the standalone intutic-proxy binary as a service')
801
+ .option('--port <port>', 'With --proxy: proxy listen port (default: the port of $INTUTIC_PROXY_URL, else 4000)')
802
+ .option('--valkey-url <url>', 'With --proxy: Valkey URL to attach to; omit to run standalone (INTUTIC_STANDALONE=1)')
803
+ .option('--upstream-url <url>', 'With --proxy: upstream LLM provider base URL')
804
+ .action(async (opts, cmd) => {
805
+ if (opts.proxy) {
806
+ // The standalone proxy has no control plane, so no workspace or key.
807
+ // Its unit carries the same environment `intutic start` sets, fixed at
808
+ // install time rather than probed at launch.
809
+ const { installProxyService } = await import('./commands/install-daemon.js');
810
+ await installProxyService({
811
+ port: opts.port,
812
+ valkeyUrl: opts.valkeyUrl,
813
+ upstreamUrl: opts.upstreamUrl,
814
+ binaryPath: opts.binaryPath,
815
+ dryRun: opts.dryRun,
816
+ system: opts.system,
817
+ });
818
+ return;
819
+ }
820
+ // Required for the two daemons; commander's `requiredOption` cannot express
821
+ // "unless --proxy", so the check lives here with the same wording.
822
+ if (!opts.workspaceId)
823
+ cmd.error("error: required option '--workspace-id <id>' not specified");
824
+ if (!opts.apiKey)
825
+ cmd.error("error: required option '--api-key <key>' not specified");
826
+ // `installMcpDaemon` and `buildMcpPlist` were written, tested and exported,
827
+ // and then nothing called them: every route into install-daemon.ts landed
828
+ // on `installDaemon`, so the MCP proxy daemon could not be installed by any
829
+ // command the CLI offered. This flag is that missing route.
830
+ const { installDaemon, installMcpDaemon } = await import('./commands/install-daemon.js');
831
+ const install = opts.mcp ? installMcpDaemon : installDaemon;
832
+ await install({
833
+ workspaceId: opts.workspaceId,
834
+ apiKey: opts.apiKey,
835
+ controlPlaneUrl: opts.controlPlaneUrl,
797
836
  binaryPath: opts.binaryPath,
798
837
  dryRun: opts.dryRun,
799
838
  system: opts.system,
800
839
  });
801
- return;
802
- }
803
- // Required for the two daemons; commander's `requiredOption` cannot express
804
- // "unless --proxy", so the check lives here with the same wording.
805
- if (!opts.workspaceId)
806
- cmd.error("error: required option '--workspace-id <id>' not specified");
807
- if (!opts.apiKey)
808
- cmd.error("error: required option '--api-key <key>' not specified");
809
- // `installMcpDaemon` and `buildMcpPlist` were written, tested and exported,
810
- // and then nothing called them: every route into install-daemon.ts landed
811
- // on `installDaemon`, so the MCP proxy daemon could not be installed by any
812
- // command the CLI offered (TD-153). This flag is that missing route.
813
- const { installDaemon, installMcpDaemon } = await import('./commands/install-daemon.js');
814
- const install = opts.mcp ? installMcpDaemon : installDaemon;
815
- await install({
816
- workspaceId: opts.workspaceId,
817
- apiKey: opts.apiKey,
818
- controlPlaneUrl: opts.controlPlaneUrl,
819
- binaryPath: opts.binaryPath,
820
- dryRun: opts.dryRun,
821
- system: opts.system,
822
840
  });
823
- });
824
- daemon
825
- .command('uninstall')
826
- .alias('uninstall-daemon')
827
- .description('Remove the sync-daemon system service and stop it permanently.')
828
- .option('--dry-run', 'Print what would be done without writing files')
829
- .option('--system', 'Uninstall the system-level service')
830
- .option('--mcp', 'Uninstall the MCP proxy daemon instead of the sync-daemon')
831
- .option('--proxy', 'Uninstall the standalone intutic-proxy service instead of the sync-daemon')
832
- .action(async (opts) => {
833
- const { uninstallDaemon, uninstallMcpDaemon, uninstallProxyService } = await import('./commands/install-daemon.js');
834
- const uninstall = opts.proxy ? uninstallProxyService : opts.mcp ? uninstallMcpDaemon : uninstallDaemon;
835
- await uninstall({ dryRun: opts.dryRun, system: opts.system });
836
- });
841
+ }
842
+ function defineDaemonUninstall(cmd) {
843
+ return cmd
844
+ .description('Remove the sync-daemon system service and stop it permanently.')
845
+ .option('--dry-run', 'Print what would be done without writing files')
846
+ .option('--system', 'Uninstall the system-level service')
847
+ .option('--mcp', 'Uninstall the MCP proxy daemon instead of the sync-daemon')
848
+ .option('--proxy', 'Uninstall the standalone intutic-proxy service instead of the sync-daemon')
849
+ .action(async (opts) => {
850
+ const { uninstallDaemon, uninstallMcpDaemon, uninstallProxyService } = await import('./commands/install-daemon.js');
851
+ const uninstall = opts.proxy ? uninstallProxyService : opts.mcp ? uninstallMcpDaemon : uninstallDaemon;
852
+ await uninstall({ dryRun: opts.dryRun, system: opts.system });
853
+ });
854
+ }
855
+ defineDaemonInstall(daemon.command('install'));
856
+ defineDaemonUninstall(daemon.command('uninstall'));
857
+ defineDaemonInstall(program.command('install-daemon'))
858
+ .summary("Shortcut for 'intutic daemon install'");
859
+ defineDaemonUninstall(program.command('uninstall-daemon'))
860
+ .summary("Shortcut for 'intutic daemon uninstall'");
837
861
  daemon
838
862
  .command('status')
839
863
  .description('Show sync-daemon system service status (or the standalone proxy service with --proxy).')
@@ -870,39 +894,6 @@ daemon
870
894
  const { daemonStart, proxyServiceStart, mcpDaemonStart } = await import('./commands/install-daemon.js');
871
895
  await (opts.proxy ? proxyServiceStart() : opts.mcp ? mcpDaemonStart() : daemonStart());
872
896
  });
873
- // Top-level shortcuts (for discoverability)
874
- program
875
- .command('install-daemon', { hidden: false })
876
- .description('Shortcut for \'intutic daemon install\' — install sync-daemon as system service')
877
- .requiredOption('--workspace-id <id>', 'Workspace ID')
878
- .requiredOption('--api-key <key>', 'Workspace API key')
879
- .option('--control-plane-url <url>', 'Control plane URL', 'https://api.intutic.ai')
880
- .option('--binary-path <path>', 'Path to intutic CLI binary')
881
- .option('--dry-run', 'Print what would be done without writing files')
882
- .option('--system', 'Install as a system-level service')
883
- .action(async (opts) => {
884
- const { installDaemon } = await import('./commands/install-daemon.js');
885
- await installDaemon({
886
- workspaceId: opts.workspaceId,
887
- apiKey: opts.apiKey,
888
- controlPlaneUrl: opts.controlPlaneUrl,
889
- binaryPath: opts.binaryPath,
890
- dryRun: opts.dryRun,
891
- system: opts.system,
892
- });
893
- });
894
- program
895
- .command('uninstall-daemon', { hidden: false })
896
- .description('Shortcut for \'intutic daemon uninstall\'')
897
- .option('--dry-run', 'Print what would be done without writing files')
898
- .option('--system', 'Uninstall the system-level service')
899
- .option('--mcp', 'Uninstall the MCP proxy daemon instead of the sync-daemon')
900
- .option('--proxy', 'Uninstall the standalone intutic-proxy service instead of the sync-daemon')
901
- .action(async (opts) => {
902
- const { uninstallDaemon, uninstallMcpDaemon, uninstallProxyService } = await import('./commands/install-daemon.js');
903
- const uninstall = opts.proxy ? uninstallProxyService : opts.mcp ? uninstallMcpDaemon : uninstallDaemon;
904
- await uninstall({ dryRun: opts.dryRun, system: opts.system });
905
- });
906
897
  // ── Skill commands ─────────────────────────────────────────────────────────
907
898
  const skillCmd = program
908
899
  .command('skill')
@@ -920,9 +911,10 @@ skillCmd
920
911
  .option('--sarif', 'Output findings as a SARIF 2.1.0 document (for GitHub Code Scanning / CI interop) instead of the human-readable report')
921
912
  .option('--engine <engine>', "Scanning engine: 'native' (default, always runs) or 'cisco' to also run the opt-in Cisco " +
922
913
  "skill-scanner integration (requires the 'skill-scanner' binary on PATH — pipx install cisco-ai-skill-scanner)", 'native')
914
+ .option('--exit-zero', 'Exit 0 even when there are findings (by default findings exit 1)')
923
915
  .action(async (opts) => {
924
916
  const { runSkillAudit } = await import('./commands/skill.js');
925
- await runSkillAudit({ sarif: opts.sarif, engine: opts.engine });
917
+ await runSkillAudit({ sarif: opts.sarif, engine: opts.engine, exitZero: opts.exitZero });
926
918
  });
927
919
  skillCmd
928
920
  .command('scan-staged')
@@ -969,7 +961,7 @@ loopCmd
969
961
  .option('--approve', 'Release the hold; the run resumes')
970
962
  .option('--reject', 'Refuse the hold; the run is killed')
971
963
  .option('--note <note>', 'Why, recorded against the run')
972
- .option('--dev', 'Target the local control plane')
964
+ .option('--dev', 'Use local control plane (http://localhost:3001)')
973
965
  .action(async (loopRunId, opts) => {
974
966
  const { runLoopReview } = await import('./commands/skill.js');
975
967
  await runLoopReview(loopRunId, opts);
@@ -1154,7 +1146,7 @@ const attenuateCmd = program
1154
1146
  .description('Attenuate an API key to a narrower child key (capability subset + optional TTL)')
1155
1147
  .option('--parent-key <keyId>', 'Parent API key ID to attenuate')
1156
1148
  .option('--caps <caps>', 'Comma-separated capability subset to grant the child key')
1157
- .option('--ttl <seconds>', 'Child key TTL in seconds (server default 14400, max 86400)')
1149
+ .option('--ttl <seconds>', 'Child key TTL in whole seconds, 60 to 86400 (default 14400)')
1158
1150
  .option('--dev', 'Use local control plane (http://localhost:3001)')
1159
1151
  .action(async (opts) => {
1160
1152
  const { runAttenuate } = await import('./commands/attenuate.js');