@provos/ironcurtain 0.6.0 → 0.7.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 (187) hide show
  1. package/README.md +21 -2
  2. package/dist/cli-help.d.ts +63 -0
  3. package/dist/cli-help.js +102 -0
  4. package/dist/cli-help.js.map +1 -0
  5. package/dist/cli.js +73 -56
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config/config-command.js +136 -2
  8. package/dist/config/config-command.js.map +1 -1
  9. package/dist/config/generated/tool-annotations.json +487 -338
  10. package/dist/config/index.d.ts +21 -1
  11. package/dist/config/index.js +12 -4
  12. package/dist/config/index.js.map +1 -1
  13. package/dist/config/mcp-servers.json +2 -8
  14. package/dist/config/paths.d.ts +30 -0
  15. package/dist/config/paths.js +58 -0
  16. package/dist/config/paths.js.map +1 -1
  17. package/dist/config/tool-description-hints.json +15 -0
  18. package/dist/config/types.d.ts +7 -0
  19. package/dist/config/user-config.d.ts +21 -0
  20. package/dist/config/user-config.js +8 -0
  21. package/dist/config/user-config.js.map +1 -1
  22. package/dist/cron/compile-task-policy.d.ts +17 -0
  23. package/dist/cron/compile-task-policy.js +39 -0
  24. package/dist/cron/compile-task-policy.js.map +1 -0
  25. package/dist/cron/cron-scheduler.d.ts +65 -0
  26. package/dist/cron/cron-scheduler.js +184 -0
  27. package/dist/cron/cron-scheduler.js.map +1 -0
  28. package/dist/cron/file-lock.d.ts +41 -0
  29. package/dist/cron/file-lock.js +156 -0
  30. package/dist/cron/file-lock.js.map +1 -0
  31. package/dist/cron/format-utils.d.ts +18 -0
  32. package/dist/cron/format-utils.js +145 -0
  33. package/dist/cron/format-utils.js.map +1 -0
  34. package/dist/cron/git-sync.d.ts +22 -0
  35. package/dist/cron/git-sync.js +121 -0
  36. package/dist/cron/git-sync.js.map +1 -0
  37. package/dist/cron/headless-transport.d.ts +27 -0
  38. package/dist/cron/headless-transport.js +43 -0
  39. package/dist/cron/headless-transport.js.map +1 -0
  40. package/dist/cron/job-commands.d.ts +39 -0
  41. package/dist/cron/job-commands.js +522 -0
  42. package/dist/cron/job-commands.js.map +1 -0
  43. package/dist/cron/job-store.d.ts +35 -0
  44. package/dist/cron/job-store.js +111 -0
  45. package/dist/cron/job-store.js.map +1 -0
  46. package/dist/cron/types.d.ts +127 -0
  47. package/dist/cron/types.js +25 -0
  48. package/dist/cron/types.js.map +1 -0
  49. package/dist/daemon/control-socket.d.ts +127 -0
  50. package/dist/daemon/control-socket.js +261 -0
  51. package/dist/daemon/control-socket.js.map +1 -0
  52. package/dist/daemon/daemon-command.d.ts +13 -0
  53. package/dist/daemon/daemon-command.js +230 -0
  54. package/dist/daemon/daemon-command.js.map +1 -0
  55. package/dist/daemon/ironcurtain-daemon.d.ts +90 -0
  56. package/dist/daemon/ironcurtain-daemon.js +548 -0
  57. package/dist/daemon/ironcurtain-daemon.js.map +1 -0
  58. package/dist/docker/adapters/claude-code.js +7 -47
  59. package/dist/docker/adapters/claude-code.js.map +1 -1
  60. package/dist/docker/adapters/goose.d.ts +58 -0
  61. package/dist/docker/adapters/goose.js +250 -0
  62. package/dist/docker/adapters/goose.js.map +1 -0
  63. package/dist/docker/adapters/shared-scripts.d.ts +29 -0
  64. package/dist/docker/adapters/shared-scripts.js +80 -0
  65. package/dist/docker/adapters/shared-scripts.js.map +1 -0
  66. package/dist/docker/agent-adapter.d.ts +12 -0
  67. package/dist/docker/agent-adapter.js.map +1 -1
  68. package/dist/docker/agent-registry.d.ts +6 -1
  69. package/dist/docker/agent-registry.js +10 -1
  70. package/dist/docker/agent-registry.js.map +1 -1
  71. package/dist/docker/docker-agent-session.d.ts +2 -25
  72. package/dist/docker/docker-agent-session.js +7 -106
  73. package/dist/docker/docker-agent-session.js.map +1 -1
  74. package/dist/docker/docker-infrastructure.d.ts +8 -0
  75. package/dist/docker/docker-infrastructure.js +12 -11
  76. package/dist/docker/docker-infrastructure.js.map +1 -1
  77. package/dist/docker/pty-session.js +7 -4
  78. package/dist/docker/pty-session.js.map +1 -1
  79. package/dist/escalation/escalation-watcher.d.ts +2 -0
  80. package/dist/escalation/escalation-watcher.js +3 -0
  81. package/dist/escalation/escalation-watcher.js.map +1 -1
  82. package/dist/escalation/session-registry.js +1 -14
  83. package/dist/escalation/session-registry.js.map +1 -1
  84. package/dist/index.js +30 -1
  85. package/dist/index.js.map +1 -1
  86. package/dist/mux/mux-app.js +11 -6
  87. package/dist/mux/mux-app.js.map +1 -1
  88. package/dist/mux/mux-command.js +20 -9
  89. package/dist/mux/mux-command.js.map +1 -1
  90. package/dist/mux/mux-input-handler.d.ts +5 -0
  91. package/dist/mux/mux-input-handler.js +64 -9
  92. package/dist/mux/mux-input-handler.js.map +1 -1
  93. package/dist/mux/mux-renderer.d.ts +20 -0
  94. package/dist/mux/mux-renderer.js +127 -35
  95. package/dist/mux/mux-renderer.js.map +1 -1
  96. package/dist/mux/mux-splash.js +2 -1
  97. package/dist/mux/mux-splash.js.map +1 -1
  98. package/dist/mux/paste-interceptor.d.ts +22 -0
  99. package/dist/mux/paste-interceptor.js +143 -0
  100. package/dist/mux/paste-interceptor.js.map +1 -0
  101. package/dist/mux/pty-bridge.js +1 -1
  102. package/dist/mux/pty-bridge.js.map +1 -1
  103. package/dist/mux/types.d.ts +8 -1
  104. package/dist/mux/types.js +17 -5
  105. package/dist/mux/types.js.map +1 -1
  106. package/dist/pipeline/annotate.js +2 -1
  107. package/dist/pipeline/annotate.js.map +1 -1
  108. package/dist/pipeline/compile.d.ts +4 -34
  109. package/dist/pipeline/compile.js +29 -568
  110. package/dist/pipeline/compile.js.map +1 -1
  111. package/dist/pipeline/constitution-compiler.js +1 -1
  112. package/dist/pipeline/mcp-connections.d.ts +16 -0
  113. package/dist/pipeline/mcp-connections.js +61 -0
  114. package/dist/pipeline/mcp-connections.js.map +1 -0
  115. package/dist/pipeline/pipeline-runner.d.ts +86 -0
  116. package/dist/pipeline/pipeline-runner.js +554 -0
  117. package/dist/pipeline/pipeline-runner.js.map +1 -0
  118. package/dist/pipeline/pipeline-shared.d.ts +29 -2
  119. package/dist/pipeline/pipeline-shared.js +68 -12
  120. package/dist/pipeline/pipeline-shared.js.map +1 -1
  121. package/dist/pipeline/refresh-lists.js +38 -12
  122. package/dist/pipeline/refresh-lists.js.map +1 -1
  123. package/dist/pipeline/scenario-generator.js +2 -1
  124. package/dist/pipeline/scenario-generator.js.map +1 -1
  125. package/dist/pipeline/tool-annotator.d.ts +3 -3
  126. package/dist/pipeline/tool-annotator.js +96 -4
  127. package/dist/pipeline/tool-annotator.js.map +1 -1
  128. package/dist/pipeline/types.d.ts +52 -0
  129. package/dist/sandbox/index.js +36 -1
  130. package/dist/sandbox/index.js.map +1 -1
  131. package/dist/session/agent-session.d.ts +3 -0
  132. package/dist/session/agent-session.js +10 -1
  133. package/dist/session/agent-session.js.map +1 -1
  134. package/dist/session/index.js +54 -5
  135. package/dist/session/index.js.map +1 -1
  136. package/dist/session/preflight.js +26 -6
  137. package/dist/session/preflight.js.map +1 -1
  138. package/dist/session/prompts.d.ts +14 -0
  139. package/dist/session/prompts.js +39 -2
  140. package/dist/session/prompts.js.map +1 -1
  141. package/dist/session/session-manager.d.ts +105 -0
  142. package/dist/session/session-manager.js +134 -0
  143. package/dist/session/session-manager.js.map +1 -0
  144. package/dist/session/types.d.ts +27 -0
  145. package/dist/session/workspace-validation.d.ts +2 -0
  146. package/dist/session/workspace-validation.js +6 -5
  147. package/dist/session/workspace-validation.js.map +1 -1
  148. package/dist/signal/bot-command.d.ts +2 -0
  149. package/dist/signal/bot-command.js +8 -0
  150. package/dist/signal/bot-command.js.map +1 -1
  151. package/dist/signal/format.d.ts +27 -0
  152. package/dist/signal/format.js +59 -0
  153. package/dist/signal/format.js.map +1 -1
  154. package/dist/signal/signal-bot-daemon.d.ts +27 -25
  155. package/dist/signal/signal-bot-daemon.js +145 -130
  156. package/dist/signal/signal-bot-daemon.js.map +1 -1
  157. package/dist/signal/signal-container.js +63 -19
  158. package/dist/signal/signal-container.js.map +1 -1
  159. package/dist/signal/signal-transport.js +3 -8
  160. package/dist/signal/signal-transport.js.map +1 -1
  161. package/dist/trusted-process/domain-utils.d.ts +38 -4
  162. package/dist/trusted-process/domain-utils.js +118 -11
  163. package/dist/trusted-process/domain-utils.js.map +1 -1
  164. package/dist/trusted-process/index.js +6 -2
  165. package/dist/trusted-process/index.js.map +1 -1
  166. package/dist/trusted-process/mcp-client-manager.js +11 -5
  167. package/dist/trusted-process/mcp-client-manager.js.map +1 -1
  168. package/dist/trusted-process/mcp-proxy-server.d.ts +2 -0
  169. package/dist/trusted-process/mcp-proxy-server.js +59 -16
  170. package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
  171. package/dist/trusted-process/permissive-output-validator.d.ts +23 -0
  172. package/dist/trusted-process/permissive-output-validator.js +10 -0
  173. package/dist/trusted-process/permissive-output-validator.js.map +1 -0
  174. package/dist/trusted-process/policy-engine.d.ts +27 -4
  175. package/dist/trusted-process/policy-engine.js +152 -57
  176. package/dist/trusted-process/policy-engine.js.map +1 -1
  177. package/dist/trusted-process/tool-description-hints.d.ts +19 -0
  178. package/dist/trusted-process/tool-description-hints.js +53 -0
  179. package/dist/trusted-process/tool-description-hints.js.map +1 -0
  180. package/dist/types/argument-roles.d.ts +33 -1
  181. package/dist/types/argument-roles.js +129 -1
  182. package/dist/types/argument-roles.js.map +1 -1
  183. package/docker/Dockerfile.goose +34 -0
  184. package/docker/entrypoint-goose.sh +30 -0
  185. package/package.json +1 -1
  186. package/src/config/generated/tool-annotations.json +487 -338
  187. package/src/config/mcp-servers.json +2 -8
package/README.md CHANGED
@@ -152,7 +152,7 @@ Session IDs are printed on session start and stored under `~/.ironcurtain/sessio
152
152
 
153
153
  ### Terminal multiplexer (recommended for Docker Agent Mode)
154
154
 
155
- The terminal multiplexer is the recommended way to use IronCurtain with Docker Agent Mode. It gives you the full power of Claude Code's interactive TUI spinners, diffs, file previews, slash commands — while IronCurtain mediates every tool call through its policy engine. All in a single terminal.
155
+ The terminal multiplexer is the recommended way to use IronCurtain with Docker Agent Mode. It gives you the full power of your agent's interactive TUI (Claude Code or Goose) while IronCurtain mediates every tool call through its policy engine. All in a single terminal.
156
156
 
157
157
  ```bash
158
158
  ironcurtain mux
@@ -160,7 +160,7 @@ ironcurtain mux
160
160
 
161
161
  **Key capabilities:**
162
162
 
163
- - **Full agent TUI** — Claude Code runs in a PTY inside a Docker container with no network access. You interact with it exactly as if it were running locally.
163
+ - **Full agent TUI** — The agent (Claude Code or Goose) runs in a PTY inside a Docker container with no network access. You interact with it exactly as if it were running locally.
164
164
  - **Inline escalation handling** — When a tool call needs approval, an escalation panel overlays the viewport. No separate terminal needed — press Ctrl-A to enter command mode, `/approve N` or `/deny N`, and return.
165
165
  - **Trusted user input** — Text typed in command mode (Ctrl-A) is captured on the host side before entering the container. This creates a verified intent signal that the [auto-approver](#auto-approve-escalations) can use — e.g., typing "push my changes to origin" will auto-approve a subsequent `git_push` escalation.
166
166
  - **Tab management** — Spawn multiple concurrent sessions (`/new`), switch between them (`/tab N`, Alt-1..9), close them (`/close`).
@@ -194,6 +194,22 @@ ironcurtain bot # Start the Signal bot daemon
194
194
 
195
195
  See [TRANSPORT.md](TRANSPORT.md) for setup instructions, architecture details, and why we chose Signal over alternatives like Telegram.
196
196
 
197
+ ### Daemon mode
198
+
199
+ A unified long-running daemon that combines Signal messaging with scheduled cron jobs. Define recurring tasks with per-job security policies, and IronCurtain runs them headlessly on a cron schedule.
200
+
201
+ ```bash
202
+ ironcurtain daemon add-job # Interactive wizard to define a scheduled job
203
+ ironcurtain daemon # Start the daemon (Signal + cron)
204
+ ironcurtain daemon --no-signal # Cron-only mode (no Signal transport)
205
+ ironcurtain daemon list-jobs # List jobs with schedule and status
206
+ ironcurtain daemon logs <id> # Show recent run summaries
207
+ ```
208
+
209
+ Each job has its own task description, security constitution (compiled into per-job policy rules), persistent workspace, optional git repo sync, and configurable resource budgets. Escalations are auto-denied in headless mode unless Signal is configured for approval routing.
210
+
211
+ See [DAEMON.md](DAEMON.md) for the full setup guide, job definition reference, and troubleshooting.
212
+
197
213
  ## Session Commands
198
214
 
199
215
  Commands available during an **interactive** or **single-shot** session:
@@ -291,6 +307,7 @@ IronCurtain stores configuration and session data in `~/.ironcurtain/`:
291
307
  ├── generated/ # User-compiled policy artifacts (overrides package defaults)
292
308
  ├── signal-data/ # Signal transport persistent data (registration keys)
293
309
  ├── pty-registry/ # Active PTY session registrations (auto-managed)
310
+ ├── jobs/ # Cron job definitions, workspaces, and run records
294
311
  ├── sessions/
295
312
  │ └── {sessionId}/
296
313
  │ ├── sandbox/ # Per-session filesystem sandbox
@@ -451,6 +468,8 @@ src/
451
468
  ├── escalation/ # Escalation listener: session registry, TUI dashboard, state
452
469
  ├── mux/ # Terminal multiplexer: PTY bridge, renderer, trusted input
453
470
  ├── signal/ # Signal messaging transport (bot daemon, setup, formatting)
471
+ ├── daemon/ # Unified daemon (Signal + cron scheduler, control socket)
472
+ ├── cron/ # Cron job management (scheduler, job store, git sync, policy)
454
473
  ├── docker/ # Docker agent mode, PTY session, MITM proxy, adapters
455
474
  ├── servers/ # Built-in MCP servers (fetch, web search providers)
456
475
  └── types/ # Shared type definitions
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared CLI help formatting module.
3
+ *
4
+ * Provides a declarative CommandSpec interface and formatting utilities
5
+ * so every subcommand produces consistent, aligned help output.
6
+ */
7
+ export interface CommandOption {
8
+ /** The long flag name, without dashes (e.g., 'agent'). */
9
+ readonly flag: string;
10
+ /** Optional single-character short alias (e.g., 'a'). */
11
+ readonly short?: string;
12
+ /** Human-readable description. */
13
+ readonly description: string;
14
+ /** Placeholder for the value (e.g., '<name>'). Omit for boolean flags. */
15
+ readonly placeholder?: string;
16
+ }
17
+ export interface CommandSpec {
18
+ /** The full command name as invoked (e.g., 'ironcurtain daemon'). */
19
+ readonly name: string;
20
+ /** One-line summary of what the command does. */
21
+ readonly description: string;
22
+ /** Usage lines shown at the top. Each string is a separate line. */
23
+ readonly usage: readonly string[];
24
+ /** Named subcommands, displayed in a table after the usage block. */
25
+ readonly subcommands?: readonly {
26
+ readonly name: string;
27
+ readonly description: string;
28
+ }[];
29
+ /** Command-line options, displayed in a table after subcommands. */
30
+ readonly options?: readonly CommandOption[];
31
+ /** Example invocations shown at the bottom. */
32
+ readonly examples?: readonly string[];
33
+ }
34
+ /**
35
+ * Formats a CommandSpec into a complete help string.
36
+ *
37
+ * Layout:
38
+ * <name> - <description>
39
+ *
40
+ * Usage:
41
+ * <usage lines>
42
+ *
43
+ * Subcommands: (if any)
44
+ * <table>
45
+ *
46
+ * Options: (if any)
47
+ * <table>
48
+ *
49
+ * Examples: (if any)
50
+ * <lines>
51
+ */
52
+ export declare function formatHelp(spec: CommandSpec): string;
53
+ /**
54
+ * Prints formatted help to stderr.
55
+ */
56
+ export declare function printHelp(spec: CommandSpec): void;
57
+ /**
58
+ * Convenience: if `values.help` is truthy, prints help and returns true.
59
+ * Callers can use: `if (checkHelp(values, spec)) return;`
60
+ */
61
+ export declare function checkHelp(values: {
62
+ help?: boolean;
63
+ }, spec: CommandSpec): boolean;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared CLI help formatting module.
3
+ *
4
+ * Provides a declarative CommandSpec interface and formatting utilities
5
+ * so every subcommand produces consistent, aligned help output.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Formatting
9
+ // ---------------------------------------------------------------------------
10
+ /** Pads `text` with spaces on the right to reach at least `width` characters. */
11
+ function pad(text, width) {
12
+ return text.length >= width ? text : text + ' '.repeat(width - text.length);
13
+ }
14
+ /** Formats a single option into its flag string (e.g., '-a, --agent <name>'). */
15
+ function formatOptionFlag(opt) {
16
+ const parts = [];
17
+ if (opt.short) {
18
+ parts.push(`-${opt.short},`);
19
+ }
20
+ let longFlag = `--${opt.flag}`;
21
+ if (opt.placeholder) {
22
+ longFlag += ` ${opt.placeholder}`;
23
+ }
24
+ if (opt.short) {
25
+ parts.push(longFlag);
26
+ }
27
+ else {
28
+ // Indent to align with flags that have a short alias.
29
+ // "-X, " is 4 chars, so we add 4 spaces of leading indent.
30
+ parts.push(` ${longFlag}`);
31
+ }
32
+ return parts.join(' ');
33
+ }
34
+ /**
35
+ * Renders a two-column table (label + description) with consistent alignment.
36
+ * Each row is indented by 2 spaces.
37
+ */
38
+ function formatTable(rows) {
39
+ const maxLabel = rows.reduce((max, r) => Math.max(max, r.label.length), 0);
40
+ const colWidth = maxLabel + 2; // 2 spaces between columns
41
+ return rows.map((r) => ` ${pad(r.label, colWidth)}${r.description}`).join('\n');
42
+ }
43
+ /**
44
+ * Formats a CommandSpec into a complete help string.
45
+ *
46
+ * Layout:
47
+ * <name> - <description>
48
+ *
49
+ * Usage:
50
+ * <usage lines>
51
+ *
52
+ * Subcommands: (if any)
53
+ * <table>
54
+ *
55
+ * Options: (if any)
56
+ * <table>
57
+ *
58
+ * Examples: (if any)
59
+ * <lines>
60
+ */
61
+ export function formatHelp(spec) {
62
+ const sections = [];
63
+ // Header
64
+ sections.push(`${spec.name} - ${spec.description}`);
65
+ // Usage
66
+ sections.push('Usage:\n' + spec.usage.map((u) => ` ${u}`).join('\n'));
67
+ // Subcommands
68
+ if (spec.subcommands && spec.subcommands.length > 0) {
69
+ const rows = spec.subcommands.map((s) => ({ label: s.name, description: s.description }));
70
+ sections.push('Subcommands:\n' + formatTable(rows));
71
+ }
72
+ // Options
73
+ if (spec.options && spec.options.length > 0) {
74
+ const rows = spec.options.map((o) => ({
75
+ label: formatOptionFlag(o),
76
+ description: o.description,
77
+ }));
78
+ sections.push('Options:\n' + formatTable(rows));
79
+ }
80
+ // Examples
81
+ if (spec.examples && spec.examples.length > 0) {
82
+ sections.push('Examples:\n' + spec.examples.map((e) => ` ${e}`).join('\n'));
83
+ }
84
+ return sections.join('\n\n');
85
+ }
86
+ /**
87
+ * Prints formatted help to stderr.
88
+ */
89
+ export function printHelp(spec) {
90
+ console.error(formatHelp(spec));
91
+ }
92
+ /**
93
+ * Convenience: if `values.help` is truthy, prints help and returns true.
94
+ * Callers can use: `if (checkHelp(values, spec)) return;`
95
+ */
96
+ export function checkHelp(values, spec) {
97
+ if (!values.help)
98
+ return false;
99
+ printHelp(spec);
100
+ return true;
101
+ }
102
+ //# sourceMappingURL=cli-help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs';
4
4
  import { dirname, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { parseArgs } from 'node:util';
7
+ import { checkHelp, printHelp as printSpecHelp } from './cli-help.js';
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  function getVersion() {
9
10
  // Works from both dist/ and src/ since package.json is one level up in either case.
@@ -11,57 +12,48 @@ function getVersion() {
11
12
  const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
12
13
  return pkg.version;
13
14
  }
14
- function printHelp() {
15
- console.error(`
16
- ironcurtain - Secure agent runtime with policy-driven tool mediation
17
-
18
- Usage:
19
- ironcurtain <command> [options]
20
-
21
- Commands:
22
- start [task] Run the agent (interactive or single-shot)
23
- mux Terminal multiplexer for PTY sessions (requires node-pty)
24
- escalation-listener Aggregate escalation notifications from PTY sessions
25
- bot Run the Signal messaging transport daemon
26
- setup Run the first-start wizard (always runs)
27
- setup-signal Interactive Signal transport onboarding
28
- --re-trust: re-verify a changed identity key
29
- annotate-tools Classify MCP tool arguments via LLM
30
- compile-policy Compile constitution into enforceable policy rules
31
- refresh-lists Re-resolve dynamic lists without full recompilation
32
- customize-policy Customize your policy via LLM-assisted conversation
33
- config Edit configuration interactively
34
- help Show this help message
35
-
36
- Options:
37
- -h, --help Show this help message
38
- -v, --version Show version number
39
- -a, --agent <name> Agent mode: builtin or claude-code (Docker)
40
- Auto-detects if omitted: Docker if available, else builtin
41
- -w, --workspace <path> Use an existing directory as the agent's workspace
42
- instead of creating a fresh sandbox
43
- --pty Attach terminal directly to agent PTY (Docker mode only)
44
- Ctrl-\\ is the emergency exit; run 'reset' to recover
45
- the terminal if killed ungracefully
46
- --list-agents List registered agent adapters
47
-
48
- Examples:
49
- ironcurtain start "task" # Auto-detects Docker or builtin
50
- ironcurtain start # Interactive session
51
- ironcurtain start "Summarize files in ." # Single-shot task
52
- ironcurtain start --resume <session-id> # Resume a session
53
- ironcurtain start -w ./my-project "Fix bugs" # Work in existing directory
54
- ironcurtain start --agent claude-code "task" # Docker: Claude Code
55
- ironcurtain start --pty # PTY mode: interactive Docker terminal
56
- ironcurtain start --list-agents # List available agents
57
- ironcurtain annotate-tools # Classify tool arguments
58
- ironcurtain compile-policy # Compile policy from constitution
59
- ironcurtain refresh-lists # Refresh all dynamic lists
60
- ironcurtain refresh-lists --list major-news # Refresh a single list
61
- ironcurtain refresh-lists --with-mcp # Include MCP-backed lists
62
- ironcurtain customize-policy # Customize policy interactively
63
- `.trim());
64
- }
15
+ const topLevelSpec = {
16
+ name: 'ironcurtain',
17
+ description: 'Secure agent runtime with policy-driven tool mediation',
18
+ usage: ['ironcurtain <command> [options]'],
19
+ subcommands: [
20
+ { name: 'start [task]', description: 'Run the agent (interactive or single-shot)' },
21
+ { name: 'daemon', description: 'Unified Signal + cron daemon' },
22
+ { name: 'mux', description: 'Terminal multiplexer for PTY sessions (requires node-pty)' },
23
+ { name: 'escalation-listener', description: 'Aggregate escalation notifications from PTY sessions' },
24
+ { name: 'bot', description: "Alias for 'daemon' (backward compatible)" },
25
+ { name: 'setup', description: 'Run the first-start wizard (always runs)' },
26
+ { name: 'setup-signal', description: 'Interactive Signal transport onboarding' },
27
+ { name: 'annotate-tools', description: 'Classify MCP tool arguments via LLM' },
28
+ { name: 'compile-policy', description: 'Compile constitution into enforceable policy rules' },
29
+ { name: 'refresh-lists', description: 'Re-resolve dynamic lists without full recompilation' },
30
+ { name: 'customize-policy', description: 'Customize your policy via LLM-assisted conversation' },
31
+ { name: 'config', description: 'Edit configuration interactively' },
32
+ { name: 'help', description: 'Show this help message' },
33
+ ],
34
+ options: [
35
+ { flag: 'help', short: 'h', description: 'Show this help message' },
36
+ { flag: 'version', short: 'v', description: 'Show version number' },
37
+ ],
38
+ examples: [
39
+ 'ironcurtain start "Summarize files in ." # Single-shot task',
40
+ 'ironcurtain start # Interactive session',
41
+ 'ironcurtain start --resume <session-id> # Resume a session',
42
+ 'ironcurtain start -w ./my-project "Fix bugs" # Work in existing directory',
43
+ 'ironcurtain start --agent claude-code "task" # Docker: Claude Code',
44
+ 'ironcurtain start --pty # PTY mode',
45
+ 'ironcurtain daemon # Start the daemon',
46
+ 'ironcurtain daemon list-jobs # List scheduled jobs',
47
+ 'ironcurtain compile-policy # Compile policy',
48
+ 'ironcurtain refresh-lists --with-mcp # Refresh dynamic lists',
49
+ ],
50
+ };
51
+ const setupSignalSpec = {
52
+ name: 'ironcurtain setup-signal',
53
+ description: 'Interactive Signal transport onboarding',
54
+ usage: ['ironcurtain setup-signal [options]'],
55
+ options: [{ flag: 're-trust', description: 'Re-verify a changed identity key' }],
56
+ };
65
57
  const { values, positionals } = parseArgs({
66
58
  args: process.argv.slice(2),
67
59
  options: {
@@ -76,8 +68,11 @@ if (values.version) {
76
68
  process.exit(0);
77
69
  }
78
70
  const subcommand = positionals[0];
79
- if (values.help || subcommand === 'help' || !subcommand) {
80
- printHelp();
71
+ // Show top-level help when no subcommand is given or 'help' is the subcommand.
72
+ // When a recognized subcommand is present, --help passes through to the
73
+ // subcommand handler via process.argv.slice(3).
74
+ if (!subcommand || subcommand === 'help') {
75
+ printSpecHelp(topLevelSpec);
81
76
  process.exit(0);
82
77
  }
83
78
  switch (subcommand) {
@@ -116,10 +111,30 @@ switch (subcommand) {
116
111
  await runFirstStart();
117
112
  break;
118
113
  }
114
+ case 'daemon': {
115
+ const { runDaemonCommand } = await import('./daemon/daemon-command.js');
116
+ await runDaemonCommand(process.argv.slice(3));
117
+ break;
118
+ }
119
119
  case 'bot': {
120
+ // 'bot' is an alias for 'daemon' (backward compatibility)
121
+ const botArgs = process.argv.slice(3);
122
+ if (botArgs.includes('--help') || botArgs.includes('-h')) {
123
+ const { botSpec } = await import('./signal/bot-command.js');
124
+ printSpecHelp(botSpec);
125
+ break;
126
+ }
120
127
  const agentName = values.agent;
121
- const { runBot } = await import('./signal/bot-command.js');
122
- await runBot({ agent: agentName });
128
+ if (botArgs.some((a) => !a.startsWith('-'))) {
129
+ // Has subcommands -- route through daemon command
130
+ const { runDaemonCommand } = await import('./daemon/daemon-command.js');
131
+ await runDaemonCommand(botArgs);
132
+ }
133
+ else {
134
+ // No subcommands -- existing bot behavior
135
+ const { runBot } = await import('./signal/bot-command.js');
136
+ await runBot({ agent: agentName });
137
+ }
123
138
  break;
124
139
  }
125
140
  case 'mux': {
@@ -133,6 +148,8 @@ switch (subcommand) {
133
148
  break;
134
149
  }
135
150
  case 'setup-signal': {
151
+ if (checkHelp({ help: process.argv.includes('--help') || process.argv.includes('-h') }, setupSignalSpec))
152
+ break;
136
153
  const reTrust = process.argv.includes('--re-trust');
137
154
  const { runSignalSetup } = await import('./signal/setup-signal.js');
138
155
  await runSignalSetup({ reTrust });
@@ -140,7 +157,7 @@ switch (subcommand) {
140
157
  }
141
158
  default:
142
159
  console.error(`Unknown command: ${subcommand}\n`);
143
- printHelp();
160
+ printSpecHelp(topLevelSpec);
144
161
  process.exit(1);
145
162
  }
146
163
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ import * as p from '@clack/prompts';
9
9
  import { readFileSync } from 'node:fs';
10
10
  import { resolve, dirname } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
- import { loadUserConfig, saveUserConfig, validateModelId, ESCALATION_TIMEOUT_MIN, ESCALATION_TIMEOUT_MAX, WEB_SEARCH_PROVIDERS, WEB_SEARCH_PROVIDER_LABELS, WEB_SEARCH_PROVIDER_URLS, } from './user-config.js';
12
+ import { loadUserConfig, saveUserConfig, validateModelId, ESCALATION_TIMEOUT_MIN, ESCALATION_TIMEOUT_MAX, WEB_SEARCH_PROVIDERS, WEB_SEARCH_PROVIDER_LABELS, WEB_SEARCH_PROVIDER_URLS, GOOSE_PROVIDERS, DOCKER_AGENTS, } from './user-config.js';
13
13
  import { getUserConfigPath } from './paths.js';
14
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
15
  /** Known model options for selection prompts. */
@@ -70,7 +70,14 @@ function formatModelShort(id) {
70
70
  }
71
71
  export function computeDiff(resolved, pending) {
72
72
  const diffs = [];
73
- const topLevelKeys = ['agentModelId', 'policyModelId', 'escalationTimeoutSeconds'];
73
+ const topLevelKeys = [
74
+ 'agentModelId',
75
+ 'policyModelId',
76
+ 'escalationTimeoutSeconds',
77
+ 'gooseProvider',
78
+ 'gooseModel',
79
+ 'preferredDockerAgent',
80
+ ];
74
81
  for (const key of topLevelKeys) {
75
82
  if (key in pending && pending[key] !== undefined && pending[key] !== resolved[key]) {
76
83
  diffs.push([key, { from: resolved[key], to: pending[key] }]);
@@ -615,6 +622,126 @@ async function handleServerCredentials(resolved, pending) {
615
622
  }
616
623
  }
617
624
  }
625
+ // ─── Docker Agent Settings ────────────────────────────────────
626
+ /** Human-readable labels for Goose providers. */
627
+ const GOOSE_PROVIDER_LABELS = {
628
+ anthropic: 'Anthropic',
629
+ openai: 'OpenAI',
630
+ google: 'Google',
631
+ };
632
+ /** Human-readable labels for Docker agents. */
633
+ const DOCKER_AGENT_LABELS = {
634
+ 'claude-code': 'Claude Code',
635
+ goose: 'Goose',
636
+ };
637
+ async function handleDockerAgent(resolved, pending) {
638
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
639
+ while (true) {
640
+ const currentPreferred = pending.preferredDockerAgent ?? resolved.preferredDockerAgent;
641
+ const options = [
642
+ {
643
+ value: 'preferredDockerAgent',
644
+ label: 'Preferred agent',
645
+ hint: DOCKER_AGENT_LABELS[currentPreferred],
646
+ },
647
+ {
648
+ value: 'configureGoose',
649
+ label: 'Configure Goose...',
650
+ hint: gooseConfigHint(resolved, pending),
651
+ },
652
+ { value: 'back', label: 'Back' },
653
+ ];
654
+ const field = await p.select({
655
+ message: 'Docker Agent Settings',
656
+ options,
657
+ });
658
+ if (isCancelled(field) || field === 'back')
659
+ return;
660
+ if (field === 'preferredDockerAgent') {
661
+ const agentOptions = DOCKER_AGENTS.map((agent) => ({
662
+ value: agent,
663
+ label: DOCKER_AGENT_LABELS[agent],
664
+ hint: agent === currentPreferred ? '(current)' : undefined,
665
+ }));
666
+ const selected = await p.select({
667
+ message: 'Select preferred Docker agent:',
668
+ options: agentOptions,
669
+ initialValue: currentPreferred,
670
+ });
671
+ if (isCancelled(selected))
672
+ continue;
673
+ const agent = selected;
674
+ if (agent !== currentPreferred) {
675
+ pending.preferredDockerAgent = agent;
676
+ }
677
+ }
678
+ else if (field === 'configureGoose') {
679
+ await handleGooseConfig(resolved, pending);
680
+ }
681
+ }
682
+ }
683
+ /** Goose-specific configuration submenu (provider + model). */
684
+ async function handleGooseConfig(resolved, pending) {
685
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
686
+ while (true) {
687
+ const currentProvider = pending.gooseProvider ?? resolved.gooseProvider;
688
+ const currentModel = pending.gooseModel ?? resolved.gooseModel;
689
+ const field = await p.select({
690
+ message: 'Goose Configuration',
691
+ options: [
692
+ {
693
+ value: 'gooseProvider',
694
+ label: 'LLM provider',
695
+ hint: GOOSE_PROVIDER_LABELS[currentProvider],
696
+ },
697
+ {
698
+ value: 'gooseModel',
699
+ label: 'Model',
700
+ hint: currentModel,
701
+ },
702
+ { value: 'back', label: 'Back' },
703
+ ],
704
+ });
705
+ if (isCancelled(field) || field === 'back')
706
+ return;
707
+ if (field === 'gooseProvider') {
708
+ const providerOptions = GOOSE_PROVIDERS.map((prov) => ({
709
+ value: prov,
710
+ label: GOOSE_PROVIDER_LABELS[prov],
711
+ hint: prov === currentProvider ? '(current)' : undefined,
712
+ }));
713
+ const selected = await p.select({
714
+ message: 'Select Goose LLM provider:',
715
+ options: providerOptions,
716
+ initialValue: currentProvider,
717
+ });
718
+ if (isCancelled(selected))
719
+ continue;
720
+ const provider = selected;
721
+ if (provider !== currentProvider) {
722
+ pending.gooseProvider = provider;
723
+ }
724
+ }
725
+ else if (field === 'gooseModel') {
726
+ const input = await p.text({
727
+ message: 'Goose model identifier:',
728
+ placeholder: currentModel,
729
+ validate: (val) => (!val ? 'Model ID is required' : undefined),
730
+ });
731
+ if (isCancelled(input))
732
+ continue;
733
+ const model = input;
734
+ if (model !== currentModel) {
735
+ pending.gooseModel = model;
736
+ }
737
+ }
738
+ }
739
+ }
740
+ function gooseConfigHint(resolved, pending) {
741
+ const provider = GOOSE_PROVIDER_LABELS[pending.gooseProvider ?? resolved.gooseProvider];
742
+ const model = pending.gooseModel ?? resolved.gooseModel;
743
+ return `${provider}, ${model}`;
744
+ }
618
745
  // ─── Menu descriptions ───────────────────────────────────────
619
746
  function modelsHint(resolved, pending) {
620
747
  const agent = formatModelShort(pending.agentModelId ?? resolved.agentModelId);
@@ -645,6 +772,9 @@ function serverCredentialsHint(resolved, pending) {
645
772
  return 'none';
646
773
  return configured.map(([name]) => name).join(', ');
647
774
  }
775
+ function dockerAgentHint(resolved, pending) {
776
+ return DOCKER_AGENT_LABELS[pending.preferredDockerAgent ?? resolved.preferredDockerAgent];
777
+ }
648
778
  function changeCount(resolved, pending) {
649
779
  const diffs = computeDiff(resolved, pending);
650
780
  if (diffs.length === 0)
@@ -680,6 +810,7 @@ export async function runConfigCommand() {
680
810
  { value: 'compact', label: `Auto-Compact (${autoCompactHint(resolved, pending)})` },
681
811
  { value: 'websearch', label: `Web Search (${webSearchHint(resolved, pending)})` },
682
812
  { value: 'credentials', label: `Server Credentials (${serverCredentialsHint(resolved, pending)})` },
813
+ { value: 'dockerAgent', label: `Docker Agent (${dockerAgentHint(resolved, pending)})` },
683
814
  { value: 'save', label: 'Save & Exit', hint: changeCount(resolved, pending) },
684
815
  { value: 'cancel', label: 'Cancel', hint: 'discard all changes' },
685
816
  ],
@@ -707,6 +838,9 @@ export async function runConfigCommand() {
707
838
  case 'credentials':
708
839
  await handleServerCredentials(resolved, pending);
709
840
  break;
841
+ case 'dockerAgent':
842
+ await handleDockerAgent(resolved, pending);
843
+ break;
710
844
  case 'cancel':
711
845
  p.cancel('Changes discarded.');
712
846
  return;