@provos/ironcurtain 0.5.1 → 0.6.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 (74) hide show
  1. package/README.md +66 -66
  2. package/dist/cli.js +9 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config/types.d.ts +6 -0
  5. package/dist/docker/adapters/claude-code.js +1 -1
  6. package/dist/docker/adapters/claude-code.js.map +1 -1
  7. package/dist/docker/docker-infrastructure.js +10 -3
  8. package/dist/docker/docker-infrastructure.js.map +1 -1
  9. package/dist/docker/mitm-proxy.d.ts +5 -2
  10. package/dist/docker/mitm-proxy.js +169 -46
  11. package/dist/docker/mitm-proxy.js.map +1 -1
  12. package/dist/docker/oauth-credentials.d.ts +15 -2
  13. package/dist/docker/oauth-credentials.js +92 -8
  14. package/dist/docker/oauth-credentials.js.map +1 -1
  15. package/dist/docker/oauth-token-manager.d.ts +80 -0
  16. package/dist/docker/oauth-token-manager.js +133 -0
  17. package/dist/docker/oauth-token-manager.js.map +1 -0
  18. package/dist/docker/pty-session.d.ts +2 -0
  19. package/dist/docker/pty-session.js +10 -4
  20. package/dist/docker/pty-session.js.map +1 -1
  21. package/dist/escalation/escalation-watcher.js +2 -0
  22. package/dist/escalation/escalation-watcher.js.map +1 -1
  23. package/dist/escalation/listener-command.js +9 -58
  24. package/dist/escalation/listener-command.js.map +1 -1
  25. package/dist/escalation/listener-lock.d.ts +20 -0
  26. package/dist/escalation/listener-lock.js +69 -0
  27. package/dist/escalation/listener-lock.js.map +1 -0
  28. package/dist/index.js +21 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/mux/mux-app.d.ts +24 -0
  31. package/dist/mux/mux-app.js +445 -0
  32. package/dist/mux/mux-app.js.map +1 -0
  33. package/dist/mux/mux-command.d.ts +7 -0
  34. package/dist/mux/mux-command.js +93 -0
  35. package/dist/mux/mux-command.js.map +1 -0
  36. package/dist/mux/mux-escalation-manager.d.ts +48 -0
  37. package/dist/mux/mux-escalation-manager.js +162 -0
  38. package/dist/mux/mux-escalation-manager.js.map +1 -0
  39. package/dist/mux/mux-input-handler.d.ts +49 -0
  40. package/dist/mux/mux-input-handler.js +457 -0
  41. package/dist/mux/mux-input-handler.js.map +1 -0
  42. package/dist/mux/mux-renderer.d.ts +86 -0
  43. package/dist/mux/mux-renderer.js +758 -0
  44. package/dist/mux/mux-renderer.js.map +1 -0
  45. package/dist/mux/mux-splash.d.ts +17 -0
  46. package/dist/mux/mux-splash.js +322 -0
  47. package/dist/mux/mux-splash.js.map +1 -0
  48. package/dist/mux/pty-bridge.d.ts +79 -0
  49. package/dist/mux/pty-bridge.js +151 -0
  50. package/dist/mux/pty-bridge.js.map +1 -0
  51. package/dist/mux/trusted-input.d.ts +27 -0
  52. package/dist/mux/trusted-input.js +27 -0
  53. package/dist/mux/trusted-input.js.map +1 -0
  54. package/dist/mux/types.d.ts +92 -0
  55. package/dist/mux/types.js +45 -0
  56. package/dist/mux/types.js.map +1 -0
  57. package/dist/sandbox/index.js +4 -0
  58. package/dist/sandbox/index.js.map +1 -1
  59. package/dist/session/agent-session.js +3 -0
  60. package/dist/session/agent-session.js.map +1 -1
  61. package/dist/session/index.js +14 -7
  62. package/dist/session/index.js.map +1 -1
  63. package/dist/session/types.d.ts +6 -0
  64. package/dist/session/workspace-validation.d.ts +17 -0
  65. package/dist/session/workspace-validation.js +65 -0
  66. package/dist/session/workspace-validation.js.map +1 -0
  67. package/dist/trusted-process/auto-approver.d.ts +13 -4
  68. package/dist/trusted-process/auto-approver.js +12 -5
  69. package/dist/trusted-process/auto-approver.js.map +1 -1
  70. package/dist/trusted-process/escalation.js +5 -0
  71. package/dist/trusted-process/escalation.js.map +1 -1
  72. package/dist/trusted-process/mcp-proxy-server.js +39 -16
  73. package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
  74. package/package.json +6 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * CLI entry point for `ironcurtain mux`.
3
+ *
4
+ * Parses command-line options, loads config, acquires the listener lock,
5
+ * creates the MuxApp, and runs until quit.
6
+ */
7
+ import chalk from 'chalk';
8
+ import { mkdirSync } from 'node:fs';
9
+ import { getListenerLockPath, getPtyRegistryDir } from '../config/paths.js';
10
+ import { acquireLock, releaseLock } from '../escalation/listener-lock.js';
11
+ import { loadUserConfig } from '../config/user-config.js';
12
+ import { parseModelId, resolveApiKeyForProvider } from '../config/model-provider.js';
13
+ import { loadConfig } from '../config/index.js';
14
+ import { createMuxApp } from './mux-app.js';
15
+ export async function main(args) {
16
+ // Check for optional mux dependencies
17
+ try {
18
+ await import('node-pty');
19
+ }
20
+ catch {
21
+ process.stderr.write(chalk.red('Error: ironcurtain mux requires the node-pty package.\n') + 'Install it with: npm install node-pty\n');
22
+ process.exit(1);
23
+ }
24
+ try {
25
+ await import('terminal-kit');
26
+ }
27
+ catch {
28
+ process.stderr.write(chalk.red('Error: ironcurtain mux requires the terminal-kit package.\n') +
29
+ 'Install it with: npm install terminal-kit\n');
30
+ process.exit(1);
31
+ }
32
+ // Parse args
33
+ let agent = 'claude-code';
34
+ if (args) {
35
+ for (let i = 0; i < args.length; i++) {
36
+ if ((args[i] === '--agent' || args[i] === '-a') && args[i + 1]) {
37
+ agent = args[i + 1];
38
+ i++;
39
+ }
40
+ }
41
+ }
42
+ // Acquire single-instance lock
43
+ const lockPath = getListenerLockPath();
44
+ if (!acquireLock(lockPath)) {
45
+ process.stderr.write(chalk.red('Another escalation listener or mux is already running.\n') +
46
+ 'Only one instance can run at a time to prevent escalation conflicts.\n');
47
+ process.exit(1);
48
+ }
49
+ // Ensure registry directory exists
50
+ const registryDir = getPtyRegistryDir();
51
+ mkdirSync(registryDir, { recursive: true, mode: 0o700 });
52
+ // Pre-flight warnings (shown before fullscreen takes over)
53
+ let hasWarnings = false;
54
+ try {
55
+ const userConfig = loadUserConfig({ readOnly: true });
56
+ if (userConfig.autoApprove.enabled) {
57
+ const { provider } = parseModelId(userConfig.autoApprove.modelId);
58
+ const apiKey = resolveApiKeyForProvider(provider, userConfig);
59
+ if (!apiKey) {
60
+ process.stderr.write(chalk.yellow(`Warning: auto-approve is enabled but no API key found for provider "${provider}".\n` +
61
+ 'Auto-approve will be silently disabled. Set the API key in your environment or config.\n'));
62
+ hasWarnings = true;
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ // Config load failure is not fatal here -- the child sessions will report it.
68
+ }
69
+ if (hasWarnings) {
70
+ process.stderr.write(chalk.dim('\nStarting in 3 seconds...\n'));
71
+ await new Promise((r) => setTimeout(r, 3000));
72
+ }
73
+ // Load config once for workspace validation
74
+ let protectedPaths = [];
75
+ try {
76
+ protectedPaths = loadConfig().protectedPaths;
77
+ }
78
+ catch {
79
+ // Config load failure is not fatal; child sessions will report it.
80
+ }
81
+ try {
82
+ const app = createMuxApp({
83
+ agent,
84
+ autoSpawn: false,
85
+ protectedPaths,
86
+ });
87
+ await app.start();
88
+ }
89
+ finally {
90
+ releaseLock(lockPath);
91
+ }
92
+ }
93
+ //# sourceMappingURL=mux-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mux-command.js","sourceRoot":"","sources":["../../src/mux/mux-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * MuxEscalationManager -- manages escalation watchers for all PTY sessions.
3
+ *
4
+ * Wraps the existing EscalationWatcher and ListenerState modules for the
5
+ * mux context. Creates watchers directly when sessions are spawned, and
6
+ * optionally polls the PTY registry for externally-spawned sessions.
7
+ */
8
+ import type { PtySessionRegistration } from '../docker/pty-types.js';
9
+ import { type ListenerState } from '../escalation/listener-state.js';
10
+ export interface MuxEscalationManager {
11
+ /** Current state (sessions, pending escalations, history). */
12
+ readonly state: ListenerState;
13
+ /** Number of pending escalations across all sessions. */
14
+ readonly pendingCount: number;
15
+ /**
16
+ * Registers a session for escalation watching.
17
+ */
18
+ addSession(registration: PtySessionRegistration): void;
19
+ /**
20
+ * Removes a session's watcher.
21
+ */
22
+ removeSession(sessionId: string): void;
23
+ /** Resolves a pending escalation by display number. */
24
+ resolve(displayNumber: number, decision: 'approved' | 'denied'): string;
25
+ /** Resolves all pending escalations. */
26
+ resolveAll(decision: 'approved' | 'denied'): string;
27
+ /** Starts polling for externally-spawned sessions. */
28
+ startRegistryPolling(): void;
29
+ /** Stops all watchers and polling. */
30
+ stop(): void;
31
+ /** Register callback for state changes (triggers redraws). */
32
+ onChange(callback: () => void): void;
33
+ /**
34
+ * Registers a callback fired when registry polling discovers a new
35
+ * external session. Used to back-fill bridge registrations that
36
+ * timed out during initial discovery.
37
+ */
38
+ onSessionDiscovered(callback: (reg: PtySessionRegistration) => void): void;
39
+ /**
40
+ * Marks a session ID as managed so registry polling treats it as
41
+ * owned by the mux (won't re-add after removal).
42
+ */
43
+ claimSession(sessionId: string): void;
44
+ }
45
+ /**
46
+ * Creates a new MuxEscalationManager.
47
+ */
48
+ export declare function createMuxEscalationManager(): MuxEscalationManager;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * MuxEscalationManager -- manages escalation watchers for all PTY sessions.
3
+ *
4
+ * Wraps the existing EscalationWatcher and ListenerState modules for the
5
+ * mux context. Creates watchers directly when sessions are spawned, and
6
+ * optionally polls the PTY registry for externally-spawned sessions.
7
+ */
8
+ import { mkdirSync } from 'node:fs';
9
+ import { getPtyRegistryDir } from '../config/paths.js';
10
+ import { readActiveRegistrations } from '../escalation/session-registry.js';
11
+ import { createEscalationWatcher } from '../escalation/escalation-watcher.js';
12
+ import { createInitialState, addSession, removeSession, addEscalation, resolveEscalation, expireEscalation, } from '../escalation/listener-state.js';
13
+ /** Poll interval for the session registry directory (ms). */
14
+ const REGISTRY_POLL_INTERVAL_MS = 1000;
15
+ /**
16
+ * Creates a new MuxEscalationManager.
17
+ */
18
+ export function createMuxEscalationManager() {
19
+ let state = createInitialState();
20
+ const changeCallbacks = [];
21
+ const discoveryCallbacks = [];
22
+ let registryPollInterval = null;
23
+ // Track session IDs that we manage (spawned by mux)
24
+ const managedSessionIds = new Set();
25
+ // Tombstones: removed sessions that may still linger in the registry briefly
26
+ const removedSessionIds = new Set();
27
+ function notifyChange() {
28
+ for (const cb of changeCallbacks)
29
+ cb();
30
+ }
31
+ function createWatcherForSession(sessionId, escalationDir) {
32
+ return createEscalationWatcher(escalationDir, {
33
+ onEscalation: (request) => {
34
+ state = addEscalation(state, sessionId, request);
35
+ notifyChange();
36
+ // BEL to alert user
37
+ process.stderr.write('\x07');
38
+ },
39
+ onEscalationExpired: (escalationId) => {
40
+ state = expireEscalation(state, sessionId, escalationId);
41
+ notifyChange();
42
+ },
43
+ }, { pollIntervalMs: 300 });
44
+ }
45
+ return {
46
+ get state() {
47
+ return state;
48
+ },
49
+ get pendingCount() {
50
+ return state.pendingEscalations.size;
51
+ },
52
+ addSession(registration) {
53
+ managedSessionIds.add(registration.sessionId);
54
+ const watcher = createWatcherForSession(registration.sessionId, registration.escalationDir);
55
+ state = addSession(state, registration, watcher);
56
+ watcher.start();
57
+ notifyChange();
58
+ },
59
+ removeSession(sessionId) {
60
+ const session = state.sessions.get(sessionId);
61
+ session?.watcher.stop();
62
+ removedSessionIds.add(sessionId);
63
+ state = removeSession(state, sessionId);
64
+ notifyChange();
65
+ },
66
+ resolve(displayNumber, decision) {
67
+ const escalation = state.pendingEscalations.get(displayNumber);
68
+ if (!escalation) {
69
+ return `No pending escalation #${displayNumber}`;
70
+ }
71
+ const session = state.sessions.get(escalation.sessionId);
72
+ if (!session) {
73
+ return `Session for escalation #${displayNumber} no longer active`;
74
+ }
75
+ try {
76
+ const accepted = session.watcher.resolve(escalation.request.escalationId, decision);
77
+ state = resolveEscalation(state, displayNumber, decision);
78
+ notifyChange();
79
+ if (!accepted) {
80
+ return `Escalation #${displayNumber} expired (response was too late)`;
81
+ }
82
+ const label = decision === 'approved' ? 'APPROVED' : 'DENIED';
83
+ return `Escalation #${displayNumber} ${label}`;
84
+ }
85
+ catch (err) {
86
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
87
+ }
88
+ },
89
+ resolveAll(decision) {
90
+ if (state.pendingEscalations.size === 0) {
91
+ return 'No pending escalations';
92
+ }
93
+ const messages = [];
94
+ const nums = [...state.pendingEscalations.keys()].sort((a, b) => a - b);
95
+ for (const num of nums) {
96
+ messages.push(this.resolve(num, decision));
97
+ }
98
+ return messages.join('\n');
99
+ },
100
+ startRegistryPolling() {
101
+ if (registryPollInterval)
102
+ return;
103
+ const registryDir = getPtyRegistryDir();
104
+ mkdirSync(registryDir, { recursive: true, mode: 0o700 });
105
+ registryPollInterval = setInterval(() => {
106
+ const registrations = readActiveRegistrations(registryDir);
107
+ const currentIds = new Set(registrations.map((r) => r.sessionId));
108
+ const stateIds = new Set(state.sessions.keys());
109
+ let changed = false;
110
+ // Add externally-spawned sessions (skip managed, tombstoned)
111
+ for (const reg of registrations) {
112
+ if (!stateIds.has(reg.sessionId) &&
113
+ !managedSessionIds.has(reg.sessionId) &&
114
+ !removedSessionIds.has(reg.sessionId)) {
115
+ const watcher = createWatcherForSession(reg.sessionId, reg.escalationDir);
116
+ state = addSession(state, reg, watcher);
117
+ watcher.start();
118
+ changed = true;
119
+ for (const cb of discoveryCallbacks)
120
+ cb(reg);
121
+ }
122
+ }
123
+ // Clear tombstones once the registry entry disappears
124
+ for (const id of removedSessionIds) {
125
+ if (!currentIds.has(id)) {
126
+ removedSessionIds.delete(id);
127
+ }
128
+ }
129
+ // Remove gone external sessions
130
+ for (const id of stateIds) {
131
+ if (!currentIds.has(id) && !managedSessionIds.has(id)) {
132
+ const session = state.sessions.get(id);
133
+ session?.watcher.stop();
134
+ state = removeSession(state, id);
135
+ changed = true;
136
+ }
137
+ }
138
+ if (changed)
139
+ notifyChange();
140
+ }, REGISTRY_POLL_INTERVAL_MS);
141
+ },
142
+ stop() {
143
+ if (registryPollInterval) {
144
+ clearInterval(registryPollInterval);
145
+ registryPollInterval = null;
146
+ }
147
+ for (const session of state.sessions.values()) {
148
+ session.watcher.stop();
149
+ }
150
+ },
151
+ onChange(callback) {
152
+ changeCallbacks.push(callback);
153
+ },
154
+ onSessionDiscovered(callback) {
155
+ discoveryCallbacks.push(callback);
156
+ },
157
+ claimSession(sessionId) {
158
+ managedSessionIds.add(sessionId);
159
+ },
160
+ };
161
+ }
162
+ //# sourceMappingURL=mux-escalation-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mux-escalation-manager.js","sourceRoot":"","sources":["../../src/mux/mux-escalation-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * MuxInputHandler -- key routing state machine for the terminal multiplexer.
3
+ *
4
+ * Two input modes:
5
+ * - PTY mode: all keystrokes go to the active PtyBridge except Ctrl-A
6
+ * - Command mode: keystrokes collected in a line buffer, dispatched on Enter
7
+ *
8
+ * Ctrl-A toggles between modes (following screen/tmux convention).
9
+ * Pressing Ctrl-A in PTY mode enters command mode; pressing it again
10
+ * returns to PTY mode.
11
+ */
12
+ import type { InputMode, MuxAction } from './types.js';
13
+ export type PickerPhase = 'menu' | 'browse';
14
+ export interface PickerState {
15
+ phase: PickerPhase;
16
+ menuSelection: number;
17
+ inputPath: string;
18
+ cursorPos: number;
19
+ entries: string[];
20
+ selectedIndex: number;
21
+ scrollOffset: number;
22
+ inList: boolean;
23
+ error: string | null;
24
+ }
25
+ export interface MuxInputHandler {
26
+ /** Current input mode. */
27
+ readonly mode: InputMode;
28
+ /** Current command-mode input buffer (for display). */
29
+ readonly inputBuffer: string;
30
+ /** Cursor position within the input buffer. */
31
+ readonly cursorPos: number;
32
+ /** Current picker state (null when not in picker mode). */
33
+ readonly pickerState: PickerState | null;
34
+ /** Enter picker mode (called by /new command handler). */
35
+ enterPickerMode(): void;
36
+ /** Re-enter picker browse phase with a validation error. */
37
+ enterBrowseWithError(path: string, error: string): void;
38
+ /** Exit picker mode and return to PTY mode (no side effects). */
39
+ exitPickerMode(): void;
40
+ /**
41
+ * Processes a key event from terminal-kit.
42
+ * Returns a MuxAction describing what the orchestrator should do.
43
+ */
44
+ handleKey(key: string): MuxAction;
45
+ }
46
+ export interface MuxInputHandlerOptions {
47
+ initialMode?: InputMode;
48
+ }
49
+ export declare function createMuxInputHandler(options?: MuxInputHandlerOptions): MuxInputHandler;