cli-remote-agent 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/setup.ts","../../shared/src/types.ts","../../shared/src/protocol.ts","../../shared/src/constants.ts","../../shared/src/index.ts","../src/logger.ts","../src/claude-plugin-installer.ts","../src/local-control.ts","../src/tmux.ts","../src/shell.ts","../src/pty-session.ts","../src/terminal-manager.ts","../src/heartbeat.ts","../src/file-explorer.ts","../src/vpn-manager.ts","../src/claude-path.ts","../src/claude-sessions.ts","../src/claude-conversation.ts","../src/index.ts","../src/cli.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport crypto from 'crypto';\n\nexport interface VpnProfileConfig {\n id: string;\n name: string;\n type: 'wireguard' | 'openvpn' | 'azure';\n configFile?: string; // path to .conf/.ovpn — absolute or relative to ~/.crc-agent/vpn/\n tunnelName?: string; // WireGuard: tunnel service name (defaults to id)\n serviceName?: string; // macOS: network service name from `scutil --nc list` (e.g., \"My Server\")\n tunnelblickName?: string; // macOS OpenVPN: Tunnelblick configuration name (from `osascript -e 'tell application \"Tunnelblick\" to get name of configurations'`)\n}\n\nexport interface AgentConfig {\n agentId: string;\n serverUrl: string;\n secret: string;\n shell: string;\n homeDir?: string;\n vpn?: {\n profiles: VpnProfileConfig[];\n };\n}\n\nexport const CONFIG_DIR = path.join(os.homedir(), '.crc-agent');\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\n\n// Legacy placeholder secret from older default configs — treated as \"not configured\".\nconst PLACEHOLDER_SECRET = 'changeme';\n\n// Loopback port the agent listens on for Claude Code hook callbacks (so events\n// fired while Claude runs in Warp/tmux reach the agent -> server -> push).\nexport const LOCAL_CONTROL_PORT = Number(process.env.CRC_LOCAL_PORT) || 47600;\n\nexport function defaultAgentId(): string {\n return os.hostname().toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\nfunction defaultConfig(): AgentConfig {\n return {\n agentId: defaultAgentId(),\n // No serverUrl by default — the agent must be enrolled via `crc-agent setup`.\n serverUrl: '',\n // Random secret so an unconfigured install never ships a known secret.\n secret: crypto.randomBytes(24).toString('hex'),\n shell: 'auto',\n };\n}\n\n/**\n * True only when a real, usable config exists: file present, parseable, with a\n * non-empty serverUrl and a secret that isn't the legacy placeholder.\n */\nexport function isConfigured(): boolean {\n if (!fs.existsSync(CONFIG_FILE)) return false;\n try {\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n const cfg = JSON.parse(raw.replace(/^/, '')) as Partial<AgentConfig>;\n if (!cfg.serverUrl || !cfg.secret) return false;\n if (cfg.secret === PLACEHOLDER_SECRET) return false;\n return true;\n } catch {\n return false;\n }\n}\n\nexport function loadConfig(): AgentConfig {\n if (!fs.existsSync(CONFIG_FILE)) {\n const config = defaultConfig();\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));\n console.log(`Created default config at ${CONFIG_FILE} — run \\`crc-agent setup\\` to enroll.`);\n return config;\n }\n\n try {\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\n // Strip a leading UTF-8 BOM (e.g., files saved by Windows Notepad) before parsing.\n return JSON.parse(raw.replace(/^/, '')) as AgentConfig;\n } catch (err) {\n console.error(\n `Failed to read/parse config at ${CONFIG_FILE}: ${(err as Error).message}\\n` +\n 'Falling back to default config. Fix the file and restart to apply your settings.'\n );\n return defaultConfig();\n }\n}\n","#!/usr/bin/env node\nimport fs from 'fs';\nimport readline from 'readline';\nimport { CONFIG_DIR, CONFIG_FILE, defaultAgentId, type AgentConfig } from './config.js';\n\ninterface EnrollToken {\n v?: number;\n serverUrl: string;\n agentId: string;\n secret: string;\n}\n\nfunction parseArgs(argv: string[]): { token?: string; help?: boolean } {\n const out: { token?: string; help?: boolean } = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === '--token' || a === '-t') {\n out.token = argv[++i];\n } else if (a.startsWith('--token=')) {\n out.token = a.slice('--token='.length);\n } else if (a === '--help' || a === '-h') {\n out.help = true;\n }\n }\n return out;\n}\n\nfunction decodeToken(token: string): EnrollToken {\n // base64url JSON — {v, serverUrl, agentId, secret}\n const json = Buffer.from(token, 'base64url').toString('utf-8');\n let parsed: any;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error('Invalid token: not valid base64url-encoded JSON.');\n }\n if (!parsed || typeof parsed !== 'object') throw new Error('Invalid token payload.');\n if (!parsed.serverUrl || !parsed.agentId || !parsed.secret) {\n throw new Error('Invalid token: missing serverUrl, agentId, or secret.');\n }\n return parsed as EnrollToken;\n}\n\nfunction ask(rl: readline.Interface, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, (answer) => resolve(answer)));\n}\n\nfunction writeConfig(cfg: AgentConfig): void {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));\n console.log(`Saved. Start the agent with: crc-agent`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv.slice(2));\n\n if (args.help) {\n console.log(\n [\n 'Usage: crc-agent setup [--token <base64url>]',\n '',\n 'Enroll this machine as a Claude Remote agent.',\n '',\n 'Modes:',\n ' --token <token> Enroll from the token shown in the web \"Add agent\" dialog.',\n ' (no args) Interactive prompts for serverUrl, agentId, secret.',\n '',\n 'Environment overrides (skip prompts): CRC_SERVER_URL, CRC_AGENT_ID, CRC_SECRET',\n '',\n `Config is written to ${CONFIG_FILE}`,\n ].join('\\n')\n );\n return;\n }\n\n const exists = fs.existsSync(CONFIG_FILE);\n\n // 1) Token mode — overwrites without prompting.\n if (args.token) {\n const t = decodeToken(args.token);\n writeConfig({ agentId: t.agentId, serverUrl: t.serverUrl, secret: t.secret, shell: 'auto' });\n return;\n }\n\n // 2) Env-override mode — if all three are present, overwrite without prompting.\n const envServer = process.env.CRC_SERVER_URL;\n const envAgent = process.env.CRC_AGENT_ID;\n const envSecret = process.env.CRC_SECRET;\n if (envServer && envSecret) {\n writeConfig({\n agentId: envAgent || defaultAgentId(),\n serverUrl: envServer,\n secret: envSecret,\n shell: 'auto',\n });\n return;\n }\n\n // 3) Interactive mode.\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n if (exists) {\n const overwrite = await ask(rl, `Config already exists at ${CONFIG_FILE}. Overwrite? (y/N) `);\n if (!/^y(es)?$/i.test(overwrite.trim())) {\n console.log('Aborted. Existing config left unchanged.');\n return;\n }\n }\n\n const serverUrl = (envServer || (await ask(rl, 'Server URL (e.g. wss://crc.example.com): '))).trim();\n if (!serverUrl) throw new Error('Server URL is required.');\n\n const agentIdRaw = envAgent || (await ask(rl, `Agent ID [${defaultAgentId()}]: `));\n const agentId = (agentIdRaw || '').trim() || defaultAgentId();\n\n const secret = (envSecret || (await ask(rl, 'Agent secret: '))).trim();\n if (!secret) throw new Error('Secret is required.');\n\n writeConfig({ agentId, serverUrl, secret, shell: 'auto' });\n } finally {\n rl.close();\n }\n}\n\nmain().catch((err) => {\n console.error(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n});\n","// --- Core ---\n\nexport interface AgentCapabilities {\n terminal: boolean;\n fileTransfer: boolean;\n}\n\nexport interface AgentInfo {\n id: string;\n name: string;\n hostname: string;\n platform: 'win32' | 'darwin' | 'linux';\n arch: string;\n status: 'online' | 'offline';\n cpuUsage: number;\n memoryUsage: number;\n uptime: number;\n activeSessions: number;\n lastSeen: number;\n pathSeparator: '\\\\' | '/';\n homeDirectory: string;\n rootPaths: string[];\n capabilities: AgentCapabilities;\n}\n\nexport type SessionStatus = 'attached' | 'detached' | 'dead';\n\nexport interface TerminalSession {\n id: string;\n agentId: string;\n name: string;\n status: SessionStatus;\n cols: number;\n rows: number;\n createdAt: number;\n lastAttachedAt: number;\n}\n\nexport interface HeartbeatPayload {\n hostname: string;\n platform: 'win32' | 'darwin' | 'linux';\n arch: string;\n cpuUsage: number;\n memoryUsage: number;\n uptime: number;\n activeSessions: number;\n pathSeparator: '\\\\' | '/';\n homeDirectory: string;\n rootPaths: string[];\n capabilities: AgentCapabilities;\n}\n\n// --- VPN ---\n\nexport type VpnType = 'wireguard' | 'openvpn' | 'azure';\nexport type VpnStatus = 'connected' | 'disconnected' | 'connecting' | 'disconnecting' | 'error';\n\nexport interface VpnProfile {\n id: string;\n name: string;\n type: VpnType;\n status: VpnStatus;\n error?: string;\n}\n\n// --- File Explorer ---\n\nexport interface FileEntry {\n name: string;\n isDirectory: boolean;\n size: number;\n modified: number;\n}\n\n// --- Claude Sessions ---\n\nexport interface ClaudeSessionInfo {\n sessionId: string;\n projectPath: string;\n firstMessage: string; // first user message (truncated)\n lastTimestamp: string; // ISO 8601\n messageCount: number;\n model?: string;\n slug?: string;\n gitBranch?: string;\n}\n","// Socket.IO event names — the contract between all components\n\n// --- Agent -> Server ---\nexport const AGENT_HEARTBEAT = 'agent:heartbeat' as const;\nexport const TERMINAL_OUTPUT = 'terminal:output' as const;\nexport const TERMINAL_EXIT = 'terminal:exit' as const;\n\n// --- Server -> Agent ---\nexport const TERMINAL_OPEN = 'terminal:open' as const;\nexport const TERMINAL_INPUT = 'terminal:input' as const;\nexport const TERMINAL_RESIZE = 'terminal:resize' as const;\nexport const TERMINAL_CLOSE = 'terminal:close' as const;\n\n// --- Server -> Client ---\nexport const AGENTS_UPDATE = 'agents:update' as const;\nexport const TERMINAL_READY = 'terminal:ready' as const;\n\n// --- Session lifecycle events ---\n// Client -> Server\nexport const SESSION_LIST = 'session:list' as const;\nexport const SESSION_CREATE = 'session:create' as const;\nexport const SESSION_ATTACH = 'session:attach' as const;\nexport const SESSION_DETACH = 'session:detach' as const;\nexport const SESSION_RENAME = 'session:rename' as const;\nexport const SESSION_KILL = 'session:kill' as const;\nexport const SESSION_KILL_ALL = 'session:killAll' as const;\n\n// Server -> Client\nexport const SESSIONS_UPDATE = 'sessions:update' as const;\nexport const SESSION_BUFFER = 'session:buffer' as const;\nexport const SESSION_DETACHED = 'session:detached' as const;\n\n// Server -> Agent\nexport const SESSION_SYNC = 'session:sync' as const;\n\n// Agent -> Server\nexport const SESSION_SYNC_RESULT = 'session:sync:result' as const;\n\n// --- VPN Events ---\n// Client -> Server\nexport const VPN_LIST = 'vpn:list' as const;\nexport const VPN_CONNECT = 'vpn:connect' as const;\nexport const VPN_DISCONNECT = 'vpn:disconnect' as const;\n\n// Server -> Client / Agent -> Server\nexport const VPN_UPDATE = 'vpn:update' as const;\n\n// --- File Explorer Events ---\n// Client -> Server\nexport const FILES_LIST = 'files:list' as const;\nexport const FILES_DOWNLOAD = 'files:download' as const;\n\n// Server -> Agent / Agent -> Server\nexport const FILES_LIST_RESULT = 'files:list:result' as const;\nexport const FILES_DOWNLOAD_READY = 'files:download:ready' as const;\nexport const FILES_DOWNLOAD_ERROR = 'files:download:error' as const;\n\n// --- Agent Exec (one-shot command) ---\n// Client -> Server -> Agent\nexport const AGENT_EXEC = 'agent:exec' as const;\n// Agent -> Server -> Client\nexport const AGENT_EXEC_RESULT = 'agent:exec:result' as const;\n\n// --- Claude Conversation (live JSONL reading) ---\n// Client -> Server -> Agent\nexport const CLAUDE_CONV_READ = 'claude:conv:read' as const;\n// Agent -> Server -> Client\nexport const CLAUDE_CONV_DATA = 'claude:conv:data' as const;\n\n// --- Claude Sessions Events ---\n// Client -> Server\nexport const CLAUDE_SESSIONS_LIST = 'claude:sessions:list' as const;\n// Agent -> Server -> Client\nexport const CLAUDE_SESSIONS_RESULT = 'claude:sessions:result' as const;\n\n// --- Claude hook events (from the agent's Claude Code hooks, fired even when\n// Claude runs outside a CRC terminal — e.g. in Warp/tmux) ---\n// Agent -> Server\nexport const CLAUDE_HOOK = 'claude:hook' as const;\n// Server -> Client (in-app toast for connected clients; push covers closed apps)\nexport const CLAUDE_NOTIFY = 'claude:notify' as const;\n\n// --- Shared tmux sessions (mirror what runs in Warp/tmux on the PC) ---\n// Client -> Server -> Agent\nexport const TMUX_LIST = 'tmux:list' as const;\n// Agent -> Server -> Client\nexport const TMUX_LIST_RESULT = 'tmux:list:result' as const;\n\n// --- Payload types ---\nexport interface TerminalOutputPayload {\n sessionId: string;\n data: string;\n}\n\nexport interface TerminalExitPayload {\n sessionId: string;\n exitCode: number;\n sessionName?: string;\n agentId?: string;\n}\n\nexport interface TerminalOpenPayload {\n sessionId?: string;\n agentId?: string;\n cols: number;\n rows: number;\n tmux?: string;\n launch?: string;\n}\n\nexport interface TerminalInputPayload {\n sessionId: string;\n data: string;\n}\n\nexport interface TerminalResizePayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface TerminalClosePayload {\n sessionId: string;\n}\n\nexport interface TerminalReadyPayload {\n sessionId: string;\n}\n\nexport interface SessionListPayload {\n agentId: string;\n}\n\nexport interface SessionCreatePayload {\n agentId: string;\n name?: string;\n cols: number;\n rows: number;\n // When set, the PTY attaches to (or creates) this tmux session instead of a\n // plain shell — so the session is mirrored with Warp/tmux on the PC.\n tmux?: string;\n // Command to run if the tmux session is newly created (e.g. 'claude').\n launch?: string;\n}\n\nexport interface SessionAttachPayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface SessionDetachPayload {\n sessionId: string;\n}\n\nexport interface SessionRenamePayload {\n sessionId: string;\n name: string;\n}\n\nexport interface SessionKillPayload {\n sessionId: string;\n}\n\nexport interface SessionKillAllPayload {\n agentId: string;\n}\n\nexport interface SessionBufferPayload {\n sessionId: string;\n data: string;\n}\n\nexport interface SessionDetachedPayload {\n sessionId: string;\n reason: string;\n}\n\nexport interface SessionSyncResultPayload {\n sessionIds: string[];\n}\n\n// --- VPN Payloads ---\n\nexport interface VpnListPayload {\n agentId: string;\n}\n\nexport interface VpnConnectPayload {\n agentId?: string;\n profileId: string;\n}\n\nexport interface VpnDisconnectPayload {\n agentId?: string;\n profileId: string;\n}\n\nexport interface VpnUpdatePayload {\n agentId?: string;\n profiles: import('./types.js').VpnProfile[];\n}\n\n// --- Agent Exec Payloads ---\n\nexport interface AgentExecPayload {\n agentId?: string;\n requestId?: string;\n command: string;\n cwd: string;\n}\n\nexport interface AgentExecResultPayload {\n requestId: string;\n agentId?: string;\n stdout: string;\n stderr: string;\n error?: string;\n}\n\n// --- Claude Conversation Payloads ---\n\nexport interface ClaudeConvMessage {\n type: 'user' | 'assistant' | 'tool_use' | 'tool_result';\n content: string;\n toolName?: string;\n toolId?: string;\n timestamp?: string;\n model?: string;\n}\n\nexport interface ClaudeConvReadPayload {\n agentId?: string;\n projectPath: string;\n sessionId?: string;\n afterLine?: number;\n}\n\nexport interface ClaudeConvDataPayload {\n agentId?: string;\n sessionId: string;\n messages: ClaudeConvMessage[];\n totalLines: number;\n error?: string;\n}\n\n// --- Claude Sessions Payloads ---\n\nexport interface ClaudeSessionsListPayload {\n agentId?: string;\n projectPath?: string; // optional: filter to one project. If omitted, list all.\n}\n\nexport interface ClaudeSessionsResultPayload {\n agentId?: string;\n sessions: import('./types.js').ClaudeSessionInfo[];\n error?: string;\n}\n\nexport interface ClaudeHookPayload {\n agentId?: string;\n event: string; // 'stop' | 'idle_prompt' | 'permission_request' | 'prompt_submit' | 'tool_complete'\n query?: string;\n response?: string;\n summary?: string;\n tool_name?: string;\n // Where the turn happened, so a notification can deep-link to the transcript.\n projectPath?: string;\n claudeSessionId?: string;\n}\n\nexport interface ClaudeNotifyPayload {\n agentId?: string;\n event: string;\n title: string;\n body: string;\n}\n\n// --- Shared tmux session payloads ---\n\nexport interface TmuxSessionInfo {\n name: string;\n windows: number;\n attached: boolean;\n activity?: number; // last-activity unix seconds\n}\n\nexport interface TmuxListPayload {\n agentId?: string;\n requestId?: string;\n}\n\nexport interface TmuxListResultPayload {\n requestId?: string;\n agentId?: string;\n sessions: TmuxSessionInfo[];\n error?: string;\n}\n\n// --- File Explorer Payloads ---\n\nexport interface FilesListPayload {\n agentId?: string;\n requestId?: string;\n path: string;\n}\n\nexport interface FilesListResultPayload {\n requestId: string;\n agentId?: string;\n path: string;\n entries: import('./types.js').FileEntry[];\n error?: string;\n}\n\nexport interface FilesDownloadPayload {\n agentId?: string;\n requestId?: string;\n path: string;\n}\n\nexport interface FilesDownloadReadyPayload {\n requestId: string;\n agentId?: string;\n fileId: string;\n fileName: string;\n downloadUrl: string;\n size: number;\n}\n\nexport interface FilesDownloadErrorPayload {\n requestId: string;\n agentId?: string;\n path?: string;\n error: string;\n}\n","export const HEARTBEAT_INTERVAL = 15_000;\nexport const HEARTBEAT_TIMEOUT = 45_000;\nexport const SESSION_BUFFER_SIZE = 50_000;\nexport const SESSION_MAX_PER_AGENT = 10;\nexport const FILE_TTL = 3_600_000;\nexport const FILE_MAX_SIZE = 524_288_000;\nexport const FILE_CLEANUP_INTERVAL = 600_000;\nexport const FILE_MAX_STORAGE = 2_147_483_648;\n","export * from './types.js';\nexport * from './protocol.js';\nexport * from './constants.js';\n","import pino from 'pino';\n\nexport const logger = pino({\n level: 'info',\n transport: { target: 'pino-pretty', options: { colorize: true } },\n});\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { logger } from './logger.js';\nimport type { ClaudeHookPayload } from '@crc/shared';\n\n// We install notify hooks by writing them directly into ~/.claude/settings.json\n// (merged, idempotent). This is far more reliable than the plugin mechanism —\n// plugins only load from a registered marketplace, whereas user-settings hooks\n// always load. Works on macOS, Linux, and Windows (Claude runs command hooks via\n// Git Bash there); needs only curl (present on all three).\n\nfunction getSettingsPath(): string {\n const home = process.env.HOME || process.env.USERPROFILE || '';\n return join(home, '.claude', 'settings.json');\n}\n\n// One alert per task: install only the Stop hook (\"Claude finished\"). We also\n// clean up any Notification/SubagentStop hooks earlier versions installed, so\n// they stop firing the duplicate \"waiting\"/permission alerts.\nconst INSTALL_EVENTS = ['Stop'];\nconst MANAGED_EVENTS = ['Stop', 'Notification', 'SubagentStop'];\n\nexport function installClaudeHooks(port: number): void {\n const settingsPath = getSettingsPath();\n const url = `http://127.0.0.1:${port}/hook`;\n // Read the hook's JSON from stdin and POST it to the local agent endpoint.\n // Always exit 0 and stay quiet so it never interferes with Claude's flow.\n const command = `curl -s -m 2 -X POST -H \"Content-Type: application/json\" --data-binary @- ${url} >/dev/null 2>&1 || true`;\n\n let settings: Record<string, any> = {};\n try {\n if (existsSync(settingsPath)) {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n }\n } catch (err) {\n logger.warn({ err }, 'Could not parse ~/.claude/settings.json — skipping hook install');\n return;\n }\n\n const hooks: Record<string, any[]> = settings.hooks || (settings.hooks = {});\n const isOurs = (group: unknown) => JSON.stringify(group).includes(url);\n const desired = { hooks: [{ type: 'command', command }] };\n\n let changed = false;\n for (const ev of MANAGED_EVENTS) {\n const existed = Array.isArray(hooks[ev]);\n const kept = (existed ? hooks[ev] : []).filter((g) => !isOurs(g));\n const next = INSTALL_EVENTS.includes(ev) ? [...kept, desired] : kept;\n const before = existed ? JSON.stringify(hooks[ev]) : '';\n if (next.length === 0) {\n if (existed) {\n delete hooks[ev];\n changed = true;\n }\n } else if (before !== JSON.stringify(next)) {\n hooks[ev] = next;\n changed = true;\n }\n }\n\n if (!changed) {\n logger.info('CRC Claude notify hooks already present in settings.json');\n return;\n }\n\n try {\n mkdirSync(dirname(settingsPath), { recursive: true });\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n logger.info({ install: INSTALL_EVENTS, url }, 'Installed CRC Claude notify hooks into settings.json (restart claude to apply)');\n } catch (err) {\n logger.warn({ err }, 'Failed to write CRC notify hooks to settings.json');\n }\n}\n\n/**\n * Normalize a raw Claude Code hook payload (native format, keyed by\n * hook_event_name) into our simplified {event, ...} shape. Returns null for\n * events we don't notify on.\n */\nexport function normalizeClaudeHook(raw: any): ClaudeHookPayload | null {\n if (!raw || typeof raw !== 'object') return null;\n // Already our simplified format (e.g. a manual test POST).\n if (typeof raw.event === 'string' && !raw.hook_event_name) return raw as ClaudeHookPayload;\n\n const name = raw.hook_event_name;\n if (name === 'Stop' || name === 'SubagentStop') {\n // Carry cwd + session so the notification can deep-link to the transcript.\n // The response text is filled in later (after a small delay) so Claude has\n // finished flushing the final message to the transcript first.\n return {\n event: 'stop',\n projectPath: typeof raw.cwd === 'string' ? raw.cwd : undefined,\n claudeSessionId: typeof raw.session_id === 'string' ? raw.session_id : undefined,\n };\n }\n // Notification (idle / permission) is intentionally NOT notified — Stop\n // already covers \"Claude is done and waiting\", so this avoids duplicate alerts.\n return null;\n}\n\n/** Read the last assistant text message from a transcript JSONL for the notification body. */\nexport function lastAssistantSummary(transcriptPath?: string): string | undefined {\n if (!transcriptPath) return undefined;\n try {\n const lines = readFileSync(transcriptPath, 'utf-8').split('\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (!line) continue;\n try {\n const obj = JSON.parse(line);\n if (obj?.type === 'assistant' && obj.message?.content) {\n const c = obj.message.content;\n const text = Array.isArray(c)\n ? c.filter((x: any) => x?.type === 'text').map((x: any) => x.text).join(' ')\n : typeof c === 'string' ? c : '';\n if (text.trim()) return text.trim().replace(/\\s+/g, ' ').slice(0, 180);\n }\n } catch {\n /* skip malformed line */\n }\n }\n } catch {\n /* unreadable transcript */\n }\n return undefined;\n}\n","import http from 'http';\nimport type { ClaudeHookPayload } from '@crc/shared';\nimport { logger } from './logger.js';\n\n/**\n * A loopback-only HTTP server the Claude Code notify hooks POST to. This lets\n * Claude completion/permission events reach the agent even when Claude runs\n * OUTSIDE a CRC terminal (e.g. inside Warp or tmux on the PC), so they can be\n * forwarded to the server for Web Push.\n *\n * Bound to 127.0.0.1 only — never exposed off-machine.\n */\nexport function startLocalControl(\n port: number,\n onHook: (payload: ClaudeHookPayload) => void\n): http.Server {\n const server = http.createServer((req, res) => {\n if (req.method === 'POST' && req.url === '/hook') {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk;\n if (body.length > 64 * 1024) req.destroy(); // cap\n });\n req.on('end', () => {\n try {\n // Accept both our simplified shape ({event}) and Claude Code's native\n // hook payload ({hook_event_name}); the caller normalizes it.\n const payload = JSON.parse(body) as ClaudeHookPayload;\n if (payload && typeof payload === 'object') onHook(payload);\n } catch {\n /* ignore malformed */\n }\n res.writeHead(204);\n res.end();\n });\n req.on('error', () => {\n res.writeHead(400);\n res.end();\n });\n return;\n }\n res.writeHead(404);\n res.end();\n });\n\n server.on('error', (err) => {\n logger.warn({ err, port }, 'Local control server error (Claude hooks may not reach push)');\n });\n server.listen(port, '127.0.0.1', () => {\n logger.info({ port }, 'Local control endpoint listening (127.0.0.1) for Claude hooks');\n });\n return server;\n}\n","import { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport type { TmuxSessionInfo } from '@crc/shared';\n\nconst execFileAsync = promisify(execFile);\n\nconst IS_WIN = process.platform === 'win32';\n\n// On Windows there's no native tmux, but WSL has it. Run tmux through the\n// default WSL distro so we can mirror sessions the user runs in Warp's WSL tab.\nfunction tmuxCmd(args: string[]): { file: string; args: string[] } {\n return IS_WIN ? { file: 'wsl.exe', args: ['tmux', ...args] } : { file: 'tmux', args };\n}\n\n/**\n * List tmux sessions so the phone can mirror one (the same session the user has\n * open in Warp / a WSL tab). Empty if tmux isn't available or no server runs.\n */\nexport async function listTmuxSessions(): Promise<TmuxSessionInfo[]> {\n const fmt = '#{session_name}\\t#{session_windows}\\t#{session_attached}\\t#{session_activity}';\n const { file, args } = tmuxCmd(['list-sessions', '-F', fmt]);\n try {\n const { stdout } = await execFileAsync(file, args, { timeout: 6000 });\n return stdout\n .split('\\n')\n .map((line) => line.replace(/\\r$/, '').trim())\n .filter(Boolean)\n .map((line) => {\n const [name, windows, attached, activity] = line.split('\\t');\n return {\n name,\n windows: parseInt(windows, 10) || 1,\n attached: attached === '1',\n activity: parseInt(activity, 10) || undefined,\n };\n });\n } catch {\n return [];\n }\n}\n\n/** Shell-quote a value for safe single-quoted interpolation. */\nfunction shq(v: string): string {\n return `'${v.replace(/'/g, `'\\\\''`)}'`;\n}\n\n/**\n * Build the process a PTY runs to attach (or create) a shared tmux session.\n * `-A` attaches if it exists, else creates it (running `launch` if given).\n * Returns a {file, args} spec spawned directly (no wrapping shell).\n */\nexport function buildTmuxLaunch(\n name: string,\n launch: string | undefined,\n shell: string\n): { file: string; args: string[] } {\n const trimmed = launch && launch.trim() ? launch.trim() : undefined;\n\n if (IS_WIN) {\n // wsl.exe tmux new-session -A -s <name> [launch]\n const args = ['tmux', 'new-session', '-A', '-s', name];\n if (trimmed) args.push(trimmed);\n return { file: 'wsl.exe', args };\n }\n\n // POSIX: run via a login shell so PATH (e.g. Homebrew tmux) is loaded, and\n // set aggressive-resize so the PC's view doesn't shrink to the phone's size.\n let cmd = `tmux set-option -g aggressive-resize on 2>/dev/null; exec tmux new-session -A -s ${shq(name)}`;\n if (trimmed) cmd += ` ${shq(trimmed)}`;\n return { file: shell, args: ['-l', '-c', cmd] };\n}\n","import { execSync } from 'child_process';\n\nexport function detectShell(preference: string): string {\n if (preference !== 'auto') return preference;\n\n if (process.platform === 'win32') {\n // Try PowerShell 7+ first\n try {\n execSync('where pwsh', { stdio: 'ignore' });\n return 'pwsh.exe';\n } catch {\n return 'powershell.exe';\n }\n }\n\n // macOS / Linux\n return process.env.SHELL || '/bin/bash';\n}\n","import * as pty from 'node-pty';\nimport { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { tmpdir, homedir } from 'os';\nimport { SESSION_BUFFER_SIZE } from '@crc/shared';\nimport { detectShell } from './shell.js';\n\n// ── Shell integration for command-completion notifications ──────────\n// Injects a precmd/PROMPT_COMMAND hook that sends BEL (\\a) when a\n// command takes >= THRESHOLD seconds. The web client's bell detection\n// picks it up and fires a browser notification.\n\nconst THRESHOLD = 3; // seconds\nconst INIT_DIR = join(tmpdir(), 'crc-shell-init');\nconst BASH_INIT = join(INIT_DIR, 'bashrc');\nconst ZSH_DIR = join(INIT_DIR, 'zsh');\nconst ZSH_RC = join(ZSH_DIR, '.zshrc');\nconst ZSH_ENV = join(ZSH_DIR, '.zshenv');\n\nconst INIT_VERSION = '2'; // bump to force regeneration\n\nfunction ensureInitFiles(): void {\n const versionFile = join(INIT_DIR, '.version');\n if (existsSync(versionFile)) {\n try {\n if (readFileSync(versionFile, 'utf-8').trim() === INIT_VERSION) return;\n } catch { /* regenerate */ }\n }\n mkdirSync(ZSH_DIR, { recursive: true });\n\n // Bash: The DEBUG trap fires before EVERY simple command, including\n // those inside PROMPT_COMMAND. So we compute the elapsed time IN the\n // trap (saved to __crc_elapsed) and check it in PROMPT_COMMAND.\n // The DEBUG trap before PROMPT_COMMAND's first command gives us the\n // duration of the LAST user command. __crc_armed skips the first\n // prompt (shell init) so we don't get a false notification on startup.\n writeFileSync(BASH_INIT, [\n '[ -f ~/.bash_profile ] && source ~/.bash_profile',\n '[ -f ~/.bashrc ] && source ~/.bashrc',\n '__crc_s=$SECONDS',\n '__crc_elapsed=0',\n '__crc_armed=0',\n '__crc_orig_pc=\"$PROMPT_COMMAND\"',\n `trap '__crc_elapsed=$((SECONDS - __crc_s)); __crc_s=$SECONDS' DEBUG`,\n `PROMPT_COMMAND='[ \"$__crc_armed\" = 1 ] && [ \\${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf \"\\\\a\"; __crc_armed=1; eval \"$__crc_orig_pc\"'`,\n '',\n ].join('\\n'), 'utf-8');\n\n // Zsh: preexec/precmd don't have the bash DEBUG trap issue —\n // preexec fires only for user commands, not for precmd itself.\n writeFileSync(ZSH_ENV, [\n '[ -f \"${ZDOTDIR_ORIG:-$HOME}/.zshenv\" ] && source \"${ZDOTDIR_ORIG:-$HOME}/.zshenv\"',\n '',\n ].join('\\n'), 'utf-8');\n\n writeFileSync(ZSH_RC, [\n '[ -f \"${ZDOTDIR_ORIG:-$HOME}/.zshrc\" ] && source \"${ZDOTDIR_ORIG:-$HOME}/.zshrc\"',\n '__crc_s=$SECONDS',\n '__crc_armed=0',\n `__crc_preexec() { __crc_s=$SECONDS; }`,\n `__crc_precmd() { (( __crc_armed )) && (( SECONDS - __crc_s >= ${THRESHOLD} )) && printf '\\\\a'; __crc_armed=1; }`,\n 'autoload -Uz add-zsh-hook 2>/dev/null',\n 'if type add-zsh-hook &>/dev/null; then',\n ' add-zsh-hook preexec __crc_preexec',\n ' add-zsh-hook precmd __crc_precmd',\n 'fi',\n '',\n ].join('\\n'), 'utf-8');\n\n writeFileSync(versionFile, INIT_VERSION, 'utf-8');\n}\n\n// Resolve a working directory that actually exists on disk. node-pty throws\n// synchronously if `cwd` doesn't exist, and on Windows HOME may point to a\n// nonexistent POSIX path (Git-Bash/MSYS), so we validate each candidate and\n// fall back to the first that exists. On Windows USERPROFILE is preferred\n// over HOME.\nfunction resolveCwd(cwd?: string): string {\n const candidates =\n process.platform === 'win32'\n ? [cwd, homedir(), process.env.USERPROFILE, process.env.HOME]\n : [cwd, homedir(), process.env.HOME, process.env.USERPROFILE];\n for (const c of candidates) {\n if (c) {\n try {\n if (existsSync(c)) return c;\n } catch {\n /* try next candidate */\n }\n }\n }\n return homedir();\n}\n\nexport class PtySession {\n id: string;\n private pty: pty.IPty;\n private buffer: string = '';\n private maxBufferSize = SESSION_BUFFER_SIZE;\n private _attached = true;\n private _detachedAt = 0;\n\n constructor(\n id: string,\n cols: number,\n rows: number,\n shellPreference: string,\n cwd?: string,\n launch?: { file: string; args: string[] }\n ) {\n this.id = id;\n const shell = detectShell(shellPreference);\n const isBash = /bash(\\.exe)?$/.test(shell);\n const isZsh = /zsh(\\.exe)?$/.test(shell);\n\n const env: Record<string, string> = { ...process.env, TERM: 'xterm-256color' } as Record<string, string>;\n\n // A launch spec (e.g. attaching a tmux session) is spawned directly — no\n // shell-integration rcfile, it owns the session. Otherwise spawn the shell.\n let file = shell;\n const args: string[] = [];\n if (launch) {\n file = launch.file;\n args.push(...launch.args);\n } else {\n ensureInitFiles();\n if (isBash) {\n args.push('--rcfile', BASH_INIT);\n } else if (isZsh) {\n env.ZDOTDIR_ORIG = process.env.ZDOTDIR || process.env.HOME || '';\n env.ZDOTDIR = ZSH_DIR;\n }\n }\n\n const resolvedCwd = resolveCwd(cwd);\n\n try {\n this.pty = pty.spawn(file, args, {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: resolvedCwd,\n env,\n });\n } catch (err) {\n // A bad cwd (or spawn target) can make node-pty throw synchronously. Retry\n // once from the validated home directory before giving up.\n console.error(`[pty] spawn failed (file=${file}, cwd=${resolvedCwd}):`, err);\n this.pty = pty.spawn(file, args, {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: homedir(),\n env,\n });\n }\n }\n\n write(data: string): void {\n this.pty.write(data);\n }\n\n resize(cols: number, rows: number): void {\n this.pty.resize(cols, rows);\n }\n\n kill(): void {\n this.pty.kill();\n }\n\n onData(callback: (data: string) => void): void {\n this.pty.onData(callback);\n }\n\n onExit(callback: (exit: { exitCode: number; signal?: number }) => void): void {\n this.pty.onExit(callback);\n }\n\n appendToBuffer(data: string): void {\n this.buffer += data;\n if (this.buffer.length > this.maxBufferSize) {\n let sliced = this.buffer.slice(this.buffer.length - this.maxBufferSize);\n // The naive slice can cut mid-ANSI-escape or split a surrogate pair,\n // corrupting replay. Advance the start to the next safe boundary: drop\n // everything up to and including the first '\\n', or up to (but not\n // including) the first ESC, whichever comes first — so replay begins at\n // a fresh line or a clean escape start.\n const nl = sliced.indexOf('\\n');\n const esc = sliced.indexOf('\\x1b');\n if (nl !== -1 && (esc === -1 || nl < esc)) {\n sliced = sliced.slice(nl + 1);\n } else if (esc > 0) {\n sliced = sliced.slice(esc);\n }\n this.buffer = sliced;\n }\n }\n\n getAndClearBuffer(): string {\n const data = this.buffer;\n this.buffer = '';\n return data;\n }\n\n setAttached(v: boolean): void {\n this._attached = v;\n this._detachedAt = v ? 0 : Date.now();\n }\n\n isAttached(): boolean {\n return this._attached;\n }\n\n // Timestamp (ms) when this session was last detached, or 0 if attached.\n getDetachedAt(): number {\n return this._detachedAt;\n }\n}\n","import { PtySession } from './pty-session.js';\nimport { logger } from './logger.js';\n\nconst sessions = new Map<string, PtySession>();\n\nexport function createTerminalSession(\n sessionId: string,\n cols: number,\n rows: number,\n shellPreference: string,\n cwd: string | undefined,\n onData: (sessionId: string, data: string) => void,\n onExit: (sessionId: string, exitCode: number) => void,\n launch?: { file: string; args: string[] }\n): void {\n const session = new PtySession(sessionId, cols, rows, shellPreference, cwd, launch);\n\n session.onData((data) => {\n session.appendToBuffer(data);\n if (session.isAttached()) {\n onData(sessionId, data);\n }\n });\n\n session.onExit((exit) => {\n sessions.delete(sessionId);\n onExit(sessionId, exit.exitCode);\n logger.info({ sessionId, exitCode: exit.exitCode }, 'PTY exited');\n });\n\n sessions.set(sessionId, session);\n logger.info({ sessionId, cols, rows }, 'PTY session created');\n}\n\nexport function writeToSession(sessionId: string, data: string): void {\n sessions.get(sessionId)?.write(data);\n}\n\nexport function resizeSession(sessionId: string, cols: number, rows: number): void {\n sessions.get(sessionId)?.resize(cols, rows);\n}\n\nexport function detachSession(sessionId: string): void {\n const session = sessions.get(sessionId);\n if (session) {\n session.setAttached(false);\n logger.info({ sessionId }, 'PTY session detached');\n }\n}\n\nexport function attachSession(sessionId: string, cols: number, rows: number): string {\n const session = sessions.get(sessionId);\n if (!session) return '';\n session.setAttached(true);\n session.resize(cols, rows);\n const buffered = session.getAndClearBuffer();\n logger.info({ sessionId, bufferSize: buffered.length }, 'PTY session reattached');\n return buffered;\n}\n\nexport function closeSession(sessionId: string): void {\n const session = sessions.get(sessionId);\n if (session) {\n session.kill();\n sessions.delete(sessionId);\n logger.info({ sessionId }, 'PTY session closed');\n }\n}\n\nexport function closeAllSessions(): void {\n for (const [id, session] of sessions) {\n session.kill();\n logger.info({ sessionId: id }, 'PTY session force-closed');\n }\n sessions.clear();\n}\n\nexport function detachAllSessions(): void {\n for (const [id, session] of sessions) {\n session.setAttached(false);\n logger.info({ sessionId: id }, 'PTY session detached (disconnect)');\n }\n}\n\nexport function getAliveSessionIds(): string[] {\n return Array.from(sessions.keys());\n}\n\nexport function getActiveSessionCount(): number {\n return sessions.size;\n}\n\n// Kill and remove any detached sessions whose idle duration exceeds maxIdleMs.\n// Returns the ids of the sessions that were reaped.\nexport function reapDetachedSessions(maxIdleMs: number): string[] {\n const now = Date.now();\n const reaped: string[] = [];\n for (const [id, session] of sessions) {\n if (session.isAttached()) continue;\n const detachedAt = session.getDetachedAt();\n if (detachedAt > 0 && now - detachedAt > maxIdleMs) {\n session.kill();\n sessions.delete(id);\n reaped.push(id);\n logger.info({ sessionId: id, idleMs: now - detachedAt }, 'PTY session reaped (idle)');\n }\n }\n return reaped;\n}\n","import os from 'os';\nimport path from 'path';\nimport { execSync } from 'child_process';\nimport type { HeartbeatPayload } from '@crc/shared';\nimport { getActiveSessionCount } from './terminal-manager.js';\n\n// Previous { idle, total } CPU tick sample, used to compute usage as a delta\n// between calls. os.cpus() reports cumulative-since-boot times, so summing\n// them once yields a flat near-idle figure; the delta reflects recent load.\nlet prevCpu: { idle: number; total: number } | null = null;\n\nfunction getCpuUsage(): number {\n const cpus = os.cpus();\n let idle = 0;\n let total = 0;\n for (const cpu of cpus) {\n for (const type of Object.values(cpu.times)) {\n total += type;\n }\n idle += cpu.times.idle;\n }\n\n const prev = prevCpu;\n prevCpu = { idle, total };\n\n if (!prev) return 0; // first sample has no baseline to diff against\n\n const idleDelta = idle - prev.idle;\n const totalDelta = total - prev.total;\n if (totalDelta <= 0) return 0;\n\n return Math.round((1 - idleDelta / totalDelta) * 100);\n}\n\nfunction getRootPaths(): string[] {\n if (process.platform === 'win32') {\n try {\n const output = execSync('wmic logicaldisk get name', { encoding: 'utf-8' });\n return output\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => /^[A-Z]:$/.test(l))\n .map((l) => l + '\\\\');\n } catch {\n return ['C:\\\\'];\n }\n }\n return ['/'];\n}\n\nexport function buildHeartbeat(homeDir?: string): HeartbeatPayload {\n const totalMem = os.totalmem();\n const freeMem = os.freemem();\n\n return {\n hostname: os.hostname(),\n platform: process.platform as 'win32' | 'darwin' | 'linux',\n arch: os.arch(),\n cpuUsage: getCpuUsage(),\n memoryUsage: Math.round(((totalMem - freeMem) / totalMem) * 100),\n uptime: Math.round(os.uptime()),\n activeSessions: getActiveSessionCount(),\n pathSeparator: path.sep as '\\\\' | '/',\n homeDirectory: homeDir || os.homedir(),\n rootPaths: getRootPaths(),\n capabilities: { terminal: true, fileTransfer: false },\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { logger } from './logger.js';\nimport { FILE_MAX_SIZE } from '@crc/shared';\nimport type { FileEntry } from '@crc/shared';\n\n// The agent's own config/secret directory. Browsing or downloading anything\n// inside it (e.g. the agent secret) is denied so it can't be exfiltrated.\nconst SECRET_DIR = path.join(os.homedir(), '.crc-agent');\n\n// True if `resolved` is the secret dir itself or any descendant of it.\nfunction isInSecretDir(resolved: string): boolean {\n const rel = path.relative(SECRET_DIR, resolved);\n // Empty rel => same path. A rel that doesn't start with '..' and isn't\n // absolute means resolved is inside SECRET_DIR.\n return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel));\n}\n\nexport function listDirectory(dirPath: string): { entries: FileEntry[]; error?: string } {\n try {\n const resolved = path.resolve(dirPath);\n if (isInSecretDir(resolved)) {\n return { entries: [], error: 'Access denied' };\n }\n if (!fs.existsSync(resolved)) {\n return { entries: [], error: 'Path does not exist' };\n }\n\n const stat = fs.statSync(resolved);\n if (!stat.isDirectory()) {\n return { entries: [], error: 'Not a directory' };\n }\n\n const dirents = fs.readdirSync(resolved, { withFileTypes: true });\n const entries: FileEntry[] = [];\n\n for (const dirent of dirents) {\n try {\n const fullPath = path.join(resolved, dirent.name);\n const s = fs.statSync(fullPath);\n entries.push({\n name: dirent.name,\n isDirectory: dirent.isDirectory(),\n size: dirent.isDirectory() ? 0 : s.size,\n modified: s.mtimeMs,\n });\n } catch {\n // Skip files we can't stat (permission denied, etc.)\n }\n }\n\n // Sort: directories first, then alphabetical\n entries.sort((a, b) => {\n if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n\n return { entries };\n } catch (err: any) {\n logger.error({ path: dirPath, error: err.message }, 'Failed to list directory');\n return { entries: [], error: err.message };\n }\n}\n\nexport async function downloadFile(\n filePath: string,\n serverUrl: string,\n secret: string,\n agentId: string\n): Promise<{ fileId: string; fileName: string; downloadUrl: string; size: number } | { error: string }> {\n try {\n const resolved = path.resolve(filePath);\n if (isInSecretDir(resolved)) {\n return { error: 'Access denied' };\n }\n if (!fs.existsSync(resolved)) {\n return { error: 'File does not exist' };\n }\n\n const stat = fs.statSync(resolved);\n if (stat.isDirectory()) {\n return { error: 'Cannot download a directory' };\n }\n if (stat.size > FILE_MAX_SIZE) {\n return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };\n }\n\n const fileName = path.basename(resolved);\n // Stream the file rather than reading it fully into memory, so large\n // downloads stay memory-bounded.\n const fileStream = fs.createReadStream(resolved);\n\n // POST to server's receive endpoint\n const baseUrl = serverUrl.replace('wss://', 'https://').replace('ws://', 'http://');\n const url = `${baseUrl}/api/files/receive`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'X-File-Name': fileName,\n 'X-Agent-Id': agentId,\n 'X-Agent-Secret': secret,\n 'Content-Length': String(stat.size),\n },\n body: fileStream as any,\n duplex: 'half',\n } as any);\n\n if (!res.ok) {\n return { error: `Upload failed: ${res.status}` };\n }\n\n const result = await res.json();\n return {\n fileId: result.fileId,\n fileName: result.fileName,\n downloadUrl: result.downloadUrl,\n size: stat.size,\n };\n } catch (err: any) {\n logger.error({ path: filePath, error: err.message }, 'Failed to download file');\n return { error: err.message };\n }\n}\n","import { exec } from 'child_process';\nimport { promisify } from 'util';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport type { VpnProfile, VpnStatus } from '@crc/shared';\nimport type { VpnProfileConfig } from './config.js';\nimport { logger } from './logger.js';\n\nconst execAsync = promisify(exec);\nconst isWindows = process.platform === 'win32';\nconst VPN_DIR = path.join(os.homedir(), '.crc-agent', 'vpn');\n\n// Escape a value for embedding inside a /bin/sh single-quoted string.\nfunction shq(value: string): string {\n return value.replace(/'/g, \"'\\\\''\");\n}\n\n// Escape a value for embedding inside a PowerShell single-quoted string.\nfunction psq(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nasync function runCmd(cmd: string, shell?: string): Promise<{ stdout: string; stderr: string }> {\n try {\n return await execAsync(cmd, {\n timeout: 20000,\n shell: shell || (isWindows ? 'powershell.exe' : '/bin/sh'),\n });\n } catch (err: any) {\n return { stdout: err.stdout || '', stderr: err.stderr || err.message };\n }\n}\n\nfunction getConfigPath(profile: VpnProfileConfig): string {\n // configFile can be absolute or relative to ~/.crc-agent/vpn/\n const file = profile.configFile || `${profile.id}.conf`;\n if (path.isAbsolute(file)) return file;\n return path.join(VPN_DIR, file);\n}\n\n// ========== macOS scutil helpers ==========\n\nasync function getScutilStatus(serviceName: string): Promise<VpnStatus> {\n const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);\n const firstLine = stdout.trim().split('\\n')[0];\n if (firstLine === 'Connected') return 'connected';\n if (firstLine === 'Connecting') return 'connecting';\n if (firstLine === 'Disconnecting') return 'disconnecting';\n return 'disconnected';\n}\n\nasync function scutilStart(serviceName: string): Promise<string | null> {\n const { stderr } = await runCmd(`scutil --nc start '${shq(serviceName)}'`);\n return stderr || null;\n}\n\nasync function scutilStop(serviceName: string): Promise<string | null> {\n const { stderr } = await runCmd(`scutil --nc stop '${shq(serviceName)}'`);\n return stderr || null;\n}\n\n// ========== macOS Tunnelblick (AppleScript) helpers ==========\n// Tunnelblick doesn't register configs with scutil — it has its own AppleScript API.\n// States from `get state`: EXITING, DISCONNECTING, CONNECTING, RECONNECTING, CONNECTED.\n\nasync function runOsascript(script: string): Promise<{ stdout: string; stderr: string }> {\n // Pass the timeout wrapper as separate -e arguments — embedding \\n in a single\n // -e argument breaks because shell quoting doesn't interpret \\n as a newline.\n return runCmd(\n `osascript -e 'with timeout of 15 seconds' -e ${JSON.stringify(script)} -e 'end timeout'`\n );\n}\n\n// Escape a value for embedding inside an AppleScript double-quoted string.\nfunction asq(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\nasync function getTunnelblickStatus(configName: string): Promise<VpnStatus> {\n const { stdout } = await runOsascript(\n `tell application \"Tunnelblick\" to get state of first configuration where name = \"${asq(configName)}\"`\n );\n const s = stdout.trim();\n if (s === 'CONNECTED') return 'connected';\n if (s === 'CONNECTING' || s === 'RECONNECTING') return 'connecting';\n if (s === 'DISCONNECTING') return 'disconnecting';\n return 'disconnected';\n}\n\nasync function tunnelblickConnect(configName: string): Promise<string | null> {\n const { stderr } = await runOsascript(\n `tell application \"Tunnelblick\" to connect \"${asq(configName)}\"`\n );\n return stderr || null;\n}\n\nasync function tunnelblickDisconnect(configName: string): Promise<string | null> {\n const { stderr } = await runOsascript(\n `tell application \"Tunnelblick\" to disconnect \"${asq(configName)}\"`\n );\n return stderr || null;\n}\n\n// ========== STATUS ==========\n\nasync function getWireGuardStatus(profile: VpnProfileConfig): Promise<VpnStatus> {\n if (isWindows) {\n const tunnelName = profile.tunnelName || profile.id;\n const { stdout } = await runCmd(\n `Get-Service -Name 'WireGuardTunnel$${psq(tunnelName)}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Status`\n );\n const s = stdout.trim();\n if (s === 'Running') return 'connected';\n if (s === 'StartPending') return 'connecting';\n if (s === 'StopPending') return 'disconnecting';\n return 'disconnected';\n }\n // macOS: use scutil --nc if serviceName is configured\n if (profile.serviceName) {\n return getScutilStatus(profile.serviceName);\n }\n // Fallback: wg-quick CLI\n const tunnelName = profile.tunnelName || profile.id;\n const { stdout } = await runCmd(`wg show '${shq(tunnelName)}' 2>/dev/null`);\n return stdout.trim() ? 'connected' : 'disconnected';\n}\n\nasync function getOpenVpnStatus(profile: VpnProfileConfig): Promise<VpnStatus> {\n if (isWindows) {\n // Check if ovpnconnector service is running for this profile\n const { stdout } = await runCmd(\n `Get-Service -Name 'ovpnconnector' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Status`\n );\n if (stdout.trim() === 'Running') {\n // Verify it's connected by checking for tun/tap adapter\n const { stdout: netOut } = await runCmd(\n `Get-NetAdapter | Where-Object { $_.InterfaceDescription -like '*OpenVPN*' -and $_.Status -eq 'Up' } | Measure-Object | Select-Object -ExpandProperty Count`\n );\n return parseInt(netOut.trim(), 10) > 0 ? 'connected' : 'connecting';\n }\n return 'disconnected';\n }\n // macOS: prefer Tunnelblick (AppleScript) if configured\n if (profile.tunnelblickName) {\n return getTunnelblickStatus(profile.tunnelblickName);\n }\n // macOS: use scutil --nc if serviceName is configured\n if (profile.serviceName) {\n return getScutilStatus(profile.serviceName);\n }\n // Fallback: check if openvpn process is running\n const { stdout } = await runCmd('pgrep -f \"openvpn.*\\\\.ovpn\" | head -1');\n return stdout.trim() ? 'connected' : 'disconnected';\n}\n\nasync function getAzureStatus(profile: VpnProfileConfig): Promise<VpnStatus> {\n if (isWindows) {\n // Windows: same as OpenVPN (ovpnconnector-based)\n return getOpenVpnStatus(profile);\n }\n // macOS: Azure VPN Client registers as a network extension\n if (profile.serviceName) {\n return getScutilStatus(profile.serviceName);\n }\n return 'disconnected';\n}\n\nasync function getStatus(profile: VpnProfileConfig): Promise<VpnStatus> {\n try {\n if (profile.type === 'wireguard') {\n return await getWireGuardStatus(profile);\n }\n if (profile.type === 'azure') {\n return await getAzureStatus(profile);\n }\n return await getOpenVpnStatus(profile);\n } catch (err: any) {\n logger.error({ profile: profile.id, error: err.message }, 'Status check failed');\n return 'disconnected';\n }\n}\n\n// ========== CONNECT ==========\n\nasync function connectWireGuard(profile: VpnProfileConfig): Promise<string | null> {\n const tunnelName = profile.tunnelName || profile.id;\n\n if (isWindows) {\n const confPath = getConfigPath(profile);\n if (!fs.existsSync(confPath)) {\n return `Config file not found: ${confPath}`;\n }\n // Use elevated PowerShell to install and start the tunnel service\n // This triggers a UAC prompt on the local machine\n const script = `\n & 'C:\\\\Program Files\\\\WireGuard\\\\wireguard.exe' /installtunnelservice '${confPath.replace(/'/g, \"''\")}';\n Start-Sleep -Seconds 2;\n Set-Service -Name 'WireGuardTunnel\\$${psq(tunnelName)}' -StartupType Manual -ErrorAction SilentlyContinue\n `.trim();\n await runCmd(\n `Start-Process powershell -ArgumentList '-NoProfile','-Command','${script.replace(/'/g, \"''\")}' -Verb RunAs -Wait -ErrorAction Stop 2>&1`\n );\n await new Promise((r) => setTimeout(r, 2000));\n const status = await getWireGuardStatus(profile);\n if (status !== 'connected') return 'Tunnel installed but not connected — check WireGuard GUI';\n return null;\n }\n // macOS: use scutil --nc if serviceName is configured\n if (profile.serviceName) {\n return scutilStart(profile.serviceName);\n }\n // Fallback: wg-quick CLI (requires sudo + brew install wireguard-tools)\n const confPath = getConfigPath(profile);\n const { stderr } = await runCmd(`sudo wg-quick up '${shq(confPath)}'`);\n return stderr || null;\n}\n\nasync function connectOpenVpn(profile: VpnProfileConfig): Promise<string | null> {\n if (isWindows) {\n const confPath = getConfigPath(profile);\n if (!fs.existsSync(confPath)) {\n return `Config file not found: ${confPath}`;\n }\n const connector = 'C:\\\\Program Files\\\\OpenVPN Connect\\\\ovpnconnector.exe';\n // Kill GUI app if running (conflicts with ovpnconnector)\n await runCmd(`Stop-Process -Name 'OpenVPNConnect' -Force -ErrorAction SilentlyContinue`);\n // Install service if not present\n await runCmd(`& '${connector}' install 2>&1`);\n // Stop any existing connection\n await runCmd(`& '${connector}' stop 2>&1`);\n await new Promise((r) => setTimeout(r, 1000));\n // Set profile and start\n const { stdout: setOut, stderr: setErr } = await runCmd(`& '${connector}' set-config profile '${psq(confPath)}' 2>&1`);\n const setOutput = setOut + setErr;\n if (setOutput.toLowerCase().includes('failed')) {\n return `Failed to set profile: ${setOutput.trim()}`;\n }\n const { stdout: startOut, stderr: startErr } = await runCmd(`& '${connector}' start 2>&1`);\n const startOutput = startOut + startErr;\n if (startOutput.toLowerCase().includes('error') || startOutput.toLowerCase().includes('aborting')) {\n return startOutput.trim();\n }\n return null;\n }\n // macOS: prefer Tunnelblick (AppleScript) if configured\n if (profile.tunnelblickName) {\n return tunnelblickConnect(profile.tunnelblickName);\n }\n // macOS: use scutil --nc if serviceName is configured (OpenVPN Connect app)\n if (profile.serviceName) {\n return scutilStart(profile.serviceName);\n }\n // Fallback: openvpn CLI (requires sudo + brew install openvpn)\n const confPath = getConfigPath(profile);\n if (!fs.existsSync(confPath)) {\n return `Config file not found: ${confPath}`;\n }\n const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);\n return stderr || null;\n}\n\nasync function connectAzure(profile: VpnProfileConfig): Promise<string | null> {\n if (isWindows) {\n const confPath = getConfigPath(profile);\n if (!fs.existsSync(confPath)) {\n return `Config file not found: ${confPath}`;\n }\n // Import the XML config into Azure VPN Client and open the app\n await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);\n await new Promise((r) => setTimeout(r, 2000));\n await runCmd(`Start-Process 'shell:AppsFolder\\\\Microsoft.AzureVpn_8wekyb3d8bbwe!App'`);\n return 'Azure VPN app opened — requires Azure AD sign-in to connect';\n }\n // macOS: Azure VPN Client registers as a network extension — use scutil\n if (profile.serviceName) {\n const err = await scutilStart(profile.serviceName);\n if (err) return err;\n // Azure VPN may need the app open for AAD auth if not cached\n await runCmd(`open -a 'Azure VPN Client'`);\n return null;\n }\n return 'Azure VPN: set serviceName in config (from scutil --nc list)';\n}\n\nasync function disconnectAzure(profile: VpnProfileConfig): Promise<string | null> {\n if (isWindows) {\n await runCmd(`Start-Process 'shell:AppsFolder\\\\Microsoft.AzureVpn_8wekyb3d8bbwe!App'`);\n return 'Azure VPN app opened — disconnect from the app';\n }\n // macOS: use scutil to disconnect\n if (profile.serviceName) {\n return scutilStop(profile.serviceName);\n }\n return 'Azure VPN: set serviceName in config (from scutil --nc list)';\n}\n\n// ========== DISCONNECT ==========\n\nasync function disconnectWireGuard(profile: VpnProfileConfig): Promise<string | null> {\n const tunnelName = profile.tunnelName || profile.id;\n if (isWindows) {\n // Use elevated PowerShell to uninstall the tunnel service\n const script = `& 'C:\\\\Program Files\\\\WireGuard\\\\wireguard.exe' /uninstalltunnelservice '${psq(tunnelName)}'`;\n await runCmd(\n `Start-Process powershell -ArgumentList '-NoProfile','-Command','${script.replace(/'/g, \"''\")}' -Verb RunAs -Wait -ErrorAction Stop 2>&1`\n );\n await new Promise((r) => setTimeout(r, 2000));\n const status = await getWireGuardStatus(profile);\n if (status === 'connected') {\n return 'Failed to disconnect — try disconnecting from WireGuard GUI';\n }\n return null;\n }\n // macOS: use scutil --nc if serviceName is configured\n if (profile.serviceName) {\n return scutilStop(profile.serviceName);\n }\n // Fallback: wg-quick CLI\n const confPath = getConfigPath(profile);\n const { stderr } = await runCmd(`sudo wg-quick down '${shq(confPath)}'`);\n return stderr || null;\n}\n\nasync function disconnectOpenVpn(profile: VpnProfileConfig): Promise<string | null> {\n if (isWindows) {\n const connector = 'C:\\\\Program Files\\\\OpenVPN Connect\\\\ovpnconnector.exe';\n const { stderr, stdout } = await runCmd(`& '${connector}' stop 2>&1`);\n const output = stdout + stderr;\n if (output.toLowerCase().includes('error')) return output.trim();\n return null;\n }\n // macOS: prefer Tunnelblick (AppleScript) if configured\n if (profile.tunnelblickName) {\n return tunnelblickDisconnect(profile.tunnelblickName);\n }\n // macOS: use scutil --nc if serviceName is configured\n if (profile.serviceName) {\n return scutilStop(profile.serviceName);\n }\n // Fallback: kill openvpn process\n await runCmd('sudo pkill -f \"openvpn.*\\\\.ovpn\"');\n return null;\n}\n\n// ========== PUBLIC API ==========\n\nexport async function getProfiles(configs: VpnProfileConfig[]): Promise<VpnProfile[]> {\n const profiles: VpnProfile[] = [];\n for (const cfg of configs) {\n const status = await getStatus(cfg);\n profiles.push({ id: cfg.id, name: cfg.name, type: cfg.type, status });\n }\n return profiles;\n}\n\nexport async function connectVpn(\n configs: VpnProfileConfig[],\n profileId: string\n): Promise<VpnProfile[]> {\n const cfg = configs.find((c) => c.id === profileId);\n if (!cfg) return getProfiles(configs);\n\n logger.info({ profileId, type: cfg.type }, 'Connecting VPN');\n let error: string | null = null;\n\n if (cfg.type === 'wireguard') {\n error = await connectWireGuard(cfg);\n } else if (cfg.type === 'azure') {\n error = await connectAzure(cfg);\n } else {\n error = await connectOpenVpn(cfg);\n }\n\n if (error) logger.error({ profileId, error }, 'VPN connect failed');\n\n await new Promise((r) => setTimeout(r, 3000));\n const profiles = await getProfiles(configs);\n\n if (error) {\n const p = profiles.find((p) => p.id === profileId);\n if (p) { p.status = 'error'; p.error = error.slice(0, 200); }\n }\n return profiles;\n}\n\nexport async function disconnectVpn(\n configs: VpnProfileConfig[],\n profileId: string\n): Promise<VpnProfile[]> {\n const cfg = configs.find((c) => c.id === profileId);\n if (!cfg) return getProfiles(configs);\n\n logger.info({ profileId, type: cfg.type }, 'Disconnecting VPN');\n let error: string | null = null;\n\n if (cfg.type === 'wireguard') {\n error = await disconnectWireGuard(cfg);\n } else if (cfg.type === 'azure') {\n error = await disconnectAzure(cfg);\n } else {\n error = await disconnectOpenVpn(cfg);\n }\n\n if (error) logger.error({ profileId, error }, 'VPN disconnect failed');\n\n await new Promise((r) => setTimeout(r, 2000));\n const profiles = await getProfiles(configs);\n\n if (error) {\n const p = profiles.find((p) => p.id === profileId);\n if (p) { p.status = 'error'; p.error = error.slice(0, 200); }\n }\n return profiles;\n}\n","/**\n * Encode a project path to the directory name Claude uses in ~/.claude/projects/\n *\n * Claude Code replaces EVERY non-alphanumeric character with '-'. This handles\n * ':', '\\\\', '/', '.', and spaces uniformly, so both backslash and forward-slash\n * Windows cwds encode the same way.\n *\n * Unix: /Users/alice/code → -Users-alice-code\n * Windows: C:\\Projects\\myapp → C--Projects-myapp\n * Windows: C:/Projects/myapp → C--Projects-myapp\n * C:\\Projects\\site.com → C--Projects-site-com\n */\nexport function encodeProjectPath(projectPath: string): string {\n return projectPath.replace(/[^A-Za-z0-9]/g, '-');\n}\n\n/**\n * Find the matching project directory for a given path.\n * Tries exact encoding first, then falls back to an anchored prefix match\n * (so we never bleed into a sibling project's sessions).\n */\nexport function findProjectDirs(allDirs: string[], filterProjectPath: string): string[] {\n const encoded = encodeProjectPath(filterProjectPath);\n\n // Exact match\n if (allDirs.includes(encoded)) {\n return [encoded];\n }\n\n // Case-insensitive exact match (Windows drive letters can vary in case)\n const lowerEncoded = encoded.toLowerCase();\n const caseMatch = allDirs.filter((d) => d.toLowerCase() === lowerEncoded);\n if (caseMatch.length > 0) {\n return caseMatch;\n }\n\n // Anchored prefix match: the requested encoded name must be a directory-segment\n // prefix of the candidate (or vice-versa), so we don't over-match a different\n // project that merely shares a tail segment. A candidate qualifies only when one\n // encoded name is a prefix of the other at a '-' boundary (or equal).\n const isAnchoredMatch = (a: string, b: string): boolean => {\n if (a === b) return true;\n if (a.startsWith(b) && a[b.length] === '-') return true;\n if (b.startsWith(a) && b[a.length] === '-') return true;\n return false;\n };\n return allDirs.filter((d) => isAnchoredMatch(d.toLowerCase(), lowerEncoded));\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport readline from 'readline';\nimport type { ClaudeSessionInfo } from '@crc/shared';\nimport { logger } from './logger.js';\nimport { encodeProjectPath, findProjectDirs } from './claude-path.js';\n\nconst CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');\n\ninterface ParsedSession {\n firstMessage: string;\n lastTimestamp: string;\n messageCount: number;\n model?: string;\n slug?: string;\n gitBranch?: string;\n cwd?: string; // actual project path from message metadata\n}\n\nasync function parseSessionFile(filePath: string): Promise<ParsedSession | null> {\n try {\n const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n let firstMessage = '';\n let lastTimestamp = '';\n let messageCount = 0;\n let model: string | undefined;\n let slug: string | undefined;\n let gitBranch: string | undefined;\n let cwd: string | undefined;\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n try {\n const obj = JSON.parse(line);\n\n if (obj.timestamp) lastTimestamp = obj.timestamp;\n if (obj.cwd && !cwd) cwd = obj.cwd;\n\n if (obj.type === 'user' && !firstMessage) {\n const content = obj.message?.content;\n if (typeof content === 'string') {\n firstMessage = content.slice(0, 200);\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (block?.type === 'text' && block.text) {\n firstMessage = block.text.slice(0, 200);\n break;\n }\n }\n }\n messageCount++;\n } else if (obj.type === 'user' || obj.type === 'assistant') {\n messageCount++;\n if (obj.type === 'assistant') {\n if (obj.message?.model) model = obj.message.model;\n if (obj.slug) slug = obj.slug;\n }\n if (obj.gitBranch) gitBranch = obj.gitBranch;\n }\n } catch {\n // skip unparseable lines\n }\n }\n\n if (!firstMessage && messageCount === 0) return null;\n\n return { firstMessage: firstMessage || '(no message)', lastTimestamp, messageCount, model, slug, gitBranch, cwd };\n } catch (err: any) {\n logger.error({ filePath, error: err.message }, 'Failed to parse session file');\n return null;\n }\n}\n\nexport async function listClaudeSessions(filterProjectPath?: string): Promise<ClaudeSessionInfo[]> {\n const sessions: ClaudeSessionInfo[] = [];\n\n if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) return sessions;\n\n // Per-entry stat that never throws — a broken symlink / EACCES / ENOENT race\n // (Claude rotating files) on one entry must not reject the whole listing.\n const isDirSafe = (d: string): boolean => {\n try {\n return fs.statSync(path.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();\n } catch {\n return false;\n }\n };\n\n let allEntries: string[];\n try {\n allEntries = fs.readdirSync(CLAUDE_PROJECTS_DIR);\n } catch {\n return sessions;\n }\n\n let projectDirs: string[];\n if (filterProjectPath) {\n // Find dirs that could match this project path\n // Since dir names use '-' for '/', we match by checking if the cwd in sessions matches\n const allDirs = allEntries.filter(isDirSafe);\n projectDirs = findProjectDirs(allDirs, filterProjectPath);\n if (projectDirs.length === 0) return sessions;\n } else {\n projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith('-private-'));\n }\n\n for (const dirName of projectDirs) {\n const dirPath = path.join(CLAUDE_PROJECTS_DIR, dirName);\n\n let files: string[];\n try {\n files = fs.readdirSync(dirPath).filter((f) => f.endsWith('.jsonl'));\n } catch {\n continue;\n }\n\n for (const file of files) {\n const sessionId = file.replace('.jsonl', '');\n const parsed = await parseSessionFile(path.join(dirPath, file));\n if (!parsed) continue;\n\n // Use cwd from session metadata (accurate), fall back to dir name decode\n const projectPath = parsed.cwd || dirName.replace(/^-/, '/').replace(/-/g, '/');\n\n sessions.push({\n sessionId,\n projectPath,\n firstMessage: parsed.firstMessage,\n lastTimestamp: parsed.lastTimestamp,\n messageCount: parsed.messageCount,\n model: parsed.model,\n slug: parsed.slug,\n gitBranch: parsed.gitBranch,\n });\n }\n }\n\n // Sort by most recent first\n sessions.sort((a, b) => (b.lastTimestamp > a.lastTimestamp ? 1 : -1));\n\n return sessions;\n}\n","import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport readline from 'readline';\nimport { logger } from './logger.js';\nimport { encodeProjectPath, findProjectDirs } from './claude-path.js';\n\nconst CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');\n\nexport interface ConversationMessage {\n type: 'user' | 'assistant' | 'tool_use' | 'tool_result';\n content: string;\n toolName?: string;\n toolId?: string;\n timestamp?: string;\n model?: string;\n}\n\nexport interface ConversationData {\n sessionId: string;\n messages: ConversationMessage[];\n totalLines: number;\n}\n\n/**\n * Resolve the on-disk project directory for a given project path.\n * Tries exact encoding, then a case-insensitive match, then a cwd-based\n * scanning fallback (via findProjectDirs) so a project still resolves when\n * the exact encoded name doesn't match.\n */\nfunction findProjectDir(projectPath: string): string | null {\n const encoded = encodeProjectPath(projectPath);\n\n const dirPath = path.join(CLAUDE_PROJECTS_DIR, encoded);\n if (fs.existsSync(dirPath)) return dirPath;\n\n if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) return null;\n\n // List dirs safely — a single bad entry (broken symlink / EACCES / ENOENT\n // race while Claude rotates files) must not reject the whole resolution.\n let allDirs: string[];\n try {\n allDirs = fs.readdirSync(CLAUDE_PROJECTS_DIR).filter((d) => {\n try {\n return fs.statSync(path.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();\n } catch {\n return false;\n }\n });\n } catch {\n return null;\n }\n\n // Case-insensitive + anchored scanning fallback.\n const matches = findProjectDirs(allDirs, projectPath);\n if (matches.length > 0) {\n return path.join(CLAUDE_PROJECTS_DIR, matches[0]);\n }\n return null;\n}\n\n/**\n * Locate a session transcript by its (globally unique) session id, scanning every\n * project dir. Claude stores a session's transcript under the dir where the session\n * STARTED — which differs from the hook-reported cwd when the user `cd`s mid-session\n * (a completion notification then deep-links with the current cwd, whose encoded dir\n * may not exist). The session id disambiguates reliably, so we fall back to it.\n */\nfunction findSessionFileById(sessionId: string): string | null {\n if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null; // guard against path traversal\n if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) return null;\n\n let dirs: string[];\n try {\n dirs = fs.readdirSync(CLAUDE_PROJECTS_DIR);\n } catch {\n return null;\n }\n\n for (const dir of dirs) {\n const candidate = path.join(CLAUDE_PROJECTS_DIR, dir, `${sessionId}.jsonl`);\n try {\n if (fs.statSync(candidate).isFile()) return candidate;\n } catch {\n // not here (or rotated away mid-scan) — keep looking\n }\n }\n return null;\n}\n\nfunction findLatestSessionFile(projectPath: string): { filePath: string; sessionId: string } | null {\n const dirPath = findProjectDir(projectPath);\n\n if (!dirPath) return null;\n\n let entries: string[];\n try {\n entries = fs.readdirSync(dirPath);\n } catch {\n return null;\n }\n\n const files = entries\n .filter((f) => f.endsWith('.jsonl') && !f.includes('/'))\n .map((f) => {\n // A per-entry stat can throw (file rotated away mid-scan); skip on error.\n try {\n return { name: f, mtime: fs.statSync(path.join(dirPath, f)).mtimeMs };\n } catch {\n return null;\n }\n })\n .filter((f): f is { name: string; mtime: number } => f !== null)\n .sort((a, b) => b.mtime - a.mtime);\n\n if (files.length === 0) return null;\n\n return {\n filePath: path.join(dirPath, files[0].name),\n sessionId: files[0].name.replace('.jsonl', ''),\n };\n}\n\nexport async function readConversation(\n projectPath: string,\n afterLine: number = 0,\n specificSessionId?: string\n): Promise<ConversationData | null> {\n let filePath: string;\n let sessionId: string;\n\n if (specificSessionId) {\n sessionId = specificSessionId;\n // Fast path: the transcript sits under the encoded cwd (normal case).\n const dirPath = findProjectDir(projectPath);\n const direct = dirPath ? path.join(dirPath, `${specificSessionId}.jsonl`) : null;\n if (direct && fs.existsSync(direct)) {\n filePath = direct;\n } else {\n // Fallback: the session id is unique — find it wherever it actually lives\n // (handles cwd drift during a session, subagent cwds, case/format skew).\n const byId = findSessionFileById(specificSessionId);\n if (!byId) return null;\n filePath = byId;\n }\n } else {\n const found = findLatestSessionFile(projectPath);\n if (!found) return null;\n filePath = found.filePath;\n sessionId = found.sessionId;\n }\n\n try {\n const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n const messages: ConversationMessage[] = [];\n let lineNum = 0;\n\n for await (const line of rl) {\n lineNum++;\n if (lineNum <= afterLine) continue;\n if (!line.trim()) continue;\n\n try {\n const obj = JSON.parse(line);\n\n if (obj.type === 'user') {\n const content = obj.message?.content;\n let text = '';\n if (typeof content === 'string') {\n text = content;\n } else if (Array.isArray(content)) {\n text = content\n .filter((b: any) => b.type === 'text')\n .map((b: any) => b.text)\n .join('\\n');\n }\n if (text) {\n messages.push({\n type: 'user',\n content: text,\n timestamp: obj.timestamp,\n });\n }\n } else if (obj.type === 'assistant') {\n const content = obj.message?.content;\n if (!Array.isArray(content)) continue;\n\n for (const block of content) {\n if (block.type === 'text' && block.text?.trim()) {\n messages.push({\n type: 'assistant',\n content: block.text,\n timestamp: obj.timestamp,\n model: obj.message?.model,\n });\n } else if (block.type === 'tool_use') {\n // Summarize tool input\n let inputSummary = '';\n const input = block.input || {};\n if (input.command) inputSummary = input.command;\n else if (input.file_path) inputSummary = input.file_path;\n else if (input.pattern) inputSummary = input.pattern;\n else if (input.prompt) inputSummary = input.prompt.slice(0, 100);\n else {\n const keys = Object.keys(input);\n if (keys.length > 0) inputSummary = keys.join(', ');\n }\n\n messages.push({\n type: 'tool_use',\n content: inputSummary,\n toolName: block.name,\n toolId: block.id,\n timestamp: obj.timestamp,\n });\n } else if (block.type === 'tool_result') {\n let resultText = '';\n if (typeof block.content === 'string') {\n resultText = block.content;\n } else if (Array.isArray(block.content)) {\n resultText = block.content\n .filter((b: any) => b.type === 'text')\n .map((b: any) => b.text)\n .join('\\n');\n }\n if (resultText) {\n messages.push({\n type: 'tool_result',\n content: resultText.length > 500 ? resultText.slice(0, 500) + '...' : resultText,\n toolId: block.tool_use_id,\n timestamp: obj.timestamp,\n });\n }\n }\n }\n }\n } catch {\n // skip unparseable lines\n }\n }\n\n return { sessionId, messages, totalLines: lineNum };\n } catch (err: any) {\n logger.error({ filePath, error: err.message }, 'Failed to read conversation');\n return null;\n }\n}\n","#!/usr/bin/env node\nimport { io } from 'socket.io-client';\nimport {\n AGENT_HEARTBEAT,\n TERMINAL_OPEN,\n TERMINAL_INPUT,\n TERMINAL_RESIZE,\n TERMINAL_CLOSE,\n TERMINAL_OUTPUT,\n TERMINAL_EXIT,\n SESSION_SYNC,\n SESSION_SYNC_RESULT,\n SESSION_ATTACH,\n SESSION_DETACH,\n SESSION_BUFFER,\n FILES_LIST,\n FILES_LIST_RESULT,\n FILES_DOWNLOAD,\n FILES_DOWNLOAD_READY,\n FILES_DOWNLOAD_ERROR,\n VPN_LIST,\n VPN_CONNECT,\n VPN_DISCONNECT,\n VPN_UPDATE,\n CLAUDE_SESSIONS_LIST,\n CLAUDE_SESSIONS_RESULT,\n CLAUDE_CONV_READ,\n CLAUDE_CONV_DATA,\n CLAUDE_HOOK,\n TMUX_LIST,\n TMUX_LIST_RESULT,\n AGENT_EXEC,\n AGENT_EXEC_RESULT,\n HEARTBEAT_INTERVAL,\n type TerminalOpenPayload,\n type TerminalInputPayload,\n type TerminalResizePayload,\n type TerminalClosePayload,\n type SessionAttachPayload,\n type SessionDetachPayload,\n type FilesListPayload,\n type FilesDownloadPayload,\n type VpnConnectPayload,\n type VpnDisconnectPayload,\n type ClaudeSessionsListPayload,\n type ClaudeConvReadPayload,\n type ClaudeHookPayload,\n type TmuxListPayload,\n type AgentExecPayload,\n} from '@crc/shared';\n\nimport { loadConfig, isConfigured, LOCAL_CONTROL_PORT } from './config.js';\nimport { logger } from './logger.js';\nimport { installClaudeHooks, normalizeClaudeHook, lastAssistantSummary } from './claude-plugin-installer.js';\nimport { startLocalControl } from './local-control.js';\nimport { listTmuxSessions, buildTmuxLaunch } from './tmux.js';\nimport { detectShell } from './shell.js';\nimport { buildHeartbeat } from './heartbeat.js';\nimport { listDirectory, downloadFile } from './file-explorer.js';\nimport { getProfiles, connectVpn, disconnectVpn } from './vpn-manager.js';\nimport { listClaudeSessions } from './claude-sessions.js';\nimport { readConversation } from './claude-conversation.js';\nimport {\n createTerminalSession,\n writeToSession,\n resizeSession,\n closeSession,\n closeAllSessions,\n detachAllSessions,\n detachSession,\n attachSession,\n getAliveSessionIds,\n reapDetachedSessions,\n} from './terminal-manager.js';\n\nif (!isConfigured()) {\n console.error('No agent configured. Run: crc-agent setup');\n process.exit(1);\n}\n\nconst config = loadConfig();\nlogger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, 'Starting agent');\n\n// --- Process-level safety nets: keep the agent (and its PTYs) alive on unexpected errors ---\nprocess.on('unhandledRejection', (reason) => {\n logger.error({ reason }, 'Unhandled promise rejection (agent kept alive)');\n});\n\nprocess.on('uncaughtException', (err) => {\n logger.error({ error: err instanceof Error ? err.message : String(err) }, 'Uncaught exception (agent kept alive)');\n});\n\n// Install Claude Code notify hooks into ~/.claude/settings.json (idempotent).\n// On Windows these run via Git Bash (when Git for Windows is installed).\ninstallClaudeHooks(LOCAL_CONTROL_PORT);\n\nconst socket = io(config.serverUrl + '/agent', {\n auth: { agentId: config.agentId, secret: config.secret },\n transports: ['websocket'],\n reconnection: true,\n reconnectionDelay: 1000,\n reconnectionDelayMax: 30000,\n reconnectionAttempts: Infinity,\n});\n\nsocket.on('connect', () => {\n logger.info('Connected to server');\n socket.emit(AGENT_HEARTBEAT, buildHeartbeat(config.homeDir));\n});\n\nsocket.on('connect_error', (err) => {\n logger.error({ error: err.message }, 'Connection error');\n});\n\nsocket.on('disconnect', (reason) => {\n logger.warn({ reason }, 'Disconnected from server');\n // Do NOT kill sessions — just detach all so they keep buffering\n detachAllSessions();\n});\n\n// --- Heartbeat loop ---\nsetInterval(() => {\n if (socket.connected) {\n socket.emit(AGENT_HEARTBEAT, buildHeartbeat(config.homeDir));\n }\n}, HEARTBEAT_INTERVAL);\n\n// --- Detached session reaper: kill PTYs that have been detached longer than 6 hours ---\nconst SESSION_MAX_IDLE_MS = 6 * 60 * 60 * 1000;\nconst reaperInterval = setInterval(() => {\n const reaped = reapDetachedSessions(SESSION_MAX_IDLE_MS);\n if (reaped.length > 0) {\n logger.info({ sessionIds: reaped, count: reaped.length }, 'Reaped detached sessions');\n }\n}, 60_000);\n// Don't let the reaper keep the process alive on its own.\nreaperInterval.unref();\n\n// --- Local control endpoint: Claude Code notify hooks POST here (works even\n// when Claude runs in Warp/tmux, outside a CRC terminal) -> forward to the\n// server, which sends Web Push + an in-app toast. Pure loopback HTTP, so it\n// runs on every platform. ---\nstartLocalControl(LOCAL_CONTROL_PORT, (raw: ClaudeHookPayload) => {\n const payload = normalizeClaudeHook(raw);\n if (!payload) return;\n logger.info(\n { rawEvent: (raw as any)?.hook_event_name || (raw as any)?.event, mapped: payload.event, project: payload.projectPath || null },\n 'Claude hook received'\n );\n\n const transcript = (raw as any)?.transcript_path;\n if (payload.event === 'stop' && typeof transcript === 'string') {\n // Give Claude ~600ms to finish flushing the final assistant message to the\n // transcript before we read it (otherwise we grab the PREVIOUS response).\n setTimeout(() => {\n payload.response = lastAssistantSummary(transcript);\n if (socket.connected) socket.emit(CLAUDE_HOOK, payload);\n }, 600);\n } else if (socket.connected) {\n socket.emit(CLAUDE_HOOK, payload);\n }\n});\n\n// --- Terminal event handlers ---\nsocket.on(TERMINAL_OPEN, (payload: TerminalOpenPayload) => {\n const { sessionId, cols, rows, tmux, launch } = payload;\n if (!sessionId) return;\n\n // If a tmux session was requested, attach (or create) it so the session is\n // shared with Warp/tmux on the PC (via WSL on Windows, native tmux elsewhere).\n const launchSpec = tmux ? buildTmuxLaunch(tmux, launch, detectShell(config.shell)) : undefined;\n\n createTerminalSession(\n sessionId,\n cols,\n rows,\n config.shell,\n config.homeDir,\n (sid, data) => {\n socket.emit(TERMINAL_OUTPUT, { sessionId: sid, data });\n },\n (sid, exitCode) => {\n socket.emit(TERMINAL_EXIT, { sessionId: sid, exitCode });\n },\n launchSpec\n );\n});\n\n// --- tmux session list ---\nsocket.on(TMUX_LIST, async (payload: TmuxListPayload) => {\n const { requestId } = payload;\n if (!requestId) return;\n try {\n const sessions = await listTmuxSessions();\n socket.emit(TMUX_LIST_RESULT, { requestId, sessions });\n } catch (err: any) {\n socket.emit(TMUX_LIST_RESULT, { requestId, sessions: [], error: err?.message || 'tmux list failed' });\n }\n});\n\nsocket.on(TERMINAL_INPUT, (payload: TerminalInputPayload) => {\n writeToSession(payload.sessionId, payload.data);\n});\n\nsocket.on(TERMINAL_RESIZE, (payload: TerminalResizePayload) => {\n resizeSession(payload.sessionId, payload.cols, payload.rows);\n});\n\nsocket.on(TERMINAL_CLOSE, (payload: TerminalClosePayload) => {\n closeSession(payload.sessionId);\n});\n\n// --- Session lifecycle handlers ---\nsocket.on(SESSION_SYNC, () => {\n const aliveIds = getAliveSessionIds();\n socket.emit(SESSION_SYNC_RESULT, { sessionIds: aliveIds });\n logger.info({ count: aliveIds.length }, 'Session sync responded');\n});\n\nsocket.on(SESSION_ATTACH, (payload: SessionAttachPayload) => {\n const buffered = attachSession(payload.sessionId, payload.cols, payload.rows);\n if (buffered) {\n socket.emit(SESSION_BUFFER, { sessionId: payload.sessionId, data: buffered });\n }\n});\n\nsocket.on(SESSION_DETACH, (payload: SessionDetachPayload) => {\n detachSession(payload.sessionId);\n});\n\n// --- File explorer handlers ---\nsocket.on(FILES_LIST, (payload: FilesListPayload) => {\n const { requestId, path: dirPath } = payload;\n if (!requestId) return;\n const result = listDirectory(dirPath);\n socket.emit(FILES_LIST_RESULT, {\n requestId,\n path: dirPath,\n entries: result.entries,\n error: result.error,\n });\n});\n\nsocket.on(FILES_DOWNLOAD, async (payload: FilesDownloadPayload) => {\n const { requestId, path: filePath } = payload;\n if (!requestId) return;\n const result = await downloadFile(filePath, config.serverUrl, config.secret, config.agentId);\n if ('error' in result) {\n logger.error({ requestId, error: result.error }, 'File download failed');\n socket.emit(FILES_DOWNLOAD_ERROR, { requestId, error: result.error });\n return;\n }\n socket.emit(FILES_DOWNLOAD_READY, {\n requestId,\n fileId: result.fileId,\n fileName: result.fileName,\n downloadUrl: result.downloadUrl,\n size: result.size,\n });\n});\n\n// --- VPN handlers (disabled on Windows — requires admin) ---\nconst vpnProfiles = process.platform === 'win32' ? [] : (config.vpn?.profiles || []);\n\nsocket.on(VPN_LIST, async () => {\n const profiles = await getProfiles(vpnProfiles);\n socket.emit(VPN_UPDATE, { profiles });\n});\n\nsocket.on(VPN_CONNECT, async (payload: VpnConnectPayload) => {\n const profiles = await connectVpn(vpnProfiles, payload.profileId);\n socket.emit(VPN_UPDATE, { profiles });\n});\n\nsocket.on(VPN_DISCONNECT, async (payload: VpnDisconnectPayload) => {\n const profiles = await disconnectVpn(vpnProfiles, payload.profileId);\n socket.emit(VPN_UPDATE, { profiles });\n});\n\n// --- Agent exec handler (one-shot commands like git pull) ---\nsocket.on(AGENT_EXEC, async (payload: AgentExecPayload) => {\n const { requestId, command, cwd } = payload;\n if (!requestId) return;\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n try {\n const { stdout, stderr } = await execAsync(command, { cwd, timeout: 30000 });\n socket.emit(AGENT_EXEC_RESULT, { requestId, stdout: stdout || '', stderr: stderr || '' });\n } catch (err: any) {\n socket.emit(AGENT_EXEC_RESULT, {\n requestId,\n stdout: err.stdout || '',\n stderr: err.stderr || '',\n error: err.message,\n });\n }\n});\n\n// --- Claude Conversation handler ---\nsocket.on(CLAUDE_CONV_READ, async (payload: ClaudeConvReadPayload) => {\n try {\n const result = await readConversation(payload.projectPath, payload.afterLine || 0, payload.sessionId);\n if (result) {\n socket.emit(CLAUDE_CONV_DATA, {\n sessionId: result.sessionId,\n messages: result.messages,\n totalLines: result.totalLines,\n });\n } else {\n socket.emit(CLAUDE_CONV_DATA, {\n sessionId: '',\n messages: [],\n totalLines: 0,\n error: 'No conversation found',\n });\n }\n } catch (err: any) {\n logger.error({ error: err?.message }, 'Failed to read conversation');\n socket.emit(CLAUDE_CONV_DATA, {\n sessionId: '',\n messages: [],\n totalLines: 0,\n error: err?.message || 'Failed to read conversation',\n });\n }\n});\n\n// --- Claude Sessions handler ---\nsocket.on(CLAUDE_SESSIONS_LIST, async (payload: ClaudeSessionsListPayload) => {\n try {\n const sessions = await listClaudeSessions(payload.projectPath);\n socket.emit(CLAUDE_SESSIONS_RESULT, { sessions });\n } catch (err: any) {\n logger.error({ error: err?.message }, 'Failed to list Claude sessions');\n socket.emit(CLAUDE_SESSIONS_RESULT, { sessions: [], error: err?.message || 'Failed to list sessions' });\n }\n});\n\n// Graceful shutdown\nprocess.on('SIGINT', () => {\n logger.info('Shutting down...');\n closeAllSessions();\n socket.disconnect();\n process.exit(0);\n});\n\nprocess.on('SIGTERM', () => {\n logger.info('Shutting down...');\n closeAllSessions();\n socket.disconnect();\n process.exit(0);\n});\n","#!/usr/bin/env node\n// `crc-agent` entry point. Dispatches subcommands before the agent boots:\n// crc-agent setup [--token <t>] -> enrollment flow (setup.ts)\n// crc-agent -> start the agent (index.ts)\n// setup.ts and index.ts both run on import, so dispatching is just picking\n// which module to load. Kept separate from index.ts so `crc-agent setup` never\n// drags in socket/pty code paths before enrollment.\nconst cmd = process.argv[2];\n\nif (cmd === 'setup') {\n // Drop the subcommand so setup's own arg parsing sees only its flags.\n process.argv.splice(2, 1);\n import('./setup.js');\n} else if (cmd === '--help' || cmd === '-h') {\n console.log(\n [\n 'Usage: crc-agent [setup [--token <token>]]',\n '',\n 'Run with no arguments to start the agent.',\n 'Enroll this machine first with: crc-agent setup --help',\n ].join('\\n')\n );\n} else {\n import('./index.js');\n}\n"],"mappings":"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","names":["os","crypto","fs","config","path","fs","readline","import_fs","pino","import_fs","import_path","http","cmd","import_child_process","import_fs","import_path","import_os","os","path","import_os","import_path","import_child_process","path","fs","import_fs","import_path","import_os","os","shq","cmd","path","tunnelName","stdout","confPath","fs","p","import_child_process","import_util","import_fs","import_path","import_os","os","fs","readline","sessions","path","import_fs","import_path","import_os","import_readline","os","path","CLAUDE_PROJECTS_DIR","fs","readline","import_fs","import_path","import_os","import_readline","os","sessions","exec","promisify","execAsync"]}
package/dist/index.js CHANGED
@@ -1181,6 +1181,24 @@ function findProjectDir(projectPath) {
1181
1181
  }
1182
1182
  return null;
1183
1183
  }
1184
+ function findSessionFileById(sessionId) {
1185
+ if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1186
+ if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1187
+ let dirs;
1188
+ try {
1189
+ dirs = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1190
+ } catch {
1191
+ return null;
1192
+ }
1193
+ for (const dir of dirs) {
1194
+ const candidate = import_path8.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1195
+ try {
1196
+ if (import_fs7.default.statSync(candidate).isFile()) return candidate;
1197
+ } catch {
1198
+ }
1199
+ }
1200
+ return null;
1201
+ }
1184
1202
  function findLatestSessionFile(projectPath) {
1185
1203
  const dirPath = findProjectDir(projectPath);
1186
1204
  if (!dirPath) return null;
@@ -1207,11 +1225,16 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1207
1225
  let filePath;
1208
1226
  let sessionId;
1209
1227
  if (specificSessionId) {
1210
- const dirPath = findProjectDir(projectPath);
1211
- if (!dirPath) return null;
1212
- filePath = import_path8.default.join(dirPath, `${specificSessionId}.jsonl`);
1213
1228
  sessionId = specificSessionId;
1214
- if (!import_fs7.default.existsSync(filePath)) return null;
1229
+ const dirPath = findProjectDir(projectPath);
1230
+ const direct = dirPath ? import_path8.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1231
+ if (direct && import_fs7.default.existsSync(direct)) {
1232
+ filePath = direct;
1233
+ } else {
1234
+ const byId = findSessionFileById(specificSessionId);
1235
+ if (!byId) return null;
1236
+ filePath = byId;
1237
+ }
1215
1238
  } else {
1216
1239
  const found = findLatestSessionFile(projectPath);
1217
1240
  if (!found) return null;