@synkro-sh/cli 1.6.41 → 1.6.43

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../cli/installer/agentDetect.ts","../cli/installer/ccHookConfig.ts","../cli/installer/cursorHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/skillParser.ts","../cli/installer/hookScripts.ts","../cli/installer/hookScriptsTs.ts","../cli/auth/stub.ts","../cli/auth/index.ts","../cli/api/projects.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/repoConnect.ts","../cli/installer/promptFetcher.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.ts","../cli/commands/setupGithub.ts","../cli/commands/install.ts","../cli/local-cc/install.ts","../cli/commands/disconnect.ts","../cli/local-cc/turnLog.ts","../cli/local-cc/client.ts","../cli/commands/grade.ts","../cli/local-cc/pueue.ts","../cli/local-cc/settings.ts","../cli/commands/localCc.ts","../cli/commands/lifecycle.ts","../cli/commands/config.ts","../cli/bootstrap.js"],"sourcesContent":["/**\n * Detect which AI coding agents are installed on the user's machine.\n *\n * Returns a list of agents with their config paths so the installer\n * knows where to write hook configs.\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\n\nexport type AgentKind = 'claude_code' | 'cursor';\n\nexport interface DetectedAgent {\n kind: AgentKind;\n name: string;\n binaryPath?: string;\n configDir: string;\n settingsPath: string;\n version?: string;\n}\n\nfunction which(cmd: string): string | undefined {\n try {\n const result = execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();\n return result || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getVersion(cmd: string): string | undefined {\n try {\n const result = execSync(`${cmd} --version 2>&1`, { encoding: 'utf-8', timeout: 5000 }).trim();\n return result.split('\\n')[0];\n } catch {\n return undefined;\n }\n}\n\nexport function detectAgents(): DetectedAgent[] {\n const agents: DetectedAgent[] = [];\n const home = homedir();\n\n // Claude Code — require the binary on PATH; ~/.claude/ alone is not enough\n // (Synkro and other tools create that directory).\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary) {\n agents.push({\n kind: 'claude_code',\n name: 'Claude Code',\n binaryPath: claudeBinary,\n configDir: claudeConfigDir,\n settingsPath: join(claudeConfigDir, 'settings.json'),\n version: getVersion('claude'),\n });\n }\n\n // Cursor — require the binary on PATH; ~/.cursor/ alone is not enough.\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n if (cursorBinary) {\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version: getVersion('cursor'),\n });\n }\n\n return agents;\n}\n\nexport function findClaudeAuth(): { path: string; exists: boolean } {\n // CC stores OAuth token in macOS keychain (item: \"Claude Code-credentials\")\n // or in ~/.claude/auth.json on Linux. We don't extract it; we ask user to\n // run `claude setup-token` for headless use.\n const authJsonPath = join(homedir(), '.claude', 'auth.json');\n return { path: authJsonPath, exists: existsSync(authJsonPath) };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.claude/settings.json.\n *\n * Preserves any other hooks the user has configured (Corridor, Noma, custom\n * scripts, etc.) — we only add our entries, never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport interface SynkroHookConfig {\n bashJudgeScriptPath: string;\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n transcriptSyncScriptPath: string;\n userPromptSubmitScriptPath: string;\n installScanScriptPath: string;\n skipTranscriptSync?: boolean;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\ninterface HookEntry {\n matcher?: string;\n hooks: Array<Record<string, unknown>>;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CCSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n PostToolUse?: HookEntry[];\n SessionEnd?: HookEntry[];\n SessionStart?: HookEntry[];\n Stop?: HookEntry[];\n [k: string]: unknown;\n };\n [k: string]: unknown;\n}\n\nfunction readSettings(path: string): CCSettings {\n if (!existsSync(path)) return {};\n try {\n const raw = readFileSync(path, 'utf-8');\n return JSON.parse(raw) as CCSettings;\n } catch (err) {\n throw new Error(`Failed to parse ${path}: ${(err as Error).message}`);\n }\n}\n\nfunction writeSettingsAtomic(path: string, settings: CCSettings): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmpPath = `${path}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, path);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];\n return hooks.some((h: any) =>\n typeof h?.command === 'string' && h.command.includes('/.synkro/hooks/'),\n );\n}\n\nfunction removeSynkroEntries(events: CCSettings['hooks'] extends infer H ? H : never, eventName: string): void {\n if (!events) return;\n const arr = (events as any)[eventName];\n if (!Array.isArray(arr)) return;\n (events as any)[eventName] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\n/**\n * Merge Synkro hooks into the settings file. Idempotent — replaces any\n * existing Synkro-managed entries with the new versions.\n */\nexport function installCCHooks(settingsPath: string, config: SynkroHookConfig): void {\n const settings = readSettings(settingsPath);\n settings.hooks = settings.hooks ?? {};\n\n // Remove any prior Synkro entries (to support `synkro update`)\n removeSynkroEntries(settings.hooks as any, 'PreToolUse');\n removeSynkroEntries(settings.hooks as any, 'PostToolUse');\n removeSynkroEntries(settings.hooks as any, 'SessionEnd');\n removeSynkroEntries(settings.hooks as any, 'SessionStart');\n removeSynkroEntries(settings.hooks as any, 'UserPromptSubmit');\n // Also clean up any older `Stop`-event entry from earlier v1.6 builds.\n removeSynkroEntries(settings.hooks as any, 'Stop');\n\n settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];\n settings.hooks.PostToolUse = settings.hooks.PostToolUse ?? [];\n settings.hooks.SessionEnd = settings.hooks.SessionEnd ?? [];\n settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];\n settings.hooks.UserPromptSubmit = (settings.hooks.UserPromptSubmit as any[]) ?? [];\n\n // PreToolUse Bash → install scan (caches result for bash judge to read)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.installScanScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Bash/Read/Grep/Glob → command hook script (Cerebras-judged)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash|Read|Grep|Glob',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 30,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → two hooks in ONE entry so CC waits for all\n // before processing deny decisions. Each hook runs in parallel:\n // 1. edit-precheck: org rules grading on channel 1 (port 8929)\n // 2. cve-precheck: CVE/OSV dependency scan (curl, no LLM)\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 30,\n },\n {\n type: 'command',\n command: config.cwePrecheckScriptPath,\n timeout: 30,\n },\n {\n type: 'command',\n command: config.cvePrecheckScriptPath,\n timeout: 10,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Agent → scan subagent prompts against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'Agent',\n hooks: [\n {\n type: 'command',\n command: config.agentJudgeScriptPath,\n timeout: 30,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse ExitPlanMode → advisory plan review against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'ExitPlanMode',\n hooks: [\n {\n type: 'command',\n command: config.planJudgeScriptPath,\n timeout: 45,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PostToolUse Edit/Write removed — PreToolUse hooks handle grading now.\n\n // PostToolUse Bash → flips pending precheck_corrections row to 'allow'\n // once the bash command actually executed. Required for the bash trendline\n // (approved-vs-rejected) the dashboard reads from precheck_corrections.\n settings.hooks.PostToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashFollowupScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionEnd → end-of-session summary line (`[synkro] stop → N findings: ...`).\n // We use SessionEnd, not Stop, because Stop fires after every agent turn\n // (which would spam the user in interactive mode); SessionEnd fires once\n // when the session itself terminates.\n settings.hooks.SessionEnd.push({\n hooks: [\n {\n type: 'command',\n command: config.stopSummaryScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionStart → \"[synkro] session start → N open findings in this repo\" if any.\n settings.hooks.SessionStart.push({\n hooks: [\n {\n type: 'command',\n command: config.sessionStartScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // UserPromptSubmit → captures explicit consent keywords from user messages.\n // Writes a file-based grant so the next blocked tool call is allowed through.\n (settings.hooks.UserPromptSubmit as any[]).push({\n hooks: [\n {\n type: 'command',\n command: config.userPromptSubmitScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Stop → usage telemetry + optional transcript sync (fires after every agent turn).\n // Always installed: usage tracking is ungated; transcript sync is gated inside the script.\n settings.hooks.Stop = settings.hooks.Stop ?? [];\n removeSynkroEntries(settings.hooks as any, 'Stop');\n settings.hooks.Stop.push({\n hooks: [\n {\n type: 'command',\n command: config.transcriptSyncScriptPath,\n timeout: 3,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n writeSettingsAtomic(settingsPath, settings);\n}\n\n/**\n * Remove all Synkro-managed hook entries from settings.json.\n * Used by `synkro disconnect`.\n */\nexport function uninstallCCHooks(settingsPath: string): boolean {\n if (!existsSync(settingsPath)) return false;\n const settings = readSettings(settingsPath);\n if (!settings.hooks) return false;\n\n const events = ['PreToolUse', 'PostToolUse', 'SessionEnd', 'SessionStart', 'Stop', 'UserPromptSubmit'] as const;\n for (const evt of events) {\n removeSynkroEntries(settings.hooks as any, evt);\n }\n\n // If a hook event array is now empty, delete it\n for (const evt of events) {\n if (Array.isArray((settings.hooks as any)[evt]) && (settings.hooks as any)[evt].length === 0) {\n delete (settings.hooks as any)[evt];\n }\n }\n // If hooks object is now empty, delete it\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n writeSettingsAtomic(settingsPath, settings);\n return true;\n}\n\n/**\n * Check whether Synkro hooks are currently installed in settings.json.\n * Used by `synkro status`.\n */\nexport function inspectCCHooks(settingsPath: string): {\n installed: boolean;\n preToolUseBash: boolean;\n postToolUseEdit: boolean;\n sessionEnd: boolean;\n sessionStart: boolean;\n} {\n if (!existsSync(settingsPath)) {\n return { installed: false, preToolUseBash: false, postToolUseEdit: false, sessionEnd: false, sessionStart: false };\n }\n const settings = readSettings(settingsPath);\n const pre = (settings.hooks as any)?.PreToolUse ?? [];\n const post = (settings.hooks as any)?.PostToolUse ?? [];\n const sessionEndHooks = (settings.hooks as any)?.SessionEnd ?? [];\n const sessionStartHooks = (settings.hooks as any)?.SessionStart ?? [];\n const preToolUseBash = pre.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const postToolUseEdit = post.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionEnd = sessionEndHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionStart = sessionStartHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n return {\n installed: preToolUseBash || postToolUseEdit || sessionEnd || sessionStart,\n preToolUseBash,\n postToolUseEdit,\n sessionEnd,\n sessionStart,\n };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.cursor/hooks.json.\n *\n * Cursor hooks reuse the same TypeScript scripts as Claude Code (cc-*.ts),\n * run with SYNKRO_HOOK_FORMAT=cursor so _synkro-common translates CC JSON output\n * to Cursor's permission JSON for preToolUse. CC install is unchanged.\n */\nimport { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve, normalize } from 'node:path';\nimport { homedir } from 'node:os';\n\nexport interface CursorHookConfig {\n /** Cursor-specific bash/shell judge (preToolUse with Shell|Bash matcher) */\n bashJudgeScriptPath: string;\n /** Cursor-specific post-edit capture (afterFileEdit event shape) */\n editCaptureScriptPath: string;\n /** Capture afterAgentThought/afterAgentResponse before transcript redaction */\n agentCaptureScriptPath: string;\n /** Shared cc-*.ts scripts (invoked with SYNKRO_HOOK_FORMAT=cursor) */\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n userPromptSubmitScriptPath: string;\n transcriptSyncScriptPath: string;\n installScanScriptPath: string;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Run a shared cc-*.ts hook with Cursor output translation. */\nfunction cursorCcCmd(scriptPath: string): string {\n return 'env SYNKRO_HOOK_FORMAT=cursor bun run ' + shellQuote(scriptPath);\n}\n\nfunction bunRunCmd(scriptPath: string): string {\n return 'bun run ' + shellQuote(scriptPath);\n}\n\nconst ALLOWED_PARENT_DIRS = [\n resolve(homedir(), '.cursor'),\n resolve(homedir(), '.config', 'cursor'),\n];\n\nfunction validateHooksPath(path: string): string {\n const resolved = resolve(normalize(path));\n if (!ALLOWED_PARENT_DIRS.some(dir => resolved.startsWith(dir + '/') || resolved === dir)) {\n throw new Error(`Hooks path must be under ~/.cursor or ~/.config/cursor, got: ${resolved}`);\n }\n return resolved;\n}\n\ninterface CursorHookEntry {\n command: string;\n timeout?: number;\n failClosed?: boolean;\n matcher?: string;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CursorHooksFile {\n version?: number;\n hooks?: {\n [event: string]: CursorHookEntry[];\n };\n}\n\nfunction readHooksFile(rawPath: string): CursorHooksFile {\n const safePath = validateHooksPath(rawPath);\n try {\n const raw = readFileSync(safePath, 'utf-8');\n return JSON.parse(raw) as CursorHooksFile;\n } catch (err: any) {\n if (err?.code === 'ENOENT') return { version: 1, hooks: {} };\n throw new Error(`Failed to parse ${safePath}: ${(err as Error).message}`);\n }\n}\n\nfunction writeHooksFileAtomic(rawPath: string, data: CursorHooksFile): void {\n const safePath = validateHooksPath(rawPath);\n mkdirSync(dirname(safePath), { recursive: true });\n const tmpPath = `${safePath}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, safePath);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n return typeof entry?.command === 'string' && entry.command.includes('/.synkro/hooks/');\n}\n\nconst ALL_EVENTS = [\n 'sessionStart', 'sessionEnd', 'beforeSubmitPrompt', 'stop',\n 'beforeShellExecution', 'afterShellExecution',\n 'preToolUse', 'afterFileEdit', 'postToolUse',\n 'afterAgentThought', 'afterAgentResponse',\n];\n\nfunction removeSynkroEntries(hooks: CursorHooksFile['hooks'], event: string): void {\n if (!hooks) return;\n const arr = hooks[event];\n if (!Array.isArray(arr)) return;\n hooks[event] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\nfunction pushCcHook(\n hooks: CursorHooksFile['hooks'],\n event: string,\n scriptPath: string,\n opts: { timeout: number; matcher?: string; failClosed?: boolean },\n): void {\n hooks![event] = hooks![event] ?? [];\n hooks![event]!.push({\n command: cursorCcCmd(scriptPath),\n timeout: opts.timeout,\n failClosed: opts.failClosed ?? false,\n ...(opts.matcher ? { matcher: opts.matcher } : {}),\n [SYNKRO_MARKER]: true,\n });\n}\n\nexport function installCursorHooks(hooksJsonPath: string, config: CursorHookConfig): void {\n const file = readHooksFile(hooksJsonPath);\n file.version = file.version ?? 1;\n file.hooks = file.hooks ?? {};\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n const h = file.hooks;\n\n pushCcHook(h, 'sessionStart', config.sessionStartScriptPath, { timeout: 5 });\n pushCcHook(h, 'sessionEnd', config.stopSummaryScriptPath, { timeout: 10 });\n pushCcHook(h, 'beforeSubmitPrompt', config.userPromptSubmitScriptPath, { timeout: 5 });\n pushCcHook(h, 'stop', config.transcriptSyncScriptPath, { timeout: 3 });\n\n // beforeShellExecution: install scan runs first (caches result for bash judge)\n h.beforeShellExecution = h.beforeShellExecution ?? [];\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 60,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'afterShellExecution', config.bashFollowupScriptPath, { timeout: 10 });\n\n // preToolUse: install scan before bash judge (caches result for bash judge)\n h.preToolUse = h.preToolUse ?? [];\n h.preToolUse.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 60,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 15,\n failClosed: false,\n matcher: 'Shell|Bash|Read|ReadFile|Grep|Glob|terminal|run_terminal_cmd|execute_command|read_file|grep_search|file_search|list_dir|codebase_search|delete_file',\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'preToolUse', config.editPrecheckScriptPath, {\n timeout: 15,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cwePrecheckScriptPath, {\n timeout: 60,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cvePrecheckScriptPath, {\n timeout: 20,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.agentJudgeScriptPath, {\n timeout: 15,\n matcher: 'Agent|Task',\n });\n pushCcHook(h, 'preToolUse', config.planJudgeScriptPath, {\n timeout: 20,\n matcher: 'ExitPlanMode|SwitchMode|CreatePlan',\n });\n\n h.afterFileEdit = h.afterFileEdit ?? [];\n h.afterFileEdit.push({\n command: bunRunCmd(config.editCaptureScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'postToolUse', config.bashFollowupScriptPath, {\n timeout: 10,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command|delete_file',\n });\n\n h.afterAgentThought = h.afterAgentThought ?? [];\n h.afterAgentThought.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n h.afterAgentResponse = h.afterAgentResponse ?? [];\n h.afterAgentResponse.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n writeHooksFileAtomic(hooksJsonPath, file);\n}\n\nexport function uninstallCursorHooks(hooksJsonPath: string): boolean {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return false;\n }\n if (!file.hooks) return false;\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n for (const evt of ALL_EVENTS) {\n if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {\n delete file.hooks[evt];\n }\n }\n if (Object.keys(file.hooks).length === 0) {\n delete file.hooks;\n }\n\n writeHooksFileAtomic(hooksJsonPath, file);\n return true;\n}\n\nfunction preToolUseUsesScript(hooks: CursorHookEntry[] | undefined, scriptBasename: string): boolean {\n return (hooks ?? []).some((e) =>\n isSynkroEntry(e) && typeof e.command === 'string' && e.command.includes(scriptBasename),\n );\n}\n\nexport function inspectCursorHooks(hooksJsonPath: string): {\n installed: boolean;\n sessionStart: boolean;\n sessionEnd: boolean;\n beforeSubmitPrompt: boolean;\n stop: boolean;\n beforeShellExecution: boolean;\n afterShellExecution: boolean;\n preToolUse: boolean;\n preToolUseBash: boolean;\n preToolUseEdit: boolean;\n preToolUseCwe: boolean;\n preToolUseCve: boolean;\n preToolUseAgent: boolean;\n preToolUsePlan: boolean;\n afterFileEdit: boolean;\n postToolUse: boolean;\n} {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return {\n installed: false,\n sessionStart: false, sessionEnd: false, beforeSubmitPrompt: false, stop: false,\n beforeShellExecution: false, afterShellExecution: false,\n preToolUse: false, preToolUseBash: false, preToolUseEdit: false,\n preToolUseCwe: false, preToolUseCve: false, preToolUseAgent: false, preToolUsePlan: false,\n afterFileEdit: false, postToolUse: false,\n };\n }\n const h = file.hooks ?? {};\n const sessionStart = (h.sessionStart ?? []).some((e) => isSynkroEntry(e));\n const sessionEnd = (h.sessionEnd ?? []).some((e) => isSynkroEntry(e));\n const beforeSubmitPrompt = (h.beforeSubmitPrompt ?? []).some((e) => isSynkroEntry(e));\n const stop = (h.stop ?? []).some((e) => isSynkroEntry(e));\n const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry(e));\n const afterShellExecution = (h.afterShellExecution ?? []).some((e) => isSynkroEntry(e));\n const pre = h.preToolUse ?? [];\n const preToolUseBash = preToolUseUsesScript(pre, 'cc-bash-judge') || preToolUseUsesScript(pre, 'cursor-bash-judge');\n const preToolUseEdit = preToolUseUsesScript(pre, 'cc-edit-precheck') || preToolUseUsesScript(pre, 'cursor-edit-precheck');\n const preToolUseCwe = preToolUseUsesScript(pre, 'cc-cwe-precheck');\n const preToolUseCve = preToolUseUsesScript(pre, 'cc-cve-precheck');\n const preToolUseAgent = preToolUseUsesScript(pre, 'cc-agent-judge');\n const preToolUsePlan = preToolUseUsesScript(pre, 'cc-plan-judge');\n const preToolUse = preToolUseBash || preToolUseEdit || preToolUseCwe || preToolUseCve || preToolUseAgent || preToolUsePlan;\n const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry(e));\n const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry(e));\n return {\n installed: sessionStart || sessionEnd || beforeSubmitPrompt || stop\n || beforeShellExecution || afterShellExecution || preToolUse || afterFileEdit || postToolUse,\n sessionStart, sessionEnd, beforeSubmitPrompt, stop,\n beforeShellExecution, afterShellExecution,\n preToolUse, preToolUseBash, preToolUseEdit, preToolUseCwe, preToolUseCve, preToolUseAgent, preToolUsePlan,\n afterFileEdit, postToolUse,\n };\n}\n","/**\n * Atomically merge the Synkro guardrails MCP server entry into ~/.claude.json.\n *\n * CC's MCP config lives in ~/.claude.json (NOT ~/.claude/settings.json — those\n * are different files). It accepts an HTTP-transport server with a static\n * Authorization header set at config-write time. We register one entry,\n * `synkro-guardrails`, marked with `__synkro_managed__: true` so we can safely\n * remove it on `synkro disconnect` without touching the user's other servers.\n *\n * Mirrors the pattern in ccHookConfig.ts — read-modify-write atomically via\n * tmpfile + rename; preserve any other top-level keys; never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nconst SYNKRO_MARKER = '__synkro_managed__';\nconst SYNKRO_SERVER_NAME = 'synkro-guardrails';\nconst CC_CONFIG_PATH = join(homedir(), '.claude.json');\n\ninterface ClaudeJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\ninterface McpServerEntry {\n type?: 'http' | 'stdio' | 'sse';\n url?: string;\n command?: string;\n args?: string[];\n headers?: Record<string, string>;\n env?: Record<string, string>;\n [SYNKRO_MARKER]?: boolean;\n [k: string]: unknown;\n}\n\nfunction readClaudeJson(): ClaudeJson {\n if (!existsSync(CC_CONFIG_PATH)) return {};\n try {\n const raw = readFileSync(CC_CONFIG_PATH, 'utf-8');\n return JSON.parse(raw) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeClaudeJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CC_CONFIG_PATH), { recursive: true });\n const tmpPath = `${CC_CONFIG_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CC_CONFIG_PATH);\n}\n\nexport interface InstallMcpOptions {\n gatewayUrl: string; // e.g. http://localhost:8788\n // Long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails. Minted by\n // POST /api/v1/cli/mcp-token during install. We deliberately do NOT write\n // the WorkOS access token here anymore — that one expires in 5 min and\n // silently breaks the CC MCP connection.\n bearerToken: string;\n local?: boolean;\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.claude.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readClaudeJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Remove any prior Synkro-managed entry (so re-running install picks up\n // a refreshed JWT). Leave non-Synkro entries alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n const proxyScript = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'stdio',\n command: 'bun',\n args: ['run', proxyScript],\n [SYNKRO_MARKER]: true,\n };\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url: `stdio://${proxyScript}` };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'http',\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.claude.json.\n * Returns true if anything was removed.\n */\nexport function uninstallMcpConfig(): boolean {\n if (!existsSync(CC_CONFIG_PATH)) return false;\n const config = readClaudeJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n // If the mcpServers object is now empty, drop it to keep the file tidy.\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeClaudeJsonAtomic(config);\n return true;\n}\n\n/**\n * Inspect whether the Synkro MCP server entry is currently registered.\n */\nexport function inspectMcpConfig(): {\n installed: boolean;\n configPath: string;\n url?: string;\n} {\n if (!existsSync(CC_CONFIG_PATH)) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n const config = readClaudeJson();\n const entry = config.mcpServers?.[SYNKRO_SERVER_NAME];\n if (!entry || entry[SYNKRO_MARKER] !== true) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n return { installed: true, configPath: CC_CONFIG_PATH, url: entry.url };\n}\n\n// ─── Cursor MCP Config ───\n\nconst CURSOR_MCP_PATH = join(homedir(), '.cursor', 'mcp.json');\n\ninterface CursorMcpJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\nfunction readCursorMcpJson(): CursorMcpJson {\n if (!existsSync(CURSOR_MCP_PATH)) return {};\n try {\n const raw = readFileSync(CURSOR_MCP_PATH, 'utf-8');\n return JSON.parse(raw) as CursorMcpJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeCursorMcpJsonAtomic(config: CursorMcpJson): void {\n mkdirSync(dirname(CURSOR_MCP_PATH), { recursive: true });\n const tmpPath = `${CURSOR_MCP_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CURSOR_MCP_PATH);\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.cursor/mcp.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installCursorMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readCursorMcpJson();\n config.mcpServers = config.mcpServers ?? {};\n\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n // Honors SYNKRO_MCP_PORT so the containerised deployment can register\n // its host-mapped port (e.g. 18931) instead of the bare-host default.\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n const url = `http://127.0.0.1:${port}/`;\n const jwtPath = join(homedir(), '.synkro', '.mcp-jwt');\n let jwt = '';\n try { jwt = readFileSync(jwtPath, 'utf-8').trim(); } catch {}\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n ...(jwt ? { headers: { Authorization: `Bearer ${jwt}` } } : {}),\n [SYNKRO_MARKER]: true,\n };\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.cursor/mcp.json.\n */\nexport function uninstallCursorMcpConfig(): boolean {\n if (!existsSync(CURSOR_MCP_PATH)) return false;\n const config = readCursorMcpJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeCursorMcpJsonAtomic(config);\n return true;\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport function resolveSkillPaths(skills: string[], repoRoot: string): string[] {\n return skills\n .filter(s => s.endsWith('.md'))\n .map(s => resolve(repoRoot, s))\n .filter(p => existsSync(p));\n}\n","// :)\n/**\n * Bash hook scripts for Cursor IDE adapter and shared common utilities.\n *\n * CC hooks have been moved to hookScriptsTs.ts (TypeScript + Bun runtime).\n * This file retains the bash common script (sourced by Cursor hooks) and\n * the Cursor-specific adapter scripts.\n */\n\nexport const SYNKRO_COMMON_SCRIPT = `#!/bin/bash\n# Shared Synkro hook utilities — sourced by all hook scripts.\n\nsynkro_log() { echo \"[synkro] $1\" >&2; }\n\n# Load config\n_SYNKRO_CONFIG=\"$HOME/.synkro/config.env\"\nif [ -f \"$_SYNKRO_CONFIG\" ]; then\n set -a; . \"$_SYNKRO_CONFIG\"; set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nsynkro_load_jwt() {\n if [ ! -f \"$CREDS_PATH\" ]; then echo \"\"; return 1; fi\n jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null\n}\n\nsynkro_refresh_jwt() {\n # Lock via mkdir (atomic on all Unix including macOS — no flock needed)\n local lockdir=\"\\${CREDS_PATH}.lockdir\"\n if ! mkdir \"$lockdir\" 2>/dev/null; then\n # Another hook is refreshing — wait and re-read\n local _w=0\n while [ -d \"$lockdir\" ] && [ $_w -lt 5 ]; do sleep 0.5; _w=$((_w+1)); done\n JWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n return 0\n fi\n trap \"rmdir \\\\\"$lockdir\\\\\" 2>/dev/null\" RETURN\n\n # Re-check expiry — another hook may have just refreshed\n local p2 exp2 now2\n p2=$(printf '%s' \"$JWT\" | cut -d. -f2)\n case $((\\${#p2} % 4)) in 2) p2=\"\\${p2}==\";; 3) p2=\"\\${p2}=\";; esac\n exp2=$(printf '%s' \"$p2\" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)\n now2=$(date -u +%s)\n if [ $((exp2 - now2)) -ge 60 ]; then return 0; fi\n\n local rt\n rt=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$rt\" ]; then return 1; fi\n local resp\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$(jq -n --arg rt \"$rt\" '{refresh_token:$rt}')\" \\\\\n --max-time 4 2>/dev/null)\n local new_at\n new_at=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_at\" ]; then return 1; fi\n local new_rt\n new_rt=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n [ -z \"$new_rt\" ] && new_rt=\"$rt\"\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n local existing\n existing=$(cat \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$existing\" ] || ! echo \"$existing\" | jq -e '.' >/dev/null 2>&1; then\n existing='{}'\n fi\n echo \"$existing\" | jq --arg at \"$new_at\" --arg rt \"$new_rt\" '. + {access_token:$at,refresh_token:$rt}' > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_at\"\n}\n\nsynkro_ensure_fresh_jwt() {\n [ -z \"$JWT\" ] && return 1\n local p exp now\n p=$(printf '%s' \"$JWT\" | cut -d. -f2)\n case $((\\${#p} % 4)) in 2) p=\"\\${p}==\";; 3) p=\"\\${p}=\";; esac\n exp=$(printf '%s' \"$p\" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)\n now=$(date -u +%s)\n [ $((exp - now)) -lt 60 ] && synkro_refresh_jwt\n}\n\nsynkro_detect_repo() {\n local cwd=\"\\${1:-.}\"\n if command -v git >/dev/null 2>&1; then\n local r\n r=$(git -C \"$cwd\" remote get-url origin 2>/dev/null || true)\n [ -n \"$r\" ] && echo \"$r\" | sed -E 's|^git@[^:]+:||; s|^https?://[^/]+/||; s|\\\\.git$||' && return\n fi\n echo \"\"\n}\n\nsynkro_channel_up() {\n (exec 3<>/dev/tcp/127.0.0.1/\\${SYNKRO_CHANNEL_PORT:-8929}) 2>/dev/null && exec 3<&- 3>&-\n}\n\n# Fetch hook config. Sets SYNKRO_CAPTURE_DEPTH, SYNKRO_TIER, SYNKRO_RULES, SYNKRO_SILENT, SYNKRO_POLICY_NAME.\n_SYNKRO_MCP_JWT_FILE=\"$HOME/.synkro/.mcp-jwt\"\n\nsynkro_load_config() {\n local resp\n resp=$(curl -sS \"\\${GATEWAY_URL}/api/v1/hook/config\\${1:+?$1}\" -H \"Authorization: Bearer $JWT\" --max-time 4 2>/dev/null || echo \"\")\n if [ -z \"$resp\" ]; then return; fi\n SYNKRO_CAPTURE_DEPTH=$(echo \"$resp\" | jq -r '.capture_depth // \"local_only\"' 2>/dev/null)\n SYNKRO_TIER=$(echo \"$resp\" | jq -r '.tier // \"standard\"' 2>/dev/null)\n SYNKRO_SILENT=$(echo \"$resp\" | jq -r '.silent_mode // false' 2>/dev/null)\n SYNKRO_POLICY_NAME=$(echo \"$resp\" | jq -r '.active_policy_name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$resp\" | jq -c '[.rules[]? | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n}\n\nsynkro_local_capture() {\n local event_json=\"$1\"\n local ts mcp_token line\n ts=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\")\n line=$(echo \"$event_json\" | jq -c --arg ts \"$ts\" '. + {_ts: $ts}' 2>/dev/null)\n [ -z \"$line\" ] && return\n [ -f \"$_SYNKRO_MCP_JWT_FILE\" ] || return\n mcp_token=$(cat \"$_SYNKRO_MCP_JWT_FILE\" 2>/dev/null)\n [ -z \"$mcp_token\" ] && return\n curl -fsS -m 2 -X POST \"http://127.0.0.1:\\${SYNKRO_MCP_PORT:-8931}/api/ingest\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer \\${mcp_token}\" \\\\\n -d \"$(jq -nc --argjson e \"$line\" '{data: $e}')\" >/dev/null 2>&1 &\n}\n\nsynkro_tag() {\n if [ \"$SYNKRO_SILENT\" = \"true\" ]; then echo \"[synkro:silent]\"; return; fi\n local route=\"\\${1:-\\$(synkro_route)}\"\n local rs=\"\\${SYNKRO_POLICY_NAME:-all}\"\n echo \"[synkro:\\${route}:\\${rs}]\"\n}\n\nsynkro_route() {\n [ \"$SYNKRO_CAPTURE_DEPTH\" = \"local_only\" ] && echo \"local\" && return\n synkro_channel_up && echo \"local\" && return\n echo \"cloud\"\n}\n\nSYNKRO_CONSENT_FILE=\"$HOME/.synkro/.local-consent\"\n\n_TAB=\\$(printf '\\\\t')\n\nsynkro_consent_grant() {\n local sid=\"\\$1\" hash=\"\\$2\"\n printf '%s\\\\t%s\\\\tactive\\\\n' \"$sid\" \"$hash\" >> \"$SYNKRO_CONSENT_FILE\" 2>/dev/null || true\n}\n\nsynkro_consent_has_active() {\n local sid=\"\\$1\" hash=\"\\$2\"\n grep -q \"^\\${sid}\\${_TAB}\\${hash}\\${_TAB}active\\$\" \"$SYNKRO_CONSENT_FILE\" 2>/dev/null\n}\n\nsynkro_consent_consume() {\n local sid=\"\\$1\" hash=\"\\$2\"\n [ ! -f \"$SYNKRO_CONSENT_FILE\" ] && return\n local tmp=\"\\${SYNKRO_CONSENT_FILE}.tmp\"\n local pat=\"\\${sid}\\${_TAB}\\${hash}\\${_TAB}active\"\n local rep=\"\\${sid}\\${_TAB}\\${hash}\\${_TAB}consumed\"\n awk -v p=\"$pat\" -v r=\"$rep\" '{if(\\$0==p)print r;else print}' \"$SYNKRO_CONSENT_FILE\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$SYNKRO_CONSENT_FILE\" 2>/dev/null || true\n}\n\nsynkro_post_with_retry() {\n local url=\"$1\" body=\"$2\" timeout=\"\\${3:-8}\"\n local resp\n resp=$(curl -sS -X POST \"$url\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$body\" --max-time \"$timeout\" 2>/dev/null || echo \"\")\n if echo \"$resp\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if synkro_refresh_jwt; then\n resp=$(curl -sS -X POST \"$url\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$body\" --max-time \"$timeout\" 2>/dev/null || echo \"\")\n fi\n fi\n echo \"$resp\"\n}\n`;\n\n\n// ─── Cursor IDE adapter scripts (legacy bash — install writes TypeScript from hookScriptsTs.ts) ───\n\nexport const CURSOR_BASH_JUDGE_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\nsynkro_ensure_fresh_jwt\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nCOMMAND=$(echo \"$PAYLOAD\" | jq -r '.command // empty' 2>/dev/null)\nif [ -z \"$COMMAND\" ]; then echo '{}'; exit 0; fi\n\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\n\nCMD_SHORT=$(printf '%s' \"$COMMAND\" | head -c 80)\nsynkro_log \"bashGuard checking: $CMD_SHORT\"\n\nsynkro_load_config\nif [ \"$SYNKRO_SILENT\" = \"true\" ]; then\n echo '{}'; exit 0\nfi\n\nBODY=$(jq -n \\\\\n --arg cmd \"$COMMAND\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg repo \"$GIT_REPO\" \\\\\n '{\n hook_event: \"PreToolUse\",\n tool_name: \"Bash\",\n tool_input: {command: $cmd},\n response_format: \"cursor\",\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n repo: (if ($repo | length) > 0 then $repo else null end)\n }')\n\nRESP=$(synkro_post_with_retry \"\\${GATEWAY_URL}/api/v1/hook/judge\" \"$BODY\" 6)\n\nif [ -z \"$RESP\" ]; then\n synkro_log \"bashGuard $CMD_SHORT → error (timeout)\"\n echo '{}'; exit 0\nfi\n\n# Server returns cursor-format directly in hook_response\nif echo \"$RESP\" | jq -e '.hook_response' >/dev/null 2>&1; then\n echo \"$RESP\" | jq -c '.hook_response'\nelse\n echo '{}'\nfi\nexit 0\n`;\n\nexport const CURSOR_EDIT_PRECHECK_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\nsynkro_ensure_fresh_jwt\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\n\nFILE_PATH=$(echo \"$PAYLOAD\" | jq -r '.tool_input.file_path // .tool_input.path // .tool_input.target_file // empty' 2>/dev/null)\nCONTENT=$(echo \"$PAYLOAD\" | jq -r '.tool_input.content // .tool_input.new_string // .tool_input.code_edit // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then echo '{}'; exit 0; fi\n\nBASENAME=$(basename \"$FILE_PATH\" 2>/dev/null || echo \"$FILE_PATH\")\nsynkro_log \"editGuard checking: $BASENAME\"\n\nsynkro_load_config\nif [ \"$SYNKRO_SILENT\" = \"true\" ]; then\n echo '{}'; exit 0\nfi\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg content \"$CONTENT\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg repo \"$GIT_REPO\" \\\\\n '{\n hook_event: \"PreToolUse\",\n tool_name: \"Edit\",\n tool_input: {file_path: $file_path, content: $content},\n file_path: $file_path,\n content: $content,\n response_format: \"cursor\",\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n repo: (if ($repo | length) > 0 then $repo else null end)\n }')\n\nRESP=$(synkro_post_with_retry \"\\${GATEWAY_URL}/api/v1/hook/judge\" \"$BODY\" 8)\n\nif [ -z \"$RESP\" ]; then\n synkro_log \"editGuard $BASENAME → error (timeout)\"\n echo '{}'; exit 0\nfi\n\nif echo \"$RESP\" | jq -e '.hook_response' >/dev/null 2>&1; then\n echo \"$RESP\" | jq -c '.hook_response'\nelse\n echo '{}'\nfi\nexit 0\n`;\n\nexport const CURSOR_EDIT_CAPTURE_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nFILE_PATH=$(echo \"$PAYLOAD\" | jq -r '.file_path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then echo '{}'; exit 0; fi\n\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // .workspace_roots[0] // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\nBASENAME=$(basename \"$FILE_PATH\" 2>/dev/null || echo \"$FILE_PATH\")\n\nFULL_PATH=\"$FILE_PATH\"\n[ -n \"$CWD\" ] && FULL_PATH=\"$CWD/$FILE_PATH\"\nFULL_CONTENT=\"\"\n[ -f \"$FULL_PATH\" ] && FULL_CONTENT=$(head -c 50000 \"$FULL_PATH\" 2>/dev/null || true)\n\nDEPS_JSON=\"{}\"\n_PKG_DIR=\"\\${CWD:-.}\"\nwhile [ \"$_PKG_DIR\" != \"/\" ]; do\n if [ -f \"$_PKG_DIR/package.json\" ]; then\n DEPS_JSON=$(jq -c '(.dependencies // {}) + (.devDependencies // {})' \"$_PKG_DIR/package.json\" 2>/dev/null || echo \"{}\")\n break\n fi\n _PKG_DIR=$(dirname \"$_PKG_DIR\")\ndone\n\nsynkro_log \"editScan $BASENAME\"\n\n(\n BODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" --arg content \"$FULL_CONTENT\" \\\\\n --arg session_id \"$SESSION_ID\" --arg cwd \"$CWD\" --arg repo \"$GIT_REPO\" \\\\\n --argjson deps \"$DEPS_JSON\" \\\\\n '{capture_type:\"edit_scan\",tool_input:{file_path:$file_path,content:$content},edit_verdict:{ok:true},dependencies:$deps}\n + (if ($session_id | length) > 0 then {session_id:$session_id} else {} end)\n + (if ($cwd | length) > 0 then {cwd:$cwd} else {} end)\n + (if ($repo | length) > 0 then {repo:$repo} else {} end)')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/hook/capture\" \\\\\n -H \"Content-Type: application/json\" -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" --max-time 10 >/dev/null 2>&1 || true\n) &\ndisown 2>/dev/null || true\n\necho '{}'\nexit 0\n`;\n\nexport const CURSOR_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in Shell|Bash|terminal|run_terminal_cmd|execute_command) ;; *) echo '{}'; exit 0 ;; esac\n\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\n\nIS_ERROR=$(echo \"$PAYLOAD\" | jq -r '.tool_result.is_error // false' 2>/dev/null)\nCMD=$(echo \"$PAYLOAD\" | jq -r '.tool_input.command // empty' 2>/dev/null)\nCMD_HASH=\"\"\nif [ -n \"$CMD\" ]; then\n CMD_HASH=$(printf '%s' \"$CMD\" | shasum -a 256 | cut -c1-16)\nfi\n\nif [ -n \"$CMD_HASH\" ] && [ -n \"$SESSION_ID\" ]; then\n if [ \"$IS_ERROR\" = \"false\" ]; then\n synkro_consent_consume \"$SESSION_ID\" \"$CMD_HASH\"\n else\n if ! synkro_consent_has_active \"$SESSION_ID\" \"$CMD_HASH\"; then\n synkro_consent_grant \"$SESSION_ID\" \"$CMD_HASH\"\n fi\n fi\nfi\n\nif [ -n \"$SESSION_ID\" ] && [ -n \"$TOOL_USE_ID\" ]; then\n (\n BODY=$(jq -n --arg sid \"$SESSION_ID\" --arg tid \"$TOOL_USE_ID\" \\\\\n --argjson err \"$IS_ERROR\" --arg ch \"$CMD_HASH\" \\\\\n '{capture_type:\"bash_followup\",session_id:$sid,tool_use_id:$tid,is_error:$err,command_hash:$ch}')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/hook/capture\" \\\\\n -H \"Content-Type: application/json\" -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" --max-time 3 >/dev/null 2>&1 || true\n ) &\n disown 2>/dev/null || true\nfi\n\necho '{}'\nexit 0\n`;\n","// :)\n/**\n * TypeScript hook scripts for Bun runtime — written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Each export is the full source code of a TypeScript file that runs via `#!/usr/bin/env bun`.\n * All grading, classification, CVE scanning, and persistence lives server-side.\n * Local mode: grade via local-cc channel; rules + embeddings from local PGLite only.\n */\n\nexport const SYNKRO_COMMON_TS = `\n// Shared Synkro hook utilities — imported by all hook scripts.\nimport { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, renameSync, openSync, closeSync, unlinkSync, readdirSync, statSync, createReadStream } from 'node:fs';\nimport { createInterface } from 'node:readline';\nimport { join, dirname, basename, extname, resolve as resolvePath } from 'node:path';\nimport { homedir } from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { constants as FS_CONSTANTS } from 'node:fs';\n\n// ─── Config ───\n\nconst HOME = homedir();\nconst CONFIG_PATH = join(HOME, '.synkro', 'config.env');\n\n// Load config.env into process.env\nif (existsSync(CONFIG_PATH)) {\n try {\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx < 1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n let val = trimmed.slice(eqIdx + 1).trim();\n // Strip surrounding quotes\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1);\n }\n process.env[key] = val;\n }\n } catch {}\n}\n\nconst ALLOWED_GATEWAY_HOSTS = new Set(['api.synkro.sh', 'localhost', '127.0.0.1']);\nfunction validateGatewayUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (!ALLOWED_GATEWAY_HOSTS.has(u.hostname)) return 'https://api.synkro.sh';\n return raw.replace(/\\\\/+$/, '');\n } catch {\n return 'https://api.synkro.sh';\n }\n}\nexport const GATEWAY_URL = validateGatewayUrl(process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh');\nexport const CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(HOME, '.synkro', 'credentials.json');\nconst LAST_PROMPT_FILE = join(HOME, '.synkro', '.last-prompt');\n\n// ─── Path Validation ───\n\nexport function isPathUnder(filePath: string, cwd: string): boolean {\n if (!filePath || !cwd) return false;\n const resolved = resolvePath(filePath);\n const base = resolvePath(cwd);\n return resolved.startsWith(base + '/') || resolved === base;\n}\n\nconst SHELL_CODE_FILE_EXT = /\\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte)$/i;\n\nexport interface ShellCodeWrite {\n filePath: string;\n content: string;\n}\n\n/** Detect shell commands that write/rewrite source files (closes Edit-tool CWE bypass). */\nexport function extractShellCodeWrites(command: string, cwd: string): ShellCodeWrite[] {\n if (!command.trim()) return [];\n const writes: ShellCodeWrite[] = [];\n const seen = new Set<string>();\n\n function add(rawPath: string, content: string) {\n const trimmed = rawPath.trim().replace(/^['\"]|['\"]$/g, '');\n if (!trimmed) return;\n const resolved = trimmed.startsWith('/') ? resolvePath(trimmed) : resolvePath(cwd || '.', trimmed);\n if (!SHELL_CODE_FILE_EXT.test(resolved)) return;\n const key = resolved;\n if (seen.has(key)) return;\n seen.add(key);\n writes.push({ filePath: resolved, content: content.slice(0, 6000) });\n }\n\n const heredocBodies: string[] = [];\n const heredocRe = /<<-?\\\\s*['\"]?(\\\\w+)['\"]?\\\\s*\\\\n([\\\\s\\\\S]*?)\\\\n\\\\1\\\\b/g;\n let hm: RegExpExecArray | null;\n while ((hm = heredocRe.exec(command)) !== null) {\n heredocBodies.push(hm[2]);\n }\n const body = heredocBodies.length > 0 ? heredocBodies.join('\\\\n\\\\n') : command;\n\n for (const m of command.matchAll(/Path\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]\\\\s*\\\\)/g)) add(m[1], body);\n for (const m of command.matchAll(/writeFileSync\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]/g)) add(m[1], body);\n for (const m of command.matchAll(/writeFile\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]/g)) add(m[1], body);\n for (const m of command.matchAll(/(?:^|[\\\\s;|])(?:>>?)\\\\s*([^\\\\s;&|]+\\\\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte))\\\\b/gim)) {\n add(m[1], body);\n }\n for (const m of command.matchAll(/\\\\btee(?:\\\\s+-a)?\\\\s+([^\\\\s;&|]+\\\\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte))\\\\b/gi)) {\n add(m[1], body);\n }\n\n return writes;\n}\n\n// ─── Logging ───\n\n// Hooks must keep stderr quiet for non-error paths. Claude Code's PreToolUse\n// hook protocol surfaces any stderr from a non-blocking hook as a\n// \"PreToolUse:Bash hook error\" toast — even though the hook returned a\n// non-blocking status. That toast interleaves with the tool-call header in the\n// terminal and mangles the rendered output. Route routine progress lines to a\n// rolling file under ~/.synkro/ so we keep the diagnostic trail without\n// polluting the agent UI. Stderr is reserved for hard failures (caught at the\n// top-level catch in each hook).\nconst HOOK_LOG_PATH = join(HOME, '.synkro', '.hooks.log');\nconst HOOK_LOG_MAX_BYTES = 2 * 1024 * 1024;\n\nexport function log(msg: string): void {\n // \\`SYNKRO_HOOK_DEBUG=1\\` mirrors to stderr — useful when iterating on hook\n // logic, off by default so normal sessions don't get toasts.\n if (process.env.SYNKRO_HOOK_DEBUG === '1') {\n process.stderr.write('[synkro] ' + msg + '\\\\n');\n }\n try {\n if (existsSync(HOOK_LOG_PATH)) {\n const sz = statSync(HOOK_LOG_PATH).size;\n if (sz > HOOK_LOG_MAX_BYTES) {\n try { renameSync(HOOK_LOG_PATH, HOOK_LOG_PATH + '.1'); } catch {}\n }\n }\n appendFileSync(HOOK_LOG_PATH, new Date().toISOString() + ' [synkro] ' + msg + '\\\\n', 'utf-8');\n } catch {}\n}\n\n// ─── JWT Management ───\n\nexport function loadJwt(): string | null {\n try {\n if (!existsSync(CREDS_PATH)) return null;\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));\n return creds.access_token || null;\n } catch {\n return null;\n }\n}\n\nfunction decodeJwtExp(jwt: string): number {\n try {\n const parts = jwt.split('.');\n if (parts.length < 2) return 0;\n let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n while (payload.length % 4) payload += '=';\n const decoded = Buffer.from(payload, 'base64').toString('utf-8');\n const obj = JSON.parse(decoded);\n return obj.exp || 0;\n } catch {\n return 0;\n }\n}\n\nexport async function refreshJwt(jwt: string): Promise<string> {\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));\n const rt = creds.refresh_token;\n if (!rt) return jwt;\n\n const resp = await fetch(GATEWAY_URL + '/api/auth/refresh', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: rt }),\n signal: AbortSignal.timeout(4000),\n });\n\n if (!resp.ok) {\n log('refresh failed: HTTP ' + resp.status);\n return jwt;\n }\n\n const data = await resp.json() as any;\n const newAt = data.access_token;\n if (!newAt) return jwt;\n\n const newRt = data.refresh_token || rt;\n const existing = (() => {\n try { return JSON.parse(readFileSync(CREDS_PATH, 'utf-8')); } catch { return {}; }\n })();\n const updated = { ...existing, access_token: newAt, refresh_token: newRt };\n const tmp = CREDS_PATH + '.synkro.tmp';\n writeFileSync(tmp, JSON.stringify(updated, null, 2));\n renameSync(tmp, CREDS_PATH);\n return newAt;\n}\n\nfunction jwtIsExpired(jwt: string): boolean {\n const exp = decodeJwtExp(jwt);\n return exp - Math.floor(Date.now() / 1000) < 60;\n}\n\nfunction lockIsStale(lockfile: string, maxAgeMs = 8000): boolean {\n try {\n const stat = require('node:fs').statSync(lockfile);\n return Date.now() - stat.mtimeMs > maxAgeMs;\n } catch {\n return false;\n }\n}\n\nexport async function ensureFreshJwt(jwt: string): Promise<string> {\n if (!jwt) return jwt;\n if (!jwtIsExpired(jwt)) return jwt;\n\n const lockfile = CREDS_PATH + '.lock';\n\n // Clean stale lock left by a killed hook\n if (existsSync(lockfile) && lockIsStale(lockfile)) {\n try { unlinkSync(lockfile); } catch {}\n }\n\n let fd = -1;\n try {\n fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);\n } catch {\n // Another process is refreshing — wait for it\n for (let i = 0; i < 8; i++) {\n await new Promise(r => setTimeout(r, 500));\n if (!existsSync(lockfile)) break;\n }\n // Stale lock after full wait — force-remove\n if (existsSync(lockfile) && lockIsStale(lockfile)) {\n try { unlinkSync(lockfile); } catch {}\n }\n // Re-read — the winner should have written a fresh JWT\n const fresh = loadJwt();\n if (fresh && !jwtIsExpired(fresh)) return fresh;\n // Winner's refresh failed — try to acquire lock ourselves\n try {\n fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);\n } catch {\n return fresh || jwt;\n }\n }\n\n try {\n // Re-check — another hook may have written fresh creds while we waited for lock\n const freshJwt = loadJwt();\n if (freshJwt && !jwtIsExpired(freshJwt)) return freshJwt;\n return await refreshJwt(jwt);\n } catch {\n return jwt;\n } finally {\n try { closeSync(fd); } catch {}\n try { unlinkSync(lockfile); } catch {}\n }\n}\n\n// ─── Repo Detection ───\n\nconst REPO_CACHE_PATH = join(homedir(), '.synkro', '.last-repo');\n\nexport function readCachedRepo(): string {\n try { return readFileSync(REPO_CACHE_PATH, 'utf-8').trim(); } catch { return ''; }\n}\n\nexport function writeCachedRepo(repo: string): void {\n if (!repo) return;\n try { writeFileSync(REPO_CACHE_PATH, repo, 'utf-8'); } catch {}\n}\n\nfunction repoFromGitDir(cwd: string): string {\n if (!cwd) return '';\n try {\n const url = execSync('git remote get-url origin 2>/dev/null', { cwd, timeout: 3000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (url) {\n return url\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\\\/\\\\/[^/]+\\\\//, '')\n .replace(/\\\\.git$/, '');\n }\n } catch {}\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (root) return root.split('/').pop() || '';\n } catch {}\n return '';\n}\n\n// Repo resolution order:\n// 1. explicit cwd from the hook payload\n// 2. workspace_roots (Cursor sends this; CC does not)\n// 3. CC transcript-path slug (~/.claude/projects/-Users-m-foo/...)\n// 4. hook process cwd\n// 5. absolute paths parsed out of the command (cat /abs/path, edit /abs/file, etc.)\n// Steps 1-4 try \\`git remote get-url origin\\` then \\`git rev-parse --show-toplevel\\`.\n// Step 5 climbs up the parent dirs of each parsed path until one exists, then\n// tries the same git lookups. Returns '' if nothing resolves — the server then\n// stores 'local' which the UI renders as \"—\".\nexport function detectRepo(\n cwd: string,\n transcriptPath?: string,\n command?: string,\n workspaceRoots?: string[],\n): string {\n const candidates: string[] = [];\n if (cwd) candidates.push(cwd);\n if (workspaceRoots && Array.isArray(workspaceRoots)) {\n for (const r of workspaceRoots) {\n if (typeof r === 'string' && r) candidates.push(r);\n }\n }\n if (transcriptPath) {\n const ccSlug = transcriptPath.match(/\\\\/projects\\\\/(-[^/]+)\\\\//);\n if (ccSlug) candidates.push('/' + ccSlug[1].slice(1).replace(/-/g, '/'));\n }\n candidates.push(process.cwd());\n\n const tried = new Set<string>();\n for (const c of candidates) {\n if (tried.has(c)) continue;\n tried.add(c);\n const repo = repoFromGitDir(c);\n if (repo) return repo;\n }\n\n if (command) {\n const seen = new Set<string>();\n const re = /(?:^|[\\\\s=:\"'(\\\\[])((?:\\\\/|~\\\\/)[A-Za-z0-9_.\\\\-/]+)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(command)) !== null) {\n let p = m[1];\n if (p.startsWith('~/')) p = HOME + p.slice(1);\n if (seen.has(p)) continue;\n seen.add(p);\n let dir = p;\n for (let i = 0; i < 8; i++) {\n try {\n if (existsSync(dir) && statSync(dir).isDirectory()) break;\n } catch {}\n const idx = dir.lastIndexOf('/');\n if (idx <= 0) { dir = ''; break; }\n dir = dir.slice(0, idx);\n }\n if (!dir || tried.has(dir)) continue;\n tried.add(dir);\n const repo = repoFromGitDir(dir);\n if (repo) return repo;\n }\n }\n return '';\n}\n\n// ─── Git Root ───\n\nexport function findGitRoot(cwd: string): string {\n if (!cwd) return '';\n try {\n return execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch { return ''; }\n}\n\n// ─── .synkro file ───\n\nexport interface SynkroFileConfig {\n version: number;\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };\n workers: { claude?: number; cursor?: number };\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n}\n\nconst SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {\n version: 1,\n harness: ['claude-code', 'cursor'],\n grader: { pool: 'auto' },\n workers: {},\n ruleset: 'default',\n skills: [],\n scanning: { cwe: true, cve: true },\n};\n\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n\n if (line.match(/^\\\\S/) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null;\n currentArr = null;\n\n const colonIdx = line.indexOf(':');\n const key = line.slice(0, colonIdx).trim();\n const val = line.slice(colonIdx + 1).trim();\n currentKey = key;\n\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (line.match(/^ - /)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (line.match(/^ \\\\S/) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const colonIdx = line.indexOf(':');\n const k = line.slice(0, colonIdx).trim();\n const v = line.slice(colonIdx + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nlet _synkroFileCache: SynkroFileConfig | undefined;\n\nexport function loadSynkroFile(cwd?: string): SynkroFileConfig {\n if (_synkroFileCache) return _synkroFileCache;\n const root = cwd ? findGitRoot(cwd) : '';\n if (!root) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }\n const fp = root + '/.synkro';\n try {\n if (!existsSync(fp)) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }\n const raw = readFileSync(fp, 'utf-8');\n const parsed = raw.trimStart().startsWith('{') ? JSON.parse(raw) : parseSynkroYaml(raw);\n const validHarness = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => validHarness.includes(h as any))\n : ['claude-code', 'cursor'];\n _synkroFileCache = {\n version: parsed.version || 1,\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,\n },\n workers: {\n ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),\n ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),\n },\n ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],\n scanning: {\n cwe: parsed.scanning?.cwe !== false,\n cve: parsed.scanning?.cve !== false,\n },\n };\n return _synkroFileCache;\n } catch {\n _synkroFileCache = SYNKRO_FILE_DEFAULTS;\n return _synkroFileCache;\n }\n}\n\nexport function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind): AgentKind {\n if (synkroFile.grader.pool === 'auto') return hookAgentKind;\n if (synkroFile.grader.pool === 'claude') return 'claude_code';\n return 'cursor';\n}\n\n// ─── Channel Health ───\n\nexport async function channelUp(port = 18929): Promise<boolean> {\n return new Promise(resolve => {\n const sock = require('node:net').connect(port, '127.0.0.1');\n const done = (ok: boolean) => { try { sock.destroy(); } catch {} resolve(ok); };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(3000, () => done(false));\n });\n}\n\nexport async function cweChannelUp(): Promise<boolean> {\n return channelUp(18930);\n}\n\n// ─── Mode Normalization ───\n\nexport function normalizeMode(m?: string): 'ask' | 'fix' {\n if (m === 'blocking' || m === 'ask') return 'ask';\n if (m === 'audit' || m === 'fix') return 'fix';\n return 'ask';\n}\n\n// ─── Config Loading ───\n\nexport interface RuleExample {\n text: string;\n verdict: 'violation' | 'ok';\n note?: string;\n}\n\nexport interface Rule {\n rule_id: string;\n text: string;\n severity: string;\n category: string;\n mode: string;\n examples?: RuleExample[];\n}\n\nexport interface HookConfig {\n captureDepth: string;\n tier: string;\n silent: boolean;\n policyName: string;\n rules: Rule[];\n scanExemptions: Array<{ path: string; cwe_id: string }>;\n // User-owned data axes. gradingMode: 'local' (container worker pool) | 'byok'\n // (LLM API with the user's own key). storageMode: 'local' (PGLite) | 'cloud'\n // (Timescale). Sourced from config.env (the installed choice); the server\n // value from /v1/hook/config is only a fallback when the env var is unset.\n gradingMode: string;\n storageMode: string;\n}\n\n/** True when telemetry + rules must stay on-machine (PGLite). Default: local. */\nexport function isLocalStorageMode(): boolean {\n return (process.env.SYNKRO_STORAGE_MODE || 'local') === 'local';\n}\n\nfunction mapHookRules(raw: unknown[]): Rule[] {\n return raw.map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: normalizeMode(r.mode),\n examples: Array.isArray(r.examples) ? r.examples : undefined,\n }));\n}\n\nexport async function loadConfig(jwt: string, query?: string): Promise<HookConfig> {\n const config: HookConfig = {\n captureDepth: 'local_only',\n tier: 'standard',\n silent: false,\n policyName: '',\n rules: [],\n scanExemptions: [],\n gradingMode: process.env.SYNKRO_GRADING_MODE || 'local',\n storageMode: process.env.SYNKRO_STORAGE_MODE || 'local',\n };\n\n // Kick the telemetry spool drainer. Fire-and-forget: it runs concurrently\n // with the grade that follows this call, so it adds no latency to the hook.\n drainSpool().catch(() => {});\n\n // Local-first: fetch from the local MCP server (PGLite-backed) — zero network egress.\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/hook-config', {\n signal: AbortSignal.timeout(1500),\n });\n if (resp.ok) {\n const raw = await resp.json() as any;\n const policy = raw.policy || null;\n if (policy) {\n config.policyName = policy.name || '';\n config.rules = mapHookRules(policy.rules || []);\n }\n config.silent = raw.silent === true;\n if (Array.isArray(raw.scan_exemptions)) {\n config.scanExemptions = raw.scan_exemptions\n .filter((e: any) => e && typeof e.path === 'string')\n .map((e: any) => ({ path: e.path, cwe_id: e.cwe_id || '' }));\n }\n return config;\n }\n } catch {}\n\n if (isLocalStorageMode()) {\n log('hook-config: local PGLite unavailable — skipping cloud rules fallback');\n return config;\n }\n\n // Cloud storage mode only: bootstrap rules from the gateway.\n try {\n const url = GATEWAY_URL + '/api/v1/hook/config' + (query ? '?' + query : '');\n const resp = await fetch(url, {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(4000),\n });\n const data = await resp.json() as any;\n config.captureDepth = data.capture_depth || 'local_only';\n config.tier = data.tier || 'standard';\n config.silent = data.silent_mode === true || data.silent_mode === 'true';\n if (!process.env.SYNKRO_GRADING_MODE && data.grading_mode) config.gradingMode = data.grading_mode;\n if (!process.env.SYNKRO_STORAGE_MODE && data.storage_mode) config.storageMode = data.storage_mode;\n config.policyName = data.active_policy_name || '';\n if (Array.isArray(data.scan_exemptions)) {\n config.scanExemptions = data.scan_exemptions\n .filter((e: any) => e && typeof e.path === 'string')\n .map((e: any) => ({ path: e.path, cwe_id: e.cwe_id || '' }));\n }\n if (Array.isArray(data.rules)) config.rules = mapHookRules(data.rules);\n } catch {}\n return config;\n}\n\n// ─── Routing ───\n\nexport async function route(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'cloud'> {\n const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';\n if (gradingMode === 'byok') return 'cloud';\n if (config.captureDepth === 'local_only') return 'local';\n if (await channelUp()) return 'local';\n return 'cloud';\n}\n\nexport async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {\n const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';\n if (gradingMode === 'byok') return 'byok';\n if (await cweChannelUp()) return 'local';\n return 'skip';\n}\n\n// ─── Tag Building ───\n\nexport function tag(rt: string, config: HookConfig, grader?: string): string {\n if (config.silent) return '[synkro:silent]';\n const rs = config.policyName || 'all';\n const g = grader ? ':' + (grader === 'claude_code' ? 'claude' : grader) : '';\n return '[synkro:' + rt + ':' + rs + g + ']';\n}\n\n// ─── Local Grading (direct channel call) ───\n\ntype GradeRole = 'grade-edit' | 'grade-bash' | 'grade-plan' | 'grade-cwe';\n\n// Which coding agent fired this grade. The dispatcher routes grades to a\n// worker pool of the matching kind so a Cursor grade is judged by Cursor and\n// a Claude grade by Claude. Defaults to 'claude_code' so existing cc-* hook\n// call sites need no change; cursor-* hooks pass 'cursor' explicitly.\nexport type AgentKind = 'claude_code' | 'cursor';\n\nconst ROLE_MAP: Record<string, GradeRole> = {\n edit: 'grade-edit', bash: 'grade-bash', plan: 'grade-plan', cwe: 'grade-cwe',\n};\n\nasync function channelGrade(role: GradeRole, prompt: string, _jwt: string, port: number, timeoutMs = 30000, agentKind: AgentKind = 'claude_code'): Promise<string> {\n const body = JSON.stringify({ role, payload: prompt, content: prompt, agent_kind: agentKind });\n\n const resp = await fetch('http://127.0.0.1:' + port + '/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error('channel ' + resp.status + ': ' + text.slice(0, 200));\n }\n\n const data = await resp.json() as { result?: string; error?: string };\n if (data.error) throw new Error(data.error);\n return String(data.result || '');\n}\n\n// BYOK grading — grade via the cloud /v1/grade endpoint, which runs the same\n// grader prompt through an LLM API using the org's own provider key. Any\n// non-2xx (incl. 422 when no key is configured) throws, so the caller's\n// existing catch falls open — never a hard block on a grader error.\nasync function cloudGrade(surface: string, prompt: string, jwt: string, timeoutMs: number): Promise<string> {\n const resp = await fetch(GATEWAY_URL + '/api/v1/grade', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ surface, grader_prompt: prompt }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error('cloud grade ' + resp.status + ': ' + text.slice(0, 200));\n }\n const data = await resp.json() as { verdict?: string };\n return String(data.verdict || '');\n}\n\nexport async function localGrade(surface: string, prompt: string, timeoutMs = 30000, agentKind: AgentKind = 'claude_code'): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n // BYOK grading mode routes the grade through an LLM API instead of the\n // on-device channel worker pool. The grader prompt + parseVerdict are shared.\n if ((process.env.SYNKRO_GRADING_MODE || 'local') === 'byok') {\n return cloudGrade(surface, prompt, jwt, timeoutMs);\n }\n if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');\n return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs, agentKind);\n}\n\nexport async function localGradeCwe(prompt: string, agentKind: AgentKind = 'claude_code', timeoutMs = 45000): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade('grade-cwe', prompt, jwt, 18930, timeoutMs, agentKind);\n}\n\n// ─── Rule Pre-Filter (embedding-based) ───\n\n/** User message + action for embedding search — intent surfaces boundary/consent rules the command alone misses. */\nexport function ruleFilterText(action: string, userMessage?: string | null): string {\n const user = (userMessage || '').trim();\n const act = (action || '').trim();\n if (!user) return act.slice(0, 4000);\n if (!act) return user.slice(0, 4000);\n return (user + '\\\\n' + act).slice(0, 4000);\n}\n\nexport async function filterRules(commandText: string, allRules: Rule[]): Promise<Rule[]> {\n if (allRules.length <= 3) return allRules;\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/filter-rules', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text: commandText, top_k: 3 }),\n signal: AbortSignal.timeout(500),\n });\n if (!resp.ok) return allRules;\n const data = await resp.json() as { rules?: Array<Record<string, unknown>> };\n if (!data.rules || data.rules.length === 0) return allRules;\n // Local PGLite owns the rule set — trust filter-rules output directly.\n if (isLocalStorageMode()) return mapHookRules(data.rules);\n const selectedIds = new Set(data.rules.map(r => String(r.rule_id || '')));\n return allRules.filter(r => selectedIds.has(r.rule_id));\n } catch {\n return allRules;\n }\n}\n\n// ─── Safe-read short-circuit (shared by cc-bash-judge + cursor-bash-judge) ───\n// Read-only tool calls, and bash pipelines where every segment is a pure\n// in-repo read, are allowed instantly without an LLM grade. Strict by design:\n// any $ / backtick / redirect / shell metachar, any non-whitelisted verb, any\n// .. traversal, or any absolute path outside repoRoot falls through to the judge.\n\nconst SAFE_READ_TOOLS = new Set([\n 'Read', 'ReadFile', 'read_file', 'Grep', 'grep_search', 'codebase_search',\n 'file_search', 'Glob', 'list_dir',\n]);\nconst SAFE_SHELL_TOOLS = new Set([\n 'Bash', 'Shell', 'terminal', 'run_terminal_cmd', 'execute_command',\n]);\n\nfunction isSafeBashSegment(seg: string, repoRoot: string): boolean {\n const UNSAFE_CHARS = ['>', ';', '&', '\\`', '$'];\n for (const ch of UNSAFE_CHARS) { if (seg.indexOf(ch) !== -1) return false; }\n const padded = ' ' + seg + ' ';\n const UNSAFE_WORDS = [\n ' sudo ', ' su ', ' rm ', ' mv ', ' cp ', ' chmod ', ' chown ',\n ' tee ', ' kill ', ' sed -i', ' sed --in-place',\n ' sh -c', ' bash -c', ' zsh -c', ' eval ', ' exec ',\n ];\n for (const w of UNSAFE_WORDS) { if (padded.indexOf(w) !== -1) return false; }\n const SAFE_VERBS = new Set([\n 'cat','head','tail','less','more','grep','egrep','fgrep','rg','ag',\n 'find','fd','ls','wc','cmp','diff','file','stat','which','whereis','type',\n 'pwd','whoami','id','date','echo','printf','true','false',\n 'jq','yq','sort','uniq','cut','tr','xxd','hexdump','od','column',\n 'node','npm','pnpm','yarn','bun','python','python3','ruby','go','rustc','cargo',\n 'git',\n ]);\n const tokens = seg.trim().split(' ').filter(t => t.length > 0);\n const verb = tokens[0] || '';\n if (!SAFE_VERBS.has(verb)) return false;\n if (verb === 'find' || verb === 'fd') {\n const BAD = new Set([\n '-exec','-execdir','-ok','-okdir','-delete',\n '-fprint','-fprintf','-fprint0','-fls','--exec','--exec-batch',\n ]);\n for (const t of tokens) { if (BAD.has(t)) return false; }\n }\n if (verb === 'git') {\n const SAFE_GIT = new Set([\n 'log','show','diff','blame','status','rev-parse',\n 'ls-files','ls-tree','cat-file','shortlog','reflog',\n 'describe','symbolic-ref','--version',\n ]);\n const sub = tokens[1] || '';\n if (!SAFE_GIT.has(sub)) return false;\n } else if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {\n const sub = tokens[1] || '';\n const SAFE_PKG = new Set([\n '--version','-v','version','list','ls','why','view','show','info','outdated',\n '-h','--help','help',\n ]);\n if (!SAFE_PKG.has(sub)) return false;\n } else if (['node','python','python3','ruby','rustc'].includes(verb)) {\n const sub = tokens[1] || '';\n if (sub !== '--version' && sub !== '-v' && sub !== '-V') return false;\n }\n if (!repoRoot) return false;\n for (let i = 1; i < tokens.length; i++) {\n const stripped = tokens[i].replace(/^['\"]/, '').replace(/['\"]$/, '');\n if (stripped.startsWith('~')) return false;\n // Reject any .. traversal segment — a relative path with .. can escape the\n // repo root just as easily as an absolute one.\n if (stripped.split('/').some(p => p === '..')) return false;\n if (stripped.startsWith('/') && !isPathUnder(stripped, repoRoot)) return false;\n }\n return true;\n}\n\nexport function isSafeInRepoRead(toolName: string, command: string, repoRoot: string): boolean {\n // Read/Grep/Glob are synthesized into cat/grep/find commands — validate paths\n // the same way as bash reads instead of blanket-allowing every tool call.\n if (SAFE_READ_TOOLS.has(toolName)) {\n if (!command || !repoRoot) return false;\n return isSafeBashSegment(command.trim(), repoRoot);\n }\n if (!SAFE_SHELL_TOOLS.has(toolName)) return false;\n if (!command || !repoRoot) return false;\n const segments = command.split('|');\n for (const seg of segments) {\n const t = seg.trim();\n if (t.length === 0) return false;\n if (!isSafeBashSegment(t, repoRoot)) return false;\n }\n return true;\n}\n\n// ─── Install protection: server-side pkg-scan (shared by both bash judges) ───\n// Parses an install command, scans the packages for CVEs / typosquats /\n// malicious tarballs / low reputation, and returns a structured verdict the\n// caller renders in its own (cc or cursor) output format.\n\nexport interface InstallScanResult {\n scanned: boolean;\n action: 'allow' | 'warn' | 'block';\n blockContext: string;\n summary: string;\n scannedLabel: string;\n findings: Array<{ advisoryId: string; name: string; version: string; severity: string; detail: string }>;\n violatedIds: string[];\n}\n\nexport async function runInstallScan(command: string, jwt: string): Promise<InstallScanResult> {\n const empty: InstallScanResult = {\n scanned: false, action: 'allow', blockContext: '', summary: '',\n scannedLabel: '', findings: [], violatedIds: [],\n };\n // Stage 0 — recall-tuned pre-filter. Cheap, dependency-free, runs on every\n // Bash command. A false positive only costs a wasted server round-trip; a\n // false negative would let a vulnerable install through — so keep it loose.\n const lc = command.toLowerCase();\n const HINTS = [\n 'npm', 'pnpm', 'yarn', 'bun', 'pip', 'cargo', 'gem', 'composer',\n 'apt', 'apk', 'brew', 'dnf', 'yum', 'pacman', 'install', 'require',\n 'eval', '$(', '| sh', '| bash', 'curl', 'wget', 'make ',\n ];\n if (!HINTS.some(h => lc.includes(h))) return empty;\n\n try {\n let clientPackages: Array<{ name: string; version: string; ecosystem: string }> = [];\n try {\n const mod = await import(new URL('./installExtractCore.ts', import.meta.url).href);\n clientPackages = mod.extractDeterministicPkgRequests(command);\n } catch (err) {\n log('installExtract client error: ' + String(err));\n }\n\n const scanResp = await fetch(GATEWAY_URL + '/api/v1/pkg-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ command, packages: clientPackages }),\n signal: AbortSignal.timeout(10000),\n }).then(r => r.json()) as any;\n const action = scanResp?.action || 'allow';\n const pkgResults = Array.isArray(scanResp?.packages) ? scanResp.packages : [];\n const summary = scanResp?.summary || '';\n const scannedLabel = pkgResults.map((p: any) => p.name + '@' + p.version).join(', ');\n if (action === 'block') {\n // Every critical/high signal (uncapped) + the true CVE total. The grader\n // only sees what we put in blockContext — so the real count must be\n // STATED here; a bare preview lets it under-report (the count is data,\n // not something the grader should infer from a truncated list).\n const highSignals = pkgResults\n .flatMap((p: any) => (p.signals || []).filter((s: any) => s.severity === 'critical' || s.severity === 'high'));\n const cveCount = pkgResults\n .flatMap((p: any) => (p.signals || []))\n .filter((s: any) => s.type === 'cve').length;\n const findings: InstallScanResult['findings'] = [];\n const seenFindingKeys = new Set<string>();\n for (const p of pkgResults) {\n for (const s of (p.signals || [])) {\n const advisoryMatch = (s.detail || '').match(/\\\\b(GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}|CVE-\\\\d{4}-\\\\d+)\\\\b/i);\n const advisoryId = advisoryMatch ? advisoryMatch[1] : (s.type === 'cve' ? 'pkg:' + p.name : (s.type || 'signal'));\n const key = advisoryId + ':' + p.name + '@' + p.version;\n if (seenFindingKeys.has(key)) continue;\n seenFindingKeys.add(key);\n findings.push({\n advisoryId,\n name: p.name,\n version: p.version,\n severity: s.severity || 'high',\n detail: s.detail || summary,\n });\n }\n }\n // Preview the top 5 detail lines; the headline carries the true total.\n const blockSignals = highSignals.slice(0, 5);\n const headline = cveCount > 0\n ? cveCount + ' known CVE' + (cveCount === 1 ? '' : 's') + ' found in ' + (scannedLabel || 'the requested install') + '.\\\\n'\n : '';\n const preview = blockSignals.map((s: any) => s.detail).join('\\\\n') || summary;\n const more = highSignals.length > blockSignals.length\n ? '\\\\n(+' + (highSignals.length - blockSignals.length) + ' more critical/high findings not shown)'\n : '';\n return {\n scanned: true, action: 'block',\n blockContext: headline + preview + more\n + '\\\\nReport the CVE count and fix version exactly as stated above — do not estimate.'\n + '\\\\nDo NOT install packages with security risks. Use a patched version or a different package.',\n summary, scannedLabel, findings,\n violatedIds: blockSignals.map((s: any) => s.type + ':' + (s.detail || '').slice(0, 40)),\n };\n }\n return {\n scanned: true, action: action === 'warn' ? 'warn' : 'allow',\n blockContext: '', summary, scannedLabel, findings: [], violatedIds: [],\n };\n } catch {\n // Fail closed — could not verify the install (timeout / server\n // unreachable). Blocking an unverified install beats letting a\n // vulnerable package through.\n return {\n scanned: true, action: 'block',\n blockContext: 'Synkro could not verify this install is safe — the package scanner timed out or was unreachable. This is a verification failure, not a confirmed vulnerability. Retry once the Synkro server is reachable.',\n summary: 'install scan unavailable', scannedLabel: '', findings: [], violatedIds: ['scan_unavailable'],\n };\n }\n}\n\n// ─── Session Action Log ───\n\nconst SESSIONS_DIR = join(HOME, '.synkro', 'sessions');\nconst SAFE_SESSION_ID = /^[\\\\w-]+$/;\n\ninterface SessionAction {\n ts: string;\n tool: string;\n summary: string;\n file?: string;\n outcome?: string;\n}\n\nfunction sessionLogPath(sessionId: string): string | null {\n if (!sessionId || !SAFE_SESSION_ID.test(sessionId)) return null;\n return join(SESSIONS_DIR, sessionId + '.jsonl');\n}\n\nexport function appendSessionAction(sessionId: string, entry: SessionAction): void {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return;\n let step = 0;\n try {\n mkdirSync(SESSIONS_DIR, { recursive: true });\n try { step = readFileSync(logPath, 'utf-8').split('\\\\n').filter(Boolean).length + 1; } catch { step = 1; }\n appendFileSync(logPath, JSON.stringify(entry) + '\\\\n', 'utf-8');\n } catch {}\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return;\n fetch('http://127.0.0.1:' + mcpPort + '/api/session-action', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ session_id: sessionId, step, tool: entry.tool, summary: entry.summary, file: entry.file, outcome: entry.outcome }),\n signal: AbortSignal.timeout(2000),\n }).catch(() => {});\n}\n\nexport function readSessionLog(sessionId: string): SessionAction[] {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return [];\n try {\n if (!existsSync(logPath)) return [];\n const raw = readFileSync(logPath, 'utf-8');\n return raw.split('\\\\n').filter(Boolean).map(l => {\n try { return JSON.parse(l); } catch { return null; }\n }).filter((x): x is SessionAction => x !== null);\n } catch { return []; }\n}\n\nexport function compressSessionLog(actions: SessionAction[]): string {\n if (actions.length === 0) return '';\n const total = actions.length;\n const lines: string[] = [];\n\n if (total > 200) {\n const old = actions.slice(0, total - 200);\n const counts: Record<string, number> = {};\n const dirs = new Set<string>();\n for (const a of old) {\n counts[a.tool] = (counts[a.tool] || 0) + 1;\n if (a.file) {\n const dir = a.file.split('/').slice(0, -1).join('/') || '.';\n dirs.add(dir);\n }\n }\n const parts = Object.entries(counts).map(([t, c]) => c + ' ' + t).join(', ');\n const dirHint = dirs.size > 0 ? ' (' + [...dirs].slice(0, 3).join(', ') + ')' : '';\n lines.push(' [' + old.length + ' earlier: ' + parts + dirHint + ']');\n }\n\n const tier2Start = Math.max(0, total - 200);\n const tier2End = Math.max(0, total - 10);\n for (let i = tier2Start; i < tier2End; i++) {\n const a = actions[i];\n const target = a.file || a.summary.slice(0, 40);\n const out = a.outcome ? ' -> ' + a.outcome : '';\n lines.push(' ' + (i + 1) + '. ' + a.tool + ' ' + target + out);\n }\n\n const tier1Start = Math.max(0, total - 10);\n if (tier2End > tier2Start) lines.push(' --- recent ---');\n for (let i = tier1Start; i < total; i++) {\n const a = actions[i];\n const out = a.outcome ? ' -> ' + a.outcome : '';\n lines.push(' ' + (i + 1) + '. ' + a.summary + out);\n }\n\n return 'SESSION HISTORY (' + total + ' actions):\\\\n' + lines.join('\\\\n');\n}\n\nexport function cleanupSessionLog(sessionId: string): void {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return;\n try { unlinkSync(logPath); } catch {}\n}\n\n// ─── Verdict Parsing ───\n\nexport interface Verdict {\n ok: boolean;\n reason: string;\n suggestedFix: string;\n ruleId: string;\n ruleMode: string;\n severity: string;\n category: string;\n}\n\nexport function parseVerdict(resp: string): Verdict {\n const verdict: Verdict = {\n ok: true,\n reason: '',\n suggestedFix: '',\n ruleId: '',\n ruleMode: '',\n severity: 'low',\n category: 'clean',\n };\n\n // Flatten newlines for easier regex\n const flat = resp.replace(/\\\\n/g, ' ');\n const outerMatch = flat.match(/<synkro-verdict>(.*)<\\\\/synkro-verdict>/);\n if (!outerMatch) return verdict;\n const inner = outerMatch[1];\n\n const okMatch = inner.match(/<ok>(.*?)<\\\\/ok>/);\n if (okMatch) verdict.ok = okMatch[1].trim() !== 'false';\n\n const decisionMatch = inner.match(/<decision>(.*?)<\\\\/decision>/) || inner.match(/<overall>(.*?)<\\\\/overall>/);\n if (decisionMatch) {\n const d = decisionMatch[1].trim().toLowerCase();\n if (d === 'block' || d === 'blocking' || d === 'deny' || d === 'fail') verdict.ok = false;\n else if (d === 'allow' || d === 'pass' || d === 'approve') verdict.ok = true;\n }\n\n const reasonMatch = inner.match(/<reason>(.*?)<\\\\/reason>/) || inner.match(/<reasoning>(.*?)<\\\\/reasoning>/);\n if (reasonMatch) verdict.reason = reasonMatch[1].trim();\n\n const fixMatch = inner.match(/<suggested_fix>(.*?)<\\\\/suggested_fix>/);\n if (fixMatch) verdict.suggestedFix = fixMatch[1].trim();\n\n if (!verdict.ok) {\n const ruleIdMatch = inner.match(/<rule_id>(.*?)<\\\\/rule_id>/);\n const ruleModeMatch = inner.match(/<rule_mode>(.*?)<\\\\/rule_mode>/);\n const sevMatch = inner.match(/<risk_level>(.*?)<\\\\/risk_level>/);\n\n if (ruleIdMatch) {\n verdict.ruleId = ruleIdMatch[1].trim();\n } else {\n // Try to find inside a <violation> block\n const violationMatch = inner.match(/<violation>(.*?)<\\\\/violation>/);\n if (violationMatch) {\n const vBlock = violationMatch[1];\n const vRuleId = vBlock.match(/<rule_id>(.*?)<\\\\/rule_id>/);\n if (vRuleId) verdict.ruleId = vRuleId[1].trim();\n if (!verdict.reason) {\n const vReason = vBlock.match(/<reason>(.*?)<\\\\/reason>/);\n if (vReason) verdict.reason = vReason[1].trim();\n }\n if (!verdict.suggestedFix) {\n const vFix = vBlock.match(/<suggested_fix>(.*?)<\\\\/suggested_fix>/);\n if (vFix) verdict.suggestedFix = vFix[1].trim();\n }\n if (!sevMatch) {\n const vSev = vBlock.match(/<severity>(.*?)<\\\\/severity>/);\n if (vSev) verdict.severity = vSev[1].trim();\n }\n }\n }\n\n if (ruleModeMatch) verdict.ruleMode = ruleModeMatch[1].trim();\n if (sevMatch) verdict.severity = sevMatch[1].trim();\n verdict.severity = verdict.severity || 'high';\n\n const catMatch = inner.match(/<category>(.*?)<\\\\/category>/);\n verdict.category = catMatch ? catMatch[1].trim() : 'uncategorized';\n\n // Fallback: extract rule ID from reason text\n if (!verdict.ruleId && verdict.reason) {\n const rMatch = verdict.reason.match(/[Rr]\\\\d{3}/);\n if (rMatch) verdict.ruleId = rMatch[0];\n }\n }\n\n return verdict;\n}\n\n// ─── Telemetry Dispatch ───\n\n// Gated cloud telemetry POST — fires only when storage mode is 'cloud'. The\n// local PGLite spool (appendLocalTelemetry) always gets the data regardless,\n// so 'local' storage keeps everything on the machine.\nexport function shipCloud(jwt: string, path: string, body: Record<string, any>, timeoutMs = 3000): void {\n if ((process.env.SYNKRO_STORAGE_MODE || 'local') !== 'cloud') return;\n fetch(GATEWAY_URL + path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n }).catch(() => {});\n}\n\nexport function dispatchCapture(\n jwt: string,\n hookType: string,\n verdictStr: string,\n severity: string,\n category: string,\n toolName: string,\n repo: string,\n sessionId: string,\n captureDepth: string,\n opts?: {\n command?: string;\n reasoning?: string;\n rulesChecked?: Rule[] | string;\n violatedRules?: string[];\n recentUserMessages?: string[];\n ccModel?: string;\n linesAdded?: number;\n linesRemoved?: number;\n },\n): void {\n // Fire-and-forget\n const eventId = mintEventId('evt');\n const model = normalizeCaptureModel(opts?.ccModel || 'unknown');\n\n const body: Record<string, any> = {\n capture_type: 'local_verdict',\n event_id: eventId,\n // Source-time of the action. The ingest handler uses this for created_at\n // — without it, the spool drain stamps every event with NOW() and a batch\n // of actions taken minutes apart all collapse to the drain instant.\n _ts: new Date().toISOString(),\n hook_type: hookType,\n verdict: verdictStr,\n severity,\n category,\n cc_model: model,\n model,\n tool_name: toolName,\n };\n const resolvedRepo = repo || detectRepo('') || readCachedRepo();\n if (resolvedRepo) body.repo = resolvedRepo;\n if (sessionId) body.session_id = sessionId;\n\n // Local telemetry always gets full content — data never leaves the machine\n const localBody = { ...body };\n if (opts) {\n if (opts.command) localBody.command = opts.command;\n if (opts.reasoning) localBody.reasoning = opts.reasoning;\n if (opts.rulesChecked) localBody.rules_checked = opts.rulesChecked;\n if (opts.violatedRules) localBody.violated_rules = opts.violatedRules;\n if (opts.recentUserMessages) localBody.recent_user_messages = opts.recentUserMessages;\n if (opts.linesAdded != null) localBody.lines_added = opts.linesAdded;\n if (opts.linesRemoved != null) localBody.lines_removed = opts.linesRemoved;\n }\n appendLocalTelemetry(localBody);\n\n // Cloud copy carries the same full content as the local spool; shipCloud\n // gates on storage mode (no-op when storage is local).\n if (opts) {\n if (opts.command) body.command = opts.command;\n if (opts.reasoning) body.reasoning = opts.reasoning;\n if (opts.rulesChecked) body.rules_checked = opts.rulesChecked;\n if (opts.violatedRules) body.violated_rules = opts.violatedRules;\n if (opts.recentUserMessages) body.recent_user_messages = opts.recentUserMessages;\n }\n shipCloud(jwt, '/api/v1/hook/capture', body);\n}\n\n// ─── Durable Telemetry Spool ───\n// Telemetry must survive process death, container restarts, and ingest-server\n// backpressure. Every event is appended synchronously to a local JSONL spool.\n// drainSpool streams claim files line-by-line (never loads multi-GB into RAM),\n// ships in batches, and on partial failure only re-spools the batches that\n// did not land — never the whole claim file (that caused 2GB amplification).\n\nconst TELEMETRY_SPOOL = join(HOME, '.synkro', 'telemetry-spool.jsonl');\nconst SPOOL_DRAIN_PREFIX = 'telemetry-spool.jsonl.draining.';\nconst SPOOL_DRAIN_LOCK = join(HOME, '.synkro', 'telemetry-spool.drain.lock');\nconst SPOOL_MAX_CLAIM_BYTES = 50 * 1024 * 1024;\nconst SPOOL_BATCH_SIZE = 200;\nconst SPOOL_DRAIN_LOCK_STALE_MS = 120_000;\n\n/** Stable id for spool rows — required for idempotent ingest (ON CONFLICT). */\nexport function mintEventId(prefix = 'evt'): string {\n return prefix + '_' + Date.now() + '_' + process.pid;\n}\n\nexport function appendLocalTelemetry(body: Record<string, any>): void {\n if ((process.env.SYNKRO_STORAGE_MODE || 'local') !== 'local') return;\n const event = { ...body };\n if (!event.event_id) {\n const ct = String(event.capture_type || '');\n const prefix = ct === 'usage_tick' ? 'usage' : ct === 'edit_scan' ? 'edit' : 'evt';\n event.event_id = mintEventId(prefix);\n }\n if (!event._ts) event._ts = new Date().toISOString();\n try {\n appendFileSync(TELEMETRY_SPOOL, JSON.stringify(event) + '\\\\n');\n } catch {}\n // Realtime: fire-and-forget POST to the local server so events appear\n // in the dashboard immediately. Spool file remains the durable fallback.\n try {\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n let token = '';\n try { token = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (token) {\n fetch('http://127.0.0.1:' + port + '/api/ingest', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },\n body: JSON.stringify(event),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n } catch {}\n}\n\nfunction tryAcquireDrainLock(): boolean {\n try {\n if (existsSync(SPOOL_DRAIN_LOCK)) {\n if (Date.now() - statSync(SPOOL_DRAIN_LOCK).mtimeMs < SPOOL_DRAIN_LOCK_STALE_MS) return false;\n unlinkSync(SPOOL_DRAIN_LOCK);\n }\n mkdirSync(dirname(SPOOL_DRAIN_LOCK), { recursive: true });\n writeFileSync(SPOOL_DRAIN_LOCK, String(process.pid) + '\\\\n');\n return true;\n } catch {\n return false;\n }\n}\n\nfunction releaseDrainLock(): void {\n try { unlinkSync(SPOOL_DRAIN_LOCK); } catch {}\n}\n\nasync function postSpoolBatch(mcpPort: string, mcpToken: string, events: any[]): Promise<boolean> {\n if (!events.length) return true;\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/ingest/batch', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ events }),\n signal: AbortSignal.timeout(30000),\n });\n if (!resp.ok) {\n const errBody = await resp.text().catch(() => '');\n log('drainSpool batch HTTP ' + resp.status + ': ' + errBody.slice(0, 120));\n }\n return resp.ok;\n } catch (e) {\n log('drainSpool batch error: ' + ((e as Error).message || String(e)).slice(0, 120));\n return false;\n }\n}\n\nfunction quarantineOversizedClaim(claimPath: string): void {\n const stuck = claimPath + '.STUCK-OVERSIZED.bak';\n try {\n renameSync(claimPath, stuck);\n log('drainSpool quarantined oversized claim → ' + basename(stuck));\n } catch (e) {\n log('drainSpool quarantine failed: ' + String(e));\n }\n}\n\nasync function drainClaimFile(claimPath: string, mcpPort: string, mcpToken: string): Promise<void> {\n let sz = 0;\n try { sz = statSync(claimPath).size; } catch { return; }\n if (sz === 0) {\n try { unlinkSync(claimPath); } catch {}\n return;\n }\n if (sz > SPOOL_MAX_CLAIM_BYTES) {\n quarantineOversizedClaim(claimPath);\n return;\n }\n\n const pending: any[] = [];\n let batch: any[] = [];\n let failed = false;\n\n const rl = createInterface({\n input: createReadStream(claimPath, { encoding: 'utf8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (failed) {\n const t = line.trim();\n if (!t) continue;\n try { pending.push(JSON.parse(t)); } catch {}\n continue;\n }\n const t = line.trim();\n if (!t) continue;\n try {\n batch.push(JSON.parse(t));\n } catch {\n continue;\n }\n if (batch.length < SPOOL_BATCH_SIZE) continue;\n const chunk = batch.splice(0, SPOOL_BATCH_SIZE);\n if (!(await postSpoolBatch(mcpPort, mcpToken, chunk))) {\n pending.push(...chunk);\n failed = true;\n }\n }\n\n if (!failed && batch.length > 0) {\n if (!(await postSpoolBatch(mcpPort, mcpToken, batch))) {\n pending.push(...batch);\n failed = true;\n } else {\n batch = [];\n }\n } else if (failed && batch.length > 0) {\n pending.push(...batch);\n }\n\n if (pending.length === 0) {\n try { unlinkSync(claimPath); } catch {}\n return;\n }\n\n try {\n for (const evt of pending) {\n appendFileSync(TELEMETRY_SPOOL, JSON.stringify(evt) + '\\\\n');\n }\n try { unlinkSync(claimPath); } catch {}\n log('drainSpool re-spooled ' + pending.length + ' events from ' + basename(claimPath));\n } catch (e) {\n log('drainSpool re-spool failed: ' + String(e));\n }\n}\n\nexport async function drainSpool(): Promise<void> {\n if (!tryAcquireDrainLock()) return;\n\n const dir = join(HOME, '.synkro');\n const claimed: string[] = [];\n\n try {\n try {\n if (existsSync(TELEMETRY_SPOOL) && statSync(TELEMETRY_SPOOL).size > 0) {\n const claim = join(dir, SPOOL_DRAIN_PREFIX + process.pid + '.' + Date.now());\n renameSync(TELEMETRY_SPOOL, claim);\n claimed.push(claim);\n }\n } catch {}\n\n try {\n for (const f of readdirSync(dir)) {\n if (!f.startsWith(SPOOL_DRAIN_PREFIX)) continue;\n if (f.includes('.STUCK-') || f.endsWith('.bak')) continue;\n const full = join(dir, f);\n if (claimed.indexOf(full) !== -1) continue;\n try {\n if (Date.now() - statSync(full).mtimeMs > 30000) claimed.push(full);\n } catch {}\n }\n } catch {}\n\n if (claimed.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return;\n\n for (const f of claimed) {\n await drainClaimFile(f, mcpPort, mcpToken);\n }\n } finally {\n releaseDrainLock();\n }\n}\n\n// ─── Rule Mode Lookup ───\n\nexport function ruleMode(ruleId: string, rules: Rule[]): 'ask' | 'fix' {\n if (!ruleId || !rules.length) return 'ask';\n const matched = rules.filter(r => r.rule_id === ruleId);\n if (matched.some(r => r.mode === 'blocking' || r.mode === 'ask')) return 'ask';\n return normalizeMode(matched[0]?.mode);\n}\n\n// ─── Content Reconstruction ───\n\nfunction patchTextFromToolInput(toolInput: any): string {\n return String(toolInput.patch ?? toolInput.content ?? toolInput.code_edit ?? toolInput.new_string ?? toolInput.input ?? toolInput.diff ?? '');\n}\n\nfunction filePathFromPatch(patchText: string): string {\n const match = patchText.match(/^\\\\*\\\\*\\\\* (?:Add|Update|Delete) File:\\\\s*(.+)$/m);\n return match?.[1]?.trim() || '';\n}\n\nfunction contentFromPatch(patchText: string): string {\n const addedOrContext = patchText\n .split('\\\\n')\n .filter(line => (line.startsWith('+') && !line.startsWith('+++')) || line.startsWith(' '))\n .map(line => line.slice(1))\n .join('\\\\n')\n .trim();\n return addedOrContext || patchText;\n}\n\nexport function filePathFromToolInput(toolInput: any): string {\n return toolInput.file_path || toolInput.notebook_path || toolInput.path || toolInput.target_file || filePathFromPatch(patchTextFromToolInput(toolInput));\n}\n\nexport function reconstructContent(toolName: string, toolInput: any, filePath: string, cwd?: string): string {\n const canRead = filePath && cwd && isPathUnder(filePath, cwd);\n switch (toolName) {\n case 'ApplyPatch':\n case 'apply_patch':\n return contentFromPatch(patchTextFromToolInput(toolInput));\n case 'Write':\n return toolInput.content || toolInput.contents || '';\n case 'edit_file':\n case 'reapply':\n return toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n case 'Edit': {\n let content = '';\n try {\n if (canRead && existsSync(filePath)) {\n content = readFileSync(filePath, 'utf-8').slice(0, 65536);\n }\n } catch {}\n const oldStr = toolInput.old_string || '';\n const newStr = toolInput.new_string || '';\n if (oldStr && content.includes(oldStr)) {\n return content.replace(oldStr, newStr);\n }\n return content || newStr;\n }\n case 'MultiEdit': {\n let content = '';\n try {\n if (canRead && existsSync(filePath)) {\n content = readFileSync(filePath, 'utf-8').slice(0, 65536);\n }\n } catch {}\n const edits = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n for (const edit of edits) {\n if (!edit || typeof edit !== 'object') continue;\n const old = edit.old_string || '';\n const nw = edit.new_string || '';\n if (old && content.includes(old)) {\n content = content.replace(old, nw);\n }\n }\n return content;\n }\n case 'NotebookEdit':\n case 'edit_notebook':\n return toolInput.new_source || '';\n case 'StrReplace':\n return toolInput.new_string || toolInput.content || toolInput.code_edit || '';\n default:\n return toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n }\n}\n\n// ─── HTTP with Retry ───\n\nexport async function postWithRetry(url: string, body: any, jwt: string, timeout = 8000): Promise<any> {\n let currentJwt = jwt;\n let resp: Response;\n try {\n resp = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + currentJwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n });\n } catch {\n return null;\n }\n\n let data: any;\n try { data = await resp.json(); } catch { return null; }\n\n // Retry on token expiry\n if (data?.detail && (data.detail.includes('Token has expired') || data.detail.includes('Invalid or expired token'))) {\n try {\n currentJwt = await refreshJwt(currentJwt);\n const resp2 = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + currentJwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n });\n data = await resp2.json();\n } catch {\n return null;\n }\n }\n\n return data;\n}\n\n// ─── Read Stdin ───\n\nexport async function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf-8');\n}\n\n// ─── Transcript path + message extraction (CC + Cursor) ───\n\n/** Cursor stores transcripts under ~/.cursor/projects/{slug}/agent-transcripts/ */\nexport function cursorProjectSlug(workspaceRoot: string): string {\n return workspaceRoot.replace(new RegExp('^[/]+'), '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n}\n\n/** Resolve transcript JSONL — payload field, CURSOR_TRANSCRIPT_PATH, or Cursor agent-transcripts dir. */\nexport function resolveTranscriptPath(payload: Record<string, unknown>): string {\n const explicit = typeof payload.transcript_path === 'string' ? payload.transcript_path.trim() : '';\n if (explicit && existsSync(explicit)) return explicit;\n\n const fromEnv = (process.env.CURSOR_TRANSCRIPT_PATH || '').trim();\n if (fromEnv && existsSync(fromEnv)) return fromEnv;\n\n if (!isCursorHookFormat()) return explicit || fromEnv || '';\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd)\n || workspaceRoots[0]\n || (process.env.CURSOR_PROJECT_DIR || '');\n if (!sessionId || !cwd) return explicit || fromEnv || '';\n\n const base = join(HOME, '.cursor', 'projects', cursorProjectSlug(cwd), 'agent-transcripts');\n const nested = join(base, sessionId, sessionId + '.jsonl');\n if (existsSync(nested)) return nested;\n if (existsSync(join(base, sessionId))) return nested;\n const flat = join(base, sessionId + '.jsonl');\n if (existsSync(flat)) return flat;\n\n return explicit || fromEnv || '';\n}\n\nfunction transcriptEntryType(entry: Record<string, unknown>): string {\n return String(entry.type || entry.role || '');\n}\n\nfunction transcriptContentBlock(entry: Record<string, unknown>): Record<string, unknown> | null {\n const msg = entry.message as Record<string, unknown> | string | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (content && typeof content === 'object' && !Array.isArray(content)) {\n return content as Record<string, unknown>;\n }\n return null;\n}\n\nfunction isProviderRedactedPlaceholder(text: string): boolean {\n const t = text.trim();\n if (!t) return false;\n return t === '[REDACTED]' || /^\\\\[REDACTED\\\\](\\\\s*\\\\[REDACTED\\\\])*$/.test(t);\n}\n\nfunction extractContentBlockText(block: Record<string, unknown>): string {\n const type = String(block.type || '');\n if (type === 'text' && typeof block.text === 'string') return block.text;\n if ((type === 'thinking' || type === 'redacted_thinking') && typeof block.thinking === 'string') {\n return block.thinking;\n }\n if (type === 'tool_result') {\n const c = block.content;\n if (typeof c === 'string') return c;\n if (Array.isArray(c)) {\n return c.map((part: unknown) => {\n if (typeof part === 'string') return part;\n const p = part as Record<string, unknown>;\n return typeof p.text === 'string' ? p.text : '';\n }).filter(Boolean).join('\\\\n');\n }\n return '';\n }\n return '';\n}\n\nfunction thoughtOverlayPath(sessionId: string): string {\n return join(HOME, '.synkro', 'sessions', sessionId, 'thought-overlay.jsonl');\n}\n\n/** Cursor afterAgentThought delivers full thinking; transcript JSONL stores [REDACTED]. Queue for transcript sync. */\nexport function appendThoughtOverlay(sessionId: string, text: string): void {\n const trimmed = text.trim();\n if (!sessionId || !trimmed || isProviderRedactedPlaceholder(trimmed)) return;\n const path = thoughtOverlayPath(sessionId);\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, JSON.stringify({ text: trimmed, ts: Date.now() }) + '\\\\n', 'utf-8');\n}\n\nfunction consumeThoughtOverlay(sessionId: string): string {\n const path = thoughtOverlayPath(sessionId);\n if (!existsSync(path)) return '';\n try {\n const lines = readFileSync(path, 'utf-8').split('\\\\n').filter(l => l.trim());\n if (lines.length === 0) return '';\n const first = JSON.parse(lines[0]) as { text?: string };\n const rest = lines.slice(1).join('\\\\n');\n if (rest.trim()) writeFileSync(path, rest + (rest.endsWith('\\\\n') ? '' : '\\\\n'), 'utf-8');\n else unlinkSync(path);\n const text = typeof first.text === 'string' ? first.text.trim() : '';\n return text && !isProviderRedactedPlaceholder(text) ? text : '';\n } catch {\n return '';\n }\n}\n\n/** Push a single conversation turn (used by Cursor afterAgentThought/afterAgentResponse hooks). */\nexport async function pushConversationMessage(\n sessionId: string,\n role: 'user' | 'assistant',\n content: string,\n opts: { gitRepo?: string; patchRedacted?: boolean; seq?: number } = {},\n): Promise<boolean> {\n const text = content.trim().slice(0, 8000);\n if (!sessionId || !text || isProviderRedactedPlaceholder(text)) return false;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return false;\n\n const seq = typeof opts.seq === 'number'\n ? opts.seq\n : Date.now() % 1_000_000_000;\n\n try {\n const body: Record<string, unknown> = {\n session_id: sessionId,\n repo: opts.gitRepo || '',\n messages: [{\n type: role,\n content: text,\n ts: new Date().toISOString(),\n message_index: seq,\n patch_redacted: opts.patchRedacted ?? true,\n }],\n };\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(5000),\n });\n return resp.ok;\n } catch {\n return false;\n }\n}\n\nfunction extractTranscriptEntryText(\n entry: Record<string, unknown>,\n maxLen = 8000,\n sessionId = '',\n): string {\n const block = transcriptContentBlock(entry);\n if (block) {\n const type = String(block.type || '');\n if (type === 'tool_use' || type === 'tool_call') return '';\n let text = extractContentBlockText(block);\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) text = overlay;\n }\n return text ? text.slice(0, maxLen) : '';\n }\n\n const msg = entry.message as Record<string, unknown> | string | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (typeof content === 'string') {\n let text = content;\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) text = overlay;\n }\n return text ? text.slice(0, maxLen) : '';\n }\n if (Array.isArray(content)) {\n const text = content.map((c: unknown) => {\n if (typeof c === 'string') return c;\n const b = c as Record<string, unknown>;\n const type = String(b?.type || '');\n if (type === 'tool_use' || type === 'tool_call') return '';\n return extractContentBlockText(b);\n }).filter(Boolean).join('\\\\n\\\\n').trim();\n if (!text) return '';\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) return overlay.slice(0, maxLen);\n }\n return text.slice(0, maxLen);\n }\n if (typeof msg === 'string') return msg.slice(0, maxLen);\n return '';\n}\n\n/** Offset-tracked ingest of new user/assistant turns into PGLite conversation_messages. */\nexport async function syncConversationTranscript(\n sessionId: string,\n transcriptPath: string,\n gitRepo = '',\n): Promise<{ ingested: number; messages: Array<Record<string, unknown>> }> {\n if (!sessionId || !transcriptPath || !existsSync(transcriptPath)) return { ingested: 0, messages: [] };\n\n const offsetDir = join(HOME, '.synkro', '.transcript-offsets');\n mkdirSync(offsetDir, { recursive: true });\n const offsetFile = join(offsetDir, sessionId);\n let offset = 0;\n if (existsSync(offsetFile)) {\n try { offset = parseInt(readFileSync(offsetFile, 'utf-8').trim(), 10) || 0; } catch {}\n }\n\n const raw = readFileSync(transcriptPath, 'utf-8');\n const allLines = raw.split('\\\\n').filter(l => l.trim());\n const totalLines = allLines.length;\n if (totalLines <= offset) return { ingested: 0, messages: [] };\n\n let startIdx = offset;\n const delta = totalLines - offset;\n if (delta > 200) startIdx = totalLines - 200;\n\n const messages: Array<Record<string, unknown>> = [];\n for (let i = startIdx; i < totalLines; i++) {\n try {\n const entry = JSON.parse(allLines[i]) as Record<string, unknown>;\n const kind = transcriptEntryType(entry);\n if (kind !== 'user' && kind !== 'assistant') continue;\n const text = extractTranscriptEntryText(entry, 8000, sessionId);\n if (!text) continue;\n\n const msgObj = entry.message as Record<string, unknown> | undefined;\n const content = (msgObj && typeof msgObj === 'object' ? msgObj.content : undefined) ?? entry.content;\n const singleBlock = transcriptContentBlock(entry);\n const msg: Record<string, unknown> = {\n message_index: i,\n type: kind,\n content: text,\n ts: entry.timestamp || null,\n };\n if (kind === 'assistant') {\n const blocks = Array.isArray(content)\n ? content\n : (singleBlock ? [singleBlock] : []);\n const toolCalls = blocks\n .filter((c: unknown) => {\n const b = c as Record<string, unknown>;\n return b?.type === 'tool_use' || b?.type === 'tool_call';\n })\n .map((c: unknown) => {\n const b = c as Record<string, unknown>;\n return {\n name: b.name,\n input: JSON.stringify(b.input || b.arguments || {}).slice(0, 500),\n id: b.id,\n };\n });\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n if (msgObj && typeof msgObj === 'object') {\n msg.model = msgObj.model || null;\n const u = msgObj.usage as Record<string, unknown> | undefined;\n if (u) {\n msg.usage = {\n input_tokens: u.input_tokens,\n output_tokens: u.output_tokens,\n cache_creation_input_tokens: u.cache_creation_input_tokens,\n cache_read_input_tokens: u.cache_read_input_tokens,\n };\n }\n }\n }\n messages.push(msg);\n } catch {}\n }\n\n if (messages.length === 0) {\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n return { ingested: 0, messages: [] };\n }\n\n const rawPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n const mcpPort = (rawPort > 0 && rawPort < 65536) ? rawPort : 18931;\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return { ingested: 0, messages };\n\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ session_id: sessionId, repo: gitRepo, messages }),\n signal: AbortSignal.timeout(5000),\n });\n if (resp.ok) {\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n const data = await resp.json() as { ingested?: number };\n return { ingested: data.ingested ?? messages.length, messages };\n }\n } catch {}\n return { ingested: 0, messages };\n}\n\nexport interface TranscriptContext {\n userIntent: string;\n recentUserMessages: string[];\n recentMessages: Array<{ type: string; text: string }>;\n recentActions: Array<{ tool: string; input: string }>;\n sessionSummary: string;\n ccModel: string;\n ccUsage: Record<string, any>;\n}\n\nexport function extractTranscript(transcriptPath: string | undefined): TranscriptContext {\n const ctx: TranscriptContext = {\n userIntent: '',\n recentUserMessages: [],\n recentMessages: [],\n recentActions: [],\n sessionSummary: '',\n ccModel: '',\n ccUsage: {},\n };\n\n if (!transcriptPath || !existsSync(transcriptPath)) return ctx;\n\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const lines = raw.split('\\\\n').filter(l => l.trim());\n // Take the last 400 lines\n const tail = lines.slice(-400);\n\n const parsed: any[] = [];\n for (const line of tail) {\n try { parsed.push(JSON.parse(line)); } catch {}\n }\n\n // Recent user messages (last 5)\n const userMsgs: string[] = [];\n for (const entry of parsed) {\n if (transcriptEntryType(entry) !== 'user') continue;\n const text = extractTranscriptEntryText(entry);\n if (text) userMsgs.push(text);\n }\n ctx.recentUserMessages = userMsgs.slice(-5);\n ctx.userIntent = ctx.recentUserMessages[ctx.recentUserMessages.length - 1] || '';\n\n // Recent messages (last 10, user + assistant)\n const msgs: Array<{ type: string; text: string }> = [];\n for (const entry of parsed) {\n const kind = transcriptEntryType(entry);\n if (kind !== 'user' && kind !== 'assistant') continue;\n const text = extractTranscriptEntryText(entry, 500);\n if (text) msgs.push({ type: kind, text });\n }\n ctx.recentMessages = msgs.slice(-10);\n\n // Recent tool calls (last 5)\n const actions: Array<{ tool: string; input: string }> = [];\n for (const entry of parsed) {\n if (transcriptEntryType(entry) !== 'assistant') continue;\n const msg = entry.message;\n const content = msg?.content ?? entry.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use' && block.type !== 'tool_call') continue;\n actions.push({\n tool: block.name || '',\n input: JSON.stringify(block.input || block.arguments || {}).slice(0, 200),\n });\n }\n }\n ctx.recentActions = actions.slice(-5);\n\n // Session summary\n for (const entry of parsed) {\n if (entry.type === 'summary' && entry.summary) {\n ctx.sessionSummary = entry.summary;\n }\n }\n\n // CC model\n const assistantEntries = parsed.filter(e => transcriptEntryType(e) === 'assistant');\n if (assistantEntries.length > 0) {\n const last = assistantEntries[assistantEntries.length - 1];\n ctx.ccModel = last.message?.model || '';\n const usage = last.message?.usage;\n if (usage) {\n ctx.ccUsage = {\n input_tokens: usage.input_tokens,\n output_tokens: usage.output_tokens,\n cache_creation_input_tokens: usage.cache_creation_input_tokens,\n cache_read_input_tokens: usage.cache_read_input_tokens,\n };\n }\n }\n } catch {}\n\n return ctx;\n}\n\n// ─── Last Prompt ───\n\nexport function readLastPrompt(sessionId?: string): string {\n try {\n if (sessionId) {\n const perSession = join(SESSIONS_DIR, sessionId + '.last-prompt');\n if (existsSync(perSession)) return readFileSync(perSession, 'utf-8').trim();\n }\n if (!existsSync(LAST_PROMPT_FILE)) return '';\n return readFileSync(LAST_PROMPT_FILE, 'utf-8').trim();\n } catch {\n return '';\n }\n}\n\n// ─── Find Nearest Package Dependencies ───\n\nexport function findNearestDeps(filePath: string): Record<string, string> {\n let dir = dirname(filePath);\n while (dir !== '/' && dir !== '.') {\n const pkg = join(dir, 'package.json');\n if (existsSync(pkg)) {\n try {\n const data = JSON.parse(readFileSync(pkg, 'utf-8'));\n return { ...(data.dependencies || {}), ...(data.devDependencies || {}) };\n } catch {}\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return {};\n}\n\n// ─── Consent Tracking ───\n\nconst CONSENT_FILE = join(HOME, '.synkro', '.local-consent');\n\nexport function consentGrant(sessionId: string, hash: string): void {\n try {\n const dir = dirname(CONSENT_FILE);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const line = sessionId + '\\\\t' + hash + '\\\\tactive\\\\n';\n const { appendFileSync } = require('node:fs');\n appendFileSync(CONSENT_FILE, line, 'utf-8');\n } catch {}\n}\n\nexport function consentHasActive(sessionId: string, hash: string): boolean {\n try {\n if (!existsSync(CONSENT_FILE)) return false;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n return content.includes(sessionId + '\\\\t' + hash + '\\\\tactive');\n } catch {\n return false;\n }\n}\n\nexport function consentConsume(sessionId: string, hash: string): void {\n try {\n if (!existsSync(CONSENT_FILE)) return;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n const target = sessionId + '\\\\t' + hash + '\\\\tactive';\n const replacement = sessionId + '\\\\t' + hash + '\\\\tconsumed';\n const updated = content.split('\\\\n').map(l => l === target ? replacement : l).join('\\\\n');\n writeFileSync(CONSENT_FILE, updated, 'utf-8');\n } catch {}\n}\n\n// ─── Crypto Hash ───\n\nexport function hashCommand(cmd: string): string {\n const { createHash } = require('node:crypto');\n return createHash('sha256').update(cmd).digest('hex').slice(0, 16);\n}\n\n// ─── Edit line delta + transcript usage ───\n\nexport function countTextLines(text: string): number {\n if (!text) return 0;\n return text.split('\\\\n').length;\n}\n\n/** Count lines added/removed from a write, search-replace, or multi-edit payload. */\nexport function countEditLineDelta(source: {\n writeContent?: string;\n /** When set, Write counts net delta vs the file on disk instead of full file length. */\n existingContent?: string;\n old_string?: string;\n new_string?: string;\n edits?: Array<{ old_string?: string; new_string?: string }>;\n}): { linesAdded: number; linesRemoved: number } {\n let linesAdded = 0;\n let linesRemoved = 0;\n if (source.writeContent != null && source.writeContent !== '') {\n const newLines = countTextLines(String(source.writeContent));\n if (source.existingContent != null && source.existingContent !== '') {\n const oldLines = countTextLines(String(source.existingContent));\n return {\n linesAdded: Math.max(0, newLines - oldLines),\n linesRemoved: Math.max(0, oldLines - newLines),\n };\n }\n return { linesAdded: newLines, linesRemoved: 0 };\n }\n if (source.old_string != null && source.new_string != null) {\n const oldLines = countTextLines(String(source.old_string));\n const newLines = countTextLines(String(source.new_string));\n return {\n linesAdded: Math.max(0, newLines - oldLines),\n linesRemoved: Math.max(0, oldLines - newLines),\n };\n }\n if (Array.isArray(source.edits)) {\n for (const e of source.edits) {\n if (!e || typeof e !== 'object') continue;\n const oldLines = countTextLines(String(e.old_string || ''));\n const newLines = countTextLines(String(e.new_string || ''));\n linesAdded += Math.max(0, newLines - oldLines);\n linesRemoved += Math.max(0, oldLines - newLines);\n }\n }\n return { linesAdded, linesRemoved };\n}\n\n/** Normalize model names so the same Cursor session doesn't split across agent buckets. */\nexport function normalizeCaptureModel(model: string): string {\n const m = (model || '').trim();\n if (!m || m === 'unknown') return 'unknown';\n if (m.startsWith('cursor/') || m.startsWith('claude-') || m.startsWith('gpt-') || m.startsWith('gemini-')) return m;\n if (/^composer/i.test(m) || m === 'auto' || m === 'default') return 'cursor/' + m;\n return m;\n}\n\n/** Line metrics for guard_checks — Cursor Edit/StrReplace post deltas via afterFileEdit. */\nexport function captureLineMetrics(\n agentKind: 'cursor' | 'claude_code',\n toolName: string,\n linesAdded: number,\n linesRemoved: number,\n): { linesAdded?: number; linesRemoved?: number } {\n if (agentKind === 'cursor' && toolName !== 'Write') return {};\n return { linesAdded, linesRemoved };\n}\n\nfunction usageTextFromTranscriptEntry(entry: Record<string, unknown>): string {\n const msg = entry.message as Record<string, unknown> | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (typeof content === 'string') return content;\n if (content && typeof content === 'object' && !Array.isArray(content)) {\n const block = content as Record<string, unknown>;\n if (block.type === 'text' && typeof block.text === 'string') return block.text;\n if (typeof block.text === 'string') return block.text;\n return '';\n }\n if (!Array.isArray(content)) return '';\n return content.map((c: unknown) => {\n if (typeof c === 'string') return c;\n const b = c as Record<string, unknown>;\n if (b?.type === 'text' && typeof b.text === 'string') return b.text;\n if (typeof b?.text === 'string') return b.text;\n if (b?.type === 'tool_use' || b?.type === 'tool_call') {\n return JSON.stringify(b.input || b.arguments || {});\n }\n return '';\n }).join('\\\\n');\n}\n\nfunction addUsageBlock(\n result: { model: string; totals: Record<string, number> },\n u: Record<string, unknown> | null | undefined,\n): boolean {\n if (!u) return false;\n const inp = Number(u.input_tokens ?? u.inputTokens ?? 0) || 0;\n const out = Number(u.output_tokens ?? u.outputTokens ?? 0) || 0;\n const cw = Number(u.cache_creation_input_tokens ?? u.cacheCreationInputTokens ?? 0) || 0;\n const cr = Number(u.cache_read_input_tokens ?? u.cacheReadInputTokens ?? 0) || 0;\n if (inp + out + cw + cr <= 0) return false;\n result.totals.in += inp;\n result.totals.out += out;\n result.totals.cw += cw;\n result.totals.cr += cr;\n return true;\n}\n\nfunction msgModel(entry: Record<string, unknown>): string {\n const msg = entry.message as Record<string, unknown> | undefined;\n return typeof msg?.model === 'string' ? msg.model : '';\n}\n\n/** Sum token usage from CC or Cursor agent-transcript JSONL. */\nexport function aggregateUsage(\n transcriptPath: string,\n opts?: { modelFallback?: string },\n): { model: string; totals: Record<string, number> } {\n const result = { model: opts?.modelFallback || '', totals: { in: 0, out: 0, cw: 0, cr: 0 } };\n if (!transcriptPath || !existsSync(transcriptPath)) return result;\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const entries: Record<string, unknown>[] = [];\n for (const line of raw.split('\\\\n')) {\n if (!line.trim()) continue;\n try { entries.push(JSON.parse(line) as Record<string, unknown>); } catch {}\n }\n\n let sawExplicit = false;\n for (const entry of entries) {\n const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;\n const msg = entry.message as Record<string, unknown> | undefined;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, tokenCount)) sawExplicit = true;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, msg?.usage as Record<string, unknown>)) sawExplicit = true;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, entry.usage as Record<string, unknown>)) sawExplicit = true;\n }\n\n for (const entry of entries) {\n const role = String(entry.role || entry.type || '');\n const modelInfo = entry.modelInfo as Record<string, unknown> | undefined;\n const model = (typeof modelInfo?.modelName === 'string' && modelInfo.modelName)\n || (typeof modelInfo?.model === 'string' && modelInfo.model)\n || (typeof entry.model === 'string' && entry.model)\n || msgModel(entry);\n if (model) result.model = model;\n\n const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;\n const msg = entry.message as Record<string, unknown> | undefined;\n const hadLine = addUsageBlock(result, tokenCount)\n || addUsageBlock(result, msg?.usage as Record<string, unknown>)\n || addUsageBlock(result, entry.usage as Record<string, unknown>);\n\n if (hadLine || sawExplicit) continue;\n\n const text = usageTextFromTranscriptEntry(entry);\n if (!text) continue;\n const est = Math.ceil(text.length / 4);\n if (role === 'user' || entry.type === 'user') result.totals.in += est;\n else if (role === 'assistant' || entry.type === 'assistant') result.totals.out += est;\n }\n } catch {}\n return result;\n}\n\n/** Emit a usage_tick telemetry row when transcript usage is non-zero. */\nexport function emitUsageTick(params: {\n sessionId: string;\n usage: { model: string; totals: Record<string, number> };\n hookType: string;\n gitRepo?: string;\n modelFallback?: string;\n}): void {\n const { sessionId, usage, hookType, gitRepo, modelFallback } = params;\n if (!sessionId || usage.totals.in + usage.totals.out <= 0) return;\n let model = usage.model || modelFallback || 'unknown';\n if (modelFallback && !usage.model) model = modelFallback;\n if (isCursorHookFormat() && model && !model.startsWith('cursor/') && model !== 'cursor') {\n model = 'cursor/' + model;\n }\n appendLocalTelemetry({\n capture_type: 'usage_tick',\n event_id: mintEventId('usage'),\n hook_type: hookType,\n verdict: 'allow',\n severity: 'none',\n session_id: sessionId,\n model,\n cc_model: model,\n cc_usage: {\n input_tokens: usage.totals.in,\n output_tokens: usage.totals.out,\n cache_creation_input_tokens: usage.totals.cw,\n cache_read_input_tokens: usage.totals.cr,\n },\n ...(gitRepo ? { repo: gitRepo } : {}),\n });\n}\n\nexport function cursorModelFromPayload(payload: Record<string, unknown>): string {\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n if (!rawModel) return 'cursor';\n return rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel;\n}\n\n// ─── Scan Finding Dispatch ───\n\nexport type ScanFindingInput = {\n finding_type: 'cwe' | 'cve';\n finding_id: string;\n severity?: string;\n detail?: string;\n description?: string;\n package_name?: string;\n package_version?: string;\n fixed_version?: string;\n aliases?: string[];\n references?: Array<{ type: string; url: string }>;\n cwe_name?: string;\n};\n\n/** Persist open scan_findings rows for a block and flush the telemetry spool. */\nexport function emitBlockScanFindings(\n jwt: string,\n captureDepth: string,\n ctx: { session_id: string; file_path: string; repo?: string },\n findings: ScanFindingInput[],\n fallback?: ScanFindingInput,\n): void {\n const rows = findings.length > 0 ? findings : (fallback ? [fallback] : []);\n for (const f of rows) {\n dispatchFinding(jwt, {\n session_id: ctx.session_id,\n file_path: ctx.file_path,\n repo: ctx.repo,\n status: 'open',\n ...f,\n }, captureDepth);\n }\n if (rows.length > 0) drainSpool().catch(() => {});\n}\n\nexport function dispatchFinding(\n jwt: string,\n finding: {\n session_id: string;\n file_path: string;\n repo?: string;\n finding_type: 'cwe' | 'cve';\n finding_id: string;\n severity?: string;\n status: 'open' | 'resolved' | 'exempted';\n detail?: string;\n description?: string;\n package_name?: string;\n package_version?: string;\n fixed_version?: string;\n aliases?: string[];\n references?: Array<{ type: string; url: string }>;\n cwe_name?: string;\n },\n captureDepth: string,\n): void {\n const localEntry: Record<string, any> = {\n capture_type: 'scan_finding',\n ...finding,\n };\n appendLocalTelemetry(localEntry);\n\n // Cloud copy carries the full finding; shipCloud gates on storage mode.\n const cloudBody: Record<string, any> = {\n finding_type: finding.finding_type,\n finding_id: finding.finding_id,\n severity: finding.severity,\n status: finding.status,\n session_id: finding.session_id,\n file_path: finding.file_path,\n repo: finding.repo,\n package_name: finding.package_name,\n package_version: finding.package_version,\n fixed_version: finding.fixed_version,\n aliases: finding.aliases,\n references: finding.references,\n cwe_name: finding.cwe_name,\n detail: finding.detail,\n description: finding.description,\n };\n shipCloud(jwt, '/api/v1/hook/finding', cloudBody);\n}\n\nexport function dispatchScanResult(\n jwt: string,\n scan: {\n session_id: string;\n file_path: string;\n scan_type: 'cve' | 'cwe' | 'pkg';\n result: 'pass' | 'block' | 'error';\n finding_count: number;\n finding_ids?: string[];\n severity?: string;\n repo?: string;\n },\n): void {\n const localEntry: Record<string, any> = {\n capture_type: 'scan_result',\n event_id: 'scan_' + Date.now() + '_' + process.pid,\n _ts: new Date().toISOString(),\n ...scan,\n };\n appendLocalTelemetry(localEntry);\n shipCloud(jwt, '/api/v1/hook/scan-result', {\n scan_type: scan.scan_type,\n result: scan.result,\n finding_count: scan.finding_count,\n finding_ids: scan.finding_ids,\n severity: scan.severity,\n session_id: scan.session_id,\n file_path: scan.file_path,\n repo: scan.repo,\n });\n}\n\n// ─── Hook tool-name sets (CC + Cursor) ───\n\nexport const EDIT_TOOL_NAMES = new Set([\n 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'StrReplace',\n 'edit_file', 'reapply', 'edit_notebook', 'ApplyPatch', 'apply_patch',\n]);\nexport const SHELL_TOOL_NAMES = new Set([\n 'Bash', 'Shell', 'Read', 'Grep', 'Glob', 'terminal', 'run_terminal_cmd', 'execute_command',\n]);\nexport const AGENT_TOOL_NAMES = new Set(['Agent', 'Task']);\nexport const PLAN_TOOL_NAMES = new Set(['ExitPlanMode', 'SwitchMode', 'CreatePlan']);\n\nexport function isEditTool(toolName: string): boolean {\n return EDIT_TOOL_NAMES.has(toolName);\n}\nexport function isShellTool(toolName: string): boolean {\n return SHELL_TOOL_NAMES.has(toolName);\n}\nexport function isAgentTool(toolName: string): boolean {\n return AGENT_TOOL_NAMES.has(toolName);\n}\nexport function isPlanTool(toolName: string): boolean {\n return PLAN_TOOL_NAMES.has(toolName);\n}\n\nexport function hookSessionId(payload: Record<string, unknown>): string {\n return String(payload.session_id ?? payload.conversation_id ?? '');\n}\n\nexport function isCursorHookFormat(): boolean {\n return process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor');\n}\n\n// Cursor reads CC hooks from ~/.claude/settings.json and fires them alongside\n// its own ~/.cursor/hooks.json entries. When that happens, agentKind is\n// 'claude_code' but the payload model is non-Claude (e.g. gpt-5.5).\n// Return true so the CC hook can bail out and let Cursor's hooks handle it.\nexport function isCursorInvokingCcHook(agentKind: string, model: string): boolean {\n if (agentKind === 'cursor') return false;\n if (!model || model === 'unknown' || model === '') return false;\n return !model.startsWith('claude-');\n}\n\nlet cursorHookExited = false;\n\nexport function setupCursorHookSignals(): void {\n if (!isCursorHookFormat()) return;\n process.on('SIGTERM', () => outputEmpty());\n}\n\nfunction cursorHookExit(): never {\n cursorHookExited = true;\n process.exit(0);\n}\n\n// ─── Grader-unavailable diagnostic log ───\n// Records every time a hook tried to call the local grader and fell open\n// because the call failed. JSONL at ~/.synkro/grader-unavailable.log so the\n// user can pinpoint cause (timeout vs ECONNREFUSED vs HTTP 5xx vs sick pool)\n// instead of guessing from a one-shot system message in the CC UI.\n\nconst UNAVAIL_LOG = join(HOME, '.synkro', 'grader-unavailable.log');\n\nexport function isGraderNotConfigured(errorMessage: string): boolean {\n const lower = errorMessage.toLowerCase();\n return lower.includes('no worker') || lower.includes('not configured') || lower.includes('agent_kind') || lower.includes('no pool');\n}\n\nexport function graderUnavailableMessage(hook: string, target: string, errorMessage: string, agentKind: AgentKind = 'claude_code'): string {\n if (errorMessage === 'SYNKRO_CHANNEL_DOWN') {\n return hook + ' ' + target + ' → local grader unavailable (container not running), skipped';\n }\n if (isGraderNotConfigured(errorMessage)) {\n const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';\n return hook + ' ' + target + ' → local grader not configured for ' + agent + '. Add this agent to your .synkro file and run \\`synkro install\\`.';\n }\n return hook + ' ' + target + ' → local grader unavailable, skipped';\n}\n\nexport function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {\n try {\n const entry = {\n ts: new Date().toISOString(),\n hook,\n target,\n error: errorMessage.slice(0, 500),\n };\n appendFileSync(UNAVAIL_LOG, JSON.stringify(entry) + '\\\\n', 'utf-8');\n } catch {\n // best-effort — never let logging failure cascade into a hook failure\n }\n}\n\n// ─── Output Helpers ───\n\nexport function outputJson(obj: any): void {\n if (isCursorHookFormat()) {\n if (obj?.permission === 'allow') {\n const u = typeof obj.user_message === 'string' ? obj.user_message : '';\n const a = typeof obj.agent_message === 'string' ? obj.agent_message : u;\n if (u || a) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({ permission: 'allow' }) + '\\\\n');\n }\n cursorHookExit();\n }\n }\n const hso = obj?.hookSpecificOutput;\n const sys = typeof obj?.systemMessage === 'string' ? obj.systemMessage : '';\n if (hso?.permissionDecision === 'deny') {\n const reason = hso.permissionDecisionReason || hso.additionalContext || sys;\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({\n permission: 'deny',\n user_message: sys || reason,\n agent_message: hso.additionalContext || reason,\n }) + '\\\\n');\n }\n cursorHookExit();\n }\n const addCtx = typeof hso?.additionalContext === 'string' ? hso.additionalContext : '';\n const ctx = sys || addCtx;\n if (ctx) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({ permission: 'allow' }) + '\\\\n');\n }\n cursorHookExit();\n }\n outputEmpty();\n return;\n }\n console.log(JSON.stringify(obj));\n}\n\nexport function outputEmpty(): void {\n if (isCursorHookFormat()) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n cursorHookExit();\n }\n console.log('{}');\n}\n`;\n\n\n// ─── CC PreToolUse Edit/Write/MultiEdit/NotebookEdit pre-check (TypeScript) ───\n\nexport const EDIT_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, reconstructContent, isPathUnder, postWithRetry,\n readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,\n appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,\n captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n\n const fileShort = basename(filePath);\n log('editGuard checking: ' + fileShort);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName || 'Edit', summary: 'editing ' + fileShort, file: filePath });\n\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n // Reconstruct proposed content\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) { outputEmpty(); return; }\n\n // Build diff field\n let diffField: any = null;\n if (toolInput.old_string != null || toolInput.new_string != null || toolInput.edits != null) {\n diffField = {};\n if (toolInput.old_string != null) diffField.old_string = toolInput.old_string;\n if (toolInput.new_string != null) diffField.new_string = toolInput.new_string;\n if (toolInput.edits != null) diffField.edits = toolInput.edits;\n }\n\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n let existingContent = '';\n if (toolName === 'Write' && existsSync(fullPath)) {\n try { existingContent = readFileSync(fullPath, 'utf-8'); } catch {}\n }\n\n // Compute lines added/removed (Write = net delta vs file on disk)\n const { linesAdded, linesRemoved } = countEditLineDelta(\n toolName === 'Write'\n ? {\n writeContent: toolInput.content || toolInput.contents || '',\n existingContent,\n }\n : {\n old_string: toolInput.old_string,\n new_string: toolInput.new_string,\n edits: Array.isArray(toolInput.edits) ? toolInput.edits : undefined,\n },\n );\n const lineMetrics = captureLineMetrics(agentKind, toolName, linesAdded, linesRemoved);\n\n // Read file before edit for cloud payload\n let fileBefore = '';\n if (toolName !== 'Write' && filePath && isPathUnder(filePath, cwd || '.') && existsSync(fullPath)) {\n try { fileBefore = readFileSync(fullPath, 'utf-8').slice(0, 65536); } catch {}\n }\n\n // Extract transcript context\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n const captureModel = agentKind === 'cursor'\n ? cursorModelFromPayload(payload)\n : (transcript.ccModel || String(payload.model ?? payload.model_id ?? ''));\n\n if (isCursorInvokingCcHook(agentKind, captureModel)) { outputEmpty(); return; }\n\n // Model detection: prefer transcript (CC), fall back to payload (Cursor)\n if (!transcript.ccModel) {\n transcript.ccModel = captureModel;\n }\n\n // Load config and decide route\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' editGuard → skipped (silent mode)' });\n return;\n }\n\n if (rt === 'local') {\n // ─── Local grading: org rules ONLY (channel 1, port 18929) ───\n const proposedShort = proposed.slice(0, 4000);\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const graderContent = 'file=' + filePath + ' content=' + proposedShort;\n const relevantRules = await filterRules(\n ruleFilterText(graderContent, transcript.userIntent || lastPrompt),\n config.rules,\n );\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'File: ' + filePath,\n 'Proposed content (first 4000 chars):',\n proposedShort,\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this edit — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no hardcoded secrets in file. R005: in-repo path only.\" Cover every rule shown.',\n ].join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('editGuard', fileShort, errMsg);\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('editGuard', fileShort, errMsg, graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const editContent = 'file=' + filePath + ' content=' + proposed.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const denyReason = mode === 'fix'\n ? 'Guard: ' + guardReason + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the violation in code yourself.'\n : 'Guard: ' + guardReason + '\\\\nAsk the user for explicit consent before retrying.';\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: relevantRules, violatedRules,\n ccModel: captureModel, ...lineMetrics,\n });\n outputJson({\n systemMessage: tagStr + ' editGuard ' + fileShort + ' → blocked: ' + guardReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n return;\n }\n\n // Clean\n dispatchCapture(jwt, 'edit', 'pass', 'clean', verdict.category || 'trivial_edit',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n ccModel: captureModel, ...lineMetrics,\n });\n const passLine = tagStr + ' editGuard ' + fileShort + ' → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({ systemMessage: passLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge. ' + (verdict.reason || 'no policy violations detected') } });\n return;\n }\n\n // ─── Cloud grading ───\n const deps = findNearestDeps(filePath);\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n file_path: filePath,\n content: proposed,\n file_before: fileBefore || null,\n diff: diffField,\n dependencies: deps,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('editGuard ' + fileShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('editGuard ' + fileShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n const hookResp = resp.hook_response;\n const decision = hookResp?.hookSpecificOutput?.permissionDecision;\n\n if (decision === 'deny' || decision === 'ask') {\n log('editGuard ' + fileShort + ' → BLOCKED');\n // Strip permissionDecision — we use systemMessage only\n const cleaned = { ...hookResp };\n if (cleaned.hookSpecificOutput) {\n cleaned.hookSpecificOutput = { ...cleaned.hookSpecificOutput };\n delete cleaned.hookSpecificOutput.permissionDecision;\n delete cleaned.hookSpecificOutput.permissionDecisionReason;\n }\n outputJson(cleaned);\n } else {\n const reason = hookResp.reason || '';\n log('editGuard ' + fileShort + ' → pass' + (reason ? ': ' + reason : ''));\n outputJson(hookResp);\n }\n } catch (err) {\n log('editGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse CWE scan (standalone, channel 2) (TypeScript) ───\n\nexport const CWE_PRECHECK_TS = String.raw`#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,\n localGradeCwe, parseVerdict, reconstructContent, readStdin, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, isShellTool, isCursorHookFormat,\n extractShellCodeWrites, hookSessionId, filePathFromToolInput, emitBlockScanFindings, dispatchFinding, dispatchCapture, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { basename, extname, resolve, join, dirname } from 'node:path';\nimport { readFileSync, readdirSync, existsSync } from 'node:fs';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nfunction detectModel(payload: Record<string, unknown>): string {\n const raw = String(payload.model ?? payload.model_id ?? '');\n if (agentKind === 'cursor') return raw ? (raw.startsWith('cursor/') ? raw : 'cursor/' + raw) : 'cursor';\n return raw || '';\n}\n\ninterface PackageCapability {\n name: string;\n description: string;\n capabilities: string[];\n sourceExcerpt: string;\n}\n\nconst NON_CODE_EXTS = new Set([\n '.md', '.mdx', '.txt', '.rst', '.adoc', '.org',\n '.log', '.csv', '.tsv', '.html', '.htm',\n '.lock', '.gitignore', '.dockerignore', '.npmignore',\n '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.pdf',\n '.woff', '.woff2', '.ttf', '.otf',\n]);\n\ninterface CweScanTarget {\n filePath: string;\n cweContent: string;\n cweDiffSection: string;\n toolName: string;\n toolInput: any;\n}\n\nconst JS_DANGEROUS_MODULES = new Set([\n 'child_process', 'net', 'dgram', 'http', 'https', 'fs', 'vm',\n 'worker_threads', 'cluster', 'dns', 'tls', 'crypto',\n]);\n\nconst PY_DANGEROUS_MODULES = new Set([\n 'subprocess', 'os', 'socket', 'requests', 'urllib', 'ctypes',\n 'importlib', 'shutil', 'tempfile', 'webbrowser',\n]);\n\nfunction detectNewImports(toolName: string, toolInput: any, fileExt: string): string[] {\n const newCode = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'\n ? (toolInput.new_string || '')\n : toolName === 'ApplyPatch' || toolName === 'apply_patch'\n ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')\n : toolName === 'MultiEdit'\n ? (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\\n') : '')\n : (toolInput.content || toolInput.code_edit || '');\n\n if (!newCode) return [];\n const imports: string[] = [];\n\n if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(fileExt)) {\n const jsMatches = newCode.matchAll(/(?:from\\s+['\"]|require\\s*\\(\\s*['\"])([^./'\"@][^'\"]*|@[^'\"]+)['\"]/g);\n for (const m of jsMatches) {\n const pkg = m[1].startsWith('@') ? m[1].split('/').slice(0, 2).join('/') : m[1].split('/')[0];\n if (pkg && !imports.includes(pkg)) imports.push(pkg);\n }\n } else if (['.py'].includes(fileExt)) {\n const pyMatches = newCode.matchAll(/(?:^import\\s+|^from\\s+)([a-zA-Z_]\\w*)/gm);\n for (const m of pyMatches) {\n if (m[1] && !imports.includes(m[1])) imports.push(m[1]);\n }\n }\n\n return imports;\n}\n\nfunction scanPackageCapabilities(pkgName: string, cwd: string): PackageCapability | null {\n const pkgDir = resolve(cwd, 'node_modules', pkgName);\n if (!existsSync(pkgDir)) return null;\n\n try {\n const pkgJsonPath = join(pkgDir, 'package.json');\n if (!existsSync(pkgJsonPath)) return null;\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n\n const capabilities: string[] = [];\n const dangerousFiles: string[] = [];\n\n function scanDir(dir: string, depth: number) {\n if (depth > 3 || dangerousFiles.length >= 3) return;\n let entries: string[];\n try { entries = readdirSync(dir); } catch { return; }\n for (const entry of entries) {\n if (entry === 'node_modules' || entry.startsWith('.')) continue;\n const full = join(dir, entry);\n if (entry.endsWith('.js') || entry.endsWith('.mjs') || entry.endsWith('.cjs') || entry.endsWith('.ts')) {\n try {\n const src = readFileSync(full, 'utf-8').slice(0, 5000);\n for (const mod of JS_DANGEROUS_MODULES) {\n if (src.includes(\"'\" + mod + \"'\") || src.includes('\"' + mod + '\"')) {\n if (!capabilities.includes(mod)) capabilities.push(mod);\n if (dangerousFiles.length < 3) {\n const short = full.replace(pkgDir + '/', '');\n const lines = src.split('\\n').filter(l =>\n JS_DANGEROUS_MODULES.has(l.match(/require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\)/)?.[1] || '') ||\n JS_DANGEROUS_MODULES.has(l.match(/from\\s+['\"]([^'\"]+)['\"]/)?.[1] || '')\n ).slice(0, 5).join('\\n');\n if (lines) dangerousFiles.push('--- ' + short + ' ---\\n' + lines);\n }\n break;\n }\n }\n } catch {}\n } else {\n try {\n const stat = require('node:fs').statSync(full);\n if (stat.isDirectory()) scanDir(full, depth + 1);\n } catch {}\n }\n }\n }\n\n scanDir(pkgDir, 0);\n\n if (capabilities.length === 0) return null;\n\n return {\n name: pkgName,\n description: pkgJson.description || '',\n capabilities,\n sourceExcerpt: dangerousFiles.join('\\n'),\n };\n } catch {\n return null;\n }\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const shellCommand = typeof payload.command === 'string' ? payload.command.trim() : '';\n const ccModel = detectModel(payload);\n\n if (isCursorInvokingCcHook(agentKind, ccModel)) { outputEmpty(); return; }\n\n const targets: CweScanTarget[] = [];\n\n if (isCursorHookFormat() && (shellCommand || isShellTool(toolName))) {\n const cmd = shellCommand || String(toolInput.command || '');\n if (!cmd) { outputEmpty(); return; }\n for (const w of extractShellCodeWrites(cmd, cwd)) {\n if (w.filePath.includes('/.synkro/hooks/')) continue;\n const ext = extname(w.filePath).toLowerCase();\n if (NON_CODE_EXTS.has(ext)) continue;\n targets.push({\n filePath: w.filePath,\n cweContent: w.content,\n cweDiffSection: '',\n toolName: toolName || 'Shell',\n toolInput: {},\n });\n }\n if (targets.length === 0) { outputEmpty(); return; }\n } else if (isEditTool(toolName)) {\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n const fileExt = extname(filePath).toLowerCase();\n if (NON_CODE_EXTS.has(fileExt)) { outputEmpty(); return; }\n\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) { outputEmpty(); return; }\n\n let cweContent: string;\n let cweDiffSection = '';\n if (toolName === 'Edit' || toolName === 'MultiEdit' || toolName === 'edit_file' || toolName === 'reapply' || toolName === 'ApplyPatch' || toolName === 'apply_patch') {\n const newStr = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'\n ? (toolInput.new_string || '')\n : toolName === 'ApplyPatch' || toolName === 'apply_patch'\n ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')\n : (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\\n') : '');\n cweDiffSection = newStr.slice(0, 4000);\n const changeIdx = proposed.indexOf(newStr);\n if (changeIdx >= 0 && proposed.length > 6000) {\n const start = Math.max(0, changeIdx - 2000);\n const end = Math.min(proposed.length, changeIdx + newStr.length + 2000);\n cweContent = proposed.slice(start, end);\n } else {\n cweContent = proposed.slice(0, 6000);\n }\n } else {\n cweContent = proposed.slice(0, 4000);\n }\n\n targets.push({ filePath, cweContent, cweDiffSection, toolName, toolInput });\n } else {\n outputEmpty();\n return;\n }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await cweRoute(config, synkroFile);\n\n if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cweScan:' + (graderPool === 'claude_code' ? 'claude' : graderPool) + '] skipped (silent mode)' });\n return;\n }\n\n for (const scan of targets) {\n const filePath = scan.filePath;\n const cweContent = scan.cweContent;\n const cweDiffSection = scan.cweDiffSection;\n const scanToolName = scan.toolName;\n const scanToolInput = scan.toolInput;\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n const fileShort = basename(filePath);\n const fileExt = extname(filePath).toLowerCase();\n\n const exemptedCwes = new Set<string>();\n for (const ex of config.scanExemptions) {\n if (ex.cwe_id && filePath.includes(ex.path)) {\n exemptedCwes.add(ex.cwe_id.toUpperCase());\n }\n }\n\n const graderLabel = graderPool === 'claude_code' ? 'claude' : graderPool;\n const cweTag = '[synkro:' + rt + ':cweScan:' + graderLabel + ']';\n\n if (rt === 'skip') {\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, 'channel down', graderPool) });\n return;\n }\n\n if (rt === 'byok') {\n let packageContext: PackageCapability[] | undefined;\n if (cwd) {\n const newImports = detectNewImports(scanToolName, scanToolInput, fileExt);\n if (newImports.length > 0) {\n const caps = newImports\n .slice(0, 5)\n .map(pkg => scanPackageCapabilities(pkg, cwd))\n .filter((c): c is PackageCapability => c !== null);\n if (caps.length > 0) packageContext = caps;\n }\n }\n const scanBody: any = { file_path: filePath, content: cweContent };\n if (packageContext) {\n scanBody.package_context = packageContext.map(c => ({\n name: c.name, description: c.description, capabilities: c.capabilities, source_excerpt: c.sourceExcerpt,\n }));\n }\n\n let cweResp: any;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(scanBody),\n signal: AbortSignal.timeout(12000),\n });\n if (!resp.ok) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud CWE scan failed (HTTP ' + resp.status + '), skipped' });\n return;\n }\n cweResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud CWE scan timeout, skipped' });\n return;\n }\n\n const findings = Array.isArray(cweResp?.findings) ? cweResp.findings : [];\n if (cweResp?.action === 'deny' && findings.length > 0) {\n const activeCweIds = findings\n .filter((f: any) => f.mode === 'blocking' || f.mode === 'ask')\n .map((f: any) => f.cwe)\n .filter((id: string) => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n continue;\n }\n\n const displayIds = activeCweIds.slice(0, 3).join(', ');\n const count = activeCweIds.length;\n const label = count === 1 ? 'match' : 'matches';\n const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';\n\n const fixLines = findings\n .filter((f: any) => activeCweIds.includes(f.cwe) && f.suggested_fix)\n .map((f: any) => '[' + f.cwe + '] Fix: ' + f.suggested_fix);\n const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';\n const denyDetail = '[' + displayIds + '] ' + (findings[0]?.reason || 'code weakness detected');\n const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \\u2014 resolve the weakness in code yourself.';\n\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n activeCweIds.map((cweId) => {\n const f = findings.find((x: any) => x.cwe === cweId);\n return {\n finding_type: 'cwe' as const,\n finding_id: cweId,\n severity: f?.severity || 'high',\n detail: f?.reason || 'code weakness detected',\n cwe_name: f?.name || undefined,\n };\n }),\n {\n finding_type: 'cwe',\n finding_id: activeCweIds[0] || 'CWE-UNKNOWN',\n severity: findings[0]?.severity || 'high',\n detail: denyDetail,\n },\n );\n\n dispatchCapture(jwt, 'cwe', 'block', findings[0]?.severity || 'high', 'security',\n scanToolName, gitRepo, sessionId, config.captureDepth, {\n command: (isShellTool(scanToolName) ? 'shell write ' : 'edit ') + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\n ccModel,\n });\n\n outputJson({\n systemMessage: cweMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: 'pass',\n status: 'resolved',\n }, config.captureDepth);\n continue;\n }\n\n if (rt === 'local') {\n let cweRules: any[] = [];\n let cweRuleFetchFailed = false;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-rules?ext=' + encodeURIComponent(fileExt), {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(4000),\n });\n if (!resp.ok) {\n log('CWE rules fetch failed: HTTP ' + resp.status);\n cweRuleFetchFailed = true;\n } else {\n const data = await resp.json() as any;\n cweRules = data.rules || [];\n }\n } catch (fetchErr: any) {\n log('CWE rules fetch error: ' + (fetchErr?.message || String(fetchErr)));\n cweRuleFetchFailed = true;\n }\n\n if (cweRuleFetchFailed) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 CWE rules unavailable \\u2014 scan skipped' });\n return;\n }\n\n if (cweRules.length === 0) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (no CWE rules for ' + fileExt + ')' });\n return;\n }\n\n let localPkgContext = '';\n if (cwd) {\n const newImports = detectNewImports(scanToolName, scanToolInput, fileExt);\n const caps = newImports.slice(0, 5)\n .map(pkg => scanPackageCapabilities(pkg, cwd))\n .filter((c): c is PackageCapability => c !== null);\n if (caps.length > 0) {\n localPkgContext = '\\n\\nImported packages with notable capabilities:\\n' +\n caps.map(c => '- ' + c.name + ' (claims: \"' + c.description + '\")\\n Capabilities: ' + c.capabilities.join(', ') + '\\n' + c.sourceExcerpt).join('\\n');\n }\n }\n\n const exemptionNote = exemptedCwes.size > 0\n ? '\\n\\nEXEMPTED CWEs (already known, DO NOT report these — focus on NEW weaknesses only): ' + [...exemptedCwes].join(', ')\n : '';\n\n function buildCwePrompt(content: string): string {\n const diffBlock = cweDiffSection\n ? '\\n\\n=== NEW/CHANGED CODE (evaluate THIS for CWE weaknesses) ===\\n' + cweDiffSection + '\\n=== END NEW CODE ===\\n\\nThe surrounding content below is CONTEXT ONLY to help you understand imports, variables, and scope. Do NOT report issues found only in the context section.\\n'\n : '';\n return [\n 'File: ' + filePath,\n diffBlock,\n 'Full content window:',\n content,\n '',\n 'CWE rules to check against:',\n JSON.stringify(cweRules),\n ].join('\\n') + localPkgContext + exemptionNote;\n }\n\n const SPLIT_THRESHOLD = 4000;\n const OVERLAP = 500;\n let gradeResponses: string[] = [];\n\n if (cweContent.length > SPLIT_THRESHOLD) {\n const mid = Math.floor(cweContent.length / 2);\n const chunk1 = cweContent.slice(0, mid + OVERLAP);\n const chunk2 = cweContent.slice(mid - OVERLAP);\n try {\n const [resp1, resp2] = await Promise.all([\n localGradeCwe(buildCwePrompt(chunk1), graderPool),\n localGradeCwe(buildCwePrompt(chunk2), graderPool),\n ]);\n gradeResponses = [resp1, resp2];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n logGraderUnavailable('cweGuard', fileShort, reason);\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });\n return;\n }\n } else {\n try {\n gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent), graderPool)];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n logGraderUnavailable('cweGuard', fileShort, reason);\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });\n return;\n }\n }\n\n const cweIds: string[] = [];\n const fixes: Record<string, string> = {};\n let mergedReason = '';\n let mergedSeverity = '';\n let mergedCategory = '';\n let anyFailed = false;\n\n for (const gradeResp of gradeResponses) {\n const v = parseVerdict(gradeResp);\n if (!v.ok) {\n anyFailed = true;\n if (v.reason) mergedReason = mergedReason || v.reason;\n if (v.severity) mergedSeverity = mergedSeverity || v.severity;\n if (v.category) mergedCategory = mergedCategory || v.category;\n const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\\/rule_id>/g) || [];\n for (const m of ruleIdMatches.slice(0, 5)) {\n const id = m.replace(/<\\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');\n if (id && !cweIds.includes(id)) cweIds.push(id);\n }\n const fMatches = gradeResp.match(/<suggested_fix>([^<]+)<\\/suggested_fix>/g) || [];\n const respIds = ruleIdMatches.map(rm => rm.replace(/<\\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-'));\n for (let i = 0; i < Math.min(respIds.length, fMatches.length); i++) {\n if (!fixes[respIds[i]]) fixes[respIds[i]] = fMatches[i].replace(/<\\/?suggested_fix>/g, '').trim();\n }\n }\n }\n\n const verdict = anyFailed\n ? { ok: false, reason: mergedReason, severity: mergedSeverity, category: mergedCategory }\n : { ok: true, reason: '', severity: '', category: '' };\n\n if (!verdict.ok) {\n const activeCweIds = cweIds.filter(id => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n continue;\n }\n\n const cweNameMap = new Map<string, string>();\n for (const r of cweRules) {\n if (r.cwe && r.name) cweNameMap.set(r.cwe.toUpperCase(), r.name);\n }\n\n const displayIds = activeCweIds.slice(0, 3).join(', ');\n const count = activeCweIds.length;\n const label = count === 1 ? 'match' : 'matches';\n const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';\n const denyDetail = '[' + displayIds + '] ' + (verdict.reason || 'code weakness detected');\n const fixLines = activeCweIds\n .filter(id => fixes[id])\n .map(id => '[' + id + '] Fix: ' + fixes[id]);\n const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';\n const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the weakness in code yourself.';\n\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n activeCweIds.map((cweId) => ({\n finding_type: 'cwe' as const,\n finding_id: cweId,\n severity: verdict.severity || 'high',\n detail: verdict.reason || 'code weakness detected',\n cwe_name: cweNameMap.get(cweId.toUpperCase()) || undefined,\n })),\n {\n finding_type: 'cwe',\n finding_id: activeCweIds[0] || 'CWE-UNKNOWN',\n severity: verdict.severity || 'high',\n detail: verdict.reason || denyDetail,\n },\n );\n\n dispatchCapture(jwt, 'cwe', 'block', verdict.severity || 'high', verdict.category || 'security',\n scanToolName, gitRepo, sessionId, config.captureDepth, {\n command: (isShellTool(scanToolName) ? 'shell write ' : 'edit ') + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\n ccModel,\n });\n\n outputJson({\n systemMessage: cweMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: 'pass',\n status: 'resolved',\n }, config.captureDepth);\n continue;\n }\n }\n\n outputEmpty();\n } catch (err) {\n log('cweGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse CVE scan (standalone, curl only) (TypeScript) ───\n\nexport const CVE_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,\n reconstructContent, readStdin, findNearestDeps, filePathFromToolInput, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, dispatchCapture, dispatchScanResult, extractTranscript, emitBlockScanFindings, resolveTranscriptPath, GATEWAY_URL,\n isCursorHookFormat,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\nimport { readFileSync } from 'node:fs';\n\nconst MANIFEST_NAMES = new Set([\n 'package.json', 'requirements.txt', 'requirements-dev.txt', 'requirements-test.txt',\n 'Pipfile', 'go.mod', 'go.sum', 'Gemfile', 'pom.xml', 'Cargo.toml', 'composer.json', 'pyproject.toml',\n]);\n\nfunction isManifest(filename: string): boolean {\n if (MANIFEST_NAMES.has(filename)) return true;\n if (filename.startsWith('requirements') && filename.endsWith('.txt')) return true;\n if (filename.startsWith('build.gradle')) return true;\n if (filename.endsWith('.cabal')) return true;\n return false;\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n\n const fileShort = basename(filePath);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const rt = await route(config);\n\n if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cveScan] ' + fileShort + ' → skipped (silent mode)' });\n return;\n }\n\n const cveTag = '[synkro:' + rt + ':cveScan]';\n\n // Reconstruct proposed content\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) {\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → skip (no content)' });\n return;\n }\n\n const proposedShort = proposed.slice(0, 4000);\n\n // For code files, find nearest package.json and extract deps\n let deps: Record<string, string> = {};\n if (!isManifest(fileShort)) {\n deps = findNearestDeps(filePath);\n }\n\n // For package.json edits, diff the proposed content against the file on\n // disk and scan only the added/upgraded packages. Catches the case the\n // bash installScan misses: a bare \\`pnpm install\\` after a manifest bump\n // arrives with no package tokens on the cmdline, so the install-time\n // scanner has nothing to feed OSV. By gating at the edit, the install\n // that follows is implicitly safe — there's nothing new to scan.\n if (fileShort === 'package.json') {\n try {\n let currentContent = '';\n try { currentContent = readFileSync(filePath, 'utf-8'); } catch {}\n const extractMod: any = await import(new URL('./installExtractCore.ts', import.meta.url).href);\n const deltaPkgs = extractMod.extractPackageJsonDelta(currentContent, proposed) as Array<{ name: string; version: string; ecosystem: string }>;\n if (deltaPkgs.length > 0) {\n const pkgScanResp = await fetch(GATEWAY_URL + '/api/v1/pkg-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ command: 'edit ' + filePath, packages: deltaPkgs }),\n signal: AbortSignal.timeout(8000),\n }).then(r => r.json()).catch(() => null) as any;\n const action = pkgScanResp?.action || 'allow';\n if (action === 'block') {\n const pkgResults = Array.isArray(pkgScanResp?.packages) ? pkgScanResp.packages : [];\n const summary = pkgScanResp?.summary || (deltaPkgs.length + ' package(s) flagged');\n const violatedIds: string[] = [];\n const cveFindings: Array<{\n finding_type: 'cve'; finding_id: string; severity?: string; detail?: string;\n description?: string; package_name?: string; package_version?: string;\n fixed_version?: string; aliases?: string[]; references?: Array<{ type: string; url: string }>;\n }> = [];\n for (const p of pkgResults) {\n if (Array.isArray(p?.findings)) {\n for (const f of p.findings) {\n const aliasCve = Array.isArray(f?.aliases) ? f.aliases.find((a: string) => a.startsWith('CVE-')) : null;\n const fid = aliasCve || f?.id || 'unknown';\n if (fid && !violatedIds.includes(fid)) violatedIds.push(fid);\n cveFindings.push({\n finding_type: 'cve',\n finding_id: fid,\n severity: f?.severity || 'high',\n detail: f?.summary || f?.title || 'vulnerable dependency',\n description: f?.details || undefined,\n package_name: p?.name,\n package_version: p?.version,\n fixed_version: f?.fixed || undefined,\n aliases: f?.aliases || undefined,\n references: f?.references || undefined,\n });\n }\n }\n }\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n cveFindings,\n {\n finding_type: 'cve',\n finding_id: violatedIds[0] || 'SYNKRO_PKGSCAN',\n severity: 'critical',\n detail: summary.slice(0, 500),\n package_name: deltaPkgs[0]?.name,\n package_version: deltaPkgs[0]?.version,\n },\n );\n // Model name isn't in the PreToolUse payload — pull it from the\n // transcript (last assistant entry's message.model). Matches what\n // the bash judge does at capture time.\n let ccModel: string | undefined = String(payload.model ?? payload.model_id ?? '') || undefined;\n if (!ccModel && transcriptPath) {\n try { ccModel = extractTranscript(transcriptPath).ccModel || undefined; } catch {}\n }\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath, reasoning: summary.slice(0, 200),\n violatedRules: violatedIds,\n ccModel,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'pkg',\n result: 'block', finding_count: violatedIds.length,\n finding_ids: violatedIds, severity: 'critical',\n repo: gitRepo || undefined,\n });\n const tagStr = '[synkro:' + rt + ':pkgScan]';\n const denyReason = tagStr + ' BLOCKED: ' + summary + '\\\\nDo not write this version. Pick a fixed/safe version instead.';\n outputJson({\n systemMessage: denyReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n return;\n }\n }\n } catch (err) {\n log('pkgDeltaScan error: ' + String(err));\n }\n }\n\n // CVE scan via OSV API\n const cveBody = {\n file_path: filePath,\n content: proposedShort,\n dependencies: deps,\n };\n\n let cveResp: any;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cve-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(cveBody),\n signal: AbortSignal.timeout(8000),\n });\n cveResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → error (timeout)' });\n return;\n }\n\n const findings = Array.isArray(cveResp?.findings) ? cveResp.findings : [];\n if (findings.length > 0) {\n const cveRows = findings.slice(0, 10).map((f: any) => {\n const cveId = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown';\n return {\n finding_type: 'cve' as const,\n finding_id: cveId,\n severity: f.severity || 'high',\n detail: f.summary || f.title || 'vulnerable dependency',\n description: f.details || undefined,\n package_name: f.package || undefined,\n package_version: f.version || undefined,\n fixed_version: f.fixed || undefined,\n aliases: f.aliases || undefined,\n references: f.references || undefined,\n };\n });\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n cveRows,\n cveRows[0],\n );\n\n const formatFinding = (f: any): string => {\n const id = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || '?';\n const pkg = f.package || '?';\n const ver = f.version || '?';\n const title = f.title || f.summary || 'vulnerable';\n const fix = f.fixed ? ' (fix: >=' + f.fixed + ')' : ' (no safe version)';\n return '[' + id + '] ' + pkg + '@' + ver + ': ' + title + fix;\n };\n\n const top3 = findings.slice(0, 3).map(formatFinding).join('; ');\n const count = findings.length;\n const label = count === 1 ? 'advisory' : 'advisories';\n const cveMsg = cveTag + ' ' + fileShort + ' → ' + count + ' ' + label;\n const ctx = 'CVE: ' + top3 + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — upgrade the vulnerable dependencies yourself.';\n\n const cveIds = findings.slice(0, 10).map((f: any) =>\n (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown'\n );\n let cveCcModel: string | undefined = String(payload.model ?? payload.model_id ?? '') || undefined;\n if (!cveCcModel && transcriptPath) {\n try { cveCcModel = extractTranscript(transcriptPath).ccModel || undefined; } catch {}\n }\n dispatchCapture(jwt, 'cve', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: top3,\n violatedRules: cveIds,\n ccModel: cveCcModel,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'cve',\n result: 'block', finding_count: findings.length,\n finding_ids: cveIds, severity: 'critical',\n repo: gitRepo || undefined,\n });\n\n outputJson({\n systemMessage: cveMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'cve',\n result: 'pass', finding_count: 0,\n repo: gitRepo || undefined,\n });\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → clean' });\n } catch (err) {\n log('cveGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC/Cursor PreToolUse Install Scan (standalone, fires before bash judge) ───\n\nexport const INSTALL_SCAN_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,\n readStdin, runInstallScan, emitBlockScanFindings, dispatchCapture, dispatchScanResult, hashCommand,\n outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, log, GATEWAY_URL,\n resolveTranscriptPath, isCursorHookFormat, loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const command = typeof payload.command === 'string' ? payload.command : (toolInput.command || '');\n if (!command) { outputEmpty(); return; }\n\n const toolName = payload.tool_name || '';\n // beforeShellExecution supplies command directly; preToolUse uses tool_name + tool_input.\n if (!isShellTool(toolName) && typeof payload.command !== 'string') { outputEmpty(); return; }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const scan = await runInstallScan(command, jwt);\n\n if (scan.scanned) {\n try {\n mkdirSync(SCAN_CACHE_DIR, { recursive: true });\n writeFileSync(join(SCAN_CACHE_DIR, hashCommand(command)), JSON.stringify(scan), 'utf-8');\n } catch {}\n }\n\n if (!scan.scanned) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const repo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n const config = await loadConfig(jwt);\n const isCursor = isCursorHookFormat();\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, isCursor ? 'cursor' : 'claude_code');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const model = isCursor ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');\n\n if (scan.action === 'block') {\n const pkgLabel = scan.scannedLabel || '';\n const pkgName = pkgLabel.split('@')[0] || undefined;\n const pkgVersion = pkgLabel.includes('@') ? pkgLabel.split('@').slice(1).join('@') : undefined;\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: command, repo: repo || undefined },\n scan.findings.map((f) => ({\n finding_type: 'cve' as const,\n finding_id: f.advisoryId + ':' + f.name,\n severity: f.severity,\n detail: f.detail,\n package_name: f.name,\n package_version: f.version,\n })),\n {\n finding_type: 'cve',\n finding_id: 'SYNKRO_PKGSCAN',\n severity: 'critical',\n detail: scan.blockContext.slice(0, 500) || scan.summary || 'Blocked package install',\n package_name: pkgName,\n package_version: pkgVersion,\n },\n );\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: scan.blockContext.slice(0, 200),\n violatedRules: scan.violatedIds,\n ccModel: model || undefined,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: command, scan_type: 'pkg',\n result: 'block', finding_count: scan.violatedIds?.length || 1,\n finding_ids: scan.violatedIds, severity: 'critical',\n repo: repo || undefined,\n });\n const denyReason = '[synkro:installScan] BLOCKED: ' + scan.summary + '\\\\nDo not retry this install. Suggest a safe version to the user instead.';\n outputJson({\n systemMessage: denyReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n } else if (scan.action === 'warn') {\n outputJson({\n systemMessage: '[synkro:installScan] ' + scan.summary,\n hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '[synkro:installScan] ' + scan.summary },\n });\n } else {\n outputEmpty();\n }\n } catch (err) {\n log('installScan error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse Bash/Read/Grep/Glob judge (TypeScript) ───\n\nexport const BASH_JUDGE_TS = String.raw`#!/usr/bin/env bun\nimport process from 'node:process';\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,\n loadSynkroFile, effectiveGraderPool,\n hashCommand, resolveTranscriptPath, isCursorHookFormat,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nfunction readCachedScan(command: string): any | null {\n try {\n const path = join(SCAN_CACHE_DIR, hashCommand(command));\n if (!existsSync(path)) return null;\n const data = JSON.parse(readFileSync(path, 'utf-8'));\n unlinkSync(path);\n return data;\n } catch { return null; }\n}\n\nconst DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';\nconst DEDUP_TTL_MS = 3000;\n\nfunction isDuplicate(command: string, sessionId: string): boolean {\n const hash = createHash('md5').update(sessionId + ':' + command).digest('hex').slice(0, 12);\n const marker = DEDUP_DIR + '/' + hash;\n try {\n if (existsSync(marker)) {\n const age = Date.now() - statSync(marker).mtimeMs;\n if (age < DEDUP_TTL_MS) return true;\n }\n } catch {}\n try {\n mkdirSync(DEDUP_DIR, { recursive: true });\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return false;\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isShellTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const transcript = extractTranscript(transcriptPath);\n\n let command = '';\n switch (toolName) {\n case 'Bash':\n case 'Shell':\n case 'terminal':\n case 'run_terminal_cmd':\n case 'execute_command':\n command = toolInput.command || ''; break;\n case 'Read': command = 'cat ' + (toolInput.file_path || ''); break;\n case 'Grep': command = \"grep -r '\" + (toolInput.pattern || '') + \"' \" + (toolInput.path || '.'); break;\n case 'Glob': command = \"find . -name '\" + (toolInput.pattern || '') + \"'\"; break;\n }\n if (!command) { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n\n if (isDuplicate(command, sessionId)) {\n log('bashGuard skip (dedup): ' + command.slice(0, 80));\n outputEmpty();\n return;\n }\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n // Load JWT + routing config eagerly so even the short-circuit message\n // carries the live pack name + local/cloud tag. Cost: ~200-500ms for the\n // config fetch (network call, no caching). The fetch is unavoidable for\n // the LLM path anyway — we just pay it sooner so the short-circuit can\n // produce a properly-tagged system message.\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, 'claude_code');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' bashGuard → skipped (silent mode)' });\n return;\n }\n\n if (isSafeInRepoRead(toolName, command, cwd)) {\n log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');\n appendLocalTelemetry({\n capture_type: 'local_verdict',\n verdict: 'pass',\n hook_type: 'bash',\n category: 'safe_read',\n tool_name: toolName,\n command: command.slice(0, 200),\n session_id: sessionId,\n repo: gitRepo || cwd,\n cc_model: transcript.ccModel,\n reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',\n });\n outputJson({\n systemMessage: tagStr + ' bashGuard → pass: safe in-repo read',\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n additionalContext: tagStr + ' bashGuard pass: safe in-repo read.',\n },\n });\n return;\n }\n\n // ─── Install protection: install-scan runs first and owns block traces ───\n let scanConcern = '';\n let scanBlockContext = '';\n if (toolName === 'Bash') {\n const scan = readCachedScan(command);\n if (scan?.action === 'block') {\n log('bashGuard deferring to install-scan block: ' + cmdShort);\n outputEmpty();\n return;\n }\n if (scan?.action === 'warn') {\n scanBlockContext = scan.blockContext || scan.summary || '';\n scanConcern = 'PACKAGE SCANNER WARNING: ' + scanBlockContext\n + ' Mention this to the user before proceeding.';\n }\n }\n\n const lastPrompt = readLastPrompt(sessionId);\n\n // jwt + config + rt + tagStr already loaded eagerly at top of main\n // (so the short-circuit could emit a properly-tagged message). Silent\n // mode was also checked up there.\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(\n ruleFilterText(command, transcript.userIntent || lastPrompt),\n config.rules,\n );\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Command: ' + command,\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n scanConcern,\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no secrets in grep args. R005: in-repo path only.\" Cover every rule shown.',\n 'Rules with preconditions (e.g. \"run X before Y\") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', errMsg);\n if (scanConcern) {\n const ctx = scanBlockContext + ' Synkro flagged this install but the grader is unavailable to check consent — ask the user for explicit consent, then retry.';\n outputJson({\n systemMessage: tagStr + ' bashGuard → install blocked (scan flagged; grader unavailable)',\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const blockMsg = mode === 'fix'\n ? tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before retrying — do not ask the user.'\n : tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: blockMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext: blockMsg },\n });\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: relevantRules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' bashGuard → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({\n systemMessage: reason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason },\n });\n dispatchCapture(jwt, 'bash', 'pass', 'clean', verdict.category || 'trivial_utility',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n cc_model: transcript.ccModel || null,\n cc_usage: transcript.ccUsage || {},\n session_summary: transcript.sessionSummary || null,\n };\n\n if (scanConcern) {\n // Cloud grading does not carry the package-scanner concern through the\n // consent flow — block here with ask-mode messaging so the user can\n // consent and retry.\n const ctx = scanBlockContext + ' Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: tagStr + ' bashGuard → install blocked: ' + cmdShort,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('bashGuard ' + cmdShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n outputJson(resp.hook_response);\n } catch (err) {\n log('bashGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse Agent subagent judge (TypeScript) ───\n\nexport const AGENT_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isAgentTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const prompt = toolInput.prompt || '';\n const description = toolInput.description || '';\n const subagentType = toolInput.subagent_type || 'general-purpose';\n if (!prompt) { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd, transcriptPath, prompt, workspaceRoots);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Agent', summary: 'spawn ' + subagentType + ': ' + description.slice(0, 80) });\n\n const promptShort = prompt.slice(0, 80);\n log('agentGuard checking: ' + description + ' (' + subagentType + ')');\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n if (!transcript.ccModel) {\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n if (agentKind === 'cursor') {\n transcript.ccModel = rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n } else {\n transcript.ccModel = rawModel || '';\n }\n }\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n const msg = tagStr + ' agentGuard → skipped (silent mode)';\n outputJson({ systemMessage: msg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: msg } });\n return;\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const agentText = 'agent=' + subagentType + ' description=' + description + ' prompt=' + prompt.slice(0, 2000);\n const relevantRules = await filterRules(agentText, config.rules);\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Tool: Agent (subagent spawn)',\n 'Subagent type: ' + subagentType,\n 'Description: ' + description,\n 'Subagent prompt (first 4000 chars):',\n prompt.slice(0, 4000),\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), errMsg);\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('agentGuard', subagentType || 'agent', errMsg, graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const agentContent = 'agent=' + subagentType + ' desc=' + description + ' prompt=' + prompt.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const reason = mode === 'fix'\n ? tagStr + ' agentGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before retrying — do not ask the user.'\n : tagStr + ' agentGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: reason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: reason },\n });\n dispatchCapture(jwt, 'agent', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: guardReason, rulesChecked: relevantRules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' agentGuard → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({ systemMessage: reason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason } });\n dispatchCapture(jwt, 'agent', 'pass', 'clean', verdict.category || 'subagent_spawn',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n cc_model: transcript.ccModel || null,\n cc_usage: transcript.ccUsage || {},\n session_summary: transcript.sessionSummary || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('agentGuard ' + promptShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('agentGuard ' + promptShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n outputJson(resp.hook_response);\n } catch (err) {\n log('agentGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse ExitPlanMode plan review (TypeScript) ───\n\nexport const PLAN_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,\n outputJson, outputEmpty, setupCursorHookSignals, isPlanTool, hookSessionId, GATEWAY_URL,\n filterRules, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nfunction findLatestPlanInDir(plansDir: string): string | null {\n if (!existsSync(plansDir)) return null;\n try {\n const files = readdirSync(plansDir)\n .filter(f => f.endsWith('.md'))\n .map(f => ({ name: f, mtime: statSync(join(plansDir, f)).mtimeMs }))\n .sort((a, b) => b.mtime - a.mtime);\n return files.length > 0 ? join(plansDir, files[0].name) : null;\n } catch {\n return null;\n }\n}\n\nfunction findLatestPlan(): string | null {\n const dirs = [\n join(homedir(), '.claude', 'plans'),\n join(homedir(), '.cursor', 'plans'),\n ];\n let best: { path: string; mtime: number } | null = null;\n for (const dir of dirs) {\n const p = findLatestPlanInDir(dir);\n if (!p) continue;\n try {\n const mtime = statSync(p).mtimeMs;\n if (!best || mtime > best.mtime) best = { path: p, mtime };\n } catch {}\n }\n return best?.path ?? null;\n}\n\nfunction appendReviewToPlan(planFile: string, verdict: string): void {\n try {\n let content = readFileSync(planFile, 'utf-8');\n content = content.replace(/<!-- synkro-plan-review -->[\\\\s\\\\S]*?<!-- \\\\/synkro-plan-review -->/g, '').trimEnd();\n const now = new Date().toISOString().replace('T', ' ').slice(0, 16);\n content += '\\\\n\\\\n<!-- synkro-plan-review -->\\\\n\\\\n---\\\\n\\\\n**Synkro Plan Review** \\\\u2014 ' + now + '\\\\n\\\\n' + verdict + '\\\\n\\\\n<!-- /synkro-plan-review -->\\\\n';\n writeFileSync(planFile, content, 'utf-8');\n } catch {}\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isPlanTool(toolName)) { outputEmpty(); return; }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }\n\n const planFile = findLatestPlan();\n if (!planFile) { outputEmpty(); return; }\n const plan = readFileSync(planFile, 'utf-8');\n if (plan.length < 20) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, plan, workspaceRoots);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'ExitPlanMode', summary: 'plan review: ' + plan.slice(0, 80) });\n\n const planShort = plan.slice(0, 80);\n log('planReview checking: ' + planShort + '...');\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const ccModel = agentKind === 'cursor' ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' planReview → skipped (silent mode)' });\n return;\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(plan.slice(0, 2000), config.rules);\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Plan:',\n plan.slice(0, 8000),\n 'Org rules: ' + JSON.stringify(relevantRules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('plan', graderPrompt, undefined, graderPool);\n } catch (err) {\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('planReview', 'plan', (err as Error).message || String(err), graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const planContent = plan.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const reviewMsg = (verdict.ruleId ? '(first: ' + verdict.ruleId + ') ' : '') + (verdict.reason || 'check org rules during implementation');\n appendReviewToPlan(planFile, '\\\\u26a0\\\\ufe0f Advisory \\\\u2014 ' + reviewMsg);\n const advLine = tagStr + ' planReview → ' + reviewMsg;\n outputJson({ systemMessage: advLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local plan judge (advisory). ' + reviewMsg } });\n dispatchCapture(jwt, 'plan_review', 'advisory', verdict.severity || 'medium', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: verdict.reason || 'check org rules',\n rulesChecked: relevantRules, violatedRules, ccModel: ccModel || undefined,\n });\n } else {\n const reviewMsg = verdict.reason || 'no relevant org rules for this plan';\n appendReviewToPlan(planFile, '\\\\u2705 Clean \\\\u2014 ' + reviewMsg);\n const cleanLine = tagStr + ' planReview → clean: ' + reviewMsg;\n outputJson({ systemMessage: cleanLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local plan judge. ' + reviewMsg } });\n dispatchCapture(jwt, 'plan_review', 'clean', 'clean', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: reviewMsg,\n rulesChecked: relevantRules, violatedRules: [], ccModel: ccModel || undefined,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const body = {\n hook_event: 'PreToolUse',\n tool_name: 'ExitPlanMode',\n tool_input: { plan: plan.slice(0, 16000) },\n session_id: sessionId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 12000);\n\n if (!resp) {\n log('planReview → error (timeout)');\n outputEmpty();\n return;\n }\n\n const hookResp = resp?.hook_response;\n if (!hookResp) { outputEmpty(); return; }\n\n const decision = hookResp?.hookSpecificOutput?.permissionDecision;\n if (decision) {\n const reason = hookResp?.hookSpecificOutput?.permissionDecisionReason || 'check org rules';\n appendReviewToPlan(planFile, '\\\\u26a0\\\\ufe0f Advisory \\\\u2014 ' + reason);\n outputJson({ systemMessage: tagStr + ' planReview → advisory: ' + reason });\n } else {\n const cloudMsg = hookResp.systemMessage || '';\n if (cloudMsg) appendReviewToPlan(planFile, '\\\\u2705 ' + cloudMsg);\n outputJson(hookResp);\n }\n } catch (err) {\n log('planReview error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC SessionEnd stop summary (TypeScript) ───\n\nexport const STOP_SUMMARY_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, loadConfig, tag, readStdin, aggregateUsage, cleanupSessionLog,\n outputJson, outputEmpty, shipCloud, setupCursorHookSignals, hookSessionId, GATEWAY_URL,\n resolveTranscriptPath, emitUsageTick, cursorModelFromPayload, log,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n const modelFallback = cursorModelFromPayload(payload);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n if (transcriptPath) {\n const usage = aggregateUsage(transcriptPath, { modelFallback });\n emitUsageTick({ sessionId, usage, hookType: 'session_end', gitRepo, modelFallback });\n }\n\n let resp: any;\n try {\n const r = await fetch(GATEWAY_URL + '/api/v1/cli/session-summary?session_id=' + encodeURIComponent(sessionId), {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n resp = await r.json();\n } catch {\n outputEmpty();\n return;\n }\n\n const edits = resp?.edits_scanned || 0;\n const findings = resp?.findings || 0;\n const autoFixed = resp?.auto_fixed || 0;\n const open = resp?.open || 0;\n\n if (!edits) { cleanupSessionLog(sessionId); outputEmpty(); return; }\n\n const config = await loadConfig(jwt);\n const tagStr = tag('local', config);\n\n cleanupSessionLog(sessionId);\n\n if (!findings) {\n outputJson({ systemMessage: tagStr + ' stop → 0 issues across ' + edits + ' edit(s), session complete' });\n } else {\n outputJson({ systemMessage: tagStr + ' stop → ' + findings + ' finding(s): ' + autoFixed + ' auto-fixed, ' + open + ' open' });\n }\n } catch (err) {\n log('stopSummary error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC SessionStart (TypeScript) ───\n\nexport const SESSION_START_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, channelUp, tag, readStdin, writeCachedRepo,\n outputJson, outputEmpty, setupCursorHookSignals, hookSessionId, resolveTranscriptPath, GATEWAY_URL,\n isLocalStorageMode, loadSynkroFile, log, type HookConfig,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const sessionId = hookSessionId(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n if (gitRepo) writeCachedRepo(gitRepo);\n\n let jwt = loadJwt();\n\n const isChannelUp = await channelUp();\n const synkroFile = loadSynkroFile(cwd);\n const gradingMode = synkroFile.grader.mode || process.env.SYNKRO_GRADING_MODE || 'local';\n const rt = gradingMode === 'byok' ? 'cloud' : (isChannelUp ? 'local' : 'cloud');\n\n let policyName = '';\n let silent = false;\n let openFindings = 0;\n\n if (isLocalStorageMode()) {\n try {\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const r = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/hook-config', {\n signal: AbortSignal.timeout(1500),\n });\n if (r.ok) {\n const data = await r.json() as any;\n silent = data.silent === true;\n policyName = data.policy?.name || '';\n }\n } catch {}\n } else if (jwt) {\n try {\n const url = GATEWAY_URL + '/api/v1/hook/config?session_id=' + encodeURIComponent(sessionId || '') + '&repo=' + encodeURIComponent(gitRepo || '');\n const r = await fetch(url, {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n const data = await r.json() as any;\n silent = data.silent_mode === true || data.silent_mode === 'true';\n policyName = data.active_policy_name || '';\n openFindings = data.session_context?.open_findings || 0;\n } catch {}\n }\n\n const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };\n const tagStr = tag(rt, fakeConfig);\n const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');\n\n if (!jwt) {\n outputJson({ systemMessage: routeLine });\n return;\n }\n\n // Sync grading mode to API profile so the dashboard reflects the actual config\n const fastInference = gradingMode === 'byok';\n fetch(GATEWAY_URL + '/api/v1/cli/me', {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ fast_inference: fastInference }),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n\n if (!openFindings) {\n outputJson({ systemMessage: routeLine });\n } else if (openFindings === 1) {\n outputJson({ systemMessage: routeLine + '\\\\n' + tagStr + ' session start → 1 open finding in this repo from a prior session.' });\n } else {\n outputJson({ systemMessage: routeLine + '\\\\n' + tagStr + ' session start → ' + openFindings + ' open findings in this repo from prior sessions.' });\n }\n } catch (err) {\n log('sessionStart error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PostToolUse Bash followup (TypeScript) ───\n\nexport const BASH_FOLLOWUP_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, loadConfig, readStdin, hashCommand, consentGrant, consentHasActive, consentConsume,\n appendSessionAction,\n outputEmpty, appendLocalTelemetry, shipCloud, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const shellCmd = typeof payload.command === 'string' ? payload.command : (payload.tool_input?.command || '');\n if (!isShellTool(toolName) && !shellCmd) { outputEmpty(); return; }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || payload.tool_call_id || 'cursor-shell';\n if (!sessionId) { outputEmpty(); return; }\n\n let isError = payload.tool_result?.is_error === true;\n try {\n const out = JSON.parse(payload.tool_output || '{}');\n if ((typeof out.exitCode === 'number' && out.exitCode !== 0) || out.is_error === true) isError = true;\n } catch {}\n const cmd = shellCmd;\n const cmdHash = cmd ? hashCommand(cmd) : '';\n\n if (cmdHash && sessionId) {\n if (!isError) {\n consentConsume(sessionId, cmdHash);\n } else {\n if (!consentHasActive(sessionId, cmdHash)) {\n consentGrant(sessionId, cmdHash);\n }\n }\n }\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Bash', summary: cmd.slice(0, 120), outcome: isError ? 'exit 1' : 'exit 0' });\n\n const body = {\n capture_type: 'bash_followup',\n session_id: sessionId,\n tool_use_id: toolUseId,\n is_error: isError,\n command_hash: cmdHash,\n };\n\n appendLocalTelemetry(body);\n shipCloud(jwt, '/api/v1/hook/capture', body);\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC Stop transcript sync (TypeScript) ───\n\nexport const TRANSCRIPT_SYNC_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, readStdin, aggregateUsage, appendLocalTelemetry,\n outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL, readSessionLog, shipCloud,\n resolveTranscriptPath, syncConversationTranscript, emitUsageTick, cursorModelFromPayload,\n} from './_synkro-common.ts';\nimport { readFileSync } from 'node:fs';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n if (!sessionId || !transcriptPath) {\n outputEmpty();\n return;\n }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const usage = aggregateUsage(transcriptPath, { modelFallback: cursorModelFromPayload(payload) });\n emitUsageTick({\n sessionId,\n usage,\n hookType: 'stop',\n gitRepo: detectRepo(cwd, transcriptPath, '', workspaceRoots),\n modelFallback: cursorModelFromPayload(payload),\n });\n\n const cloudConsent = process.env.SYNKRO_TRANSCRIPT_CONSENT !== 'no';\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n\n const { messages } = await syncConversationTranscript(sessionId, transcriptPath, gitRepo || '');\n\n if (cloudConsent && gitRepo && messages.length > 0 && (process.env.SYNKRO_STORAGE_MODE || 'local') === 'cloud') {\n fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({\n repo: gitRepo,\n sessions: [{ cc_session_id: sessionId, messages, actions: readSessionLog(sessionId) }],\n }),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n }\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC UserPromptSubmit (TypeScript) ───\n\nexport const USER_PROMPT_SUBMIT_TS = `#!/usr/bin/env bun\nimport { readStdin, appendLocalTelemetry, aggregateUsage, outputEmpty, setupCursorHookSignals, hookSessionId, detectRepo, resolveTranscriptPath, syncConversationTranscript, emitUsageTick, cursorModelFromPayload } from './_synkro-common.ts';\nimport { writeFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n const payload = JSON.parse(input);\n const msg = payload.message || payload.prompt || payload.content || '';\n if (msg) {\n const promptFile = join(homedir(), '.synkro', '.last-prompt');\n mkdirSync(dirname(promptFile), { recursive: true, mode: 0o700 });\n writeFileSync(promptFile, msg, { encoding: 'utf-8', mode: 0o600 });\n const sid = hookSessionId(payload);\n if (sid) {\n const sessDir = join(homedir(), '.synkro', 'sessions');\n mkdirSync(sessDir, { recursive: true, mode: 0o700 });\n writeFileSync(join(sessDir, sid + '.last-prompt'), msg, { encoding: 'utf-8', mode: 0o600 });\n }\n }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n if (sessionId && transcriptPath) {\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n syncConversationTranscript(sessionId, transcriptPath, gitRepo || '').catch(() => {});\n const modelFallback = cursorModelFromPayload(payload);\n const usage = aggregateUsage(transcriptPath, { modelFallback });\n emitUsageTick({ sessionId, usage, hookType: 'prompt_submit', gitRepo, modelFallback });\n }\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── Cursor IDE TypeScript adapter scripts ───\n\nexport const CURSOR_BASH_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, dispatchFinding, ruleMode, normalizeMode, filterRules, ruleFilterText,\n isSafeInRepoRead, resolveTranscriptPath, postWithRetry, readStdin, hashCommand,\n extractTranscript, readLastPrompt, readSessionLog, compressSessionLog,\n appendLocalTelemetry, logGraderUnavailable, graderUnavailableMessage, log, GATEWAY_URL,\n loadSynkroFile, effectiveGraderPool,\n type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nfunction readCachedScan(command: string): any | null {\n try {\n const path = join(SCAN_CACHE_DIR, hashCommand(command));\n if (!existsSync(path)) return null;\n const data = JSON.parse(readFileSync(path, 'utf-8'));\n unlinkSync(path);\n return data;\n } catch { return null; }\n}\n\nconst DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';\nconst DEDUP_TTL_MS = 3000;\n\nfunction isDuplicate(command: string, sessionId: string): boolean {\n const hash = createHash('md5').update(sessionId + ':' + command).digest('hex').slice(0, 12);\n const marker = DEDUP_DIR + '/' + hash;\n try {\n if (existsSync(marker)) {\n const age = Date.now() - statSync(marker).mtimeMs;\n if (age < DEDUP_TTL_MS) return true;\n }\n } catch {}\n try {\n mkdirSync(DEDUP_DIR, { recursive: true });\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return false;\n}\n\n// Cursor beforeShellExecution timeout is 15s; stay under it (JWT refresh + grade).\nconst CURSOR_GRADE_TIMEOUT_MS = 12000;\nconst CURSOR_CLOUD_TIMEOUT_MS = 9000;\n\nlet hookDone = false;\n\nfunction finishAllow(): never {\n if (!hookDone) {\n hookDone = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n process.exit(0);\n}\n\nfunction finishWith(payload: Record<string, unknown>): never {\n hookDone = true;\n process.stdout.write(JSON.stringify(payload) + '\\\\n');\n process.exit(0);\n}\n\nprocess.on('SIGTERM', () => finishAllow());\n\nconst SHELL_TOOL_NAMES = new Set(['Bash', 'Shell', 'terminal', 'run_terminal_cmd', 'execute_command']);\nconst READ_TOOL_NAMES = new Set(['Read', 'ReadFile', 'read_file']);\nconst SEARCH_TOOL_NAMES = new Set(['Grep', 'grep_search', 'codebase_search', 'file_search']);\nconst DIR_TOOL_NAMES = new Set(['Glob', 'list_dir']);\nconst DELETE_TOOL_NAMES = new Set(['delete_file']);\nconst BASH_PRE_TOOL_NAMES = new Set([...SHELL_TOOL_NAMES, ...READ_TOOL_NAMES, ...SEARCH_TOOL_NAMES, ...DIR_TOOL_NAMES, ...DELETE_TOOL_NAMES]);\n\nfunction extractCommand(payload: Record<string, unknown>): { command: string; toolName: string } {\n const direct = typeof payload.command === 'string' ? payload.command : '';\n if (direct) return { command: direct, toolName: 'Bash' };\n\n const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : '';\n if (!BASH_PRE_TOOL_NAMES.has(toolName)) return { command: '', toolName };\n\n const toolInput = (payload.tool_input && typeof payload.tool_input === 'object')\n ? payload.tool_input as Record<string, unknown>\n : {};\n\n let command = '';\n if (SHELL_TOOL_NAMES.has(toolName)) {\n command = String(toolInput.command ?? '');\n } else if (READ_TOOL_NAMES.has(toolName)) {\n command = 'cat ' + String(toolInput.file_path ?? toolInput.path ?? '');\n } else if (SEARCH_TOOL_NAMES.has(toolName)) {\n command = \"grep -r '\" + String(toolInput.pattern ?? toolInput.query ?? '') + \"' \" + String(toolInput.path ?? '.');\n } else if (DIR_TOOL_NAMES.has(toolName)) {\n command = \"find . -name '\" + String(toolInput.pattern ?? toolInput.relative_workspace_path ?? '') + \"'\";\n } else if (DELETE_TOOL_NAMES.has(toolName)) {\n command = 'rm ' + String(toolInput.target_file ?? toolInput.file_path ?? toolInput.path ?? '');\n }\n return { command, toolName: toolName || 'Bash' };\n}\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finishAllow();\n\n const payload = JSON.parse(input) as Record<string, unknown>;\n const { command, toolName } = extractCommand(payload);\n if (!command) finishAllow();\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string') : [];\n const cwd = typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : (workspaceRoots[0] || '');\n const sessionId = String(payload.conversation_id ?? payload.session_id ?? '');\n\n if (isDuplicate(command, sessionId)) {\n log('bashGuard skip (dedup): ' + command.slice(0, 80));\n finishAllow();\n }\n\n const transcriptPath = resolveTranscriptPath(payload);\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const model = rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n const repo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n // Instant-allow read-only tool calls + safe in-repo bash reads — no grade,\n // no network. Critical under Cursor's tight 15s beforeShellExecution budget.\n if (isSafeInRepoRead(toolName, command, cwd)) {\n log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');\n appendLocalTelemetry({\n capture_type: 'local_verdict', verdict: 'pass', hook_type: 'bash',\n category: 'safe_read', tool_name: toolName, command: command.slice(0, 200),\n session_id: sessionId, repo: repo || cwd,\n cc_model: model,\n reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',\n });\n finishAllow();\n }\n\n let jwt = loadJwt();\n if (!jwt) finishAllow();\n jwt = await ensureFreshJwt(jwt);\n\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n const config = await loadConfig(jwt);\n if (config.silent) finishAllow();\n\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, 'cursor');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n // Install protection — install-scan runs first and owns block traces.\n let scanConcern = '';\n let scanBlockContext = '';\n if (SHELL_TOOL_NAMES.has(toolName)) {\n const scan = readCachedScan(command);\n if (scan?.action === 'block') {\n log('bashGuard deferring to install-scan block: ' + cmdShort);\n finishAllow();\n }\n if (scan?.action === 'warn') {\n scanBlockContext = scan.blockContext || scan.summary || '';\n scanConcern = 'PACKAGE SCANNER WARNING: ' + scanBlockContext\n + ' Mention this to the user before proceeding.';\n }\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(\n ruleFilterText(command, transcript.userIntent || lastPrompt),\n config.rules,\n );\n\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (repo || 'unknown'),\n sessionLog,\n 'Command: ' + command,\n 'User intent (last human message): ' + (transcript.userIntent || lastPrompt || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n scanConcern,\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no secrets in grep args. R005: in-repo path only.\" Cover every rule shown.',\n 'Rules with preconditions (e.g. \"run X before Y\") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS, graderPool);\n } catch (e) {\n logGraderUnavailable('bashGuard', command.slice(0, 200), (e as Error).message || String(e));\n if (scanConcern) {\n // Grader unavailable to run the consent check — fail closed on a\n // scanner-flagged install (ask-mode so the user can still consent).\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' installScan → blocked (grader unavailable): ' + cmdShort,\n agent_message: 'Synkro flagged this install: ' + scanBlockContext + ' The grader is unavailable to check consent — ask the user for explicit consent, then retry.',\n });\n }\n const errMsg = (e as Error).message || String(e);\n log('bashGuard ' + cmdShort + ' → pass (grade unavailable): ' + errMsg);\n finishWith({ permission: 'allow', user_message: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg, graderPool) });\n }\n\n const verdict = parseVerdict(gradeResp);\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const agentMsg = mode === 'fix'\n ? 'Synkro safety judge. Fix this before retrying — do not ask the user. Reasoning: ' + (verdict.reason || guardReason)\n : 'Synkro safety judge. Ask the user for explicit consent before retrying. Reasoning: ' + (verdict.reason || guardReason);\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: relevantRules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' bashGuard → block: ' + guardReason,\n agent_message: agentMsg,\n });\n } else {\n dispatchCapture(jwt, 'bash', 'pass', 'clean', verdict.category || 'clean',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n ccModel: model,\n });\n }\n\n const passReason = verdict.reason || 'no policy violations detected';\n log('bashGuard ' + cmdShort + ' → pass: ' + passReason);\n finishWith({ permission: 'allow' });\n }\n\n if (scanConcern) {\n // Cloud grading does not carry the package-scanner concern through the\n // consent flow — block here with ask-mode messaging so the user can\n // consent and retry.\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' installScan → blocked: ' + cmdShort,\n agent_message: 'Synkro flagged this install: ' + scanBlockContext + ' Ask the user for explicit consent before retrying.',\n });\n }\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName || 'Bash',\n tool_input: { command },\n response_format: 'cursor',\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n session_id: sessionId || null,\n cwd: cwd || null,\n repo: repo || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, CURSOR_CLOUD_TIMEOUT_MS);\n\n if (!resp) {\n log('bashGuard ' + cmdShort + ' → pass (cloud timeout)');\n finishAllow();\n }\n\n if (resp.hook_response) {\n const hr = resp.hook_response as Record<string, unknown>;\n if (hr.permission === 'allow') {\n const um = String(hr.user_message || '');\n const am = String(hr.agent_message || um);\n if (um || am) {\n finishWith({ permission: 'allow' });\n }\n }\n finishWith(hr);\n }\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n finishAllow();\n } catch (e) {\n log('bashGuard error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('bashGuard fatal: ' + String(e));\n finishAllow();\n});`;\n\nexport const CURSOR_EDIT_CAPTURE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, readStdin, resolveTranscriptPath,\n appendSessionAction, appendLocalTelemetry, shipCloud, log, GATEWAY_URL,\n countEditLineDelta, dispatchCapture, hookSessionId, cursorModelFromPayload,\n isLocalStorageMode,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\n\nlet hookDone = false;\n\nfunction finish(): never {\n if (!hookDone) {\n hookDone = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n process.exit(0);\n}\n\nprocess.on('SIGTERM', () => finish());\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finish();\n\n const payload = JSON.parse(input) as Record<string, unknown>;\n const filePath = String(payload.file_path || payload.path || payload.target_file || '');\n if (!filePath) finish();\n\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const sessionId = hookSessionId(payload);\n const repo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n const model = cursorModelFromPayload(payload);\n\n const edits = Array.isArray(payload.edits) ? payload.edits as Array<{ old_string?: string; new_string?: string }> : [];\n const { linesAdded, linesRemoved } = countEditLineDelta({ edits });\n\n log('editScan ' + basename(filePath) + ' +' + linesAdded + '/-' + linesRemoved);\n\n appendSessionAction(sessionId, {\n ts: new Date().toISOString(),\n tool: 'Edit',\n summary: 'wrote ' + basename(filePath) + (linesAdded || linesRemoved ? (' (+' + linesAdded + '/-' + linesRemoved + ')') : ''),\n file: filePath,\n outcome: 'ok',\n });\n\n let jwt = loadJwt();\n if (!jwt) finish();\n jwt = await ensureFreshJwt(jwt);\n\n let dependencies: Record<string, string> = {};\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n let pkgDir = cwd || dirname(fullPath);\n while (pkgDir !== '/' && pkgDir !== '.') {\n const pkgPath = join(pkgDir, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n dependencies = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };\n } catch {}\n break;\n }\n const parent = dirname(pkgDir);\n if (parent === pkgDir) break;\n pkgDir = parent;\n }\n\n const localSpoolBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, lines_added: linesAdded, lines_removed: linesRemoved },\n edit_verdict: { ok: true },\n dependencies,\n cc_model: model,\n model,\n };\n if (sessionId) localSpoolBody.session_id = sessionId;\n if (cwd) localSpoolBody.cwd = cwd;\n if (repo) localSpoolBody.repo = repo;\n appendLocalTelemetry(localSpoolBody);\n\n if (!isLocalStorageMode()) {\n let fileContent = '';\n try {\n if (existsSync(fullPath)) {\n fileContent = readFileSync(fullPath).slice(0, 50000).toString('utf-8');\n }\n } catch {}\n const cloudBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, content: fileContent },\n edit_verdict: { ok: true },\n dependencies,\n cc_model: model,\n model,\n };\n if (sessionId) cloudBody.session_id = sessionId;\n if (cwd) cloudBody.cwd = cwd;\n if (repo) cloudBody.repo = repo;\n shipCloud(jwt, '/api/v1/hook/capture', cloudBody, 10000);\n }\n\n if (sessionId) {\n dispatchCapture(jwt, 'edit', 'pass', 'clean', 'trivial_edit', 'Edit', repo, sessionId, 'full', {\n command: 'file=' + filePath,\n reasoning: 'Cursor afterFileEdit',\n ccModel: model,\n linesAdded,\n linesRemoved,\n });\n }\n\n finish();\n } catch (e) {\n log('editScan error: ' + String(e));\n finish();\n }\n}\n\nmain().catch((e) => {\n log('editScan fatal: ' + String(e));\n finish();\n});`;\n\nexport const CURSOR_AGENT_CAPTURE_TS = `#!/usr/bin/env bun\n/** Capture Cursor agent thinking/response text before transcript JSONL redacts it. */\nimport {\n readStdin, outputEmpty, setupCursorHookSignals, hookSessionId, detectRepo,\n appendThoughtOverlay, pushConversationMessage,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n const payload = JSON.parse(input) as Record<string, unknown>;\n const event = String(payload.hook_event_name || '');\n const text = typeof payload.text === 'string' ? payload.text.trim() : '';\n if (!text || text === '[REDACTED]') { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? payload.workspace_roots.filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const gitRepo = detectRepo(cwd, '', '', workspaceRoots);\n\n if (event === 'afterAgentThought') {\n appendThoughtOverlay(sessionId, text);\n await pushConversationMessage(sessionId, 'assistant', text, { gitRepo, patchRedacted: true });\n } else if (event === 'afterAgentResponse') {\n await pushConversationMessage(sessionId, 'assistant', text, { gitRepo, patchRedacted: false });\n }\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n","/**\n * Synkro CLI Authentication\n *\n * OAuth-style authentication flow for CLI integration with Synkro web platform.\n * Mirrors the Node.js reference implementation.\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from \"node:http\";\nimport { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport jwt from \"jsonwebtoken\";\n\n// Types\ninterface WorkOSJwtPayload {\n iss: string; // \"https://api.workos.com/\"\n sub: string; // user ID\n aud?: string; // client ID\n exp: number;\n iat: number;\n email?: string;\n org_id?: string;\n role?: string;\n permissions?: string[];\n sid?: string; // session ID\n}\n\n// Configuration — matches the desktop app pattern (packages/desktop/src-tauri/src/auth.rs).\n// Dev dashboard runs on :4322; CLI listens on 8100 for the OAuth callback.\nconst PORT = 8100;\n// Same poisoning concern as the gateway URL: a developer's local .env or\n// op:// expansion can land in SYNKRO_WEB_AUTH_URL. Only honor http(s) values;\n// fall through to the prod dashboard otherwise so the OAuth callback always\n// has a real origin to open the browser at.\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : \"https://app.synkro.sh\";\nconst AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join(homedir(), \".synkro\", \"credentials.json\");\nconst RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;\nconst SYNKRO_API_URL = (RAW_API_URL && /^https?:\\/\\//.test(RAW_API_URL))\n ? RAW_API_URL\n : \"https://api.synkro.sh\";\n\n// Types — matches the AuthCredentials shape returned by the dashboard's\n// /api/auth/cli-callback (see packages/app/src/pages/api/auth/cli-callback.ts).\nexport interface AuthCredentials {\n access_token: string;\n refresh_token: string;\n user_id?: string;\n email?: string;\n org_id?: string;\n state?: string;\n}\n\nexport interface UserInfo {\n id: string;\n email: string;\n org_id?: string;\n}\n\n// HTML responses\nconst SUCCESS_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Successful - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n .checkmark {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #10b981;\n margin: 0 auto 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .checkmark svg {\n width: 50px;\n height: 50px;\n stroke: white;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n .close-note {\n margin-top: 1.5rem;\n font-size: 0.875rem;\n color: #9ca3af;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"checkmark\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\"></path>\n </svg>\n </div>\n <h1>Authentication Successful!</h1>\n <p>Your Synkro CLI has been authenticated.</p>\n <p class=\"close-note\">You can close this window and return to your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\nconst ERROR_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Failed - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Authentication Failed</h1>\n <p>Please try again from your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\n/**\n * Open URL in default browser\n */\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n\n switch (os) {\n case \"darwin\":\n bin = \"open\";\n args = [url];\n break;\n case \"win32\":\n // `start` is a cmd built-in, so we host it via cmd /c, but pass the URL\n // as a literal arg through execFile (no shell parsing) to keep shell\n // metacharacters from being interpreted.\n bin = \"cmd\";\n args = [\"/c\", \"start\", \"\", url];\n break;\n default:\n bin = \"xdg-open\";\n args = [url];\n }\n\n // execFile (vs exec) does NOT spawn a shell, so url is treated as a single\n // argv entry regardless of contents. Removes shell-injection risk if url\n // ever ends up containing $/`/;/&/etc.\n execFile(bin, args, (error) => {\n if (error) {\n console.error(\"Failed to open browser automatically.\");\n console.log(`Please open this URL manually: ${url}`);\n }\n });\n}\n\n/**\n * Save authentication credentials to file\n */\nexport function saveCredentials(data: AuthCredentials): void {\n const dir = dirname(AUTH_FILE);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Load saved authentication credentials\n */\nexport function loadCredentials(): AuthCredentials | null {\n if (!existsSync(AUTH_FILE)) {\n return null;\n }\n\n try {\n const content = readFileSync(AUTH_FILE, \"utf8\");\n return JSON.parse(content);\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Create HTTP server to receive OAuth callback.\n *\n * Mirrors packages/desktop/src-tauri/src/auth.rs:\n * - Listens on PORT\n * - Handles OPTIONS preflight (CORS)\n * - Catches /auth?token=...&refresh_token=...&user_id=...&email=...&org_id=...&state=...\n * - Returns success HTML on token receipt\n */\nfunction createCallbackServer(): Promise<AuthCredentials> {\n // Tokens land via POST body (JSON), never query params, so they don't get\n // logged into req.url, browser DevTools URL bars, server access logs, or\n // tooling that snapshots URLs. CORS origin is pinned to the configured\n // dashboard so a random page on a different origin can't post forged\n // credentials at the local listener.\n const CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": SYNKRO_WEB_AUTH_URL,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n };\n\n return new Promise((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n // CORS preflight — only echo the pinned origin if the request actually\n // comes from there; otherwise omit ACAO so the browser blocks the call.\n if (req.method === \"OPTIONS\") {\n const origin = req.headers.origin;\n if (origin === SYNKRO_WEB_AUTH_URL) {\n res.writeHead(204, CORS_HEADERS);\n } else {\n res.writeHead(204, {\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n });\n }\n res.end();\n return;\n }\n\n // Reject requests whose Origin header isn't the dashboard. Browsers\n // send Origin on cross-origin POSTs, so this stops a hostile page from\n // forging credentials at our local listener even if it bypasses CORS.\n const reqOrigin = req.headers.origin;\n if (reqOrigin && reqOrigin !== SYNKRO_WEB_AUTH_URL) {\n res.writeHead(403, { \"Vary\": \"Origin\" });\n res.end();\n return;\n }\n\n if (!req.url) {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n const url = new URL(req.url, `http://localhost:${PORT}`);\n\n if (url.pathname !== \"/auth\") {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n if (req.method !== \"POST\") {\n // Reject GET so a stale/older dashboard build can't deliver tokens\n // via query-string. Forces upgrade. CLI 1.0.4+ requires the v1.6+\n // dashboard build.\n res.writeHead(405, { ...CORS_HEADERS, \"Allow\": \"POST, OPTIONS\", \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n return;\n }\n\n // Cap body at 16 KB — JWT pairs are ~4–8 KB; anything bigger is junk.\n const MAX_BODY = 16 * 1024;\n const chunks: Buffer[] = [];\n let total = 0;\n let aborted = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > MAX_BODY) {\n aborted = true;\n res.writeHead(413, CORS_HEADERS);\n res.end();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (aborted) return;\n let parsed: any;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"invalid_json\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: invalid JSON body\"));\n }, 200);\n return;\n }\n\n const token: string | undefined = parsed?.token;\n if (!token || typeof token !== \"string\") {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing_token\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: missing token\"));\n }, 200);\n return;\n }\n\n const authData: AuthCredentials = {\n access_token: token,\n refresh_token: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : \"\",\n user_id: typeof parsed.user_id === \"string\" ? parsed.user_id : undefined,\n email: typeof parsed.email === \"string\" ? parsed.email : undefined,\n org_id: typeof parsed.org_id === \"string\" ? parsed.org_id : undefined,\n state: typeof parsed.state === \"string\" ? parsed.state : undefined,\n };\n\n res.writeHead(200, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n\n setTimeout(() => {\n server.close();\n resolve(authData);\n }, 200);\n });\n req.on(\"error\", (e) => {\n if (aborted) return;\n aborted = true;\n try { res.writeHead(500, CORS_HEADERS); res.end(); } catch {}\n setTimeout(() => {\n server.close();\n reject(e);\n }, 200);\n });\n });\n\n server.listen(PORT);\n\n server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${PORT} is already in use. Close any other Synkro CLI instance and retry.`,\n ),\n );\n } else {\n reject(error);\n }\n });\n });\n}\n\nexport type AuthStatus =\n | { phase: 'starting' }\n | { phase: 'browser-opened'; url: string }\n | { phase: 'waiting' }\n | { phase: 'success' }\n | { phase: 'error'; message: string };\n\n/**\n * Initiate the OAuth-style authentication flow\n */\nexport async function authenticate(\n onStatus?: (status: AuthStatus) => void,\n): Promise<AuthCredentials | null> {\n const emit = onStatus || (() => {});\n\n try {\n emit({ phase: 'starting' });\n\n // Start local server to receive the callback\n const serverPromise = createCallbackServer();\n\n // Open browser to the CLI auth page\n const authUrl = `${SYNKRO_WEB_AUTH_URL}/cli-auth?port=${PORT}`;\n openBrowser(authUrl);\n\n emit({ phase: 'browser-opened', url: authUrl });\n emit({ phase: 'waiting' });\n\n // Wait for authentication callback\n const data = await serverPromise;\n\n emit({ phase: 'success' });\n saveCredentials(data);\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emit({ phase: 'error', message });\n return null;\n }\n}\n\n/**\n * Check if user is authenticated (credentials exist and token not expired)\n */\nexport function isAuthenticated(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Also check token expiry\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true; // Can't decode, assume valid (refresh will handle it)\n\n // Consider expired if past expiration (no buffer here — ensureValidToken handles refresh buffer)\n return Date.now() < decoded.exp * 1000;\n } catch {\n return true; // Decode failed, let ensureValidToken handle it\n }\n}\n\n/**\n * Get current user ID from JWT token\n */\nexport function getCurrentUserId(): string {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded?.sub) {\n throw new Error(\"Invalid token\");\n }\n\n return decoded.sub;\n}\n\n/**\n * Get user info from JWT\n */\nexport function getUserInfo(): UserInfo {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n // Prefer the explicit user_id/email/org_id stashed during the OAuth callback\n // (the dashboard returns them as query params). Fall back to JWT decode if\n // we somehow received older creds without those fields.\n if (creds.user_id) {\n return {\n id: creds.user_id,\n email: creds.email ?? '',\n org_id: creds.org_id,\n };\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n\n return {\n id: decoded.sub,\n email: decoded.email ?? '',\n org_id: decoded.org_id,\n };\n}\n\n/**\n * Get access token for API calls\n */\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\n/**\n * Check if token is expired (with 5 min buffer)\n */\nexport function isTokenExpired(): boolean {\n const creds = loadCredentials();\n if (!creds) return true;\n\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true;\n\n // Expired if less than 5 minutes remaining\n const expiresAt = decoded.exp * 1000;\n const buffer = 5 * 60 * 1000; // 5 minutes\n return Date.now() > expiresAt - buffer;\n } catch {\n return true;\n }\n}\n\n/**\n * Refresh the access token using refresh_token\n */\nexport async function refreshToken(): Promise<boolean> {\n const creds = loadCredentials();\n if (!creds?.refresh_token) return false;\n\n try {\n const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: creds.refresh_token }),\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (data.access_token) {\n saveCredentials({\n ...creds,\n access_token: data.access_token,\n refresh_token: data.refresh_token || creds.refresh_token,\n });\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n// Mutex: prevent concurrent token refresh races\nlet refreshPromise: Promise<boolean> | null = null;\n\n/**\n * Ensure we have a valid token, refreshing if needed\n */\nexport async function ensureValidToken(): Promise<boolean> {\n if (!isAuthenticated()) return false;\n\n if (isTokenExpired()) {\n if (!refreshPromise) {\n refreshPromise = refreshToken().finally(() => { refreshPromise = null; });\n }\n const refreshed = await refreshPromise;\n if (!refreshed) {\n clearCredentials();\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Clear saved credentials (logout)\n */\nexport function clearCredentials(): void {\n if (existsSync(AUTH_FILE)) {\n unlinkSync(AUTH_FILE);\n }\n}\n\n/**\n * Get secrets for a user's integrations\n * In production, this would fetch from Infisical vault using the access token\n *\n * These are secrets the USER provides for THEIR integrations:\n * - AWS credentials (for their S3 buckets)\n * - HuggingFace token (for their datasets)\n * - Langsmith API key (for their projects)\n */\nexport async function getSecrets(\n userId: string,\n integrationId: string,\n): Promise<Record<string, string>> {\n // TODO: In production, use access token to fetch from Infisical\n // For now, return from environment (dev mode)\n return {\n AWS_ACCESS_KEY_ID: process.env.USER_AWS_KEY || \"\",\n AWS_SECRET_ACCESS_KEY: process.env.USER_AWS_SECRET || \"\",\n AWS_REGION: process.env.USER_AWS_REGION || \"us-east-1\",\n HF_TOKEN: process.env.USER_HF_TOKEN || \"\",\n LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || \"\",\n };\n}\n","/**\n * Synkro CLI Authentication Module\n *\n * Exports authentication functions for use throughout the CLI.\n */\n\nexport {\n authenticate,\n isAuthenticated,\n getCurrentUserId,\n getUserInfo,\n getAccessToken,\n loadCredentials,\n saveCredentials,\n clearCredentials,\n getSecrets,\n isTokenExpired,\n refreshToken,\n ensureValidToken,\n} from './stub.js';\n\nexport type { AuthCredentials, UserInfo, AuthStatus } from './stub.js';\n","// :)\n/**\n * CLI API client for project and violation endpoints.\n *\n * Calls the Synkro API (Hono/Cloudflare Workers) for project management, violations, and policy upsert.\n * SYNKRO_CRUD_URL points to the API server (defaults to http://localhost:8788/api).\n */\n\nimport { getAccessToken, ensureValidToken } from '../auth/index.js';\n\nlet API_URL = 'https://api.synkro.sh/api';\n\nexport function setApiBaseUrl(url: string): void {\n API_URL = url;\n}\n\n// Types\n\nexport interface Project {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n api_key_count: number;\n created_at: string | null;\n repos?: Array<{ id: string; github_repo_id: number | null; full_name: string }>;\n}\n\nexport interface Violation {\n id: string | null;\n run_id: string | null;\n score: number | null;\n severity: string | null;\n issues: string[];\n rules_violated: string[];\n messages: Array<{ role: string; content: string }>;\n model: string | null;\n comment: string | null;\n created_at: string | null;\n}\n\nexport interface ViolationsResponse {\n violations: Violation[];\n total: number;\n project_id: string;\n project_slug: string | null;\n policy_text: string | null;\n rules: Array<Record<string, unknown>>;\n}\n\nexport interface GetViolationsOptions {\n limit?: number;\n offset?: number;\n severity?: string;\n}\n\nexport interface PolicyUpsertResponse {\n ok: boolean;\n policy_id: string;\n}\n\n// API helper\n\nasync function callApi<T>(\n method: string,\n endpoint: string,\n body?: Record<string, unknown>\n): Promise<T> {\n if (!API_URL) {\n throw new Error('API URL not configured. Run `synkro install` first.');\n }\n\n const url = `${API_URL}${endpoint}`;\n await ensureValidToken();\n const accessToken = getAccessToken();\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (accessToken) {\n headers['Authorization'] = `Bearer ${accessToken}`;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ detail: response.statusText }));\n throw new Error(error.detail || `API error: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport interface CreateProjectResponse {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n created_at: string | null;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n key_prefix: string;\n name: string;\n project_id: string;\n created_at: string | null;\n}\n\n// Project API functions\n\n/**\n * Create a new project.\n */\nexport async function createProject(\n name: string,\n repos?: Array<{ github_repo_id?: number; full_name: string; default_branch?: string; private?: boolean }>,\n): Promise<CreateProjectResponse> {\n const body: Record<string, unknown> = { name };\n if (repos && repos.length > 0) body.repos = repos;\n return callApi<CreateProjectResponse>('POST', '/projects', body);\n}\n\n/**\n * Create an API key for a project. Returns plaintext key (shown once).\n */\nexport async function createApiKey(\n projectId: string,\n name?: string\n): Promise<CreateApiKeyResponse> {\n return callApi<CreateApiKeyResponse>('POST', '/api-keys', {\n project_id: projectId,\n name: name || 'CLI Key',\n });\n}\n\n/**\n * List all projects for the authenticated user.\n */\nexport async function listProjects(): Promise<Project[]> {\n return callApi<Project[]>('GET', '/projects');\n}\n\n/**\n * Get violations for a project with optional filtering.\n * Also returns the active policy text and rules.\n */\nexport async function getViolations(\n projectId: string,\n options: GetViolationsOptions = {}\n): Promise<ViolationsResponse> {\n const params = new URLSearchParams();\n if (options.limit) params.set('limit', String(options.limit));\n if (options.offset) params.set('offset', String(options.offset));\n if (options.severity) params.set('severity', options.severity);\n\n const qs = params.toString();\n const endpoint = `/projects/${projectId}/violations${qs ? `?${qs}` : ''}`;\n return callApi<ViolationsResponse>('GET', endpoint);\n}\n\n/**\n * Upsert a policy for a project (deploy rules to gateway).\n */\nexport async function upsertPolicy(\n projectId: string,\n policyText: string,\n rules: Array<Record<string, unknown>>,\n ruleCount: number\n): Promise<PolicyUpsertResponse> {\n return callApi<PolicyUpsertResponse>('POST', `/projects/${projectId}/policies`, {\n policy_text: policyText,\n rules,\n rule_count: ruleCount,\n });\n}\n\n/**\n * Update a project's configuration.\n */\nexport async function updateProject(\n projectId: string,\n updates: {\n name?: string;\n provider?: string;\n langsmith_project?: string;\n is_active?: boolean;\n }\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('PATCH', `/projects/${projectId}`, updates as Record<string, unknown>);\n}\n\n/**\n * Unlink a repo from a project.\n */\nexport async function unlinkRepo(\n projectId: string,\n repoId: string,\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('DELETE', `/projects/${projectId}/repos/${repoId}`);\n}\n\n/**\n * Resolve a project by name or slug (fuzzy match against user's projects).\n * Returns the first matching project or null.\n */\nexport async function resolveProject(nameOrSlug: string): Promise<Project | null> {\n const projects = await listProjects();\n const query = nameOrSlug.toLowerCase();\n\n // Exact slug match\n const exactSlug = projects.find(p => p.slug === nameOrSlug);\n if (exactSlug) return exactSlug;\n\n // Exact name match (case-insensitive)\n const exactName = projects.find(p => p.name.toLowerCase() === query);\n if (exactName) return exactName;\n\n // Partial name match\n const partial = projects.find(p => p.name.toLowerCase().includes(query));\n if (partial) return partial;\n\n // Partial slug match\n const partialSlug = projects.find(p => p.slug.includes(query));\n if (partialSlug) return partialSlug;\n\n return null;\n}\n","// :)\n/**\n * GitHub Actions workflow YAML template for Synkro PR scanning.\n *\n * Customer commits this to .github/workflows/synkro.yml. Triggers on\n * pull_request open/synchronize/reopened. Runs `synkro scan-pr` which\n * spawns claude --print with their CLAUDE_CODE_OAUTH_TOKEN per file.\n *\n * Both CLIs install via npm (npm registry attestations + signed packages\n * are verified by npm itself). No curl-pipe-to-bash; we publish to npm\n * specifically so CI can install us through a trusted package manager.\n */\n\nexport const SYNKRO_WORKFLOW_YAML = `name: Synkro Security Review\non:\n pull_request:\n types: [opened, synchronize, reopened]\n workflow_dispatch:\n inputs:\n pr_number:\n description: PR number to scan\n required: true\n sha:\n description: Commit SHA to scan\n required: true\n\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: write\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n ref: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n\n - name: Cache npm globals\n id: cache-npm-global\n uses: actions/cache@v4\n with:\n path: ~/.npm-global\n key: synkro-cli-\\${{ runner.os }}-v1\n\n - name: Install Synkro CLI + Claude Code CLI\n run: |\n npm config set prefix ~/.npm-global\n npm install -g @synkro-sh/cli @anthropic-ai/claude-code\n echo \"$HOME/.npm-global/bin\" >> $GITHUB_PATH\n\n - name: Run Synkro PR scan\n run: synkro-cli scan-pr\n env:\n CLAUDE_CODE_OAUTH_TOKEN: \\${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}\n SYNKRO_API_KEY: \\${{ secrets.SYNKRO_API_KEY }}\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n SYNKRO_PR_NUMBER: \\${{ inputs.pr_number || github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_SHA: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n SYNKRO_GATEWAY_URL: \\${{ vars.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh' }}\n`;\n\nexport const WORKFLOW_FILENAME = 'synkro.yml';\nexport const WORKFLOW_PATH = '.github/workflows/synkro.yml';\n","/**\n * GitHub repo setup for PR scanning.\n *\n * Uses `gh secret set` to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY\n * as repo secrets, then writes .github/workflows/synkro.yml to the local\n * clone if present.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\nexport interface GitHubAuthOptions {\n token: string;\n}\n\nfunction ghSecretSet(token: string, owner: string, repo: string, name: string, value: string): void {\n execSync(`gh secret set ${name} --repo ${owner}/${repo} --body -`, {\n input: value,\n env: { ...process.env, GH_TOKEN: token },\n stdio: ['pipe', 'ignore', 'pipe'],\n timeout: 30_000,\n });\n}\n\n/**\n * List repos the token has access to.\n */\nexport async function listAccessibleRepos(opts: GitHubAuthOptions): Promise<Array<{ owner: string; repo: string; full_name: string }>> {\n const repos: Array<{ owner: string; repo: string; full_name: string }> = [];\n let page = 1;\n while (page <= 5) { // cap pagination\n const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator`;\n const resp = await fetch(url, {\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n if (!resp.ok) {\n throw new Error(`GitHub API ${resp.status} listing repos`);\n }\n const data = await resp.json() as Array<{ full_name: string; owner: { login: string }; name: string }>;\n if (data.length === 0) break;\n for (const r of data) {\n repos.push({ owner: r.owner.login, repo: r.name, full_name: r.full_name });\n }\n if (data.length < 100) break;\n page++;\n }\n return repos;\n}\n\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken?: string; synkroApiKey: string },\n): Promise<void> {\n try { execSync('gh --version', { stdio: 'ignore', timeout: 5000 }); } catch {\n throw new Error('GitHub CLI (gh) not found. Install it: https://cli.github.com');\n }\n if (secrets.claudeCodeOauthToken) {\n ghSecretSet(opts.token, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken);\n }\n ghSecretSet(opts.token, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey);\n}\n\n/**\n * Write the workflow YAML to a local repo clone (if the user is in one).\n * Returns the absolute path written, or null if cwd isn't a git repo.\n */\nexport function writeWorkflowFile(repoRootPath: string): string | null {\n const workflowDir = join(repoRootPath, '.github', 'workflows');\n mkdirSync(workflowDir, { recursive: true });\n const workflowFile = join(workflowDir, 'synkro.yml');\n writeFileSync(workflowFile, SYNKRO_WORKFLOW_YAML, 'utf-8');\n return workflowFile;\n}\n\n/**\n * Find the git repo root for a given cwd. Returns null if not in a git repo.\n */\nexport function findGitRoot(startCwd: string): string | null {\n let cur = startCwd;\n while (cur && cur !== '/') {\n if (existsSync(join(cur, '.git'))) return cur;\n const parent = join(cur, '..');\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nexport const SECRET_NAMES = {\n CLAUDE_OAUTH: 'CLAUDE_CODE_OAUTH_TOKEN',\n SYNKRO_API_KEY: 'SYNKRO_API_KEY',\n} as const;\n\nexport const WORKFLOW_RELATIVE_PATH = WORKFLOW_PATH;\n","// :)\n/**\n * Shared helpers for repo connection during install and `synkro link`.\n *\n * Two paths:\n * 1. Local git repo — detect from `git remote get-url origin`, any provider\n * 2. GitHub OAuth — browser flow, list repos, interactive picker\n */\nimport { execSync, execFileSync } from 'node:child_process';\nimport { readdirSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { createProject, listProjects } from '../api/projects.js';\nimport { listAccessibleRepos } from '../installer/githubSetup.js';\n\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : 'https://app.synkro.sh';\nconst GITHUB_PORT = 8101;\n\nfunction detectGitRepo(): { fullName: string; shortName: string } | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n // Match any git host — github, gitlab, bitbucket, self-hosted, etc.\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (!match) return null;\n const fullName = match[1];\n return { fullName, shortName: fullName.split('/').pop() || fullName };\n } catch { return null; }\n}\n\nfunction detectSubdirRepos(): Array<{ fullName: string; shortName: string }> {\n try {\n const entries = readdirSync('.', { withFileTypes: true });\n const repos: Array<{ fullName: string; shortName: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n try {\n const remoteUrl = execFileSync('git', ['-C', entry.name, 'remote', 'get-url', 'origin'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (match) {\n const fullName = match[1];\n repos.push({ fullName, shortName: fullName.split('/').pop() || fullName });\n }\n } catch { /* not a git repo or no remote */ }\n }\n return repos;\n } catch { return []; }\n}\n\nfunction ask(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nfunction waitForGithubToken(): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n });\n res.end();\n return;\n }\n\n if (req.url !== '/auth' || req.method !== 'POST') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const parsed = JSON.parse(body);\n if (!parsed.github_token) {\n res.writeHead(400, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ error: 'missing github_token' }));\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ ok: true }));\n setTimeout(() => server.close(), 200);\n resolve(parsed.github_token);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'invalid json' }));\n }\n });\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${GITHUB_PORT} is in use. Close other processes and try again.`));\n } else {\n reject(err);\n }\n });\n\n server.listen(GITHUB_PORT);\n });\n}\n\nfunction openBrowser(url: string): void {\n const { execFile } = require('node:child_process');\n const plat = process.platform;\n const cb = (err: Error | null) => {\n if (err) console.log(` Open this URL manually: ${url}`);\n };\n if (plat === 'darwin') execFile('open', [url], cb);\n else if (plat === 'win32') execFile('cmd', ['/c', 'start', '', url], cb);\n else execFile('xdg-open', [url], cb);\n}\n\nasync function connectGithubAndSelectRepos(): Promise<Array<{ full_name: string }>> {\n const url = `${SYNKRO_WEB_AUTH_URL}/cli-github?port=${GITHUB_PORT}`;\n console.log(' Opening browser for GitHub authorization...');\n openBrowser(url);\n\n console.log(' Waiting for GitHub authorization...');\n const ghToken = await waitForGithubToken();\n console.log(' ✓ GitHub connected\\n');\n\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.log(' No accessible repos found on GitHub.');\n return [];\n }\n\n console.log(` Found ${repos.length} repos:\\n`);\n repos.forEach((r: any, i: number) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const selection = await ask(rl, ' Select repos (comma-separated numbers, e.g. 1,3,5): ');\n const indices = selection\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n\n if (indices.length === 0) {\n console.log(' No repos selected.');\n return [];\n }\n\n return indices.map((i) => ({ full_name: repos[i].full_name }));\n } finally {\n rl.close();\n }\n}\n\nexport async function promptRepoConnection(opts?: { linkRepo?: boolean }): Promise<void> {\n const localRepo = detectGitRepo();\n const subdirRepos = !localRepo ? detectSubdirRepos() : [];\n\n if (opts?.linkRepo && localRepo) {\n console.log('Connect repos to Synkro:\\n');\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n console.log();\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n try {\n console.log('Connect repos to Synkro:\\n');\n\n let existingFullNames = new Set<string>();\n if (subdirRepos.length > 0) {\n try {\n const existing = await listProjects();\n existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n } catch {}\n }\n\n if (subdirRepos.length > 0) {\n console.log(` Found ${subdirRepos.length} repo(s) in this directory:\\n`);\n subdirRepos.forEach((r, i) => {\n const linked = existingFullNames.has(r.fullName);\n console.log(` ${String(i + 1).padStart(3)}. ${r.fullName}${linked ? ' ✓ linked' : ''}`);\n });\n const ghIdx = subdirRepos.length + 1;\n const skipIdx = subdirRepos.length + 2;\n console.log(` ${String(ghIdx).padStart(3)}. Connect GitHub to select repos`);\n console.log(` ${String(skipIdx).padStart(3)}. Skip for now`);\n console.log();\n\n const selection = await ask(rl, ' Select repos to link (comma-separated numbers, e.g. 1,3): ');\n const nums = selection.split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));\n console.log();\n rl.close();\n\n if (nums.includes(ghIdx)) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n const newRepos = selectedRepos.filter((r) => !existingFullNames.has(r.full_name));\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n }\n } else if (nums.includes(skipIdx) || nums.length === 0) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n const repoIndices = nums.map((n) => n - 1).filter((n) => n >= 0 && n < subdirRepos.length);\n const toLink = repoIndices.map((i) => subdirRepos[i]).filter((r) => !existingFullNames.has(r.fullName));\n if (toLink.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n for (const repo of toLink) {\n try {\n await createProject(repo.shortName, [{ full_name: repo.fullName }]);\n console.log(` ✓ Created project \"${repo.shortName}\" linked to ${repo.fullName}`);\n } catch (err) {\n console.warn(` ⚠ Could not link ${repo.fullName}: ${(err as Error).message}`);\n }\n }\n }\n }\n } else {\n const options: string[] = [];\n if (localRepo) {\n options.push(`Link this repo (${localRepo.fullName})`);\n }\n options.push('Connect GitHub to select repos');\n options.push('Skip for now');\n\n options.forEach((opt, i) => {\n console.log(` ${i + 1}. ${opt}`);\n });\n console.log();\n\n const choice = await ask(rl, ' Choose (number): ');\n const choiceNum = parseInt(choice.trim(), 10);\n console.log();\n rl.close();\n\n const localIdx = localRepo ? 1 : -1;\n const githubIdx = localRepo ? 2 : 1;\n const skipIdx = localRepo ? 3 : 2;\n\n if (choiceNum === localIdx && localRepo) {\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n } else if (choiceNum === githubIdx) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n try {\n const existing = await listProjects();\n const linkedNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !linkedNames.has(r.full_name));\n\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repos: ${(err as Error).message}`);\n }\n }\n } else if (choiceNum === skipIdx) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n console.log(' Invalid choice. Skipping repo connection.');\n }\n }\n } catch {\n rl.close();\n }\n console.log();\n}\n","/**\n * Fetch judge prompt metadata from the Synkro API.\n * Prompts are fetched live on every grade call — never cached to disk.\n * This module only fetches the version string for display during install.\n */\n\ninterface PromptsMetaResponse {\n version: string;\n}\n\nexport async function fetchJudgePrompts(opts: {\n gatewayUrl: string;\n jwt: string;\n forceRefresh?: boolean;\n}): Promise<{\n version: string;\n}> {\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/hook/config`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${opts.jwt}`,\n 'User-Agent': 'synkro-cli/1.0',\n },\n signal: AbortSignal.timeout(5000),\n });\n\n if (!resp.ok) {\n return { version: 'unknown' };\n }\n\n const data = await resp.json() as { prompts?: { version?: string } };\n return { version: data.prompts?.version ?? 'unknown' };\n}\n","/**\n * macOS Keychain export for the containerised Synkro install.\n *\n * Claude Code on macOS stores its auth blob in the user's login keychain under\n * the service name `Claude Code-credentials`. A Linux container can't reach\n * the keychain, so on `synkro install` we shell out to the `security` CLI,\n * extract the blob, and write it to a host-side file that the container\n * bind-mounts read-write.\n *\n * Refresh policy: claude rotates its token periodically. We install a launchd\n * agent that re-exports every 45 min so the container picks up new tokens\n * without manual intervention. Stale tokens between rotations fail at most a\n * single grade before the next refresh runs.\n *\n * IMPORTANT (per Synkro Plan Review R003): the exported file lives ONLY on the\n * host under ~/.synkro/claude-creds/ and is bind-mounted at container runtime.\n * It must never be COPY'd into a Dockerfile or otherwise baked into image\n * layers — image layers are part of the publishable artefact and would leak\n * the customer's credentials to anyone who pulls the image.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, statSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nexport const CLAUDE_CREDS_DIR = join(SYNKRO_DIR, 'claude-creds');\nexport const CLAUDE_CREDS_FILE = join(CLAUDE_CREDS_DIR, '.credentials.json');\n\n// Cursor grading uses @cursor/sdk, which authenticates ONLY with a\n// dashboard-issued Cursor API key — the macOS keychain OAuth token is\n// rejected by the SDK. So there is no keychain bridge for Cursor: the user\n// places their API key (cursor.com → Settings → API Keys) in this file and\n// it is bind-mounted into the container.\nexport const CURSOR_CREDS_DIR = join(SYNKRO_DIR, 'cursor-creds');\nexport const CURSOR_API_KEY_FILE = join(CURSOR_CREDS_DIR, 'api-key');\n\n// Keychain service name claude uses on macOS. Don't change without verifying\n// against the installed Claude Code build first — name drift is silent and\n// shows up later as \"all my grades say unavailable.\"\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\n// launchd label for the periodic refresh agent.\nconst LAUNCHD_LABEL = 'com.synkro.cli.claude-creds-refresh';\nconst LAUNCHD_PLIST = join(homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);\nconst REFRESH_INTERVAL_SECONDS = 45 * 60; // 45 minutes — Claude OAuth tokens expire in ~1h\n\nexport class KeychainExportError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'KeychainExportError';\n }\n}\n\n/**\n * True when the platform actually needs the keychain bridge. Linux stores\n * creds in a file already; only darwin uses the keychain.\n */\nexport function needsKeychainBridge(): boolean {\n return platform() === 'darwin';\n}\n\n/**\n * Read the Claude Code credential blob from the macOS keychain. Returns null\n * if the entry doesn't exist (claude not signed in) or if the security CLI\n * fails for any reason. The caller should treat null as \"auth not configured\n * yet\" rather than a hard error — the user may not have launched claude yet.\n */\nexport function readKeychainCreds(): string | null {\n if (platform() !== 'darwin') return null;\n const r = spawnSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) return null;\n const blob = (r.stdout || '').trim();\n return blob || null;\n}\n\n/**\n * One-shot export: pull creds from the keychain and write them to the\n * host-side file the container bind-mounts. Returns the absolute file path,\n * or null if there was nothing to export.\n *\n * The directory and file are chmodded to 700/600 so other users on the host\n * can't read the credentials. The container's bind mount preserves these\n * perms inside the namespace.\n */\nexport function exportKeychainCreds(): string | null {\n const blob = readKeychainCreds();\n if (!blob) return null;\n mkdirSync(CLAUDE_CREDS_DIR, { recursive: true });\n chmodSync(CLAUDE_CREDS_DIR, 0o700);\n writeFileSync(CLAUDE_CREDS_FILE, blob, 'utf-8');\n chmodSync(CLAUDE_CREDS_FILE, 0o600);\n return CLAUDE_CREDS_FILE;\n}\n\n/**\n * True when the user has placed a Cursor API key at CURSOR_API_KEY_FILE.\n * Used by the installer to decide whether the Cursor worker pool can run.\n * We never read the key value here — only confirm the file is present and\n * non-empty so the worker can pick it up via the bind mount.\n */\nexport function cursorApiKeyConfigured(): boolean {\n try {\n return existsSync(CURSOR_API_KEY_FILE)\n && readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Persist a Cursor API key to CURSOR_API_KEY_FILE (0600, dir 0700). The\n * containerised worker bind-mounts the file and reads the key at grade time.\n * Same R003 rule as the Claude creds: host-only file, never baked into an\n * image layer.\n */\nexport function writeCursorApiKey(key: string): void {\n const trimmed = key.trim();\n if (!trimmed) return;\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n chmodSync(CURSOR_CREDS_DIR, 0o700);\n writeFileSync(CURSOR_API_KEY_FILE, trimmed, 'utf-8');\n chmodSync(CURSOR_API_KEY_FILE, 0o600);\n}\n\n/**\n * Check whether the stored Cursor API key is actually accepted by Cursor.\n * Hits the Cursor API's `GET /v1/me` (Basic auth — the key is the username).\n *\n * Returns true (accepted), false (definitively rejected — 401/403), or null\n * (could not determine: no key, network error, or an ambiguous status). A null\n * result must NOT be treated as invalid — a transient network blip should\n * never nag the user to re-enter a working key.\n */\nexport async function validateCursorApiKey(): Promise<boolean | null> {\n let key: string;\n try {\n key = readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n if (!key) return null;\n try {\n const auth = Buffer.from(`${key}:`).toString('base64');\n const r = await fetch('https://api.cursor.com/v1/me', {\n headers: { Authorization: `Basic ${auth}` },\n signal: AbortSignal.timeout(8_000),\n });\n if (r.status === 401 || r.status === 403) return false;\n if (r.ok) return true;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether the exported creds file is older than its refresh interval. Used by\n * `synkro local-cc status` to decide if we should re-export proactively even\n * though launchd should be handling it.\n */\nexport function credsAreStale(): boolean {\n if (!existsSync(CLAUDE_CREDS_FILE)) return true;\n try {\n const ageMs = Date.now() - statSync(CLAUDE_CREDS_FILE).mtimeMs;\n return ageMs > REFRESH_INTERVAL_SECONDS * 1000;\n } catch {\n return true;\n }\n}\n\n/**\n * Install a launchd agent that runs `synkro local-cc refresh-creds` every\n * REFRESH_INTERVAL_SECONDS. Idempotent — overwrites the existing plist if\n * present. Caller should also `launchctl bootout` + `launchctl bootstrap` to\n * apply changes; we keep that in install.ts so this module stays side-effect\n * minimal.\n *\n * Returns the absolute path to the written plist for the caller to load.\n */\nexport function writeRefreshAgent(synkroBinPath: string): string {\n if (platform() !== 'darwin') {\n throw new KeychainExportError('writeRefreshAgent is darwin-only');\n }\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n\n // Use a shell wrapper so PATH resolution works even if the binary moves\n // (e.g. /usr/local/bin vs /opt/homebrew/bin). The plist runs /bin/bash -c\n // which inherits the user's PATH from the login environment.\n const shellCmd = `export PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\" && { \"${synkroBinPath}\" local-cc refresh-creds || synkro local-cc refresh-creds; }`;\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/bash</string>\n <string>-c</string>\n <string>${shellCmd}</string>\n </array>\n <key>StartInterval</key>\n <integer>${REFRESH_INTERVAL_SECONDS}</integer>\n <key>RunAtLoad</key>\n <true/>\n <key>StandardErrorPath</key>\n <string>${join(SYNKRO_DIR, 'claude-creds-refresh.log')}</string>\n <key>StandardOutPath</key>\n <string>${join(SYNKRO_DIR, 'claude-creds-refresh.log')}</string>\n</dict>\n</plist>\n`;\n writeFileSync(LAUNCHD_PLIST, plist, 'utf-8');\n return LAUNCHD_PLIST;\n}\n\n/**\n * Load (or reload) the launchd refresh agent. Safe to call multiple times.\n */\nexport function loadRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n // bootout returns non-zero if it isn't loaded; that's fine, we just want to\n // ensure a clean state before bootstrap.\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const r = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) {\n throw new KeychainExportError(\n `launchctl bootstrap failed: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n}\n\n/**\n * Stop the launchd refresh agent and remove the plist. Used by `synkro\n * disconnect` / `synkro uninstall` so we don't leave orphan agents behind.\n */\nexport function uninstallRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n try {\n if (existsSync(LAUNCHD_PLIST)) {\n require('node:fs').unlinkSync(LAUNCHD_PLIST);\n }\n } catch { /* best-effort */ }\n}\n\n/**\n * Convenience wrapper for `synkro local-cc refresh-creds`. Re-exports the\n * Claude keychain creds. Cursor needs no refresh agent — its API key is a\n * static dashboard key the user supplies once; it doesn't rotate like the\n * Claude OAuth token.\n */\nexport function refreshCreds(): boolean {\n const path = exportKeychainCreds();\n return path !== null;\n}\n\n/**\n * Read the current exported creds (if any). Used by status commands.\n */\nexport function readExportedCreds(): string | null {\n try {\n return readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n } catch {\n return null;\n }\n}\n","/**\n * Containerised Synkro install path.\n *\n * Replaces the host-side pueue + tmux + cc_sessions stack with a single\n * Docker container that bundles MCP server, pglite-db, grader dispatcher,\n * and N tmux-managed claude grader workers.\n *\n * Activated when SYNKRO_DEPLOYMENT_MODE=docker is set. The bare-host path\n * remains the default until two stable release cycles have validated this\n * path under real load (see Phase 5 in the architecture plan).\n *\n * Host-side files this module touches:\n * ~/.synkro/ (bind-mounted RO as /data/synkro-host;\n * the container reads .mcp-jwt and\n * credentials.json from inside it)\n * ~/.synkro/pgdata/ (bind-mounted RW, persistent telemetry)\n * ~/.synkro/claude-creds/ (bind-mounted RW on macOS; symlinked\n * from ~/.claude on Linux)\n *\n * Never bakes credentials into the image — every secret is a runtime bind\n * mount of a host file. See macKeychain.ts for the macOS bridge.\n */\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport {\n CLAUDE_CREDS_DIR,\n CURSOR_CREDS_DIR,\n exportKeychainCreds,\n cursorApiKeyConfigured,\n needsKeychainBridge,\n writeRefreshAgent,\n loadRefreshAgent,\n} from './macKeychain.js';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nconst MCP_JWT_PATH = join(SYNKRO_DIR, '.mcp-jwt');\nconst PGDATA_PATH = join(SYNKRO_DIR, 'pgdata');\n// Docker Desktop on macOS can't reliably bind-mount individual files (the mount\n// shows up as -????? inside the container). Stage the host's ~/.claude.json\n// inside a sibling directory and bind-mount the directory instead.\nconst CLAUDE_HOST_STATE_DIR = join(SYNKRO_DIR, 'claude-host-state');\nconst CLAUDE_HOST_STATE_FILE = join(CLAUDE_HOST_STATE_DIR, '.claude.json');\n\n// Host-side ports the installer maps from the container's internal ports.\n// Bumped by 10000 to avoid colliding with the bare-host install's defaults\n// when both are sitting on the same machine during cutover (Phase 5).\nconst HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || '18931', 10);\nconst HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10);\nconst HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || '18930', 10);\n// PGLite TCP socket published from the container's internal 5433 so host tools\n// (psql/DBeaver/TablePlus) can inspect the telemetry DB. The container's\n// pglite-bootstrap multiplexer enforces access; the 127.0.0.1 bind is the\n// network-layer allowlist.\nconst HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || '15433', 10);\n\nconst CONTAINER_NAME = 'synkro-server';\n// Canonical GHCR coordinates published by .github/workflows/docker-publish.yml.\n// Override at install time via SYNKRO_IMAGE_TAG (or mirror to a private\n// registry and set SYNKRO_IMAGE_REGISTRY for the hedge-fund deployment).\nconst DEFAULT_IMAGE = 'ghcr.io/synkro-sh/synkro-server:latest';\n\nexport class DockerInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'DockerInstallError';\n }\n}\n\nexport type WorkerProvider = 'claude_code' | 'cursor';\n\n/**\n * Split a total worker count across the selected providers.\n * - both providers → even split (an odd worker goes to Claude)\n * - single provider → the whole pool\n * - no providers → treated as Claude-only (legacy default)\n * Used by `synkro install` and `synkro local-cc start|restart` so the split\n * rule lives in exactly one place.\n */\nexport function splitWorkers(total: number, providers: WorkerProvider[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n} {\n const t = Math.max(0, Math.floor(total));\n const hasClaude = providers.includes('claude_code');\n const hasCursor = providers.includes('cursor');\n if (hasClaude && hasCursor) {\n const cursorWorkers = Math.floor(t / 2);\n return { claudeWorkers: t - cursorWorkers, cursorWorkers };\n }\n if (hasCursor) return { claudeWorkers: 0, cursorWorkers: t };\n return { claudeWorkers: t, cursorWorkers: 0 };\n}\n\n/** Normalize a provider token. Accepts claude / claude-code / cc / cursor. */\nfunction normalizeProvider(p: string): WorkerProvider | null {\n const v = p.trim().toLowerCase();\n if (v === 'claude' || v === 'claude-code' || v === 'claude_code' || v === 'cc') return 'claude_code';\n if (v === 'cursor') return 'cursor';\n return null;\n}\n\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n if (/^\\S/.test(line) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null; currentArr = null;\n const ci = line.indexOf(':');\n const key = line.slice(0, ci).trim();\n const val = line.slice(ci + 1).trim();\n currentKey = key;\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (/^ - /.test(line)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (/^ \\S/.test(line) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const ci = line.indexOf(':');\n const k = line.slice(0, ci).trim();\n const v = line.slice(ci + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nfunction readSynkroFileConfig(): { pool: 'auto' | 'claude' | 'cursor'; claudeWorkers?: number; cursorWorkers?: number } {\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return { pool: 'auto' };\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return { pool: 'auto' };\n const raw = readFileSync(fp, 'utf-8');\n const parsed = raw.trimStart().startsWith('{') ? JSON.parse(raw) : parseSynkroYaml(raw);\n const pool = ['auto', 'claude', 'cursor'].includes(parsed?.grader?.pool) ? parsed.grader.pool : 'auto';\n const cw = typeof parsed?.workers?.claude === 'number' ? Math.max(0, Math.floor(parsed.workers.claude)) : undefined;\n const curw = typeof parsed?.workers?.cursor === 'number' ? Math.max(0, Math.floor(parsed.workers.cursor)) : undefined;\n return { pool, claudeWorkers: cw, cursorWorkers: curw };\n } catch {}\n return { pool: 'auto' };\n}\n\n/**\n * Parse `--workers N|--workers=N` and `--provider(s) a,b` flags from a\n * start/restart invocation and compute the per-kind worker split.\n * - `explicit` is true when any worker/provider flag was supplied — callers\n * use it to decide between a config-changing recreate vs a plain start.\n * - Providers default to the coding agents detected on the machine.\n * - Total defaults to 8, capped at 64.\n */\nexport function resolveWorkerConfig(rest: string[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n explicit: boolean;\n} {\n let workers = 8;\n let explicit = false;\n const providers: WorkerProvider[] = [];\n const addProviders = (csv: string) => {\n for (const p of csv.split(',')) {\n const np = normalizeProvider(p);\n if (np && !providers.includes(np)) providers.push(np);\n }\n };\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n if (a === '--workers' || a === '-w') { workers = parseInt(rest[++i] || '8', 10); explicit = true; }\n else if (a.startsWith('--workers=')) { workers = parseInt(a.slice('--workers='.length), 10); explicit = true; }\n else if (a === '--provider' || a === '--providers') { addProviders(rest[++i] || ''); explicit = true; }\n else if (a.startsWith('--provider=')) { addProviders(a.slice('--provider='.length)); explicit = true; }\n else if (a.startsWith('--providers=')) { addProviders(a.slice('--providers='.length)); explicit = true; }\n }\n if (!Number.isFinite(workers) || workers < 1) workers = 8;\n workers = Math.min(workers, 64);\n let provs = providers;\n if (provs.length === 0) {\n const sc = readSynkroFileConfig();\n if (sc.claudeWorkers != null || sc.cursorWorkers != null) {\n const cw = sc.claudeWorkers || 0;\n const curw = sc.cursorWorkers || 0;\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };\n }\n if (sc.pool === 'cursor') {\n provs = ['cursor'];\n } else if (sc.pool === 'claude') {\n provs = ['claude_code'];\n } else {\n provs = detectAgents().map(a => a.kind) as WorkerProvider[];\n if (provs.length === 0) provs = ['claude_code'];\n }\n }\n return { ...splitWorkers(workers, provs), explicit };\n}\n\n/** Resolve which image tag this install should pull. */\nexport function imageTag(): string {\n const registry = process.env.SYNKRO_IMAGE_REGISTRY || ''; // e.g. registry.hedgefund.internal\n const tag = process.env.SYNKRO_IMAGE_TAG || DEFAULT_IMAGE;\n return registry ? `${registry.replace(/\\/+$/, '')}/${tag.replace(/^.*\\//, '')}` : tag;\n}\n\n/** True iff docker is on PATH and the daemon is reachable. */\nexport function assertDockerAvailable(): void {\n const v = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (v.status !== 0) {\n throw new DockerInstallError(\n 'Docker CLI is not installed or the daemon is not reachable.\\n' +\n 'Install Docker Desktop (macOS) or docker-engine (Linux), start the daemon, then re-run.',\n );\n }\n}\n\n/**\n * Compute the bind-mount source for the claude credentials dir. macOS uses\n * the exported keychain shadow; Linux uses ~/.claude directly.\n */\nfunction claudeCredsHostDir(): string {\n if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;\n return join(homedir(), '.claude');\n}\n\n/**\n * Idempotent docker pull + run. If a container with the same name is already\n * up, this stops it first so the new image starts cleanly (matches\n * `synkro update` semantics).\n *\n * Returns the resolved image tag for logging.\n */\n/**\n * Resolve the absolute path to the `synkro` binary for the launchd refresh\n * agent. The location varies by install method (Homebrew's /opt/homebrew/bin,\n * /usr/local/bin, an npm prefix) — never hardcode it, or the refresh job\n * silently fails and the container's Claude creds expire.\n */\nfunction resolveSynkroBin(): string {\n const which = spawnSync('which', ['synkro'], { encoding: 'utf-8', timeout: 5_000 });\n const resolved = (which.stdout || '').split('\\n')[0].trim();\n return resolved || 'synkro';\n}\n\n// Host-side PGLite orphans from pre-Docker installs (or earlier crashed\n// container starts that re-spawned the host bin) squat on the published\n// PGLite port, hold the only client slot under the old maxConnections=1\n// default, and write into a separate pgdata from the container's mount.\n// The migration commit that moved PGLite into the container deleted the\n// source file but never killed running processes — they daemonize to\n// launchd (PPID=1) and outlive the install that spawned them. Sweep them\n// before launching the new container so the publish maps to the right\n// PGLite. Container bun processes are isolated inside their PID namespace\n// and never appear in the host's ps output, so this is safe.\nfunction sweepHostPglite(): void {\n const ps = spawnSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5_000 });\n if (ps.status !== 0) return;\n const targets: number[] = [];\n for (const line of (ps.stdout || '').split('\\n')) {\n if (!/bun\\b/.test(line)) continue;\n if (!/pglite-(db|bootstrap)\\.ts\\b/.test(line)) continue;\n const m = line.trim().match(/^(\\d+)\\s/);\n if (!m) continue;\n const pid = parseInt(m[1], 10);\n if (pid > 0 && pid !== process.pid) targets.push(pid);\n }\n if (targets.length === 0) return;\n console.log(` Sweeping ${targets.length} stale host PGLite process(es): ${targets.join(', ')}`);\n for (const pid of targets) {\n try { process.kill(pid, 'SIGTERM'); } catch {}\n }\n const deadline = Date.now() + 3000;\n while (Date.now() < deadline) {\n const alive = targets.filter(pid => {\n try { process.kill(pid, 0); return true; } catch { return false; }\n });\n if (alive.length === 0) return;\n spawnSync('sleep', ['0.2'], { timeout: 1_000 });\n }\n for (const pid of targets) {\n try { process.kill(pid, 'SIGKILL'); } catch {}\n }\n}\n\nexport async function dockerInstall(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n hostPglitePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n // Per-kind worker counts. claudeWorkers falls back to the legacy\n // workersPerPool; cursorWorkers defaults to 0 (Claude-only install).\n const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;\n const cursorWorkers = opts.cursorWorkers ?? 0;\n const totalWorkers = claudeWorkers + cursorWorkers;\n\n // Ensure persistent host dirs exist before docker tries to bind-mount them.\n // Docker creates missing bind sources as root-owned dirs which then break\n // permissions inside the container — pre-create them as the host user.\n mkdirSync(PGDATA_PATH, { recursive: true });\n mkdirSync(BACKUP_DIR, { recursive: true });\n\n // Stage the host's ~/.claude.json into a dir we can bind-mount cleanly on\n // macOS. Without this the container's workers see -????? at the mount path\n // and Claude Code prompts for browser OAuth on every launch.\n mkdirSync(CLAUDE_HOST_STATE_DIR, { recursive: true });\n const hostClaudeJson = join(homedir(), '.claude.json');\n if (existsSync(hostClaudeJson)) {\n copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);\n }\n if (!existsSync(MCP_JWT_PATH)) {\n throw new DockerInstallError(\n `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`,\n );\n }\n\n // Pre-create the Cursor creds dir so the bind mount target is owned by the\n // host user (Docker would otherwise create it root-owned).\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n\n // On macOS, export keychain creds + install the launchd refresh agent\n // before starting the container so the workers have something to auth with.\n if (needsKeychainBridge()) {\n // Claude creds are only required when the install includes Claude workers.\n if (claudeWorkers > 0) {\n const path = exportKeychainCreds();\n if (!path) {\n throw new DockerInstallError(\n 'Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container.',\n );\n }\n }\n // Cursor uses a dashboard API key (the @cursor/sdk rejects the keychain\n // OAuth token). Warn — don't hard-fail — so a \"both agents\" install still\n // proceeds; the Cursor pool stays idle until the key file is present.\n if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {\n console.warn(' ⚠ No Cursor API key found — Cursor grader workers will be idle.');\n console.warn(' Generate a key at cursor.com → Settings → API Keys, then:');\n console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);\n }\n // launchd refresh agent — Claude only (Cursor's API key is static).\n const plist = writeRefreshAgent(resolveSynkroBin());\n try { loadRefreshAgent(); } catch (err) {\n console.warn(` ⚠ launchd refresh agent not loaded: ${(err as Error).message}`);\n console.warn(` Plist written to ${plist} — load manually with launchctl bootstrap when ready.`);\n }\n } else {\n // Linux — make sure ~/.claude exists so the bind mount target is real.\n mkdirSync(join(homedir(), '.claude'), { recursive: true });\n }\n\n // Pull (or update) the image.\n console.log(` Pulling ${image}...`);\n const pull = spawnSync('docker', ['pull', image], { encoding: 'utf-8', stdio: 'inherit', timeout: 600_000 });\n if (pull.status !== 0) {\n throw new DockerInstallError(`docker pull ${image} failed`);\n }\n\n // Gracefully stop existing container if running (snapshot + checkpoint).\n const existing = dockerStatus();\n if (existing.running) {\n console.log(' Stopping existing container gracefully...');\n await dockerSafeStop();\n }\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Kill any leftover host-side PGLite from pre-Docker installs so the new\n // container's published port 5433 isn't shadowed by a stale daemon.\n sweepHostPglite();\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\n //\n // SECURITY: every -p below MUST keep the `127.0.0.1:` host-bind prefix. The\n // container's /api/local/* REST endpoints (snapshot, telemetry, hook-config)\n // are unauthenticated — the localhost-only bind IS the access boundary.\n // Never drop the prefix or switch to 0.0.0.0 / --network host: that would\n // expose an unauthenticated control surface to the LAN.\n const credsDir = claudeCredsHostDir();\n const args = [\n 'run',\n '--detach',\n '--name', CONTAINER_NAME,\n '--restart', 'unless-stopped',\n '-p', `127.0.0.1:${HOST_MCP_PORT}:8931`,\n '-p', `127.0.0.1:${HOST_GRADER_PORT}:8929`,\n '-p', `127.0.0.1:${HOST_CWE_PORT}:8930`,\n '-p', `127.0.0.1:${HOST_PGLITE_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${BACKUP_DIR}:/data/backups`,\n // The whole host ~/.synkro directory, read-only. The container copies\n // .mcp-jwt and credentials.json out of it at boot. A directory mount\n // sidesteps Docker Desktop for macOS's unreliable single-file bind mounts\n // (which previously left a dangling symlink that blocked container start).\n '-v', `${SYNKRO_DIR}:/data/synkro-host:ro`,\n '-v', `${credsDir}:/home/synkro/.claude:rw`,\n '-v', `${join(homedir(), '.claude')}:/data/claude-host:ro`,\n '-v', `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,\n // Cursor creds — mounted RW so the in-container refresher can rotate the\n // access token in place. Only mounted when the install includes Cursor.\n ...(cursorWorkers > 0\n ? ['-v', `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`]\n : []),\n '-e', `WORKERS_PER_POOL=${totalWorkers}`,\n '-e', `CLAUDE_WORKERS=${claudeWorkers}`,\n '-e', `CURSOR_WORKERS=${cursorWorkers}`,\n // Pass through the batch-size lever if the operator set it. Defaults\n // inside the container to 5; clamped to [1, 20] by synkro-server.ts.\n ...(process.env.SYNKRO_MAX_BATCH_SIZE\n ? ['-e', `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`]\n : []),\n // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.\n ...(process.env.SYNKRO_CURSOR_MODEL\n ? ['-e', `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`]\n : []),\n // Connected repo — the server seeds the local app + ruleset named after it.\n ...(opts.connectedRepo\n ? ['-e', `SYNKRO_CONNECTED_REPO=${opts.connectedRepo}`]\n : []),\n // Real account identity (from config.env via process.env) — telemetry is\n // stamped with these instead of a `local-user` placeholder, so the data is\n // correctly attributed from the first graded command.\n ...(process.env.SYNKRO_ORG_ID\n ? ['-e', `SYNKRO_ORG_ID=${process.env.SYNKRO_ORG_ID}`]\n : []),\n ...(process.env.SYNKRO_USER_ID\n ? ['-e', `SYNKRO_USER_ID=${process.env.SYNKRO_USER_ID}`]\n : []),\n ...(process.env.SYNKRO_EMAIL\n ? ['-e', `SYNKRO_EMAIL=${process.env.SYNKRO_EMAIL}`]\n : []),\n image,\n ];\n const run = spawnSync('docker', args, { encoding: 'utf-8', stdio: 'inherit', timeout: 60_000 });\n if (run.status !== 0) {\n throw new DockerInstallError(`docker run failed (image ${image})`);\n }\n\n return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };\n}\n\n/**\n * Poll the container's MCP /healthz until it returns 200, or fail after the\n * timeout. Used by install to give a single clean ready signal instead of the\n * multi-port wait the bare-host install has to do.\n */\nexport async function waitForContainerReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_MCP_PORT}/health`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) return true;\n } catch { /* keep retrying */ }\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\nexport async function waitForWorkersReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_GRADER_PORT}/healthz`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) {\n const data = await r.json() as { workers?: number; healthy?: number };\n if ((data.healthy || 0) > 0) return true;\n }\n } catch {}\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\n/**\n * Remove the container (after it's already stopped). Does NOT stop gracefully —\n * callers must use dockerSafeStop() first if the container is running.\n */\nexport function dockerRemove(): void {\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/** @deprecated Use dockerSafeStop() + dockerRemove() instead. */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], { encoding: 'utf-8', timeout: 45_000 });\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/**\n * `synkro update` for the containerised install — pull + restart, preserve\n * all bind-mounted state.\n */\nexport async function dockerUpdate(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<void> {\n if (dockerStatus().running) {\n await dockerSafeStop();\n }\n dockerRemove();\n await dockerInstall(opts);\n}\n\n/** Show host-mapped ports + container status. */\nexport function dockerStatus(): { running: boolean; image?: string; healthz?: string } {\n const r = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const status = (r.stdout || '').trim();\n if (status !== 'running') return { running: false };\n return {\n running: true,\n image: imageTag(),\n healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`,\n };\n}\n\n/**\n * Read the worker-pool config baked into the installed container's env, so\n * `synkro update` can rebuild on a fresh image without resetting the pool.\n * Works on stopped containers too. Returns null when no container exists.\n */\nexport function readContainerConfig(): {\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} | null {\n const r = spawnSync('docker', ['inspect', '--format', '{{json .Config.Env}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0 || !r.stdout) return null;\n let env: unknown;\n try { env = JSON.parse(r.stdout.trim()); } catch { return null; }\n if (!Array.isArray(env)) return null;\n const get = (k: string): string | undefined => {\n const hit = env.find((e): e is string => typeof e === 'string' && e.startsWith(k + '='));\n return hit ? hit.slice(k.length + 1) : undefined;\n };\n const num = (s: string | undefined): number | undefined => {\n if (s === undefined) return undefined;\n const n = parseInt(s, 10);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n claudeWorkers: num(get('CLAUDE_WORKERS')),\n cursorWorkers: num(get('CURSOR_WORKERS')),\n connectedRepo: get('SYNKRO_CONNECTED_REPO') || undefined,\n };\n}\n\n// ─── Safe lifecycle commands ────────────────────────────────────────────────\n\nconst BACKUP_DIR = join(SYNKRO_DIR, 'pgdata-backups');\n\n/**\n * Graceful stop: snapshot → docker stop (30s for CHECKPOINT+flush) → verify.\n * Never force-kills. Returns details about what happened.\n */\nexport async function dockerSafeStop(): Promise<{\n ok: boolean;\n snapshot?: { ok: boolean; error?: string };\n exitCode?: number;\n pgdataCheck?: { healthy: boolean; details: string };\n}> {\n const status = dockerStatus();\n if (!status.running) {\n console.log(' Container is not running.');\n return { ok: true, pgdataCheck: checkPgdata() };\n }\n\n // 1. Request a pre-shutdown snapshot via the REST endpoint\n console.log(' Requesting data snapshot before shutdown...');\n let snapshot: { ok: boolean; error?: string } = { ok: false, error: 'not attempted' };\n try {\n const resp = await fetch(`http://127.0.0.1:${HOST_MCP_PORT}/api/local/snapshot`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reason: 'cli-stop' }),\n signal: AbortSignal.timeout(20_000),\n });\n if (resp.ok) {\n snapshot = { ok: true };\n console.log(' ✓ Snapshot saved.');\n } else {\n snapshot = { ok: false, error: `HTTP ${resp.status}` };\n console.warn(` ⚠ Snapshot request failed (HTTP ${resp.status}). Proceeding with stop.`);\n }\n } catch (e) {\n snapshot = { ok: false, error: String(e).slice(0, 100) };\n console.warn(` ⚠ Snapshot request failed: ${snapshot.error}. Proceeding with stop.`);\n }\n\n // 2. docker stop with 30s grace period (entrypoint trap does CHECKPOINT + final snapshot)\n console.log(' Stopping container (30s grace for CHECKPOINT + WAL flush)...');\n const stop = spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 45_000,\n });\n\n // 3. Verify clean exit\n const inspect = spawnSync('docker', ['inspect', '--format', '{{.State.ExitCode}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const exitCode = parseInt((inspect.stdout || '').trim(), 10);\n if (exitCode === 0) {\n console.log(' ✓ Container stopped cleanly (exit 0).');\n } else {\n console.warn(` ⚠ Container exited with code ${exitCode}.`);\n }\n\n // 4. Check pgdata health on host\n const pgCheck = checkPgdata();\n if (pgCheck.healthy) {\n console.log(` ✓ pgdata looks healthy: ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata check: ${pgCheck.details}`);\n }\n\n return { ok: stop.status === 0, snapshot, exitCode, pgdataCheck: pgCheck };\n}\n\n/**\n * Start the container (must already exist from a prior install).\n * Checks pgdata integrity, starts container, waits for healthcheck.\n */\nexport async function dockerSafeStart(): Promise<{\n ok: boolean;\n pgdataState: string;\n error?: string;\n}> {\n // Check if already running\n const status = dockerStatus();\n if (status.running) {\n console.log(' Container is already running.');\n return { ok: true, pgdataState: 'running' };\n }\n\n // Check if container exists (stopped)\n const exists = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (exists.status !== 0) {\n return { ok: false, pgdataState: 'no_container', error: 'No synkro-server container found. Run `synkro install` first.' };\n }\n\n // Check pgdata state\n const pgCheck = checkPgdata();\n if (existsSync(PGDATA_PATH) && readdirSync(PGDATA_PATH).length > 0) {\n if (pgCheck.healthy) {\n console.log(` pgdata: existing data found — ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata: ${pgCheck.details}`);\n console.log(' Starting anyway — entrypoint will attempt recovery from snapshots if needed.');\n }\n } else {\n console.log(' pgdata: no existing data — fresh start.');\n mkdirSync(PGDATA_PATH, { recursive: true });\n }\n\n // Start the container\n console.log(' Starting container...');\n const start = spawnSync('docker', ['start', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 30_000,\n });\n if (start.status !== 0) {\n return { ok: false, pgdataState: 'start_failed', error: `docker start failed: ${(start.stderr || '').slice(0, 200)}` };\n }\n\n // Wait for healthcheck\n console.log(' Waiting for server to become healthy...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ Server is healthy and ready.');\n return { ok: true, pgdataState: pgCheck.healthy ? 'existing' : 'recovered' };\n } else {\n return { ok: false, pgdataState: 'unhealthy', error: 'Server did not become healthy within 60s. Check: docker logs synkro-server' };\n }\n}\n\n/**\n * Restart: full safe stop, then safe start. Sequential — never overlapping.\n */\nexport async function dockerSafeRestart(): Promise<{ ok: boolean; stop: Awaited<ReturnType<typeof dockerSafeStop>>; start: Awaited<ReturnType<typeof dockerSafeStart>> }> {\n console.log(' === Stop ===');\n const stopResult = await dockerSafeStop();\n if (!stopResult.ok) {\n console.error(' Stop failed. Aborting restart.');\n return { ok: false, stop: stopResult, start: { ok: false, pgdataState: 'not_started', error: 'stop failed' } };\n }\n console.log('\\n === Start ===');\n const startResult = await dockerSafeStart();\n return { ok: startResult.ok, stop: stopResult, start: startResult };\n}\n\nfunction checkPgdata(): { healthy: boolean; details: string } {\n if (!existsSync(PGDATA_PATH)) return { healthy: false, details: 'pgdata directory does not exist' };\n const entries = readdirSync(PGDATA_PATH);\n if (entries.length === 0) return { healthy: true, details: 'empty (fresh start)' };\n const hasPidFile = entries.includes('postmaster.pid');\n const hasWalDir = entries.includes('pg_wal');\n const hasPgControl = entries.includes('global') || entries.includes('pg_control');\n if (hasPidFile) return { healthy: false, details: 'stale postmaster.pid present (unclean shutdown)' };\n if (!hasWalDir) return { healthy: false, details: 'pg_wal directory missing' };\n if (!hasPgControl) return { healthy: false, details: 'pg_control/global directory missing' };\n return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };\n}\n","/**\n * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Ensure user is logged in (has JWT + user_id + org_id in ~/.synkro/credentials.json).\n * 2. Check if GitHub is connected via WorkOS Pipes.\n * 3. If not, get Pipes OAuth URL and open browser for the user to authorize.\n * 4. Poll until GitHub connection is established.\n * 5. Run `claude setup-token` to get Claude Code OAuth token.\n * 6. List accessible repos via GitHub API (using Pipes token).\n * 7. Interactive multi-select.\n * 8. Push secrets (SYNKRO_API_KEY + CLAUDE_CODE_OAUTH_TOKEN) to each repo.\n * 9. Write .github/workflows/synkro.yml to the local repo if cwd is one.\n */\nimport { createInterface } from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport { execSync, spawn as nodeSpawn } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken, getUserInfo } from '../auth/stub.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfig(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nasync function prompt(rl: ReturnType<typeof createInterface>, q: string, opts: { silent?: boolean } = {}): Promise<string> {\n if (opts.silent) {\n process.stdout.write(q);\n const wasRaw = (process.stdin as any).isRaw;\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(true);\n return await new Promise<string>((resolve) => {\n let chunk = '';\n const onData = (data: Buffer) => {\n const s = data.toString('utf-8');\n if (s === '\\r' || s === '\\n' || s === '\\r\\n') {\n process.stdin.removeListener('data', onData);\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(wasRaw ?? false);\n process.stdout.write('\\n');\n resolve(chunk);\n return;\n }\n if (s === '\u0003') process.exit(130);\n if (s === '' || s === '\\b') { chunk = chunk.slice(0, -1); return; }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n switch (os) {\n case 'darwin': bin = 'open'; args = [url]; break;\n case 'win32': bin = 'cmd'; args = ['/c', 'start', '', url]; break;\n default: bin = 'xdg-open'; args = [url]; break;\n }\n execFile(bin, args, () => {});\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nfunction captureClaudeSetupToken(): Promise<string> {\n const tmpFile = join(SYNKRO_DIR, `token-capture-${Date.now()}.raw`);\n return new Promise((resolve, reject) => {\n const proc = nodeSpawn('script', ['-q', tmpFile, 'claude', 'setup-token'], {\n stdio: 'inherit',\n });\n proc.on('error', (err) => reject(new Error(`Failed to spawn claude setup-token: ${err.message}`)));\n proc.on('close', (code) => {\n let raw = '';\n try { raw = readFileSync(tmpFile, 'utf-8'); } catch (e) {\n reject(new Error(`Could not read script output file: ${(e as Error).message}`));\n return;\n }\n try { unlinkSync(tmpFile); } catch {}\n if (code !== 0) { reject(new Error(`claude setup-token exited with code ${code}`)); return; }\n // Grab yellow-colored text segments (token is rendered in RGB 255,193,7)\n const yellowRe = /\\x1B\\[38;2;255;193;7m([^\\x1B]*)/g;\n let yellow = '';\n let m: RegExpExecArray | null;\n while ((m = yellowRe.exec(raw)) !== null) yellow += m[1];\n const token = yellow.replace(/\\s/g, '').match(/sk-ant-oat01-[A-Za-z0-9_-]+/);\n if (!token) { reject(new Error(`Could not find token in claude setup-token output (file=${raw.length}b, yellow=${yellow.length}b)`)); return; }\n resolve(token[0]);\n });\n });\n}\n\nasync function apiCall<T = any>(gatewayUrl: string, jwt: string, path: string, opts: RequestInit = {}): Promise<T> {\n const resp = await fetch(`${gatewayUrl}${path}`, {\n ...opts,\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n ...(opts.headers || {}),\n },\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`API ${resp.status}: ${text.slice(0, 200)}`);\n }\n return resp.json() as Promise<T>;\n}\n\n/**\n * Connect GitHub via WorkOS Pipes OAuth. Returns the token if connected, null if user\n * declines or times out. Exported so `install` can embed this in its flow.\n */\nexport async function connectGitHub(gatewayUrl: string, jwt: string, opts: { silent?: boolean } = {}): Promise<string | null> {\n // Check if already connected\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log(' ✓ GitHub already connected via Synkro.');\n return result.token;\n }\n } catch {}\n\n // Get Pipes OAuth URL and open browser\n if (!opts.silent) console.log(' Opening browser to authorize GitHub...');\n try {\n const authResp = await apiCall<{ url: string }>(\n gatewayUrl, jwt, '/api/pipes-widget/authorize/github', { method: 'POST', body: '{}' },\n );\n openBrowser(authResp.url);\n if (!opts.silent) console.log(' Waiting for authorization...');\n } catch (err) {\n if (!opts.silent) console.error(` Failed to start GitHub authorization: ${(err as Error).message}`);\n return null;\n }\n\n // Poll until connected (max 2 minutes)\n const deadline = Date.now() + 120_000;\n while (Date.now() < deadline) {\n await sleep(2000);\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log('\\n ✓ GitHub connected!');\n return result.token;\n }\n } catch {}\n if (!opts.silent) process.stdout.write('.');\n }\n if (!opts.silent) console.error('\\n Timed out waiting for GitHub authorization.');\n return null;\n}\n\nexport interface SetupGithubOptions {\n nonInteractive?: boolean;\n githubToken?: string;\n skipClaudeToken?: boolean;\n}\n\nexport async function setupGithubCommand(opts: SetupGithubOptions = {}): Promise<void> {\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro-cli login` first.');\n process.exit(1);\n }\n const config = readConfig();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const jwt = getAccessToken();\n if (!jwt) {\n console.error('Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.');\n process.exit(1);\n }\n\n // ── 1. Mint CI API key ──────────────────────────────────────────────\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const minted = await apiCall<{ api_key: string; expires_at: string }>(\n gatewayUrl, jwt, '/api/v1/cli/ci-api-key', { method: 'POST', body: '{}' },\n );\n synkroCiApiKey = minted.api_key;\n console.log(` ✓ Issued CI key (${synkroCiApiKey.slice(0, 18)}…), expires ${minted.expires_at.slice(0, 10)}`);\n } catch (err) {\n console.error(`Failed to mint CI API key: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // ── 2. Get GitHub token via WorkOS Pipes ────────────────────────────\n let ghToken: string;\n\n if (opts.githubToken) {\n ghToken = opts.githubToken;\n } else if (opts.nonInteractive) {\n // In non-interactive mode (CI), try Pipes first, fall back to gh CLI\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n ghToken = result.token;\n } else {\n throw new Error('not connected');\n }\n } catch {\n try {\n ghToken = execSync('gh auth token', { encoding: 'utf-8', timeout: 5000 }).trim();\n } catch {\n console.error('GitHub not connected. Run `synkro-cli setup-github` interactively to connect.');\n return;\n }\n }\n } else {\n // Interactive mode — use Pipes OAuth\n console.log('\\nConnecting to GitHub...');\n const token = await connectGitHub(gatewayUrl, jwt);\n if (!token) {\n console.error('GitHub connection failed. Try again.');\n process.exit(1);\n }\n ghToken = token;\n console.log();\n }\n\n // ── 3. Claude Code OAuth token ──────────────────────────────────────\n let claudeToken: string | undefined;\n if (!opts.skipClaudeToken) {\n console.log('Generating Claude Code OAuth token...');\n console.log(' A browser window will open — authorize with your Claude account.\\n');\n try {\n claudeToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(`Failed to get Claude token: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token received from `claude setup-token`. Expected sk-ant-oat01-...');\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n console.log(' Validating token...');\n try {\n const validateResult = execSync(\n 'claude --print --output-format json \"say ok\"',\n { env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken }, encoding: 'utf-8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] },\n );\n const result = JSON.parse(validateResult);\n if (result.is_error) throw new Error(result.result || 'auth failed');\n console.log(' ✓ Token validated.\\n');\n } catch (err) {\n console.error(`Token validation failed: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n }\n\n // ── 4. Select repos ─────────────────────────────────────────────────\n let selected: Array<{ owner: string; repo: string; full_name: string }>;\n if (opts.nonInteractive) {\n let currentFullName: string | null = null;\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const m = remoteUrl.match(/(?:github\\.com)[:/](.+?)(?:\\.git)?$/);\n if (m) currentFullName = m[1];\n } catch {}\n if (!currentFullName) {\n console.warn(' ⚠ Not in a GitHub repo. Skipping PR scan setup.');\n return;\n }\n const [owner, repo] = currentFullName.split('/');\n selected = [{ owner, repo, full_name: currentFullName }];\n console.log(` Auto-selected repo: ${currentFullName}`);\n } else {\n console.log('Fetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken! });\n if (repos.length === 0) {\n console.error('No accessible repos found. Check your GitHub permissions.');\n process.exit(1);\n }\n console.log(`\\nFound ${repos.length} accessible repo(s):\\n`);\n repos.slice(0, 100).forEach((r, i) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n const rl2 = createInterface({ input, output });\n const selectionRaw = await prompt(rl2, 'Select repos to enable (comma-separated numbers, e.g. 1,3,5): ');\n const selectedIdx = selectionRaw\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl2.close();\n process.exit(1);\n }\n selected = selectedIdx.map((i) => repos[i]);\n console.log(`\\nWill push secrets to ${selected.length} repo(s):`);\n for (const r of selected) console.log(` • ${r.full_name}`);\n console.log();\n const confirm = (await prompt(rl2, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl2.close();\n process.exit(0);\n }\n rl2.close();\n }\n\n // ── 5. Push secrets ─────────────────────────────────────────────────\n console.log();\n for (const r of selected) {\n process.stdout.write(`Pushing secrets to ${r.full_name}... `);\n try {\n await pushSecretsToRepo(\n { token: ghToken! },\n r.owner,\n r.repo,\n {\n claudeCodeOauthToken: claudeToken,\n synkroApiKey: synkroCiApiKey,\n },\n );\n console.log('✓');\n } catch (err) {\n console.log(`✗ (${(err as Error).message})`);\n }\n }\n\n // ── 6. Write workflow file ──────────────────────────────────────────\n console.log();\n const gitRoot = findGitRoot(process.cwd());\n if (gitRoot) {\n const written = writeWorkflowFile(gitRoot);\n if (written) {\n console.log(`Wrote workflow: ${written}`);\n console.log('Commit and push it to enable PR scanning.');\n }\n } else {\n console.log('Not in a git repo. To enable scanning, add this file to your repo:');\n console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);\n console.log(` Content: run \\`synkro-cli setup-github\\` from inside a repo to write it automatically`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (Claude Code, Cursor), drops hook scripts in\n * ~/.synkro/hooks/, writes settings.json hook entries, fetches the latest\n * Edit/Write prompt from the gateway and inlines it into settings, writes\n * config.env. Triggers `synkro login` if not already authed.\n */\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks, uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks, uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig, uninstallMcpConfig, uninstallCursorMcpConfig } from '../installer/mcpConfig.js';\nimport { resolveSkillPaths } from '../installer/skillParser.js';\nimport { SYNKRO_COMMON_SCRIPT } from '../installer/hookScripts.js';\nimport { SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS, CURSOR_EDIT_CAPTURE_TS, CURSOR_AGENT_CAPTURE_TS, INSTALL_SCAN_TS } from '../installer/hookScriptsTs.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo, ensureValidToken } from '../auth/stub.js';\nimport { promptRepoConnection } from './repoConnect.js';\nimport { setApiBaseUrl, listProjects } from '../api/projects.js';\n// connectGitHub import removed — GitHub PR scanning is hidden; the install no\n// longer connects GitHub. setupGithub.ts is kept for when it's re-enabled.\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { dockerInstall, waitForContainerReady, waitForWorkersReady, splitWorkers, type WorkerProvider } from '../local-cc/dockerInstall.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: string;\ndeclare const __INSTALL_EXTRACT_CORE_SRC__: string;\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst HOOKS_DIR = join(SYNKRO_DIR, 'hooks');\nconst BIN_DIR = join(SYNKRO_DIR, 'bin');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nconst MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline';\n\nconst HOME = homedir();\nconst TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');\nconst PORT = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\nconst URL = \\`http://127.0.0.1:\\${PORT}\\`;\n\nlet token = '';\ntry { token = readFileSync(TOKEN_PATH, 'utf-8').trim(); } catch {}\n\nconst rl = createInterface({ input: process.stdin, terminal: false });\n\nrl.on('line', async (line) => {\n if (!line.trim()) return;\n let msg;\n try { msg = JSON.parse(line); } catch { return; }\n if (!msg.id && msg.method?.startsWith('notifications/')) return;\n\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': \\`Bearer \\${token}\\`,\n },\n body: line,\n signal: AbortSignal.timeout(30000),\n });\n if (resp.status === 204) return;\n const body = await resp.text();\n process.stdout.write(body + '\\\\n');\n } catch (err) {\n if (msg.id != null) {\n process.stdout.write(JSON.stringify({\n jsonrpc: '2.0',\n id: msg.id,\n error: { code: -32603, message: 'MCP proxy: HTTP server unreachable' },\n }) + '\\\\n');\n }\n }\n});\n`;\n\ninterface InstallOptions {\n gatewayUrl?: string; // override default\n apiKey?: string; // skip prompting if provided (for tests/CI)\n skipAuth?: boolean;\n noMcp?: boolean; // skip registering the guardrails MCP server\n force?: boolean; // bypass the \"already installed\" short-circuit\n linkRepo?: boolean; // auto-link current repo (non-interactive)\n cursorApiKey?: string; // Cursor API key for the cursor grader pool\n}\n\n// Accept a gateway URL only if it's a plain http(s) URL. The published CLI is\n// invoked from arbitrary cwds where a developer's monorepo .env / op://\n// reference / direnv export may have poisoned SYNKRO_GATEWAY_URL with a value\n// the CLI can't actually call (e.g. \"op://dev/synkro/gateway-url\"). Silently\n// ignoring those falls through to the prod default so customers never have to\n// wrestle with shell hygiene before `synkro install` works.\nfunction sanitizeGatewayCandidate(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n return /^https?:\\/\\//.test(raw) ? raw : undefined;\n}\n\nexport function parseArgs(argv: string[]): InstallOptions {\n const opts: InstallOptions = {};\n for (const a of argv) {\n if (a.startsWith('--api-key=')) opts.apiKey = a.slice('--api-key='.length);\n else if (a.startsWith('--gateway=')) opts.gatewayUrl = a.slice('--gateway='.length);\n else if (a.startsWith('--cursor-api-key=')) opts.cursorApiKey = a.slice('--cursor-api-key='.length);\n else if (a === '--skip-auth') opts.skipAuth = true;\n else if (a === '--no-mcp') opts.noMcp = true;\n else if (a === '--force' || a === '-f') opts.force = true;\n else if (a === '--link-repo') opts.linkRepo = true;\n }\n if (!opts.gatewayUrl) {\n const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);\n if (fromEnv) opts.gatewayUrl = fromEnv;\n }\n // NOTE: we deliberately do NOT pick up SYNKRO_API_KEY from process.env. The\n // root .env / global env may contain a stale or unrelated SYNKRO_API_KEY.\n // The legitimate sources are: --api-key flag, or fresh OAuth via authenticate().\n return opts;\n}\n\n// Ask the user which detected agents they want guardrails installed for.\n// If only one is detected, no prompt — return as-is. If multiple, show a\n// picker that accepts: \"1\" (first), \"2\" (second), \"3\"/\"both\"/\"all\"/empty\n// (everything). Anything else re-prompts.\nasync function promptAgentSelection<T extends { kind: string; name: string }>(\n detected: T[],\n): Promise<T[]> {\n if (detected.length <= 1) return detected;\n\n console.log('Multiple coding agents detected. Which ones do you want Synkro guardrails installed for?');\n detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));\n console.log(` ${detected.length + 1}. Both / all (default)`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (): Promise<T[]> => new Promise((resolve) => {\n rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {\n const t = answer.trim().toLowerCase();\n if (t === '' || t === String(detected.length + 1) || t === 'both' || t === 'all') {\n rl.close();\n return resolve(detected);\n }\n const n = parseInt(t, 10);\n if (Number.isInteger(n) && n >= 1 && n <= detected.length) {\n rl.close();\n return resolve([detected[n - 1]]);\n }\n console.log('Invalid choice. Try again.');\n resolve(ask());\n });\n });\n return ask();\n}\n\n// Collect the Cursor API key when the install includes a Cursor worker pool.\n// Sources, in order: --cursor-api-key flag, SYNKRO_CURSOR_API_KEY env, then\n// an interactive paste prompt. An already-configured key is kept only if\n// Cursor still accepts it.\nasync function promptCursorApiKey(opts: InstallOptions): Promise<void> {\n const { cursorApiKeyConfigured, writeCursorApiKey, validateCursorApiKey } = await import('../local-cc/macKeychain.js');\n if (cursorApiKeyConfigured()) {\n // A key file exists — confirm Cursor still accepts it before trusting it.\n // Only a definitive rejection (false) re-prompts; a network blip (null)\n // leaves the working key alone.\n const valid = await validateCursorApiKey();\n if (valid !== false) {\n console.log(' ✓ Cursor API key validated.');\n return;\n }\n console.warn(' ⚠ The saved Cursor API key was rejected by Cursor (revoked or expired).');\n console.warn(' Paste a fresh key below, or press Enter to skip (Cursor workers stay idle).');\n // fall through to re-source / re-prompt the key\n }\n\n const provided = (opts.cursorApiKey || process.env.SYNKRO_CURSOR_API_KEY || '').trim();\n if (provided) {\n writeCursorApiKey(provided);\n console.log(' ✓ Cursor API key saved to ~/.synkro/cursor-creds/api-key');\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const key = await new Promise<string>((resolve) => {\n rl.question(\n 'Cursor grading needs a Cursor API key (cursor.com → Settings → API Keys).\\n' +\n 'Paste it now, or press Enter to skip (Cursor workers stay idle until set): ',\n (answer) => { rl.close(); resolve(answer.trim()); },\n );\n });\n if (key) {\n writeCursorApiKey(key);\n console.log(' ✓ Cursor API key saved.');\n } else {\n console.log(' ⚠ Skipped — Cursor workers will be idle. Re-run install or pass --cursor-api-key=… later.');\n }\n}\n\n// Where grading runs: local (on-device container worker pool) or byok (an LLM\n// API call with the user's own provider key). Default — and the non-TTY\n// answer — is local.\nasync function promptGradingMode(): Promise<'local' | 'byok'> {\n if (!process.stdin.isTTY) return 'local';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should grading run?\\n' +\n ' local — on this machine, via the Synkro container (default)\\n' +\n ' byok — via an LLM API using your own provider key\\n' +\n 'Choose [local] / byok: ',\n (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'byok' ? 'byok' : 'local');\n },\n );\n });\n}\n\n// Where telemetry is stored: local (the container's PGLite, on this machine)\n// or cloud (Synkro's Timescale). Default — and the non-TTY answer — is local.\nasync function promptStorageMode(): Promise<'local' | 'cloud'> {\n if (!process.stdin.isTTY) return 'local';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should telemetry be stored?\\n' +\n ' local — on this machine only (default)\\n' +\n ' cloud — sent to Synkro cloud\\n' +\n 'Choose [local] / cloud: ',\n (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'cloud' ? 'cloud' : 'local');\n },\n );\n });\n}\n\nasync function promptTranscriptConsent(): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Import and embed your Claude Code session history?\\n' +\n 'This indexes past sessions so Ask Synkro can answer questions\\n' +\n 'about your coding patterns and the dashboard shows full history. (Y/n) ',\n (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n },\n );\n });\n}\n\nconst OFFSETS_DIR = join(SYNKRO_DIR, '.transcript-offsets');\n\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n mkdirSync(OFFSETS_DIR, { recursive: true });\n mkdirSync(join(SYNKRO_DIR, 'sessions'), { recursive: true });\n}\n\nexport function writeHookScripts(): {\n bashScript: string;\n bashFollowupScript: string;\n editPrecheckScript: string;\n cwePrecheckScript: string;\n cvePrecheckScript: string;\n planJudgeScript: string;\n agentJudgeScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n transcriptSyncScript: string;\n userPromptSubmitScript: string;\n installScanScript: string;\n cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\n cursorAgentCaptureScript: string;\n} {\n // Source is inlined at build time via tsup's `define` (see tsup.config.ts).\n // Reading it from disk doesn't work after npm publish because packages/api\n // isn't shipped with the CLI tarball.\n const installExtractCorePath = join(HOOKS_DIR, 'installExtractCore.ts');\n\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.ts');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.ts');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.ts');\n const cwePrecheckScriptPath = join(HOOKS_DIR, 'cc-cwe-precheck.ts');\n const cvePrecheckScriptPath = join(HOOKS_DIR, 'cc-cve-precheck.ts');\n const planJudgeScriptPath = join(HOOKS_DIR, 'cc-plan-judge.ts');\n const agentJudgeScriptPath = join(HOOKS_DIR, 'cc-agent-judge.ts');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.ts');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.ts');\n const transcriptSyncScriptPath = join(HOOKS_DIR, 'cc-transcript-sync.ts');\n const userPromptSubmitScriptPath = join(HOOKS_DIR, 'cc-user-prompt-submit.ts');\n const commonScriptPath = join(HOOKS_DIR, '_synkro-common.ts');\n const commonBashScriptPath = join(HOOKS_DIR, '_synkro-common.sh');\n const installScanScriptPath = join(HOOKS_DIR, 'cc-install-scan.ts');\n const cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-capture.ts');\n const cursorAgentCapturePath = join(HOOKS_DIR, 'cursor-agent-capture.ts');\n const mcpStdioProxyPath = join(HOOKS_DIR, 'mcp-stdio-proxy.ts');\n\n writeFileSync(bashScriptPath, BASH_JUDGE_TS, 'utf-8');\n writeFileSync(bashFollowupScriptPath, BASH_FOLLOWUP_TS, 'utf-8');\n writeFileSync(editPrecheckScriptPath, EDIT_PRECHECK_TS, 'utf-8');\n writeFileSync(cwePrecheckScriptPath, CWE_PRECHECK_TS, 'utf-8');\n writeFileSync(cvePrecheckScriptPath, CVE_PRECHECK_TS, 'utf-8');\n writeFileSync(planJudgeScriptPath, PLAN_JUDGE_TS, 'utf-8');\n writeFileSync(agentJudgeScriptPath, AGENT_JUDGE_TS, 'utf-8');\n writeFileSync(stopSummaryScriptPath, STOP_SUMMARY_TS, 'utf-8');\n writeFileSync(sessionStartScriptPath, SESSION_START_TS, 'utf-8');\n writeFileSync(transcriptSyncScriptPath, TRANSCRIPT_SYNC_TS, 'utf-8');\n writeFileSync(userPromptSubmitScriptPath, USER_PROMPT_SUBMIT_TS, 'utf-8');\n writeFileSync(commonScriptPath, SYNKRO_COMMON_TS, 'utf-8');\n writeFileSync(commonBashScriptPath, SYNKRO_COMMON_SCRIPT, 'utf-8');\n writeFileSync(installScanScriptPath, INSTALL_SCAN_TS, 'utf-8');\n writeFileSync(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, 'utf-8');\n writeFileSync(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, 'utf-8');\n writeFileSync(cursorAgentCapturePath, CURSOR_AGENT_CAPTURE_TS, 'utf-8');\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, 'utf-8');\n writeFileSync(installExtractCorePath, __INSTALL_EXTRACT_CORE_SRC__, 'utf-8');\n\n chmodSync(bashScriptPath, 0o755);\n chmodSync(bashFollowupScriptPath, 0o755);\n chmodSync(editPrecheckScriptPath, 0o755);\n chmodSync(cwePrecheckScriptPath, 0o755);\n chmodSync(cvePrecheckScriptPath, 0o755);\n chmodSync(planJudgeScriptPath, 0o755);\n chmodSync(agentJudgeScriptPath, 0o755);\n chmodSync(stopSummaryScriptPath, 0o755);\n chmodSync(sessionStartScriptPath, 0o755);\n chmodSync(transcriptSyncScriptPath, 0o755);\n chmodSync(userPromptSubmitScriptPath, 0o755);\n chmodSync(commonScriptPath, 0o755);\n chmodSync(commonBashScriptPath, 0o755);\n chmodSync(installScanScriptPath, 0o755);\n chmodSync(cursorBashJudgePath, 0o755);\n chmodSync(cursorEditCapturePath, 0o755);\n chmodSync(cursorAgentCapturePath, 0o755);\n chmodSync(mcpStdioProxyPath, 0o755);\n chmodSync(installExtractCorePath, 0o755);\n\n return {\n bashScript: bashScriptPath,\n bashFollowupScript: bashFollowupScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n cwePrecheckScript: cwePrecheckScriptPath,\n cvePrecheckScript: cvePrecheckScriptPath,\n planJudgeScript: planJudgeScriptPath,\n agentJudgeScript: agentJudgeScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\n transcriptSyncScript: transcriptSyncScriptPath,\n userPromptSubmitScript: userPromptSubmitScriptPath,\n installScanScript: installScanScriptPath,\n cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\n cursorAgentCaptureScript: cursorAgentCapturePath,\n };\n}\n\n// Sanitize values before writing into config.env — the file is sourced as a\n// shell script, so any unquoted shell metacharacter in a value is dangerous\n// (newlines smuggle new assignments; (){}#`$ etc. let an attacker run code\n// on `source config.env`). Two defenses:\n// 1. Strip non-printable ASCII (drops newlines, tabs, control chars)\n// 2. Wrap the result in single-quotes — single-quoted shell strings are\n// entirely literal, so anything inside is safe regardless of contents.\n// Internal single-quotes are escaped using the standard '\\'' trick.\nfunction sanitizeConfigValue(raw: string | undefined, maxLen = 256): string {\n if (!raw) return '';\n return raw\n .replace(/[^\\x20-\\x7E]/g, '') // drop non-printable (newlines/tabs/control)\n .slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n // Escape any single quote in `value` for inclusion inside a single-quoted\n // shell string: 'foo'\"'\"'bar' renders as foo'bar.\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Resolve the absolute path to the synkro bundle (dist/bootstrap.js) so hook\n// scripts can invoke `node \"$SYNKRO_CLI_BIN\" grade ...` without relying on\n// the user's shell PATH. Returning a single path (vs a command string) keeps\n// the hook invocation a single quoted arg — no shell-injection surface.\n//\n// process.argv[1] is the script the user executed: with the pnpm/npm shim\n// it's the absolute path to dist/bootstrap.js. With `just synkro install`\n// it's also the absolute bundle path (the just recipe runs `node bootstrap.js`).\nfunction resolveSynkroBundle(): string | null {\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) return scriptPath;\n return null;\n}\n\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string; inference?: string; synkroBin?: string | null; transcriptConsent?: boolean; localInference?: boolean; deploymentMode?: 'bare-host' | 'docker'; gradingMode?: 'local' | 'byok'; storageMode?: 'local' | 'cloud' }): void {\n const credsPath = join(SYNKRO_DIR, 'credentials.json');\n const safeGateway = sanitizeConfigValue(opts.gatewayUrl);\n const safeUserId = sanitizeConfigValue(opts.userId);\n const safeOrgId = sanitizeConfigValue(opts.orgId);\n const safeEmail = sanitizeConfigValue(opts.email);\n const safeTier = sanitizeConfigValue(opts.tier ?? 'pro', 32);\n const safeInference = sanitizeConfigValue(opts.inference ?? 'fast', 16);\n // Allow up to 1KB for the bin command since it may be `node /long/path/to/bootstrap.js`.\n const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? '', 1024);\n const lines = [\n '# Synkro CLI config (managed by synkro install)',\n '# JWT auth — the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime',\n '# and send Authorization: Bearer <access_token> on every gateway call.',\n `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,\n `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,\n `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,\n `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,\n `SYNKRO_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\n if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);\n if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);\n if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);\n if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);\n if (opts.transcriptConsent !== undefined) {\n lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? 'yes' : 'no')}`);\n }\n lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? 'yes' : 'no')}`);\n // Persisted so hooks see the same value without shell-sourced env.\n const safeMode = sanitizeConfigValue(opts.deploymentMode ?? 'docker', 16);\n lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);\n // User-owned data axes — where grading runs and where telemetry is stored.\n lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? 'local', 16))}`);\n lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? 'local', 16))}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Resolve the deployment mode. Live env var wins (lets ops experiment without\n * mutating the persisted config), then the persisted SYNKRO_DEPLOYMENT_MODE\n * from ~/.synkro/config.env, then defaults to docker.\n *\n * Returns the lowercased string for direct comparison.\n */\nfunction resolveDeploymentMode(): 'docker' | 'bare-host' {\n const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();\n if (envOverride === 'bare-host' || envOverride === 'docker') return envOverride;\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n const val = m?.[1]?.toLowerCase();\n if (val === 'bare-host' || val === 'docker') return val;\n }\n } catch { /* fall through to default */ }\n return 'docker';\n}\n\nfunction collectLocalMetadata(includeClaudeCode = true): Record<string, unknown> {\n const meta: Record<string, unknown> = { platform: process.platform };\n try {\n meta.display_name = execSync('git config user.name', { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {}\n try {\n const remote = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remote.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const m = sshMatch || httpMatch;\n if (m) meta.active_repo = m[1];\n } catch {}\n\n if (!includeClaudeCode) return meta;\n\n try {\n meta.cc_version = execSync('claude --version', { encoding: 'utf-8', timeout: 5000 }).trim().split('\\n')[0];\n } catch {}\n\n const claudeDir = join(homedir(), '.claude');\n\n try {\n const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));\n const plugins = Object.keys(settings.enabledPlugins ?? {}).filter(k => settings.enabledPlugins[k]);\n if (plugins.length) meta.enabled_plugins = plugins;\n if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;\n } catch {}\n\n try {\n const mcpCache = JSON.parse(readFileSync(join(claudeDir, 'mcp-needs-auth-cache.json'), 'utf-8'));\n const mcpNames = Object.keys(mcpCache);\n if (mcpNames.length) meta.mcp_servers = mcpNames;\n } catch {}\n\n try {\n const mcpList = execSync('claude mcp list 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });\n const connected = mcpList.split('\\n')\n .filter(l => l.includes('Connected'))\n .map(l => l.split(':')[0].trim())\n .filter(Boolean);\n if (connected.length) meta.mcp_servers_connected = connected;\n } catch {}\n\n try {\n const sessionsDir = join(claudeDir, 'sessions');\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json')).slice(-5);\n for (const f of files) {\n const s = JSON.parse(readFileSync(join(sessionsDir, f), 'utf-8'));\n if (s.version) { meta.cc_version = meta.cc_version || s.version; break; }\n }\n } catch {}\n\n return meta;\n}\n\nasync function fetchUserProfile(gatewayUrl: string, token: string, hasClaudeCode = true): Promise<{ tier: string; inference: string; localInference: boolean; captureDepth: string }> {\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n headers: { 'Authorization': `Bearer ${token}` },\n });\n if (!resp.ok) return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n const data = await resp.json() as { fast_inference?: boolean; plan_tier?: string; local_inference?: boolean; capture_depth?: string };\n\n const meta = collectLocalMetadata(hasClaudeCode);\n fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(meta),\n }).catch(() => {});\n\n return {\n tier: data.plan_tier ?? 'pro',\n inference: data.fast_inference ? 'fast' : 'standard',\n localInference: !!data.local_inference,\n captureDepth: data.capture_depth ?? 'full',\n };\n } catch {\n return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n }\n}\n\n// JWT-only auth — no API key minting. The hook scripts read the JWT from\n// ~/.synkro/credentials.json and send it as Authorization: Bearer <jwt>.\n// Auth middleware on the gateway resolves user/org from JWT claims via JWKS.\n\n// The CLI ships the user's WorkOS Bearer JWT to the gateway URL. If a\n// hostile actor sets --gateway=http://evil.example.com on a user's machine,\n// the JWT lands at the attacker's server. Allow only:\n// - https://*.synkro.sh and synkro.sh apex\n// - http(s)://localhost or 127.0.0.1 (dev)\n// Anything else is rejected before any fetch is attempted.\nfunction assertGatewayAllowed(gatewayUrl: string): void {\n let parsed: URL;\n try { parsed = new URL(gatewayUrl); }\n catch { throw new Error(`Invalid gateway URL: ${gatewayUrl}`); }\n const proto = parsed.protocol;\n const host = parsed.hostname;\n if (proto !== 'http:' && proto !== 'https:') {\n throw new Error(`Gateway URL must be http(s); got ${proto}`);\n }\n const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';\n const isSynkro = host === 'synkro.sh' || host.endsWith('.synkro.sh');\n if (proto === 'http:' && !isLocalhost) {\n throw new Error(`Gateway URL must be HTTPS for non-localhost hosts; got ${gatewayUrl}`);\n }\n if (!isLocalhost && !isSynkro) {\n throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);\n }\n}\n\n// Detect a complete prior install: every hook script present, config.env\n// written, and CC settings.json carries our `__synkro_managed__` markers.\n// MCP registration is opt-out (--no-mcp) so we don't gate on it. Returns\n// true only when every required surface is present so we never short-\n// circuit a partial / broken install.\nfunction isAlreadyInstalled(): boolean {\n const requiredScripts = [\n join(HOOKS_DIR, 'cc-bash-judge.ts'),\n join(HOOKS_DIR, 'cc-bash-followup.ts'),\n join(HOOKS_DIR, 'cc-edit-precheck.ts'),\n join(HOOKS_DIR, 'cc-cve-precheck.ts'),\n join(HOOKS_DIR, 'cc-plan-judge.ts'),\n join(HOOKS_DIR, 'cc-stop-summary.ts'),\n join(HOOKS_DIR, 'cc-session-start.ts'),\n ];\n if (!requiredScripts.every((p) => existsSync(p))) return false;\n if (!existsSync(CONFIG_PATH)) return false;\n\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hooks = settings?.hooks;\n if (!hooks || typeof hooks !== 'object') return false;\n const hasManaged = (kind: string) =>\n Array.isArray(hooks[kind]) &&\n hooks[kind].some((entry: Record<string, unknown>) => entry?.__synkro_managed__ === true);\n if (!hasManaged('PreToolUse')) return false;\n if (!hasManaged('PostToolUse')) return false;\n if (!hasManaged('SessionEnd')) return false;\n if (!hasManaged('SessionStart')) return false;\n } catch {\n return false;\n }\n return true;\n}\n\n\nexport async function installCommand(opts: InstallOptions = {}): Promise<void> {\n // Synkro anchors all state — projects, telemetry, rulesets — to a repo\n // identity. Without a git repo there is nothing to key that state on, so a\n // repo is mandatory. This is a purely local check (`git remote get-url\n // origin`) — no GitHub login involved.\n if (!detectGitRepo()) {\n console.error('Synkro must be installed inside a git repository.');\n console.error(' `cd` into your project and re-run `synkro install`.');\n process.exit(1);\n }\n\n const gatewayUrl = opts.gatewayUrl\n || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL)\n || 'https://api.synkro.sh';\n\n // Reject hostile gateway overrides before we leak the JWT.\n try {\n assertGatewayAllowed(gatewayUrl);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n // No short-circuit. Plain `synkro install` is always a full refresh —\n // re-pulls the latest container image, re-writes hooks, re-registers the\n // MCP server. Each individual step is idempotent on its own; auth reuses\n // existing valid credentials so the user isn't bounced to the browser\n // unnecessarily.\n console.log('Synkro install starting...\\n');\n\n // 1. Auth via browser OAuth (WorkOS). Persists JWT + refresh_token to\n // ~/.synkro/credentials.json. The hook scripts authenticate against\n // the gateway with `Authorization: Bearer <access_token>` — auth\n // middleware resolves user/org from the JWT claims directly.\n if (!isAuthenticated()) {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n switch (status.phase) {\n case 'starting': console.log(' Starting local callback server...'); break;\n case 'browser-opened': console.log(` Browser opened: ${status.url}`); break;\n case 'waiting': console.log(' Waiting for browser auth to complete...'); break;\n case 'success': console.log(' ✓ Authenticated'); break;\n case 'error': console.error(` ✗ ${status.message}`); break;\n }\n });\n if (!result) {\n console.error('Authentication failed. If you are running a self-hosted dashboard, set SYNKRO_WEB_AUTH_URL to its origin.');\n process.exit(1);\n }\n }\n const token = getAccessToken();\n if (!token) {\n console.error('No access token available after auth.');\n process.exit(1);\n }\n\n // 1b. GitHub PR scanning is hidden for now — the install no longer prompts\n // for it. The code path (connectGitHub / setupGithubCommand below) is kept\n // intact, just unreachable, for when PR scanning is re-enabled.\n const ghToken: string | null = null;\n\n // 1c. Connect repos (local git or GitHub OAuth)\n setApiBaseUrl(`${gatewayUrl}/api`);\n await promptRepoConnection({ linkRepo: opts.linkRepo });\n\n // 2. Detect installed agents\n const detected = detectAgents();\n if (detected.length === 0) {\n console.error('No supported coding agents detected. Install Claude Code or Cursor first.');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of detected) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n // Let the user pick which detected agents to install hooks for. Skipped\n // automatically when only one is detected.\n const agents = await promptAgentSelection(detected);\n if (agents.length < detected.length) {\n console.log(`Installing hooks for: ${agents.map(a => a.name).join(', ')}\\n`);\n }\n\n // Two user-owned data axes — both default to local. Persisted to config.env;\n // changeable later in the dashboard under Settings.\n const gradingMode = await promptGradingMode();\n const storageMode = await promptStorageMode();\n console.log(` grading: ${gradingMode} storage: ${storageMode}\\n`);\n if (gradingMode === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.\\n');\n }\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/\\n');\n\n // Kill any stale legacy Python grader daemons so they don't keep handling\n // requests after we've switched to the local-CC channel path.\n for (const mode of ['edit', 'bash']) {\n const pidFile = join(SYNKRO_DIR, 'daemon', mode, 'daemon.pid');\n try {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n if (pid > 0) {\n process.kill(pid, 'SIGTERM');\n console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);\n }\n } catch {}\n }\n\n\n // 3b. Transcript consent — import + embed existing CC sessions.\n let transcriptConsent = true;\n if (process.stdin.isTTY) {\n transcriptConsent = await promptTranscriptConsent();\n if (transcriptConsent) {\n console.log(' ✓ Session import enabled\\n');\n } else {\n console.log(' ✗ Session import skipped\\n');\n }\n }\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n let hasCursor = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n hasClaudeCode = true;\n installCCHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.bashScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n skipTranscriptSync: !transcriptConsent,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n } else if (agent.kind === 'cursor') {\n hasCursor = true;\n installCursorHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Fetch user profile early — we need captureDepth to decide local vs cloud MCP.\n let userId: string | undefined;\n let orgId: string | undefined;\n let email: string | undefined;\n try {\n const info = getUserInfo();\n userId = info.id;\n orgId = info.org_id;\n email = info.email;\n } catch {\n // unreachable — we just authenticated above\n }\n const profile = await fetchUserProfile(gatewayUrl, token, hasClaudeCode);\n // Cloud-only when both axes are cloud: BYOK grading + cloud storage bypass\n // the container's worker pool AND its PGLite, so skip Docker entirely and\n // register the MCP against the cloud endpoint. Any other combination still\n // needs the local container (for grading and/or local storage).\n const cloudOnly = gradingMode === 'byok' && storageMode === 'cloud';\n const useLocalMcp = !cloudOnly;\n if (cloudOnly) {\n console.log('Cloud-only setup (BYOK grading + cloud storage) — skipping the local container.\\n');\n }\n\n // 5c. Register the Synkro Guardrails MCP server in ~/.claude.json.\n // Local mode: point to localhost:8931, start local server, backfill rules.\n // Cloud mode: mint long-lived JWT, point to cloud endpoint.\n if (hasClaudeCode && !opts.noMcp) {\n if (useLocalMcp) {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n let mcpJwt = '';\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n mcpJwt = minted.token;\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), mcpJwt + '\\n', { mode: 0o600 });\n } else {\n console.warn(' ⚠ Could not mint MCP token — local server will reject requests until re-installed.');\n }\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: mcpJwt, local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log();\n } catch (err) {\n console.warn(` ⚠ Local MCP setup failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry.');\n console.log();\n }\n } else {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log(` expires: ${minted.expires_at} (~1 year)`);\n console.log(' (restart any running Claude Code session for it to load)');\n console.log();\n } catch (err) {\n console.warn(` ⚠ MCP registration failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry MCP setup.');\n console.log();\n }\n }\n }\n\n // 5d. Register the Synkro Guardrails MCP server in ~/.cursor/mcp.json.\n if (hasCursor && !opts.noMcp) {\n try {\n if (useLocalMcp) {\n // Ensure JWT exists (may already be minted in 5c for Claude Code)\n const jwtPath = join(SYNKRO_DIR, '.mcp-jwt');\n if (!existsSync(jwtPath)) {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(jwtPath, minted.token + '\\n', { mode: 0o600 });\n }\n }\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: '', local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n } else {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n }\n console.log();\n } catch (err) {\n console.warn(` ⚠ Cursor MCP registration failed: ${(err as Error).message}`);\n console.log();\n }\n }\n\n // 6. Write config.env — hook scripts read SYNKRO_GATEWAY_URL and the\n // credentials path. They auth via Bearer JWT (resolved from the\n // credentials file at runtime so refreshes Just Work).\n const synkroBundle = resolveSynkroBundle();\n // Persist the deployment mode so subsequent installs / hooks see the same\n // value without depending on shell-sourced env.\n const persistedMode = resolveDeploymentMode();\n writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode, gradingMode, storageMode });\n console.log(`Wrote config to ${CONFIG_PATH}`);\n console.log(` inference: ${profile.inference} (server-side grading)`);\n if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);\n if (synkroBundle) console.log(` SYNKRO_CLI_BIN=${synkroBundle}`);\n else console.warn(' ⚠ Could not resolve synkro bundle path; hooks will fall back to PATH lookup of `synkro`.');\n\n try {\n const prompts = await fetchJudgePrompts({ gatewayUrl, jwt: token });\n console.log(` prompts: ${prompts.version} (live)`);\n } catch (err) {\n console.warn(` ⚠ Could not cache judge prompts: ${(err as Error).message}`);\n }\n\n // Write .synkro to repo root if one doesn't exist yet.\n writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode });\n console.log();\n\n // The container provides the grading worker pool and local PGLite — needed\n // for any setup except cloud-only (BYOK grading + cloud storage).\n if (useLocalMcp) {\n const { assertDockerAvailable } = await import('../local-cc/dockerInstall.js');\n try {\n assertDockerAvailable();\n } catch (err) {\n console.error(`\\n✗ ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Cursor worker pool needs a Cursor API key — collect it before starting\n // the container so the workers come up authenticated.\n if (hasCursor) {\n await promptCursorApiKey(opts);\n }\n\n console.log('Installing Synkro server container...');\n // Read .synkro for worker config: explicit counts > pool > agent detection.\n const sf = readFullSynkroFile();\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {\n claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));\n cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (claudeWorkers + cursorWorkers === 0) { claudeWorkers = 0; cursorWorkers = 8; }\n console.log(` .synkro: explicit workers — ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n } else {\n const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();\n let providers: WorkerProvider[] = [];\n if (synkroFilePool === 'cursor') {\n providers = ['cursor'];\n } else if (synkroFilePool === 'claude') {\n providers = ['claude_code'];\n } else {\n if (hasClaudeCode) providers.push('claude_code');\n if (hasCursor) providers.push('cursor');\n }\n ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));\n if (synkroFilePool !== 'auto') console.log(` .synkro: grader pool set to ${synkroFilePool}`);\n }\n console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n // Connected repo — the container names the seeded app + ruleset after it.\n const connectedRepo = detectGitRepo() || undefined;\n const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } =\n await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);\n console.log(' waiting for container to be ready...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ container ready');\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n try {\n const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpJwt}` },\n body: JSON.stringify({ capture_type: 'healthcheck', event_id: `healthcheck_${Date.now()}` }),\n signal: AbortSignal.timeout(5000),\n });\n if (ingestResp.ok) {\n console.log(' ✓ ingest endpoint verified');\n } else {\n console.warn(` ⚠ ingest endpoint returned ${ingestResp.status} — telemetry spool may not drain.`);\n console.warn(' The .mcp-jwt token may not match the container. Try: synkro uninstall && synkro install');\n }\n } catch {\n console.warn(' ⚠ ingest endpoint unreachable — telemetry spool may not drain.');\n }\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log(' ✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn(' ⚠ workers did not register within 30s — skill sync skipped');\n }\n } else {\n console.error(' ✗ container did not become healthy within 60s');\n console.error(' Run `docker logs synkro-server` to debug.');\n process.exit(1);\n }\n console.log();\n }\n\n // 7. Import + embed CC session transcripts (only if user consented and CC is installed).\n if (transcriptConsent && hasClaudeCode) {\n const repo = detectGitRepo();\n if (repo) {\n if (storageMode === 'local') {\n // Local: sync into PGLite — the container's embedding model vectors each message automatically.\n try {\n let mcpToken = '';\n try { mcpToken = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (mcpToken) {\n const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n const result = await syncTranscriptsLocal(mcpPort, mcpToken, repo);\n if (result.messages > 0) {\n console.log(` ✓ Imported ${result.sessions} sessions (${result.messages} messages) into local store.`);\n console.log(' Embeddings generated. Analyzing for rule suggestions...');\n try {\n const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: AbortSignal.timeout(90000),\n });\n if (suggestResp.ok) {\n const suggestResult = await suggestResp.json() as { suggested?: number; skippedDuplicates?: number };\n if (suggestResult.suggested && suggestResult.suggested > 0) {\n console.log(` ✓ Generated ${suggestResult.suggested} rule suggestions from your session history.`);\n console.log(' Ask Claude: \"show me suggested rules\" to review them.\\n');\n } else {\n console.log(' No rule suggestions generated yet — more session data needed.\\n');\n }\n }\n } catch {\n console.log(' Rule analysis will run automatically as more sessions are recorded.\\n');\n }\n }\n } else {\n console.warn(' ⚠ Session import skipped — container auth token not found.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Local session import failed: ${(err as Error).message}\\n`);\n }\n } else {\n // Cloud: send to Timescale via gateway — BYOK embedding model from inference config.\n try {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(` ✓ Indexed ${ingested} session insights from Claude Code history.`);\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n try {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(` ✓ Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);\n console.log(' Embeddings use your configured inference provider.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\n`);\n }\n }\n }\n }\n\n // 8. PR scan setup (secrets + workflow) — only if GitHub was connected in step 1b\n if (ghToken) {\n const { setupGithubCommand } = await import('./setupGithub.js');\n await setupGithubCommand({ nonInteractive: true, githubToken: ghToken });\n }\n\n // 9. Done\n console.log('✓ Synkro installed.');\n}\n\n/**\n * Identify the git repo `synkro install` is running in. `git` itself walks up\n * from the cwd, so this resolves correctly from any subdirectory or worktree.\n * Tries the `origin` remote (→ owner/repo), then falls back to the repo's\n * top-level directory name. Returns null only when the cwd is not a git repo\n * at all — mirrors the hook's detectRepo() so the seeded project id and the id\n * telemetry lands under always agree.\n */\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n\n if (/^\\S/.test(line) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null;\n currentArr = null;\n\n const colonIdx = line.indexOf(':');\n const key = line.slice(0, colonIdx).trim();\n const val = line.slice(colonIdx + 1).trim();\n currentKey = key;\n\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (/^ - /.test(line)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (/^ \\S/.test(line) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const colonIdx = line.indexOf(':');\n const k = line.slice(0, colonIdx).trim();\n const v = line.slice(colonIdx + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nfunction parseSynkroFileRaw(content: string): Record<string, any> {\n return content.trimStart().startsWith('{') ? JSON.parse(content) : parseSynkroYaml(content);\n}\n\nfunction writeSynkroFileIfMissing(opts: { hasClaudeCode: boolean; hasCursor: boolean; gradingMode: string }): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return;\n const fp = join(root, '.synkro');\n if (existsSync(fp)) {\n console.log(` .synkro: ${fp} (existing, respected)`);\n return;\n }\n let pool: string = 'auto';\n const harness: string[] = [];\n if (opts.hasClaudeCode) { harness.push('claude-code'); if (!opts.hasCursor) pool = 'claude'; }\n if (opts.hasCursor) { harness.push('cursor'); if (!opts.hasClaudeCode) pool = 'cursor'; }\n const mode = opts.gradingMode === 'byok' ? 'byok' : 'local';\n const yaml = [\n 'version: 1',\n '',\n 'harness:',\n ...harness.map(h => ` - ${h}`),\n '',\n 'grader:',\n ` pool: ${pool}`,\n ` mode: ${mode}`,\n '',\n 'ruleset: default',\n '',\n 'skills: []',\n '',\n 'scanning:',\n ' cwe: true',\n ' cve: true',\n '',\n ].join('\\n');\n writeFileSync(fp, yaml, 'utf-8');\n console.log(` .synkro: wrote ${fp} (pool=${pool}, mode=${mode})`);\n } catch {}\n}\n\nfunction readSynkroFilePool(): 'auto' | 'claude' | 'cursor' {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return 'auto';\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return 'auto';\n const parsed = parseSynkroFileRaw(readFileSync(fp, 'utf-8'));\n const pool = parsed?.grader?.pool;\n if (pool === 'cursor' || pool === 'claude') return pool;\n } catch {}\n return 'auto';\n}\n\ninterface SynkroFile {\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude' | 'cursor'; mode: string };\n workers: { claude?: number; cursor?: number };\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n _repoRoot: string;\n}\n\nfunction readFullSynkroFile(): SynkroFile | null {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return null;\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return null;\n const parsed = parseSynkroFileRaw(readFileSync(fp, 'utf-8'));\n const valid = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => valid.includes(h as any))\n : ['claude-code', 'cursor'];\n return {\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : 'local',\n },\n workers: {\n ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),\n ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),\n },\n ruleset: parsed.ruleset || 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string' && s.endsWith('.md')) : [],\n scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },\n _repoRoot: root,\n };\n } catch { return null; }\n}\n\nexport function reconcileHarness(): { claudeWorkers: number; cursorWorkers: number } | null {\n const sf = readFullSynkroFile();\n if (!sf) {\n console.log('No .synkro file found in repo root — skipping harness reconciliation.');\n return null;\n }\n\n const wantCC = sf.harness.includes('claude-code');\n const wantCursor = sf.harness.includes('cursor');\n console.log(`.synkro: harness=[${sf.harness.join(', ')}] pool=${sf.grader.pool} mode=${sf.grader.mode}`);\n\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/');\n\n const ccSettings = join(homedir(), '.claude', 'settings.json');\n if (wantCC) {\n installCCHooks(ccSettings, {\n bashJudgeScriptPath: scripts.bashScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(' ✓ Claude Code hooks registered');\n try {\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcpJwt) {\n installMcpConfig({ gatewayUrl: '', bearerToken: mcpJwt, local: true });\n console.log(' ✓ Claude Code MCP registered');\n }\n } catch {}\n } else {\n if (uninstallCCHooks(ccSettings)) console.log(' ✗ Claude Code hooks removed');\n if (uninstallMcpConfig()) console.log(' ✗ Claude Code MCP removed');\n }\n\n const cursorHooks = join(homedir(), '.cursor', 'hooks.json');\n if (wantCursor) {\n installCursorHooks(cursorHooks, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(' ✓ Cursor hooks registered');\n try {\n installCursorMcpConfig({ gatewayUrl: '', bearerToken: '', local: true });\n console.log(' ✓ Cursor MCP registered');\n } catch {}\n } else {\n if (uninstallCursorHooks(cursorHooks)) console.log(' ✗ Cursor hooks removed');\n if (uninstallCursorMcpConfig()) console.log(' ✗ Cursor MCP removed');\n }\n\n // Explicit worker counts from .synkro take priority\n if (sf.workers.claude != null || sf.workers.cursor != null) {\n const cw = Math.max(0, Math.floor(sf.workers.claude || 0));\n const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };\n }\n\n const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const providers: WorkerProvider[] = [];\n if (sf.grader.pool === 'cursor') {\n providers.push('cursor');\n } else if (sf.grader.pool === 'claude') {\n providers.push('claude_code');\n } else {\n if (wantCC) providers.push('claude_code');\n if (wantCursor) providers.push('cursor');\n }\n if (providers.length === 0) providers.push('claude_code');\n return splitWorkers(total, providers);\n}\n\nexport async function syncSkillFiles(): Promise<void> {\n const sf = readFullSynkroFile();\n if (!sf || sf.skills.length === 0) return;\n\n const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);\n if (resolved.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const tasks = resolved.map(fp => {\n const content = readFileSync(fp, 'utf-8');\n const source = `skill:${fp.split('/').pop()}`;\n if (!content.trim()) return null;\n return { source, content };\n }).filter(Boolean) as { source: string; content: string }[];\n\n if (tasks.length === 0) return;\n\n const results = await Promise.allSettled(tasks.map(async ({ source, content }) => {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source, content }),\n signal: AbortSignal.timeout(65000),\n });\n if (!resp.ok) throw new Error(`${resp.status}`);\n const result = await resp.json() as { created: number; message?: string };\n return { source, ...result };\n }));\n\n for (const r of results) {\n if (r.status === 'fulfilled') {\n const { source, created, message } = r.value;\n console.log(created > 0\n ? ` ✓ skill ${source}: ${created} new rules added`\n : ` ✓ skill ${source}: ${message || 'up to date'}`);\n } else {\n console.warn(` ⚠ skill sync failed: ${r.reason}`);\n }\n }\n}\n\nexport function detectGitRepo(): string | null {\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return '';\n }\n };\n const remoteUrl = run('git remote get-url origin');\n if (remoteUrl) {\n return remoteUrl\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\/\\/[^/]+\\//, '')\n .replace(/\\.git$/, '');\n }\n // No origin remote — fall back to the repo's top-level directory name.\n const root = run('git rev-parse --show-toplevel');\n return root ? (root.split('/').pop() || null) : null;\n}\n\nfunction getClaudeProjectsFolder(): string | null {\n const cwd = process.cwd();\n // CC stores transcripts in ~/.claude/projects/{sanitized-path}/\n // where sanitized-path replaces / with -\n const sanitized = '-' + cwd.replace(/\\//g, '-');\n const projectsDir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(projectsDir) ? projectsDir : null;\n}\n\ninterface SessionInsight {\n session_id: string;\n insight_type: 'summary' | 'user_message';\n content: string;\n metadata?: Record<string, unknown>;\n}\n\nfunction extractSessionInsights(projectsDir: string): SessionInsight[] {\n const insights: SessionInsight[] = [];\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Extract compaction summaries\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user' &&\n typeof entry.message?.content === 'string' &&\n entry.message.content.startsWith('This session is being continued')) {\n insights.push({\n session_id: sessionId,\n insight_type: 'summary',\n content: entry.message.content.slice(0, 4000),\n metadata: { source: 'compaction_summary' },\n });\n }\n } catch {}\n }\n\n // Extract user messages (last 20 per session — recent preferences)\n const userMessages: string[] = [];\n for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user') {\n const text = typeof entry.message?.content === 'string'\n ? entry.message.content\n : Array.isArray(entry.message?.content)\n ? entry.message.content.map((b: any) => b.text ?? b).filter((t: any) => typeof t === 'string').join(' ')\n : null;\n if (text && text.length > 10 && text.length < 2000 && !text.startsWith('This session is being continued')) {\n userMessages.push(text);\n }\n }\n } catch {}\n }\n for (const msg of userMessages.reverse()) {\n insights.push({\n session_id: sessionId,\n insight_type: 'user_message',\n content: msg.slice(0, 2000),\n });\n }\n } catch {}\n }\n\n return insights;\n}\n\nasync function ingestSessionTranscripts(gatewayUrl: string, token: string, repo: string): Promise<number> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return 0;\n\n const insights = extractSessionInsights(projectsDir);\n if (insights.length === 0) return 0;\n\n console.log(`Found ${insights.length} session insights from Claude Code history...`);\n\n // Send in batches of 100\n let total = 0;\n for (let i = 0; i < insights.length; i += 100) {\n const batch = insights.slice(i, i + 100);\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ingest-sessions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions: batch }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number };\n total += result.accepted;\n }\n } catch {}\n }\n\n return total;\n}\n\ninterface TranscriptMessage {\n message_index: number;\n type: 'user' | 'assistant';\n content: string;\n tool_calls?: Array<{ name: string; input: string; id: string }>;\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n}\n\nfunction extractTextContent(content: any): string {\n if (typeof content === 'string') return content.slice(0, 8000);\n if (Array.isArray(content)) {\n return content\n .filter((b: any) => typeof b === 'string' || (b?.type === 'text'))\n .map((b: any) => typeof b === 'string' ? b : (b?.text || ''))\n .join(' ')\n .slice(0, 8000);\n }\n return '';\n}\n\nfunction parseTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n\n const msg: TranscriptMessage = {\n message_index: i,\n type: entry.type,\n content: extractTextContent(entry.message?.content),\n };\n\n if (entry.type === 'assistant') {\n if (Array.isArray(entry.message?.content)) {\n const toolCalls = entry.message.content\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({\n name: b.name || '',\n input: JSON.stringify(b.input || {}).slice(0, 500),\n id: b.id || '',\n }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n }\n if (entry.message?.model) msg.model = entry.message.model;\n if (entry.message?.usage) {\n msg.usage = {\n input_tokens: entry.message.usage.input_tokens,\n output_tokens: entry.message.usage.output_tokens,\n cache_creation_input_tokens: entry.message.usage.cache_creation_input_tokens,\n cache_read_input_tokens: entry.message.usage.cache_read_input_tokens,\n };\n }\n }\n\n if (msg.content.length > 0) messages.push(msg);\n } catch {}\n }\n\n return messages;\n}\n\nasync function syncTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${files.length} CC session transcripts, importing + embedding...`);\n\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;\n if (messages.length === 0) continue;\n\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: sessionId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === files.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);\n }\n\n // Write offset so the Stop hook doesn't re-send\n try {\n const content = readFileSync(join(projectsDir, file), 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nasync function syncTranscriptsBulk(gatewayUrl: string, token: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(`Found ${files.length} CC session transcripts, syncing...`);\n\n const maxMessagesPerSession = 500;\n let totalSessions = 0;\n let totalMessages = 0;\n\n // Batch sessions into groups of 5\n for (let i = 0; i < files.length; i += 5) {\n const batch = files.slice(i, i + 5);\n const sessions: Array<{ cc_session_id: string; messages: TranscriptMessage[] }> = [];\n\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > maxMessagesPerSession\n ? allMessages.slice(-maxMessagesPerSession)\n : allMessages;\n\n if (messages.length > 0) {\n sessions.push({ cc_session_id: sessionId, messages });\n }\n } catch {}\n }\n\n if (sessions.length === 0) continue;\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number; sessions: number };\n totalMessages += result.accepted;\n totalSessions += result.sessions;\n }\n } catch {}\n\n // Write offset files so the Stop hook doesn't re-send\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n }\n\n return { sessions: totalSessions, messages: totalMessages };\n}\n","/**\n * Filesystem setup for the local-CC channel plugin.\n *\n * Idempotent: safe to call multiple times. Writes:\n * ~/.synkro/cc_sessions/synkro-channel.ts (the Bun MCP plugin)\n * ~/.synkro/cc_sessions/package.json (deps for the plugin)\n * ~/.synkro/cc_sessions/.claude/settings.json (fastMode:true scoped to this cwd)\n *\n * Then patches ~/.claude.json to register the plugin under mcpServers, and runs\n * `bun install` in the session dir so @modelcontextprotocol/sdk is on disk.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, copyFileSync, renameSync, unlinkSync, openSync, fsyncSync, closeSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { spawnSync } from 'node:child_process';\n\nexport const CLAUDE_JSON_BACKUP_PATH = join(homedir(), '.claude.json.synkro-bak');\n\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\nexport const PLUGIN_PATH = join(SESSION_DIR, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH = join(SESSION_DIR, 'package.json');\nexport const PLUGIN_SETTINGS_DIR = join(SESSION_DIR, '.claude');\nexport const PLUGIN_SETTINGS_PATH = join(PLUGIN_SETTINGS_DIR, 'settings.json');\nexport const PROJECT_MCP_PATH = join(SESSION_DIR, '.mcp.json');\nexport const CLAUDE_JSON_PATH = join(homedir(), '.claude.json');\nexport const RUN_SCRIPT_PATH = join(SESSION_DIR, 'run-claude.sh');\nexport const TMUX_SESSION_NAME = 'synkro-local-cc';\n\n// Channel 1 (general A) — pueue task → claude listening on this internal port.\n// The MCP server's dispatcher (8929) round-robins between Channel 1 and 3.\nexport const CHANNEL_1_PORT = 8941;\n\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\nexport const PLUGIN_PATH_2 = join(SESSION_DIR_2, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_2 = join(SESSION_DIR_2, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_2 = join(SESSION_DIR_2, '.claude');\nexport const PLUGIN_SETTINGS_PATH_2 = join(PLUGIN_SETTINGS_DIR_2, 'settings.json');\nexport const PROJECT_MCP_PATH_2 = join(SESSION_DIR_2, '.mcp.json');\nexport const RUN_SCRIPT_PATH_2 = join(SESSION_DIR_2, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_2 = 'synkro-local-cc-2';\n// CWE worker A. Dispatcher on 8930 round-robins Channel 2 and 4.\nexport const CHANNEL_2_PORT = 8951;\n\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\nexport const PLUGIN_PATH_3 = join(SESSION_DIR_3, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_3 = join(SESSION_DIR_3, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_3 = join(SESSION_DIR_3, '.claude');\nexport const PLUGIN_SETTINGS_PATH_3 = join(PLUGIN_SETTINGS_DIR_3, 'settings.json');\nexport const PROJECT_MCP_PATH_3 = join(SESSION_DIR_3, '.mcp.json');\nexport const RUN_SCRIPT_PATH_3 = join(SESSION_DIR_3, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_3 = 'synkro-local-cc-3';\nexport const CHANNEL_3_PORT = 8942; // general worker B\n\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\nexport const PLUGIN_PATH_4 = join(SESSION_DIR_4, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_4 = join(SESSION_DIR_4, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_4 = join(SESSION_DIR_4, '.claude');\nexport const PLUGIN_SETTINGS_PATH_4 = join(PLUGIN_SETTINGS_DIR_4, 'settings.json');\nexport const PROJECT_MCP_PATH_4 = join(SESSION_DIR_4, '.mcp.json');\nexport const RUN_SCRIPT_PATH_4 = join(SESSION_DIR_4, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_4 = 'synkro-local-cc-4';\nexport const CHANNEL_4_PORT = 8952; // CWE worker B\n\n/**\n * Bash wrapper that owns the tmux session hosting `claude`. Pueue runs this\n * script as its task. The script:\n * 1. Kills any prior tmux session by the same name (idempotent restart).\n * 2. Starts a detached tmux session running claude. tmux gives claude a\n * real pty, so it stays in interactive mode (vs. dropping into --print).\n * 3. Blocks on tmux's lifetime so pueue's task duration mirrors claude's.\n * `synkro local-cc stop` kills the tmux session, which unblocks the\n * poll and lets pueue mark the task done.\n *\n * Dismissal of startup dialogs (workspace trust / dev-channels /\n * MCP consent) is owned by the TS-side waitForChannelReady, which polls\n * the channel TCP port and injects \\`tmux send-keys ... Enter\\` each tick\n * the probe fails. That's adaptive (no fixed window) and self-terminating\n * (stops the moment the port binds).\n */\nconst RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Do not edit.\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME}\nLOG=\"$HOME/.synkro/cc_sessions/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\n# Pre-flight checks\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH. Install Claude Code first.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\n# Check claude is authenticated\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed. Is Claude Code installed correctly?\"\n exit 1\nfi\n\nlog \"Starting local-CC session...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\n# Kill any previous session so restarts come up clean.\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\n# Start claude inside a detached tmux session so it has a real pty.\n# Redirect stderr to the log so we can see why it dies.\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\n# Claude's --dangerously-load-development-channels shows a confirmation\n# prompt: option 1 = \"I am using this for local development\" (accept),\n# option 2 = \"Exit\". Auto-accept by sending '1' + Enter.\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n # Additional Enter for any follow-up prompts (workspace trust, MCP consent)\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to claude session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n log \"Try running claude manually to verify auth: claude --print 'say ok'\"\n exit 1\nfi\n\nlog \"tmux session started successfully.\"\n\n# Block on the tmux session so pueue's task lifetime tracks claude's.\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst RUN_SCRIPT_SOURCE_2 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Channel 2 (CWE scan, port ${CHANNEL_2_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_2}\nLOG=\"$HOME/.synkro/cc_sessions_2/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC channel 2 (port ${CHANNEL_2_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to channel 2 session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_2_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// General worker B (port CHANNEL_3_PORT) — dispatcher pairs this with cc_sessions on 8929.\nconst RUN_SCRIPT_SOURCE_3 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. General worker B (port ${CHANNEL_3_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_3}\nLOG=\"$HOME/.synkro/cc_sessions_3/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC general worker B (port ${CHANNEL_3_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to general worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_3_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// CWE worker B (port CHANNEL_4_PORT) — dispatcher pairs this with cc_sessions_2 on 8930.\nconst RUN_SCRIPT_SOURCE_4 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. CWE worker B (port ${CHANNEL_4_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_4}\nLOG=\"$HOME/.synkro/cc_sessions_4/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC CWE worker B (port ${CHANNEL_4_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to CWE worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_4_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst MCP_SERVER_NAME = 'synkro-local';\n\nconst PLUGIN_PACKAGE_JSON = JSON.stringify(\n {\n name: 'synkro-local-channel',\n private: true,\n version: '0.1.0',\n type: 'module',\n dependencies: {\n '@modelcontextprotocol/sdk': '^1.0.0',\n },\n },\n null,\n 2,\n) + '\\n';\n\nexport class LocalCCInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCInstallError';\n }\n}\n\ninterface ChannelInstallSlot {\n sessionDir: string;\n pluginPath: string;\n pluginPkgPath: string;\n pluginSettingsDir: string;\n pluginSettingsPath: string;\n projectMcpPath: string;\n runScriptPath: string;\n runScriptSource: string;\n}\n\nconst CHANNELS: readonly ChannelInstallSlot[] = [\n { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH, pluginPkgPath: PLUGIN_PKG_PATH, pluginSettingsDir: PLUGIN_SETTINGS_DIR, pluginSettingsPath: PLUGIN_SETTINGS_PATH, projectMcpPath: PROJECT_MCP_PATH, runScriptPath: RUN_SCRIPT_PATH, runScriptSource: RUN_SCRIPT_SOURCE },\n { sessionDir: SESSION_DIR_2, pluginPath: PLUGIN_PATH_2, pluginPkgPath: PLUGIN_PKG_PATH_2, pluginSettingsDir: PLUGIN_SETTINGS_DIR_2, pluginSettingsPath: PLUGIN_SETTINGS_PATH_2, projectMcpPath: PROJECT_MCP_PATH_2, runScriptPath: RUN_SCRIPT_PATH_2, runScriptSource: RUN_SCRIPT_SOURCE_2 },\n { sessionDir: SESSION_DIR_3, pluginPath: PLUGIN_PATH_3, pluginPkgPath: PLUGIN_PKG_PATH_3, pluginSettingsDir: PLUGIN_SETTINGS_DIR_3, pluginSettingsPath: PLUGIN_SETTINGS_PATH_3, projectMcpPath: PROJECT_MCP_PATH_3, runScriptPath: RUN_SCRIPT_PATH_3, runScriptSource: RUN_SCRIPT_SOURCE_3 },\n { sessionDir: SESSION_DIR_4, pluginPath: PLUGIN_PATH_4, pluginPkgPath: PLUGIN_PKG_PATH_4, pluginSettingsDir: PLUGIN_SETTINGS_DIR_4, pluginSettingsPath: PLUGIN_SETTINGS_PATH_4, projectMcpPath: PROJECT_MCP_PATH_4, runScriptPath: RUN_SCRIPT_PATH_4, runScriptSource: RUN_SCRIPT_SOURCE_4 },\n];\n\nfunction writePluginFiles(): void {\n for (const c of CHANNELS) {\n mkdirSync(c.sessionDir, { recursive: true });\n mkdirSync(c.pluginSettingsDir, { recursive: true });\n // Channel plugin source lives in the Docker image only (IP protection).\n // Workers inside the container use /app/channel-plugin.ts directly.\n writeFileSync(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, 'utf-8');\n writeFileSync(\n c.pluginSettingsPath,\n JSON.stringify({\n fastMode: true,\n enabledMcpjsonServers: ['synkro-local'],\n }, null, 2) + '\\n',\n 'utf-8',\n );\n writeFileSync(c.runScriptPath, c.runScriptSource, 'utf-8');\n chmodSync(c.runScriptPath, 0o755);\n }\n}\n\nfunction runBunInstall(): void {\n for (const c of CHANNELS) {\n const r = spawnSync('bun', ['install', '--silent'], {\n cwd: c.sessionDir,\n encoding: 'utf-8',\n timeout: 120_000,\n });\n if (r.status !== 0) {\n throw new LocalCCInstallError(\n `bun install failed in ${c.sessionDir}: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n }\n}\n\ninterface ClaudeJson {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n projects?: Record<string, Record<string, unknown>>;\n [k: string]: unknown;\n}\n\n/**\n * Safely apply `mutator` to ~/.claude.json with five layers of protection:\n * 1. Refuse to operate on malformed input — never try to \"fix\" a corrupted file.\n * 2. Take a rolling backup at ~/.claude.json.synkro-bak before any write.\n * 3. Validate the post-mutation result is still well-formed JSON and didn't\n * lose any top-level keys that existed in the original.\n * 4. Atomic write via tmp + fsync + rename(2) (POSIX-atomic).\n * 5. Restore from backup on any error in the write phase.\n *\n * If the file doesn't exist (no claude install on this machine), the mutation\n * is silently skipped — we don't create the file, since claude owns it.\n */\nfunction safelyMutateClaudeJson(mutator: (json: ClaudeJson) => boolean): void {\n if (!existsSync(CLAUDE_JSON_PATH)) {\n // claude.json doesn't exist; nothing to mutate. We don't create it.\n return;\n }\n\n // (1) Read + parse, refuse to proceed on malformed input.\n const originalText = readFileSync(CLAUDE_JSON_PATH, 'utf-8');\n let parsed: ClaudeJson;\n try {\n parsed = JSON.parse(originalText) as ClaudeJson;\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to modify malformed ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Please fix the JSON manually before retrying.`,\n err,\n );\n }\n\n const originalKeys = new Set(Object.keys(parsed));\n\n // (Mutator returns true if it changed anything; false means no-op.)\n const dirty = mutator(parsed);\n if (!dirty) return;\n\n // (3a) Validate the in-memory mutation didn't drop a top-level key.\n for (const k of originalKeys) {\n if (!(k in parsed)) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: mutator dropped top-level key \"${k}\". ` +\n `This is a bug — please report.`,\n );\n }\n }\n\n const newText = JSON.stringify(parsed, null, 2) + '\\n';\n\n // (3b) Round-trip parse to guarantee well-formed output before any write.\n try {\n JSON.parse(newText);\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: serialized result is not valid JSON. ` +\n `This is a bug — please report.`,\n err,\n );\n }\n\n // (2) Take a rolling backup of the *original* (pre-mutation) bytes.\n copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);\n\n // (4) Atomic write: tmp + fsync + rename. This guarantees a reader\n // never sees a half-written file — they see either the old or new.\n const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;\n try {\n writeFileSync(tmpPath, newText, 'utf-8');\n // fsync the tmp file so the bytes are durably on disk before rename.\n const fd = openSync(tmpPath, 'r');\n try { fsyncSync(fd); } finally { closeSync(fd); }\n renameSync(tmpPath, CLAUDE_JSON_PATH);\n } catch (err) {\n // (5) Best-effort recovery: restore the original from backup.\n try { unlinkSync(tmpPath); } catch { /* ignore */ }\n try { copyFileSync(CLAUDE_JSON_BACKUP_PATH, CLAUDE_JSON_PATH); } catch { /* ignore */ }\n throw new LocalCCInstallError(\n `failed to write ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Backup at ${CLAUDE_JSON_BACKUP_PATH} preserves the prior state.`,\n err,\n );\n }\n}\n\n/**\n * Register the synkro-local MCP server at PROJECT scope (.mcp.json inside\n * ~/.synkro/cc_sessions) — NOT at user scope in ~/.claude.json. User scope\n * would make every claude session on the machine spawn the plugin and race\n * to bind the UDS, which corrupts request routing.\n */\nfunction writeProjectMcpJson(): void {\n for (const c of CHANNELS) {\n const mcp = {\n mcpServers: {\n [MCP_SERVER_NAME]: {\n command: 'bun',\n args: [c.pluginPath],\n },\n },\n };\n writeFileSync(c.projectMcpPath, JSON.stringify(mcp, null, 2) + '\\n', 'utf-8');\n }\n}\n\n/**\n * Pre-accept the workspace trust dialog and approve the project-scoped MCP\n * server in ~/.claude.json so claude doesn't block on those prompts under\n * tmux. Also strips any stale top-level mcpServers[\"synkro-local\"] left over\n * from previous (incorrect) installs.\n *\n * All mutations go through safelyMutateClaudeJson — see that function for\n * the integrity guarantees.\n */\nfunction patchClaudeJson(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n\n // Remove any stale user-scope registration from previous installs.\n if (parsed.mcpServers && typeof parsed.mcpServers === 'object' && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n\n if (!parsed.projects || typeof parsed.projects !== 'object') {\n parsed.projects = {};\n }\n const projects = parsed.projects as Record<string, Record<string, unknown>>;\n\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n const existing = projects[dir] && typeof projects[dir] === 'object'\n ? projects[dir]\n : {};\n const wantEnabled = Array.from(new Set([\n ...((existing.enabledMcpjsonServers as string[] | undefined) ?? []),\n MCP_SERVER_NAME,\n ]));\n const next = {\n ...existing,\n hasTrustDialogAccepted: true,\n hasCompletedProjectOnboarding: true,\n enabledMcpjsonServers: wantEnabled,\n };\n if (\n existing.hasTrustDialogAccepted !== true ||\n existing.hasCompletedProjectOnboarding !== true ||\n JSON.stringify(existing.enabledMcpjsonServers ?? []) !== JSON.stringify(wantEnabled)\n ) {\n projects[dir] = next;\n dirty = true;\n }\n }\n return dirty;\n });\n}\n\n/**\n * Run the full local-CC install. Throws LocalCCInstallError on any failure\n * the caller is expected to surface. Does NOT start the pueue task — call\n * `pueue.ensureRunning()` after for that.\n */\nexport function installLocalCC(): { sessionDir: string; pluginPath: string } {\n // bun is required for the plugin runtime — auto-install if missing.\n let bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n if (process.platform === 'darwin') {\n console.log(' Installing bun via brew...');\n const brewR = spawnSync('brew', ['install', 'oven-sh/bun/bun'], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n if (brewR.status !== 0) {\n throw new LocalCCInstallError('bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash');\n }\n bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n throw new LocalCCInstallError('bun installed but not found on PATH. Restart your terminal and re-run install.');\n }\n } else {\n throw new LocalCCInstallError('bun is required. Install it: curl -fsSL https://bun.sh/install | bash');\n }\n }\n\n writePluginFiles();\n runBunInstall();\n writeProjectMcpJson();\n patchClaudeJson();\n\n return { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH };\n}\n\n/**\n * Strip every trace of local-cc from ~/.claude.json:\n * - user-scope mcpServers[\"synkro-local\"] (legacy from older installs)\n * - projects[SESSION_DIR] entry (workspace trust + enabledMcpjsonServers we added)\n *\n * Goes through safelyMutateClaudeJson for the same integrity guarantees as\n * patchClaudeJson — atomic write, backup, post-write validation. Errors are\n * surfaced so the caller sees them, not silently swallowed.\n *\n * Plugin files in ~/.synkro/cc_sessions/ are removed by the caller via\n * `rm -rf ~/.synkro` when uninstalling with --purge.\n */\nexport function uninstallLocalCC(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n if (parsed.mcpServers && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n if (parsed.projects && typeof parsed.projects === 'object' && parsed.projects[dir]) {\n delete parsed.projects[dir];\n dirty = true;\n }\n }\n return dirty;\n });\n}\n","// :)\n/**\n * synkro disconnect — remove all Synkro hook entries from agent settings.\n *\n * Preserves any other hooks the user has (Corridor, Noma, custom).\n *\n * Normal uninstall removes everything — hooks, configs, container, image, and\n * ~/.synkro — EXCEPT the user's scan data (~/.synkro/pgdata and pgdata-backups).\n * `--purge` wipes that data too, after an explicit typed confirmation, leaving\n * the machine as if Synkro was never installed.\n *\n * Order matters: tear down LIVE runtime first (pueue task + tmux session +\n * spawned bun plugin), THEN strip config, THEN remove files. Reversing the\n * order leaves orphaned processes holding the TCP port and a tmux session\n * with claude running from a deleted cwd.\n */\nimport { existsSync, rmSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { uninstallMcpConfig, uninstallCursorMcpConfig } from '../installer/mcpConfig.js';\nimport { uninstallLocalCC } from '../local-cc/install.js';\nimport { dockerRemove, dockerStatus, dockerSafeStop, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nasync function tearDownLocalCC(): Promise<void> {\n const docker = dockerStatus();\n if (docker.running) {\n await dockerSafeStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n dockerRemove();\n console.log('✓ removed synkro-server container');\n\n // Remove the Docker image too — a later `synkro install` just re-pulls it.\n try {\n const image = imageTag();\n const r = spawnSync('docker', ['rmi', '-f', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(r.status === 0 ? `✓ removed Docker image ${image}` : '· no Docker image to remove');\n } catch { /* docker may not be installed */ }\n\n if (needsKeychainBridge()) {\n try { uninstallRefreshAgent(); console.log('✓ launchd refresh agent removed'); } catch { /* best-effort */ }\n }\n\n uninstallLocalCC();\n console.log('✓ cleaned ~/.claude.json entries');\n}\n\nfunction confirmPurge(): Promise<boolean> {\n console.log('⚠ WARNING — synkro uninstall --purge');\n console.log(' This permanently deletes ALL Synkro data on this machine,');\n console.log(' including every scan finding, telemetry record, and backup in');\n console.log(' ~/.synkro/pgdata and ~/.synkro/pgdata-backups. It cannot be undone.\\n');\n if (!process.stdin.isTTY) {\n console.log(' Non-interactive shell — re-run in a terminal to confirm.');\n return Promise.resolve(false);\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\" Type 'yes' to wipe everything (anything else cancels): \", (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'yes');\n });\n });\n}\n\nexport async function disconnectCommand(args: string[] = []): Promise<void> {\n const purge = args.includes('--purge');\n\n if (purge && !(await confirmPurge())) {\n console.log('\\nAborted — nothing was removed.');\n return;\n }\n\n console.log('\\nSynkro uninstall starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n await tearDownLocalCC();\n\n const agents = detectAgents();\n let sawClaudeCode = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n sawClaudeCode = true;\n const removed = uninstallCCHooks(agent.settingsPath);\n console.log(`${removed ? '✓' : '·'} ${agent.name}: ${removed ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n } else if (agent.kind === 'cursor') {\n const removed = uninstallCursorHooks(agent.settingsPath);\n console.log(`${removed ? '✓' : '·'} ${agent.name}: ${removed ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n }\n }\n\n // Also remove the Synkro guardrails MCP server entries from config files\n // (this is the OTHER mcp server — the rule-suggestions one — distinct from\n // the local-cc channel server cleaned up above).\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails (CC): ${mcpRemoved ? 'removed from ~/.claude.json' : 'no entry found'}`);\n }\n {\n const cursorMcpRemoved = uninstallCursorMcpConfig();\n console.log(`${cursorMcpRemoved ? '✓' : '·'} MCP guardrails (Cursor): ${cursorMcpRemoved ? 'removed from ~/.cursor/mcp.json' : 'no entry found'}`);\n }\n\n // Filesystem cleanup. Normal uninstall removes everything except the user's\n // scan data (pgdata + pgdata-backups); --purge removes that too.\n if (existsSync(SYNKRO_DIR)) {\n if (purge) {\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n console.log(`✓ wiped ${SYNKRO_DIR} entirely — including all scan data and backups`);\n } else {\n const keep = new Set([join(SYNKRO_DIR, 'pgdata'), join(SYNKRO_DIR, 'pgdata-backups')]);\n const preserved: string[] = [];\n for (const entry of readdirSync(SYNKRO_DIR)) {\n const full = join(SYNKRO_DIR, entry);\n if (keep.has(full)) { preserved.push(entry); continue; }\n rmSync(full, { recursive: true, force: true });\n }\n if (preserved.length > 0) {\n console.log(`✓ removed Synkro config from ${SYNKRO_DIR} (kept your scan data: ${preserved.join(', ')})`);\n console.log(' run `synkro uninstall --purge` to delete that too');\n } else {\n console.log(`✓ removed ${SYNKRO_DIR}`);\n }\n }\n } else {\n console.log(`· ${SYNKRO_DIR} already gone`);\n }\n\n console.log(purge\n ? '\\nSynkro fully removed — this machine is clean, as if it was never installed.'\n : '\\nSynkro uninstalled. Your scan data is preserved.');\n}\n","/**\n * JSONL log of every channel turn (grade / classify) — one entry per line.\n *\n * Written by `submitToChannel` after each request completes (success or fail).\n * Read by `synkro local-cc logs` to show recent activity.\n *\n * Best-effort: any I/O error here is swallowed so logging never breaks the\n * actual grading path.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, closeSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { LocalCCRole } from './prompts.js';\n\nexport const TURN_LOG_PATH = join(homedir(), '.synkro', 'cc_sessions', 'turns.log');\n\nexport type TurnStatus = 'ok' | 'timeout' | 'error';\n\nexport interface TurnEntry {\n /** ISO timestamp of when the request started. */\n ts: string;\n role: LocalCCRole;\n /** Wall-clock ms from submit to result/error. */\n duration_ms: number;\n status: TurnStatus;\n /** Truncated request payload (first ~400 chars of caller-supplied text). */\n request_preview: string;\n /** Truncated response result (first ~400 chars). Empty on non-ok. */\n response_preview: string;\n /** Parsed severity if we can extract one from the verdict. Optional. */\n severity?: string;\n /** Error message if status is timeout/error. */\n error?: string;\n}\n\nconst PREVIEW_MAX = 400;\n\nfunction truncate(s: string, max = PREVIEW_MAX): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + '… [+' + (s.length - max) + ' chars]';\n}\n\n/** Best-effort extraction of severity from the result text. */\nfunction extractSeverity(result: string): string | undefined {\n // <synkro-verdict>{\"severity\":\"audit\",…}</synkro-verdict>\n const m = result.match(/<synkro-(?:verdict|intent)>([\\s\\S]*?)<\\/synkro-(?:verdict|intent)>/);\n if (!m) return undefined;\n try {\n const obj = JSON.parse(m[1]) as { severity?: string; verdict?: string; type?: string; ok?: boolean };\n if (obj.severity) return String(obj.severity);\n if (typeof obj.ok === 'boolean') return obj.ok ? 'ok' : 'violations';\n if (obj.type) return String(obj.type);\n if (obj.verdict) return String(obj.verdict);\n } catch {\n // not parseable JSON inside the tag — ignore\n }\n return undefined;\n}\n\n/** Append one turn to the JSONL log. Best-effort — never throws. */\nexport function appendTurn(args: {\n startedAt: number;\n role: LocalCCRole;\n request: string;\n result?: string;\n status: TurnStatus;\n error?: string;\n}): void {\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n const entry: TurnEntry = {\n ts: new Date(args.startedAt).toISOString(),\n role: args.role,\n duration_ms: Date.now() - args.startedAt,\n status: args.status,\n request_preview: truncate(args.request),\n response_preview: args.result ? truncate(args.result) : '',\n severity: args.result ? extractSeverity(args.result) : undefined,\n error: args.error,\n };\n appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // never let logging fail the call site\n }\n}\n\n/**\n * Read the most recent N turn entries (newest first). Returns an empty array\n * on any error.\n */\nexport function readRecentTurns(n = 20): TurnEntry[] {\n if (!existsSync(TURN_LOG_PATH)) return [];\n try {\n // For typical sizes this is fine; if the file grows huge we'd switch to\n // tail-style reverse reading. Cap by stat size for safety.\n const size = statSync(TURN_LOG_PATH).size;\n if (size === 0) return [];\n const text = readFileSync(TURN_LOG_PATH, 'utf-8');\n const lines = text.split('\\n').filter(Boolean);\n const lastN = lines.slice(-n).reverse();\n return lastN\n .map(line => {\n try { return JSON.parse(line) as TurnEntry; } catch { return null; }\n })\n .filter((x): x is TurnEntry => x !== null);\n } catch {\n return [];\n }\n}\n\n/**\n * Tail the turn log: invoke `onEntry` for each new entry appended after the\n * call. Handles file truncation/rotation by re-syncing offset to 0 when size\n * shrinks. Returns a `stop()` to detach the watcher.\n */\nexport function followTurns(onEntry: (t: TurnEntry) => void): () => void {\n // Make sure the file exists so watchFile has something to poll. Creating\n // an empty file is harmless if it already exists.\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n if (!existsSync(TURN_LOG_PATH)) {\n appendFileSync(TURN_LOG_PATH, '', 'utf-8');\n }\n } catch {\n // best-effort\n }\n\n let lastSize = (() => {\n try { return statSync(TURN_LOG_PATH).size; } catch { return 0; }\n })();\n let pendingPartial = ''; // bytes after the last \\n if a write split on a line boundary\n\n const drainNewBytes = (from: number, to: number): void => {\n if (to <= from) return;\n let fd: number | null = null;\n try {\n fd = openSync(TURN_LOG_PATH, 'r');\n const len = to - from;\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, from);\n const text = pendingPartial + buf.toString('utf-8');\n const lastNewline = text.lastIndexOf('\\n');\n if (lastNewline === -1) {\n pendingPartial = text;\n return;\n }\n const complete = text.slice(0, lastNewline);\n pendingPartial = text.slice(lastNewline + 1);\n for (const line of complete.split('\\n')) {\n if (!line) continue;\n try {\n onEntry(JSON.parse(line) as TurnEntry);\n } catch {\n // skip malformed lines\n }\n }\n } catch {\n // best-effort\n } finally {\n if (fd !== null) {\n try { closeSync(fd); } catch { /* noop */ }\n }\n }\n };\n\n // Poll-based watcher (cross-platform reliable). 250ms is responsive\n // enough for tail-f UX without burning CPU.\n watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {\n if (curr.size < lastSize) {\n // file truncated/rotated — reset.\n lastSize = 0;\n pendingPartial = '';\n }\n if (curr.size > lastSize) {\n drainNewBytes(lastSize, curr.size);\n lastSize = curr.size;\n }\n });\n\n return () => unwatchFile(TURN_LOG_PATH);\n}\n","/**\n * Client for the local-CC channel plugin.\n *\n * POSTs to a TCP port on 127.0.0.1 exposed by the channel plugin running\n * inside the pueue-managed `claude` process. Returns the raw text Claude\n * wrote into the `reply` tool's `result` argument.\n */\n\nimport { request as httpRequest } from 'node:http';\nimport { connect } from 'node:net';\nimport type { LocalCCRole } from './prompts.js';\nimport { appendTurn } from './turnLog.js';\n\nexport const CHANNEL_HOST = '127.0.0.1';\nexport const CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || '8929', 10);\n\nconst DEFAULT_TIMEOUT_MS = 90_000;\n\nexport class LocalCCError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCError';\n }\n}\n\nexport interface SubmitOptions {\n /** Timeout for the round-trip in ms (default 90s). */\n timeoutMs?: number;\n /** Override the channel port (default: CHANNEL_PORT / 8929). */\n port?: number;\n}\n\n/**\n * Send a request through the channel and wait for Claude's `reply` tool output.\n * Throws LocalCCError if the channel isn't reachable, the channel times out, or\n * the plugin returns a non-2xx status.\n */\nexport async function submitToChannel(\n role: LocalCCRole,\n payload: string,\n opts: SubmitOptions = {},\n): Promise<string> {\n const body = JSON.stringify({ role, payload, content: payload });\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const port = opts.port ?? CHANNEL_PORT;\n const startedAt = Date.now();\n\n try {\n const result = await new Promise<string>((resolve, reject) => {\n const req = httpRequest({\n host: CHANNEL_HOST,\n port,\n method: 'POST',\n path: '/submit',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n timeout: timeoutMs,\n }, res => {\n const chunks: Buffer[] = [];\n res.on('data', c => chunks.push(c));\n res.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (res.statusCode !== 200) {\n reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));\n return;\n }\n try {\n const parsed = JSON.parse(text) as { result?: string; error?: string };\n if (parsed.error) {\n reject(new LocalCCError(parsed.error));\n return;\n }\n resolve(String(parsed.result ?? ''));\n } catch (err) {\n reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));\n }\n });\n });\n req.on('timeout', () => {\n req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));\n });\n req.on('error', err => {\n const msg = (err as NodeJS.ErrnoException).code === 'ECONNREFUSED'\n ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)`\n : `channel request failed: ${(err as Error).message}`;\n reject(new LocalCCError(msg, err));\n });\n req.write(body);\n req.end();\n });\n appendTurn({ startedAt, role, request: payload, result, status: 'ok' });\n return result;\n } catch (err) {\n const message = (err as Error).message ?? String(err);\n const status = /timed out/i.test(message) ? 'timeout' : 'error';\n appendTurn({ startedAt, role, request: payload, status, error: message });\n throw err;\n }\n}\n\n/** Quick TCP probe — true if anything is listening on the given port (default: CHANNEL_PORT). */\nexport function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, CHANNEL_HOST);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n","/**\n * `synkro grade <edit|bash>` — replacement for the legacy\n * ~/.synkro/bin/grader_daemon.py. Reads a grading payload from stdin,\n * routes it through the local-CC channel, and writes the raw verdict\n * (including the <synkro-verdict>...</synkro-verdict> wrapper that the\n * shell hook scripts grep for) to stdout.\n *\n * Failures print to stderr and exit non-zero so the hook scripts fall\n * through to the cloud fast-tier path cleanly.\n */\n\nimport { submitToChannel, LocalCCError } from '../local-cc/client.js';\nimport type { LocalCCRole } from '../local-cc/prompts.js';\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', c => chunks.push(c));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nexport async function gradeCommand(args: string[]): Promise<void> {\n const mode = args[0] ?? '';\n let role: LocalCCRole;\n if (mode === 'edit') role = 'grade-edit';\n else if (mode === 'bash') role = 'grade-bash';\n else if (mode === 'plan') role = 'grade-plan';\n else if (mode === 'cwe') role = 'grade-cwe';\n else {\n console.error('Usage: synkro grade <edit|bash|plan|cwe>');\n process.exit(2);\n }\n\n const payload = await readStdin();\n if (!payload.trim()) {\n console.error('synkro grade: empty stdin');\n process.exit(2);\n }\n\n try {\n const result = await submitToChannel(role, payload, { timeoutMs: 60_000 });\n process.stdout.write(result);\n if (!result.endsWith('\\n')) process.stdout.write('\\n');\n } catch (err) {\n if (err instanceof LocalCCError) {\n console.error(`synkro grade: ${err.message}`);\n } else {\n console.error(`synkro grade: ${(err as Error).message}`);\n }\n process.exit(3);\n }\n}\n","/**\n * Pueue lifecycle for the local-CC `claude` process.\n *\n * One labelled pueue task per user, started at install (or on demand) and\n * managed via the `pueue` CLI. We assume `pueue` and `pueued` are installed\n * and the daemon is running — surface a friendly error otherwise.\n */\n\nimport { execFileSync, spawnSync, spawn } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { connect } from 'node:net';\n\nexport const TASK_LABEL = 'synkro-local-cc';\nexport const TMUX_SESSION = 'synkro-local-cc';\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\n\nexport const TASK_LABEL_2 = 'synkro-local-cc-2';\nexport const TMUX_SESSION_2 = 'synkro-local-cc-2';\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\n\nexport const TASK_LABEL_3 = 'synkro-local-cc-3';\nexport const TMUX_SESSION_3 = 'synkro-local-cc-3';\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\n\nexport const TASK_LABEL_4 = 'synkro-local-cc-4';\nexport const TMUX_SESSION_4 = 'synkro-local-cc-4';\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\n\nexport class PueueError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'PueueError';\n }\n}\n\ninterface PueueStatusEntry {\n id: number;\n label?: string;\n status: string | { Running?: unknown; Done?: { result?: string } };\n command: string;\n path: string;\n}\n\ninterface PueueStatus {\n tasks: Record<string, PueueStatusEntry>;\n}\n\nfunction pueueAvailable(): void {\n const r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.');\n }\n}\n\nfunction statusJson(): PueueStatus {\n pueueAvailable();\n const r = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || 'unknown error'} — is pueued running?`);\n }\n try {\n return JSON.parse(r.stdout) as PueueStatus;\n } catch (err) {\n throw new PueueError(`pueue status returned non-JSON output: ${r.stdout.slice(0, 200)}`, err);\n }\n}\n\nexport interface TaskInfo {\n id: number;\n label: string;\n status: string;\n command: string;\n cwd: string;\n}\n\nfunction statusName(s: PueueStatusEntry['status']): string {\n if (typeof s === 'string') return s;\n if (s && typeof s === 'object') {\n if ('Running' in s) return 'Running';\n if ('Done' in s) {\n const result = (s as { Done?: { result?: string | object } }).Done?.result;\n if (typeof result === 'string') return `Done (${result})`;\n if (result && typeof result === 'object') return `Done (${Object.keys(result)[0] ?? 'unknown'})`;\n return 'Done';\n }\n return Object.keys(s)[0] ?? 'unknown';\n }\n return 'unknown';\n}\n\nexport interface ChannelIdentity {\n taskLabel: string;\n tmuxSession: string;\n sessionDir: string;\n}\n\nexport const CHANNEL_PRIMARY: ChannelIdentity = { taskLabel: TASK_LABEL, tmuxSession: TMUX_SESSION, sessionDir: SESSION_DIR };\nexport const CHANNEL_SECONDARY: ChannelIdentity = { taskLabel: TASK_LABEL_2, tmuxSession: TMUX_SESSION_2, sessionDir: SESSION_DIR_2 };\nexport const CHANNEL_TERTIARY: ChannelIdentity = { taskLabel: TASK_LABEL_3, tmuxSession: TMUX_SESSION_3, sessionDir: SESSION_DIR_3 };\nexport const CHANNEL_QUATERNARY: ChannelIdentity = { taskLabel: TASK_LABEL_4, tmuxSession: TMUX_SESSION_4, sessionDir: SESSION_DIR_4 };\nexport const ALL_CHANNELS: readonly ChannelIdentity[] = [CHANNEL_PRIMARY, CHANNEL_SECONDARY, CHANNEL_TERTIARY, CHANNEL_QUATERNARY];\n\n/** Find a synkro local-cc task by label. Returns null if absent. */\nexport function findTask(channel: ChannelIdentity = CHANNEL_PRIMARY): TaskInfo | null {\n const data = statusJson();\n for (const [id, t] of Object.entries(data.tasks)) {\n if (t.label === channel.taskLabel) {\n return {\n id: Number(id),\n label: t.label,\n status: statusName(t.status),\n command: t.command,\n cwd: t.path,\n };\n }\n }\n return null;\n}\n\n/** True if a synkro local-cc task is currently Running. */\nexport function isRunning(channel: ChannelIdentity = CHANNEL_PRIMARY): boolean {\n const t = findTask(channel);\n return t?.status === 'Running';\n}\n\nexport interface StartOptions {\n /** Override the channel plugin script path. */\n pluginPath?: string;\n /** Working directory for the claude process. */\n cwd?: string;\n /** Which channel to start (default: primary). */\n channel?: ChannelIdentity;\n}\n\n/**\n * Add a fresh pueue task for the local-cc claude session. Removes any prior\n * task with the same label first (Done or otherwise) so subsequent calls\n * always replace cleanly.\n */\nexport function startTask(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const cwd = opts.cwd ?? ch.sessionDir;\n // Remove ALL tasks with this label (not just the first) to clear stale duplicates\n let existing = findTask(ch);\n while (existing) {\n if (existing.status === 'Running' || existing.status === 'Queued') {\n spawnSync('tmux', ['kill-session', '-t', `=${ch.tmuxSession}`], { encoding: 'utf-8' });\n spawnSync('pueue', ['kill', String(existing.id)], { encoding: 'utf-8' });\n for (let i = 0; i < 10; i++) {\n const check = findTask(ch);\n if (!check || check.id !== existing.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(existing.id)], { encoding: 'utf-8' });\n existing = findTask(ch);\n }\n\n const runScript = join(cwd, 'run-claude.sh');\n const args = [\n 'add',\n '--label', ch.taskLabel,\n '--working-directory', cwd,\n '--',\n 'bash', runScript,\n ];\n\n const r = spawnSync('pueue', args, { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);\n }\n const created = findTask(ch);\n if (!created) {\n throw new PueueError(`pueue add succeeded but no task with label ${ch.taskLabel} found`);\n }\n return created;\n}\n\n/** Stop and remove the synkro local-cc task. Also kills the tmux session\n * that owns the underlying claude process. No-op if neither is present. */\nexport function stopTask(channel: ChannelIdentity = CHANNEL_PRIMARY): void {\n spawnSync('tmux', ['kill-session', '-t', `=${channel.tmuxSession}`], { encoding: 'utf-8' });\n // Remove ALL tasks with this label, waiting for each kill to land\n let t = findTask(channel);\n while (t) {\n if (t.status === 'Running' || t.status === 'Queued') {\n spawnSync('pueue', ['kill', String(t.id)], { encoding: 'utf-8' });\n // Wait for the task to leave Running state before removing\n for (let i = 0; i < 10; i++) {\n const check = findTask(channel);\n if (!check || check.id !== t.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(t.id)], { encoding: 'utf-8' });\n t = findTask(channel);\n }\n}\n\n/** Print the last N log lines for the task. Returns the raw text. */\nexport function tailLogs(lines = 80, channel: ChannelIdentity = CHANNEL_PRIMARY): string {\n const t = findTask(channel);\n if (!t) return `(no ${channel.taskLabel} task)`;\n const r = spawnSync('pueue', ['log', '--lines', String(lines), String(t.id)], { encoding: 'utf-8' });\n return r.stdout || r.stderr || '(no output)';\n}\n\n/** Ensure the task is running; start it if not. Returns the task info. */\nexport function ensureRunning(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const t = findTask(ch);\n if (t && t.status === 'Running') return t;\n return startTask(opts);\n}\n\n/** Probe a TCP port — true if something accepts a connection within timeoutMs. */\nfunction probePort(host: string, port: number, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, host);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n\n/** Dismiss startup prompts in the tmux session. Best-effort.\n * Sends '1' (dev-channels accept) then Enter (other prompts). */\nfunction tmuxDismissPrompts(tmuxSession: string = TMUX_SESSION): void {\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, '1'], { encoding: 'utf-8' });\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, 'Enter'], { encoding: 'utf-8' });\n}\n\n/**\n * Wait up to `timeoutMs` for the channel TCP listener to come up. Each tick\n * the probe fails, send an Enter into the tmux session — that walks claude\n * past any startup confirmation dialogs (workspace trust, dev-channels,\n * MCP consent). The loop self-terminates the moment the port binds, so\n * we don't keep spamming Enters once claude is at the live `❯` prompt.\n */\nexport async function waitForChannelReady(\n port: number,\n timeoutMs = 60_000,\n host = '127.0.0.1',\n tmuxSession: string = TMUX_SESSION,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await probePort(host, port)) return true;\n tmuxDismissPrompts(tmuxSession);\n await new Promise(r => setTimeout(r, 1000));\n }\n return probePort(host, port);\n}\n\nfunction brewInstall(pkg: string): boolean {\n const brew = spawnSync('brew', ['--version'], { encoding: 'utf-8' });\n if (brew.status !== 0) return false;\n console.log(` Installing ${pkg} via brew...`);\n const r = spawnSync('brew', ['install', pkg], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n return r.status === 0;\n}\n\n/** Ensure pueue is installed and pueued daemon is running. Auto-installs on macOS. */\nexport function assertPueueInstalled(): void {\n let r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('pueue')) {\n r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('pueue install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue');\n }\n }\n // Ensure pueued daemon is running\n const status = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (status.status !== 0) {\n console.log(' Starting pueued daemon...');\n const child = spawn('pueued', ['-d'], { stdio: 'ignore', detached: true });\n child.unref();\n // Give the daemon a moment to bind its socket\n spawnSync('sleep', ['1']);\n const retry = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (retry.status !== 0) {\n throw new PueueError('pueue daemon not reachable after starting pueued. Check `pueued` manually.');\n }\n }\n spawnSync('pueue', ['parallel', '2'], { encoding: 'utf-8' });\n}\n\n/** Ensure claude CLI is installed. */\nexport function assertClaudeInstalled(): void {\n const r = spawnSync('claude', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code');\n }\n}\n\n/** Ensure tmux is installed. Auto-installs on macOS. */\nexport function assertTmuxInstalled(): void {\n let r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('tmux')) {\n r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('tmux install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)');\n }\n }\n}\n\n/** No-throw helper used by the install command for status messages. */\nexport function readVersion(bin: string): string | null {\n try {\n return execFileSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {\n return null;\n }\n}\n","/**\n * Inference provider detection.\n *\n * The source of truth is the user's inference_settings in TimescaleDB.\n * At install time, `/api/v1/cli/me` returns `local_inference: true` when\n * `gradingProvider = 'claude-code'`. The install flow writes\n * `SYNKRO_LOCAL_INFERENCE='yes'` into ~/.synkro/config.env.\n *\n * At runtime the hook scripts use a TCP probe (synkro_channel_up) to decide\n * routing — this module is only used by CLI commands and intent routers.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nexport function isLocalCCEnabled(): boolean {\n if (!existsSync(CONFIG_PATH)) return false;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);\n return match?.[1] === 'yes';\n } catch {\n return false;\n }\n}\n","/**\n * `synkro local-cc <subcommand>` — manage the local pueue-backed Claude Code\n * session that powers grading and intent classification when the inference\n * provider toggle is set to local-cc.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { installLocalCC, SESSION_DIR, PLUGIN_PATH, RUN_SCRIPT_PATH, PLUGIN_SETTINGS_PATH, CLAUDE_JSON_PATH, TMUX_SESSION_NAME, CHANNEL_2_PORT, TMUX_SESSION_NAME_2 } from '../local-cc/install.js';\nimport { readRecentTurns, followTurns, TURN_LOG_PATH, type TurnEntry } from '../local-cc/turnLog.js';\nimport {\n assertClaudeInstalled,\n assertPueueInstalled,\n assertTmuxInstalled,\n ensureRunning,\n findTask,\n startTask,\n stopTask,\n tailLogs,\n waitForChannelReady,\n CHANNEL_PRIMARY,\n CHANNEL_SECONDARY,\n} from '../local-cc/pueue.js';\nimport { isLocalCCEnabled } from '../local-cc/settings.js';\nimport { refreshCreds, needsKeychainBridge, credsAreStale, CLAUDE_CREDS_FILE } from '../local-cc/macKeychain.js';\nimport { dockerStatus, dockerStop, dockerUpdate, waitForContainerReady, waitForWorkersReady, resolveWorkerConfig } from '../local-cc/dockerInstall.js';\nimport { reconcileHarness, syncSkillFiles } from './install.js';\nimport { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from 'node:fs';\n\nconst SYNKRO_CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\n/**\n * Resolution order matches commands/install.ts: live env var first, then the\n * persisted config.env value, then 'bare-host'. Kept inline rather than\n * imported from install.ts so this module stays loadable when the install\n * command tree is pruned in future cleanups.\n */\nfunction deploymentMode(): 'bare-host' | 'docker' {\n const env = (process.env.SYNKRO_DEPLOYMENT_MODE || '').toLowerCase();\n if (env === 'docker') return 'docker';\n if (env === 'bare-host') return 'bare-host';\n try {\n if (fsExistsSync(SYNKRO_CONFIG_PATH)) {\n const m = fsReadFileSync(SYNKRO_CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n if (m && m[1] === 'docker') return 'docker';\n }\n } catch { /* fall through */ }\n return 'bare-host';\n}\n\nfunction inDockerMode(): boolean { return deploymentMode() === 'docker'; }\nimport { submitToChannel, isChannelAvailable, CHANNEL_HOST, CHANNEL_PORT } from '../local-cc/client.js';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\n\nfunction printHelp(): void {\n console.log(`synkro local-cc — manage the local Claude Code inference session\n\nOVERVIEW\n Routes Synkro's grading and intent-classification work through a long-running\n Claude Code session on this machine instead of remote Inngest+Gemini.\n\n When enabled, three call sites switch over:\n • security grading on edit/bash hooks\n • intent classification (agent input → structured intent)\n • remediate intent classification\n\n The session is hosted in a detached tmux session managed by a pueue task.\n The CLI talks to it via Claude Code's channels API: a Bun MCP plugin that\n pushes events into the session and receives Claude's responses through a\n \\`reply\\` MCP tool.\n\nUSAGE\n synkro local-cc <subcommand> [args]\n\nSUBCOMMANDS\n enable Install plugin, start pueue task, flip toggle to local-cc\n disable Flip toggle back to inngest (pueue task left running)\n status Show provider toggle, pueue task state, channel reachability\n start Idempotently bring up the pueue task + wait for the channel\n stop Kill the tmux session and remove the pueue task\n restart stop, then start\n install Regenerate ~/.synkro/cc_sessions/ files (plugin, runner, settings)\n logs [N] [--raw] [--live]\n Show the last N (default 20) channel turns: when, role,\n duration, severity, request preview.\n --raw / -r include full request/response payloads\n --live / -f tail the log; print new turns as they arrive\n (Ctrl-C to exit)\n --tmux escape hatch — print the raw pueue/tmux\n pane log instead\n attach [--readonly] Attach to the tmux session hosting claude (Ctrl-B D to detach;\n --readonly / -r to attach view-only)\n test Send a smoke-test classification through the channel\n help Show this message\n\nCONFIGURATION\n Provider toggle:\n Stored server-side in your inference settings (gradingProvider).\n Toggle via: synkro local-cc enable / disable\n Or via dashboard inference settings (set grading provider to claude-code).\n\n Claude Code session settings (scoped to ~/.synkro/cc_sessions only):\n ${PLUGIN_SETTINGS_PATH}\n Currently sets {\"fastMode\": true}. Edit to add other CC settings for this\n session without affecting your other CC projects.\n\n MCP server registration (for the channel plugin):\n ${CLAUDE_JSON_PATH}\n A 'synkro-local' entry under mcpServers, pointing at:\n bun ${PLUGIN_PATH}\n Workspace trust is also pre-accepted under projects[\\`${SESSION_DIR}\\`].\n\n Channel runtime files:\n ${RUN_SCRIPT_PATH}\n bash wrapper that pueue invokes; owns the tmux session lifecycle.\n ${PLUGIN_PATH}\n Bun MCP channel plugin (auto-generated, do not edit).\n 127.0.0.1:${CHANNEL_PORT}\n Loopback TCP endpoint the CLI POSTs to in order to submit a request.\n\nENVIRONMENT VARIABLES\n SYNKRO_CHANNEL_PORT Override the TCP port used by both the plugin and\n the CLI client (loopback only). Default: 8929\n SYNKRO_CHANNEL_TIMEOUT_MS Per-request timeout inside the channel plugin\n (default: 120000)\n\nREQUIRED TOOLS\n claude Claude Code CLI, authenticated to your subscription\n pueue pueue + pueued (https://github.com/Nukesor/pueue) running\n tmux For detached pty around claude\n bun Runtime for the MCP channel plugin\n\nTROUBLESHOOTING\n • Channel unreachable after \\`enable\\`?\n synkro local-cc logs # check pueue task output\n tmux attach -t ${TMUX_SESSION_NAME} # see what claude is showing\n • Stale state after a crash:\n synkro local-cc stop && synkro local-cc start\n • Want to inspect or interact with the live session:\n synkro local-cc attach\n`);\n}\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readGatewayUrl(): string {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);\n if (m) return m[1];\n }\n return 'https://api.synkro.sh';\n}\n\nfunction updateLocalInferenceFlag(enabled: boolean): void {\n if (!existsSync(CONFIG_PATH)) return;\n let content = readFileSync(CONFIG_PATH, 'utf-8');\n const flag = enabled ? 'yes' : 'no';\n if (content.includes('SYNKRO_LOCAL_INFERENCE=')) {\n content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);\n } else {\n content = content.trimEnd() + `\\nSYNKRO_LOCAL_INFERENCE='${flag}'\\n`;\n }\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\nasync function setServerGradingProvider(provider: 'claude-code' | null): Promise<void> {\n await ensureValidToken();\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated. Run `synkro install` first.');\n const gatewayUrl = readGatewayUrl();\n const body = provider\n ? { roles: { grading: { provider, model: 'default' } } }\n : { roles: { grading: { provider: null, model: null } } };\n const resp = await fetch(`${gatewayUrl}/api/settings/inference?scope=user`, {\n method: 'PUT',\n headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`Failed to update inference settings: ${resp.status} ${text.slice(0, 200)}`);\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n console.log(`Local inference: ${isLocalCCEnabled() ? 'enabled' : 'disabled'}`);\n console.log(`Deployment mode: ${deploymentMode()}`);\n\n if (inDockerMode()) {\n const status = dockerStatus();\n if (!status.running) {\n console.log('synkro-server container: not running');\n console.log('Run `synkro install` (with SYNKRO_DEPLOYMENT_MODE=docker) to provision.');\n } else {\n console.log(`synkro-server container: running (${status.image})`);\n try {\n const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3_000) });\n console.log(`Health probe: ${r.ok ? 'ok' : `HTTP ${r.status}`}`);\n } catch (err) {\n console.log(`Health probe: ${(err as Error).message}`);\n }\n }\n if (needsKeychainBridge()) {\n console.log(`Keychain creds: ${credsAreStale() ? 'STALE — run `synkro local-cc refresh-creds`' : 'fresh'}`);\n }\n return;\n }\n\n try {\n assertPueueInstalled();\n } catch (err) {\n console.log(`Pueue: NOT AVAILABLE (${(err as Error).message})`);\n return;\n }\n\n // Channel 1 (judge: bash + edit, port 8929)\n const t = findTask(CHANNEL_PRIMARY);\n if (!t) {\n console.log('Channel 1 (judge) pueue task: not present');\n } else {\n console.log(`Channel 1 (judge) pueue task: id=${t.id} status=${t.status}`);\n }\n const ch1Up = await isChannelAvailable();\n console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? 'reachable' : 'unreachable'}`);\n const tmux1 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? 'live' : 'absent'}`);\n\n // Channel 2 (CWE, port 8930)\n const t2 = findTask(CHANNEL_SECONDARY);\n if (!t2) {\n console.log('Channel 2 (CWE) pueue task: not present');\n } else {\n console.log(`Channel 2 (CWE) pueue task: id=${t2.id} status=${t2.status}`);\n }\n const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);\n console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? 'reachable' : 'unreachable'}`);\n const tmux2 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME_2}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? 'live' : 'absent'}`);\n}\n\nasync function cmdEnable(): Promise<void> {\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n console.log('Installing local-CC channel plugin...');\n const r = installLocalCC();\n console.log(` plugin: ${r.pluginPath}`);\n console.log(` cwd: ${r.sessionDir}`);\n console.log('Starting channel 1 (judge)...');\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(` task: id=${t1.id} status=${t1.status}`);\n console.log('Starting channel 2 (CWE)...');\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(` task: id=${t2.id} status=${t2.status}`);\n console.log('Waiting for channels (auto-confirming any CC prompts)...');\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n if (ready1) console.log(` channel 1 ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);\n else console.warn(` ⚠ channel 1 did not come up within 60s — check \\`synkro local-cc logs\\``);\n if (ready2) console.log(` channel 2 ready at ${CHANNEL_HOST}:${CHANNEL_2_PORT}`);\n else console.warn(` ⚠ channel 2 (CWE) did not come up within 60s`);\n console.log('Updating inference settings...');\n await setServerGradingProvider('claude-code');\n updateLocalInferenceFlag(true);\n console.log('Grading provider set to claude-code (local inference enabled).');\n}\n\nasync function cmdDisable(): Promise<void> {\n console.log('Updating inference settings...');\n await setServerGradingProvider(null);\n updateLocalInferenceFlag(false);\n console.log('Grading provider cleared (remote inference restored). Pueue task left running — use `synkro local-cc stop` to terminate.');\n}\n\nasync function warmChannels(ready1: boolean, ready2: boolean): Promise<void> {\n const warmups: Promise<void>[] = [];\n if (ready1) {\n warmups.push(\n submitToChannel('grade-bash', 'Proposed command: echo hello\\nUser intent: warmup\\nRecent user messages: []\\nRecent actions: []\\nOrg rules: []\\n', { timeoutMs: 30_000 })\n .then(() => console.log(' channel 1 warm.'))\n .catch(() => console.log(' channel 1 warmup skipped (non-fatal).'))\n );\n }\n if (ready2) {\n warmups.push(\n submitToChannel('grade-cwe', 'File: /tmp/warmup.ts\\nContent (first 4000 chars):\\nconsole.log(\"hello\");\\n\\nCWE rules to check against:\\n[]\\n', { timeoutMs: 30_000, port: CHANNEL_2_PORT })\n .then(() => console.log(' channel 2 warm.'))\n .catch(() => console.log(' channel 2 warmup skipped (non-fatal).'))\n );\n }\n if (warmups.length) {\n console.log('Warming up inference...');\n await Promise.all(warmups);\n }\n}\n\nasync function cmdStart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n // With worker flags, `start` (re)creates the container with the requested\n // pool config. Without flags it just ensures the container is running.\n if (rest.length > 0) {\n const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);\n console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n return;\n }\n // Container is normally managed by docker's `--restart unless-stopped`,\n // so \"start\" really just means: ensure it's running and healthy. If it's\n // not even installed, point the user at `synkro install`.\n const status = dockerStatus();\n if (!status.running) {\n console.warn('synkro-server container is not running. Run `synkro install` to provision it.');\n process.exitCode = 1;\n return;\n }\n console.log('synkro-server container already running — waiting for /healthz...');\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? `✓ container ready (${status.healthz})` : '⚠ container did not pass /healthz within 60s');\n return;\n }\n\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(`Channel 1 (judge): id=${t1.id} status=${t1.status}`);\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 2 (CWE): id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction cmdStop(): void {\n if (inDockerMode()) {\n dockerStop();\n console.log('synkro-server container stopped and removed.');\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n console.log('Both channels stopped.');\n}\n\nasync function cmdRestart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n const { explicit } = resolveWorkerConfig(rest);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (explicit) {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n } else {\n const reconciled = reconcileHarness();\n if (reconciled) {\n ({ claudeWorkers, cursorWorkers } = reconciled);\n } else {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n }\n }\n console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n if (ready) {\n const workersUp = await waitForWorkersReady(30_000);\n console.log(workersUp ? '✓ workers ready' : '⚠ workers did not register within 30s');\n if (workersUp) await syncSkillFiles();\n }\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n const t1 = startTask({ channel: CHANNEL_PRIMARY });\n const t2 = startTask({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 1 restarted: id=${t1.id} status=${t1.status}`);\n console.log(`Channel 2 restarted: id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction relativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (!Number.isFinite(ts)) return iso;\n const sec = Math.max(0, Math.round((Date.now() - ts) / 1000));\n if (sec < 60) return `${sec}s ago`;\n if (sec < 3600) return `${Math.round(sec / 60)}m ago`;\n if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;\n return `${Math.round(sec / 86400)}d ago`;\n}\n\nfunction colorize(s: string, code: number): string {\n if (!process.stdout.isTTY) return s;\n return `\u001b[${code}m${s}\u001b[0m`;\n}\n\nfunction statusGlyph(t: TurnEntry): string {\n if (t.status === 'ok') return colorize('✓', 32); // green check\n if (t.status === 'timeout') return colorize('⏱', 33); // yellow clock\n return colorize('✗', 31); // red X\n}\n\nfunction severityCell(t: TurnEntry): string {\n if (t.severity) {\n const sev = t.severity;\n if (sev === 'block' || sev === 'violations' || sev === 'unclear') return colorize(sev, 33);\n return colorize(sev, 36); // cyan for everything else\n }\n if (t.error) return colorize(t.error.slice(0, 40), 31);\n return '—';\n}\n\nfunction firstLine(s: string): string {\n return s.split('\\n').find(l => l.trim().length > 0)?.trim() ?? '';\n}\n\nfunction formatTurn(t: TurnEntry, raw: boolean): string {\n const when = relativeTime(t.ts).padEnd(8);\n const dur = (t.duration_ms < 1000\n ? `${t.duration_ms}ms`\n : `${(t.duration_ms / 1000).toFixed(1)}s`).padStart(7);\n const role = t.role.padEnd(24);\n const sev = severityCell(t).padEnd(20);\n const preview = (() => {\n const req = firstLine(t.request_preview).slice(0, 60);\n return colorize(req, 90);\n })();\n const head = `${statusGlyph(t)} ${when} ${dur} ${role} ${sev} ${preview}`;\n if (!raw) return head;\n // raw mode: include full request + response previews on subsequent lines\n const blocks: string[] = [head];\n blocks.push(colorize(' request:', 90));\n blocks.push(' ' + t.request_preview.replace(/\\n/g, '\\n '));\n if (t.response_preview) {\n blocks.push(colorize(' response:', 90));\n blocks.push(' ' + t.response_preview.replace(/\\n/g, '\\n '));\n }\n if (t.error) {\n blocks.push(colorize(' error:', 31) + ' ' + t.error);\n }\n return blocks.join('\\n');\n}\n\nfunction cmdLogs(rest: string[]): void | Promise<void> {\n let n = 20;\n let raw = false;\n let live = false;\n\n if (inDockerMode()) {\n // In docker mode the meaningful logs come from the container itself: MCP\n // boot, dispatcher routing, worker tmux output. Forward to `docker logs`\n // and let docker handle the --follow / --tail semantics.\n const followFlag = rest.includes('--live') || rest.includes('-f') ? ['--follow'] : [];\n const tailArg = (() => {\n for (const arg of rest) {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) return String(parsed);\n }\n return '200';\n })();\n spawnSync('docker', ['logs', '--tail', tailArg, ...followFlag, 'synkro-server'], { stdio: 'inherit' });\n return;\n }\n\n for (const arg of rest) {\n if (arg === '--raw' || arg === '-r') raw = true;\n else if (arg === '--live' || arg === '-f') live = true;\n else if (arg === '--tmux') {\n // Escape hatch: show the original raw pueue/tmux output for the task.\n console.log(tailLogs(80));\n return;\n } else {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) n = parsed;\n }\n }\n\n const header = ' ' + colorize('status when dur role severity request', 90);\n\n // Print backlog (newest first) — same as non-live mode.\n const turns = readRecentTurns(n);\n if (turns.length === 0) {\n if (!live) {\n console.log(`No turns logged yet at ${TURN_LOG_PATH}.`);\n console.log('Run a few requests through the channel (synkro local-cc test) and try again.');\n return;\n }\n console.log(`No turns logged yet at ${TURN_LOG_PATH} — waiting for new entries… (Ctrl-C to exit)`);\n } else {\n console.log(`Last ${turns.length} channel turn(s) (newest first):`);\n console.log(header);\n for (const t of turns) console.log(' ' + formatTurn(t, raw));\n }\n\n if (!live) {\n if (!raw) console.log(' ' + colorize('(use --raw / -r to see full payloads, --live / -f to follow)', 90));\n return;\n }\n\n // Live tail. Follow the JSONL log; print each new entry as it arrives.\n // Returning a Promise keeps the process alive until SIGINT.\n return new Promise<void>(resolve => {\n console.log(' ' + colorize('— following new turns (Ctrl-C to exit) —', 90));\n const stop = followTurns(t => {\n console.log(' ' + formatTurn(t, raw));\n });\n const onSigint = () => {\n stop();\n process.removeListener('SIGINT', onSigint);\n resolve();\n };\n process.on('SIGINT', onSigint);\n });\n}\n\nfunction cmdAttach(rest: string[]): void {\n assertTmuxInstalled();\n const readonly = rest.some(a => a === '--readonly' || a === '-r');\n\n // Verify the session exists before tmux drops the user into a confusing state.\n const has = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n if (has.status !== 0) {\n console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);\n process.exit(1);\n }\n\n if (!process.stdout.isTTY) {\n console.error('attach requires a TTY. Run this command directly in your terminal, not piped.');\n process.exit(1);\n }\n\n console.log(`Attaching to tmux session '${TMUX_SESSION_NAME}'${readonly ? ' (read-only)' : ''}.`);\n console.log('Detach with Ctrl-B then D. (Do not press Ctrl-C — that would interrupt claude.)');\n console.log();\n\n const args = readonly\n ? ['attach-session', '-r', '-t', TMUX_SESSION_NAME]\n : ['attach-session', '-t', TMUX_SESSION_NAME];\n const r = spawnSync('tmux', args, { stdio: 'inherit' });\n process.exit(r.status ?? 0);\n}\n\nasync function cmdTest(): Promise<void> {\n console.log('Sending smoke-test grading request through the channel...');\n const result = await submitToChannel(\n 'grade-bash',\n 'Command: echo \"hello world\"\\nIntent: testing channel connectivity',\n );\n console.log('Raw reply:');\n console.log(result);\n}\n\nfunction cmdInstall(): void {\n const r = installLocalCC();\n console.log(`Reinstalled plugin at ${r.pluginPath}`);\n}\n\n/**\n * Re-export Claude Code credentials from the macOS keychain into the\n * host-side file the container bind-mounts. Called by the launchd refresh\n * agent every 6 hours, and exposed as `synkro local-cc refresh-creds` for\n * manual invocation if the user just re-authed.\n *\n * On Linux this is a no-op — creds already live in a file the container can\n * read directly via bind mount.\n */\nfunction cmdRefreshCreds(): void {\n if (!needsKeychainBridge()) {\n console.log('No-op on this platform — credentials are already file-based.');\n return;\n }\n const refreshed = refreshCreds();\n if (refreshed) {\n console.log(`Exported claude credentials to ${CLAUDE_CREDS_FILE}`);\n } else {\n console.warn('No Claude Code credentials found in the keychain.');\n console.warn('Run `claude login` first, then re-run this command.');\n process.exitCode = 1;\n }\n if (credsAreStale()) {\n console.warn('⚠ Exported credentials are older than the refresh interval.');\n }\n}\n\nexport async function localCcCommand(args: string[]): Promise<void> {\n const sub = args[0] ?? '';\n try {\n switch (sub) {\n case 'enable': await cmdEnable(); break;\n case 'disable': cmdDisable(); break;\n case 'status': await cmdStatus(); break;\n case 'start': await cmdStart(args.slice(1)); break;\n case 'stop': cmdStop(); break;\n case 'restart': await cmdRestart(args.slice(1)); break;\n case 'install': cmdInstall(); break;\n case 'logs': await cmdLogs(args.slice(1)); break;\n case 'attach': cmdAttach(args.slice(1)); break;\n case 'test': await cmdTest(); break;\n case 'refresh-creds': cmdRefreshCreds(); break;\n case '':\n case 'help':\n case '--help':\n case '-h':\n printHelp(); break;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n printHelp();\n process.exit(1);\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n","/**\n * synkro stop / start / restart — safe container lifecycle commands.\n *\n * These ensure PGLite data is always persisted to the host before shutdown,\n * and verify integrity on start. Agents should use these instead of raw\n * docker commands to avoid data corruption.\n */\nimport {\n dockerSafeStop, dockerSafeStart, dockerSafeRestart, assertDockerAvailable,\n resolveWorkerConfig, dockerUpdate, waitForContainerReady, waitForWorkersReady, readContainerConfig,\n} from '../local-cc/dockerInstall.js';\nimport { detectGitRepo, reconcileHarness, syncSkillFiles } from './install.js';\n\n/**\n * The connected repo for a container recreate. start/restart/update all run\n * from inside the repo, so detect it live; fall back to whatever the existing\n * container recorded. Without this, recreating the container drops\n * SYNKRO_CONNECTED_REPO and the dashboard loses its project anchor.\n */\nfunction resolveConnectedRepo(): string | undefined {\n return detectGitRepo() ?? readContainerConfig()?.connectedRepo ?? undefined;\n}\n\nexport async function stopCommand(): Promise<void> {\n assertDockerAvailable();\n console.log('Synkro: stopping server\\n');\n const result = await dockerSafeStop();\n if (!result.ok) {\n console.error('\\nStop failed. Check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nServer stopped.');\n}\n\nexport async function startCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n // With --workers / --provider(s) flags, recreate the container with the\n // requested pool config (container env is fixed at `docker run` time, so a\n // plain `docker start` can't change it). Without flags, plain safe-start.\n const cfg = resolveWorkerConfig(rest);\n if (cfg.explicit) {\n console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer is running.');\n return;\n }\n console.log('Synkro: starting server\\n');\n const result = await dockerSafeStart();\n if (!result.ok) {\n console.error(`\\nStart failed: ${result.error}`);\n process.exit(1);\n }\n console.log('\\nServer is running.');\n}\n\nexport async function updateCommand(): Promise<void> {\n assertDockerAvailable();\n // Read the installed container's pool config so the rebuilt container keeps\n // the same worker split instead of resetting to defaults.\n const cfg = readContainerConfig();\n if (!cfg) {\n console.error('No synkro-server container found. Run `synkro install` first.');\n process.exit(1);\n }\n const claudeWorkers = cfg.claudeWorkers ?? 8;\n const cursorWorkers = cfg.cursorWorkers ?? 0;\n console.log('Synkro: updating to the latest container image');\n console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)\\n`);\n // dockerUpdate: graceful stop (snapshot + checkpoint) → remove → pull latest → recreate.\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(90_000);\n if (!ready) {\n console.error('\\n⚠ container did not pass its health check within 90s — check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nSynkro updated — now running the latest version.');\n}\n\nexport async function restartCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n const cfg = resolveWorkerConfig(rest);\n\n // Reconcile harness from .synkro (hooks, MCP, worker allocation)\n let claudeWorkers = cfg.claudeWorkers;\n let cursorWorkers = cfg.cursorWorkers;\n if (!cfg.explicit) {\n const reconciled = reconcileHarness();\n if (reconciled) {\n claudeWorkers = reconciled.claudeWorkers;\n cursorWorkers = reconciled.cursorWorkers;\n }\n }\n\n console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer restarted successfully.');\n\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log('✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn('⚠ workers did not register within 30s — skill sync skipped');\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isAuthenticated, getAccessToken } from '../auth/stub.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nfunction updateConfigValue(key: string, value: string): void {\n if (!existsSync(CONFIG_PATH)) {\n console.error('No config found. Run `synkro install` first.');\n process.exit(1);\n }\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\n');\n const pattern = new RegExp(`^${key}=`);\n let found = false;\n const updated = lines.map((line) => {\n if (pattern.test(line.trim())) {\n found = true;\n return `${key}='${value}'`;\n }\n return line;\n });\n if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);\n writeFileSync(CONFIG_PATH, updated.join('\\n'), 'utf-8');\n}\n\n// After a grading/storage change, bring the local container in line with the\n// new modes — this is what makes switching seamless (no separate install step).\n// Cloud-only (BYOK grading + cloud storage) needs no container; every other\n// combo needs it. Only acts when crossing that boundary; wrapped so a Docker\n// hiccup never fails the config write itself.\nasync function reconcileContainer(): Promise<void> {\n const cfg = readConfigEnv();\n const cloudOnly =\n (cfg.SYNKRO_GRADING_MODE || 'local') === 'byok' &&\n (cfg.SYNKRO_STORAGE_MODE || 'local') === 'cloud';\n\n try {\n const { dockerInstall, dockerSafeStop, readContainerConfig, assertDockerAvailable, waitForContainerReady } =\n await import('../local-cc/dockerInstall.js');\n const existing = readContainerConfig();\n\n if (cloudOnly && existing) {\n console.log('Cloud-only mode — the local container is no longer needed; stopping it...');\n await dockerSafeStop();\n console.log(' ✓ container stopped (grading → BYOK, telemetry → cloud).');\n } else if (!cloudOnly && !existing) {\n assertDockerAvailable();\n console.log('This mode needs the Synkro container — provisioning...');\n const { detectGitRepo } = await import('./install.js');\n await dockerInstall({ claudeWorkers: 8, cursorWorkers: 0, connectedRepo: detectGitRepo() ?? undefined });\n const ready = await waitForContainerReady(60_000);\n console.log(ready\n ? ' ✓ container running.'\n : ' ⚠ container did not pass its health check — see `docker logs synkro-server`.');\n }\n // else: the container's presence already matches the mode — nothing to do.\n } catch (err) {\n console.warn(` ⚠ Container reconcile skipped: ${(err as Error).message}`);\n console.warn(' Run `synkro install` to (re)provision the container if needed.');\n }\n}\n\nexport async function configCommand(args: string[]): Promise<void> {\n if (args.length === 0) {\n const config = readConfigEnv();\n console.log('Synkro config:\\n');\n console.log(` grading: ${config.SYNKRO_GRADING_MODE || 'local'}`);\n console.log(` storage: ${config.SYNKRO_STORAGE_MODE || 'local'}`);\n console.log(` inference: ${config.SYNKRO_INFERENCE || 'fast'}`);\n console.log(` tier: ${config.SYNKRO_TIER || 'pro'}`);\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh'}`);\n console.log(` version: ${config.SYNKRO_VERSION || '?'}`);\n console.log(`\\nTo change:`);\n console.log(` synkro config grading <local|byok> — where grading runs`);\n console.log(` synkro config storage <local|cloud> — where telemetry is stored`);\n console.log(` synkro config --inference fast|standard`);\n return;\n }\n\n // User-owned data axes — written to config.env; the hooks load config.env at\n // process start, so the change takes effect on the next graded tool call.\n if (args[0] === 'grading') {\n const value = args[1];\n if (value !== 'local' && value !== 'byok') {\n console.error('Usage: synkro config grading <local|byok>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_GRADING_MODE', value);\n console.log(`✓ Grading mode set to '${value}'.`);\n if (value === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n }\n await reconcileContainer();\n return;\n }\n if (args[0] === 'storage') {\n const value = args[1];\n if (value !== 'local' && value !== 'cloud') {\n console.error('Usage: synkro config storage <local|cloud>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_STORAGE_MODE', value);\n console.log(`✓ Storage mode set to '${value}'.`);\n await reconcileContainer();\n return;\n }\n\n let inferenceValue: string | undefined;\n for (const a of args) {\n if (a.startsWith('--inference=')) inferenceValue = a.slice('--inference='.length);\n else if (a === '--inference' && args.indexOf(a) + 1 < args.length) inferenceValue = args[args.indexOf(a) + 1];\n }\n\n if (!inferenceValue || !['fast', 'standard'].includes(inferenceValue)) {\n console.error('Usage: synkro config --inference fast|standard');\n process.exit(1);\n }\n\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro login` first.');\n process.exit(1);\n }\n\n const token = getAccessToken();\n const config = readConfigEnv();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ fast_inference: inferenceValue === 'fast' }),\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to reach server: ${(err as Error).message}`);\n process.exit(1);\n }\n\n updateConfigValue('SYNKRO_INFERENCE', inferenceValue);\n console.log(`✓ Inference set to '${inferenceValue}'.`);\n}\n","#!/usr/bin/env node\n/**\n * Synkro CLI bootstrap.\n *\n * Loads .env, then dispatches to the right subcommand.\n */\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst envCandidates = [\n resolve(process.env.HOME ?? '', '.synkro', 'config.env'),\n];\nfor (const envPath of envCandidates) {\n if (!existsSync(envPath)) continue;\n const envContent = readFileSync(envPath, 'utf-8');\n for (const line of envContent.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex <= 0) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n const value = trimmed.slice(eqIndex + 1).trim().replace(/^['\"]|['\"]$/g, '');\n if (!process.env[key] && !value.startsWith('op://')) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\n\nfunction printVersion() {\n console.log(__SYNKRO_CLI_VERSION__);\n}\n\nfunction printHelp() {\n console.log(`Synkro CLI — runtime safety for AI coding agents\n\nUsage:\n synkro <command> [options]\n\nCommands:\n install [--force] Install or update Synkro\n uninstall [--purge] Remove Synkro (keeps scan data; --purge wipes that too)\n stop Gracefully stop the server (snapshot + checkpoint)\n start [opts] Start the server (with pgdata integrity check)\n restart [opts] Safe restart (stop → start, data preserved)\n update Pull the latest container image and safely restart\n config Show or change grading + storage modes\n version Show version\n\n config:\n synkro config show current settings\n synkro config grading <local|byok> where grading runs\n synkro config storage <local|cloud> where telemetry is stored\n\n start/restart opts (recreate the worker pool):\n --workers N total grader workers (default 8, even-split)\n --providers a,b grading agents: claude, cursor (or both)\n e.g. synkro restart --workers 16 --providers claude,cursor\n\nQuick start:\n $ synkro install # one-time setup\n $ claude # use Claude Code normally; Synkro judges in real time\n`);\n}\n\nasync function main() {\n switch (cmd) {\n case 'install': {\n const { installCommand, parseArgs } = await import('./commands/install.js');\n await installCommand(parseArgs(subArgs));\n break;\n }\n case 'uninstall':\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n disconnectCommand(subArgs);\n break;\n }\n case 'login': {\n const { authenticate, isAuthenticated } = await import('./auth/stub.js');\n if (isAuthenticated()) {\n console.log('Already authenticated.');\n } else {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n if (status.phase === 'success') console.log(' ✓ Authenticated');\n else if (status.phase === 'error') console.error(' ✗ ' + status.message);\n });\n if (!result) { console.error('Authentication failed.'); process.exit(1); }\n }\n break;\n }\n case 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(subArgs);\n break;\n }\n case 'local-cc': {\n const { localCcCommand } = await import('./commands/localCc.js');\n await localCcCommand(subArgs);\n break;\n }\n case 'version':\n case '--version':\n case '-v': {\n printVersion();\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n case '': {\n printHelp();\n break;\n }\n case 'stop': {\n const { stopCommand } = await import('./commands/lifecycle.js');\n await stopCommand();\n break;\n }\n case 'start': {\n const { startCommand } = await import('./commands/lifecycle.js');\n await startCommand(args.slice(1));\n break;\n }\n case 'restart': {\n const { restartCommand } = await import('./commands/lifecycle.js');\n await restartCommand(args.slice(1));\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/lifecycle.js');\n await updateCommand();\n break;\n }\n case 'config': {\n const { configCommand } = await import('./commands/config.js');\n await configCommand(args.slice(1));\n break;\n }\n default: {\n console.error(`Unknown command: ${cmd}`);\n printHelp();\n process.exit(1);\n }\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA88EzB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuQzB,IAAM,kBAAkB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqjB/B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8RxB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0HxB,IAAM,gBAAgB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8S7B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgMvB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgMtB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwExB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+FzB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoEzB,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkE3B,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgD9B,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["cmd","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","dirname","homedir","isSynkroEntry","SYNKRO_MARKER","removeSynkroEntries","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","url","jwt","existsSync","resolve","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","join","dirname","args","resolve","existsSync","mkdirSync","writeFileSync","execSync","join","execSync","createServer","resolve","SYNKRO_WEB_AUTH_URL","openBrowser","execFile","RAW_WEB_AUTH_URL","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","platform","join","SYNKRO_DIR","existsSync","mkdirSync","readFileSync","readdirSync","homedir","join","execSync","spawnSync","which","args","createInterface","execSync","existsSync","readFileSync","unlinkSync","homedir","platform","join","execFile","resolve","openBrowser","args","SYNKRO_DIR","jwt","detectGitRepo","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","readdirSync","homedir","join","execSync","createInterface","ask","resolve","cursorApiKeyConfigured","writeCursorApiKey","validateCursorApiKey","SYNKRO_DIR","CONFIG_PATH","assertDockerAvailable","setupGithubCommand","parseSynkroYaml","cmd","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","copyFileSync","renameSync","unlinkSync","join","homedir","spawnSync","init_install","existsSync","readdirSync","homedir","join","spawnSync","createInterface","resolve","args","SYNKRO_DIR","init_install","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","resolve","args","execFileSync","spawnSync","homedir","join","connect","args","resolve","SESSION_DIR","SESSION_DIR_2","SESSION_DIR_3","SESSION_DIR_4","existsSync","readFileSync","homedir","join","CONFIG_PATH","spawnSync","homedir","join","existsSync","readFileSync","writeFileSync","CONFIG_PATH","jwt","ready","resolve","args","init_install","detectGitRepo","readFileSync","writeFileSync","existsSync","join","homedir","CONFIG_PATH","dockerInstall","dockerSafeStop","readContainerConfig","assertDockerAvailable","waitForContainerReady","detectGitRepo","args","config","SYNKRO_DIR","readFileSync","existsSync","resolve","printHelp","installCommand","parseArgs","disconnectCommand","authenticate","isAuthenticated","gradeCommand","localCcCommand","stopCommand","startCommand","restartCommand","updateCommand","configCommand"]}
1
+ {"version":3,"sources":["../cli/installer/agentDetect.ts","../cli/installer/ccHookConfig.ts","../cli/installer/cursorHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/skillParser.ts","../cli/installer/hookScripts.ts","../cli/installer/hookScriptsTs.ts","../cli/auth/stub.ts","../cli/auth/index.ts","../cli/api/projects.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/repoConnect.ts","../cli/installer/promptFetcher.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.ts","../cli/commands/setupGithub.ts","../cli/commands/install.ts","../cli/local-cc/install.ts","../cli/commands/disconnect.ts","../cli/local-cc/turnLog.ts","../cli/local-cc/client.ts","../cli/commands/grade.ts","../cli/local-cc/pueue.ts","../cli/local-cc/settings.ts","../cli/commands/localCc.ts","../cli/commands/lifecycle.ts","../cli/commands/config.ts","../cli/bootstrap.js"],"sourcesContent":["/**\n * Detect which AI coding agents are installed on the user's machine.\n *\n * Returns a list of agents with their config paths so the installer\n * knows where to write hook configs.\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execFileSync, execSync } from 'node:child_process';\n\nexport type AgentKind = 'claude_code' | 'cursor';\n\nexport interface DetectedAgent {\n kind: AgentKind;\n name: string;\n binaryPath?: string;\n configDir: string;\n settingsPath: string;\n version?: string;\n}\n\nfunction which(cmd: string): string | undefined {\n try {\n const result = execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();\n return result || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getVersion(cmd: string): string | undefined {\n try {\n const result = execFileSync(cmd, ['--version'], { encoding: 'utf-8', timeout: 5000 }).trim();\n const line = result.split('\\n')[0];\n return line.replace(/\\s*\\(Claude Code\\)/, '');\n } catch {\n return undefined;\n }\n}\n\nexport function detectAgents(): DetectedAgent[] {\n const agents: DetectedAgent[] = [];\n const home = homedir();\n\n // Claude Code — require the binary on PATH; ~/.claude/ alone is not enough\n // (Synkro and other tools create that directory).\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary) {\n agents.push({\n kind: 'claude_code',\n name: 'Claude Code',\n binaryPath: claudeBinary,\n configDir: claudeConfigDir,\n settingsPath: join(claudeConfigDir, 'settings.json'),\n version: getVersion('claude'),\n });\n }\n\n // Cursor — GUI app, rarely has a `cursor` binary on PATH. Check the app\n // bundle (/Applications/Cursor.app on macOS) and ~/.cursor/ (created by\n // Cursor on first launch) as additional signals.\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n const cursorApp = process.platform === 'darwin' && existsSync('/Applications/Cursor.app');\n if (cursorBinary || cursorApp || existsSync(cursorConfigDir)) {\n let version: string | undefined;\n if (cursorBinary) {\n version = getVersion('cursor');\n } else if (cursorApp) {\n try {\n const plist = execSync('defaults read /Applications/Cursor.app/Contents/Info CFBundleShortVersionString 2>/dev/null', { encoding: 'utf-8', timeout: 3000 }).trim();\n if (plist) version = plist;\n } catch {}\n }\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version,\n });\n }\n\n return agents;\n}\n\nexport function findClaudeAuth(): { path: string; exists: boolean } {\n // CC stores OAuth token in macOS keychain (item: \"Claude Code-credentials\")\n // or in ~/.claude/auth.json on Linux. We don't extract it; we ask user to\n // run `claude setup-token` for headless use.\n const authJsonPath = join(homedir(), '.claude', 'auth.json');\n return { path: authJsonPath, exists: existsSync(authJsonPath) };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.claude/settings.json.\n *\n * Preserves any other hooks the user has configured (Corridor, Noma, custom\n * scripts, etc.) — we only add our entries, never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport interface SynkroHookConfig {\n bashJudgeScriptPath: string;\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n transcriptSyncScriptPath: string;\n userPromptSubmitScriptPath: string;\n installScanScriptPath: string;\n skipTranscriptSync?: boolean;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\ninterface HookEntry {\n matcher?: string;\n hooks: Array<Record<string, unknown>>;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CCSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n PostToolUse?: HookEntry[];\n SessionEnd?: HookEntry[];\n SessionStart?: HookEntry[];\n Stop?: HookEntry[];\n [k: string]: unknown;\n };\n [k: string]: unknown;\n}\n\nfunction readSettings(path: string): CCSettings {\n if (!existsSync(path)) return {};\n try {\n const raw = readFileSync(path, 'utf-8');\n return JSON.parse(raw) as CCSettings;\n } catch (err) {\n throw new Error(`Failed to parse ${path}: ${(err as Error).message}`);\n }\n}\n\nfunction writeSettingsAtomic(path: string, settings: CCSettings): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmpPath = `${path}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, path);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];\n return hooks.some((h: any) =>\n typeof h?.command === 'string' && h.command.includes('/.synkro/hooks/'),\n );\n}\n\nfunction removeSynkroEntries(events: CCSettings['hooks'] extends infer H ? H : never, eventName: string): void {\n if (!events) return;\n const arr = (events as any)[eventName];\n if (!Array.isArray(arr)) return;\n (events as any)[eventName] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\n/**\n * Merge Synkro hooks into the settings file. Idempotent — replaces any\n * existing Synkro-managed entries with the new versions.\n */\nexport function installCCHooks(settingsPath: string, config: SynkroHookConfig): void {\n const settings = readSettings(settingsPath);\n settings.hooks = settings.hooks ?? {};\n\n // Remove any prior Synkro entries (to support `synkro update`)\n removeSynkroEntries(settings.hooks as any, 'PreToolUse');\n removeSynkroEntries(settings.hooks as any, 'PostToolUse');\n removeSynkroEntries(settings.hooks as any, 'SessionEnd');\n removeSynkroEntries(settings.hooks as any, 'SessionStart');\n removeSynkroEntries(settings.hooks as any, 'UserPromptSubmit');\n // Also clean up any older `Stop`-event entry from earlier v1.6 builds.\n removeSynkroEntries(settings.hooks as any, 'Stop');\n\n settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];\n settings.hooks.PostToolUse = settings.hooks.PostToolUse ?? [];\n settings.hooks.SessionEnd = settings.hooks.SessionEnd ?? [];\n settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];\n settings.hooks.UserPromptSubmit = (settings.hooks.UserPromptSubmit as any[]) ?? [];\n\n // PreToolUse Bash → install scan (caches result for bash judge to read)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.installScanScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Bash/Read/Grep/Glob → command hook script (Cerebras-judged)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash|Read|Grep|Glob',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 30,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → two hooks in ONE entry so CC waits for all\n // before processing deny decisions. Each hook runs in parallel:\n // 1. edit-precheck: org rules grading on channel 1 (port 8929)\n // 2. cve-precheck: CVE/OSV dependency scan (curl, no LLM)\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 30,\n },\n {\n type: 'command',\n command: config.cwePrecheckScriptPath,\n timeout: 30,\n },\n {\n type: 'command',\n command: config.cvePrecheckScriptPath,\n timeout: 10,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Agent → scan subagent prompts against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'Agent',\n hooks: [\n {\n type: 'command',\n command: config.agentJudgeScriptPath,\n timeout: 30,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse ExitPlanMode → advisory plan review against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'ExitPlanMode',\n hooks: [\n {\n type: 'command',\n command: config.planJudgeScriptPath,\n timeout: 45,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PostToolUse Edit/Write removed — PreToolUse hooks handle grading now.\n\n // PostToolUse Bash → flips pending precheck_corrections row to 'allow'\n // once the bash command actually executed. Required for the bash trendline\n // (approved-vs-rejected) the dashboard reads from precheck_corrections.\n settings.hooks.PostToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashFollowupScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionEnd → end-of-session summary line (`[synkro] stop → N findings: ...`).\n // We use SessionEnd, not Stop, because Stop fires after every agent turn\n // (which would spam the user in interactive mode); SessionEnd fires once\n // when the session itself terminates.\n settings.hooks.SessionEnd.push({\n hooks: [\n {\n type: 'command',\n command: config.stopSummaryScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionStart → \"[synkro] session start → N open findings in this repo\" if any.\n settings.hooks.SessionStart.push({\n hooks: [\n {\n type: 'command',\n command: config.sessionStartScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // UserPromptSubmit → captures explicit consent keywords from user messages.\n // Writes a file-based grant so the next blocked tool call is allowed through.\n (settings.hooks.UserPromptSubmit as any[]).push({\n hooks: [\n {\n type: 'command',\n command: config.userPromptSubmitScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Stop → usage telemetry + optional transcript sync (fires after every agent turn).\n // Always installed: usage tracking is ungated; transcript sync is gated inside the script.\n settings.hooks.Stop = settings.hooks.Stop ?? [];\n removeSynkroEntries(settings.hooks as any, 'Stop');\n settings.hooks.Stop.push({\n hooks: [\n {\n type: 'command',\n command: config.transcriptSyncScriptPath,\n timeout: 3,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n writeSettingsAtomic(settingsPath, settings);\n}\n\n/**\n * Remove all Synkro-managed hook entries from settings.json.\n * Used by `synkro disconnect`.\n */\nexport function uninstallCCHooks(settingsPath: string): boolean {\n if (!existsSync(settingsPath)) return false;\n const settings = readSettings(settingsPath);\n if (!settings.hooks) return false;\n\n const events = ['PreToolUse', 'PostToolUse', 'SessionEnd', 'SessionStart', 'Stop', 'UserPromptSubmit'] as const;\n for (const evt of events) {\n removeSynkroEntries(settings.hooks as any, evt);\n }\n\n // If a hook event array is now empty, delete it\n for (const evt of events) {\n if (Array.isArray((settings.hooks as any)[evt]) && (settings.hooks as any)[evt].length === 0) {\n delete (settings.hooks as any)[evt];\n }\n }\n // If hooks object is now empty, delete it\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n writeSettingsAtomic(settingsPath, settings);\n return true;\n}\n\n/**\n * Check whether Synkro hooks are currently installed in settings.json.\n * Used by `synkro status`.\n */\nexport function inspectCCHooks(settingsPath: string): {\n installed: boolean;\n preToolUseBash: boolean;\n postToolUseEdit: boolean;\n sessionEnd: boolean;\n sessionStart: boolean;\n} {\n if (!existsSync(settingsPath)) {\n return { installed: false, preToolUseBash: false, postToolUseEdit: false, sessionEnd: false, sessionStart: false };\n }\n const settings = readSettings(settingsPath);\n const pre = (settings.hooks as any)?.PreToolUse ?? [];\n const post = (settings.hooks as any)?.PostToolUse ?? [];\n const sessionEndHooks = (settings.hooks as any)?.SessionEnd ?? [];\n const sessionStartHooks = (settings.hooks as any)?.SessionStart ?? [];\n const preToolUseBash = pre.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const postToolUseEdit = post.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionEnd = sessionEndHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionStart = sessionStartHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n return {\n installed: preToolUseBash || postToolUseEdit || sessionEnd || sessionStart,\n preToolUseBash,\n postToolUseEdit,\n sessionEnd,\n sessionStart,\n };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.cursor/hooks.json.\n *\n * Cursor hooks reuse the same TypeScript scripts as Claude Code (cc-*.ts),\n * run with SYNKRO_HOOK_FORMAT=cursor so _synkro-common translates CC JSON output\n * to Cursor's permission JSON for preToolUse. CC install is unchanged.\n */\nimport { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve, normalize } from 'node:path';\nimport { homedir } from 'node:os';\n\nexport interface CursorHookConfig {\n /** Cursor-specific bash/shell judge (preToolUse with Shell|Bash matcher) */\n bashJudgeScriptPath: string;\n /** Cursor-specific post-edit capture (afterFileEdit event shape) */\n editCaptureScriptPath: string;\n /** Capture afterAgentThought/afterAgentResponse before transcript redaction */\n agentCaptureScriptPath: string;\n /** Shared cc-*.ts scripts (invoked with SYNKRO_HOOK_FORMAT=cursor) */\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n userPromptSubmitScriptPath: string;\n transcriptSyncScriptPath: string;\n installScanScriptPath: string;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Run a shared cc-*.ts hook with Cursor output translation. */\nfunction cursorCcCmd(scriptPath: string): string {\n return 'env SYNKRO_HOOK_FORMAT=cursor bun run ' + shellQuote(scriptPath);\n}\n\nfunction bunRunCmd(scriptPath: string): string {\n return 'bun run ' + shellQuote(scriptPath);\n}\n\nconst ALLOWED_PARENT_DIRS = [\n resolve(homedir(), '.cursor'),\n resolve(homedir(), '.config', 'cursor'),\n];\n\nfunction validateHooksPath(path: string): string {\n const resolved = resolve(normalize(path));\n if (!ALLOWED_PARENT_DIRS.some(dir => resolved.startsWith(dir + '/') || resolved === dir)) {\n throw new Error(`Hooks path must be under ~/.cursor or ~/.config/cursor, got: ${resolved}`);\n }\n return resolved;\n}\n\ninterface CursorHookEntry {\n command: string;\n timeout?: number;\n failClosed?: boolean;\n matcher?: string;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CursorHooksFile {\n version?: number;\n hooks?: {\n [event: string]: CursorHookEntry[];\n };\n}\n\nfunction readHooksFile(rawPath: string): CursorHooksFile {\n const safePath = validateHooksPath(rawPath);\n try {\n const raw = readFileSync(safePath, 'utf-8');\n return JSON.parse(raw) as CursorHooksFile;\n } catch (err: any) {\n if (err?.code === 'ENOENT') return { version: 1, hooks: {} };\n throw new Error(`Failed to parse ${safePath}: ${(err as Error).message}`);\n }\n}\n\nfunction writeHooksFileAtomic(rawPath: string, data: CursorHooksFile): void {\n const safePath = validateHooksPath(rawPath);\n mkdirSync(dirname(safePath), { recursive: true });\n const tmpPath = `${safePath}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, safePath);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n return typeof entry?.command === 'string' && entry.command.includes('/.synkro/hooks/');\n}\n\nconst ALL_EVENTS = [\n 'sessionStart', 'sessionEnd', 'beforeSubmitPrompt', 'stop',\n 'beforeShellExecution', 'afterShellExecution',\n 'preToolUse', 'afterFileEdit', 'postToolUse',\n 'afterAgentThought', 'afterAgentResponse',\n];\n\nfunction removeSynkroEntries(hooks: CursorHooksFile['hooks'], event: string): void {\n if (!hooks) return;\n const arr = hooks[event];\n if (!Array.isArray(arr)) return;\n hooks[event] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\nfunction pushCcHook(\n hooks: CursorHooksFile['hooks'],\n event: string,\n scriptPath: string,\n opts: { timeout: number; matcher?: string; failClosed?: boolean },\n): void {\n hooks![event] = hooks![event] ?? [];\n hooks![event]!.push({\n command: cursorCcCmd(scriptPath),\n timeout: opts.timeout,\n failClosed: opts.failClosed ?? false,\n ...(opts.matcher ? { matcher: opts.matcher } : {}),\n [SYNKRO_MARKER]: true,\n });\n}\n\nexport function installCursorHooks(hooksJsonPath: string, config: CursorHookConfig): void {\n const file = readHooksFile(hooksJsonPath);\n file.version = file.version ?? 1;\n file.hooks = file.hooks ?? {};\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n const h = file.hooks;\n\n pushCcHook(h, 'sessionStart', config.sessionStartScriptPath, { timeout: 5 });\n pushCcHook(h, 'sessionEnd', config.stopSummaryScriptPath, { timeout: 10 });\n pushCcHook(h, 'beforeSubmitPrompt', config.userPromptSubmitScriptPath, { timeout: 5 });\n pushCcHook(h, 'stop', config.transcriptSyncScriptPath, { timeout: 3 });\n\n // beforeShellExecution: install scan runs first (caches result for bash judge)\n h.beforeShellExecution = h.beforeShellExecution ?? [];\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 60,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'afterShellExecution', config.bashFollowupScriptPath, { timeout: 10 });\n\n // preToolUse: install scan before bash judge (caches result for bash judge)\n h.preToolUse = h.preToolUse ?? [];\n h.preToolUse.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 60,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 15,\n failClosed: false,\n matcher: 'Shell|Bash|Read|ReadFile|Grep|Glob|terminal|run_terminal_cmd|execute_command|read_file|grep_search|file_search|list_dir|codebase_search|delete_file',\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'preToolUse', config.editPrecheckScriptPath, {\n timeout: 15,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cwePrecheckScriptPath, {\n timeout: 60,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cvePrecheckScriptPath, {\n timeout: 20,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.agentJudgeScriptPath, {\n timeout: 15,\n matcher: 'Agent|Task',\n });\n pushCcHook(h, 'preToolUse', config.planJudgeScriptPath, {\n timeout: 20,\n matcher: 'ExitPlanMode|SwitchMode|CreatePlan',\n });\n\n h.afterFileEdit = h.afterFileEdit ?? [];\n h.afterFileEdit.push({\n command: bunRunCmd(config.editCaptureScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'postToolUse', config.bashFollowupScriptPath, {\n timeout: 10,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command|delete_file',\n });\n\n h.afterAgentThought = h.afterAgentThought ?? [];\n h.afterAgentThought.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n h.afterAgentResponse = h.afterAgentResponse ?? [];\n h.afterAgentResponse.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n writeHooksFileAtomic(hooksJsonPath, file);\n}\n\nexport function uninstallCursorHooks(hooksJsonPath: string): boolean {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return false;\n }\n if (!file.hooks) return false;\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n for (const evt of ALL_EVENTS) {\n if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {\n delete file.hooks[evt];\n }\n }\n if (Object.keys(file.hooks).length === 0) {\n delete file.hooks;\n }\n\n writeHooksFileAtomic(hooksJsonPath, file);\n return true;\n}\n\nfunction preToolUseUsesScript(hooks: CursorHookEntry[] | undefined, scriptBasename: string): boolean {\n return (hooks ?? []).some((e) =>\n isSynkroEntry(e) && typeof e.command === 'string' && e.command.includes(scriptBasename),\n );\n}\n\nexport function inspectCursorHooks(hooksJsonPath: string): {\n installed: boolean;\n sessionStart: boolean;\n sessionEnd: boolean;\n beforeSubmitPrompt: boolean;\n stop: boolean;\n beforeShellExecution: boolean;\n afterShellExecution: boolean;\n preToolUse: boolean;\n preToolUseBash: boolean;\n preToolUseEdit: boolean;\n preToolUseCwe: boolean;\n preToolUseCve: boolean;\n preToolUseAgent: boolean;\n preToolUsePlan: boolean;\n afterFileEdit: boolean;\n postToolUse: boolean;\n} {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return {\n installed: false,\n sessionStart: false, sessionEnd: false, beforeSubmitPrompt: false, stop: false,\n beforeShellExecution: false, afterShellExecution: false,\n preToolUse: false, preToolUseBash: false, preToolUseEdit: false,\n preToolUseCwe: false, preToolUseCve: false, preToolUseAgent: false, preToolUsePlan: false,\n afterFileEdit: false, postToolUse: false,\n };\n }\n const h = file.hooks ?? {};\n const sessionStart = (h.sessionStart ?? []).some((e) => isSynkroEntry(e));\n const sessionEnd = (h.sessionEnd ?? []).some((e) => isSynkroEntry(e));\n const beforeSubmitPrompt = (h.beforeSubmitPrompt ?? []).some((e) => isSynkroEntry(e));\n const stop = (h.stop ?? []).some((e) => isSynkroEntry(e));\n const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry(e));\n const afterShellExecution = (h.afterShellExecution ?? []).some((e) => isSynkroEntry(e));\n const pre = h.preToolUse ?? [];\n const preToolUseBash = preToolUseUsesScript(pre, 'cc-bash-judge') || preToolUseUsesScript(pre, 'cursor-bash-judge');\n const preToolUseEdit = preToolUseUsesScript(pre, 'cc-edit-precheck') || preToolUseUsesScript(pre, 'cursor-edit-precheck');\n const preToolUseCwe = preToolUseUsesScript(pre, 'cc-cwe-precheck');\n const preToolUseCve = preToolUseUsesScript(pre, 'cc-cve-precheck');\n const preToolUseAgent = preToolUseUsesScript(pre, 'cc-agent-judge');\n const preToolUsePlan = preToolUseUsesScript(pre, 'cc-plan-judge');\n const preToolUse = preToolUseBash || preToolUseEdit || preToolUseCwe || preToolUseCve || preToolUseAgent || preToolUsePlan;\n const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry(e));\n const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry(e));\n return {\n installed: sessionStart || sessionEnd || beforeSubmitPrompt || stop\n || beforeShellExecution || afterShellExecution || preToolUse || afterFileEdit || postToolUse,\n sessionStart, sessionEnd, beforeSubmitPrompt, stop,\n beforeShellExecution, afterShellExecution,\n preToolUse, preToolUseBash, preToolUseEdit, preToolUseCwe, preToolUseCve, preToolUseAgent, preToolUsePlan,\n afterFileEdit, postToolUse,\n };\n}\n","/**\n * Atomically merge the Synkro guardrails MCP server entry into ~/.claude.json.\n *\n * CC's MCP config lives in ~/.claude.json (NOT ~/.claude/settings.json — those\n * are different files). It accepts an HTTP-transport server with a static\n * Authorization header set at config-write time. We register one entry,\n * `synkro-guardrails`, marked with `__synkro_managed__: true` so we can safely\n * remove it on `synkro disconnect` without touching the user's other servers.\n *\n * Mirrors the pattern in ccHookConfig.ts — read-modify-write atomically via\n * tmpfile + rename; preserve any other top-level keys; never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nconst SYNKRO_MARKER = '__synkro_managed__';\nconst SYNKRO_SERVER_NAME = 'synkro-guardrails';\nconst CC_CONFIG_PATH = join(homedir(), '.claude.json');\n\ninterface ClaudeJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\ninterface McpServerEntry {\n type?: 'http' | 'stdio' | 'sse';\n url?: string;\n command?: string;\n args?: string[];\n headers?: Record<string, string>;\n env?: Record<string, string>;\n [SYNKRO_MARKER]?: boolean;\n [k: string]: unknown;\n}\n\nfunction readClaudeJson(): ClaudeJson {\n if (!existsSync(CC_CONFIG_PATH)) return {};\n try {\n const raw = readFileSync(CC_CONFIG_PATH, 'utf-8');\n return JSON.parse(raw) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeClaudeJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CC_CONFIG_PATH), { recursive: true });\n const tmpPath = `${CC_CONFIG_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CC_CONFIG_PATH);\n}\n\nexport interface InstallMcpOptions {\n gatewayUrl: string; // e.g. http://localhost:8788\n // Long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails. Minted by\n // POST /api/v1/cli/mcp-token during install. We deliberately do NOT write\n // the WorkOS access token here anymore — that one expires in 5 min and\n // silently breaks the CC MCP connection.\n bearerToken: string;\n local?: boolean;\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.claude.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readClaudeJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Remove any prior Synkro-managed entry (so re-running install picks up\n // a refreshed JWT). Leave non-Synkro entries alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n const proxyScript = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'stdio',\n command: 'bun',\n args: ['run', proxyScript],\n [SYNKRO_MARKER]: true,\n };\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url: `stdio://${proxyScript}` };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'http',\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.claude.json.\n * Returns true if anything was removed.\n */\nexport function uninstallMcpConfig(): boolean {\n if (!existsSync(CC_CONFIG_PATH)) return false;\n const config = readClaudeJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n // If the mcpServers object is now empty, drop it to keep the file tidy.\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeClaudeJsonAtomic(config);\n return true;\n}\n\n/**\n * Inspect whether the Synkro MCP server entry is currently registered.\n */\nexport function inspectMcpConfig(): {\n installed: boolean;\n configPath: string;\n url?: string;\n} {\n if (!existsSync(CC_CONFIG_PATH)) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n const config = readClaudeJson();\n const entry = config.mcpServers?.[SYNKRO_SERVER_NAME];\n if (!entry || entry[SYNKRO_MARKER] !== true) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n return { installed: true, configPath: CC_CONFIG_PATH, url: entry.url };\n}\n\n// ─── Cursor MCP Config ───\n\nconst CURSOR_MCP_PATH = join(homedir(), '.cursor', 'mcp.json');\n\ninterface CursorMcpJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\nfunction readCursorMcpJson(): CursorMcpJson {\n if (!existsSync(CURSOR_MCP_PATH)) return {};\n try {\n const raw = readFileSync(CURSOR_MCP_PATH, 'utf-8');\n return JSON.parse(raw) as CursorMcpJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeCursorMcpJsonAtomic(config: CursorMcpJson): void {\n mkdirSync(dirname(CURSOR_MCP_PATH), { recursive: true });\n const tmpPath = `${CURSOR_MCP_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CURSOR_MCP_PATH);\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.cursor/mcp.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installCursorMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readCursorMcpJson();\n config.mcpServers = config.mcpServers ?? {};\n\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n // Honors SYNKRO_MCP_PORT so the containerised deployment can register\n // its host-mapped port (e.g. 18931) instead of the bare-host default.\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n const url = `http://127.0.0.1:${port}/`;\n const jwtPath = join(homedir(), '.synkro', '.mcp-jwt');\n let jwt = '';\n try { jwt = readFileSync(jwtPath, 'utf-8').trim(); } catch {}\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n ...(jwt ? { headers: { Authorization: `Bearer ${jwt}` } } : {}),\n [SYNKRO_MARKER]: true,\n };\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.cursor/mcp.json.\n */\nexport function uninstallCursorMcpConfig(): boolean {\n if (!existsSync(CURSOR_MCP_PATH)) return false;\n const config = readCursorMcpJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeCursorMcpJsonAtomic(config);\n return true;\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport function resolveSkillPaths(skills: string[], repoRoot: string): string[] {\n return skills\n .filter(s => s.endsWith('.md'))\n .map(s => resolve(repoRoot, s))\n .filter(p => existsSync(p));\n}\n","// :)\n/**\n * Bash hook scripts for Cursor IDE adapter and shared common utilities.\n *\n * CC hooks have been moved to hookScriptsTs.ts (TypeScript + Bun runtime).\n * This file retains the bash common script (sourced by Cursor hooks) and\n * the Cursor-specific adapter scripts.\n */\n\nexport const SYNKRO_COMMON_SCRIPT = `#!/bin/bash\n# Shared Synkro hook utilities — sourced by all hook scripts.\n\nsynkro_log() { echo \"[synkro] $1\" >&2; }\n\n# Load config\n_SYNKRO_CONFIG=\"$HOME/.synkro/config.env\"\nif [ -f \"$_SYNKRO_CONFIG\" ]; then\n set -a; . \"$_SYNKRO_CONFIG\"; set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nsynkro_load_jwt() {\n if [ ! -f \"$CREDS_PATH\" ]; then echo \"\"; return 1; fi\n jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null\n}\n\nsynkro_refresh_jwt() {\n # Lock via mkdir (atomic on all Unix including macOS — no flock needed)\n local lockdir=\"\\${CREDS_PATH}.lockdir\"\n if ! mkdir \"$lockdir\" 2>/dev/null; then\n # Another hook is refreshing — wait and re-read\n local _w=0\n while [ -d \"$lockdir\" ] && [ $_w -lt 5 ]; do sleep 0.5; _w=$((_w+1)); done\n JWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n return 0\n fi\n trap \"rmdir \\\\\"$lockdir\\\\\" 2>/dev/null\" RETURN\n\n # Re-check expiry — another hook may have just refreshed\n local p2 exp2 now2\n p2=$(printf '%s' \"$JWT\" | cut -d. -f2)\n case $((\\${#p2} % 4)) in 2) p2=\"\\${p2}==\";; 3) p2=\"\\${p2}=\";; esac\n exp2=$(printf '%s' \"$p2\" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)\n now2=$(date -u +%s)\n if [ $((exp2 - now2)) -ge 60 ]; then return 0; fi\n\n local rt\n rt=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$rt\" ]; then return 1; fi\n local resp\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$(jq -n --arg rt \"$rt\" '{refresh_token:$rt}')\" \\\\\n --max-time 4 2>/dev/null)\n local new_at\n new_at=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_at\" ]; then return 1; fi\n local new_rt\n new_rt=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n [ -z \"$new_rt\" ] && new_rt=\"$rt\"\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n local existing\n existing=$(cat \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$existing\" ] || ! echo \"$existing\" | jq -e '.' >/dev/null 2>&1; then\n existing='{}'\n fi\n echo \"$existing\" | jq --arg at \"$new_at\" --arg rt \"$new_rt\" '. + {access_token:$at,refresh_token:$rt}' > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_at\"\n}\n\nsynkro_ensure_fresh_jwt() {\n [ -z \"$JWT\" ] && return 1\n local p exp now\n p=$(printf '%s' \"$JWT\" | cut -d. -f2)\n case $((\\${#p} % 4)) in 2) p=\"\\${p}==\";; 3) p=\"\\${p}=\";; esac\n exp=$(printf '%s' \"$p\" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)\n now=$(date -u +%s)\n [ $((exp - now)) -lt 60 ] && synkro_refresh_jwt\n}\n\nsynkro_detect_repo() {\n local cwd=\"\\${1:-.}\"\n if command -v git >/dev/null 2>&1; then\n local r\n r=$(git -C \"$cwd\" remote get-url origin 2>/dev/null || true)\n [ -n \"$r\" ] && echo \"$r\" | sed -E 's|^git@[^:]+:||; s|^https?://[^/]+/||; s|\\\\.git$||' && return\n fi\n echo \"\"\n}\n\nsynkro_channel_up() {\n (exec 3<>/dev/tcp/127.0.0.1/\\${SYNKRO_CHANNEL_PORT:-8929}) 2>/dev/null && exec 3<&- 3>&-\n}\n\n# Fetch hook config. Sets SYNKRO_CAPTURE_DEPTH, SYNKRO_TIER, SYNKRO_RULES, SYNKRO_SILENT, SYNKRO_POLICY_NAME.\n_SYNKRO_MCP_JWT_FILE=\"$HOME/.synkro/.mcp-jwt\"\n\nsynkro_load_config() {\n local resp\n resp=$(curl -sS \"\\${GATEWAY_URL}/api/v1/hook/config\\${1:+?$1}\" -H \"Authorization: Bearer $JWT\" --max-time 4 2>/dev/null || echo \"\")\n if [ -z \"$resp\" ]; then return; fi\n SYNKRO_CAPTURE_DEPTH=$(echo \"$resp\" | jq -r '.capture_depth // \"local_only\"' 2>/dev/null)\n SYNKRO_TIER=$(echo \"$resp\" | jq -r '.tier // \"standard\"' 2>/dev/null)\n SYNKRO_SILENT=$(echo \"$resp\" | jq -r '.silent_mode // false' 2>/dev/null)\n SYNKRO_POLICY_NAME=$(echo \"$resp\" | jq -r '.active_policy_name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$resp\" | jq -c '[.rules[]? | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n}\n\nsynkro_local_capture() {\n local event_json=\"$1\"\n local ts mcp_token line\n ts=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\")\n line=$(echo \"$event_json\" | jq -c --arg ts \"$ts\" '. + {_ts: $ts}' 2>/dev/null)\n [ -z \"$line\" ] && return\n [ -f \"$_SYNKRO_MCP_JWT_FILE\" ] || return\n mcp_token=$(cat \"$_SYNKRO_MCP_JWT_FILE\" 2>/dev/null)\n [ -z \"$mcp_token\" ] && return\n curl -fsS -m 2 -X POST \"http://127.0.0.1:\\${SYNKRO_MCP_PORT:-8931}/api/ingest\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer \\${mcp_token}\" \\\\\n -d \"$(jq -nc --argjson e \"$line\" '{data: $e}')\" >/dev/null 2>&1 &\n}\n\nsynkro_tag() {\n if [ \"$SYNKRO_SILENT\" = \"true\" ]; then echo \"[synkro:silent]\"; return; fi\n local route=\"\\${1:-\\$(synkro_route)}\"\n local rs=\"\\${SYNKRO_POLICY_NAME:-all}\"\n echo \"[synkro:\\${route}:\\${rs}]\"\n}\n\nsynkro_route() {\n [ \"$SYNKRO_CAPTURE_DEPTH\" = \"local_only\" ] && echo \"local\" && return\n synkro_channel_up && echo \"local\" && return\n echo \"cloud\"\n}\n\nSYNKRO_CONSENT_FILE=\"$HOME/.synkro/.local-consent\"\n\n_TAB=\\$(printf '\\\\t')\n\nsynkro_consent_grant() {\n local sid=\"\\$1\" hash=\"\\$2\"\n printf '%s\\\\t%s\\\\tactive\\\\n' \"$sid\" \"$hash\" >> \"$SYNKRO_CONSENT_FILE\" 2>/dev/null || true\n}\n\nsynkro_consent_has_active() {\n local sid=\"\\$1\" hash=\"\\$2\"\n grep -q \"^\\${sid}\\${_TAB}\\${hash}\\${_TAB}active\\$\" \"$SYNKRO_CONSENT_FILE\" 2>/dev/null\n}\n\nsynkro_consent_consume() {\n local sid=\"\\$1\" hash=\"\\$2\"\n [ ! -f \"$SYNKRO_CONSENT_FILE\" ] && return\n local tmp=\"\\${SYNKRO_CONSENT_FILE}.tmp\"\n local pat=\"\\${sid}\\${_TAB}\\${hash}\\${_TAB}active\"\n local rep=\"\\${sid}\\${_TAB}\\${hash}\\${_TAB}consumed\"\n awk -v p=\"$pat\" -v r=\"$rep\" '{if(\\$0==p)print r;else print}' \"$SYNKRO_CONSENT_FILE\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$SYNKRO_CONSENT_FILE\" 2>/dev/null || true\n}\n\nsynkro_post_with_retry() {\n local url=\"$1\" body=\"$2\" timeout=\"\\${3:-8}\"\n local resp\n resp=$(curl -sS -X POST \"$url\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$body\" --max-time \"$timeout\" 2>/dev/null || echo \"\")\n if echo \"$resp\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if synkro_refresh_jwt; then\n resp=$(curl -sS -X POST \"$url\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$body\" --max-time \"$timeout\" 2>/dev/null || echo \"\")\n fi\n fi\n echo \"$resp\"\n}\n`;\n\n\n// ─── Cursor IDE adapter scripts (legacy bash — install writes TypeScript from hookScriptsTs.ts) ───\n\nexport const CURSOR_BASH_JUDGE_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\nsynkro_ensure_fresh_jwt\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nCOMMAND=$(echo \"$PAYLOAD\" | jq -r '.command // empty' 2>/dev/null)\nif [ -z \"$COMMAND\" ]; then echo '{}'; exit 0; fi\n\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\n\nCMD_SHORT=$(printf '%s' \"$COMMAND\" | head -c 80)\nsynkro_log \"bashGuard checking: $CMD_SHORT\"\n\nsynkro_load_config\nif [ \"$SYNKRO_SILENT\" = \"true\" ]; then\n echo '{}'; exit 0\nfi\n\nBODY=$(jq -n \\\\\n --arg cmd \"$COMMAND\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg repo \"$GIT_REPO\" \\\\\n '{\n hook_event: \"PreToolUse\",\n tool_name: \"Bash\",\n tool_input: {command: $cmd},\n response_format: \"cursor\",\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n repo: (if ($repo | length) > 0 then $repo else null end)\n }')\n\nRESP=$(synkro_post_with_retry \"\\${GATEWAY_URL}/api/v1/hook/judge\" \"$BODY\" 6)\n\nif [ -z \"$RESP\" ]; then\n synkro_log \"bashGuard $CMD_SHORT → error (timeout)\"\n echo '{}'; exit 0\nfi\n\n# Server returns cursor-format directly in hook_response\nif echo \"$RESP\" | jq -e '.hook_response' >/dev/null 2>&1; then\n echo \"$RESP\" | jq -c '.hook_response'\nelse\n echo '{}'\nfi\nexit 0\n`;\n\nexport const CURSOR_EDIT_PRECHECK_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\nsynkro_ensure_fresh_jwt\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\n\nFILE_PATH=$(echo \"$PAYLOAD\" | jq -r '.tool_input.file_path // .tool_input.path // .tool_input.target_file // empty' 2>/dev/null)\nCONTENT=$(echo \"$PAYLOAD\" | jq -r '.tool_input.content // .tool_input.new_string // .tool_input.code_edit // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then echo '{}'; exit 0; fi\n\nBASENAME=$(basename \"$FILE_PATH\" 2>/dev/null || echo \"$FILE_PATH\")\nsynkro_log \"editGuard checking: $BASENAME\"\n\nsynkro_load_config\nif [ \"$SYNKRO_SILENT\" = \"true\" ]; then\n echo '{}'; exit 0\nfi\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg content \"$CONTENT\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg repo \"$GIT_REPO\" \\\\\n '{\n hook_event: \"PreToolUse\",\n tool_name: \"Edit\",\n tool_input: {file_path: $file_path, content: $content},\n file_path: $file_path,\n content: $content,\n response_format: \"cursor\",\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n repo: (if ($repo | length) > 0 then $repo else null end)\n }')\n\nRESP=$(synkro_post_with_retry \"\\${GATEWAY_URL}/api/v1/hook/judge\" \"$BODY\" 8)\n\nif [ -z \"$RESP\" ]; then\n synkro_log \"editGuard $BASENAME → error (timeout)\"\n echo '{}'; exit 0\nfi\n\nif echo \"$RESP\" | jq -e '.hook_response' >/dev/null 2>&1; then\n echo \"$RESP\" | jq -c '.hook_response'\nelse\n echo '{}'\nfi\nexit 0\n`;\n\nexport const CURSOR_EDIT_CAPTURE_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then echo '{}'; exit 0; fi\n\nFILE_PATH=$(echo \"$PAYLOAD\" | jq -r '.file_path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then echo '{}'; exit 0; fi\n\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // .workspace_roots[0] // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nGIT_REPO=$(synkro_detect_repo \"\\${CWD:-.}\")\nBASENAME=$(basename \"$FILE_PATH\" 2>/dev/null || echo \"$FILE_PATH\")\n\nFULL_PATH=\"$FILE_PATH\"\n[ -n \"$CWD\" ] && FULL_PATH=\"$CWD/$FILE_PATH\"\nFULL_CONTENT=\"\"\n[ -f \"$FULL_PATH\" ] && FULL_CONTENT=$(head -c 50000 \"$FULL_PATH\" 2>/dev/null || true)\n\nDEPS_JSON=\"{}\"\n_PKG_DIR=\"\\${CWD:-.}\"\nwhile [ \"$_PKG_DIR\" != \"/\" ]; do\n if [ -f \"$_PKG_DIR/package.json\" ]; then\n DEPS_JSON=$(jq -c '(.dependencies // {}) + (.devDependencies // {})' \"$_PKG_DIR/package.json\" 2>/dev/null || echo \"{}\")\n break\n fi\n _PKG_DIR=$(dirname \"$_PKG_DIR\")\ndone\n\nsynkro_log \"editScan $BASENAME\"\n\n(\n BODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" --arg content \"$FULL_CONTENT\" \\\\\n --arg session_id \"$SESSION_ID\" --arg cwd \"$CWD\" --arg repo \"$GIT_REPO\" \\\\\n --argjson deps \"$DEPS_JSON\" \\\\\n '{capture_type:\"edit_scan\",tool_input:{file_path:$file_path,content:$content},edit_verdict:{ok:true},dependencies:$deps}\n + (if ($session_id | length) > 0 then {session_id:$session_id} else {} end)\n + (if ($cwd | length) > 0 then {cwd:$cwd} else {} end)\n + (if ($repo | length) > 0 then {repo:$repo} else {} end)')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/hook/capture\" \\\\\n -H \"Content-Type: application/json\" -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" --max-time 10 >/dev/null 2>&1 || true\n) &\ndisown 2>/dev/null || true\n\necho '{}'\nexit 0\n`;\n\nexport const CURSOR_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n. \"$SCRIPT_DIR/_synkro-common.sh\"\n\nJWT=$(synkro_load_jwt)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in Shell|Bash|terminal|run_terminal_cmd|execute_command) ;; *) echo '{}'; exit 0 ;; esac\n\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.conversation_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\n\nIS_ERROR=$(echo \"$PAYLOAD\" | jq -r '.tool_result.is_error // false' 2>/dev/null)\nCMD=$(echo \"$PAYLOAD\" | jq -r '.tool_input.command // empty' 2>/dev/null)\nCMD_HASH=\"\"\nif [ -n \"$CMD\" ]; then\n CMD_HASH=$(printf '%s' \"$CMD\" | shasum -a 256 | cut -c1-16)\nfi\n\nif [ -n \"$CMD_HASH\" ] && [ -n \"$SESSION_ID\" ]; then\n if [ \"$IS_ERROR\" = \"false\" ]; then\n synkro_consent_consume \"$SESSION_ID\" \"$CMD_HASH\"\n else\n if ! synkro_consent_has_active \"$SESSION_ID\" \"$CMD_HASH\"; then\n synkro_consent_grant \"$SESSION_ID\" \"$CMD_HASH\"\n fi\n fi\nfi\n\nif [ -n \"$SESSION_ID\" ] && [ -n \"$TOOL_USE_ID\" ]; then\n (\n BODY=$(jq -n --arg sid \"$SESSION_ID\" --arg tid \"$TOOL_USE_ID\" \\\\\n --argjson err \"$IS_ERROR\" --arg ch \"$CMD_HASH\" \\\\\n '{capture_type:\"bash_followup\",session_id:$sid,tool_use_id:$tid,is_error:$err,command_hash:$ch}')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/hook/capture\" \\\\\n -H \"Content-Type: application/json\" -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" --max-time 3 >/dev/null 2>&1 || true\n ) &\n disown 2>/dev/null || true\nfi\n\necho '{}'\nexit 0\n`;\n","// :)\n/**\n * TypeScript hook scripts for Bun runtime — written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Each export is the full source code of a TypeScript file that runs via `#!/usr/bin/env bun`.\n * All grading, classification, CVE scanning, and persistence lives server-side.\n * Local mode: grade via local-cc channel; rules + embeddings from local PGLite only.\n */\n\nexport const SYNKRO_COMMON_TS = `\n// Shared Synkro hook utilities — imported by all hook scripts.\nimport { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, renameSync, openSync, closeSync, unlinkSync, readdirSync, statSync, createReadStream } from 'node:fs';\nimport { createInterface } from 'node:readline';\nimport { join, dirname, basename, extname, resolve as resolvePath } from 'node:path';\nimport { homedir } from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { constants as FS_CONSTANTS } from 'node:fs';\n\n// ─── Config ───\n\nconst HOME = homedir();\nconst CONFIG_PATH = join(HOME, '.synkro', 'config.env');\n\n// Load config.env into process.env\nif (existsSync(CONFIG_PATH)) {\n try {\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx < 1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n let val = trimmed.slice(eqIdx + 1).trim();\n // Strip surrounding quotes\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1);\n }\n process.env[key] = val;\n }\n } catch {}\n}\n\nconst ALLOWED_GATEWAY_HOSTS = new Set(['api.synkro.sh', 'localhost', '127.0.0.1']);\nfunction validateGatewayUrl(raw: string): string {\n try {\n const u = new URL(raw);\n if (!ALLOWED_GATEWAY_HOSTS.has(u.hostname)) return 'https://api.synkro.sh';\n return raw.replace(/\\\\/+$/, '');\n } catch {\n return 'https://api.synkro.sh';\n }\n}\nexport const GATEWAY_URL = validateGatewayUrl(process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh');\nexport const CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(HOME, '.synkro', 'credentials.json');\nconst LAST_PROMPT_FILE = join(HOME, '.synkro', '.last-prompt');\n\n// ─── Path Validation ───\n\nexport function isPathUnder(filePath: string, cwd: string): boolean {\n if (!filePath || !cwd) return false;\n const resolved = resolvePath(filePath);\n const base = resolvePath(cwd);\n return resolved.startsWith(base + '/') || resolved === base;\n}\n\nconst SHELL_CODE_FILE_EXT = /\\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte)$/i;\n\nexport interface ShellCodeWrite {\n filePath: string;\n content: string;\n}\n\n/** Detect shell commands that write/rewrite source files (closes Edit-tool CWE bypass). */\nexport function extractShellCodeWrites(command: string, cwd: string): ShellCodeWrite[] {\n if (!command.trim()) return [];\n const writes: ShellCodeWrite[] = [];\n const seen = new Set<string>();\n\n function add(rawPath: string, content: string) {\n const trimmed = rawPath.trim().replace(/^['\"]|['\"]$/g, '');\n if (!trimmed) return;\n const resolved = trimmed.startsWith('/') ? resolvePath(trimmed) : resolvePath(cwd || '.', trimmed);\n if (!SHELL_CODE_FILE_EXT.test(resolved)) return;\n const key = resolved;\n if (seen.has(key)) return;\n seen.add(key);\n writes.push({ filePath: resolved, content: content.slice(0, 6000) });\n }\n\n const heredocBodies: string[] = [];\n const heredocRe = /<<-?\\\\s*['\"]?(\\\\w+)['\"]?\\\\s*\\\\n([\\\\s\\\\S]*?)\\\\n\\\\1\\\\b/g;\n let hm: RegExpExecArray | null;\n while ((hm = heredocRe.exec(command)) !== null) {\n heredocBodies.push(hm[2]);\n }\n const body = heredocBodies.length > 0 ? heredocBodies.join('\\\\n\\\\n') : command;\n\n for (const m of command.matchAll(/Path\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]\\\\s*\\\\)/g)) add(m[1], body);\n for (const m of command.matchAll(/writeFileSync\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]/g)) add(m[1], body);\n for (const m of command.matchAll(/writeFile\\\\s*\\\\(\\\\s*['\"]([^'\"]+)['\"]/g)) add(m[1], body);\n for (const m of command.matchAll(/(?:^|[\\\\s;|])(?:>>?)\\\\s*([^\\\\s;&|]+\\\\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte))\\\\b/gim)) {\n add(m[1], body);\n }\n for (const m of command.matchAll(/\\\\btee(?:\\\\s+-a)?\\\\s+([^\\\\s;&|]+\\\\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|java|rb|php|rs|vue|svelte))\\\\b/gi)) {\n add(m[1], body);\n }\n\n return writes;\n}\n\n// ─── Logging ───\n\n// Hooks must keep stderr quiet for non-error paths. Claude Code's PreToolUse\n// hook protocol surfaces any stderr from a non-blocking hook as a\n// \"PreToolUse:Bash hook error\" toast — even though the hook returned a\n// non-blocking status. That toast interleaves with the tool-call header in the\n// terminal and mangles the rendered output. Route routine progress lines to a\n// rolling file under ~/.synkro/ so we keep the diagnostic trail without\n// polluting the agent UI. Stderr is reserved for hard failures (caught at the\n// top-level catch in each hook).\nconst HOOK_LOG_PATH = join(HOME, '.synkro', '.hooks.log');\nconst HOOK_LOG_MAX_BYTES = 2 * 1024 * 1024;\n\nexport function log(msg: string): void {\n // \\`SYNKRO_HOOK_DEBUG=1\\` mirrors to stderr — useful when iterating on hook\n // logic, off by default so normal sessions don't get toasts.\n if (process.env.SYNKRO_HOOK_DEBUG === '1') {\n process.stderr.write('[synkro] ' + msg + '\\\\n');\n }\n try {\n if (existsSync(HOOK_LOG_PATH)) {\n const sz = statSync(HOOK_LOG_PATH).size;\n if (sz > HOOK_LOG_MAX_BYTES) {\n try { renameSync(HOOK_LOG_PATH, HOOK_LOG_PATH + '.1'); } catch {}\n }\n }\n appendFileSync(HOOK_LOG_PATH, new Date().toISOString() + ' [synkro] ' + msg + '\\\\n', 'utf-8');\n } catch {}\n}\n\n// ─── JWT Management ───\n\nexport function loadJwt(): string | null {\n try {\n if (!existsSync(CREDS_PATH)) return null;\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));\n return creds.access_token || null;\n } catch {\n return null;\n }\n}\n\nfunction decodeJwtExp(jwt: string): number {\n try {\n const parts = jwt.split('.');\n if (parts.length < 2) return 0;\n let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n while (payload.length % 4) payload += '=';\n const decoded = Buffer.from(payload, 'base64').toString('utf-8');\n const obj = JSON.parse(decoded);\n return obj.exp || 0;\n } catch {\n return 0;\n }\n}\n\nexport async function refreshJwt(jwt: string): Promise<string> {\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));\n const rt = creds.refresh_token;\n if (!rt) return jwt;\n\n const resp = await fetch(GATEWAY_URL + '/api/auth/refresh', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: rt }),\n signal: AbortSignal.timeout(4000),\n });\n\n if (!resp.ok) {\n log('refresh failed: HTTP ' + resp.status);\n return jwt;\n }\n\n const data = await resp.json() as any;\n const newAt = data.access_token;\n if (!newAt) return jwt;\n\n const newRt = data.refresh_token || rt;\n const existing = (() => {\n try { return JSON.parse(readFileSync(CREDS_PATH, 'utf-8')); } catch { return {}; }\n })();\n const updated = { ...existing, access_token: newAt, refresh_token: newRt };\n const tmp = CREDS_PATH + '.synkro.tmp';\n writeFileSync(tmp, JSON.stringify(updated, null, 2));\n renameSync(tmp, CREDS_PATH);\n return newAt;\n}\n\nfunction jwtIsExpired(jwt: string): boolean {\n const exp = decodeJwtExp(jwt);\n return exp - Math.floor(Date.now() / 1000) < 60;\n}\n\nfunction lockIsStale(lockfile: string, maxAgeMs = 8000): boolean {\n try {\n const stat = require('node:fs').statSync(lockfile);\n return Date.now() - stat.mtimeMs > maxAgeMs;\n } catch {\n return false;\n }\n}\n\nexport async function ensureFreshJwt(jwt: string): Promise<string> {\n if (!jwt) return jwt;\n if (!jwtIsExpired(jwt)) return jwt;\n\n const lockfile = CREDS_PATH + '.lock';\n\n // Clean stale lock left by a killed hook\n if (existsSync(lockfile) && lockIsStale(lockfile)) {\n try { unlinkSync(lockfile); } catch {}\n }\n\n let fd = -1;\n try {\n fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);\n } catch {\n // Another process is refreshing — wait for it\n for (let i = 0; i < 8; i++) {\n await new Promise(r => setTimeout(r, 500));\n if (!existsSync(lockfile)) break;\n }\n // Stale lock after full wait — force-remove\n if (existsSync(lockfile) && lockIsStale(lockfile)) {\n try { unlinkSync(lockfile); } catch {}\n }\n // Re-read — the winner should have written a fresh JWT\n const fresh = loadJwt();\n if (fresh && !jwtIsExpired(fresh)) return fresh;\n // Winner's refresh failed — try to acquire lock ourselves\n try {\n fd = openSync(lockfile, FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL, 0o644);\n } catch {\n return fresh || jwt;\n }\n }\n\n try {\n // Re-check — another hook may have written fresh creds while we waited for lock\n const freshJwt = loadJwt();\n if (freshJwt && !jwtIsExpired(freshJwt)) return freshJwt;\n return await refreshJwt(jwt);\n } catch {\n return jwt;\n } finally {\n try { closeSync(fd); } catch {}\n try { unlinkSync(lockfile); } catch {}\n }\n}\n\n// ─── Repo Detection ───\n\nconst REPO_CACHE_PATH = join(homedir(), '.synkro', '.last-repo');\n\nexport function readCachedRepo(): string {\n try { return readFileSync(REPO_CACHE_PATH, 'utf-8').trim(); } catch { return ''; }\n}\n\nexport function writeCachedRepo(repo: string): void {\n if (!repo) return;\n try { writeFileSync(REPO_CACHE_PATH, repo, 'utf-8'); } catch {}\n}\n\nfunction repoFromGitDir(cwd: string): string {\n if (!cwd) return '';\n try {\n const url = execSync('git remote get-url origin 2>/dev/null', { cwd, timeout: 3000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (url) {\n return url\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\\\/\\\\/[^/]+\\\\//, '')\n .replace(/\\\\.git$/, '');\n }\n } catch {}\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (root) return root.split('/').pop() || '';\n } catch {}\n return '';\n}\n\n// Repo resolution order:\n// 1. explicit cwd from the hook payload\n// 2. workspace_roots (Cursor sends this; CC does not)\n// 3. CC transcript-path slug (~/.claude/projects/-Users-m-foo/...)\n// 4. hook process cwd\n// 5. absolute paths parsed out of the command (cat /abs/path, edit /abs/file, etc.)\n// Steps 1-4 try \\`git remote get-url origin\\` then \\`git rev-parse --show-toplevel\\`.\n// Step 5 climbs up the parent dirs of each parsed path until one exists, then\n// tries the same git lookups. Returns '' if nothing resolves — the server then\n// stores 'local' which the UI renders as \"—\".\nexport function detectRepo(\n cwd: string,\n transcriptPath?: string,\n command?: string,\n workspaceRoots?: string[],\n): string {\n const candidates: string[] = [];\n if (cwd) candidates.push(cwd);\n if (workspaceRoots && Array.isArray(workspaceRoots)) {\n for (const r of workspaceRoots) {\n if (typeof r === 'string' && r) candidates.push(r);\n }\n }\n if (transcriptPath) {\n const ccSlug = transcriptPath.match(/\\\\/projects\\\\/(-[^/]+)\\\\//);\n if (ccSlug) candidates.push('/' + ccSlug[1].slice(1).replace(/-/g, '/'));\n }\n candidates.push(process.cwd());\n\n const tried = new Set<string>();\n for (const c of candidates) {\n if (tried.has(c)) continue;\n tried.add(c);\n const repo = repoFromGitDir(c);\n if (repo) return repo;\n }\n\n if (command) {\n const seen = new Set<string>();\n const re = /(?:^|[\\\\s=:\"'(\\\\[])((?:\\\\/|~\\\\/)[A-Za-z0-9_.\\\\-/]+)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(command)) !== null) {\n let p = m[1];\n if (p.startsWith('~/')) p = HOME + p.slice(1);\n if (seen.has(p)) continue;\n seen.add(p);\n let dir = p;\n for (let i = 0; i < 8; i++) {\n try {\n if (existsSync(dir) && statSync(dir).isDirectory()) break;\n } catch {}\n const idx = dir.lastIndexOf('/');\n if (idx <= 0) { dir = ''; break; }\n dir = dir.slice(0, idx);\n }\n if (!dir || tried.has(dir)) continue;\n tried.add(dir);\n const repo = repoFromGitDir(dir);\n if (repo) return repo;\n }\n }\n return '';\n}\n\n// ─── Git Root ───\n\nexport function findGitRoot(cwd: string): string {\n if (!cwd) return '';\n try {\n return execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch { return ''; }\n}\n\n// ─── .synkro file ───\n\nexport interface SynkroFileConfig {\n version: number;\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };\n workers: { claude?: number; cursor?: number };\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n}\n\nconst SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {\n version: 1,\n harness: ['claude-code', 'cursor'],\n grader: { pool: 'auto' },\n workers: {},\n ruleset: 'default',\n skills: [],\n scanning: { cwe: true, cve: true },\n};\n\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n\n if (line.match(/^\\\\S/) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null;\n currentArr = null;\n\n const colonIdx = line.indexOf(':');\n const key = line.slice(0, colonIdx).trim();\n const val = line.slice(colonIdx + 1).trim();\n currentKey = key;\n\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (line.match(/^ - /)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (line.match(/^ \\\\S/) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const colonIdx = line.indexOf(':');\n const k = line.slice(0, colonIdx).trim();\n const v = line.slice(colonIdx + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nlet _synkroFileCache: SynkroFileConfig | undefined;\n\nexport function loadSynkroFile(cwd?: string): SynkroFileConfig {\n if (_synkroFileCache) return _synkroFileCache;\n const root = cwd ? findGitRoot(cwd) : '';\n if (!root) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }\n const fp = root + '/.synkro';\n try {\n if (!existsSync(fp)) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }\n const raw = readFileSync(fp, 'utf-8');\n const parsed = raw.trimStart().startsWith('{') ? JSON.parse(raw) : parseSynkroYaml(raw);\n const validHarness = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => validHarness.includes(h as any))\n : ['claude-code', 'cursor'];\n _synkroFileCache = {\n version: parsed.version || 1,\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,\n },\n workers: {\n ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),\n ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),\n },\n ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],\n scanning: {\n cwe: parsed.scanning?.cwe !== false,\n cve: parsed.scanning?.cve !== false,\n },\n };\n return _synkroFileCache;\n } catch {\n _synkroFileCache = SYNKRO_FILE_DEFAULTS;\n return _synkroFileCache;\n }\n}\n\nexport function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind): AgentKind {\n if (synkroFile.grader.pool === 'auto') return hookAgentKind;\n if (synkroFile.grader.pool === 'claude') return 'claude_code';\n return 'cursor';\n}\n\n// ─── Channel Health ───\n\nexport async function channelUp(port = 18929): Promise<boolean> {\n return new Promise(resolve => {\n const sock = require('node:net').connect(port, '127.0.0.1');\n const done = (ok: boolean) => { try { sock.destroy(); } catch {} resolve(ok); };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(3000, () => done(false));\n });\n}\n\nexport async function cweChannelUp(): Promise<boolean> {\n return channelUp(18930);\n}\n\n// ─── Mode Normalization ───\n\nexport function normalizeMode(m?: string): 'ask' | 'fix' {\n if (m === 'blocking' || m === 'ask') return 'ask';\n if (m === 'audit' || m === 'fix') return 'fix';\n return 'ask';\n}\n\n// ─── Config Loading ───\n\nexport interface RuleExample {\n text: string;\n verdict: 'violation' | 'ok';\n note?: string;\n}\n\nexport interface Rule {\n rule_id: string;\n text: string;\n severity: string;\n category: string;\n mode: string;\n examples?: RuleExample[];\n}\n\nexport interface HookConfig {\n captureDepth: string;\n tier: string;\n silent: boolean;\n policyName: string;\n rules: Rule[];\n scanExemptions: Array<{ path: string; cwe_id: string }>;\n // User-owned data axes. gradingMode: 'local' (container worker pool) | 'byok'\n // (LLM API with the user's own key). storageMode: 'local' (PGLite) | 'cloud'\n // (Timescale). Sourced from config.env (the installed choice); the server\n // value from /v1/hook/config is only a fallback when the env var is unset.\n gradingMode: string;\n storageMode: string;\n}\n\n/** True when telemetry + rules must stay on-machine (PGLite). Default: local. */\nexport function isLocalStorageMode(): boolean {\n return (process.env.SYNKRO_STORAGE_MODE || 'local') === 'local';\n}\n\nfunction mapHookRules(raw: unknown[]): Rule[] {\n return raw.map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: normalizeMode(r.mode),\n examples: Array.isArray(r.examples) ? r.examples : undefined,\n }));\n}\n\nexport async function loadConfig(jwt: string, query?: string): Promise<HookConfig> {\n const config: HookConfig = {\n captureDepth: 'local_only',\n tier: 'standard',\n silent: false,\n policyName: '',\n rules: [],\n scanExemptions: [],\n gradingMode: process.env.SYNKRO_GRADING_MODE || 'local',\n storageMode: process.env.SYNKRO_STORAGE_MODE || 'local',\n };\n\n // Kick the telemetry spool drainer. Fire-and-forget: it runs concurrently\n // with the grade that follows this call, so it adds no latency to the hook.\n drainSpool().catch(() => {});\n\n // Local-first: fetch from the local MCP server (PGLite-backed) — zero network egress.\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/hook-config', {\n signal: AbortSignal.timeout(1500),\n });\n if (resp.ok) {\n const raw = await resp.json() as any;\n const policy = raw.policy || null;\n if (policy) {\n config.policyName = policy.name || '';\n config.rules = mapHookRules(policy.rules || []);\n }\n config.silent = raw.silent === true;\n if (Array.isArray(raw.scan_exemptions)) {\n config.scanExemptions = raw.scan_exemptions\n .filter((e: any) => e && typeof e.path === 'string')\n .map((e: any) => ({ path: e.path, cwe_id: e.cwe_id || '' }));\n }\n return config;\n }\n } catch {}\n\n if (isLocalStorageMode()) {\n log('hook-config: local PGLite unavailable — skipping cloud rules fallback');\n return config;\n }\n\n // Cloud storage mode only: bootstrap rules from the gateway.\n try {\n const url = GATEWAY_URL + '/api/v1/hook/config' + (query ? '?' + query : '');\n const resp = await fetch(url, {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(4000),\n });\n const data = await resp.json() as any;\n config.captureDepth = data.capture_depth || 'local_only';\n config.tier = data.tier || 'standard';\n config.silent = data.silent_mode === true || data.silent_mode === 'true';\n if (!process.env.SYNKRO_GRADING_MODE && data.grading_mode) config.gradingMode = data.grading_mode;\n if (!process.env.SYNKRO_STORAGE_MODE && data.storage_mode) config.storageMode = data.storage_mode;\n config.policyName = data.active_policy_name || '';\n if (Array.isArray(data.scan_exemptions)) {\n config.scanExemptions = data.scan_exemptions\n .filter((e: any) => e && typeof e.path === 'string')\n .map((e: any) => ({ path: e.path, cwe_id: e.cwe_id || '' }));\n }\n if (Array.isArray(data.rules)) config.rules = mapHookRules(data.rules);\n } catch {}\n return config;\n}\n\n// ─── Routing ───\n\nexport async function route(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'cloud'> {\n const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';\n if (gradingMode === 'byok') return 'cloud';\n if (config.captureDepth === 'local_only') return 'local';\n if (await channelUp()) return 'local';\n return 'cloud';\n}\n\nexport async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {\n const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';\n if (gradingMode === 'byok') return 'byok';\n if (await cweChannelUp()) return 'local';\n return 'skip';\n}\n\n// ─── Tag Building ───\n\nexport function tag(rt: string, config: HookConfig, grader?: string): string {\n if (config.silent) return '[synkro:silent]';\n const rs = config.policyName || 'all';\n const g = grader ? ':' + (grader === 'claude_code' ? 'claude' : grader) : '';\n return '[synkro:' + rt + ':' + rs + g + ']';\n}\n\n// ─── Local Grading (direct channel call) ───\n\ntype GradeRole = 'grade-edit' | 'grade-bash' | 'grade-plan' | 'grade-cwe';\n\n// Which coding agent fired this grade. The dispatcher routes grades to a\n// worker pool of the matching kind so a Cursor grade is judged by Cursor and\n// a Claude grade by Claude. Defaults to 'claude_code' so existing cc-* hook\n// call sites need no change; cursor-* hooks pass 'cursor' explicitly.\nexport type AgentKind = 'claude_code' | 'cursor';\n\nconst ROLE_MAP: Record<string, GradeRole> = {\n edit: 'grade-edit', bash: 'grade-bash', plan: 'grade-plan', cwe: 'grade-cwe',\n};\n\nasync function channelGrade(role: GradeRole, prompt: string, _jwt: string, port: number, timeoutMs = 30000, agentKind: AgentKind = 'claude_code'): Promise<string> {\n const body = JSON.stringify({ role, payload: prompt, content: prompt, agent_kind: agentKind });\n\n const resp = await fetch('http://127.0.0.1:' + port + '/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error('channel ' + resp.status + ': ' + text.slice(0, 200));\n }\n\n const data = await resp.json() as { result?: string; error?: string };\n if (data.error) throw new Error(data.error);\n return String(data.result || '');\n}\n\n// BYOK grading — grade via the cloud /v1/grade endpoint, which runs the same\n// grader prompt through an LLM API using the org's own provider key. Any\n// non-2xx (incl. 422 when no key is configured) throws, so the caller's\n// existing catch falls open — never a hard block on a grader error.\nasync function cloudGrade(surface: string, prompt: string, jwt: string, timeoutMs: number): Promise<string> {\n const resp = await fetch(GATEWAY_URL + '/api/v1/grade', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ surface, grader_prompt: prompt }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error('cloud grade ' + resp.status + ': ' + text.slice(0, 200));\n }\n const data = await resp.json() as { verdict?: string };\n return String(data.verdict || '');\n}\n\nexport async function localGrade(surface: string, prompt: string, timeoutMs = 30000, agentKind: AgentKind = 'claude_code'): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n // BYOK grading mode routes the grade through an LLM API instead of the\n // on-device channel worker pool. The grader prompt + parseVerdict are shared.\n if ((process.env.SYNKRO_GRADING_MODE || 'local') === 'byok') {\n return cloudGrade(surface, prompt, jwt, timeoutMs);\n }\n if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');\n return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs, agentKind);\n}\n\nexport async function localGradeCwe(prompt: string, agentKind: AgentKind = 'claude_code', timeoutMs = 45000): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade('grade-cwe', prompt, jwt, 18930, timeoutMs, agentKind);\n}\n\n// ─── Rule Pre-Filter (embedding-based) ───\n\n/** User message + action for embedding search — intent surfaces boundary/consent rules the command alone misses. */\nexport function ruleFilterText(action: string, userMessage?: string | null): string {\n const user = (userMessage || '').trim();\n const act = (action || '').trim();\n if (!user) return act.slice(0, 4000);\n if (!act) return user.slice(0, 4000);\n return (user + '\\\\n' + act).slice(0, 4000);\n}\n\nexport async function filterRules(commandText: string, allRules: Rule[]): Promise<Rule[]> {\n if (allRules.length <= 3) return allRules;\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/filter-rules', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text: commandText, top_k: 3 }),\n signal: AbortSignal.timeout(500),\n });\n if (!resp.ok) return allRules;\n const data = await resp.json() as { rules?: Array<Record<string, unknown>> };\n if (!data.rules || data.rules.length === 0) return allRules;\n // Local PGLite owns the rule set — trust filter-rules output directly.\n if (isLocalStorageMode()) return mapHookRules(data.rules);\n const selectedIds = new Set(data.rules.map(r => String(r.rule_id || '')));\n return allRules.filter(r => selectedIds.has(r.rule_id));\n } catch {\n return allRules;\n }\n}\n\n// ─── Safe-read short-circuit (shared by cc-bash-judge + cursor-bash-judge) ───\n// Read-only tool calls, and bash pipelines where every segment is a pure\n// in-repo read, are allowed instantly without an LLM grade. Strict by design:\n// any $ / backtick / redirect / shell metachar, any non-whitelisted verb, any\n// .. traversal, or any absolute path outside repoRoot falls through to the judge.\n\nconst SAFE_READ_TOOLS = new Set([\n 'Read', 'ReadFile', 'read_file', 'Grep', 'grep_search', 'codebase_search',\n 'file_search', 'Glob', 'list_dir',\n]);\nconst SAFE_SHELL_TOOLS = new Set([\n 'Bash', 'Shell', 'terminal', 'run_terminal_cmd', 'execute_command',\n]);\n\nfunction isSafeBashSegment(seg: string, repoRoot: string): boolean {\n const UNSAFE_CHARS = ['>', ';', '&', '\\`', '$'];\n for (const ch of UNSAFE_CHARS) { if (seg.indexOf(ch) !== -1) return false; }\n const padded = ' ' + seg + ' ';\n const UNSAFE_WORDS = [\n ' sudo ', ' su ', ' rm ', ' mv ', ' cp ', ' chmod ', ' chown ',\n ' tee ', ' kill ', ' sed -i', ' sed --in-place',\n ' sh -c', ' bash -c', ' zsh -c', ' eval ', ' exec ',\n ];\n for (const w of UNSAFE_WORDS) { if (padded.indexOf(w) !== -1) return false; }\n const SAFE_VERBS = new Set([\n 'cat','head','tail','less','more','grep','egrep','fgrep','rg','ag',\n 'find','fd','ls','wc','cmp','diff','file','stat','which','whereis','type',\n 'pwd','whoami','id','date','echo','printf','true','false',\n 'jq','yq','sort','uniq','cut','tr','xxd','hexdump','od','column',\n 'node','npm','pnpm','yarn','bun','python','python3','ruby','go','rustc','cargo',\n 'git',\n ]);\n const tokens = seg.trim().split(' ').filter(t => t.length > 0);\n const verb = tokens[0] || '';\n if (!SAFE_VERBS.has(verb)) return false;\n if (verb === 'find' || verb === 'fd') {\n const BAD = new Set([\n '-exec','-execdir','-ok','-okdir','-delete',\n '-fprint','-fprintf','-fprint0','-fls','--exec','--exec-batch',\n ]);\n for (const t of tokens) { if (BAD.has(t)) return false; }\n }\n if (verb === 'git') {\n const SAFE_GIT = new Set([\n 'log','show','diff','blame','status','rev-parse',\n 'ls-files','ls-tree','cat-file','shortlog','reflog',\n 'describe','symbolic-ref','--version',\n ]);\n const sub = tokens[1] || '';\n if (!SAFE_GIT.has(sub)) return false;\n } else if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {\n const sub = tokens[1] || '';\n const SAFE_PKG = new Set([\n '--version','-v','version','list','ls','why','view','show','info','outdated',\n '-h','--help','help',\n ]);\n if (!SAFE_PKG.has(sub)) return false;\n } else if (['node','python','python3','ruby','rustc'].includes(verb)) {\n const sub = tokens[1] || '';\n if (sub !== '--version' && sub !== '-v' && sub !== '-V') return false;\n }\n if (!repoRoot) return false;\n for (let i = 1; i < tokens.length; i++) {\n const stripped = tokens[i].replace(/^['\"]/, '').replace(/['\"]$/, '');\n if (stripped.startsWith('~')) return false;\n // Reject any .. traversal segment — a relative path with .. can escape the\n // repo root just as easily as an absolute one.\n if (stripped.split('/').some(p => p === '..')) return false;\n if (stripped.startsWith('/') && !isPathUnder(stripped, repoRoot)) return false;\n }\n return true;\n}\n\nexport function isSafeInRepoRead(toolName: string, command: string, repoRoot: string): boolean {\n // Read/Grep/Glob are synthesized into cat/grep/find commands — validate paths\n // the same way as bash reads instead of blanket-allowing every tool call.\n if (SAFE_READ_TOOLS.has(toolName)) {\n if (!command || !repoRoot) return false;\n return isSafeBashSegment(command.trim(), repoRoot);\n }\n if (!SAFE_SHELL_TOOLS.has(toolName)) return false;\n if (!command || !repoRoot) return false;\n const segments = command.split('|');\n for (const seg of segments) {\n const t = seg.trim();\n if (t.length === 0) return false;\n if (!isSafeBashSegment(t, repoRoot)) return false;\n }\n return true;\n}\n\n// ─── Install protection: server-side pkg-scan (shared by both bash judges) ───\n// Parses an install command, scans the packages for CVEs / typosquats /\n// malicious tarballs / low reputation, and returns a structured verdict the\n// caller renders in its own (cc or cursor) output format.\n\nexport interface InstallScanResult {\n scanned: boolean;\n action: 'allow' | 'warn' | 'block';\n blockContext: string;\n summary: string;\n scannedLabel: string;\n findings: Array<{ advisoryId: string; name: string; version: string; severity: string; detail: string }>;\n violatedIds: string[];\n}\n\nexport async function runInstallScan(command: string, jwt: string): Promise<InstallScanResult> {\n const empty: InstallScanResult = {\n scanned: false, action: 'allow', blockContext: '', summary: '',\n scannedLabel: '', findings: [], violatedIds: [],\n };\n // Stage 0 — recall-tuned pre-filter. Cheap, dependency-free, runs on every\n // Bash command. A false positive only costs a wasted server round-trip; a\n // false negative would let a vulnerable install through — so keep it loose.\n const lc = command.toLowerCase();\n const HINTS = [\n 'npm', 'pnpm', 'yarn', 'bun', 'pip', 'cargo', 'gem', 'composer',\n 'apt', 'apk', 'brew', 'dnf', 'yum', 'pacman', 'install', 'require',\n 'eval', '$(', '| sh', '| bash', 'curl', 'wget', 'make ',\n ];\n if (!HINTS.some(h => lc.includes(h))) return empty;\n\n try {\n let clientPackages: Array<{ name: string; version: string; ecosystem: string }> = [];\n try {\n const mod = await import(new URL('./installExtractCore.ts', import.meta.url).href);\n clientPackages = mod.extractDeterministicPkgRequests(command);\n } catch (err) {\n log('installExtract client error: ' + String(err));\n }\n\n const scanResp = await fetch(GATEWAY_URL + '/api/v1/pkg-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ command, packages: clientPackages }),\n signal: AbortSignal.timeout(10000),\n }).then(r => r.json()) as any;\n const action = scanResp?.action || 'allow';\n const pkgResults = Array.isArray(scanResp?.packages) ? scanResp.packages : [];\n const summary = scanResp?.summary || '';\n const scannedLabel = pkgResults.map((p: any) => p.name + '@' + p.version).join(', ');\n if (action === 'block') {\n // Every critical/high signal (uncapped) + the true CVE total. The grader\n // only sees what we put in blockContext — so the real count must be\n // STATED here; a bare preview lets it under-report (the count is data,\n // not something the grader should infer from a truncated list).\n const highSignals = pkgResults\n .flatMap((p: any) => (p.signals || []).filter((s: any) => s.severity === 'critical' || s.severity === 'high'));\n const cveCount = pkgResults\n .flatMap((p: any) => (p.signals || []))\n .filter((s: any) => s.type === 'cve').length;\n const findings: InstallScanResult['findings'] = [];\n const seenFindingKeys = new Set<string>();\n for (const p of pkgResults) {\n for (const s of (p.signals || [])) {\n const advisoryMatch = (s.detail || '').match(/\\\\b(GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}|CVE-\\\\d{4}-\\\\d+)\\\\b/i);\n const advisoryId = advisoryMatch ? advisoryMatch[1] : (s.type === 'cve' ? 'pkg:' + p.name : (s.type || 'signal'));\n const key = advisoryId + ':' + p.name + '@' + p.version;\n if (seenFindingKeys.has(key)) continue;\n seenFindingKeys.add(key);\n findings.push({\n advisoryId,\n name: p.name,\n version: p.version,\n severity: s.severity || 'high',\n detail: s.detail || summary,\n });\n }\n }\n // Preview the top 5 detail lines; the headline carries the true total.\n const blockSignals = highSignals.slice(0, 5);\n const headline = cveCount > 0\n ? cveCount + ' known CVE' + (cveCount === 1 ? '' : 's') + ' found in ' + (scannedLabel || 'the requested install') + '.\\\\n'\n : '';\n const preview = blockSignals.map((s: any) => s.detail).join('\\\\n') || summary;\n const more = highSignals.length > blockSignals.length\n ? '\\\\n(+' + (highSignals.length - blockSignals.length) + ' more critical/high findings not shown)'\n : '';\n return {\n scanned: true, action: 'block',\n blockContext: headline + preview + more\n + '\\\\nReport the CVE count and fix version exactly as stated above — do not estimate.'\n + '\\\\nDo NOT install packages with security risks. Use a patched version or a different package.',\n summary, scannedLabel, findings,\n violatedIds: blockSignals.map((s: any) => s.type + ':' + (s.detail || '').slice(0, 40)),\n };\n }\n return {\n scanned: true, action: action === 'warn' ? 'warn' : 'allow',\n blockContext: '', summary, scannedLabel, findings: [], violatedIds: [],\n };\n } catch {\n // Fail closed — could not verify the install (timeout / server\n // unreachable). Blocking an unverified install beats letting a\n // vulnerable package through.\n return {\n scanned: true, action: 'block',\n blockContext: 'Synkro could not verify this install is safe — the package scanner timed out or was unreachable. This is a verification failure, not a confirmed vulnerability. Retry once the Synkro server is reachable.',\n summary: 'install scan unavailable', scannedLabel: '', findings: [], violatedIds: ['scan_unavailable'],\n };\n }\n}\n\n// ─── Session Action Log ───\n\nconst SESSIONS_DIR = join(HOME, '.synkro', 'sessions');\nconst SAFE_SESSION_ID = /^[\\\\w-]+$/;\n\ninterface SessionAction {\n ts: string;\n tool: string;\n summary: string;\n file?: string;\n outcome?: string;\n}\n\nfunction sessionLogPath(sessionId: string): string | null {\n if (!sessionId || !SAFE_SESSION_ID.test(sessionId)) return null;\n return join(SESSIONS_DIR, sessionId + '.jsonl');\n}\n\nexport function appendSessionAction(sessionId: string, entry: SessionAction): void {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return;\n let step = 0;\n try {\n mkdirSync(SESSIONS_DIR, { recursive: true });\n try { step = readFileSync(logPath, 'utf-8').split('\\\\n').filter(Boolean).length + 1; } catch { step = 1; }\n appendFileSync(logPath, JSON.stringify(entry) + '\\\\n', 'utf-8');\n } catch {}\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return;\n fetch('http://127.0.0.1:' + mcpPort + '/api/session-action', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ session_id: sessionId, step, tool: entry.tool, summary: entry.summary, file: entry.file, outcome: entry.outcome }),\n signal: AbortSignal.timeout(2000),\n }).catch(() => {});\n}\n\nexport function readSessionLog(sessionId: string): SessionAction[] {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return [];\n try {\n if (!existsSync(logPath)) return [];\n const raw = readFileSync(logPath, 'utf-8');\n return raw.split('\\\\n').filter(Boolean).map(l => {\n try { return JSON.parse(l); } catch { return null; }\n }).filter((x): x is SessionAction => x !== null);\n } catch { return []; }\n}\n\nexport function compressSessionLog(actions: SessionAction[]): string {\n if (actions.length === 0) return '';\n const total = actions.length;\n const lines: string[] = [];\n\n if (total > 200) {\n const old = actions.slice(0, total - 200);\n const counts: Record<string, number> = {};\n const dirs = new Set<string>();\n for (const a of old) {\n counts[a.tool] = (counts[a.tool] || 0) + 1;\n if (a.file) {\n const dir = a.file.split('/').slice(0, -1).join('/') || '.';\n dirs.add(dir);\n }\n }\n const parts = Object.entries(counts).map(([t, c]) => c + ' ' + t).join(', ');\n const dirHint = dirs.size > 0 ? ' (' + [...dirs].slice(0, 3).join(', ') + ')' : '';\n lines.push(' [' + old.length + ' earlier: ' + parts + dirHint + ']');\n }\n\n const tier2Start = Math.max(0, total - 200);\n const tier2End = Math.max(0, total - 10);\n for (let i = tier2Start; i < tier2End; i++) {\n const a = actions[i];\n const target = a.file || a.summary.slice(0, 40);\n const out = a.outcome ? ' -> ' + a.outcome : '';\n lines.push(' ' + (i + 1) + '. ' + a.tool + ' ' + target + out);\n }\n\n const tier1Start = Math.max(0, total - 10);\n if (tier2End > tier2Start) lines.push(' --- recent ---');\n for (let i = tier1Start; i < total; i++) {\n const a = actions[i];\n const out = a.outcome ? ' -> ' + a.outcome : '';\n lines.push(' ' + (i + 1) + '. ' + a.summary + out);\n }\n\n return 'SESSION HISTORY (' + total + ' actions):\\\\n' + lines.join('\\\\n');\n}\n\nexport function cleanupSessionLog(sessionId: string): void {\n const logPath = sessionLogPath(sessionId);\n if (!logPath) return;\n try { unlinkSync(logPath); } catch {}\n}\n\n// ─── Verdict Parsing ───\n\nexport interface Verdict {\n ok: boolean;\n reason: string;\n suggestedFix: string;\n ruleId: string;\n ruleMode: string;\n severity: string;\n category: string;\n}\n\nexport function parseVerdict(resp: string): Verdict {\n const verdict: Verdict = {\n ok: true,\n reason: '',\n suggestedFix: '',\n ruleId: '',\n ruleMode: '',\n severity: 'low',\n category: 'clean',\n };\n\n // Flatten newlines for easier regex\n const flat = resp.replace(/\\\\n/g, ' ');\n const outerMatch = flat.match(/<synkro-verdict>(.*)<\\\\/synkro-verdict>/);\n if (!outerMatch) return verdict;\n const inner = outerMatch[1];\n\n const okMatch = inner.match(/<ok>(.*?)<\\\\/ok>/);\n if (okMatch) verdict.ok = okMatch[1].trim() !== 'false';\n\n const decisionMatch = inner.match(/<decision>(.*?)<\\\\/decision>/) || inner.match(/<overall>(.*?)<\\\\/overall>/);\n if (decisionMatch) {\n const d = decisionMatch[1].trim().toLowerCase();\n if (d === 'block' || d === 'blocking' || d === 'deny' || d === 'fail') verdict.ok = false;\n else if (d === 'allow' || d === 'pass' || d === 'approve') verdict.ok = true;\n }\n\n const reasonMatch = inner.match(/<reason>(.*?)<\\\\/reason>/) || inner.match(/<reasoning>(.*?)<\\\\/reasoning>/);\n if (reasonMatch) verdict.reason = reasonMatch[1].trim();\n\n const fixMatch = inner.match(/<suggested_fix>(.*?)<\\\\/suggested_fix>/);\n if (fixMatch) verdict.suggestedFix = fixMatch[1].trim();\n\n if (!verdict.ok) {\n const ruleIdMatch = inner.match(/<rule_id>(.*?)<\\\\/rule_id>/);\n const ruleModeMatch = inner.match(/<rule_mode>(.*?)<\\\\/rule_mode>/);\n const sevMatch = inner.match(/<risk_level>(.*?)<\\\\/risk_level>/);\n\n if (ruleIdMatch) {\n verdict.ruleId = ruleIdMatch[1].trim();\n } else {\n // Try to find inside a <violation> block\n const violationMatch = inner.match(/<violation>(.*?)<\\\\/violation>/);\n if (violationMatch) {\n const vBlock = violationMatch[1];\n const vRuleId = vBlock.match(/<rule_id>(.*?)<\\\\/rule_id>/);\n if (vRuleId) verdict.ruleId = vRuleId[1].trim();\n if (!verdict.reason) {\n const vReason = vBlock.match(/<reason>(.*?)<\\\\/reason>/);\n if (vReason) verdict.reason = vReason[1].trim();\n }\n if (!verdict.suggestedFix) {\n const vFix = vBlock.match(/<suggested_fix>(.*?)<\\\\/suggested_fix>/);\n if (vFix) verdict.suggestedFix = vFix[1].trim();\n }\n if (!sevMatch) {\n const vSev = vBlock.match(/<severity>(.*?)<\\\\/severity>/);\n if (vSev) verdict.severity = vSev[1].trim();\n }\n }\n }\n\n if (ruleModeMatch) verdict.ruleMode = ruleModeMatch[1].trim();\n if (sevMatch) verdict.severity = sevMatch[1].trim();\n verdict.severity = verdict.severity || 'high';\n\n const catMatch = inner.match(/<category>(.*?)<\\\\/category>/);\n verdict.category = catMatch ? catMatch[1].trim() : 'uncategorized';\n\n // Fallback: extract rule ID from reason text\n if (!verdict.ruleId && verdict.reason) {\n const rMatch = verdict.reason.match(/[Rr]\\\\d{3}/);\n if (rMatch) verdict.ruleId = rMatch[0];\n }\n }\n\n return verdict;\n}\n\n// ─── Telemetry Dispatch ───\n\n// Gated cloud telemetry POST — fires only when storage mode is 'cloud'. The\n// local PGLite spool (appendLocalTelemetry) always gets the data regardless,\n// so 'local' storage keeps everything on the machine.\nexport function shipCloud(jwt: string, path: string, body: Record<string, any>, timeoutMs = 3000): void {\n if ((process.env.SYNKRO_STORAGE_MODE || 'local') !== 'cloud') return;\n fetch(GATEWAY_URL + path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n }).catch(() => {});\n}\n\nexport function dispatchCapture(\n jwt: string,\n hookType: string,\n verdictStr: string,\n severity: string,\n category: string,\n toolName: string,\n repo: string,\n sessionId: string,\n captureDepth: string,\n opts?: {\n command?: string;\n reasoning?: string;\n rulesChecked?: Rule[] | string;\n violatedRules?: string[];\n recentUserMessages?: string[];\n ccModel?: string;\n linesAdded?: number;\n linesRemoved?: number;\n },\n): void {\n // Fire-and-forget\n const eventId = mintEventId('evt');\n const model = normalizeCaptureModel(opts?.ccModel || 'unknown');\n\n const body: Record<string, any> = {\n capture_type: 'local_verdict',\n event_id: eventId,\n // Source-time of the action. The ingest handler uses this for created_at\n // — without it, the spool drain stamps every event with NOW() and a batch\n // of actions taken minutes apart all collapse to the drain instant.\n _ts: new Date().toISOString(),\n hook_type: hookType,\n verdict: verdictStr,\n severity,\n category,\n cc_model: model,\n model,\n tool_name: toolName,\n };\n const resolvedRepo = repo || detectRepo('') || readCachedRepo();\n if (resolvedRepo) body.repo = resolvedRepo;\n if (sessionId) body.session_id = sessionId;\n\n // Local telemetry always gets full content — data never leaves the machine\n const localBody = { ...body };\n if (opts) {\n if (opts.command) localBody.command = opts.command;\n if (opts.reasoning) localBody.reasoning = opts.reasoning;\n if (opts.rulesChecked) localBody.rules_checked = opts.rulesChecked;\n if (opts.violatedRules) localBody.violated_rules = opts.violatedRules;\n if (opts.recentUserMessages) localBody.recent_user_messages = opts.recentUserMessages;\n if (opts.linesAdded != null) localBody.lines_added = opts.linesAdded;\n if (opts.linesRemoved != null) localBody.lines_removed = opts.linesRemoved;\n }\n appendLocalTelemetry(localBody);\n\n // Cloud copy carries the same full content as the local spool; shipCloud\n // gates on storage mode (no-op when storage is local).\n if (opts) {\n if (opts.command) body.command = opts.command;\n if (opts.reasoning) body.reasoning = opts.reasoning;\n if (opts.rulesChecked) body.rules_checked = opts.rulesChecked;\n if (opts.violatedRules) body.violated_rules = opts.violatedRules;\n if (opts.recentUserMessages) body.recent_user_messages = opts.recentUserMessages;\n }\n shipCloud(jwt, '/api/v1/hook/capture', body);\n}\n\n// ─── Durable Telemetry Spool ───\n// Telemetry must survive process death, container restarts, and ingest-server\n// backpressure. Every event is appended synchronously to a local JSONL spool.\n// drainSpool streams claim files line-by-line (never loads multi-GB into RAM),\n// ships in batches, and on partial failure only re-spools the batches that\n// did not land — never the whole claim file (that caused 2GB amplification).\n\nconst TELEMETRY_SPOOL = join(HOME, '.synkro', 'telemetry-spool.jsonl');\nconst SPOOL_DRAIN_PREFIX = 'telemetry-spool.jsonl.draining.';\nconst SPOOL_DRAIN_LOCK = join(HOME, '.synkro', 'telemetry-spool.drain.lock');\nconst SPOOL_MAX_CLAIM_BYTES = 50 * 1024 * 1024;\nconst SPOOL_BATCH_SIZE = 200;\nconst SPOOL_DRAIN_LOCK_STALE_MS = 120_000;\n\n/** Stable id for spool rows — required for idempotent ingest (ON CONFLICT). */\nexport function mintEventId(prefix = 'evt'): string {\n return prefix + '_' + Date.now() + '_' + process.pid;\n}\n\nexport function appendLocalTelemetry(body: Record<string, any>): void {\n if ((process.env.SYNKRO_STORAGE_MODE || 'local') !== 'local') return;\n const event = { ...body };\n if (!event.event_id) {\n const ct = String(event.capture_type || '');\n const prefix = ct === 'usage_tick' ? 'usage' : ct === 'edit_scan' ? 'edit' : 'evt';\n event.event_id = mintEventId(prefix);\n }\n if (!event._ts) event._ts = new Date().toISOString();\n try {\n appendFileSync(TELEMETRY_SPOOL, JSON.stringify(event) + '\\\\n');\n } catch {}\n // Realtime: fire-and-forget POST to the local server so events appear\n // in the dashboard immediately. Spool file remains the durable fallback.\n try {\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n let token = '';\n try { token = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (token) {\n fetch('http://127.0.0.1:' + port + '/api/ingest', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },\n body: JSON.stringify(event),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n } catch {}\n}\n\nfunction tryAcquireDrainLock(): boolean {\n try {\n if (existsSync(SPOOL_DRAIN_LOCK)) {\n if (Date.now() - statSync(SPOOL_DRAIN_LOCK).mtimeMs < SPOOL_DRAIN_LOCK_STALE_MS) return false;\n unlinkSync(SPOOL_DRAIN_LOCK);\n }\n mkdirSync(dirname(SPOOL_DRAIN_LOCK), { recursive: true });\n writeFileSync(SPOOL_DRAIN_LOCK, String(process.pid) + '\\\\n');\n return true;\n } catch {\n return false;\n }\n}\n\nfunction releaseDrainLock(): void {\n try { unlinkSync(SPOOL_DRAIN_LOCK); } catch {}\n}\n\nasync function postSpoolBatch(mcpPort: string, mcpToken: string, events: any[]): Promise<boolean> {\n if (!events.length) return true;\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/ingest/batch', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ events }),\n signal: AbortSignal.timeout(30000),\n });\n if (!resp.ok) {\n const errBody = await resp.text().catch(() => '');\n log('drainSpool batch HTTP ' + resp.status + ': ' + errBody.slice(0, 120));\n }\n return resp.ok;\n } catch (e) {\n log('drainSpool batch error: ' + ((e as Error).message || String(e)).slice(0, 120));\n return false;\n }\n}\n\nfunction quarantineOversizedClaim(claimPath: string): void {\n const stuck = claimPath + '.STUCK-OVERSIZED.bak';\n try {\n renameSync(claimPath, stuck);\n log('drainSpool quarantined oversized claim → ' + basename(stuck));\n } catch (e) {\n log('drainSpool quarantine failed: ' + String(e));\n }\n}\n\nasync function drainClaimFile(claimPath: string, mcpPort: string, mcpToken: string): Promise<void> {\n let sz = 0;\n try { sz = statSync(claimPath).size; } catch { return; }\n if (sz === 0) {\n try { unlinkSync(claimPath); } catch {}\n return;\n }\n if (sz > SPOOL_MAX_CLAIM_BYTES) {\n quarantineOversizedClaim(claimPath);\n return;\n }\n\n const pending: any[] = [];\n let batch: any[] = [];\n let failed = false;\n\n const rl = createInterface({\n input: createReadStream(claimPath, { encoding: 'utf8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (failed) {\n const t = line.trim();\n if (!t) continue;\n try { pending.push(JSON.parse(t)); } catch {}\n continue;\n }\n const t = line.trim();\n if (!t) continue;\n try {\n batch.push(JSON.parse(t));\n } catch {\n continue;\n }\n if (batch.length < SPOOL_BATCH_SIZE) continue;\n const chunk = batch.splice(0, SPOOL_BATCH_SIZE);\n if (!(await postSpoolBatch(mcpPort, mcpToken, chunk))) {\n pending.push(...chunk);\n failed = true;\n }\n }\n\n if (!failed && batch.length > 0) {\n if (!(await postSpoolBatch(mcpPort, mcpToken, batch))) {\n pending.push(...batch);\n failed = true;\n } else {\n batch = [];\n }\n } else if (failed && batch.length > 0) {\n pending.push(...batch);\n }\n\n if (pending.length === 0) {\n try { unlinkSync(claimPath); } catch {}\n return;\n }\n\n try {\n for (const evt of pending) {\n appendFileSync(TELEMETRY_SPOOL, JSON.stringify(evt) + '\\\\n');\n }\n try { unlinkSync(claimPath); } catch {}\n log('drainSpool re-spooled ' + pending.length + ' events from ' + basename(claimPath));\n } catch (e) {\n log('drainSpool re-spool failed: ' + String(e));\n }\n}\n\nexport async function drainSpool(): Promise<void> {\n if (!tryAcquireDrainLock()) return;\n\n const dir = join(HOME, '.synkro');\n const claimed: string[] = [];\n\n try {\n try {\n if (existsSync(TELEMETRY_SPOOL) && statSync(TELEMETRY_SPOOL).size > 0) {\n const claim = join(dir, SPOOL_DRAIN_PREFIX + process.pid + '.' + Date.now());\n renameSync(TELEMETRY_SPOOL, claim);\n claimed.push(claim);\n }\n } catch {}\n\n try {\n for (const f of readdirSync(dir)) {\n if (!f.startsWith(SPOOL_DRAIN_PREFIX)) continue;\n if (f.includes('.STUCK-') || f.endsWith('.bak')) continue;\n const full = join(dir, f);\n if (claimed.indexOf(full) !== -1) continue;\n try {\n if (Date.now() - statSync(full).mtimeMs > 30000) claimed.push(full);\n } catch {}\n }\n } catch {}\n\n if (claimed.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return;\n\n for (const f of claimed) {\n await drainClaimFile(f, mcpPort, mcpToken);\n }\n } finally {\n releaseDrainLock();\n }\n}\n\n// ─── Rule Mode Lookup ───\n\nexport function ruleMode(ruleId: string, rules: Rule[]): 'ask' | 'fix' {\n if (!ruleId || !rules.length) return 'ask';\n const matched = rules.filter(r => r.rule_id === ruleId);\n if (matched.some(r => r.mode === 'blocking' || r.mode === 'ask')) return 'ask';\n return normalizeMode(matched[0]?.mode);\n}\n\n// ─── Content Reconstruction ───\n\nfunction patchTextFromToolInput(toolInput: any): string {\n return String(toolInput.patch ?? toolInput.content ?? toolInput.code_edit ?? toolInput.new_string ?? toolInput.input ?? toolInput.diff ?? '');\n}\n\nfunction filePathFromPatch(patchText: string): string {\n const match = patchText.match(/^\\\\*\\\\*\\\\* (?:Add|Update|Delete) File:\\\\s*(.+)$/m);\n return match?.[1]?.trim() || '';\n}\n\nfunction contentFromPatch(patchText: string): string {\n const addedOrContext = patchText\n .split('\\\\n')\n .filter(line => (line.startsWith('+') && !line.startsWith('+++')) || line.startsWith(' '))\n .map(line => line.slice(1))\n .join('\\\\n')\n .trim();\n return addedOrContext || patchText;\n}\n\nexport function filePathFromToolInput(toolInput: any): string {\n return toolInput.file_path || toolInput.notebook_path || toolInput.path || toolInput.target_file || filePathFromPatch(patchTextFromToolInput(toolInput));\n}\n\nexport function reconstructContent(toolName: string, toolInput: any, filePath: string, cwd?: string): string {\n const canRead = filePath && cwd && isPathUnder(filePath, cwd);\n switch (toolName) {\n case 'ApplyPatch':\n case 'apply_patch':\n return contentFromPatch(patchTextFromToolInput(toolInput));\n case 'Write':\n return toolInput.content || toolInput.contents || '';\n case 'edit_file':\n case 'reapply':\n return toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n case 'Edit': {\n let content = '';\n try {\n if (canRead && existsSync(filePath)) {\n content = readFileSync(filePath, 'utf-8').slice(0, 65536);\n }\n } catch {}\n const oldStr = toolInput.old_string || '';\n const newStr = toolInput.new_string || '';\n if (oldStr && content.includes(oldStr)) {\n return content.replace(oldStr, newStr);\n }\n return content || newStr;\n }\n case 'MultiEdit': {\n let content = '';\n try {\n if (canRead && existsSync(filePath)) {\n content = readFileSync(filePath, 'utf-8').slice(0, 65536);\n }\n } catch {}\n const edits = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n for (const edit of edits) {\n if (!edit || typeof edit !== 'object') continue;\n const old = edit.old_string || '';\n const nw = edit.new_string || '';\n if (old && content.includes(old)) {\n content = content.replace(old, nw);\n }\n }\n return content;\n }\n case 'NotebookEdit':\n case 'edit_notebook':\n return toolInput.new_source || '';\n case 'StrReplace':\n return toolInput.new_string || toolInput.content || toolInput.code_edit || '';\n default:\n return toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n }\n}\n\n// ─── HTTP with Retry ───\n\nexport async function postWithRetry(url: string, body: any, jwt: string, timeout = 8000): Promise<any> {\n let currentJwt = jwt;\n let resp: Response;\n try {\n resp = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + currentJwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n });\n } catch {\n return null;\n }\n\n let data: any;\n try { data = await resp.json(); } catch { return null; }\n\n // Retry on token expiry\n if (data?.detail && (data.detail.includes('Token has expired') || data.detail.includes('Invalid or expired token'))) {\n try {\n currentJwt = await refreshJwt(currentJwt);\n const resp2 = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + currentJwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n });\n data = await resp2.json();\n } catch {\n return null;\n }\n }\n\n return data;\n}\n\n// ─── Read Stdin ───\n\nexport async function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf-8');\n}\n\n// ─── Transcript path + message extraction (CC + Cursor) ───\n\n/** Cursor stores transcripts under ~/.cursor/projects/{slug}/agent-transcripts/ */\nexport function cursorProjectSlug(workspaceRoot: string): string {\n return workspaceRoot.replace(new RegExp('^[/]+'), '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n}\n\n/** Resolve transcript JSONL — payload field, CURSOR_TRANSCRIPT_PATH, or Cursor agent-transcripts dir. */\nexport function resolveTranscriptPath(payload: Record<string, unknown>): string {\n const explicit = typeof payload.transcript_path === 'string' ? payload.transcript_path.trim() : '';\n if (explicit && existsSync(explicit)) return explicit;\n\n const fromEnv = (process.env.CURSOR_TRANSCRIPT_PATH || '').trim();\n if (fromEnv && existsSync(fromEnv)) return fromEnv;\n\n if (!isCursorHookFormat()) return explicit || fromEnv || '';\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd)\n || workspaceRoots[0]\n || (process.env.CURSOR_PROJECT_DIR || '');\n if (!sessionId || !cwd) return explicit || fromEnv || '';\n\n const base = join(HOME, '.cursor', 'projects', cursorProjectSlug(cwd), 'agent-transcripts');\n const nested = join(base, sessionId, sessionId + '.jsonl');\n if (existsSync(nested)) return nested;\n if (existsSync(join(base, sessionId))) return nested;\n const flat = join(base, sessionId + '.jsonl');\n if (existsSync(flat)) return flat;\n\n return explicit || fromEnv || '';\n}\n\nfunction transcriptEntryType(entry: Record<string, unknown>): string {\n return String(entry.type || entry.role || '');\n}\n\nfunction transcriptContentBlock(entry: Record<string, unknown>): Record<string, unknown> | null {\n const msg = entry.message as Record<string, unknown> | string | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (content && typeof content === 'object' && !Array.isArray(content)) {\n return content as Record<string, unknown>;\n }\n return null;\n}\n\nfunction isProviderRedactedPlaceholder(text: string): boolean {\n const t = text.trim();\n if (!t) return false;\n return t === '[REDACTED]' || /^\\\\[REDACTED\\\\](\\\\s*\\\\[REDACTED\\\\])*$/.test(t);\n}\n\nfunction extractContentBlockText(block: Record<string, unknown>): string {\n const type = String(block.type || '');\n if (type === 'text' && typeof block.text === 'string') return block.text;\n if ((type === 'thinking' || type === 'redacted_thinking') && typeof block.thinking === 'string') {\n return block.thinking;\n }\n if (type === 'tool_result') {\n const c = block.content;\n if (typeof c === 'string') return c;\n if (Array.isArray(c)) {\n return c.map((part: unknown) => {\n if (typeof part === 'string') return part;\n const p = part as Record<string, unknown>;\n return typeof p.text === 'string' ? p.text : '';\n }).filter(Boolean).join('\\\\n');\n }\n return '';\n }\n return '';\n}\n\nfunction thoughtOverlayPath(sessionId: string): string {\n return join(HOME, '.synkro', 'sessions', sessionId, 'thought-overlay.jsonl');\n}\n\n/** Cursor afterAgentThought delivers full thinking; transcript JSONL stores [REDACTED]. Queue for transcript sync. */\nexport function appendThoughtOverlay(sessionId: string, text: string): void {\n const trimmed = text.trim();\n if (!sessionId || !trimmed || isProviderRedactedPlaceholder(trimmed)) return;\n const path = thoughtOverlayPath(sessionId);\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, JSON.stringify({ text: trimmed, ts: Date.now() }) + '\\\\n', 'utf-8');\n}\n\nfunction consumeThoughtOverlay(sessionId: string): string {\n const path = thoughtOverlayPath(sessionId);\n if (!existsSync(path)) return '';\n try {\n const lines = readFileSync(path, 'utf-8').split('\\\\n').filter(l => l.trim());\n if (lines.length === 0) return '';\n const first = JSON.parse(lines[0]) as { text?: string };\n const rest = lines.slice(1).join('\\\\n');\n if (rest.trim()) writeFileSync(path, rest + (rest.endsWith('\\\\n') ? '' : '\\\\n'), 'utf-8');\n else unlinkSync(path);\n const text = typeof first.text === 'string' ? first.text.trim() : '';\n return text && !isProviderRedactedPlaceholder(text) ? text : '';\n } catch {\n return '';\n }\n}\n\n/** Push a single conversation turn (used by Cursor afterAgentThought/afterAgentResponse hooks). */\nexport async function pushConversationMessage(\n sessionId: string,\n role: 'user' | 'assistant',\n content: string,\n opts: { gitRepo?: string; patchRedacted?: boolean; seq?: number } = {},\n): Promise<boolean> {\n const text = content.trim().slice(0, 8000);\n if (!sessionId || !text || isProviderRedactedPlaceholder(text)) return false;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return false;\n\n const seq = typeof opts.seq === 'number'\n ? opts.seq\n : Date.now() % 1_000_000_000;\n\n try {\n const body: Record<string, unknown> = {\n session_id: sessionId,\n repo: opts.gitRepo || '',\n messages: [{\n type: role,\n content: text,\n ts: new Date().toISOString(),\n message_index: seq,\n patch_redacted: opts.patchRedacted ?? true,\n }],\n };\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(5000),\n });\n return resp.ok;\n } catch {\n return false;\n }\n}\n\nfunction extractTranscriptEntryText(\n entry: Record<string, unknown>,\n maxLen = 8000,\n sessionId = '',\n): string {\n const block = transcriptContentBlock(entry);\n if (block) {\n const type = String(block.type || '');\n if (type === 'tool_use' || type === 'tool_call') return '';\n let text = extractContentBlockText(block);\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) text = overlay;\n }\n return text ? text.slice(0, maxLen) : '';\n }\n\n const msg = entry.message as Record<string, unknown> | string | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (typeof content === 'string') {\n let text = content;\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) text = overlay;\n }\n return text ? text.slice(0, maxLen) : '';\n }\n if (Array.isArray(content)) {\n const text = content.map((c: unknown) => {\n if (typeof c === 'string') return c;\n const b = c as Record<string, unknown>;\n const type = String(b?.type || '');\n if (type === 'tool_use' || type === 'tool_call') return '';\n return extractContentBlockText(b);\n }).filter(Boolean).join('\\\\n\\\\n').trim();\n if (!text) return '';\n if (isProviderRedactedPlaceholder(text) && sessionId) {\n const overlay = consumeThoughtOverlay(sessionId);\n if (overlay) return overlay.slice(0, maxLen);\n }\n return text.slice(0, maxLen);\n }\n if (typeof msg === 'string') return msg.slice(0, maxLen);\n return '';\n}\n\n/** Offset-tracked ingest of new user/assistant turns into PGLite conversation_messages. */\nexport async function syncConversationTranscript(\n sessionId: string,\n transcriptPath: string,\n gitRepo = '',\n): Promise<{ ingested: number; messages: Array<Record<string, unknown>> }> {\n if (!sessionId || !transcriptPath || !existsSync(transcriptPath)) return { ingested: 0, messages: [] };\n\n const offsetDir = join(HOME, '.synkro', '.transcript-offsets');\n mkdirSync(offsetDir, { recursive: true });\n const offsetFile = join(offsetDir, sessionId);\n let offset = 0;\n if (existsSync(offsetFile)) {\n try { offset = parseInt(readFileSync(offsetFile, 'utf-8').trim(), 10) || 0; } catch {}\n }\n\n const raw = readFileSync(transcriptPath, 'utf-8');\n const allLines = raw.split('\\\\n').filter(l => l.trim());\n const totalLines = allLines.length;\n if (totalLines <= offset) return { ingested: 0, messages: [] };\n\n let startIdx = offset;\n const delta = totalLines - offset;\n if (delta > 200) startIdx = totalLines - 200;\n\n const messages: Array<Record<string, unknown>> = [];\n for (let i = startIdx; i < totalLines; i++) {\n try {\n const entry = JSON.parse(allLines[i]) as Record<string, unknown>;\n const kind = transcriptEntryType(entry);\n if (kind !== 'user' && kind !== 'assistant') continue;\n const text = extractTranscriptEntryText(entry, 8000, sessionId);\n if (!text) continue;\n\n const msgObj = entry.message as Record<string, unknown> | undefined;\n const content = (msgObj && typeof msgObj === 'object' ? msgObj.content : undefined) ?? entry.content;\n const singleBlock = transcriptContentBlock(entry);\n const msg: Record<string, unknown> = {\n message_index: i,\n type: kind,\n content: text,\n ts: entry.timestamp || null,\n };\n if (kind === 'assistant') {\n const blocks = Array.isArray(content)\n ? content\n : (singleBlock ? [singleBlock] : []);\n const toolCalls = blocks\n .filter((c: unknown) => {\n const b = c as Record<string, unknown>;\n return b?.type === 'tool_use' || b?.type === 'tool_call';\n })\n .map((c: unknown) => {\n const b = c as Record<string, unknown>;\n return {\n name: b.name,\n input: JSON.stringify(b.input || b.arguments || {}).slice(0, 500),\n id: b.id,\n };\n });\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n if (msgObj && typeof msgObj === 'object') {\n msg.model = msgObj.model || null;\n const u = msgObj.usage as Record<string, unknown> | undefined;\n if (u) {\n msg.usage = {\n input_tokens: u.input_tokens,\n output_tokens: u.output_tokens,\n cache_creation_input_tokens: u.cache_creation_input_tokens,\n cache_read_input_tokens: u.cache_read_input_tokens,\n };\n }\n }\n }\n messages.push(msg);\n } catch {}\n }\n\n if (messages.length === 0) {\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n return { ingested: 0, messages: [] };\n }\n\n const rawPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n const mcpPort = (rawPort > 0 && rawPort < 65536) ? rawPort : 18931;\n let mcpToken = '';\n try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (!mcpToken) return { ingested: 0, messages };\n\n try {\n const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ session_id: sessionId, repo: gitRepo, messages }),\n signal: AbortSignal.timeout(5000),\n });\n if (resp.ok) {\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n const data = await resp.json() as { ingested?: number };\n return { ingested: data.ingested ?? messages.length, messages };\n }\n } catch {}\n return { ingested: 0, messages };\n}\n\nexport interface TranscriptContext {\n userIntent: string;\n recentUserMessages: string[];\n recentMessages: Array<{ type: string; text: string }>;\n recentActions: Array<{ tool: string; input: string }>;\n sessionSummary: string;\n ccModel: string;\n ccUsage: Record<string, any>;\n}\n\nexport function extractTranscript(transcriptPath: string | undefined): TranscriptContext {\n const ctx: TranscriptContext = {\n userIntent: '',\n recentUserMessages: [],\n recentMessages: [],\n recentActions: [],\n sessionSummary: '',\n ccModel: '',\n ccUsage: {},\n };\n\n if (!transcriptPath || !existsSync(transcriptPath)) return ctx;\n\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const lines = raw.split('\\\\n').filter(l => l.trim());\n // Take the last 400 lines\n const tail = lines.slice(-400);\n\n const parsed: any[] = [];\n for (const line of tail) {\n try { parsed.push(JSON.parse(line)); } catch {}\n }\n\n // Recent user messages (last 5)\n const userMsgs: string[] = [];\n for (const entry of parsed) {\n if (transcriptEntryType(entry) !== 'user') continue;\n const text = extractTranscriptEntryText(entry);\n if (text) userMsgs.push(text);\n }\n ctx.recentUserMessages = userMsgs.slice(-5);\n ctx.userIntent = ctx.recentUserMessages[ctx.recentUserMessages.length - 1] || '';\n\n // Recent messages (last 10, user + assistant)\n const msgs: Array<{ type: string; text: string }> = [];\n for (const entry of parsed) {\n const kind = transcriptEntryType(entry);\n if (kind !== 'user' && kind !== 'assistant') continue;\n const text = extractTranscriptEntryText(entry, 500);\n if (text) msgs.push({ type: kind, text });\n }\n ctx.recentMessages = msgs.slice(-10);\n\n // Recent tool calls (last 5)\n const actions: Array<{ tool: string; input: string }> = [];\n for (const entry of parsed) {\n if (transcriptEntryType(entry) !== 'assistant') continue;\n const msg = entry.message;\n const content = msg?.content ?? entry.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use' && block.type !== 'tool_call') continue;\n actions.push({\n tool: block.name || '',\n input: JSON.stringify(block.input || block.arguments || {}).slice(0, 200),\n });\n }\n }\n ctx.recentActions = actions.slice(-5);\n\n // Session summary\n for (const entry of parsed) {\n if (entry.type === 'summary' && entry.summary) {\n ctx.sessionSummary = entry.summary;\n }\n }\n\n // CC model\n const assistantEntries = parsed.filter(e => transcriptEntryType(e) === 'assistant');\n if (assistantEntries.length > 0) {\n const last = assistantEntries[assistantEntries.length - 1];\n ctx.ccModel = last.message?.model || '';\n const usage = last.message?.usage;\n if (usage) {\n ctx.ccUsage = {\n input_tokens: usage.input_tokens,\n output_tokens: usage.output_tokens,\n cache_creation_input_tokens: usage.cache_creation_input_tokens,\n cache_read_input_tokens: usage.cache_read_input_tokens,\n };\n }\n }\n } catch {}\n\n return ctx;\n}\n\n// ─── Last Prompt ───\n\nexport function readLastPrompt(sessionId?: string): string {\n try {\n if (sessionId) {\n const perSession = join(SESSIONS_DIR, sessionId + '.last-prompt');\n if (existsSync(perSession)) return readFileSync(perSession, 'utf-8').trim();\n }\n if (!existsSync(LAST_PROMPT_FILE)) return '';\n return readFileSync(LAST_PROMPT_FILE, 'utf-8').trim();\n } catch {\n return '';\n }\n}\n\n// ─── Find Nearest Package Dependencies ───\n\nexport function findNearestDeps(filePath: string): Record<string, string> {\n let dir = dirname(filePath);\n while (dir !== '/' && dir !== '.') {\n const pkg = join(dir, 'package.json');\n if (existsSync(pkg)) {\n try {\n const data = JSON.parse(readFileSync(pkg, 'utf-8'));\n return { ...(data.dependencies || {}), ...(data.devDependencies || {}) };\n } catch {}\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return {};\n}\n\n// ─── Consent Tracking ───\n\nconst CONSENT_FILE = join(HOME, '.synkro', '.local-consent');\n\nexport function consentGrant(sessionId: string, hash: string): void {\n try {\n const dir = dirname(CONSENT_FILE);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const line = sessionId + '\\\\t' + hash + '\\\\tactive\\\\n';\n const { appendFileSync } = require('node:fs');\n appendFileSync(CONSENT_FILE, line, 'utf-8');\n } catch {}\n}\n\nexport function consentHasActive(sessionId: string, hash: string): boolean {\n try {\n if (!existsSync(CONSENT_FILE)) return false;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n return content.includes(sessionId + '\\\\t' + hash + '\\\\tactive');\n } catch {\n return false;\n }\n}\n\nexport function consentConsume(sessionId: string, hash: string): void {\n try {\n if (!existsSync(CONSENT_FILE)) return;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n const target = sessionId + '\\\\t' + hash + '\\\\tactive';\n const replacement = sessionId + '\\\\t' + hash + '\\\\tconsumed';\n const updated = content.split('\\\\n').map(l => l === target ? replacement : l).join('\\\\n');\n writeFileSync(CONSENT_FILE, updated, 'utf-8');\n } catch {}\n}\n\n// ─── Crypto Hash ───\n\nexport function hashCommand(cmd: string): string {\n const { createHash } = require('node:crypto');\n return createHash('sha256').update(cmd).digest('hex').slice(0, 16);\n}\n\n// ─── Edit line delta + transcript usage ───\n\nexport function countTextLines(text: string): number {\n if (!text) return 0;\n return text.split('\\\\n').length;\n}\n\n/** Count lines added/removed from a write, search-replace, or multi-edit payload. */\nexport function countEditLineDelta(source: {\n writeContent?: string;\n /** When set, Write counts net delta vs the file on disk instead of full file length. */\n existingContent?: string;\n old_string?: string;\n new_string?: string;\n edits?: Array<{ old_string?: string; new_string?: string }>;\n}): { linesAdded: number; linesRemoved: number } {\n let linesAdded = 0;\n let linesRemoved = 0;\n if (source.writeContent != null && source.writeContent !== '') {\n const newLines = countTextLines(String(source.writeContent));\n if (source.existingContent != null && source.existingContent !== '') {\n const oldLines = countTextLines(String(source.existingContent));\n return {\n linesAdded: Math.max(0, newLines - oldLines),\n linesRemoved: Math.max(0, oldLines - newLines),\n };\n }\n return { linesAdded: newLines, linesRemoved: 0 };\n }\n if (source.old_string != null && source.new_string != null) {\n const oldLines = countTextLines(String(source.old_string));\n const newLines = countTextLines(String(source.new_string));\n return {\n linesAdded: Math.max(0, newLines - oldLines),\n linesRemoved: Math.max(0, oldLines - newLines),\n };\n }\n if (Array.isArray(source.edits)) {\n for (const e of source.edits) {\n if (!e || typeof e !== 'object') continue;\n const oldLines = countTextLines(String(e.old_string || ''));\n const newLines = countTextLines(String(e.new_string || ''));\n linesAdded += Math.max(0, newLines - oldLines);\n linesRemoved += Math.max(0, oldLines - newLines);\n }\n }\n return { linesAdded, linesRemoved };\n}\n\n/** Normalize model names so the same Cursor session doesn't split across agent buckets. */\nexport function normalizeCaptureModel(model: string): string {\n const m = (model || '').trim();\n if (!m || m === 'unknown') return 'unknown';\n if (m.startsWith('cursor/') || m.startsWith('claude-') || m.startsWith('gpt-') || m.startsWith('gemini-')) return m;\n if (/^composer/i.test(m) || m === 'auto' || m === 'default') return 'cursor/' + m;\n return m;\n}\n\n/** Line metrics for guard_checks — Cursor Edit/StrReplace post deltas via afterFileEdit. */\nexport function captureLineMetrics(\n agentKind: 'cursor' | 'claude_code',\n toolName: string,\n linesAdded: number,\n linesRemoved: number,\n): { linesAdded?: number; linesRemoved?: number } {\n if (agentKind === 'cursor' && toolName !== 'Write') return {};\n return { linesAdded, linesRemoved };\n}\n\nfunction usageTextFromTranscriptEntry(entry: Record<string, unknown>): string {\n const msg = entry.message as Record<string, unknown> | undefined;\n const content = (msg && typeof msg === 'object' ? msg.content : undefined) ?? entry.content;\n if (typeof content === 'string') return content;\n if (content && typeof content === 'object' && !Array.isArray(content)) {\n const block = content as Record<string, unknown>;\n if (block.type === 'text' && typeof block.text === 'string') return block.text;\n if (typeof block.text === 'string') return block.text;\n return '';\n }\n if (!Array.isArray(content)) return '';\n return content.map((c: unknown) => {\n if (typeof c === 'string') return c;\n const b = c as Record<string, unknown>;\n if (b?.type === 'text' && typeof b.text === 'string') return b.text;\n if (typeof b?.text === 'string') return b.text;\n if (b?.type === 'tool_use' || b?.type === 'tool_call') {\n return JSON.stringify(b.input || b.arguments || {});\n }\n return '';\n }).join('\\\\n');\n}\n\nfunction addUsageBlock(\n result: { model: string; totals: Record<string, number> },\n u: Record<string, unknown> | null | undefined,\n): boolean {\n if (!u) return false;\n const inp = Number(u.input_tokens ?? u.inputTokens ?? 0) || 0;\n const out = Number(u.output_tokens ?? u.outputTokens ?? 0) || 0;\n const cw = Number(u.cache_creation_input_tokens ?? u.cacheCreationInputTokens ?? 0) || 0;\n const cr = Number(u.cache_read_input_tokens ?? u.cacheReadInputTokens ?? 0) || 0;\n if (inp + out + cw + cr <= 0) return false;\n result.totals.in += inp;\n result.totals.out += out;\n result.totals.cw += cw;\n result.totals.cr += cr;\n return true;\n}\n\nfunction msgModel(entry: Record<string, unknown>): string {\n const msg = entry.message as Record<string, unknown> | undefined;\n return typeof msg?.model === 'string' ? msg.model : '';\n}\n\n/** Sum token usage from CC or Cursor agent-transcript JSONL. */\nexport function aggregateUsage(\n transcriptPath: string,\n opts?: { modelFallback?: string },\n): { model: string; totals: Record<string, number> } {\n const result = { model: opts?.modelFallback || '', totals: { in: 0, out: 0, cw: 0, cr: 0 } };\n if (!transcriptPath || !existsSync(transcriptPath)) return result;\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const entries: Record<string, unknown>[] = [];\n for (const line of raw.split('\\\\n')) {\n if (!line.trim()) continue;\n try { entries.push(JSON.parse(line) as Record<string, unknown>); } catch {}\n }\n\n let sawExplicit = false;\n for (const entry of entries) {\n const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;\n const msg = entry.message as Record<string, unknown> | undefined;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, tokenCount)) sawExplicit = true;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, msg?.usage as Record<string, unknown>)) sawExplicit = true;\n if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, entry.usage as Record<string, unknown>)) sawExplicit = true;\n }\n\n for (const entry of entries) {\n const role = String(entry.role || entry.type || '');\n const modelInfo = entry.modelInfo as Record<string, unknown> | undefined;\n const model = (typeof modelInfo?.modelName === 'string' && modelInfo.modelName)\n || (typeof modelInfo?.model === 'string' && modelInfo.model)\n || (typeof entry.model === 'string' && entry.model)\n || msgModel(entry);\n if (model) result.model = model;\n\n const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;\n const msg = entry.message as Record<string, unknown> | undefined;\n const hadLine = addUsageBlock(result, tokenCount)\n || addUsageBlock(result, msg?.usage as Record<string, unknown>)\n || addUsageBlock(result, entry.usage as Record<string, unknown>);\n\n if (hadLine || sawExplicit) continue;\n\n const text = usageTextFromTranscriptEntry(entry);\n if (!text) continue;\n const est = Math.ceil(text.length / 4);\n if (role === 'user' || entry.type === 'user') result.totals.in += est;\n else if (role === 'assistant' || entry.type === 'assistant') result.totals.out += est;\n }\n } catch {}\n return result;\n}\n\n/** Emit a usage_tick telemetry row when transcript usage is non-zero. */\nexport function emitUsageTick(params: {\n sessionId: string;\n usage: { model: string; totals: Record<string, number> };\n hookType: string;\n gitRepo?: string;\n modelFallback?: string;\n}): void {\n const { sessionId, usage, hookType, gitRepo, modelFallback } = params;\n if (!sessionId || usage.totals.in + usage.totals.out <= 0) return;\n let model = usage.model || modelFallback || 'unknown';\n if (modelFallback && !usage.model) model = modelFallback;\n if (isCursorHookFormat() && model && !model.startsWith('cursor/') && model !== 'cursor') {\n model = 'cursor/' + model;\n }\n appendLocalTelemetry({\n capture_type: 'usage_tick',\n event_id: mintEventId('usage'),\n hook_type: hookType,\n verdict: 'allow',\n severity: 'none',\n session_id: sessionId,\n model,\n cc_model: model,\n cc_usage: {\n input_tokens: usage.totals.in,\n output_tokens: usage.totals.out,\n cache_creation_input_tokens: usage.totals.cw,\n cache_read_input_tokens: usage.totals.cr,\n },\n ...(gitRepo ? { repo: gitRepo } : {}),\n });\n}\n\nexport function cursorModelFromPayload(payload: Record<string, unknown>): string {\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n if (!rawModel) return 'cursor';\n return rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel;\n}\n\n// ─── Scan Finding Dispatch ───\n\nexport type ScanFindingInput = {\n finding_type: 'cwe' | 'cve';\n finding_id: string;\n severity?: string;\n detail?: string;\n description?: string;\n package_name?: string;\n package_version?: string;\n fixed_version?: string;\n aliases?: string[];\n references?: Array<{ type: string; url: string }>;\n cwe_name?: string;\n};\n\n/** Persist open scan_findings rows for a block and flush the telemetry spool. */\nexport function emitBlockScanFindings(\n jwt: string,\n captureDepth: string,\n ctx: { session_id: string; file_path: string; repo?: string },\n findings: ScanFindingInput[],\n fallback?: ScanFindingInput,\n): void {\n const rows = findings.length > 0 ? findings : (fallback ? [fallback] : []);\n for (const f of rows) {\n dispatchFinding(jwt, {\n session_id: ctx.session_id,\n file_path: ctx.file_path,\n repo: ctx.repo,\n status: 'open',\n ...f,\n }, captureDepth);\n }\n if (rows.length > 0) drainSpool().catch(() => {});\n}\n\nexport function dispatchFinding(\n jwt: string,\n finding: {\n session_id: string;\n file_path: string;\n repo?: string;\n finding_type: 'cwe' | 'cve';\n finding_id: string;\n severity?: string;\n status: 'open' | 'resolved' | 'exempted';\n detail?: string;\n description?: string;\n package_name?: string;\n package_version?: string;\n fixed_version?: string;\n aliases?: string[];\n references?: Array<{ type: string; url: string }>;\n cwe_name?: string;\n },\n captureDepth: string,\n): void {\n const localEntry: Record<string, any> = {\n capture_type: 'scan_finding',\n ...finding,\n };\n appendLocalTelemetry(localEntry);\n\n // Cloud copy carries the full finding; shipCloud gates on storage mode.\n const cloudBody: Record<string, any> = {\n finding_type: finding.finding_type,\n finding_id: finding.finding_id,\n severity: finding.severity,\n status: finding.status,\n session_id: finding.session_id,\n file_path: finding.file_path,\n repo: finding.repo,\n package_name: finding.package_name,\n package_version: finding.package_version,\n fixed_version: finding.fixed_version,\n aliases: finding.aliases,\n references: finding.references,\n cwe_name: finding.cwe_name,\n detail: finding.detail,\n description: finding.description,\n };\n shipCloud(jwt, '/api/v1/hook/finding', cloudBody);\n}\n\nexport function dispatchScanResult(\n jwt: string,\n scan: {\n session_id: string;\n file_path: string;\n scan_type: 'cve' | 'cwe' | 'pkg';\n result: 'pass' | 'block' | 'error';\n finding_count: number;\n finding_ids?: string[];\n severity?: string;\n repo?: string;\n },\n): void {\n const localEntry: Record<string, any> = {\n capture_type: 'scan_result',\n event_id: 'scan_' + Date.now() + '_' + process.pid,\n _ts: new Date().toISOString(),\n ...scan,\n };\n appendLocalTelemetry(localEntry);\n shipCloud(jwt, '/api/v1/hook/scan-result', {\n scan_type: scan.scan_type,\n result: scan.result,\n finding_count: scan.finding_count,\n finding_ids: scan.finding_ids,\n severity: scan.severity,\n session_id: scan.session_id,\n file_path: scan.file_path,\n repo: scan.repo,\n });\n}\n\n// ─── Hook tool-name sets (CC + Cursor) ───\n\nexport const EDIT_TOOL_NAMES = new Set([\n 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'StrReplace',\n 'edit_file', 'reapply', 'edit_notebook', 'ApplyPatch', 'apply_patch',\n]);\nexport const SHELL_TOOL_NAMES = new Set([\n 'Bash', 'Shell', 'Read', 'Grep', 'Glob', 'terminal', 'run_terminal_cmd', 'execute_command',\n]);\nexport const AGENT_TOOL_NAMES = new Set(['Agent', 'Task']);\nexport const PLAN_TOOL_NAMES = new Set(['ExitPlanMode', 'SwitchMode', 'CreatePlan']);\n\nexport function isEditTool(toolName: string): boolean {\n return EDIT_TOOL_NAMES.has(toolName);\n}\nexport function isShellTool(toolName: string): boolean {\n return SHELL_TOOL_NAMES.has(toolName);\n}\nexport function isAgentTool(toolName: string): boolean {\n return AGENT_TOOL_NAMES.has(toolName);\n}\nexport function isPlanTool(toolName: string): boolean {\n return PLAN_TOOL_NAMES.has(toolName);\n}\n\nexport function hookSessionId(payload: Record<string, unknown>): string {\n return String(payload.session_id ?? payload.conversation_id ?? '');\n}\n\nexport function isCursorHookFormat(): boolean {\n return process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor');\n}\n\n// Cursor reads CC hooks from ~/.claude/settings.json and fires them alongside\n// its own ~/.cursor/hooks.json entries. When that happens, agentKind is\n// 'claude_code' but the payload model is non-Claude (e.g. gpt-5.5).\n// Return true so the CC hook can bail out and let Cursor's hooks handle it.\nexport function isCursorInvokingCcHook(agentKind: string, model: string): boolean {\n if (agentKind === 'cursor') return false;\n if (!model || model === 'unknown' || model === '') return false;\n return !model.startsWith('claude-');\n}\n\nlet cursorHookExited = false;\n\nexport function setupCursorHookSignals(): void {\n if (!isCursorHookFormat()) return;\n process.on('SIGTERM', () => outputEmpty());\n}\n\nfunction cursorHookExit(): never {\n cursorHookExited = true;\n process.exit(0);\n}\n\n// ─── Grader-unavailable diagnostic log ───\n// Records every time a hook tried to call the local grader and fell open\n// because the call failed. JSONL at ~/.synkro/grader-unavailable.log so the\n// user can pinpoint cause (timeout vs ECONNREFUSED vs HTTP 5xx vs sick pool)\n// instead of guessing from a one-shot system message in the CC UI.\n\nconst UNAVAIL_LOG = join(HOME, '.synkro', 'grader-unavailable.log');\n\nexport function isGraderNotConfigured(errorMessage: string): boolean {\n const lower = errorMessage.toLowerCase();\n return lower.includes('no worker') || lower.includes('not configured') || lower.includes('agent_kind') || lower.includes('no pool');\n}\n\nexport function graderUnavailableMessage(hook: string, target: string, errorMessage: string, agentKind: AgentKind = 'claude_code'): string {\n if (errorMessage === 'SYNKRO_CHANNEL_DOWN') {\n return hook + ' ' + target + ' → local grader unavailable (container not running), skipped';\n }\n if (isGraderNotConfigured(errorMessage)) {\n const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';\n return hook + ' ' + target + ' → local grader not configured for ' + agent + '. Add this agent to your .synkro file and run \\`synkro install\\`.';\n }\n return hook + ' ' + target + ' → local grader unavailable, skipped';\n}\n\nexport function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {\n try {\n const entry = {\n ts: new Date().toISOString(),\n hook,\n target,\n error: errorMessage.slice(0, 500),\n };\n appendFileSync(UNAVAIL_LOG, JSON.stringify(entry) + '\\\\n', 'utf-8');\n } catch {\n // best-effort — never let logging failure cascade into a hook failure\n }\n}\n\n// ─── Output Helpers ───\n\nexport function outputJson(obj: any): void {\n if (isCursorHookFormat()) {\n if (obj?.permission === 'allow') {\n const u = typeof obj.user_message === 'string' ? obj.user_message : '';\n const a = typeof obj.agent_message === 'string' ? obj.agent_message : u;\n if (u || a) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({ permission: 'allow' }) + '\\\\n');\n }\n cursorHookExit();\n }\n }\n const hso = obj?.hookSpecificOutput;\n const sys = typeof obj?.systemMessage === 'string' ? obj.systemMessage : '';\n if (hso?.permissionDecision === 'deny') {\n const reason = hso.permissionDecisionReason || hso.additionalContext || sys;\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({\n permission: 'deny',\n user_message: sys || reason,\n agent_message: hso.additionalContext || reason,\n }) + '\\\\n');\n }\n cursorHookExit();\n }\n const addCtx = typeof hso?.additionalContext === 'string' ? hso.additionalContext : '';\n const ctx = sys || addCtx;\n if (ctx) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n process.stdout.write(JSON.stringify({ permission: 'allow' }) + '\\\\n');\n }\n cursorHookExit();\n }\n outputEmpty();\n return;\n }\n console.log(JSON.stringify(obj));\n}\n\nexport function outputEmpty(): void {\n if (isCursorHookFormat()) {\n if (!cursorHookExited) {\n cursorHookExited = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n cursorHookExit();\n }\n console.log('{}');\n}\n`;\n\n\n// ─── CC PreToolUse Edit/Write/MultiEdit/NotebookEdit pre-check (TypeScript) ───\n\nexport const EDIT_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, reconstructContent, isPathUnder, postWithRetry,\n readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,\n appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,\n captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n\n const fileShort = basename(filePath);\n log('editGuard checking: ' + fileShort);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName || 'Edit', summary: 'editing ' + fileShort, file: filePath });\n\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n // Reconstruct proposed content\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) { outputEmpty(); return; }\n\n // Build diff field\n let diffField: any = null;\n if (toolInput.old_string != null || toolInput.new_string != null || toolInput.edits != null) {\n diffField = {};\n if (toolInput.old_string != null) diffField.old_string = toolInput.old_string;\n if (toolInput.new_string != null) diffField.new_string = toolInput.new_string;\n if (toolInput.edits != null) diffField.edits = toolInput.edits;\n }\n\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n let existingContent = '';\n if (toolName === 'Write' && existsSync(fullPath)) {\n try { existingContent = readFileSync(fullPath, 'utf-8'); } catch {}\n }\n\n // Compute lines added/removed (Write = net delta vs file on disk)\n const { linesAdded, linesRemoved } = countEditLineDelta(\n toolName === 'Write'\n ? {\n writeContent: toolInput.content || toolInput.contents || '',\n existingContent,\n }\n : {\n old_string: toolInput.old_string,\n new_string: toolInput.new_string,\n edits: Array.isArray(toolInput.edits) ? toolInput.edits : undefined,\n },\n );\n const lineMetrics = captureLineMetrics(agentKind, toolName, linesAdded, linesRemoved);\n\n // Read file before edit for cloud payload\n let fileBefore = '';\n if (toolName !== 'Write' && filePath && isPathUnder(filePath, cwd || '.') && existsSync(fullPath)) {\n try { fileBefore = readFileSync(fullPath, 'utf-8').slice(0, 65536); } catch {}\n }\n\n // Extract transcript context\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n const captureModel = agentKind === 'cursor'\n ? cursorModelFromPayload(payload)\n : (transcript.ccModel || String(payload.model ?? payload.model_id ?? ''));\n\n if (isCursorInvokingCcHook(agentKind, captureModel)) { outputEmpty(); return; }\n\n // Model detection: prefer transcript (CC), fall back to payload (Cursor)\n if (!transcript.ccModel) {\n transcript.ccModel = captureModel;\n }\n\n // Load config and decide route\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' editGuard → skipped (silent mode)' });\n return;\n }\n\n if (rt === 'local') {\n // ─── Local grading: org rules ONLY (channel 1, port 18929) ───\n const proposedShort = proposed.slice(0, 4000);\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const graderContent = 'file=' + filePath + ' content=' + proposedShort;\n const relevantRules = await filterRules(\n ruleFilterText(graderContent, transcript.userIntent || lastPrompt),\n config.rules,\n );\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'File: ' + filePath,\n 'Proposed content (first 4000 chars):',\n proposedShort,\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this edit — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no hardcoded secrets in file. R005: in-repo path only.\" Cover every rule shown.',\n ].join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('editGuard', fileShort, errMsg);\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('editGuard', fileShort, errMsg, graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const editContent = 'file=' + filePath + ' content=' + proposed.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const denyReason = mode === 'fix'\n ? 'Guard: ' + guardReason + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the violation in code yourself.'\n : 'Guard: ' + guardReason + '\\\\nAsk the user for explicit consent before retrying.';\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: relevantRules, violatedRules,\n ccModel: captureModel, ...lineMetrics,\n });\n outputJson({\n systemMessage: tagStr + ' editGuard ' + fileShort + ' → blocked: ' + guardReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n return;\n }\n\n // Clean\n dispatchCapture(jwt, 'edit', 'pass', 'clean', verdict.category || 'trivial_edit',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n ccModel: captureModel, ...lineMetrics,\n });\n const passLine = tagStr + ' editGuard ' + fileShort + ' → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({ systemMessage: passLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge. ' + (verdict.reason || 'no policy violations detected') } });\n return;\n }\n\n // ─── Cloud grading ───\n const deps = findNearestDeps(filePath);\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n file_path: filePath,\n content: proposed,\n file_before: fileBefore || null,\n diff: diffField,\n dependencies: deps,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('editGuard ' + fileShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('editGuard ' + fileShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n const hookResp = resp.hook_response;\n const decision = hookResp?.hookSpecificOutput?.permissionDecision;\n\n if (decision === 'deny' || decision === 'ask') {\n log('editGuard ' + fileShort + ' → BLOCKED');\n // Strip permissionDecision — we use systemMessage only\n const cleaned = { ...hookResp };\n if (cleaned.hookSpecificOutput) {\n cleaned.hookSpecificOutput = { ...cleaned.hookSpecificOutput };\n delete cleaned.hookSpecificOutput.permissionDecision;\n delete cleaned.hookSpecificOutput.permissionDecisionReason;\n }\n outputJson(cleaned);\n } else {\n const reason = hookResp.reason || '';\n log('editGuard ' + fileShort + ' → pass' + (reason ? ': ' + reason : ''));\n outputJson(hookResp);\n }\n } catch (err) {\n log('editGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse CWE scan (standalone, channel 2) (TypeScript) ───\n\nexport const CWE_PRECHECK_TS = String.raw`#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,\n localGradeCwe, parseVerdict, reconstructContent, readStdin, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, isShellTool, isCursorHookFormat,\n extractShellCodeWrites, hookSessionId, filePathFromToolInput, emitBlockScanFindings, dispatchFinding, dispatchCapture, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { basename, extname, resolve, join, dirname } from 'node:path';\nimport { readFileSync, readdirSync, existsSync } from 'node:fs';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nfunction detectModel(payload: Record<string, unknown>): string {\n const raw = String(payload.model ?? payload.model_id ?? '');\n if (agentKind === 'cursor') return raw ? (raw.startsWith('cursor/') ? raw : 'cursor/' + raw) : 'cursor';\n return raw || '';\n}\n\ninterface PackageCapability {\n name: string;\n description: string;\n capabilities: string[];\n sourceExcerpt: string;\n}\n\nconst NON_CODE_EXTS = new Set([\n '.md', '.mdx', '.txt', '.rst', '.adoc', '.org',\n '.log', '.csv', '.tsv', '.html', '.htm',\n '.lock', '.gitignore', '.dockerignore', '.npmignore',\n '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.pdf',\n '.woff', '.woff2', '.ttf', '.otf',\n]);\n\ninterface CweScanTarget {\n filePath: string;\n cweContent: string;\n cweDiffSection: string;\n toolName: string;\n toolInput: any;\n}\n\nconst JS_DANGEROUS_MODULES = new Set([\n 'child_process', 'net', 'dgram', 'http', 'https', 'fs', 'vm',\n 'worker_threads', 'cluster', 'dns', 'tls', 'crypto',\n]);\n\nconst PY_DANGEROUS_MODULES = new Set([\n 'subprocess', 'os', 'socket', 'requests', 'urllib', 'ctypes',\n 'importlib', 'shutil', 'tempfile', 'webbrowser',\n]);\n\nfunction detectNewImports(toolName: string, toolInput: any, fileExt: string): string[] {\n const newCode = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'\n ? (toolInput.new_string || '')\n : toolName === 'ApplyPatch' || toolName === 'apply_patch'\n ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')\n : toolName === 'MultiEdit'\n ? (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\\n') : '')\n : (toolInput.content || toolInput.code_edit || '');\n\n if (!newCode) return [];\n const imports: string[] = [];\n\n if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(fileExt)) {\n const jsMatches = newCode.matchAll(/(?:from\\s+['\"]|require\\s*\\(\\s*['\"])([^./'\"@][^'\"]*|@[^'\"]+)['\"]/g);\n for (const m of jsMatches) {\n const pkg = m[1].startsWith('@') ? m[1].split('/').slice(0, 2).join('/') : m[1].split('/')[0];\n if (pkg && !imports.includes(pkg)) imports.push(pkg);\n }\n } else if (['.py'].includes(fileExt)) {\n const pyMatches = newCode.matchAll(/(?:^import\\s+|^from\\s+)([a-zA-Z_]\\w*)/gm);\n for (const m of pyMatches) {\n if (m[1] && !imports.includes(m[1])) imports.push(m[1]);\n }\n }\n\n return imports;\n}\n\nfunction scanPackageCapabilities(pkgName: string, cwd: string): PackageCapability | null {\n const pkgDir = resolve(cwd, 'node_modules', pkgName);\n if (!existsSync(pkgDir)) return null;\n\n try {\n const pkgJsonPath = join(pkgDir, 'package.json');\n if (!existsSync(pkgJsonPath)) return null;\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n\n const capabilities: string[] = [];\n const dangerousFiles: string[] = [];\n\n function scanDir(dir: string, depth: number) {\n if (depth > 3 || dangerousFiles.length >= 3) return;\n let entries: string[];\n try { entries = readdirSync(dir); } catch { return; }\n for (const entry of entries) {\n if (entry === 'node_modules' || entry.startsWith('.')) continue;\n const full = join(dir, entry);\n if (entry.endsWith('.js') || entry.endsWith('.mjs') || entry.endsWith('.cjs') || entry.endsWith('.ts')) {\n try {\n const src = readFileSync(full, 'utf-8').slice(0, 5000);\n for (const mod of JS_DANGEROUS_MODULES) {\n if (src.includes(\"'\" + mod + \"'\") || src.includes('\"' + mod + '\"')) {\n if (!capabilities.includes(mod)) capabilities.push(mod);\n if (dangerousFiles.length < 3) {\n const short = full.replace(pkgDir + '/', '');\n const lines = src.split('\\n').filter(l =>\n JS_DANGEROUS_MODULES.has(l.match(/require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\)/)?.[1] || '') ||\n JS_DANGEROUS_MODULES.has(l.match(/from\\s+['\"]([^'\"]+)['\"]/)?.[1] || '')\n ).slice(0, 5).join('\\n');\n if (lines) dangerousFiles.push('--- ' + short + ' ---\\n' + lines);\n }\n break;\n }\n }\n } catch {}\n } else {\n try {\n const stat = require('node:fs').statSync(full);\n if (stat.isDirectory()) scanDir(full, depth + 1);\n } catch {}\n }\n }\n }\n\n scanDir(pkgDir, 0);\n\n if (capabilities.length === 0) return null;\n\n return {\n name: pkgName,\n description: pkgJson.description || '',\n capabilities,\n sourceExcerpt: dangerousFiles.join('\\n'),\n };\n } catch {\n return null;\n }\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const shellCommand = typeof payload.command === 'string' ? payload.command.trim() : '';\n const ccModel = detectModel(payload);\n\n if (isCursorInvokingCcHook(agentKind, ccModel)) { outputEmpty(); return; }\n\n const targets: CweScanTarget[] = [];\n\n if (isCursorHookFormat() && (shellCommand || isShellTool(toolName))) {\n const cmd = shellCommand || String(toolInput.command || '');\n if (!cmd) { outputEmpty(); return; }\n for (const w of extractShellCodeWrites(cmd, cwd)) {\n if (w.filePath.includes('/.synkro/hooks/')) continue;\n const ext = extname(w.filePath).toLowerCase();\n if (NON_CODE_EXTS.has(ext)) continue;\n targets.push({\n filePath: w.filePath,\n cweContent: w.content,\n cweDiffSection: '',\n toolName: toolName || 'Shell',\n toolInput: {},\n });\n }\n if (targets.length === 0) { outputEmpty(); return; }\n } else if (isEditTool(toolName)) {\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n const fileExt = extname(filePath).toLowerCase();\n if (NON_CODE_EXTS.has(fileExt)) { outputEmpty(); return; }\n\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) { outputEmpty(); return; }\n\n let cweContent: string;\n let cweDiffSection = '';\n if (toolName === 'Edit' || toolName === 'MultiEdit' || toolName === 'edit_file' || toolName === 'reapply' || toolName === 'ApplyPatch' || toolName === 'apply_patch') {\n const newStr = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'\n ? (toolInput.new_string || '')\n : toolName === 'ApplyPatch' || toolName === 'apply_patch'\n ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')\n : (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\\n') : '');\n cweDiffSection = newStr.slice(0, 4000);\n const changeIdx = proposed.indexOf(newStr);\n if (changeIdx >= 0 && proposed.length > 6000) {\n const start = Math.max(0, changeIdx - 2000);\n const end = Math.min(proposed.length, changeIdx + newStr.length + 2000);\n cweContent = proposed.slice(start, end);\n } else {\n cweContent = proposed.slice(0, 6000);\n }\n } else {\n cweContent = proposed.slice(0, 4000);\n }\n\n targets.push({ filePath, cweContent, cweDiffSection, toolName, toolInput });\n } else {\n outputEmpty();\n return;\n }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await cweRoute(config, synkroFile);\n\n if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cweScan:' + (graderPool === 'claude_code' ? 'claude' : graderPool) + '] skipped (silent mode)' });\n return;\n }\n\n for (const scan of targets) {\n const filePath = scan.filePath;\n const cweContent = scan.cweContent;\n const cweDiffSection = scan.cweDiffSection;\n const scanToolName = scan.toolName;\n const scanToolInput = scan.toolInput;\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n const fileShort = basename(filePath);\n const fileExt = extname(filePath).toLowerCase();\n\n const exemptedCwes = new Set<string>();\n for (const ex of config.scanExemptions) {\n if (ex.cwe_id && filePath.includes(ex.path)) {\n exemptedCwes.add(ex.cwe_id.toUpperCase());\n }\n }\n\n const graderLabel = graderPool === 'claude_code' ? 'claude' : graderPool;\n const cweTag = '[synkro:' + rt + ':cweScan:' + graderLabel + ']';\n\n if (rt === 'skip') {\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, 'channel down', graderPool) });\n return;\n }\n\n if (rt === 'byok') {\n let packageContext: PackageCapability[] | undefined;\n if (cwd) {\n const newImports = detectNewImports(scanToolName, scanToolInput, fileExt);\n if (newImports.length > 0) {\n const caps = newImports\n .slice(0, 5)\n .map(pkg => scanPackageCapabilities(pkg, cwd))\n .filter((c): c is PackageCapability => c !== null);\n if (caps.length > 0) packageContext = caps;\n }\n }\n const scanBody: any = { file_path: filePath, content: cweContent };\n if (packageContext) {\n scanBody.package_context = packageContext.map(c => ({\n name: c.name, description: c.description, capabilities: c.capabilities, source_excerpt: c.sourceExcerpt,\n }));\n }\n\n let cweResp: any;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(scanBody),\n signal: AbortSignal.timeout(12000),\n });\n if (!resp.ok) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud CWE scan failed (HTTP ' + resp.status + '), skipped' });\n return;\n }\n cweResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud CWE scan timeout, skipped' });\n return;\n }\n\n const findings = Array.isArray(cweResp?.findings) ? cweResp.findings : [];\n if (cweResp?.action === 'deny' && findings.length > 0) {\n const activeCweIds = findings\n .filter((f: any) => f.mode === 'blocking' || f.mode === 'ask')\n .map((f: any) => f.cwe)\n .filter((id: string) => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n continue;\n }\n\n const displayIds = activeCweIds.slice(0, 3).join(', ');\n const count = activeCweIds.length;\n const label = count === 1 ? 'match' : 'matches';\n const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';\n\n const fixLines = findings\n .filter((f: any) => activeCweIds.includes(f.cwe) && f.suggested_fix)\n .map((f: any) => '[' + f.cwe + '] Fix: ' + f.suggested_fix);\n const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';\n const denyDetail = '[' + displayIds + '] ' + (findings[0]?.reason || 'code weakness detected');\n const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \\u2014 resolve the weakness in code yourself.';\n\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n activeCweIds.map((cweId) => {\n const f = findings.find((x: any) => x.cwe === cweId);\n return {\n finding_type: 'cwe' as const,\n finding_id: cweId,\n severity: f?.severity || 'high',\n detail: f?.reason || 'code weakness detected',\n cwe_name: f?.name || undefined,\n };\n }),\n {\n finding_type: 'cwe',\n finding_id: activeCweIds[0] || 'CWE-UNKNOWN',\n severity: findings[0]?.severity || 'high',\n detail: denyDetail,\n },\n );\n\n dispatchCapture(jwt, 'cwe', 'block', findings[0]?.severity || 'high', 'security',\n scanToolName, gitRepo, sessionId, config.captureDepth, {\n command: (isShellTool(scanToolName) ? 'shell write ' : 'edit ') + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\n ccModel,\n });\n\n outputJson({\n systemMessage: cweMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: 'pass',\n status: 'resolved',\n }, config.captureDepth);\n continue;\n }\n\n if (rt === 'local') {\n let cweRules: any[] = [];\n let cweRuleFetchFailed = false;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-rules?ext=' + encodeURIComponent(fileExt), {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(4000),\n });\n if (!resp.ok) {\n log('CWE rules fetch failed: HTTP ' + resp.status);\n cweRuleFetchFailed = true;\n } else {\n const data = await resp.json() as any;\n cweRules = data.rules || [];\n }\n } catch (fetchErr: any) {\n log('CWE rules fetch error: ' + (fetchErr?.message || String(fetchErr)));\n cweRuleFetchFailed = true;\n }\n\n if (cweRuleFetchFailed) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 CWE rules unavailable \\u2014 scan skipped' });\n return;\n }\n\n if (cweRules.length === 0) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (no CWE rules for ' + fileExt + ')' });\n return;\n }\n\n let localPkgContext = '';\n if (cwd) {\n const newImports = detectNewImports(scanToolName, scanToolInput, fileExt);\n const caps = newImports.slice(0, 5)\n .map(pkg => scanPackageCapabilities(pkg, cwd))\n .filter((c): c is PackageCapability => c !== null);\n if (caps.length > 0) {\n localPkgContext = '\\n\\nImported packages with notable capabilities:\\n' +\n caps.map(c => '- ' + c.name + ' (claims: \"' + c.description + '\")\\n Capabilities: ' + c.capabilities.join(', ') + '\\n' + c.sourceExcerpt).join('\\n');\n }\n }\n\n const exemptionNote = exemptedCwes.size > 0\n ? '\\n\\nEXEMPTED CWEs (already known, DO NOT report these — focus on NEW weaknesses only): ' + [...exemptedCwes].join(', ')\n : '';\n\n function buildCwePrompt(content: string): string {\n const diffBlock = cweDiffSection\n ? '\\n\\n=== NEW/CHANGED CODE (evaluate THIS for CWE weaknesses) ===\\n' + cweDiffSection + '\\n=== END NEW CODE ===\\n\\nThe surrounding content below is CONTEXT ONLY to help you understand imports, variables, and scope. Do NOT report issues found only in the context section.\\n'\n : '';\n return [\n 'File: ' + filePath,\n diffBlock,\n 'Full content window:',\n content,\n '',\n 'CWE rules to check against:',\n JSON.stringify(cweRules),\n ].join('\\n') + localPkgContext + exemptionNote;\n }\n\n const SPLIT_THRESHOLD = 4000;\n const OVERLAP = 500;\n let gradeResponses: string[] = [];\n\n if (cweContent.length > SPLIT_THRESHOLD) {\n const mid = Math.floor(cweContent.length / 2);\n const chunk1 = cweContent.slice(0, mid + OVERLAP);\n const chunk2 = cweContent.slice(mid - OVERLAP);\n try {\n const [resp1, resp2] = await Promise.all([\n localGradeCwe(buildCwePrompt(chunk1), graderPool),\n localGradeCwe(buildCwePrompt(chunk2), graderPool),\n ]);\n gradeResponses = [resp1, resp2];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n logGraderUnavailable('cweGuard', fileShort, reason);\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });\n return;\n }\n } else {\n try {\n gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent), graderPool)];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n logGraderUnavailable('cweGuard', fileShort, reason);\n outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });\n return;\n }\n }\n\n const cweIds: string[] = [];\n const fixes: Record<string, string> = {};\n let mergedReason = '';\n let mergedSeverity = '';\n let mergedCategory = '';\n let anyFailed = false;\n\n for (const gradeResp of gradeResponses) {\n const v = parseVerdict(gradeResp);\n if (!v.ok) {\n anyFailed = true;\n if (v.reason) mergedReason = mergedReason || v.reason;\n if (v.severity) mergedSeverity = mergedSeverity || v.severity;\n if (v.category) mergedCategory = mergedCategory || v.category;\n const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\\/rule_id>/g) || [];\n for (const m of ruleIdMatches.slice(0, 5)) {\n const id = m.replace(/<\\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');\n if (id && !cweIds.includes(id)) cweIds.push(id);\n }\n const fMatches = gradeResp.match(/<suggested_fix>([^<]+)<\\/suggested_fix>/g) || [];\n const respIds = ruleIdMatches.map(rm => rm.replace(/<\\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-'));\n for (let i = 0; i < Math.min(respIds.length, fMatches.length); i++) {\n if (!fixes[respIds[i]]) fixes[respIds[i]] = fMatches[i].replace(/<\\/?suggested_fix>/g, '').trim();\n }\n }\n }\n\n const verdict = anyFailed\n ? { ok: false, reason: mergedReason, severity: mergedSeverity, category: mergedCategory }\n : { ok: true, reason: '', severity: '', category: '' };\n\n if (!verdict.ok) {\n const activeCweIds = cweIds.filter(id => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n continue;\n }\n\n const cweNameMap = new Map<string, string>();\n for (const r of cweRules) {\n if (r.cwe && r.name) cweNameMap.set(r.cwe.toUpperCase(), r.name);\n }\n\n const displayIds = activeCweIds.slice(0, 3).join(', ');\n const count = activeCweIds.length;\n const label = count === 1 ? 'match' : 'matches';\n const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';\n const denyDetail = '[' + displayIds + '] ' + (verdict.reason || 'code weakness detected');\n const fixLines = activeCweIds\n .filter(id => fixes[id])\n .map(id => '[' + id + '] Fix: ' + fixes[id]);\n const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';\n const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the weakness in code yourself.';\n\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n activeCweIds.map((cweId) => ({\n finding_type: 'cwe' as const,\n finding_id: cweId,\n severity: verdict.severity || 'high',\n detail: verdict.reason || 'code weakness detected',\n cwe_name: cweNameMap.get(cweId.toUpperCase()) || undefined,\n })),\n {\n finding_type: 'cwe',\n finding_id: activeCweIds[0] || 'CWE-UNKNOWN',\n severity: verdict.severity || 'high',\n detail: verdict.reason || denyDetail,\n },\n );\n\n dispatchCapture(jwt, 'cwe', 'block', verdict.severity || 'high', verdict.category || 'security',\n scanToolName, gitRepo, sessionId, config.captureDepth, {\n command: (isShellTool(scanToolName) ? 'shell write ' : 'edit ') + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\n ccModel,\n });\n\n outputJson({\n systemMessage: cweMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: 'pass',\n status: 'resolved',\n }, config.captureDepth);\n continue;\n }\n }\n\n outputEmpty();\n } catch (err) {\n log('cweGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse CVE scan (standalone, curl only) (TypeScript) ───\n\nexport const CVE_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,\n reconstructContent, readStdin, findNearestDeps, filePathFromToolInput, log,\n outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, dispatchCapture, dispatchScanResult, extractTranscript, emitBlockScanFindings, resolveTranscriptPath, GATEWAY_URL,\n isCursorHookFormat,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\nimport { readFileSync } from 'node:fs';\n\nconst MANIFEST_NAMES = new Set([\n 'package.json', 'requirements.txt', 'requirements-dev.txt', 'requirements-test.txt',\n 'Pipfile', 'go.mod', 'go.sum', 'Gemfile', 'pom.xml', 'Cargo.toml', 'composer.json', 'pyproject.toml',\n]);\n\nfunction isManifest(filename: string): boolean {\n if (MANIFEST_NAMES.has(filename)) return true;\n if (filename.startsWith('requirements') && filename.endsWith('.txt')) return true;\n if (filename.startsWith('build.gradle')) return true;\n if (filename.endsWith('.cabal')) return true;\n return false;\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const filePath = filePathFromToolInput(toolInput);\n if (!filePath) { outputEmpty(); return; }\n const gitRepo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n\n if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }\n\n const fileShort = basename(filePath);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const rt = await route(config);\n\n if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cveScan] ' + fileShort + ' → skipped (silent mode)' });\n return;\n }\n\n const cveTag = '[synkro:' + rt + ':cveScan]';\n\n // Reconstruct proposed content\n const proposed = reconstructContent(toolName, toolInput, filePath, cwd);\n if (!proposed) {\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → skip (no content)' });\n return;\n }\n\n const proposedShort = proposed.slice(0, 4000);\n\n // For code files, find nearest package.json and extract deps\n let deps: Record<string, string> = {};\n if (!isManifest(fileShort)) {\n deps = findNearestDeps(filePath);\n }\n\n // For package.json edits, diff the proposed content against the file on\n // disk and scan only the added/upgraded packages. Catches the case the\n // bash installScan misses: a bare \\`pnpm install\\` after a manifest bump\n // arrives with no package tokens on the cmdline, so the install-time\n // scanner has nothing to feed OSV. By gating at the edit, the install\n // that follows is implicitly safe — there's nothing new to scan.\n if (fileShort === 'package.json') {\n try {\n let currentContent = '';\n try { currentContent = readFileSync(filePath, 'utf-8'); } catch {}\n const extractMod: any = await import(new URL('./installExtractCore.ts', import.meta.url).href);\n const deltaPkgs = extractMod.extractPackageJsonDelta(currentContent, proposed) as Array<{ name: string; version: string; ecosystem: string }>;\n if (deltaPkgs.length > 0) {\n const pkgScanResp = await fetch(GATEWAY_URL + '/api/v1/pkg-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ command: 'edit ' + filePath, packages: deltaPkgs }),\n signal: AbortSignal.timeout(8000),\n }).then(r => r.json()).catch(() => null) as any;\n const action = pkgScanResp?.action || 'allow';\n if (action === 'block') {\n const pkgResults = Array.isArray(pkgScanResp?.packages) ? pkgScanResp.packages : [];\n const summary = pkgScanResp?.summary || (deltaPkgs.length + ' package(s) flagged');\n const violatedIds: string[] = [];\n const cveFindings: Array<{\n finding_type: 'cve'; finding_id: string; severity?: string; detail?: string;\n description?: string; package_name?: string; package_version?: string;\n fixed_version?: string; aliases?: string[]; references?: Array<{ type: string; url: string }>;\n }> = [];\n for (const p of pkgResults) {\n if (Array.isArray(p?.findings)) {\n for (const f of p.findings) {\n const aliasCve = Array.isArray(f?.aliases) ? f.aliases.find((a: string) => a.startsWith('CVE-')) : null;\n const fid = aliasCve || f?.id || 'unknown';\n if (fid && !violatedIds.includes(fid)) violatedIds.push(fid);\n cveFindings.push({\n finding_type: 'cve',\n finding_id: fid,\n severity: f?.severity || 'high',\n detail: f?.summary || f?.title || 'vulnerable dependency',\n description: f?.details || undefined,\n package_name: p?.name,\n package_version: p?.version,\n fixed_version: f?.fixed || undefined,\n aliases: f?.aliases || undefined,\n references: f?.references || undefined,\n });\n }\n }\n }\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n cveFindings,\n {\n finding_type: 'cve',\n finding_id: violatedIds[0] || 'SYNKRO_PKGSCAN',\n severity: 'critical',\n detail: summary.slice(0, 500),\n package_name: deltaPkgs[0]?.name,\n package_version: deltaPkgs[0]?.version,\n },\n );\n // Model name isn't in the PreToolUse payload — pull it from the\n // transcript (last assistant entry's message.model). Matches what\n // the bash judge does at capture time.\n let ccModel: string | undefined = String(payload.model ?? payload.model_id ?? '') || undefined;\n if (!ccModel && transcriptPath) {\n try { ccModel = extractTranscript(transcriptPath).ccModel || undefined; } catch {}\n }\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath, reasoning: summary.slice(0, 200),\n violatedRules: violatedIds,\n ccModel,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'pkg',\n result: 'block', finding_count: violatedIds.length,\n finding_ids: violatedIds, severity: 'critical',\n repo: gitRepo || undefined,\n });\n const tagStr = '[synkro:' + rt + ':pkgScan]';\n const denyReason = tagStr + ' BLOCKED: ' + summary + '\\\\nDo not write this version. Pick a fixed/safe version instead.';\n outputJson({\n systemMessage: denyReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n return;\n }\n }\n } catch (err) {\n log('pkgDeltaScan error: ' + String(err));\n }\n }\n\n // CVE scan via OSV API\n const cveBody = {\n file_path: filePath,\n content: proposedShort,\n dependencies: deps,\n };\n\n let cveResp: any;\n try {\n const resp = await fetch(GATEWAY_URL + '/api/v1/cve-scan', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(cveBody),\n signal: AbortSignal.timeout(8000),\n });\n cveResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → error (timeout)' });\n return;\n }\n\n const findings = Array.isArray(cveResp?.findings) ? cveResp.findings : [];\n if (findings.length > 0) {\n const cveRows = findings.slice(0, 10).map((f: any) => {\n const cveId = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown';\n return {\n finding_type: 'cve' as const,\n finding_id: cveId,\n severity: f.severity || 'high',\n detail: f.summary || f.title || 'vulnerable dependency',\n description: f.details || undefined,\n package_name: f.package || undefined,\n package_version: f.version || undefined,\n fixed_version: f.fixed || undefined,\n aliases: f.aliases || undefined,\n references: f.references || undefined,\n };\n });\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: filePath, repo: gitRepo || undefined },\n cveRows,\n cveRows[0],\n );\n\n const formatFinding = (f: any): string => {\n const id = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || '?';\n const pkg = f.package || '?';\n const ver = f.version || '?';\n const title = f.title || f.summary || 'vulnerable';\n const fix = f.fixed ? ' (fix: >=' + f.fixed + ')' : ' (no safe version)';\n return '[' + id + '] ' + pkg + '@' + ver + ': ' + title + fix;\n };\n\n const top3 = findings.slice(0, 3).map(formatFinding).join('; ');\n const count = findings.length;\n const label = count === 1 ? 'advisory' : 'advisories';\n const cveMsg = cveTag + ' ' + fileShort + ' → ' + count + ' ' + label;\n const ctx = 'CVE: ' + top3 + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — upgrade the vulnerable dependencies yourself.';\n\n const cveIds = findings.slice(0, 10).map((f: any) =>\n (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown'\n );\n let cveCcModel: string | undefined = String(payload.model ?? payload.model_id ?? '') || undefined;\n if (!cveCcModel && transcriptPath) {\n try { cveCcModel = extractTranscript(transcriptPath).ccModel || undefined; } catch {}\n }\n dispatchCapture(jwt, 'cve', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: top3,\n violatedRules: cveIds,\n ccModel: cveCcModel,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'cve',\n result: 'block', finding_count: findings.length,\n finding_ids: cveIds, severity: 'critical',\n repo: gitRepo || undefined,\n });\n\n outputJson({\n systemMessage: cveMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: filePath, scan_type: 'cve',\n result: 'pass', finding_count: 0,\n repo: gitRepo || undefined,\n });\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → clean' });\n } catch (err) {\n log('cveGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC/Cursor PreToolUse Install Scan (standalone, fires before bash judge) ───\n\nexport const INSTALL_SCAN_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,\n readStdin, runInstallScan, emitBlockScanFindings, dispatchCapture, dispatchScanResult, hashCommand,\n outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, log, GATEWAY_URL,\n resolveTranscriptPath, isCursorHookFormat, loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const command = typeof payload.command === 'string' ? payload.command : (toolInput.command || '');\n if (!command) { outputEmpty(); return; }\n\n const toolName = payload.tool_name || '';\n // beforeShellExecution supplies command directly; preToolUse uses tool_name + tool_input.\n if (!isShellTool(toolName) && typeof payload.command !== 'string') { outputEmpty(); return; }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const scan = await runInstallScan(command, jwt);\n\n if (scan.scanned) {\n try {\n mkdirSync(SCAN_CACHE_DIR, { recursive: true });\n writeFileSync(join(SCAN_CACHE_DIR, hashCommand(command)), JSON.stringify(scan), 'utf-8');\n } catch {}\n }\n\n if (!scan.scanned) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const repo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n const config = await loadConfig(jwt);\n const isCursor = isCursorHookFormat();\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, isCursor ? 'cursor' : 'claude_code');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const model = isCursor ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');\n\n if (scan.action === 'block') {\n const pkgLabel = scan.scannedLabel || '';\n const pkgName = pkgLabel.split('@')[0] || undefined;\n const pkgVersion = pkgLabel.includes('@') ? pkgLabel.split('@').slice(1).join('@') : undefined;\n emitBlockScanFindings(\n jwt,\n config.captureDepth,\n { session_id: sessionId, file_path: command, repo: repo || undefined },\n scan.findings.map((f) => ({\n finding_type: 'cve' as const,\n finding_id: f.advisoryId + ':' + f.name,\n severity: f.severity,\n detail: f.detail,\n package_name: f.name,\n package_version: f.version,\n })),\n {\n finding_type: 'cve',\n finding_id: 'SYNKRO_PKGSCAN',\n severity: 'critical',\n detail: scan.blockContext.slice(0, 500) || scan.summary || 'Blocked package install',\n package_name: pkgName,\n package_version: pkgVersion,\n },\n );\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: scan.blockContext.slice(0, 200),\n violatedRules: scan.violatedIds,\n ccModel: model || undefined,\n });\n dispatchScanResult(jwt, {\n session_id: sessionId, file_path: command, scan_type: 'pkg',\n result: 'block', finding_count: scan.violatedIds?.length || 1,\n finding_ids: scan.violatedIds, severity: 'critical',\n repo: repo || undefined,\n });\n const denyReason = '[synkro:installScan] BLOCKED: ' + scan.summary + '\\\\nDo not retry this install. Suggest a safe version to the user instead.';\n outputJson({\n systemMessage: denyReason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },\n });\n } else if (scan.action === 'warn') {\n outputJson({\n systemMessage: '[synkro:installScan] ' + scan.summary,\n hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '[synkro:installScan] ' + scan.summary },\n });\n } else {\n outputEmpty();\n }\n } catch (err) {\n log('installScan error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse Bash/Read/Grep/Glob judge (TypeScript) ───\n\nexport const BASH_JUDGE_TS = String.raw`#!/usr/bin/env bun\nimport process from 'node:process';\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,\n loadSynkroFile, effectiveGraderPool,\n hashCommand, resolveTranscriptPath, isCursorHookFormat,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nfunction readCachedScan(command: string): any | null {\n try {\n const path = join(SCAN_CACHE_DIR, hashCommand(command));\n if (!existsSync(path)) return null;\n const data = JSON.parse(readFileSync(path, 'utf-8'));\n unlinkSync(path);\n return data;\n } catch { return null; }\n}\n\nconst DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';\nconst DEDUP_TTL_MS = 3000;\n\nfunction isDuplicate(command: string, sessionId: string): boolean {\n const hash = createHash('md5').update(sessionId + ':' + command).digest('hex').slice(0, 12);\n const marker = DEDUP_DIR + '/' + hash;\n try {\n if (existsSync(marker)) {\n const age = Date.now() - statSync(marker).mtimeMs;\n if (age < DEDUP_TTL_MS) return true;\n }\n } catch {}\n try {\n mkdirSync(DEDUP_DIR, { recursive: true });\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return false;\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isShellTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const transcript = extractTranscript(transcriptPath);\n\n let command = '';\n switch (toolName) {\n case 'Bash':\n case 'Shell':\n case 'terminal':\n case 'run_terminal_cmd':\n case 'execute_command':\n command = toolInput.command || ''; break;\n case 'Read': command = 'cat ' + (toolInput.file_path || ''); break;\n case 'Grep': command = \"grep -r '\" + (toolInput.pattern || '') + \"' \" + (toolInput.path || '.'); break;\n case 'Glob': command = \"find . -name '\" + (toolInput.pattern || '') + \"'\"; break;\n }\n if (!command) { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n\n if (isDuplicate(command, sessionId)) {\n log('bashGuard skip (dedup): ' + command.slice(0, 80));\n outputEmpty();\n return;\n }\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n // Load JWT + routing config eagerly so even the short-circuit message\n // carries the live pack name + local/cloud tag. Cost: ~200-500ms for the\n // config fetch (network call, no caching). The fetch is unavoidable for\n // the LLM path anyway — we just pay it sooner so the short-circuit can\n // produce a properly-tagged system message.\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, 'claude_code');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' bashGuard → skipped (silent mode)' });\n return;\n }\n\n if (isSafeInRepoRead(toolName, command, cwd)) {\n log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');\n appendLocalTelemetry({\n capture_type: 'local_verdict',\n verdict: 'pass',\n hook_type: 'bash',\n category: 'safe_read',\n tool_name: toolName,\n command: command.slice(0, 200),\n session_id: sessionId,\n repo: gitRepo || cwd,\n cc_model: transcript.ccModel,\n reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',\n });\n outputJson({\n systemMessage: tagStr + ' bashGuard → pass: safe in-repo read',\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n additionalContext: tagStr + ' bashGuard pass: safe in-repo read.',\n },\n });\n return;\n }\n\n // ─── Install protection: install-scan runs first and owns block traces ───\n let scanConcern = '';\n let scanBlockContext = '';\n if (toolName === 'Bash') {\n const scan = readCachedScan(command);\n if (scan?.action === 'block') {\n log('bashGuard deferring to install-scan block: ' + cmdShort);\n outputEmpty();\n return;\n }\n if (scan?.action === 'warn') {\n scanBlockContext = scan.blockContext || scan.summary || '';\n scanConcern = 'PACKAGE SCANNER WARNING: ' + scanBlockContext\n + ' Mention this to the user before proceeding.';\n }\n }\n\n const lastPrompt = readLastPrompt(sessionId);\n\n // jwt + config + rt + tagStr already loaded eagerly at top of main\n // (so the short-circuit could emit a properly-tagged message). Silent\n // mode was also checked up there.\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(\n ruleFilterText(command, transcript.userIntent || lastPrompt),\n config.rules,\n );\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Command: ' + command,\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n scanConcern,\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no secrets in grep args. R005: in-repo path only.\" Cover every rule shown.',\n 'Rules with preconditions (e.g. \"run X before Y\") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', errMsg);\n if (scanConcern) {\n const ctx = scanBlockContext + ' Synkro flagged this install but the grader is unavailable to check consent — ask the user for explicit consent, then retry.';\n outputJson({\n systemMessage: tagStr + ' bashGuard → install blocked (scan flagged; grader unavailable)',\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const blockMsg = mode === 'fix'\n ? tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before retrying — do not ask the user.'\n : tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: blockMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext: blockMsg },\n });\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: relevantRules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' bashGuard → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({\n systemMessage: reason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason },\n });\n dispatchCapture(jwt, 'bash', 'pass', 'clean', verdict.category || 'trivial_utility',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n cc_model: transcript.ccModel || null,\n cc_usage: transcript.ccUsage || {},\n session_summary: transcript.sessionSummary || null,\n };\n\n if (scanConcern) {\n // Cloud grading does not carry the package-scanner concern through the\n // consent flow — block here with ask-mode messaging so the user can\n // consent and retry.\n const ctx = scanBlockContext + ' Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: tagStr + ' bashGuard → install blocked: ' + cmdShort,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('bashGuard ' + cmdShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n outputJson(resp.hook_response);\n } catch (err) {\n log('bashGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse Agent subagent judge (TypeScript) ───\n\nexport const AGENT_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,\n outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,\n logGraderUnavailable, graderUnavailableMessage, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n type HookConfig, type Rule,\n} from './_synkro-common.ts';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isAgentTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || '';\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n const prompt = toolInput.prompt || '';\n const description = toolInput.description || '';\n const subagentType = toolInput.subagent_type || 'general-purpose';\n if (!prompt) { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd, transcriptPath, prompt, workspaceRoots);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Agent', summary: 'spawn ' + subagentType + ': ' + description.slice(0, 80) });\n\n const promptShort = prompt.slice(0, 80);\n log('agentGuard checking: ' + description + ' (' + subagentType + ')');\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n if (!transcript.ccModel) {\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n if (agentKind === 'cursor') {\n transcript.ccModel = rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n } else {\n transcript.ccModel = rawModel || '';\n }\n }\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n const msg = tagStr + ' agentGuard → skipped (silent mode)';\n outputJson({ systemMessage: msg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: msg } });\n return;\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const agentText = 'agent=' + subagentType + ' description=' + description + ' prompt=' + prompt.slice(0, 2000);\n const relevantRules = await filterRules(agentText, config.rules);\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Tool: Agent (subagent spawn)',\n 'Subagent type: ' + subagentType,\n 'Description: ' + description,\n 'Subagent prompt (first 4000 chars):',\n prompt.slice(0, 4000),\n 'User intent (last human message): ' + (transcript.userIntent || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);\n } catch (err) {\n const errMsg = (err as Error).message || String(err);\n logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), errMsg);\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('agentGuard', subagentType || 'agent', errMsg, graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const agentContent = 'agent=' + subagentType + ' desc=' + description + ' prompt=' + prompt.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const reason = mode === 'fix'\n ? tagStr + ' agentGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before retrying — do not ask the user.'\n : tagStr + ' agentGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n outputJson({\n systemMessage: reason,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: reason },\n });\n dispatchCapture(jwt, 'agent', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: guardReason, rulesChecked: relevantRules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' agentGuard → pass: ' + (verdict.reason || 'no policy violations detected');\n outputJson({ systemMessage: reason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason } });\n dispatchCapture(jwt, 'agent', 'pass', 'clean', verdict.category || 'subagent_spawn',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const isHeadless = ['acceptEdits', 'bypassPermissions', 'plan', 'auto'].includes(permissionMode)\n || process.env.SYNKRO_HEADLESS === '1';\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName,\n tool_input: toolInput,\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n recent_actions: transcript.recentActions,\n session_history: compressSessionLog(readSessionLog(sessionId)),\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n permission_mode: permissionMode || null,\n headless: isHeadless,\n cc_model: transcript.ccModel || null,\n cc_usage: transcript.ccUsage || {},\n session_summary: transcript.sessionSummary || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);\n\n if (!resp) {\n log('agentGuard ' + promptShort + ' → error (timeout)');\n outputEmpty();\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('agentGuard ' + promptShort + ' → pass (no hook_response)');\n outputEmpty();\n return;\n }\n\n outputJson(resp.hook_response);\n } catch (err) {\n log('agentGuard error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PreToolUse ExitPlanMode plan review (TypeScript) ───\n\nexport const PLAN_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,\n outputJson, outputEmpty, setupCursorHookSignals, isPlanTool, hookSessionId, GATEWAY_URL,\n filterRules, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,\n loadSynkroFile, effectiveGraderPool,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nconst agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';\n\nfunction findLatestPlanInDir(plansDir: string): string | null {\n if (!existsSync(plansDir)) return null;\n try {\n const files = readdirSync(plansDir)\n .filter(f => f.endsWith('.md'))\n .map(f => ({ name: f, mtime: statSync(join(plansDir, f)).mtimeMs }))\n .sort((a, b) => b.mtime - a.mtime);\n return files.length > 0 ? join(plansDir, files[0].name) : null;\n } catch {\n return null;\n }\n}\n\nfunction findLatestPlan(): string | null {\n const dirs = [\n join(homedir(), '.claude', 'plans'),\n join(homedir(), '.cursor', 'plans'),\n ];\n let best: { path: string; mtime: number } | null = null;\n for (const dir of dirs) {\n const p = findLatestPlanInDir(dir);\n if (!p) continue;\n try {\n const mtime = statSync(p).mtimeMs;\n if (!best || mtime > best.mtime) best = { path: p, mtime };\n } catch {}\n }\n return best?.path ?? null;\n}\n\nfunction appendReviewToPlan(planFile: string, verdict: string): void {\n try {\n let content = readFileSync(planFile, 'utf-8');\n content = content.replace(/<!-- synkro-plan-review -->[\\\\s\\\\S]*?<!-- \\\\/synkro-plan-review -->/g, '').trimEnd();\n const now = new Date().toISOString().replace('T', ' ').slice(0, 16);\n content += '\\\\n\\\\n<!-- synkro-plan-review -->\\\\n\\\\n---\\\\n\\\\n**Synkro Plan Review** \\\\u2014 ' + now + '\\\\n\\\\n' + verdict + '\\\\n\\\\n<!-- /synkro-plan-review -->\\\\n';\n writeFileSync(planFile, content, 'utf-8');\n } catch {}\n}\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n if (!isPlanTool(toolName)) { outputEmpty(); return; }\n\n const _m = String(payload.model ?? payload.model_id ?? '');\n if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }\n\n const planFile = findLatestPlan();\n if (!planFile) { outputEmpty(); return; }\n const plan = readFileSync(planFile, 'utf-8');\n if (plan.length < 20) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, plan, workspaceRoots);\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'ExitPlanMode', summary: 'plan review: ' + plan.slice(0, 80) });\n\n const planShort = plan.slice(0, 80);\n log('planReview checking: ' + planShort + '...');\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const ccModel = agentKind === 'cursor' ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');\n\n const config = await loadConfig(jwt);\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, agentKind);\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n if (config.silent) {\n outputJson({ systemMessage: tagStr + ' planReview → skipped (silent mode)' });\n return;\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(plan.slice(0, 2000), config.rules);\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Plan:',\n plan.slice(0, 8000),\n 'Org rules: ' + JSON.stringify(relevantRules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('plan', graderPrompt, undefined, graderPool);\n } catch (err) {\n outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('planReview', 'plan', (err as Error).message || String(err), graderPool) });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const planContent = plan.slice(0, 2000);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const reviewMsg = (verdict.ruleId ? '(first: ' + verdict.ruleId + ') ' : '') + (verdict.reason || 'check org rules during implementation');\n appendReviewToPlan(planFile, '\\\\u26a0\\\\ufe0f Advisory \\\\u2014 ' + reviewMsg);\n const advLine = tagStr + ' planReview → ' + reviewMsg;\n outputJson({ systemMessage: advLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local plan judge (advisory). ' + reviewMsg } });\n dispatchCapture(jwt, 'plan_review', 'advisory', verdict.severity || 'medium', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: verdict.reason || 'check org rules',\n rulesChecked: relevantRules, violatedRules, ccModel: ccModel || undefined,\n });\n } else {\n const reviewMsg = verdict.reason || 'no relevant org rules for this plan';\n appendReviewToPlan(planFile, '\\\\u2705 Clean \\\\u2014 ' + reviewMsg);\n const cleanLine = tagStr + ' planReview → clean: ' + reviewMsg;\n outputJson({ systemMessage: cleanLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local plan judge. ' + reviewMsg } });\n dispatchCapture(jwt, 'plan_review', 'clean', 'clean', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: reviewMsg,\n rulesChecked: relevantRules, violatedRules: [], ccModel: ccModel || undefined,\n });\n }\n return;\n }\n\n // ─── Cloud grading ───\n const body = {\n hook_event: 'PreToolUse',\n tool_name: 'ExitPlanMode',\n tool_input: { plan: plan.slice(0, 16000) },\n session_id: sessionId || null,\n cwd: cwd || null,\n repo: gitRepo || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 12000);\n\n if (!resp) {\n log('planReview → error (timeout)');\n outputEmpty();\n return;\n }\n\n const hookResp = resp?.hook_response;\n if (!hookResp) { outputEmpty(); return; }\n\n const decision = hookResp?.hookSpecificOutput?.permissionDecision;\n if (decision) {\n const reason = hookResp?.hookSpecificOutput?.permissionDecisionReason || 'check org rules';\n appendReviewToPlan(planFile, '\\\\u26a0\\\\ufe0f Advisory \\\\u2014 ' + reason);\n outputJson({ systemMessage: tagStr + ' planReview → advisory: ' + reason });\n } else {\n const cloudMsg = hookResp.systemMessage || '';\n if (cloudMsg) appendReviewToPlan(planFile, '\\\\u2705 ' + cloudMsg);\n outputJson(hookResp);\n }\n } catch (err) {\n log('planReview error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC SessionEnd stop summary (TypeScript) ───\n\nexport const STOP_SUMMARY_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, loadConfig, tag, readStdin, aggregateUsage, cleanupSessionLog,\n outputJson, outputEmpty, shipCloud, setupCursorHookSignals, hookSessionId, GATEWAY_URL,\n resolveTranscriptPath, emitUsageTick, cursorModelFromPayload, log,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n const modelFallback = cursorModelFromPayload(payload);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n if (transcriptPath) {\n const usage = aggregateUsage(transcriptPath, { modelFallback });\n emitUsageTick({ sessionId, usage, hookType: 'session_end', gitRepo, modelFallback });\n }\n\n let resp: any;\n try {\n const r = await fetch(GATEWAY_URL + '/api/v1/cli/session-summary?session_id=' + encodeURIComponent(sessionId), {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n resp = await r.json();\n } catch {\n outputEmpty();\n return;\n }\n\n const edits = resp?.edits_scanned || 0;\n const findings = resp?.findings || 0;\n const autoFixed = resp?.auto_fixed || 0;\n const open = resp?.open || 0;\n\n if (!edits) { cleanupSessionLog(sessionId); outputEmpty(); return; }\n\n const config = await loadConfig(jwt);\n const tagStr = tag('local', config);\n\n cleanupSessionLog(sessionId);\n\n if (!findings) {\n outputJson({ systemMessage: tagStr + ' stop → 0 issues across ' + edits + ' edit(s), session complete' });\n } else {\n outputJson({ systemMessage: tagStr + ' stop → ' + findings + ' finding(s): ' + autoFixed + ' auto-fixed, ' + open + ' open' });\n }\n } catch (err) {\n log('stopSummary error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC SessionStart (TypeScript) ───\n\nexport const SESSION_START_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, channelUp, tag, readStdin, writeCachedRepo,\n outputJson, outputEmpty, setupCursorHookSignals, hookSessionId, resolveTranscriptPath, GATEWAY_URL,\n isLocalStorageMode, loadSynkroFile, log, type HookConfig,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const sessionId = hookSessionId(payload);\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n if (gitRepo) writeCachedRepo(gitRepo);\n\n let jwt = loadJwt();\n\n const isChannelUp = await channelUp();\n const synkroFile = loadSynkroFile(cwd);\n const gradingMode = synkroFile.grader.mode || process.env.SYNKRO_GRADING_MODE || 'local';\n const rt = gradingMode === 'byok' ? 'cloud' : (isChannelUp ? 'local' : 'cloud');\n\n let policyName = '';\n let silent = false;\n let openFindings = 0;\n\n if (isLocalStorageMode()) {\n try {\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const r = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/hook-config', {\n signal: AbortSignal.timeout(1500),\n });\n if (r.ok) {\n const data = await r.json() as any;\n silent = data.silent === true;\n policyName = data.policy?.name || '';\n }\n } catch {}\n } else if (jwt) {\n try {\n const url = GATEWAY_URL + '/api/v1/hook/config?session_id=' + encodeURIComponent(sessionId || '') + '&repo=' + encodeURIComponent(gitRepo || '');\n const r = await fetch(url, {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n const data = await r.json() as any;\n silent = data.silent_mode === true || data.silent_mode === 'true';\n policyName = data.active_policy_name || '';\n openFindings = data.session_context?.open_findings || 0;\n } catch {}\n }\n\n const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };\n const tagStr = tag(rt, fakeConfig);\n const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');\n\n if (!jwt) {\n outputJson({ systemMessage: routeLine });\n return;\n }\n\n // Sync grading mode to API profile so the dashboard reflects the actual config\n const fastInference = gradingMode === 'byok';\n fetch(GATEWAY_URL + '/api/v1/cli/me', {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({ fast_inference: fastInference }),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n\n if (!openFindings) {\n outputJson({ systemMessage: routeLine });\n } else if (openFindings === 1) {\n outputJson({ systemMessage: routeLine + '\\\\n' + tagStr + ' session start → 1 open finding in this repo from a prior session.' });\n } else {\n outputJson({ systemMessage: routeLine + '\\\\n' + tagStr + ' session start → ' + openFindings + ' open findings in this repo from prior sessions.' });\n }\n } catch (err) {\n log('sessionStart error: ' + String(err));\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC PostToolUse Bash followup (TypeScript) ───\n\nexport const BASH_FOLLOWUP_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, loadConfig, readStdin, hashCommand, consentGrant, consentHasActive, consentConsume,\n appendSessionAction,\n outputEmpty, appendLocalTelemetry, shipCloud, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const shellCmd = typeof payload.command === 'string' ? payload.command : (payload.tool_input?.command || '');\n if (!isShellTool(toolName) && !shellCmd) { outputEmpty(); return; }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n const toolUseId = payload.tool_use_id || payload.tool_call_id || 'cursor-shell';\n if (!sessionId) { outputEmpty(); return; }\n\n let isError = payload.tool_result?.is_error === true;\n try {\n const out = JSON.parse(payload.tool_output || '{}');\n if ((typeof out.exitCode === 'number' && out.exitCode !== 0) || out.is_error === true) isError = true;\n } catch {}\n const cmd = shellCmd;\n const cmdHash = cmd ? hashCommand(cmd) : '';\n\n if (cmdHash && sessionId) {\n if (!isError) {\n consentConsume(sessionId, cmdHash);\n } else {\n if (!consentHasActive(sessionId, cmdHash)) {\n consentGrant(sessionId, cmdHash);\n }\n }\n }\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Bash', summary: cmd.slice(0, 120), outcome: isError ? 'exit 1' : 'exit 0' });\n\n const body = {\n capture_type: 'bash_followup',\n session_id: sessionId,\n tool_use_id: toolUseId,\n is_error: isError,\n command_hash: cmdHash,\n };\n\n appendLocalTelemetry(body);\n shipCloud(jwt, '/api/v1/hook/capture', body);\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC Stop transcript sync (TypeScript) ───\n\nexport const TRANSCRIPT_SYNC_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, detectRepo, readStdin, aggregateUsage, appendLocalTelemetry,\n outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL, readSessionLog, shipCloud,\n resolveTranscriptPath, syncConversationTranscript, emitUsageTick, cursorModelFromPayload,\n} from './_synkro-common.ts';\nimport { readFileSync } from 'node:fs';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n\n if (!sessionId || !transcriptPath) {\n outputEmpty();\n return;\n }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const usage = aggregateUsage(transcriptPath, { modelFallback: cursorModelFromPayload(payload) });\n emitUsageTick({\n sessionId,\n usage,\n hookType: 'stop',\n gitRepo: detectRepo(cwd, transcriptPath, '', workspaceRoots),\n modelFallback: cursorModelFromPayload(payload),\n });\n\n const cloudConsent = process.env.SYNKRO_TRANSCRIPT_CONSENT !== 'no';\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n\n const { messages } = await syncConversationTranscript(sessionId, transcriptPath, gitRepo || '');\n\n if (cloudConsent && gitRepo && messages.length > 0 && (process.env.SYNKRO_STORAGE_MODE || 'local') === 'cloud') {\n fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify({\n repo: gitRepo,\n sessions: [{ cc_session_id: sessionId, messages, actions: readSessionLog(sessionId) }],\n }),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n }\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── CC UserPromptSubmit (TypeScript) ───\n\nexport const USER_PROMPT_SUBMIT_TS = `#!/usr/bin/env bun\nimport { readStdin, appendLocalTelemetry, aggregateUsage, outputEmpty, setupCursorHookSignals, hookSessionId, detectRepo, resolveTranscriptPath, syncConversationTranscript, emitUsageTick, cursorModelFromPayload } from './_synkro-common.ts';\nimport { writeFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n const payload = JSON.parse(input);\n const msg = payload.message || payload.prompt || payload.content || '';\n if (msg) {\n const promptFile = join(homedir(), '.synkro', '.last-prompt');\n mkdirSync(dirname(promptFile), { recursive: true, mode: 0o700 });\n writeFileSync(promptFile, msg, { encoding: 'utf-8', mode: 0o600 });\n const sid = hookSessionId(payload);\n if (sid) {\n const sessDir = join(homedir(), '.synkro', 'sessions');\n mkdirSync(sessDir, { recursive: true, mode: 0o700 });\n writeFileSync(join(sessDir, sid + '.last-prompt'), msg, { encoding: 'utf-8', mode: 0o600 });\n }\n }\n\n const sessionId = hookSessionId(payload);\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];\n const cwd = payload.cwd || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n if (sessionId && transcriptPath) {\n const gitRepo = detectRepo(cwd, transcriptPath, '', workspaceRoots);\n syncConversationTranscript(sessionId, transcriptPath, gitRepo || '').catch(() => {});\n const modelFallback = cursorModelFromPayload(payload);\n const usage = aggregateUsage(transcriptPath, { modelFallback });\n emitUsageTick({ sessionId, usage, hookType: 'prompt_submit', gitRepo, modelFallback });\n }\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n\n// ─── Cursor IDE TypeScript adapter scripts ───\n\nexport const CURSOR_BASH_JUDGE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, dispatchFinding, ruleMode, normalizeMode, filterRules, ruleFilterText,\n isSafeInRepoRead, resolveTranscriptPath, postWithRetry, readStdin, hashCommand,\n extractTranscript, readLastPrompt, readSessionLog, compressSessionLog,\n appendLocalTelemetry, logGraderUnavailable, graderUnavailableMessage, log, GATEWAY_URL,\n loadSynkroFile, effectiveGraderPool,\n type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';\n\nfunction readCachedScan(command: string): any | null {\n try {\n const path = join(SCAN_CACHE_DIR, hashCommand(command));\n if (!existsSync(path)) return null;\n const data = JSON.parse(readFileSync(path, 'utf-8'));\n unlinkSync(path);\n return data;\n } catch { return null; }\n}\n\nconst DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';\nconst DEDUP_TTL_MS = 3000;\n\nfunction isDuplicate(command: string, sessionId: string): boolean {\n const hash = createHash('md5').update(sessionId + ':' + command).digest('hex').slice(0, 12);\n const marker = DEDUP_DIR + '/' + hash;\n try {\n if (existsSync(marker)) {\n const age = Date.now() - statSync(marker).mtimeMs;\n if (age < DEDUP_TTL_MS) return true;\n }\n } catch {}\n try {\n mkdirSync(DEDUP_DIR, { recursive: true });\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return false;\n}\n\n// Cursor beforeShellExecution timeout is 15s; stay under it (JWT refresh + grade).\nconst CURSOR_GRADE_TIMEOUT_MS = 12000;\nconst CURSOR_CLOUD_TIMEOUT_MS = 9000;\n\nlet hookDone = false;\n\nfunction finishAllow(): never {\n if (!hookDone) {\n hookDone = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n process.exit(0);\n}\n\nfunction finishWith(payload: Record<string, unknown>): never {\n hookDone = true;\n process.stdout.write(JSON.stringify(payload) + '\\\\n');\n process.exit(0);\n}\n\nprocess.on('SIGTERM', () => finishAllow());\n\nconst SHELL_TOOL_NAMES = new Set(['Bash', 'Shell', 'terminal', 'run_terminal_cmd', 'execute_command']);\nconst READ_TOOL_NAMES = new Set(['Read', 'ReadFile', 'read_file']);\nconst SEARCH_TOOL_NAMES = new Set(['Grep', 'grep_search', 'codebase_search', 'file_search']);\nconst DIR_TOOL_NAMES = new Set(['Glob', 'list_dir']);\nconst DELETE_TOOL_NAMES = new Set(['delete_file']);\nconst BASH_PRE_TOOL_NAMES = new Set([...SHELL_TOOL_NAMES, ...READ_TOOL_NAMES, ...SEARCH_TOOL_NAMES, ...DIR_TOOL_NAMES, ...DELETE_TOOL_NAMES]);\n\nfunction extractCommand(payload: Record<string, unknown>): { command: string; toolName: string } {\n const direct = typeof payload.command === 'string' ? payload.command : '';\n if (direct) return { command: direct, toolName: 'Bash' };\n\n const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : '';\n if (!BASH_PRE_TOOL_NAMES.has(toolName)) return { command: '', toolName };\n\n const toolInput = (payload.tool_input && typeof payload.tool_input === 'object')\n ? payload.tool_input as Record<string, unknown>\n : {};\n\n let command = '';\n if (SHELL_TOOL_NAMES.has(toolName)) {\n command = String(toolInput.command ?? '');\n } else if (READ_TOOL_NAMES.has(toolName)) {\n command = 'cat ' + String(toolInput.file_path ?? toolInput.path ?? '');\n } else if (SEARCH_TOOL_NAMES.has(toolName)) {\n command = \"grep -r '\" + String(toolInput.pattern ?? toolInput.query ?? '') + \"' \" + String(toolInput.path ?? '.');\n } else if (DIR_TOOL_NAMES.has(toolName)) {\n command = \"find . -name '\" + String(toolInput.pattern ?? toolInput.relative_workspace_path ?? '') + \"'\";\n } else if (DELETE_TOOL_NAMES.has(toolName)) {\n command = 'rm ' + String(toolInput.target_file ?? toolInput.file_path ?? toolInput.path ?? '');\n }\n return { command, toolName: toolName || 'Bash' };\n}\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finishAllow();\n\n const payload = JSON.parse(input) as Record<string, unknown>;\n const { command, toolName } = extractCommand(payload);\n if (!command) finishAllow();\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string') : [];\n const cwd = typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : (workspaceRoots[0] || '');\n const sessionId = String(payload.conversation_id ?? payload.session_id ?? '');\n\n if (isDuplicate(command, sessionId)) {\n log('bashGuard skip (dedup): ' + command.slice(0, 80));\n finishAllow();\n }\n\n const transcriptPath = resolveTranscriptPath(payload);\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const model = rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n const repo = detectRepo(cwd, transcriptPath, command, workspaceRoots);\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n // Instant-allow read-only tool calls + safe in-repo bash reads — no grade,\n // no network. Critical under Cursor's tight 15s beforeShellExecution budget.\n if (isSafeInRepoRead(toolName, command, cwd)) {\n log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');\n appendLocalTelemetry({\n capture_type: 'local_verdict', verdict: 'pass', hook_type: 'bash',\n category: 'safe_read', tool_name: toolName, command: command.slice(0, 200),\n session_id: sessionId, repo: repo || cwd,\n cc_model: model,\n reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',\n });\n finishAllow();\n }\n\n let jwt = loadJwt();\n if (!jwt) finishAllow();\n jwt = await ensureFreshJwt(jwt);\n\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n const config = await loadConfig(jwt);\n if (config.silent) finishAllow();\n\n const synkroFile = loadSynkroFile(cwd);\n const graderPool = effectiveGraderPool(synkroFile, 'cursor');\n const rt = await route(config, synkroFile);\n const tagStr = tag(rt, config, graderPool);\n\n // Install protection — install-scan runs first and owns block traces.\n let scanConcern = '';\n let scanBlockContext = '';\n if (SHELL_TOOL_NAMES.has(toolName)) {\n const scan = readCachedScan(command);\n if (scan?.action === 'block') {\n log('bashGuard deferring to install-scan block: ' + cmdShort);\n finishAllow();\n }\n if (scan?.action === 'warn') {\n scanBlockContext = scan.blockContext || scan.summary || '';\n scanConcern = 'PACKAGE SCANNER WARNING: ' + scanBlockContext\n + ' Mention this to the user before proceeding.';\n }\n }\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const relevantRules = await filterRules(\n ruleFilterText(command, transcript.userIntent || lastPrompt),\n config.rules,\n );\n\n const graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (repo || 'unknown'),\n sessionLog,\n 'Command: ' + command,\n 'User intent (last human message): ' + (transcript.userIntent || lastPrompt || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n 'Org rules: ' + JSON.stringify(relevantRules),\n scanConcern,\n 'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is \"fix\". The enforcement layer handles ask vs fix — your job is only to detect violations.',\n 'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said \"drop the database\" or \"delete everything\", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\\'s initial instruction is NEVER consent — only a response to a shown block counts.',\n 'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any \"not relevant\". When passing (ok=true), give a terse, specific reason each rule passes. Format: \"R003: no secrets in grep args. R005: in-repo path only.\" Cover every rule shown.',\n 'Rules with preconditions (e.g. \"run X before Y\") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS, graderPool);\n } catch (e) {\n logGraderUnavailable('bashGuard', command.slice(0, 200), (e as Error).message || String(e));\n if (scanConcern) {\n // Grader unavailable to run the consent check — fail closed on a\n // scanner-flagged install (ask-mode so the user can still consent).\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' installScan → blocked (grader unavailable): ' + cmdShort,\n agent_message: 'Synkro flagged this install: ' + scanBlockContext + ' The grader is unavailable to check consent — ask the user for explicit consent, then retry.',\n });\n }\n const errMsg = (e as Error).message || String(e);\n log('bashGuard ' + cmdShort + ' → pass (grade unavailable): ' + errMsg);\n finishWith({ permission: 'allow', user_message: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg, graderPool) });\n }\n\n const verdict = parseVerdict(gradeResp);\n\n if (!verdict.ok) {\n const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n const agentMsg = mode === 'fix'\n ? 'Synkro safety judge. Fix this before retrying — do not ask the user. Reasoning: ' + (verdict.reason || guardReason)\n : 'Synkro safety judge. Ask the user for explicit consent before retrying. Reasoning: ' + (verdict.reason || guardReason);\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: relevantRules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' bashGuard → block: ' + guardReason,\n agent_message: agentMsg,\n });\n } else {\n dispatchCapture(jwt, 'bash', 'pass', 'clean', verdict.category || 'clean',\n 'Bash', repo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: relevantRules, violatedRules: [],\n ccModel: model,\n });\n }\n\n const passReason = verdict.reason || 'no policy violations detected';\n log('bashGuard ' + cmdShort + ' → pass: ' + passReason);\n finishWith({ permission: 'allow' });\n }\n\n if (scanConcern) {\n // Cloud grading does not carry the package-scanner concern through the\n // consent flow — block here with ask-mode messaging so the user can\n // consent and retry.\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' installScan → blocked: ' + cmdShort,\n agent_message: 'Synkro flagged this install: ' + scanBlockContext + ' Ask the user for explicit consent before retrying.',\n });\n }\n\n const body: Record<string, any> = {\n hook_event: 'PreToolUse',\n tool_name: toolName || 'Bash',\n tool_input: { command },\n response_format: 'cursor',\n user_intent: transcript.userIntent || null,\n last_user_message: lastPrompt || null,\n recent_user_messages: transcript.recentUserMessages,\n recent_messages: transcript.recentMessages,\n session_id: sessionId || null,\n cwd: cwd || null,\n repo: repo || null,\n };\n\n const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, CURSOR_CLOUD_TIMEOUT_MS);\n\n if (!resp) {\n log('bashGuard ' + cmdShort + ' → pass (cloud timeout)');\n finishAllow();\n }\n\n if (resp.hook_response) {\n const hr = resp.hook_response as Record<string, unknown>;\n if (hr.permission === 'allow') {\n const um = String(hr.user_message || '');\n const am = String(hr.agent_message || um);\n if (um || am) {\n finishWith({ permission: 'allow' });\n }\n }\n finishWith(hr);\n }\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n finishAllow();\n } catch (e) {\n log('bashGuard error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('bashGuard fatal: ' + String(e));\n finishAllow();\n});`;\n\nexport const CURSOR_EDIT_CAPTURE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, readStdin, resolveTranscriptPath,\n appendSessionAction, appendLocalTelemetry, shipCloud, log, GATEWAY_URL,\n countEditLineDelta, dispatchCapture, hookSessionId, cursorModelFromPayload,\n isLocalStorageMode,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\n\nlet hookDone = false;\n\nfunction finish(): never {\n if (!hookDone) {\n hookDone = true;\n try { process.stdout.write('{}\\\\n'); } catch {}\n }\n process.exit(0);\n}\n\nprocess.on('SIGTERM', () => finish());\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finish();\n\n const payload = JSON.parse(input) as Record<string, unknown>;\n const filePath = String(payload.file_path || payload.path || payload.target_file || '');\n if (!filePath) finish();\n\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? (payload.workspace_roots as unknown[]).filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const transcriptPath = resolveTranscriptPath(payload);\n const sessionId = hookSessionId(payload);\n const repo = detectRepo(cwd, transcriptPath, filePath, workspaceRoots);\n const model = cursorModelFromPayload(payload);\n\n const edits = Array.isArray(payload.edits) ? payload.edits as Array<{ old_string?: string; new_string?: string }> : [];\n const { linesAdded, linesRemoved } = countEditLineDelta({ edits });\n\n log('editScan ' + basename(filePath) + ' +' + linesAdded + '/-' + linesRemoved);\n\n appendSessionAction(sessionId, {\n ts: new Date().toISOString(),\n tool: 'Edit',\n summary: 'wrote ' + basename(filePath) + (linesAdded || linesRemoved ? (' (+' + linesAdded + '/-' + linesRemoved + ')') : ''),\n file: filePath,\n outcome: 'ok',\n });\n\n let jwt = loadJwt();\n if (!jwt) finish();\n jwt = await ensureFreshJwt(jwt);\n\n let dependencies: Record<string, string> = {};\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n let pkgDir = cwd || dirname(fullPath);\n while (pkgDir !== '/' && pkgDir !== '.') {\n const pkgPath = join(pkgDir, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n dependencies = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };\n } catch {}\n break;\n }\n const parent = dirname(pkgDir);\n if (parent === pkgDir) break;\n pkgDir = parent;\n }\n\n const localSpoolBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, lines_added: linesAdded, lines_removed: linesRemoved },\n edit_verdict: { ok: true },\n dependencies,\n cc_model: model,\n model,\n };\n if (sessionId) localSpoolBody.session_id = sessionId;\n if (cwd) localSpoolBody.cwd = cwd;\n if (repo) localSpoolBody.repo = repo;\n appendLocalTelemetry(localSpoolBody);\n\n if (!isLocalStorageMode()) {\n let fileContent = '';\n try {\n if (existsSync(fullPath)) {\n fileContent = readFileSync(fullPath).slice(0, 50000).toString('utf-8');\n }\n } catch {}\n const cloudBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, content: fileContent },\n edit_verdict: { ok: true },\n dependencies,\n cc_model: model,\n model,\n };\n if (sessionId) cloudBody.session_id = sessionId;\n if (cwd) cloudBody.cwd = cwd;\n if (repo) cloudBody.repo = repo;\n shipCloud(jwt, '/api/v1/hook/capture', cloudBody, 10000);\n }\n\n if (sessionId) {\n dispatchCapture(jwt, 'edit', 'pass', 'clean', 'trivial_edit', 'Edit', repo, sessionId, 'full', {\n command: 'file=' + filePath,\n reasoning: 'Cursor afterFileEdit',\n ccModel: model,\n linesAdded,\n linesRemoved,\n });\n }\n\n finish();\n } catch (e) {\n log('editScan error: ' + String(e));\n finish();\n }\n}\n\nmain().catch((e) => {\n log('editScan fatal: ' + String(e));\n finish();\n});`;\n\nexport const CURSOR_AGENT_CAPTURE_TS = `#!/usr/bin/env bun\n/** Capture Cursor agent thinking/response text before transcript JSONL redacts it. */\nimport {\n readStdin, outputEmpty, setupCursorHookSignals, hookSessionId, detectRepo,\n appendThoughtOverlay, pushConversationMessage,\n} from './_synkro-common.ts';\n\nasync function main() {\n setupCursorHookSignals();\n try {\n const input = await readStdin();\n if (!input.trim()) { outputEmpty(); return; }\n const payload = JSON.parse(input) as Record<string, unknown>;\n const event = String(payload.hook_event_name || '');\n const text = typeof payload.text === 'string' ? payload.text.trim() : '';\n if (!text || text === '[REDACTED]') { outputEmpty(); return; }\n\n const sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots)\n ? payload.workspace_roots.filter((r): r is string => typeof r === 'string')\n : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const gitRepo = detectRepo(cwd, '', '', workspaceRoots);\n\n if (event === 'afterAgentThought') {\n appendThoughtOverlay(sessionId, text);\n await pushConversationMessage(sessionId, 'assistant', text, { gitRepo, patchRedacted: true });\n } else if (event === 'afterAgentResponse') {\n await pushConversationMessage(sessionId, 'assistant', text, { gitRepo, patchRedacted: false });\n }\n\n outputEmpty();\n } catch {\n outputEmpty();\n }\n}\n\nmain();\n`;\n\n","/**\n * Synkro CLI Authentication\n *\n * OAuth-style authentication flow for CLI integration with Synkro web platform.\n * Mirrors the Node.js reference implementation.\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from \"node:http\";\nimport { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport jwt from \"jsonwebtoken\";\n\n// Types\ninterface WorkOSJwtPayload {\n iss: string; // \"https://api.workos.com/\"\n sub: string; // user ID\n aud?: string; // client ID\n exp: number;\n iat: number;\n email?: string;\n org_id?: string;\n role?: string;\n permissions?: string[];\n sid?: string; // session ID\n}\n\n// Configuration — matches the desktop app pattern (packages/desktop/src-tauri/src/auth.rs).\n// Dev dashboard runs on :4322; CLI listens on 8100 for the OAuth callback.\nconst PORT = 8100;\n// Same poisoning concern as the gateway URL: a developer's local .env or\n// op:// expansion can land in SYNKRO_WEB_AUTH_URL. Only honor http(s) values;\n// fall through to the prod dashboard otherwise so the OAuth callback always\n// has a real origin to open the browser at.\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : \"https://app.synkro.sh\";\nconst AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join(homedir(), \".synkro\", \"credentials.json\");\nconst RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;\nconst SYNKRO_API_URL = (RAW_API_URL && /^https?:\\/\\//.test(RAW_API_URL))\n ? RAW_API_URL\n : \"https://api.synkro.sh\";\n\n// Types — matches the AuthCredentials shape returned by the dashboard's\n// /api/auth/cli-callback (see packages/app/src/pages/api/auth/cli-callback.ts).\nexport interface AuthCredentials {\n access_token: string;\n refresh_token: string;\n user_id?: string;\n email?: string;\n org_id?: string;\n state?: string;\n}\n\nexport interface UserInfo {\n id: string;\n email: string;\n org_id?: string;\n}\n\n// HTML responses\nconst SUCCESS_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Successful - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n .checkmark {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #10b981;\n margin: 0 auto 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .checkmark svg {\n width: 50px;\n height: 50px;\n stroke: white;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n .close-note {\n margin-top: 1.5rem;\n font-size: 0.875rem;\n color: #9ca3af;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"checkmark\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\"></path>\n </svg>\n </div>\n <h1>Authentication Successful!</h1>\n <p>Your Synkro CLI has been authenticated.</p>\n <p class=\"close-note\">You can close this window and return to your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\nconst ERROR_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Failed - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Authentication Failed</h1>\n <p>Please try again from your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\n/**\n * Open URL in default browser\n */\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n\n switch (os) {\n case \"darwin\":\n bin = \"open\";\n args = [url];\n break;\n case \"win32\":\n // `start` is a cmd built-in, so we host it via cmd /c, but pass the URL\n // as a literal arg through execFile (no shell parsing) to keep shell\n // metacharacters from being interpreted.\n bin = \"cmd\";\n args = [\"/c\", \"start\", \"\", url];\n break;\n default:\n bin = \"xdg-open\";\n args = [url];\n }\n\n // execFile (vs exec) does NOT spawn a shell, so url is treated as a single\n // argv entry regardless of contents. Removes shell-injection risk if url\n // ever ends up containing $/`/;/&/etc.\n execFile(bin, args, (error) => {\n if (error) {\n console.error(\"Failed to open browser automatically.\");\n console.log(`Please open this URL manually: ${url}`);\n }\n });\n}\n\n/**\n * Save authentication credentials to file\n */\nexport function saveCredentials(data: AuthCredentials): void {\n const dir = dirname(AUTH_FILE);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Load saved authentication credentials\n */\nexport function loadCredentials(): AuthCredentials | null {\n if (!existsSync(AUTH_FILE)) {\n return null;\n }\n\n try {\n const content = readFileSync(AUTH_FILE, \"utf8\");\n return JSON.parse(content);\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Create HTTP server to receive OAuth callback.\n *\n * Mirrors packages/desktop/src-tauri/src/auth.rs:\n * - Listens on PORT\n * - Handles OPTIONS preflight (CORS)\n * - Catches /auth?token=...&refresh_token=...&user_id=...&email=...&org_id=...&state=...\n * - Returns success HTML on token receipt\n */\nfunction createCallbackServer(): Promise<AuthCredentials> {\n // Tokens land via POST body (JSON), never query params, so they don't get\n // logged into req.url, browser DevTools URL bars, server access logs, or\n // tooling that snapshots URLs. CORS origin is pinned to the configured\n // dashboard so a random page on a different origin can't post forged\n // credentials at the local listener.\n const CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": SYNKRO_WEB_AUTH_URL,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n };\n\n return new Promise((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n // CORS preflight — only echo the pinned origin if the request actually\n // comes from there; otherwise omit ACAO so the browser blocks the call.\n if (req.method === \"OPTIONS\") {\n const origin = req.headers.origin;\n if (origin === SYNKRO_WEB_AUTH_URL) {\n res.writeHead(204, CORS_HEADERS);\n } else {\n res.writeHead(204, {\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n });\n }\n res.end();\n return;\n }\n\n // Reject requests whose Origin header isn't the dashboard. Browsers\n // send Origin on cross-origin POSTs, so this stops a hostile page from\n // forging credentials at our local listener even if it bypasses CORS.\n const reqOrigin = req.headers.origin;\n if (reqOrigin && reqOrigin !== SYNKRO_WEB_AUTH_URL) {\n res.writeHead(403, { \"Vary\": \"Origin\" });\n res.end();\n return;\n }\n\n if (!req.url) {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n const url = new URL(req.url, `http://localhost:${PORT}`);\n\n if (url.pathname !== \"/auth\") {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n if (req.method !== \"POST\") {\n // Reject GET so a stale/older dashboard build can't deliver tokens\n // via query-string. Forces upgrade. CLI 1.0.4+ requires the v1.6+\n // dashboard build.\n res.writeHead(405, { ...CORS_HEADERS, \"Allow\": \"POST, OPTIONS\", \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n return;\n }\n\n // Cap body at 16 KB — JWT pairs are ~4–8 KB; anything bigger is junk.\n const MAX_BODY = 16 * 1024;\n const chunks: Buffer[] = [];\n let total = 0;\n let aborted = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > MAX_BODY) {\n aborted = true;\n res.writeHead(413, CORS_HEADERS);\n res.end();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (aborted) return;\n let parsed: any;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"invalid_json\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: invalid JSON body\"));\n }, 200);\n return;\n }\n\n const token: string | undefined = parsed?.token;\n if (!token || typeof token !== \"string\") {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing_token\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: missing token\"));\n }, 200);\n return;\n }\n\n const authData: AuthCredentials = {\n access_token: token,\n refresh_token: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : \"\",\n user_id: typeof parsed.user_id === \"string\" ? parsed.user_id : undefined,\n email: typeof parsed.email === \"string\" ? parsed.email : undefined,\n org_id: typeof parsed.org_id === \"string\" ? parsed.org_id : undefined,\n state: typeof parsed.state === \"string\" ? parsed.state : undefined,\n };\n\n res.writeHead(200, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n\n setTimeout(() => {\n server.close();\n resolve(authData);\n }, 200);\n });\n req.on(\"error\", (e) => {\n if (aborted) return;\n aborted = true;\n try { res.writeHead(500, CORS_HEADERS); res.end(); } catch {}\n setTimeout(() => {\n server.close();\n reject(e);\n }, 200);\n });\n });\n\n server.listen(PORT);\n\n server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${PORT} is already in use. Close any other Synkro CLI instance and retry.`,\n ),\n );\n } else {\n reject(error);\n }\n });\n });\n}\n\nexport type AuthStatus =\n | { phase: 'starting' }\n | { phase: 'browser-opened'; url: string }\n | { phase: 'waiting' }\n | { phase: 'success' }\n | { phase: 'error'; message: string };\n\n/**\n * Initiate the OAuth-style authentication flow\n */\nexport async function authenticate(\n onStatus?: (status: AuthStatus) => void,\n): Promise<AuthCredentials | null> {\n const emit = onStatus || (() => {});\n\n try {\n emit({ phase: 'starting' });\n\n // Start local server to receive the callback\n const serverPromise = createCallbackServer();\n\n // Open browser to the CLI auth page\n const authUrl = `${SYNKRO_WEB_AUTH_URL}/cli-auth?port=${PORT}`;\n openBrowser(authUrl);\n\n emit({ phase: 'browser-opened', url: authUrl });\n emit({ phase: 'waiting' });\n\n // Wait for authentication callback\n const data = await serverPromise;\n\n emit({ phase: 'success' });\n saveCredentials(data);\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emit({ phase: 'error', message });\n return null;\n }\n}\n\n/**\n * Check if user is authenticated (credentials exist and token not expired)\n */\nexport function isAuthenticated(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Also check token expiry\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true; // Can't decode, assume valid (refresh will handle it)\n\n // Consider expired if past expiration (no buffer here — ensureValidToken handles refresh buffer)\n return Date.now() < decoded.exp * 1000;\n } catch {\n return true; // Decode failed, let ensureValidToken handle it\n }\n}\n\n/**\n * Get current user ID from JWT token\n */\nexport function getCurrentUserId(): string {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded?.sub) {\n throw new Error(\"Invalid token\");\n }\n\n return decoded.sub;\n}\n\n/**\n * Get user info from JWT\n */\nexport function getUserInfo(): UserInfo {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n // Prefer the explicit user_id/email/org_id stashed during the OAuth callback\n // (the dashboard returns them as query params). Fall back to JWT decode if\n // we somehow received older creds without those fields.\n if (creds.user_id) {\n return {\n id: creds.user_id,\n email: creds.email ?? '',\n org_id: creds.org_id,\n };\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n\n return {\n id: decoded.sub,\n email: decoded.email ?? '',\n org_id: decoded.org_id,\n };\n}\n\n/**\n * Get access token for API calls\n */\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\n/**\n * Check if token is expired (with 5 min buffer)\n */\nexport function isTokenExpired(): boolean {\n const creds = loadCredentials();\n if (!creds) return true;\n\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true;\n\n // Expired if less than 5 minutes remaining\n const expiresAt = decoded.exp * 1000;\n const buffer = 5 * 60 * 1000; // 5 minutes\n return Date.now() > expiresAt - buffer;\n } catch {\n return true;\n }\n}\n\n/**\n * Refresh the access token using refresh_token\n */\nexport async function refreshToken(): Promise<boolean> {\n const creds = loadCredentials();\n if (!creds?.refresh_token) return false;\n\n try {\n const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: creds.refresh_token }),\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (data.access_token) {\n saveCredentials({\n ...creds,\n access_token: data.access_token,\n refresh_token: data.refresh_token || creds.refresh_token,\n });\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n// Mutex: prevent concurrent token refresh races\nlet refreshPromise: Promise<boolean> | null = null;\n\n/**\n * Ensure we have a valid token, refreshing if needed\n */\nexport async function ensureValidToken(): Promise<boolean> {\n if (!isAuthenticated()) return false;\n\n if (isTokenExpired()) {\n if (!refreshPromise) {\n refreshPromise = refreshToken().finally(() => { refreshPromise = null; });\n }\n const refreshed = await refreshPromise;\n if (!refreshed) {\n clearCredentials();\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Clear saved credentials (logout)\n */\nexport function clearCredentials(): void {\n if (existsSync(AUTH_FILE)) {\n unlinkSync(AUTH_FILE);\n }\n}\n\n/**\n * Get secrets for a user's integrations\n * In production, this would fetch from Infisical vault using the access token\n *\n * These are secrets the USER provides for THEIR integrations:\n * - AWS credentials (for their S3 buckets)\n * - HuggingFace token (for their datasets)\n * - Langsmith API key (for their projects)\n */\nexport async function getSecrets(\n userId: string,\n integrationId: string,\n): Promise<Record<string, string>> {\n // TODO: In production, use access token to fetch from Infisical\n // For now, return from environment (dev mode)\n return {\n AWS_ACCESS_KEY_ID: process.env.USER_AWS_KEY || \"\",\n AWS_SECRET_ACCESS_KEY: process.env.USER_AWS_SECRET || \"\",\n AWS_REGION: process.env.USER_AWS_REGION || \"us-east-1\",\n HF_TOKEN: process.env.USER_HF_TOKEN || \"\",\n LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || \"\",\n };\n}\n","/**\n * Synkro CLI Authentication Module\n *\n * Exports authentication functions for use throughout the CLI.\n */\n\nexport {\n authenticate,\n isAuthenticated,\n getCurrentUserId,\n getUserInfo,\n getAccessToken,\n loadCredentials,\n saveCredentials,\n clearCredentials,\n getSecrets,\n isTokenExpired,\n refreshToken,\n ensureValidToken,\n} from './stub.js';\n\nexport type { AuthCredentials, UserInfo, AuthStatus } from './stub.js';\n","// :)\n/**\n * CLI API client for project and violation endpoints.\n *\n * Calls the Synkro API (Hono/Cloudflare Workers) for project management, violations, and policy upsert.\n * SYNKRO_CRUD_URL points to the API server (defaults to http://localhost:8788/api).\n */\n\nimport { getAccessToken, ensureValidToken } from '../auth/index.js';\n\nlet API_URL = 'https://api.synkro.sh/api';\n\nexport function setApiBaseUrl(url: string): void {\n API_URL = url;\n}\n\n// Types\n\nexport interface Project {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n api_key_count: number;\n created_at: string | null;\n repos?: Array<{ id: string; github_repo_id: number | null; full_name: string }>;\n}\n\nexport interface Violation {\n id: string | null;\n run_id: string | null;\n score: number | null;\n severity: string | null;\n issues: string[];\n rules_violated: string[];\n messages: Array<{ role: string; content: string }>;\n model: string | null;\n comment: string | null;\n created_at: string | null;\n}\n\nexport interface ViolationsResponse {\n violations: Violation[];\n total: number;\n project_id: string;\n project_slug: string | null;\n policy_text: string | null;\n rules: Array<Record<string, unknown>>;\n}\n\nexport interface GetViolationsOptions {\n limit?: number;\n offset?: number;\n severity?: string;\n}\n\nexport interface PolicyUpsertResponse {\n ok: boolean;\n policy_id: string;\n}\n\n// API helper\n\nasync function callApi<T>(\n method: string,\n endpoint: string,\n body?: Record<string, unknown>\n): Promise<T> {\n if (!API_URL) {\n throw new Error('API URL not configured. Run `synkro install` first.');\n }\n\n const url = `${API_URL}${endpoint}`;\n await ensureValidToken();\n const accessToken = getAccessToken();\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (accessToken) {\n headers['Authorization'] = `Bearer ${accessToken}`;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ detail: response.statusText }));\n throw new Error(error.detail || `API error: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport interface CreateProjectResponse {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n created_at: string | null;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n key_prefix: string;\n name: string;\n project_id: string;\n created_at: string | null;\n}\n\n// Project API functions\n\n/**\n * Create a new project.\n */\nexport async function createProject(\n name: string,\n repos?: Array<{ github_repo_id?: number; full_name: string; default_branch?: string; private?: boolean }>,\n): Promise<CreateProjectResponse> {\n const body: Record<string, unknown> = { name };\n if (repos && repos.length > 0) body.repos = repos;\n return callApi<CreateProjectResponse>('POST', '/projects', body);\n}\n\n/**\n * Create an API key for a project. Returns plaintext key (shown once).\n */\nexport async function createApiKey(\n projectId: string,\n name?: string\n): Promise<CreateApiKeyResponse> {\n return callApi<CreateApiKeyResponse>('POST', '/api-keys', {\n project_id: projectId,\n name: name || 'CLI Key',\n });\n}\n\n/**\n * List all projects for the authenticated user.\n */\nexport async function listProjects(): Promise<Project[]> {\n return callApi<Project[]>('GET', '/projects');\n}\n\n/**\n * Get violations for a project with optional filtering.\n * Also returns the active policy text and rules.\n */\nexport async function getViolations(\n projectId: string,\n options: GetViolationsOptions = {}\n): Promise<ViolationsResponse> {\n const params = new URLSearchParams();\n if (options.limit) params.set('limit', String(options.limit));\n if (options.offset) params.set('offset', String(options.offset));\n if (options.severity) params.set('severity', options.severity);\n\n const qs = params.toString();\n const endpoint = `/projects/${projectId}/violations${qs ? `?${qs}` : ''}`;\n return callApi<ViolationsResponse>('GET', endpoint);\n}\n\n/**\n * Upsert a policy for a project (deploy rules to gateway).\n */\nexport async function upsertPolicy(\n projectId: string,\n policyText: string,\n rules: Array<Record<string, unknown>>,\n ruleCount: number\n): Promise<PolicyUpsertResponse> {\n return callApi<PolicyUpsertResponse>('POST', `/projects/${projectId}/policies`, {\n policy_text: policyText,\n rules,\n rule_count: ruleCount,\n });\n}\n\n/**\n * Update a project's configuration.\n */\nexport async function updateProject(\n projectId: string,\n updates: {\n name?: string;\n provider?: string;\n langsmith_project?: string;\n is_active?: boolean;\n }\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('PATCH', `/projects/${projectId}`, updates as Record<string, unknown>);\n}\n\n/**\n * Unlink a repo from a project.\n */\nexport async function unlinkRepo(\n projectId: string,\n repoId: string,\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('DELETE', `/projects/${projectId}/repos/${repoId}`);\n}\n\n/**\n * Resolve a project by name or slug (fuzzy match against user's projects).\n * Returns the first matching project or null.\n */\nexport async function resolveProject(nameOrSlug: string): Promise<Project | null> {\n const projects = await listProjects();\n const query = nameOrSlug.toLowerCase();\n\n // Exact slug match\n const exactSlug = projects.find(p => p.slug === nameOrSlug);\n if (exactSlug) return exactSlug;\n\n // Exact name match (case-insensitive)\n const exactName = projects.find(p => p.name.toLowerCase() === query);\n if (exactName) return exactName;\n\n // Partial name match\n const partial = projects.find(p => p.name.toLowerCase().includes(query));\n if (partial) return partial;\n\n // Partial slug match\n const partialSlug = projects.find(p => p.slug.includes(query));\n if (partialSlug) return partialSlug;\n\n return null;\n}\n","// :)\n/**\n * GitHub Actions workflow YAML template for Synkro PR scanning.\n *\n * Customer commits this to .github/workflows/synkro.yml. Triggers on\n * pull_request open/synchronize/reopened. Runs `synkro scan-pr` which\n * spawns claude --print with their CLAUDE_CODE_OAUTH_TOKEN per file.\n *\n * Both CLIs install via npm (npm registry attestations + signed packages\n * are verified by npm itself). No curl-pipe-to-bash; we publish to npm\n * specifically so CI can install us through a trusted package manager.\n */\n\nexport const SYNKRO_WORKFLOW_YAML = `name: Synkro Security Review\non:\n pull_request:\n types: [opened, synchronize, reopened]\n workflow_dispatch:\n inputs:\n pr_number:\n description: PR number to scan\n required: true\n sha:\n description: Commit SHA to scan\n required: true\n\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: write\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n ref: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n\n - name: Cache npm globals\n id: cache-npm-global\n uses: actions/cache@v4\n with:\n path: ~/.npm-global\n key: synkro-cli-\\${{ runner.os }}-v1\n\n - name: Install Synkro CLI + Claude Code CLI\n run: |\n npm config set prefix ~/.npm-global\n npm install -g @synkro-sh/cli @anthropic-ai/claude-code\n echo \"$HOME/.npm-global/bin\" >> $GITHUB_PATH\n\n - name: Run Synkro PR scan\n run: synkro-cli scan-pr\n env:\n CLAUDE_CODE_OAUTH_TOKEN: \\${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}\n SYNKRO_API_KEY: \\${{ secrets.SYNKRO_API_KEY }}\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n SYNKRO_PR_NUMBER: \\${{ inputs.pr_number || github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_SHA: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n SYNKRO_GATEWAY_URL: \\${{ vars.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh' }}\n`;\n\nexport const WORKFLOW_FILENAME = 'synkro.yml';\nexport const WORKFLOW_PATH = '.github/workflows/synkro.yml';\n","/**\n * GitHub repo setup for PR scanning.\n *\n * Uses `gh secret set` to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY\n * as repo secrets, then writes .github/workflows/synkro.yml to the local\n * clone if present.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\nexport interface GitHubAuthOptions {\n token: string;\n}\n\nfunction ghSecretSet(token: string, owner: string, repo: string, name: string, value: string): void {\n execSync(`gh secret set ${name} --repo ${owner}/${repo} --body -`, {\n input: value,\n env: { ...process.env, GH_TOKEN: token },\n stdio: ['pipe', 'ignore', 'pipe'],\n timeout: 30_000,\n });\n}\n\n/**\n * List repos the token has access to.\n */\nexport async function listAccessibleRepos(opts: GitHubAuthOptions): Promise<Array<{ owner: string; repo: string; full_name: string }>> {\n const repos: Array<{ owner: string; repo: string; full_name: string }> = [];\n let page = 1;\n while (page <= 5) { // cap pagination\n const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator`;\n const resp = await fetch(url, {\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n if (!resp.ok) {\n throw new Error(`GitHub API ${resp.status} listing repos`);\n }\n const data = await resp.json() as Array<{ full_name: string; owner: { login: string }; name: string }>;\n if (data.length === 0) break;\n for (const r of data) {\n repos.push({ owner: r.owner.login, repo: r.name, full_name: r.full_name });\n }\n if (data.length < 100) break;\n page++;\n }\n return repos;\n}\n\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken?: string; synkroApiKey: string },\n): Promise<void> {\n try { execSync('gh --version', { stdio: 'ignore', timeout: 5000 }); } catch {\n throw new Error('GitHub CLI (gh) not found. Install it: https://cli.github.com');\n }\n if (secrets.claudeCodeOauthToken) {\n ghSecretSet(opts.token, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken);\n }\n ghSecretSet(opts.token, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey);\n}\n\n/**\n * Write the workflow YAML to a local repo clone (if the user is in one).\n * Returns the absolute path written, or null if cwd isn't a git repo.\n */\nexport function writeWorkflowFile(repoRootPath: string): string | null {\n const workflowDir = join(repoRootPath, '.github', 'workflows');\n mkdirSync(workflowDir, { recursive: true });\n const workflowFile = join(workflowDir, 'synkro.yml');\n writeFileSync(workflowFile, SYNKRO_WORKFLOW_YAML, 'utf-8');\n return workflowFile;\n}\n\n/**\n * Find the git repo root for a given cwd. Returns null if not in a git repo.\n */\nexport function findGitRoot(startCwd: string): string | null {\n let cur = startCwd;\n while (cur && cur !== '/') {\n if (existsSync(join(cur, '.git'))) return cur;\n const parent = join(cur, '..');\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nexport const SECRET_NAMES = {\n CLAUDE_OAUTH: 'CLAUDE_CODE_OAUTH_TOKEN',\n SYNKRO_API_KEY: 'SYNKRO_API_KEY',\n} as const;\n\nexport const WORKFLOW_RELATIVE_PATH = WORKFLOW_PATH;\n","// :)\n/**\n * Shared helpers for repo connection during install and `synkro link`.\n *\n * Two paths:\n * 1. Local git repo — detect from `git remote get-url origin`, any provider\n * 2. GitHub OAuth — browser flow, list repos, interactive picker\n */\nimport { execSync, execFileSync } from 'node:child_process';\nimport { readdirSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { createProject, listProjects } from '../api/projects.js';\nimport { listAccessibleRepos } from '../installer/githubSetup.js';\n\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : 'https://app.synkro.sh';\nconst GITHUB_PORT = 8101;\n\nfunction detectGitRepo(): { fullName: string; shortName: string } | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n // Match any git host — github, gitlab, bitbucket, self-hosted, etc.\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (!match) return null;\n const fullName = match[1];\n return { fullName, shortName: fullName.split('/').pop() || fullName };\n } catch { return null; }\n}\n\nfunction detectSubdirRepos(): Array<{ fullName: string; shortName: string }> {\n try {\n const entries = readdirSync('.', { withFileTypes: true });\n const repos: Array<{ fullName: string; shortName: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n try {\n const remoteUrl = execFileSync('git', ['-C', entry.name, 'remote', 'get-url', 'origin'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (match) {\n const fullName = match[1];\n repos.push({ fullName, shortName: fullName.split('/').pop() || fullName });\n }\n } catch { /* not a git repo or no remote */ }\n }\n return repos;\n } catch { return []; }\n}\n\nfunction ask(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nfunction waitForGithubToken(): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n });\n res.end();\n return;\n }\n\n if (req.url !== '/auth' || req.method !== 'POST') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const parsed = JSON.parse(body);\n if (!parsed.github_token) {\n res.writeHead(400, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ error: 'missing github_token' }));\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ ok: true }));\n setTimeout(() => server.close(), 200);\n resolve(parsed.github_token);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'invalid json' }));\n }\n });\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${GITHUB_PORT} is in use. Close other processes and try again.`));\n } else {\n reject(err);\n }\n });\n\n server.listen(GITHUB_PORT);\n });\n}\n\nfunction openBrowser(url: string): void {\n const { execFile } = require('node:child_process');\n const plat = process.platform;\n const cb = (err: Error | null) => {\n if (err) console.log(` Open this URL manually: ${url}`);\n };\n if (plat === 'darwin') execFile('open', [url], cb);\n else if (plat === 'win32') execFile('cmd', ['/c', 'start', '', url], cb);\n else execFile('xdg-open', [url], cb);\n}\n\nasync function connectGithubAndSelectRepos(): Promise<Array<{ full_name: string }>> {\n const url = `${SYNKRO_WEB_AUTH_URL}/cli-github?port=${GITHUB_PORT}`;\n console.log(' Opening browser for GitHub authorization...');\n openBrowser(url);\n\n console.log(' Waiting for GitHub authorization...');\n const ghToken = await waitForGithubToken();\n console.log(' ✓ GitHub connected\\n');\n\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.log(' No accessible repos found on GitHub.');\n return [];\n }\n\n console.log(` Found ${repos.length} repos:\\n`);\n repos.forEach((r: any, i: number) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const selection = await ask(rl, ' Select repos (comma-separated numbers, e.g. 1,3,5): ');\n const indices = selection\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n\n if (indices.length === 0) {\n console.log(' No repos selected.');\n return [];\n }\n\n return indices.map((i) => ({ full_name: repos[i].full_name }));\n } finally {\n rl.close();\n }\n}\n\nexport async function promptRepoConnection(opts?: { linkRepo?: boolean }): Promise<void> {\n const localRepo = detectGitRepo();\n const subdirRepos = !localRepo ? detectSubdirRepos() : [];\n\n if (opts?.linkRepo && localRepo) {\n console.log('Connect repos to Synkro:\\n');\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n console.log();\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n try {\n console.log('Connect repos to Synkro:\\n');\n\n let existingFullNames = new Set<string>();\n if (subdirRepos.length > 0) {\n try {\n const existing = await listProjects();\n existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n } catch {}\n }\n\n if (subdirRepos.length > 0) {\n console.log(` Found ${subdirRepos.length} repo(s) in this directory:\\n`);\n subdirRepos.forEach((r, i) => {\n const linked = existingFullNames.has(r.fullName);\n console.log(` ${String(i + 1).padStart(3)}. ${r.fullName}${linked ? ' ✓ linked' : ''}`);\n });\n const ghIdx = subdirRepos.length + 1;\n const skipIdx = subdirRepos.length + 2;\n console.log(` ${String(ghIdx).padStart(3)}. Connect GitHub to select repos`);\n console.log(` ${String(skipIdx).padStart(3)}. Skip for now`);\n console.log();\n\n const selection = await ask(rl, ' Select repos to link (comma-separated numbers, e.g. 1,3): ');\n const nums = selection.split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));\n console.log();\n rl.close();\n\n if (nums.includes(ghIdx)) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n const newRepos = selectedRepos.filter((r) => !existingFullNames.has(r.full_name));\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n }\n } else if (nums.includes(skipIdx) || nums.length === 0) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n const repoIndices = nums.map((n) => n - 1).filter((n) => n >= 0 && n < subdirRepos.length);\n const toLink = repoIndices.map((i) => subdirRepos[i]).filter((r) => !existingFullNames.has(r.fullName));\n if (toLink.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n for (const repo of toLink) {\n try {\n await createProject(repo.shortName, [{ full_name: repo.fullName }]);\n console.log(` ✓ Created project \"${repo.shortName}\" linked to ${repo.fullName}`);\n } catch (err) {\n console.warn(` ⚠ Could not link ${repo.fullName}: ${(err as Error).message}`);\n }\n }\n }\n }\n } else {\n const options: string[] = [];\n if (localRepo) {\n options.push(`Link this repo (${localRepo.fullName})`);\n }\n options.push('Connect GitHub to select repos');\n options.push('Skip for now');\n\n options.forEach((opt, i) => {\n console.log(` ${i + 1}. ${opt}`);\n });\n console.log();\n\n const choice = await ask(rl, ' Choose (number): ');\n const choiceNum = parseInt(choice.trim(), 10);\n console.log();\n rl.close();\n\n const localIdx = localRepo ? 1 : -1;\n const githubIdx = localRepo ? 2 : 1;\n const skipIdx = localRepo ? 3 : 2;\n\n if (choiceNum === localIdx && localRepo) {\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n } else if (choiceNum === githubIdx) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n try {\n const existing = await listProjects();\n const linkedNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !linkedNames.has(r.full_name));\n\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repos: ${(err as Error).message}`);\n }\n }\n } else if (choiceNum === skipIdx) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n console.log(' Invalid choice. Skipping repo connection.');\n }\n }\n } catch {\n rl.close();\n }\n console.log();\n}\n","/**\n * Fetch judge prompt metadata from the Synkro API.\n * Prompts are fetched live on every grade call — never cached to disk.\n * This module only fetches the version string for display during install.\n */\n\ninterface PromptsMetaResponse {\n version: string;\n}\n\nexport async function fetchJudgePrompts(opts: {\n gatewayUrl: string;\n jwt: string;\n forceRefresh?: boolean;\n}): Promise<{\n version: string;\n}> {\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/hook/config`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${opts.jwt}`,\n 'User-Agent': 'synkro-cli/1.0',\n },\n signal: AbortSignal.timeout(5000),\n });\n\n if (!resp.ok) {\n return { version: 'unknown' };\n }\n\n const data = await resp.json() as { prompts?: { version?: string } };\n return { version: data.prompts?.version ?? 'unknown' };\n}\n","/**\n * macOS Keychain export for the containerised Synkro install.\n *\n * Claude Code on macOS stores its auth blob in the user's login keychain under\n * the service name `Claude Code-credentials`. A Linux container can't reach\n * the keychain, so on `synkro install` we shell out to the `security` CLI,\n * extract the blob, and write it to a host-side file that the container\n * bind-mounts read-write.\n *\n * Refresh policy: claude rotates its token periodically. We install a launchd\n * agent that re-exports every 45 min so the container picks up new tokens\n * without manual intervention. Stale tokens between rotations fail at most a\n * single grade before the next refresh runs.\n *\n * IMPORTANT (per Synkro Plan Review R003): the exported file lives ONLY on the\n * host under ~/.synkro/claude-creds/ and is bind-mounted at container runtime.\n * It must never be COPY'd into a Dockerfile or otherwise baked into image\n * layers — image layers are part of the publishable artefact and would leak\n * the customer's credentials to anyone who pulls the image.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, statSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nexport const CLAUDE_CREDS_DIR = join(SYNKRO_DIR, 'claude-creds');\nexport const CLAUDE_CREDS_FILE = join(CLAUDE_CREDS_DIR, '.credentials.json');\n\n// Cursor grading uses @cursor/sdk, which authenticates ONLY with a\n// dashboard-issued Cursor API key — the macOS keychain OAuth token is\n// rejected by the SDK. So there is no keychain bridge for Cursor: the user\n// places their API key (cursor.com → Settings → API Keys) in this file and\n// it is bind-mounted into the container.\nexport const CURSOR_CREDS_DIR = join(SYNKRO_DIR, 'cursor-creds');\nexport const CURSOR_API_KEY_FILE = join(CURSOR_CREDS_DIR, 'api-key');\n\n// Keychain service name claude uses on macOS. Don't change without verifying\n// against the installed Claude Code build first — name drift is silent and\n// shows up later as \"all my grades say unavailable.\"\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\n// launchd label for the periodic refresh agent.\nconst LAUNCHD_LABEL = 'com.synkro.cli.claude-creds-refresh';\nconst LAUNCHD_PLIST = join(homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);\nconst REFRESH_INTERVAL_SECONDS = 45 * 60; // 45 minutes — Claude OAuth tokens expire in ~1h\n\nexport class KeychainExportError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'KeychainExportError';\n }\n}\n\n/**\n * True when the platform actually needs the keychain bridge. Linux stores\n * creds in a file already; only darwin uses the keychain.\n */\nexport function needsKeychainBridge(): boolean {\n return platform() === 'darwin';\n}\n\n/**\n * Read the Claude Code credential blob from the macOS keychain. Returns null\n * if the entry doesn't exist (claude not signed in) or if the security CLI\n * fails for any reason. The caller should treat null as \"auth not configured\n * yet\" rather than a hard error — the user may not have launched claude yet.\n */\nexport function readKeychainCreds(): string | null {\n if (platform() !== 'darwin') return null;\n const r = spawnSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) return null;\n const blob = (r.stdout || '').trim();\n return blob || null;\n}\n\n/**\n * One-shot export: pull creds from the keychain and write them to the\n * host-side file the container bind-mounts. Returns the absolute file path,\n * or null if there was nothing to export.\n *\n * The directory and file are chmodded to 700/600 so other users on the host\n * can't read the credentials. The container's bind mount preserves these\n * perms inside the namespace.\n */\nexport function exportKeychainCreds(): string | null {\n const blob = readKeychainCreds();\n if (!blob) return null;\n mkdirSync(CLAUDE_CREDS_DIR, { recursive: true });\n chmodSync(CLAUDE_CREDS_DIR, 0o700);\n writeFileSync(CLAUDE_CREDS_FILE, blob, 'utf-8');\n chmodSync(CLAUDE_CREDS_FILE, 0o600);\n return CLAUDE_CREDS_FILE;\n}\n\n/**\n * True when the user has placed a Cursor API key at CURSOR_API_KEY_FILE.\n * Used by the installer to decide whether the Cursor worker pool can run.\n * We never read the key value here — only confirm the file is present and\n * non-empty so the worker can pick it up via the bind mount.\n */\nexport function cursorApiKeyConfigured(): boolean {\n try {\n return existsSync(CURSOR_API_KEY_FILE)\n && readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Persist a Cursor API key to CURSOR_API_KEY_FILE (0600, dir 0700). The\n * containerised worker bind-mounts the file and reads the key at grade time.\n * Same R003 rule as the Claude creds: host-only file, never baked into an\n * image layer.\n */\nexport function writeCursorApiKey(key: string): void {\n const trimmed = key.trim();\n if (!trimmed) return;\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n chmodSync(CURSOR_CREDS_DIR, 0o700);\n writeFileSync(CURSOR_API_KEY_FILE, trimmed, 'utf-8');\n chmodSync(CURSOR_API_KEY_FILE, 0o600);\n}\n\n/**\n * Check whether the stored Cursor API key is actually accepted by Cursor.\n * Hits the Cursor API's `GET /v1/me` (Basic auth — the key is the username).\n *\n * Returns true (accepted), false (definitively rejected — 401/403), or null\n * (could not determine: no key, network error, or an ambiguous status). A null\n * result must NOT be treated as invalid — a transient network blip should\n * never nag the user to re-enter a working key.\n */\nexport async function validateCursorApiKey(): Promise<boolean | null> {\n let key: string;\n try {\n key = readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n if (!key) return null;\n try {\n const auth = Buffer.from(`${key}:`).toString('base64');\n const r = await fetch('https://api.cursor.com/v1/me', {\n headers: { Authorization: `Basic ${auth}` },\n signal: AbortSignal.timeout(8_000),\n });\n if (r.status === 401 || r.status === 403) return false;\n if (r.ok) return true;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether the exported creds file is older than its refresh interval. Used by\n * `synkro local-cc status` to decide if we should re-export proactively even\n * though launchd should be handling it.\n */\nexport function credsAreStale(): boolean {\n if (!existsSync(CLAUDE_CREDS_FILE)) return true;\n try {\n const ageMs = Date.now() - statSync(CLAUDE_CREDS_FILE).mtimeMs;\n return ageMs > REFRESH_INTERVAL_SECONDS * 1000;\n } catch {\n return true;\n }\n}\n\n/**\n * Install a launchd agent that runs `synkro local-cc refresh-creds` every\n * REFRESH_INTERVAL_SECONDS. Idempotent — overwrites the existing plist if\n * present. Caller should also `launchctl bootout` + `launchctl bootstrap` to\n * apply changes; we keep that in install.ts so this module stays side-effect\n * minimal.\n *\n * Returns the absolute path to the written plist for the caller to load.\n */\nexport function writeRefreshAgent(synkroBinPath: string): string {\n if (platform() !== 'darwin') {\n throw new KeychainExportError('writeRefreshAgent is darwin-only');\n }\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n\n // Use a shell wrapper so PATH resolution works even if the binary moves\n // (e.g. /usr/local/bin vs /opt/homebrew/bin). The plist runs /bin/bash -c\n // which inherits the user's PATH from the login environment.\n const shellCmd = `export PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\" && { \"${synkroBinPath}\" local-cc refresh-creds || synkro local-cc refresh-creds; }`;\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/bash</string>\n <string>-c</string>\n <string>${shellCmd}</string>\n </array>\n <key>StartInterval</key>\n <integer>${REFRESH_INTERVAL_SECONDS}</integer>\n <key>RunAtLoad</key>\n <true/>\n <key>StandardErrorPath</key>\n <string>${join(SYNKRO_DIR, 'claude-creds-refresh.log')}</string>\n <key>StandardOutPath</key>\n <string>${join(SYNKRO_DIR, 'claude-creds-refresh.log')}</string>\n</dict>\n</plist>\n`;\n writeFileSync(LAUNCHD_PLIST, plist, 'utf-8');\n return LAUNCHD_PLIST;\n}\n\n/**\n * Load (or reload) the launchd refresh agent. Safe to call multiple times.\n */\nexport function loadRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n // bootout returns non-zero if it isn't loaded; that's fine, we just want to\n // ensure a clean state before bootstrap.\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const r = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) {\n throw new KeychainExportError(\n `launchctl bootstrap failed: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n}\n\n/**\n * Stop the launchd refresh agent and remove the plist. Used by `synkro\n * disconnect` / `synkro uninstall` so we don't leave orphan agents behind.\n */\nexport function uninstallRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n try {\n if (existsSync(LAUNCHD_PLIST)) {\n require('node:fs').unlinkSync(LAUNCHD_PLIST);\n }\n } catch { /* best-effort */ }\n}\n\n/**\n * Convenience wrapper for `synkro local-cc refresh-creds`. Re-exports the\n * Claude keychain creds. Cursor needs no refresh agent — its API key is a\n * static dashboard key the user supplies once; it doesn't rotate like the\n * Claude OAuth token.\n */\nexport function refreshCreds(): boolean {\n const path = exportKeychainCreds();\n return path !== null;\n}\n\n/**\n * Read the current exported creds (if any). Used by status commands.\n */\nexport function readExportedCreds(): string | null {\n try {\n return readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n } catch {\n return null;\n }\n}\n","/**\n * Containerised Synkro install path.\n *\n * Replaces the host-side pueue + tmux + cc_sessions stack with a single\n * Docker container that bundles MCP server, pglite-db, grader dispatcher,\n * and N tmux-managed claude grader workers.\n *\n * Activated when SYNKRO_DEPLOYMENT_MODE=docker is set. The bare-host path\n * remains the default until two stable release cycles have validated this\n * path under real load (see Phase 5 in the architecture plan).\n *\n * Host-side files this module touches:\n * ~/.synkro/ (bind-mounted RO as /data/synkro-host;\n * the container reads .mcp-jwt and\n * credentials.json from inside it)\n * ~/.synkro/pgdata/ (bind-mounted RW, persistent telemetry)\n * ~/.synkro/claude-creds/ (bind-mounted RW on macOS; symlinked\n * from ~/.claude on Linux)\n *\n * Never bakes credentials into the image — every secret is a runtime bind\n * mount of a host file. See macKeychain.ts for the macOS bridge.\n */\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport {\n CLAUDE_CREDS_DIR,\n CURSOR_CREDS_DIR,\n exportKeychainCreds,\n cursorApiKeyConfigured,\n needsKeychainBridge,\n writeRefreshAgent,\n loadRefreshAgent,\n} from './macKeychain.js';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nconst MCP_JWT_PATH = join(SYNKRO_DIR, '.mcp-jwt');\nconst PGDATA_PATH = join(SYNKRO_DIR, 'pgdata');\n// Docker Desktop on macOS can't reliably bind-mount individual files (the mount\n// shows up as -????? inside the container). Stage the host's ~/.claude.json\n// inside a sibling directory and bind-mount the directory instead.\nconst CLAUDE_HOST_STATE_DIR = join(SYNKRO_DIR, 'claude-host-state');\nconst CLAUDE_HOST_STATE_FILE = join(CLAUDE_HOST_STATE_DIR, '.claude.json');\n\n// Host-side ports the installer maps from the container's internal ports.\n// Bumped by 10000 to avoid colliding with the bare-host install's defaults\n// when both are sitting on the same machine during cutover (Phase 5).\nconst HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || '18931', 10);\nconst HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10);\nconst HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || '18930', 10);\n// PGLite TCP socket published from the container's internal 5433 so host tools\n// (psql/DBeaver/TablePlus) can inspect the telemetry DB. The container's\n// pglite-bootstrap multiplexer enforces access; the 127.0.0.1 bind is the\n// network-layer allowlist.\nconst HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || '15433', 10);\n\nconst CONTAINER_NAME = 'synkro-server';\n// Canonical GHCR coordinates published by .github/workflows/docker-publish.yml.\n// Override at install time via SYNKRO_IMAGE_TAG (or mirror to a private\n// registry and set SYNKRO_IMAGE_REGISTRY for the hedge-fund deployment).\nconst DEFAULT_IMAGE = 'ghcr.io/synkro-sh/synkro-server:latest';\n\nexport class DockerInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'DockerInstallError';\n }\n}\n\nexport type WorkerProvider = 'claude_code' | 'cursor';\n\n/**\n * Split a total worker count across the selected providers.\n * - both providers → even split (an odd worker goes to Claude)\n * - single provider → the whole pool\n * - no providers → treated as Claude-only (legacy default)\n * Used by `synkro install` and `synkro local-cc start|restart` so the split\n * rule lives in exactly one place.\n */\nexport function splitWorkers(total: number, providers: WorkerProvider[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n} {\n const t = Math.max(0, Math.floor(total));\n const hasClaude = providers.includes('claude_code');\n const hasCursor = providers.includes('cursor');\n if (hasClaude && hasCursor) {\n const cursorWorkers = Math.floor(t / 2);\n return { claudeWorkers: t - cursorWorkers, cursorWorkers };\n }\n if (hasCursor) return { claudeWorkers: 0, cursorWorkers: t };\n return { claudeWorkers: t, cursorWorkers: 0 };\n}\n\n/** Normalize a provider token. Accepts claude / claude-code / cc / cursor. */\nfunction normalizeProvider(p: string): WorkerProvider | null {\n const v = p.trim().toLowerCase();\n if (v === 'claude' || v === 'claude-code' || v === 'claude_code' || v === 'cc') return 'claude_code';\n if (v === 'cursor') return 'cursor';\n return null;\n}\n\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n if (/^\\S/.test(line) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null; currentArr = null;\n const ci = line.indexOf(':');\n const key = line.slice(0, ci).trim();\n const val = line.slice(ci + 1).trim();\n currentKey = key;\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (/^ - /.test(line)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (/^ \\S/.test(line) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const ci = line.indexOf(':');\n const k = line.slice(0, ci).trim();\n const v = line.slice(ci + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nfunction readSynkroFileConfig(): { pool: 'auto' | 'claude' | 'cursor'; claudeWorkers?: number; cursorWorkers?: number } {\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return { pool: 'auto' };\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return { pool: 'auto' };\n const raw = readFileSync(fp, 'utf-8');\n const parsed = raw.trimStart().startsWith('{') ? JSON.parse(raw) : parseSynkroYaml(raw);\n const pool = ['auto', 'claude', 'cursor'].includes(parsed?.grader?.pool) ? parsed.grader.pool : 'auto';\n const cw = typeof parsed?.workers?.claude === 'number' ? Math.max(0, Math.floor(parsed.workers.claude)) : undefined;\n const curw = typeof parsed?.workers?.cursor === 'number' ? Math.max(0, Math.floor(parsed.workers.cursor)) : undefined;\n return { pool, claudeWorkers: cw, cursorWorkers: curw };\n } catch {}\n return { pool: 'auto' };\n}\n\n/**\n * Parse `--workers N|--workers=N` and `--provider(s) a,b` flags from a\n * start/restart invocation and compute the per-kind worker split.\n * - `explicit` is true when any worker/provider flag was supplied — callers\n * use it to decide between a config-changing recreate vs a plain start.\n * - Providers default to the coding agents detected on the machine.\n * - Total defaults to 8, capped at 64.\n */\nexport function resolveWorkerConfig(rest: string[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n explicit: boolean;\n} {\n let workers = 8;\n let explicit = false;\n const providers: WorkerProvider[] = [];\n const addProviders = (csv: string) => {\n for (const p of csv.split(',')) {\n const np = normalizeProvider(p);\n if (np && !providers.includes(np)) providers.push(np);\n }\n };\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n if (a === '--workers' || a === '-w') { workers = parseInt(rest[++i] || '8', 10); explicit = true; }\n else if (a.startsWith('--workers=')) { workers = parseInt(a.slice('--workers='.length), 10); explicit = true; }\n else if (a === '--provider' || a === '--providers') { addProviders(rest[++i] || ''); explicit = true; }\n else if (a.startsWith('--provider=')) { addProviders(a.slice('--provider='.length)); explicit = true; }\n else if (a.startsWith('--providers=')) { addProviders(a.slice('--providers='.length)); explicit = true; }\n }\n if (!Number.isFinite(workers) || workers < 1) workers = 8;\n workers = Math.min(workers, 64);\n let provs = providers;\n if (provs.length === 0) {\n const sc = readSynkroFileConfig();\n if (sc.claudeWorkers != null || sc.cursorWorkers != null) {\n const cw = sc.claudeWorkers || 0;\n const curw = sc.cursorWorkers || 0;\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };\n }\n if (sc.pool === 'cursor') {\n provs = ['cursor'];\n } else if (sc.pool === 'claude') {\n provs = ['claude_code'];\n } else {\n provs = detectAgents().map(a => a.kind) as WorkerProvider[];\n if (provs.length === 0) provs = ['claude_code'];\n }\n }\n return { ...splitWorkers(workers, provs), explicit };\n}\n\n/** Resolve which image tag this install should pull. */\nexport function imageTag(): string {\n const registry = process.env.SYNKRO_IMAGE_REGISTRY || ''; // e.g. registry.hedgefund.internal\n const tag = process.env.SYNKRO_IMAGE_TAG || DEFAULT_IMAGE;\n return registry ? `${registry.replace(/\\/+$/, '')}/${tag.replace(/^.*\\//, '')}` : tag;\n}\n\n/** True iff docker is on PATH and the daemon is reachable. */\nexport function assertDockerAvailable(): void {\n const v = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (v.status !== 0) {\n throw new DockerInstallError(\n 'Docker CLI is not installed or the daemon is not reachable.\\n' +\n 'Install Docker Desktop (macOS) or docker-engine (Linux), start the daemon, then re-run.',\n );\n }\n}\n\n/**\n * Compute the bind-mount source for the claude credentials dir. macOS uses\n * the exported keychain shadow; Linux uses ~/.claude directly.\n */\nfunction claudeCredsHostDir(): string {\n if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;\n return join(homedir(), '.claude');\n}\n\n/**\n * Idempotent docker pull + run. If a container with the same name is already\n * up, this stops it first so the new image starts cleanly (matches\n * `synkro update` semantics).\n *\n * Returns the resolved image tag for logging.\n */\n/**\n * Resolve the absolute path to the `synkro` binary for the launchd refresh\n * agent. The location varies by install method (Homebrew's /opt/homebrew/bin,\n * /usr/local/bin, an npm prefix) — never hardcode it, or the refresh job\n * silently fails and the container's Claude creds expire.\n */\nfunction resolveSynkroBin(): string {\n const which = spawnSync('which', ['synkro'], { encoding: 'utf-8', timeout: 5_000 });\n const resolved = (which.stdout || '').split('\\n')[0].trim();\n return resolved || 'synkro';\n}\n\n// Host-side PGLite orphans from pre-Docker installs (or earlier crashed\n// container starts that re-spawned the host bin) squat on the published\n// PGLite port, hold the only client slot under the old maxConnections=1\n// default, and write into a separate pgdata from the container's mount.\n// The migration commit that moved PGLite into the container deleted the\n// source file but never killed running processes — they daemonize to\n// launchd (PPID=1) and outlive the install that spawned them. Sweep them\n// before launching the new container so the publish maps to the right\n// PGLite. Container bun processes are isolated inside their PID namespace\n// and never appear in the host's ps output, so this is safe.\nfunction sweepHostPglite(): void {\n const ps = spawnSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5_000 });\n if (ps.status !== 0) return;\n const targets: number[] = [];\n for (const line of (ps.stdout || '').split('\\n')) {\n if (!/bun\\b/.test(line)) continue;\n if (!/pglite-(db|bootstrap)\\.ts\\b/.test(line)) continue;\n const m = line.trim().match(/^(\\d+)\\s/);\n if (!m) continue;\n const pid = parseInt(m[1], 10);\n if (pid > 0 && pid !== process.pid) targets.push(pid);\n }\n if (targets.length === 0) return;\n console.log(` Sweeping ${targets.length} stale host PGLite process(es): ${targets.join(', ')}`);\n for (const pid of targets) {\n try { process.kill(pid, 'SIGTERM'); } catch {}\n }\n const deadline = Date.now() + 3000;\n while (Date.now() < deadline) {\n const alive = targets.filter(pid => {\n try { process.kill(pid, 0); return true; } catch { return false; }\n });\n if (alive.length === 0) return;\n spawnSync('sleep', ['0.2'], { timeout: 1_000 });\n }\n for (const pid of targets) {\n try { process.kill(pid, 'SIGKILL'); } catch {}\n }\n}\n\nexport async function dockerInstall(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n hostPglitePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n // Per-kind worker counts. claudeWorkers falls back to the legacy\n // workersPerPool; cursorWorkers defaults to 0 (Claude-only install).\n const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;\n const cursorWorkers = opts.cursorWorkers ?? 0;\n const totalWorkers = claudeWorkers + cursorWorkers;\n\n // Ensure persistent host dirs exist before docker tries to bind-mount them.\n // Docker creates missing bind sources as root-owned dirs which then break\n // permissions inside the container — pre-create them as the host user.\n mkdirSync(PGDATA_PATH, { recursive: true });\n mkdirSync(BACKUP_DIR, { recursive: true });\n\n // Stage the host's ~/.claude.json into a dir we can bind-mount cleanly on\n // macOS. Without this the container's workers see -????? at the mount path\n // and Claude Code prompts for browser OAuth on every launch.\n mkdirSync(CLAUDE_HOST_STATE_DIR, { recursive: true });\n const hostClaudeJson = join(homedir(), '.claude.json');\n if (existsSync(hostClaudeJson)) {\n copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);\n }\n if (!existsSync(MCP_JWT_PATH)) {\n throw new DockerInstallError(\n `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`,\n );\n }\n\n // Pre-create the Cursor creds dir so the bind mount target is owned by the\n // host user (Docker would otherwise create it root-owned).\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n\n // On macOS, export keychain creds + install the launchd refresh agent\n // before starting the container so the workers have something to auth with.\n if (needsKeychainBridge()) {\n // Claude creds are only required when the install includes Claude workers.\n if (claudeWorkers > 0) {\n const path = exportKeychainCreds();\n if (!path) {\n throw new DockerInstallError(\n 'Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container.',\n );\n }\n }\n // Cursor uses a dashboard API key (the @cursor/sdk rejects the keychain\n // OAuth token). Warn — don't hard-fail — so a \"both agents\" install still\n // proceeds; the Cursor pool stays idle until the key file is present.\n if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {\n console.warn(' ⚠ No Cursor API key found — Cursor grader workers will be idle.');\n console.warn(' Generate a key at cursor.com → Settings → API Keys, then:');\n console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);\n }\n // launchd refresh agent — Claude only (Cursor's API key is static).\n const plist = writeRefreshAgent(resolveSynkroBin());\n try { loadRefreshAgent(); } catch (err) {\n console.warn(` ⚠ launchd refresh agent not loaded: ${(err as Error).message}`);\n console.warn(` Plist written to ${plist} — load manually with launchctl bootstrap when ready.`);\n }\n } else {\n // Linux — make sure ~/.claude exists so the bind mount target is real.\n mkdirSync(join(homedir(), '.claude'), { recursive: true });\n }\n\n // Pull (or update) the image.\n console.log(` Pulling ${image}...`);\n const pull = spawnSync('docker', ['pull', image], { encoding: 'utf-8', stdio: 'inherit', timeout: 600_000 });\n if (pull.status !== 0) {\n throw new DockerInstallError(`docker pull ${image} failed`);\n }\n\n // Gracefully stop existing container if running (snapshot + checkpoint).\n const existing = dockerStatus();\n if (existing.running) {\n console.log(' Stopping existing container gracefully...');\n await dockerSafeStop();\n }\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Kill any leftover host-side PGLite from pre-Docker installs so the new\n // container's published port 5433 isn't shadowed by a stale daemon.\n sweepHostPglite();\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\n //\n // SECURITY: every -p below MUST keep the `127.0.0.1:` host-bind prefix. The\n // container's /api/local/* REST endpoints (snapshot, telemetry, hook-config)\n // are unauthenticated — the localhost-only bind IS the access boundary.\n // Never drop the prefix or switch to 0.0.0.0 / --network host: that would\n // expose an unauthenticated control surface to the LAN.\n const credsDir = claudeCredsHostDir();\n const args = [\n 'run',\n '--detach',\n '--name', CONTAINER_NAME,\n '--restart', 'unless-stopped',\n '-p', `127.0.0.1:${HOST_MCP_PORT}:8931`,\n '-p', `127.0.0.1:${HOST_GRADER_PORT}:8929`,\n '-p', `127.0.0.1:${HOST_CWE_PORT}:8930`,\n '-p', `127.0.0.1:${HOST_PGLITE_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${BACKUP_DIR}:/data/backups`,\n // The whole host ~/.synkro directory, read-only. The container copies\n // .mcp-jwt and credentials.json out of it at boot. A directory mount\n // sidesteps Docker Desktop for macOS's unreliable single-file bind mounts\n // (which previously left a dangling symlink that blocked container start).\n '-v', `${SYNKRO_DIR}:/data/synkro-host:ro`,\n '-v', `${credsDir}:/home/synkro/.claude:rw`,\n '-v', `${join(homedir(), '.claude')}:/data/claude-host:ro`,\n '-v', `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,\n // Cursor creds — mounted RW so the in-container refresher can rotate the\n // access token in place. Only mounted when the install includes Cursor.\n ...(cursorWorkers > 0\n ? ['-v', `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`]\n : []),\n '-e', `WORKERS_PER_POOL=${totalWorkers}`,\n '-e', `CLAUDE_WORKERS=${claudeWorkers}`,\n '-e', `CURSOR_WORKERS=${cursorWorkers}`,\n // Pass through the batch-size lever if the operator set it. Defaults\n // inside the container to 5; clamped to [1, 20] by synkro-server.ts.\n ...(process.env.SYNKRO_MAX_BATCH_SIZE\n ? ['-e', `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`]\n : []),\n // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.\n ...(process.env.SYNKRO_CURSOR_MODEL\n ? ['-e', `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`]\n : []),\n // Connected repo — the server seeds the local app + ruleset named after it.\n ...(opts.connectedRepo\n ? ['-e', `SYNKRO_CONNECTED_REPO=${opts.connectedRepo}`]\n : []),\n // Real account identity (from config.env via process.env) — telemetry is\n // stamped with these instead of a `local-user` placeholder, so the data is\n // correctly attributed from the first graded command.\n ...(process.env.SYNKRO_ORG_ID\n ? ['-e', `SYNKRO_ORG_ID=${process.env.SYNKRO_ORG_ID}`]\n : []),\n ...(process.env.SYNKRO_USER_ID\n ? ['-e', `SYNKRO_USER_ID=${process.env.SYNKRO_USER_ID}`]\n : []),\n ...(process.env.SYNKRO_EMAIL\n ? ['-e', `SYNKRO_EMAIL=${process.env.SYNKRO_EMAIL}`]\n : []),\n image,\n ];\n const run = spawnSync('docker', args, { encoding: 'utf-8', stdio: 'inherit', timeout: 60_000 });\n if (run.status !== 0) {\n throw new DockerInstallError(`docker run failed (image ${image})`);\n }\n\n return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };\n}\n\n/**\n * Poll the container's MCP /healthz until it returns 200, or fail after the\n * timeout. Used by install to give a single clean ready signal instead of the\n * multi-port wait the bare-host install has to do.\n */\nexport async function waitForContainerReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_MCP_PORT}/health`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) return true;\n } catch { /* keep retrying */ }\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\nexport async function waitForWorkersReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_GRADER_PORT}/healthz`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) {\n const data = await r.json() as { workers?: number; healthy?: number };\n if ((data.healthy || 0) > 0) return true;\n }\n } catch {}\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\n/**\n * Remove the container (after it's already stopped). Does NOT stop gracefully —\n * callers must use dockerSafeStop() first if the container is running.\n */\nexport function dockerRemove(): void {\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/** @deprecated Use dockerSafeStop() + dockerRemove() instead. */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], { encoding: 'utf-8', timeout: 45_000 });\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/**\n * `synkro update` for the containerised install — pull + restart, preserve\n * all bind-mounted state.\n */\nexport async function dockerUpdate(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<void> {\n if (dockerStatus().running) {\n await dockerSafeStop();\n }\n dockerRemove();\n await dockerInstall(opts);\n}\n\n/** Show host-mapped ports + container status. */\nexport function dockerStatus(): { running: boolean; image?: string; healthz?: string } {\n const r = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const status = (r.stdout || '').trim();\n if (status !== 'running') return { running: false };\n return {\n running: true,\n image: imageTag(),\n healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`,\n };\n}\n\n/**\n * Read the worker-pool config baked into the installed container's env, so\n * `synkro update` can rebuild on a fresh image without resetting the pool.\n * Works on stopped containers too. Returns null when no container exists.\n */\nexport function readContainerConfig(): {\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} | null {\n const r = spawnSync('docker', ['inspect', '--format', '{{json .Config.Env}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0 || !r.stdout) return null;\n let env: unknown;\n try { env = JSON.parse(r.stdout.trim()); } catch { return null; }\n if (!Array.isArray(env)) return null;\n const get = (k: string): string | undefined => {\n const hit = env.find((e): e is string => typeof e === 'string' && e.startsWith(k + '='));\n return hit ? hit.slice(k.length + 1) : undefined;\n };\n const num = (s: string | undefined): number | undefined => {\n if (s === undefined) return undefined;\n const n = parseInt(s, 10);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n claudeWorkers: num(get('CLAUDE_WORKERS')),\n cursorWorkers: num(get('CURSOR_WORKERS')),\n connectedRepo: get('SYNKRO_CONNECTED_REPO') || undefined,\n };\n}\n\n// ─── Safe lifecycle commands ────────────────────────────────────────────────\n\nconst BACKUP_DIR = join(SYNKRO_DIR, 'pgdata-backups');\n\n/**\n * Graceful stop: snapshot → docker stop (30s for CHECKPOINT+flush) → verify.\n * Never force-kills. Returns details about what happened.\n */\nexport async function dockerSafeStop(): Promise<{\n ok: boolean;\n snapshot?: { ok: boolean; error?: string };\n exitCode?: number;\n pgdataCheck?: { healthy: boolean; details: string };\n}> {\n const status = dockerStatus();\n if (!status.running) {\n console.log(' Container is not running.');\n return { ok: true, pgdataCheck: checkPgdata() };\n }\n\n // 1. Request a pre-shutdown snapshot via the REST endpoint\n console.log(' Requesting data snapshot before shutdown...');\n let snapshot: { ok: boolean; error?: string } = { ok: false, error: 'not attempted' };\n try {\n const resp = await fetch(`http://127.0.0.1:${HOST_MCP_PORT}/api/local/snapshot`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reason: 'cli-stop' }),\n signal: AbortSignal.timeout(20_000),\n });\n if (resp.ok) {\n snapshot = { ok: true };\n console.log(' ✓ Snapshot saved.');\n } else {\n snapshot = { ok: false, error: `HTTP ${resp.status}` };\n console.warn(` ⚠ Snapshot request failed (HTTP ${resp.status}). Proceeding with stop.`);\n }\n } catch (e) {\n snapshot = { ok: false, error: String(e).slice(0, 100) };\n console.warn(` ⚠ Snapshot request failed: ${snapshot.error}. Proceeding with stop.`);\n }\n\n // 2. docker stop with 30s grace period (entrypoint trap does CHECKPOINT + final snapshot)\n console.log(' Stopping container (30s grace for CHECKPOINT + WAL flush)...');\n const stop = spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 45_000,\n });\n\n // 3. Verify clean exit\n const inspect = spawnSync('docker', ['inspect', '--format', '{{.State.ExitCode}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const exitCode = parseInt((inspect.stdout || '').trim(), 10);\n if (exitCode === 0) {\n console.log(' ✓ Container stopped cleanly (exit 0).');\n } else {\n console.warn(` ⚠ Container exited with code ${exitCode}.`);\n }\n\n // 4. Check pgdata health on host\n const pgCheck = checkPgdata();\n if (pgCheck.healthy) {\n console.log(` ✓ pgdata looks healthy: ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata check: ${pgCheck.details}`);\n }\n\n return { ok: stop.status === 0, snapshot, exitCode, pgdataCheck: pgCheck };\n}\n\n/**\n * Start the container (must already exist from a prior install).\n * Checks pgdata integrity, starts container, waits for healthcheck.\n */\nexport async function dockerSafeStart(): Promise<{\n ok: boolean;\n pgdataState: string;\n error?: string;\n}> {\n // Check if already running\n const status = dockerStatus();\n if (status.running) {\n console.log(' Container is already running.');\n return { ok: true, pgdataState: 'running' };\n }\n\n // Check if container exists (stopped)\n const exists = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (exists.status !== 0) {\n return { ok: false, pgdataState: 'no_container', error: 'No synkro-server container found. Run `synkro install` first.' };\n }\n\n // Check pgdata state\n const pgCheck = checkPgdata();\n if (existsSync(PGDATA_PATH) && readdirSync(PGDATA_PATH).length > 0) {\n if (pgCheck.healthy) {\n console.log(` pgdata: existing data found — ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata: ${pgCheck.details}`);\n console.log(' Starting anyway — entrypoint will attempt recovery from snapshots if needed.');\n }\n } else {\n console.log(' pgdata: no existing data — fresh start.');\n mkdirSync(PGDATA_PATH, { recursive: true });\n }\n\n // Start the container\n console.log(' Starting container...');\n const start = spawnSync('docker', ['start', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 30_000,\n });\n if (start.status !== 0) {\n return { ok: false, pgdataState: 'start_failed', error: `docker start failed: ${(start.stderr || '').slice(0, 200)}` };\n }\n\n // Wait for healthcheck\n console.log(' Waiting for server to become healthy...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ Server is healthy and ready.');\n return { ok: true, pgdataState: pgCheck.healthy ? 'existing' : 'recovered' };\n } else {\n return { ok: false, pgdataState: 'unhealthy', error: 'Server did not become healthy within 60s. Check: docker logs synkro-server' };\n }\n}\n\n/**\n * Restart: full safe stop, then safe start. Sequential — never overlapping.\n */\nexport async function dockerSafeRestart(): Promise<{ ok: boolean; stop: Awaited<ReturnType<typeof dockerSafeStop>>; start: Awaited<ReturnType<typeof dockerSafeStart>> }> {\n console.log(' === Stop ===');\n const stopResult = await dockerSafeStop();\n if (!stopResult.ok) {\n console.error(' Stop failed. Aborting restart.');\n return { ok: false, stop: stopResult, start: { ok: false, pgdataState: 'not_started', error: 'stop failed' } };\n }\n console.log('\\n === Start ===');\n const startResult = await dockerSafeStart();\n return { ok: startResult.ok, stop: stopResult, start: startResult };\n}\n\nfunction checkPgdata(): { healthy: boolean; details: string } {\n if (!existsSync(PGDATA_PATH)) return { healthy: false, details: 'pgdata directory does not exist' };\n const entries = readdirSync(PGDATA_PATH);\n if (entries.length === 0) return { healthy: true, details: 'empty (fresh start)' };\n const hasPidFile = entries.includes('postmaster.pid');\n const hasWalDir = entries.includes('pg_wal');\n const hasPgControl = entries.includes('global') || entries.includes('pg_control');\n if (hasPidFile) return { healthy: false, details: 'stale postmaster.pid present (unclean shutdown)' };\n if (!hasWalDir) return { healthy: false, details: 'pg_wal directory missing' };\n if (!hasPgControl) return { healthy: false, details: 'pg_control/global directory missing' };\n return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };\n}\n","/**\n * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Ensure user is logged in (has JWT + user_id + org_id in ~/.synkro/credentials.json).\n * 2. Check if GitHub is connected via WorkOS Pipes.\n * 3. If not, get Pipes OAuth URL and open browser for the user to authorize.\n * 4. Poll until GitHub connection is established.\n * 5. Run `claude setup-token` to get Claude Code OAuth token.\n * 6. List accessible repos via GitHub API (using Pipes token).\n * 7. Interactive multi-select.\n * 8. Push secrets (SYNKRO_API_KEY + CLAUDE_CODE_OAUTH_TOKEN) to each repo.\n * 9. Write .github/workflows/synkro.yml to the local repo if cwd is one.\n */\nimport { createInterface } from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport { execSync, spawn as nodeSpawn } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken, getUserInfo } from '../auth/stub.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfig(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nasync function prompt(rl: ReturnType<typeof createInterface>, q: string, opts: { silent?: boolean } = {}): Promise<string> {\n if (opts.silent) {\n process.stdout.write(q);\n const wasRaw = (process.stdin as any).isRaw;\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(true);\n return await new Promise<string>((resolve) => {\n let chunk = '';\n const onData = (data: Buffer) => {\n const s = data.toString('utf-8');\n if (s === '\\r' || s === '\\n' || s === '\\r\\n') {\n process.stdin.removeListener('data', onData);\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(wasRaw ?? false);\n process.stdout.write('\\n');\n resolve(chunk);\n return;\n }\n if (s === '\u0003') process.exit(130);\n if (s === '' || s === '\\b') { chunk = chunk.slice(0, -1); return; }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n switch (os) {\n case 'darwin': bin = 'open'; args = [url]; break;\n case 'win32': bin = 'cmd'; args = ['/c', 'start', '', url]; break;\n default: bin = 'xdg-open'; args = [url]; break;\n }\n execFile(bin, args, () => {});\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nfunction captureClaudeSetupToken(): Promise<string> {\n const tmpFile = join(SYNKRO_DIR, `token-capture-${Date.now()}.raw`);\n return new Promise((resolve, reject) => {\n const proc = nodeSpawn('script', ['-q', tmpFile, 'claude', 'setup-token'], {\n stdio: 'inherit',\n });\n proc.on('error', (err) => reject(new Error(`Failed to spawn claude setup-token: ${err.message}`)));\n proc.on('close', (code) => {\n let raw = '';\n try { raw = readFileSync(tmpFile, 'utf-8'); } catch (e) {\n reject(new Error(`Could not read script output file: ${(e as Error).message}`));\n return;\n }\n try { unlinkSync(tmpFile); } catch {}\n if (code !== 0) { reject(new Error(`claude setup-token exited with code ${code}`)); return; }\n // Grab yellow-colored text segments (token is rendered in RGB 255,193,7)\n const yellowRe = /\\x1B\\[38;2;255;193;7m([^\\x1B]*)/g;\n let yellow = '';\n let m: RegExpExecArray | null;\n while ((m = yellowRe.exec(raw)) !== null) yellow += m[1];\n const token = yellow.replace(/\\s/g, '').match(/sk-ant-oat01-[A-Za-z0-9_-]+/);\n if (!token) { reject(new Error(`Could not find token in claude setup-token output (file=${raw.length}b, yellow=${yellow.length}b)`)); return; }\n resolve(token[0]);\n });\n });\n}\n\nasync function apiCall<T = any>(gatewayUrl: string, jwt: string, path: string, opts: RequestInit = {}): Promise<T> {\n const resp = await fetch(`${gatewayUrl}${path}`, {\n ...opts,\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n ...(opts.headers || {}),\n },\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`API ${resp.status}: ${text.slice(0, 200)}`);\n }\n return resp.json() as Promise<T>;\n}\n\n/**\n * Connect GitHub via WorkOS Pipes OAuth. Returns the token if connected, null if user\n * declines or times out. Exported so `install` can embed this in its flow.\n */\nexport async function connectGitHub(gatewayUrl: string, jwt: string, opts: { silent?: boolean } = {}): Promise<string | null> {\n // Check if already connected\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log(' ✓ GitHub already connected via Synkro.');\n return result.token;\n }\n } catch {}\n\n // Get Pipes OAuth URL and open browser\n if (!opts.silent) console.log(' Opening browser to authorize GitHub...');\n try {\n const authResp = await apiCall<{ url: string }>(\n gatewayUrl, jwt, '/api/pipes-widget/authorize/github', { method: 'POST', body: '{}' },\n );\n openBrowser(authResp.url);\n if (!opts.silent) console.log(' Waiting for authorization...');\n } catch (err) {\n if (!opts.silent) console.error(` Failed to start GitHub authorization: ${(err as Error).message}`);\n return null;\n }\n\n // Poll until connected (max 2 minutes)\n const deadline = Date.now() + 120_000;\n while (Date.now() < deadline) {\n await sleep(2000);\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log('\\n ✓ GitHub connected!');\n return result.token;\n }\n } catch {}\n if (!opts.silent) process.stdout.write('.');\n }\n if (!opts.silent) console.error('\\n Timed out waiting for GitHub authorization.');\n return null;\n}\n\nexport interface SetupGithubOptions {\n nonInteractive?: boolean;\n githubToken?: string;\n skipClaudeToken?: boolean;\n}\n\nexport async function setupGithubCommand(opts: SetupGithubOptions = {}): Promise<void> {\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro-cli login` first.');\n process.exit(1);\n }\n const config = readConfig();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const jwt = getAccessToken();\n if (!jwt) {\n console.error('Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.');\n process.exit(1);\n }\n\n // ── 1. Mint CI API key ──────────────────────────────────────────────\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const minted = await apiCall<{ api_key: string; expires_at: string }>(\n gatewayUrl, jwt, '/api/v1/cli/ci-api-key', { method: 'POST', body: '{}' },\n );\n synkroCiApiKey = minted.api_key;\n console.log(` ✓ Issued CI key (${synkroCiApiKey.slice(0, 18)}…), expires ${minted.expires_at.slice(0, 10)}`);\n } catch (err) {\n console.error(`Failed to mint CI API key: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // ── 2. Get GitHub token via WorkOS Pipes ────────────────────────────\n let ghToken: string;\n\n if (opts.githubToken) {\n ghToken = opts.githubToken;\n } else if (opts.nonInteractive) {\n // In non-interactive mode (CI), try Pipes first, fall back to gh CLI\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n ghToken = result.token;\n } else {\n throw new Error('not connected');\n }\n } catch {\n try {\n ghToken = execSync('gh auth token', { encoding: 'utf-8', timeout: 5000 }).trim();\n } catch {\n console.error('GitHub not connected. Run `synkro-cli setup-github` interactively to connect.');\n return;\n }\n }\n } else {\n // Interactive mode — use Pipes OAuth\n console.log('\\nConnecting to GitHub...');\n const token = await connectGitHub(gatewayUrl, jwt);\n if (!token) {\n console.error('GitHub connection failed. Try again.');\n process.exit(1);\n }\n ghToken = token;\n console.log();\n }\n\n // ── 3. Claude Code OAuth token ──────────────────────────────────────\n let claudeToken: string | undefined;\n if (!opts.skipClaudeToken) {\n console.log('Generating Claude Code OAuth token...');\n console.log(' A browser window will open — authorize with your Claude account.\\n');\n try {\n claudeToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(`Failed to get Claude token: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token received from `claude setup-token`. Expected sk-ant-oat01-...');\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n console.log(' Validating token...');\n try {\n const validateResult = execSync(\n 'claude --print --output-format json \"say ok\"',\n { env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken }, encoding: 'utf-8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] },\n );\n const result = JSON.parse(validateResult);\n if (result.is_error) throw new Error(result.result || 'auth failed');\n console.log(' ✓ Token validated.\\n');\n } catch (err) {\n console.error(`Token validation failed: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n }\n\n // ── 4. Select repos ─────────────────────────────────────────────────\n let selected: Array<{ owner: string; repo: string; full_name: string }>;\n if (opts.nonInteractive) {\n let currentFullName: string | null = null;\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const m = remoteUrl.match(/(?:github\\.com)[:/](.+?)(?:\\.git)?$/);\n if (m) currentFullName = m[1];\n } catch {}\n if (!currentFullName) {\n console.warn(' ⚠ Not in a GitHub repo. Skipping PR scan setup.');\n return;\n }\n const [owner, repo] = currentFullName.split('/');\n selected = [{ owner, repo, full_name: currentFullName }];\n console.log(` Auto-selected repo: ${currentFullName}`);\n } else {\n console.log('Fetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken! });\n if (repos.length === 0) {\n console.error('No accessible repos found. Check your GitHub permissions.');\n process.exit(1);\n }\n console.log(`\\nFound ${repos.length} accessible repo(s):\\n`);\n repos.slice(0, 100).forEach((r, i) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n const rl2 = createInterface({ input, output });\n const selectionRaw = await prompt(rl2, 'Select repos to enable (comma-separated numbers, e.g. 1,3,5): ');\n const selectedIdx = selectionRaw\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl2.close();\n process.exit(1);\n }\n selected = selectedIdx.map((i) => repos[i]);\n console.log(`\\nWill push secrets to ${selected.length} repo(s):`);\n for (const r of selected) console.log(` • ${r.full_name}`);\n console.log();\n const confirm = (await prompt(rl2, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl2.close();\n process.exit(0);\n }\n rl2.close();\n }\n\n // ── 5. Push secrets ─────────────────────────────────────────────────\n console.log();\n for (const r of selected) {\n process.stdout.write(`Pushing secrets to ${r.full_name}... `);\n try {\n await pushSecretsToRepo(\n { token: ghToken! },\n r.owner,\n r.repo,\n {\n claudeCodeOauthToken: claudeToken,\n synkroApiKey: synkroCiApiKey,\n },\n );\n console.log('✓');\n } catch (err) {\n console.log(`✗ (${(err as Error).message})`);\n }\n }\n\n // ── 6. Write workflow file ──────────────────────────────────────────\n console.log();\n const gitRoot = findGitRoot(process.cwd());\n if (gitRoot) {\n const written = writeWorkflowFile(gitRoot);\n if (written) {\n console.log(`Wrote workflow: ${written}`);\n console.log('Commit and push it to enable PR scanning.');\n }\n } else {\n console.log('Not in a git repo. To enable scanning, add this file to your repo:');\n console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);\n console.log(` Content: run \\`synkro-cli setup-github\\` from inside a repo to write it automatically`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (Claude Code, Cursor), drops hook scripts in\n * ~/.synkro/hooks/, writes settings.json hook entries, fetches the latest\n * Edit/Write prompt from the gateway and inlines it into settings, writes\n * config.env. Triggers `synkro login` if not already authed.\n */\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks, uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks, uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig, uninstallMcpConfig, uninstallCursorMcpConfig } from '../installer/mcpConfig.js';\nimport { resolveSkillPaths } from '../installer/skillParser.js';\nimport { SYNKRO_COMMON_SCRIPT } from '../installer/hookScripts.js';\nimport { SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS, CURSOR_EDIT_CAPTURE_TS, CURSOR_AGENT_CAPTURE_TS, INSTALL_SCAN_TS } from '../installer/hookScriptsTs.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo, ensureValidToken } from '../auth/stub.js';\nimport { promptRepoConnection } from './repoConnect.js';\nimport { setApiBaseUrl, listProjects } from '../api/projects.js';\n// connectGitHub import removed — GitHub PR scanning is hidden; the install no\n// longer connects GitHub. setupGithub.ts is kept for when it's re-enabled.\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { dockerInstall, waitForContainerReady, waitForWorkersReady, splitWorkers, type WorkerProvider } from '../local-cc/dockerInstall.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: string;\ndeclare const __INSTALL_EXTRACT_CORE_SRC__: string;\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst HOOKS_DIR = join(SYNKRO_DIR, 'hooks');\nconst BIN_DIR = join(SYNKRO_DIR, 'bin');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nconst MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline';\n\nconst HOME = homedir();\nconst TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');\nconst PORT = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\nconst URL = \\`http://127.0.0.1:\\${PORT}\\`;\n\nlet token = '';\ntry { token = readFileSync(TOKEN_PATH, 'utf-8').trim(); } catch {}\n\nconst rl = createInterface({ input: process.stdin, terminal: false });\n\nrl.on('line', async (line) => {\n if (!line.trim()) return;\n let msg;\n try { msg = JSON.parse(line); } catch { return; }\n if (!msg.id && msg.method?.startsWith('notifications/')) return;\n\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': \\`Bearer \\${token}\\`,\n },\n body: line,\n signal: AbortSignal.timeout(30000),\n });\n if (resp.status === 204) return;\n const body = await resp.text();\n process.stdout.write(body + '\\\\n');\n } catch (err) {\n if (msg.id != null) {\n process.stdout.write(JSON.stringify({\n jsonrpc: '2.0',\n id: msg.id,\n error: { code: -32603, message: 'MCP proxy: HTTP server unreachable' },\n }) + '\\\\n');\n }\n }\n});\n`;\n\ninterface InstallOptions {\n gatewayUrl?: string; // override default\n apiKey?: string; // skip prompting if provided (for tests/CI)\n skipAuth?: boolean;\n noMcp?: boolean; // skip registering the guardrails MCP server\n force?: boolean; // bypass the \"already installed\" short-circuit\n linkRepo?: boolean; // auto-link current repo (non-interactive)\n cursorApiKey?: string; // Cursor API key for the cursor grader pool\n}\n\n// Accept a gateway URL only if it's a plain http(s) URL. The published CLI is\n// invoked from arbitrary cwds where a developer's monorepo .env / op://\n// reference / direnv export may have poisoned SYNKRO_GATEWAY_URL with a value\n// the CLI can't actually call (e.g. \"op://dev/synkro/gateway-url\"). Silently\n// ignoring those falls through to the prod default so customers never have to\n// wrestle with shell hygiene before `synkro install` works.\nfunction sanitizeGatewayCandidate(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n return /^https?:\\/\\//.test(raw) ? raw : undefined;\n}\n\nexport function parseArgs(argv: string[]): InstallOptions {\n const opts: InstallOptions = {};\n for (const a of argv) {\n if (a.startsWith('--api-key=')) opts.apiKey = a.slice('--api-key='.length);\n else if (a.startsWith('--gateway=')) opts.gatewayUrl = a.slice('--gateway='.length);\n else if (a.startsWith('--cursor-api-key=')) opts.cursorApiKey = a.slice('--cursor-api-key='.length);\n else if (a === '--skip-auth') opts.skipAuth = true;\n else if (a === '--no-mcp') opts.noMcp = true;\n else if (a === '--force' || a === '-f') opts.force = true;\n else if (a === '--link-repo') opts.linkRepo = true;\n }\n if (!opts.gatewayUrl) {\n const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);\n if (fromEnv) opts.gatewayUrl = fromEnv;\n }\n // NOTE: we deliberately do NOT pick up SYNKRO_API_KEY from process.env. The\n // root .env / global env may contain a stale or unrelated SYNKRO_API_KEY.\n // The legitimate sources are: --api-key flag, or fresh OAuth via authenticate().\n return opts;\n}\n\n// Ask the user which detected agents they want guardrails installed for.\n// If only one is detected, no prompt — return as-is. If multiple, show a\n// picker that accepts: \"1\" (first), \"2\" (second), \"3\"/\"both\"/\"all\"/empty\n// (everything). Anything else re-prompts.\nasync function promptAgentSelection<T extends { kind: string; name: string }>(\n detected: T[],\n): Promise<T[]> {\n if (detected.length <= 1) return detected;\n\n console.log('Multiple coding agents detected. Which ones do you want Synkro guardrails installed for?');\n detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));\n console.log(` ${detected.length + 1}. Both / all (default)`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (): Promise<T[]> => new Promise((resolve) => {\n rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {\n const t = answer.trim().toLowerCase();\n if (t === '' || t === String(detected.length + 1) || t === 'both' || t === 'all') {\n rl.close();\n return resolve(detected);\n }\n const n = parseInt(t, 10);\n if (Number.isInteger(n) && n >= 1 && n <= detected.length) {\n rl.close();\n return resolve([detected[n - 1]]);\n }\n console.log('Invalid choice. Try again.');\n resolve(ask());\n });\n });\n return ask();\n}\n\n// Collect the Cursor API key when the install includes a Cursor worker pool.\n// Sources, in order: --cursor-api-key flag, SYNKRO_CURSOR_API_KEY env, then\n// an interactive paste prompt. An already-configured key is kept only if\n// Cursor still accepts it.\nasync function promptCursorApiKey(opts: InstallOptions): Promise<void> {\n const { cursorApiKeyConfigured, writeCursorApiKey, validateCursorApiKey } = await import('../local-cc/macKeychain.js');\n if (cursorApiKeyConfigured()) {\n // A key file exists — confirm Cursor still accepts it before trusting it.\n // Only a definitive rejection (false) re-prompts; a network blip (null)\n // leaves the working key alone.\n const valid = await validateCursorApiKey();\n if (valid !== false) {\n console.log(' ✓ Cursor API key validated.');\n return;\n }\n console.warn(' ⚠ The saved Cursor API key was rejected by Cursor (revoked or expired).');\n console.warn(' Paste a fresh key below, or press Enter to skip (Cursor workers stay idle).');\n // fall through to re-source / re-prompt the key\n }\n\n const provided = (opts.cursorApiKey || process.env.SYNKRO_CURSOR_API_KEY || '').trim();\n if (provided) {\n writeCursorApiKey(provided);\n console.log(' ✓ Cursor API key saved to ~/.synkro/cursor-creds/api-key');\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const key = await new Promise<string>((resolve) => {\n rl.question(\n 'Cursor grading needs a Cursor API key (cursor.com → Settings → API Keys).\\n' +\n 'Paste it now, or press Enter to skip (Cursor workers stay idle until set): ',\n (answer) => { rl.close(); resolve(answer.trim()); },\n );\n });\n if (key) {\n writeCursorApiKey(key);\n console.log(' ✓ Cursor API key saved.');\n } else {\n console.log(' ⚠ Skipped — Cursor workers will be idle. Re-run install or pass --cursor-api-key=… later.');\n }\n}\n\n// Where grading runs: local (on-device container worker pool) or byok (an LLM\n// API call with the user's own provider key). Default — and the non-TTY\n// answer — is local.\nasync function promptGradingMode(): Promise<'local' | 'byok'> {\n if (!process.stdin.isTTY) return 'local';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should grading run?\\n' +\n ' local — on this machine, via the Synkro container (default)\\n' +\n ' byok — via an LLM API using your own provider key\\n' +\n 'Choose [local] / byok: ',\n (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'byok' ? 'byok' : 'local');\n },\n );\n });\n}\n\n// Where telemetry is stored: local (the container's PGLite, on this machine)\n// or cloud (Synkro's Timescale). Default — and the non-TTY answer — is local.\nasync function promptStorageMode(): Promise<'local' | 'cloud'> {\n if (!process.stdin.isTTY) return 'local';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should telemetry be stored?\\n' +\n ' local — on this machine only (default)\\n' +\n ' cloud — sent to Synkro cloud\\n' +\n 'Choose [local] / cloud: ',\n (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'cloud' ? 'cloud' : 'local');\n },\n );\n });\n}\n\nasync function promptTranscriptConsent(): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Import and embed your Claude Code session history?\\n' +\n 'This indexes past sessions so Ask Synkro can answer questions\\n' +\n 'about your coding patterns and the dashboard shows full history. (Y/n) ',\n (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n },\n );\n });\n}\n\nconst OFFSETS_DIR = join(SYNKRO_DIR, '.transcript-offsets');\n\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n mkdirSync(OFFSETS_DIR, { recursive: true });\n mkdirSync(join(SYNKRO_DIR, 'sessions'), { recursive: true });\n}\n\nexport function writeHookScripts(): {\n bashScript: string;\n bashFollowupScript: string;\n editPrecheckScript: string;\n cwePrecheckScript: string;\n cvePrecheckScript: string;\n planJudgeScript: string;\n agentJudgeScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n transcriptSyncScript: string;\n userPromptSubmitScript: string;\n installScanScript: string;\n cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\n cursorAgentCaptureScript: string;\n} {\n // Source is inlined at build time via tsup's `define` (see tsup.config.ts).\n // Reading it from disk doesn't work after npm publish because packages/api\n // isn't shipped with the CLI tarball.\n const installExtractCorePath = join(HOOKS_DIR, 'installExtractCore.ts');\n\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.ts');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.ts');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.ts');\n const cwePrecheckScriptPath = join(HOOKS_DIR, 'cc-cwe-precheck.ts');\n const cvePrecheckScriptPath = join(HOOKS_DIR, 'cc-cve-precheck.ts');\n const planJudgeScriptPath = join(HOOKS_DIR, 'cc-plan-judge.ts');\n const agentJudgeScriptPath = join(HOOKS_DIR, 'cc-agent-judge.ts');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.ts');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.ts');\n const transcriptSyncScriptPath = join(HOOKS_DIR, 'cc-transcript-sync.ts');\n const userPromptSubmitScriptPath = join(HOOKS_DIR, 'cc-user-prompt-submit.ts');\n const commonScriptPath = join(HOOKS_DIR, '_synkro-common.ts');\n const commonBashScriptPath = join(HOOKS_DIR, '_synkro-common.sh');\n const installScanScriptPath = join(HOOKS_DIR, 'cc-install-scan.ts');\n const cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-capture.ts');\n const cursorAgentCapturePath = join(HOOKS_DIR, 'cursor-agent-capture.ts');\n const mcpStdioProxyPath = join(HOOKS_DIR, 'mcp-stdio-proxy.ts');\n\n writeFileSync(bashScriptPath, BASH_JUDGE_TS, 'utf-8');\n writeFileSync(bashFollowupScriptPath, BASH_FOLLOWUP_TS, 'utf-8');\n writeFileSync(editPrecheckScriptPath, EDIT_PRECHECK_TS, 'utf-8');\n writeFileSync(cwePrecheckScriptPath, CWE_PRECHECK_TS, 'utf-8');\n writeFileSync(cvePrecheckScriptPath, CVE_PRECHECK_TS, 'utf-8');\n writeFileSync(planJudgeScriptPath, PLAN_JUDGE_TS, 'utf-8');\n writeFileSync(agentJudgeScriptPath, AGENT_JUDGE_TS, 'utf-8');\n writeFileSync(stopSummaryScriptPath, STOP_SUMMARY_TS, 'utf-8');\n writeFileSync(sessionStartScriptPath, SESSION_START_TS, 'utf-8');\n writeFileSync(transcriptSyncScriptPath, TRANSCRIPT_SYNC_TS, 'utf-8');\n writeFileSync(userPromptSubmitScriptPath, USER_PROMPT_SUBMIT_TS, 'utf-8');\n writeFileSync(commonScriptPath, SYNKRO_COMMON_TS, 'utf-8');\n writeFileSync(commonBashScriptPath, SYNKRO_COMMON_SCRIPT, 'utf-8');\n writeFileSync(installScanScriptPath, INSTALL_SCAN_TS, 'utf-8');\n writeFileSync(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, 'utf-8');\n writeFileSync(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, 'utf-8');\n writeFileSync(cursorAgentCapturePath, CURSOR_AGENT_CAPTURE_TS, 'utf-8');\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, 'utf-8');\n writeFileSync(installExtractCorePath, __INSTALL_EXTRACT_CORE_SRC__, 'utf-8');\n\n chmodSync(bashScriptPath, 0o755);\n chmodSync(bashFollowupScriptPath, 0o755);\n chmodSync(editPrecheckScriptPath, 0o755);\n chmodSync(cwePrecheckScriptPath, 0o755);\n chmodSync(cvePrecheckScriptPath, 0o755);\n chmodSync(planJudgeScriptPath, 0o755);\n chmodSync(agentJudgeScriptPath, 0o755);\n chmodSync(stopSummaryScriptPath, 0o755);\n chmodSync(sessionStartScriptPath, 0o755);\n chmodSync(transcriptSyncScriptPath, 0o755);\n chmodSync(userPromptSubmitScriptPath, 0o755);\n chmodSync(commonScriptPath, 0o755);\n chmodSync(commonBashScriptPath, 0o755);\n chmodSync(installScanScriptPath, 0o755);\n chmodSync(cursorBashJudgePath, 0o755);\n chmodSync(cursorEditCapturePath, 0o755);\n chmodSync(cursorAgentCapturePath, 0o755);\n chmodSync(mcpStdioProxyPath, 0o755);\n chmodSync(installExtractCorePath, 0o755);\n\n return {\n bashScript: bashScriptPath,\n bashFollowupScript: bashFollowupScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n cwePrecheckScript: cwePrecheckScriptPath,\n cvePrecheckScript: cvePrecheckScriptPath,\n planJudgeScript: planJudgeScriptPath,\n agentJudgeScript: agentJudgeScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\n transcriptSyncScript: transcriptSyncScriptPath,\n userPromptSubmitScript: userPromptSubmitScriptPath,\n installScanScript: installScanScriptPath,\n cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\n cursorAgentCaptureScript: cursorAgentCapturePath,\n };\n}\n\n// Sanitize values before writing into config.env — the file is sourced as a\n// shell script, so any unquoted shell metacharacter in a value is dangerous\n// (newlines smuggle new assignments; (){}#`$ etc. let an attacker run code\n// on `source config.env`). Two defenses:\n// 1. Strip non-printable ASCII (drops newlines, tabs, control chars)\n// 2. Wrap the result in single-quotes — single-quoted shell strings are\n// entirely literal, so anything inside is safe regardless of contents.\n// Internal single-quotes are escaped using the standard '\\'' trick.\nfunction sanitizeConfigValue(raw: string | undefined, maxLen = 256): string {\n if (!raw) return '';\n return raw\n .replace(/[^\\x20-\\x7E]/g, '') // drop non-printable (newlines/tabs/control)\n .slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n // Escape any single quote in `value` for inclusion inside a single-quoted\n // shell string: 'foo'\"'\"'bar' renders as foo'bar.\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Resolve the absolute path to the synkro bundle (dist/bootstrap.js) so hook\n// scripts can invoke `node \"$SYNKRO_CLI_BIN\" grade ...` without relying on\n// the user's shell PATH. Returning a single path (vs a command string) keeps\n// the hook invocation a single quoted arg — no shell-injection surface.\n//\n// process.argv[1] is the script the user executed: with the pnpm/npm shim\n// it's the absolute path to dist/bootstrap.js. With `just synkro install`\n// it's also the absolute bundle path (the just recipe runs `node bootstrap.js`).\nfunction resolveSynkroBundle(): string | null {\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) return scriptPath;\n return null;\n}\n\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string; inference?: string; synkroBin?: string | null; transcriptConsent?: boolean; localInference?: boolean; deploymentMode?: 'bare-host' | 'docker'; gradingMode?: 'local' | 'byok'; storageMode?: 'local' | 'cloud' }): void {\n const credsPath = join(SYNKRO_DIR, 'credentials.json');\n const safeGateway = sanitizeConfigValue(opts.gatewayUrl);\n const safeUserId = sanitizeConfigValue(opts.userId);\n const safeOrgId = sanitizeConfigValue(opts.orgId);\n const safeEmail = sanitizeConfigValue(opts.email);\n const safeTier = sanitizeConfigValue(opts.tier ?? 'pro', 32);\n const safeInference = sanitizeConfigValue(opts.inference ?? 'fast', 16);\n // Allow up to 1KB for the bin command since it may be `node /long/path/to/bootstrap.js`.\n const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? '', 1024);\n const lines = [\n '# Synkro CLI config (managed by synkro install)',\n '# JWT auth — the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime',\n '# and send Authorization: Bearer <access_token> on every gateway call.',\n `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,\n `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,\n `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,\n `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,\n `SYNKRO_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\n if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);\n if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);\n if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);\n if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);\n if (opts.transcriptConsent !== undefined) {\n lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? 'yes' : 'no')}`);\n }\n lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? 'yes' : 'no')}`);\n // Persisted so hooks see the same value without shell-sourced env.\n const safeMode = sanitizeConfigValue(opts.deploymentMode ?? 'docker', 16);\n lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);\n // User-owned data axes — where grading runs and where telemetry is stored.\n lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? 'local', 16))}`);\n lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? 'local', 16))}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Resolve the deployment mode. Live env var wins (lets ops experiment without\n * mutating the persisted config), then the persisted SYNKRO_DEPLOYMENT_MODE\n * from ~/.synkro/config.env, then defaults to docker.\n *\n * Returns the lowercased string for direct comparison.\n */\nfunction resolveDeploymentMode(): 'docker' | 'bare-host' {\n const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();\n if (envOverride === 'bare-host' || envOverride === 'docker') return envOverride;\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n const val = m?.[1]?.toLowerCase();\n if (val === 'bare-host' || val === 'docker') return val;\n }\n } catch { /* fall through to default */ }\n return 'docker';\n}\n\nfunction collectLocalMetadata(includeClaudeCode = true): Record<string, unknown> {\n const meta: Record<string, unknown> = { platform: process.platform };\n try {\n meta.display_name = execSync('git config user.name', { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {}\n try {\n const remote = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remote.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const m = sshMatch || httpMatch;\n if (m) meta.active_repo = m[1];\n } catch {}\n\n if (!includeClaudeCode) return meta;\n\n try {\n meta.cc_version = execSync('claude --version', { encoding: 'utf-8', timeout: 5000 }).trim().split('\\n')[0];\n } catch {}\n\n const claudeDir = join(homedir(), '.claude');\n\n try {\n const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));\n const plugins = Object.keys(settings.enabledPlugins ?? {}).filter(k => settings.enabledPlugins[k]);\n if (plugins.length) meta.enabled_plugins = plugins;\n if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;\n } catch {}\n\n try {\n const mcpCache = JSON.parse(readFileSync(join(claudeDir, 'mcp-needs-auth-cache.json'), 'utf-8'));\n const mcpNames = Object.keys(mcpCache);\n if (mcpNames.length) meta.mcp_servers = mcpNames;\n } catch {}\n\n try {\n const mcpList = execSync('claude mcp list 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });\n const connected = mcpList.split('\\n')\n .filter(l => l.includes('Connected'))\n .map(l => l.split(':')[0].trim())\n .filter(Boolean);\n if (connected.length) meta.mcp_servers_connected = connected;\n } catch {}\n\n try {\n const sessionsDir = join(claudeDir, 'sessions');\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json')).slice(-5);\n for (const f of files) {\n const s = JSON.parse(readFileSync(join(sessionsDir, f), 'utf-8'));\n if (s.version) { meta.cc_version = meta.cc_version || s.version; break; }\n }\n } catch {}\n\n return meta;\n}\n\nasync function fetchUserProfile(gatewayUrl: string, token: string, hasClaudeCode = true): Promise<{ tier: string; inference: string; localInference: boolean; captureDepth: string }> {\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n headers: { 'Authorization': `Bearer ${token}` },\n });\n if (!resp.ok) return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n const data = await resp.json() as { fast_inference?: boolean; plan_tier?: string; local_inference?: boolean; capture_depth?: string };\n\n const meta = collectLocalMetadata(hasClaudeCode);\n fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(meta),\n }).catch(() => {});\n\n return {\n tier: data.plan_tier ?? 'pro',\n inference: data.fast_inference ? 'fast' : 'standard',\n localInference: !!data.local_inference,\n captureDepth: data.capture_depth ?? 'full',\n };\n } catch {\n return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n }\n}\n\n// JWT-only auth — no API key minting. The hook scripts read the JWT from\n// ~/.synkro/credentials.json and send it as Authorization: Bearer <jwt>.\n// Auth middleware on the gateway resolves user/org from JWT claims via JWKS.\n\n// The CLI ships the user's WorkOS Bearer JWT to the gateway URL. If a\n// hostile actor sets --gateway=http://evil.example.com on a user's machine,\n// the JWT lands at the attacker's server. Allow only:\n// - https://*.synkro.sh and synkro.sh apex\n// - http(s)://localhost or 127.0.0.1 (dev)\n// Anything else is rejected before any fetch is attempted.\nfunction assertGatewayAllowed(gatewayUrl: string): void {\n let parsed: URL;\n try { parsed = new URL(gatewayUrl); }\n catch { throw new Error(`Invalid gateway URL: ${gatewayUrl}`); }\n const proto = parsed.protocol;\n const host = parsed.hostname;\n if (proto !== 'http:' && proto !== 'https:') {\n throw new Error(`Gateway URL must be http(s); got ${proto}`);\n }\n const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';\n const isSynkro = host === 'synkro.sh' || host.endsWith('.synkro.sh');\n if (proto === 'http:' && !isLocalhost) {\n throw new Error(`Gateway URL must be HTTPS for non-localhost hosts; got ${gatewayUrl}`);\n }\n if (!isLocalhost && !isSynkro) {\n throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);\n }\n}\n\n// Detect a complete prior install: every hook script present, config.env\n// written, and CC settings.json carries our `__synkro_managed__` markers.\n// MCP registration is opt-out (--no-mcp) so we don't gate on it. Returns\n// true only when every required surface is present so we never short-\n// circuit a partial / broken install.\nfunction isAlreadyInstalled(): boolean {\n const requiredScripts = [\n join(HOOKS_DIR, 'cc-bash-judge.ts'),\n join(HOOKS_DIR, 'cc-bash-followup.ts'),\n join(HOOKS_DIR, 'cc-edit-precheck.ts'),\n join(HOOKS_DIR, 'cc-cve-precheck.ts'),\n join(HOOKS_DIR, 'cc-plan-judge.ts'),\n join(HOOKS_DIR, 'cc-stop-summary.ts'),\n join(HOOKS_DIR, 'cc-session-start.ts'),\n ];\n if (!requiredScripts.every((p) => existsSync(p))) return false;\n if (!existsSync(CONFIG_PATH)) return false;\n\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hooks = settings?.hooks;\n if (!hooks || typeof hooks !== 'object') return false;\n const hasManaged = (kind: string) =>\n Array.isArray(hooks[kind]) &&\n hooks[kind].some((entry: Record<string, unknown>) => entry?.__synkro_managed__ === true);\n if (!hasManaged('PreToolUse')) return false;\n if (!hasManaged('PostToolUse')) return false;\n if (!hasManaged('SessionEnd')) return false;\n if (!hasManaged('SessionStart')) return false;\n } catch {\n return false;\n }\n return true;\n}\n\n\nexport async function installCommand(opts: InstallOptions = {}): Promise<void> {\n // Synkro anchors all state — projects, telemetry, rulesets — to a repo\n // identity. Without a git repo there is nothing to key that state on, so a\n // repo is mandatory. This is a purely local check (`git remote get-url\n // origin`) — no GitHub login involved.\n if (!detectGitRepo()) {\n console.error('Synkro must be installed inside a git repository.');\n console.error(' `cd` into your project and re-run `synkro install`.');\n process.exit(1);\n }\n\n const gatewayUrl = opts.gatewayUrl\n || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL)\n || 'https://api.synkro.sh';\n\n // Reject hostile gateway overrides before we leak the JWT.\n try {\n assertGatewayAllowed(gatewayUrl);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n // No short-circuit. Plain `synkro install` is always a full refresh —\n // re-pulls the latest container image, re-writes hooks, re-registers the\n // MCP server. Each individual step is idempotent on its own; auth reuses\n // existing valid credentials so the user isn't bounced to the browser\n // unnecessarily.\n console.log('Synkro install starting...\\n');\n\n // 1. Auth via browser OAuth (WorkOS). Persists JWT + refresh_token to\n // ~/.synkro/credentials.json. The hook scripts authenticate against\n // the gateway with `Authorization: Bearer <access_token>` — auth\n // middleware resolves user/org from the JWT claims directly.\n if (!isAuthenticated()) {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n switch (status.phase) {\n case 'starting': console.log(' Starting local callback server...'); break;\n case 'browser-opened': console.log(` Browser opened: ${status.url}`); break;\n case 'waiting': console.log(' Waiting for browser auth to complete...'); break;\n case 'success': console.log(' ✓ Authenticated'); break;\n case 'error': console.error(` ✗ ${status.message}`); break;\n }\n });\n if (!result) {\n console.error('Authentication failed. If you are running a self-hosted dashboard, set SYNKRO_WEB_AUTH_URL to its origin.');\n process.exit(1);\n }\n }\n const token = getAccessToken();\n if (!token) {\n console.error('No access token available after auth.');\n process.exit(1);\n }\n\n // 1b. GitHub PR scanning is hidden for now — the install no longer prompts\n // for it. The code path (connectGitHub / setupGithubCommand below) is kept\n // intact, just unreachable, for when PR scanning is re-enabled.\n const ghToken: string | null = null;\n\n // 1c. Connect repos (local git or GitHub OAuth)\n setApiBaseUrl(`${gatewayUrl}/api`);\n await promptRepoConnection({ linkRepo: opts.linkRepo });\n\n // 2. Detect installed agents\n const detected = detectAgents();\n if (detected.length === 0) {\n console.error('No supported coding agents detected. Install Claude Code or Cursor first.');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of detected) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n // Let the user pick which detected agents to install hooks for. Skipped\n // automatically when only one is detected.\n const agents = await promptAgentSelection(detected);\n if (agents.length < detected.length) {\n console.log(`Installing hooks for: ${agents.map(a => a.name).join(', ')}\\n`);\n }\n\n // Two user-owned data axes — both default to local. Persisted to config.env;\n // changeable later in the dashboard under Settings.\n const gradingMode = await promptGradingMode();\n const storageMode = await promptStorageMode();\n console.log(` grading: ${gradingMode} storage: ${storageMode}\\n`);\n if (gradingMode === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.\\n');\n }\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/\\n');\n\n // Kill any stale legacy Python grader daemons so they don't keep handling\n // requests after we've switched to the local-CC channel path.\n for (const mode of ['edit', 'bash']) {\n const pidFile = join(SYNKRO_DIR, 'daemon', mode, 'daemon.pid');\n try {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n if (pid > 0) {\n process.kill(pid, 'SIGTERM');\n console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);\n }\n } catch {}\n }\n\n\n // 3b. Transcript consent — import + embed existing CC sessions.\n let transcriptConsent = true;\n if (process.stdin.isTTY) {\n transcriptConsent = await promptTranscriptConsent();\n if (transcriptConsent) {\n console.log(' ✓ Session import enabled\\n');\n } else {\n console.log(' ✗ Session import skipped\\n');\n }\n }\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n let hasCursor = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n hasClaudeCode = true;\n installCCHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.bashScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n skipTranscriptSync: !transcriptConsent,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n } else if (agent.kind === 'cursor') {\n hasCursor = true;\n installCursorHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Fetch user profile early — we need captureDepth to decide local vs cloud MCP.\n let userId: string | undefined;\n let orgId: string | undefined;\n let email: string | undefined;\n try {\n const info = getUserInfo();\n userId = info.id;\n orgId = info.org_id;\n email = info.email;\n } catch {\n // unreachable — we just authenticated above\n }\n const profile = await fetchUserProfile(gatewayUrl, token, hasClaudeCode);\n // Cloud-only when both axes are cloud: BYOK grading + cloud storage bypass\n // the container's worker pool AND its PGLite, so skip Docker entirely and\n // register the MCP against the cloud endpoint. Any other combination still\n // needs the local container (for grading and/or local storage).\n const cloudOnly = gradingMode === 'byok' && storageMode === 'cloud';\n const useLocalMcp = !cloudOnly;\n if (cloudOnly) {\n console.log('Cloud-only setup (BYOK grading + cloud storage) — skipping the local container.\\n');\n }\n\n // 5c. Register the Synkro Guardrails MCP server in ~/.claude.json.\n // Local mode: point to localhost:8931, start local server, backfill rules.\n // Cloud mode: mint long-lived JWT, point to cloud endpoint.\n if (hasClaudeCode && !opts.noMcp) {\n if (useLocalMcp) {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n let mcpJwt = '';\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n mcpJwt = minted.token;\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), mcpJwt + '\\n', { mode: 0o600 });\n } else {\n console.warn(' ⚠ Could not mint MCP token — local server will reject requests until re-installed.');\n }\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: mcpJwt, local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log();\n } catch (err) {\n console.warn(` ⚠ Local MCP setup failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry.');\n console.log();\n }\n } else {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log(` expires: ${minted.expires_at} (~1 year)`);\n console.log(' (restart any running Claude Code session for it to load)');\n console.log();\n } catch (err) {\n console.warn(` ⚠ MCP registration failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry MCP setup.');\n console.log();\n }\n }\n }\n\n // 5d. Register the Synkro Guardrails MCP server in ~/.cursor/mcp.json.\n if (hasCursor && !opts.noMcp) {\n try {\n if (useLocalMcp) {\n // Ensure JWT exists (may already be minted in 5c for Claude Code)\n const jwtPath = join(SYNKRO_DIR, '.mcp-jwt');\n if (!existsSync(jwtPath)) {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(jwtPath, minted.token + '\\n', { mode: 0o600 });\n }\n }\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: '', local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n } else {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n }\n console.log();\n } catch (err) {\n console.warn(` ⚠ Cursor MCP registration failed: ${(err as Error).message}`);\n console.log();\n }\n }\n\n // 6. Write config.env — hook scripts read SYNKRO_GATEWAY_URL and the\n // credentials path. They auth via Bearer JWT (resolved from the\n // credentials file at runtime so refreshes Just Work).\n const synkroBundle = resolveSynkroBundle();\n // Persist the deployment mode so subsequent installs / hooks see the same\n // value without depending on shell-sourced env.\n const persistedMode = resolveDeploymentMode();\n writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode, gradingMode, storageMode });\n console.log(`Wrote config to ${CONFIG_PATH}`);\n console.log(` inference: ${profile.inference} (server-side grading)`);\n if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);\n if (synkroBundle) console.log(` SYNKRO_CLI_BIN=${synkroBundle}`);\n else console.warn(' ⚠ Could not resolve synkro bundle path; hooks will fall back to PATH lookup of `synkro`.');\n\n try {\n const prompts = await fetchJudgePrompts({ gatewayUrl, jwt: token });\n console.log(` prompts: ${prompts.version} (live)`);\n } catch (err) {\n console.warn(` ⚠ Could not cache judge prompts: ${(err as Error).message}`);\n }\n\n // Write .synkro to repo root if one doesn't exist yet.\n writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode });\n console.log();\n\n // The container provides the grading worker pool and local PGLite — needed\n // for any setup except cloud-only (BYOK grading + cloud storage).\n if (useLocalMcp) {\n const { assertDockerAvailable } = await import('../local-cc/dockerInstall.js');\n try {\n assertDockerAvailable();\n } catch (err) {\n console.error(`\\n✗ ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Cursor worker pool needs a Cursor API key — collect it before starting\n // the container so the workers come up authenticated.\n if (hasCursor) {\n await promptCursorApiKey(opts);\n }\n\n console.log('Installing Synkro server container...');\n // Read .synkro for worker config: explicit counts > pool > agent detection.\n const sf = readFullSynkroFile();\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {\n claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));\n cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (claudeWorkers + cursorWorkers === 0) { claudeWorkers = 0; cursorWorkers = 8; }\n console.log(` .synkro: explicit workers — ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n } else {\n const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();\n let providers: WorkerProvider[] = [];\n if (synkroFilePool === 'cursor') {\n providers = ['cursor'];\n } else if (synkroFilePool === 'claude') {\n providers = ['claude_code'];\n } else {\n if (hasClaudeCode) providers.push('claude_code');\n if (hasCursor) providers.push('cursor');\n }\n ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));\n if (synkroFilePool !== 'auto') console.log(` .synkro: grader pool set to ${synkroFilePool}`);\n }\n console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n // Connected repo — the container names the seeded app + ruleset after it.\n const connectedRepo = detectGitRepo() || undefined;\n const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } =\n await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);\n console.log(' waiting for container to be ready...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ container ready');\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n try {\n const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpJwt}` },\n body: JSON.stringify({ capture_type: 'healthcheck', event_id: `healthcheck_${Date.now()}` }),\n signal: AbortSignal.timeout(5000),\n });\n if (ingestResp.ok) {\n console.log(' ✓ ingest endpoint verified');\n } else {\n console.warn(` ⚠ ingest endpoint returned ${ingestResp.status} — telemetry spool may not drain.`);\n console.warn(' The .mcp-jwt token may not match the container. Try: synkro uninstall && synkro install');\n }\n } catch {\n console.warn(' ⚠ ingest endpoint unreachable — telemetry spool may not drain.');\n }\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log(' ✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn(' ⚠ workers did not register within 30s — skill sync skipped');\n }\n } else {\n console.error(' ✗ container did not become healthy within 60s');\n console.error(' Run `docker logs synkro-server` to debug.');\n process.exit(1);\n }\n console.log();\n }\n\n // 7. Import + embed CC session transcripts (only if user consented and CC is installed).\n if (transcriptConsent && hasClaudeCode) {\n const repo = detectGitRepo();\n if (repo) {\n if (storageMode === 'local') {\n // Local: sync into PGLite — the container's embedding model vectors each message automatically.\n try {\n let mcpToken = '';\n try { mcpToken = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (mcpToken) {\n const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n const result = await syncTranscriptsLocal(mcpPort, mcpToken, repo);\n if (result.messages > 0) {\n console.log(` ✓ Imported ${result.sessions} sessions (${result.messages} messages) into local store.`);\n console.log(' Embeddings generated. Analyzing for rule suggestions...');\n try {\n const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: AbortSignal.timeout(90000),\n });\n if (suggestResp.ok) {\n const suggestResult = await suggestResp.json() as { suggested?: number; skippedDuplicates?: number };\n if (suggestResult.suggested && suggestResult.suggested > 0) {\n console.log(` ✓ Generated ${suggestResult.suggested} rule suggestions from your session history.`);\n console.log(' Ask Claude: \"show me suggested rules\" to review them.\\n');\n } else {\n console.log(' No rule suggestions generated yet — more session data needed.\\n');\n }\n }\n } catch {\n console.log(' Rule analysis will run automatically as more sessions are recorded.\\n');\n }\n }\n } else {\n console.warn(' ⚠ Session import skipped — container auth token not found.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Local session import failed: ${(err as Error).message}\\n`);\n }\n } else {\n // Cloud: send to Timescale via gateway — BYOK embedding model from inference config.\n try {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(` ✓ Indexed ${ingested} session insights from Claude Code history.`);\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n try {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(` ✓ Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);\n console.log(' Embeddings use your configured inference provider.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\n`);\n }\n }\n }\n }\n\n // 8. PR scan setup (secrets + workflow) — only if GitHub was connected in step 1b\n if (ghToken) {\n const { setupGithubCommand } = await import('./setupGithub.js');\n await setupGithubCommand({ nonInteractive: true, githubToken: ghToken });\n }\n\n // 9. Done\n console.log('✓ Synkro installed.');\n}\n\n/**\n * Identify the git repo `synkro install` is running in. `git` itself walks up\n * from the cwd, so this resolves correctly from any subdirectory or worktree.\n * Tries the `origin` remote (→ owner/repo), then falls back to the repo's\n * top-level directory name. Returns null only when the cwd is not a git repo\n * at all — mirrors the hook's detectRepo() so the seeded project id and the id\n * telemetry lands under always agree.\n */\nfunction parseSynkroYaml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n const lines = raw.split('\\n');\n let currentKey = '';\n let currentObj: Record<string, any> | null = null;\n let currentArr: string[] | null = null;\n\n for (const line of lines) {\n if (!line.trim() || line.trim().startsWith('#')) continue;\n\n if (/^\\S/.test(line) && line.includes(':')) {\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n currentObj = null;\n currentArr = null;\n\n const colonIdx = line.indexOf(':');\n const key = line.slice(0, colonIdx).trim();\n const val = line.slice(colonIdx + 1).trim();\n currentKey = key;\n\n if (val) {\n if (val === '[]') result[key] = [];\n else if (val === 'true') result[key] = true;\n else if (val === 'false') result[key] = false;\n else if (/^\\d+$/.test(val)) result[key] = parseInt(val, 10);\n else result[key] = val;\n currentKey = '';\n }\n } else if (/^ - /.test(line)) {\n if (!currentArr) currentArr = [];\n currentArr.push(line.replace(/^ - /, '').trim());\n } else if (/^ \\S/.test(line) && line.includes(':')) {\n if (!currentObj) currentObj = {};\n const colonIdx = line.indexOf(':');\n const k = line.slice(0, colonIdx).trim();\n const v = line.slice(colonIdx + 1).trim();\n if (v === 'true') currentObj[k] = true;\n else if (v === 'false') currentObj[k] = false;\n else if (/^\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);\n else currentObj[k] = v;\n }\n }\n if (currentObj && currentKey) result[currentKey] = currentObj;\n if (currentArr && currentKey) result[currentKey] = currentArr;\n return result;\n}\n\nfunction parseSynkroFileRaw(content: string): Record<string, any> {\n return content.trimStart().startsWith('{') ? JSON.parse(content) : parseSynkroYaml(content);\n}\n\nfunction writeSynkroFileIfMissing(opts: { hasClaudeCode: boolean; hasCursor: boolean; gradingMode: string }): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return;\n const fp = join(root, '.synkro');\n if (existsSync(fp)) {\n console.log(` .synkro: ${fp} (existing, respected)`);\n return;\n }\n let pool: string = 'auto';\n const harness: string[] = [];\n if (opts.hasClaudeCode) { harness.push('claude-code'); if (!opts.hasCursor) pool = 'claude'; }\n if (opts.hasCursor) { harness.push('cursor'); if (!opts.hasClaudeCode) pool = 'cursor'; }\n const mode = opts.gradingMode === 'byok' ? 'byok' : 'local';\n const yaml = [\n 'version: 1',\n '',\n 'harness:',\n ...harness.map(h => ` - ${h}`),\n '',\n 'grader:',\n ` pool: ${pool}`,\n ` mode: ${mode}`,\n '',\n 'ruleset: default',\n '',\n 'skills: []',\n '',\n 'scanning:',\n ' cwe: true',\n ' cve: true',\n '',\n ].join('\\n');\n writeFileSync(fp, yaml, 'utf-8');\n console.log(` .synkro: wrote ${fp} (pool=${pool}, mode=${mode})`);\n } catch {}\n}\n\nfunction readSynkroFilePool(): 'auto' | 'claude' | 'cursor' {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return 'auto';\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return 'auto';\n const parsed = parseSynkroFileRaw(readFileSync(fp, 'utf-8'));\n const pool = parsed?.grader?.pool;\n if (pool === 'cursor' || pool === 'claude') return pool;\n } catch {}\n return 'auto';\n}\n\ninterface SynkroFile {\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude' | 'cursor'; mode: string };\n workers: { claude?: number; cursor?: number };\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n _repoRoot: string;\n}\n\nfunction readFullSynkroFile(): SynkroFile | null {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return null;\n const fp = join(root, '.synkro');\n if (!existsSync(fp)) return null;\n const parsed = parseSynkroFileRaw(readFileSync(fp, 'utf-8'));\n const valid = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => valid.includes(h as any))\n : ['claude-code', 'cursor'];\n return {\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : 'local',\n },\n workers: {\n ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),\n ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),\n },\n ruleset: parsed.ruleset || 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string' && s.endsWith('.md')) : [],\n scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },\n _repoRoot: root,\n };\n } catch { return null; }\n}\n\nexport function reconcileHarness(): { claudeWorkers: number; cursorWorkers: number } | null {\n const sf = readFullSynkroFile();\n if (!sf) {\n console.log('No .synkro file found in repo root — skipping harness reconciliation.');\n return null;\n }\n\n const wantCC = sf.harness.includes('claude-code');\n const wantCursor = sf.harness.includes('cursor');\n console.log(`.synkro: harness=[${sf.harness.join(', ')}] pool=${sf.grader.pool} mode=${sf.grader.mode}`);\n\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/');\n\n const ccSettings = join(homedir(), '.claude', 'settings.json');\n if (wantCC) {\n installCCHooks(ccSettings, {\n bashJudgeScriptPath: scripts.bashScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(' ✓ Claude Code hooks registered');\n try {\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcpJwt) {\n installMcpConfig({ gatewayUrl: '', bearerToken: mcpJwt, local: true });\n console.log(' ✓ Claude Code MCP registered');\n }\n } catch {}\n } else {\n if (uninstallCCHooks(ccSettings)) console.log(' ✗ Claude Code hooks removed');\n if (uninstallMcpConfig()) console.log(' ✗ Claude Code MCP removed');\n }\n\n const cursorHooks = join(homedir(), '.cursor', 'hooks.json');\n if (wantCursor) {\n installCursorHooks(cursorHooks, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n });\n console.log(' ✓ Cursor hooks registered');\n try {\n installCursorMcpConfig({ gatewayUrl: '', bearerToken: '', local: true });\n console.log(' ✓ Cursor MCP registered');\n } catch {}\n } else {\n if (uninstallCursorHooks(cursorHooks)) console.log(' ✗ Cursor hooks removed');\n if (uninstallCursorMcpConfig()) console.log(' ✗ Cursor MCP removed');\n }\n\n // Explicit worker counts from .synkro take priority\n if (sf.workers.claude != null || sf.workers.cursor != null) {\n const cw = Math.max(0, Math.floor(sf.workers.claude || 0));\n const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };\n }\n\n const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const providers: WorkerProvider[] = [];\n if (sf.grader.pool === 'cursor') {\n providers.push('cursor');\n } else if (sf.grader.pool === 'claude') {\n providers.push('claude_code');\n } else {\n if (wantCC) providers.push('claude_code');\n if (wantCursor) providers.push('cursor');\n }\n if (providers.length === 0) providers.push('claude_code');\n return splitWorkers(total, providers);\n}\n\nexport async function syncSkillFiles(): Promise<void> {\n const sf = readFullSynkroFile();\n if (!sf || sf.skills.length === 0) return;\n\n const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);\n if (resolved.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const tasks = resolved.map(fp => {\n const content = readFileSync(fp, 'utf-8');\n const source = `skill:${fp.split('/').pop()}`;\n if (!content.trim()) return null;\n return { source, content };\n }).filter(Boolean) as { source: string; content: string }[];\n\n if (tasks.length === 0) return;\n\n const results = await Promise.allSettled(tasks.map(async ({ source, content }) => {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source, content }),\n signal: AbortSignal.timeout(65000),\n });\n if (!resp.ok) throw new Error(`${resp.status}`);\n const result = await resp.json() as { created: number; message?: string };\n return { source, ...result };\n }));\n\n for (const r of results) {\n if (r.status === 'fulfilled') {\n const { source, created, message } = r.value;\n console.log(created > 0\n ? ` ✓ skill ${source}: ${created} new rules added`\n : ` ✓ skill ${source}: ${message || 'up to date'}`);\n } else {\n console.warn(` ⚠ skill sync failed: ${r.reason}`);\n }\n }\n}\n\nexport function detectGitRepo(): string | null {\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return '';\n }\n };\n const remoteUrl = run('git remote get-url origin');\n if (remoteUrl) {\n return remoteUrl\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\/\\/[^/]+\\//, '')\n .replace(/\\.git$/, '');\n }\n // No origin remote — fall back to the repo's top-level directory name.\n const root = run('git rev-parse --show-toplevel');\n return root ? (root.split('/').pop() || null) : null;\n}\n\nfunction getClaudeProjectsFolder(): string | null {\n const cwd = process.cwd();\n // CC stores transcripts in ~/.claude/projects/{sanitized-path}/\n // where sanitized-path replaces / with -\n const sanitized = '-' + cwd.replace(/\\//g, '-');\n const projectsDir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(projectsDir) ? projectsDir : null;\n}\n\ninterface SessionInsight {\n session_id: string;\n insight_type: 'summary' | 'user_message';\n content: string;\n metadata?: Record<string, unknown>;\n}\n\nfunction extractSessionInsights(projectsDir: string): SessionInsight[] {\n const insights: SessionInsight[] = [];\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Extract compaction summaries\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user' &&\n typeof entry.message?.content === 'string' &&\n entry.message.content.startsWith('This session is being continued')) {\n insights.push({\n session_id: sessionId,\n insight_type: 'summary',\n content: entry.message.content.slice(0, 4000),\n metadata: { source: 'compaction_summary' },\n });\n }\n } catch {}\n }\n\n // Extract user messages (last 20 per session — recent preferences)\n const userMessages: string[] = [];\n for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user') {\n const text = typeof entry.message?.content === 'string'\n ? entry.message.content\n : Array.isArray(entry.message?.content)\n ? entry.message.content.map((b: any) => b.text ?? b).filter((t: any) => typeof t === 'string').join(' ')\n : null;\n if (text && text.length > 10 && text.length < 2000 && !text.startsWith('This session is being continued')) {\n userMessages.push(text);\n }\n }\n } catch {}\n }\n for (const msg of userMessages.reverse()) {\n insights.push({\n session_id: sessionId,\n insight_type: 'user_message',\n content: msg.slice(0, 2000),\n });\n }\n } catch {}\n }\n\n return insights;\n}\n\nasync function ingestSessionTranscripts(gatewayUrl: string, token: string, repo: string): Promise<number> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return 0;\n\n const insights = extractSessionInsights(projectsDir);\n if (insights.length === 0) return 0;\n\n console.log(`Found ${insights.length} session insights from Claude Code history...`);\n\n // Send in batches of 100\n let total = 0;\n for (let i = 0; i < insights.length; i += 100) {\n const batch = insights.slice(i, i + 100);\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ingest-sessions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions: batch }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number };\n total += result.accepted;\n }\n } catch {}\n }\n\n return total;\n}\n\ninterface TranscriptMessage {\n message_index: number;\n type: 'user' | 'assistant';\n content: string;\n tool_calls?: Array<{ name: string; input: string; id: string }>;\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n}\n\nfunction extractTextContent(content: any): string {\n if (typeof content === 'string') return content.slice(0, 8000);\n if (Array.isArray(content)) {\n return content\n .filter((b: any) => typeof b === 'string' || (b?.type === 'text'))\n .map((b: any) => typeof b === 'string' ? b : (b?.text || ''))\n .join(' ')\n .slice(0, 8000);\n }\n return '';\n}\n\nfunction parseTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n\n const msg: TranscriptMessage = {\n message_index: i,\n type: entry.type,\n content: extractTextContent(entry.message?.content),\n };\n\n if (entry.type === 'assistant') {\n if (Array.isArray(entry.message?.content)) {\n const toolCalls = entry.message.content\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({\n name: b.name || '',\n input: JSON.stringify(b.input || {}).slice(0, 500),\n id: b.id || '',\n }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n }\n if (entry.message?.model) msg.model = entry.message.model;\n if (entry.message?.usage) {\n msg.usage = {\n input_tokens: entry.message.usage.input_tokens,\n output_tokens: entry.message.usage.output_tokens,\n cache_creation_input_tokens: entry.message.usage.cache_creation_input_tokens,\n cache_read_input_tokens: entry.message.usage.cache_read_input_tokens,\n };\n }\n }\n\n if (msg.content.length > 0) messages.push(msg);\n } catch {}\n }\n\n return messages;\n}\n\nasync function syncTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${files.length} CC session transcripts, importing + embedding...`);\n\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;\n if (messages.length === 0) continue;\n\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: sessionId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === files.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);\n }\n\n // Write offset so the Stop hook doesn't re-send\n try {\n const content = readFileSync(join(projectsDir, file), 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nasync function syncTranscriptsBulk(gatewayUrl: string, token: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(`Found ${files.length} CC session transcripts, syncing...`);\n\n const maxMessagesPerSession = 500;\n let totalSessions = 0;\n let totalMessages = 0;\n\n // Batch sessions into groups of 5\n for (let i = 0; i < files.length; i += 5) {\n const batch = files.slice(i, i + 5);\n const sessions: Array<{ cc_session_id: string; messages: TranscriptMessage[] }> = [];\n\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > maxMessagesPerSession\n ? allMessages.slice(-maxMessagesPerSession)\n : allMessages;\n\n if (messages.length > 0) {\n sessions.push({ cc_session_id: sessionId, messages });\n }\n } catch {}\n }\n\n if (sessions.length === 0) continue;\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number; sessions: number };\n totalMessages += result.accepted;\n totalSessions += result.sessions;\n }\n } catch {}\n\n // Write offset files so the Stop hook doesn't re-send\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n }\n\n return { sessions: totalSessions, messages: totalMessages };\n}\n","/**\n * Filesystem setup for the local-CC channel plugin.\n *\n * Idempotent: safe to call multiple times. Writes:\n * ~/.synkro/cc_sessions/synkro-channel.ts (the Bun MCP plugin)\n * ~/.synkro/cc_sessions/package.json (deps for the plugin)\n * ~/.synkro/cc_sessions/.claude/settings.json (fastMode:true scoped to this cwd)\n *\n * Then patches ~/.claude.json to register the plugin under mcpServers, and runs\n * `bun install` in the session dir so @modelcontextprotocol/sdk is on disk.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, copyFileSync, renameSync, unlinkSync, openSync, fsyncSync, closeSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { spawnSync } from 'node:child_process';\n\nexport const CLAUDE_JSON_BACKUP_PATH = join(homedir(), '.claude.json.synkro-bak');\n\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\nexport const PLUGIN_PATH = join(SESSION_DIR, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH = join(SESSION_DIR, 'package.json');\nexport const PLUGIN_SETTINGS_DIR = join(SESSION_DIR, '.claude');\nexport const PLUGIN_SETTINGS_PATH = join(PLUGIN_SETTINGS_DIR, 'settings.json');\nexport const PROJECT_MCP_PATH = join(SESSION_DIR, '.mcp.json');\nexport const CLAUDE_JSON_PATH = join(homedir(), '.claude.json');\nexport const RUN_SCRIPT_PATH = join(SESSION_DIR, 'run-claude.sh');\nexport const TMUX_SESSION_NAME = 'synkro-local-cc';\n\n// Channel 1 (general A) — pueue task → claude listening on this internal port.\n// The MCP server's dispatcher (8929) round-robins between Channel 1 and 3.\nexport const CHANNEL_1_PORT = 8941;\n\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\nexport const PLUGIN_PATH_2 = join(SESSION_DIR_2, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_2 = join(SESSION_DIR_2, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_2 = join(SESSION_DIR_2, '.claude');\nexport const PLUGIN_SETTINGS_PATH_2 = join(PLUGIN_SETTINGS_DIR_2, 'settings.json');\nexport const PROJECT_MCP_PATH_2 = join(SESSION_DIR_2, '.mcp.json');\nexport const RUN_SCRIPT_PATH_2 = join(SESSION_DIR_2, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_2 = 'synkro-local-cc-2';\n// CWE worker A. Dispatcher on 8930 round-robins Channel 2 and 4.\nexport const CHANNEL_2_PORT = 8951;\n\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\nexport const PLUGIN_PATH_3 = join(SESSION_DIR_3, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_3 = join(SESSION_DIR_3, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_3 = join(SESSION_DIR_3, '.claude');\nexport const PLUGIN_SETTINGS_PATH_3 = join(PLUGIN_SETTINGS_DIR_3, 'settings.json');\nexport const PROJECT_MCP_PATH_3 = join(SESSION_DIR_3, '.mcp.json');\nexport const RUN_SCRIPT_PATH_3 = join(SESSION_DIR_3, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_3 = 'synkro-local-cc-3';\nexport const CHANNEL_3_PORT = 8942; // general worker B\n\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\nexport const PLUGIN_PATH_4 = join(SESSION_DIR_4, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_4 = join(SESSION_DIR_4, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_4 = join(SESSION_DIR_4, '.claude');\nexport const PLUGIN_SETTINGS_PATH_4 = join(PLUGIN_SETTINGS_DIR_4, 'settings.json');\nexport const PROJECT_MCP_PATH_4 = join(SESSION_DIR_4, '.mcp.json');\nexport const RUN_SCRIPT_PATH_4 = join(SESSION_DIR_4, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_4 = 'synkro-local-cc-4';\nexport const CHANNEL_4_PORT = 8952; // CWE worker B\n\n/**\n * Bash wrapper that owns the tmux session hosting `claude`. Pueue runs this\n * script as its task. The script:\n * 1. Kills any prior tmux session by the same name (idempotent restart).\n * 2. Starts a detached tmux session running claude. tmux gives claude a\n * real pty, so it stays in interactive mode (vs. dropping into --print).\n * 3. Blocks on tmux's lifetime so pueue's task duration mirrors claude's.\n * `synkro local-cc stop` kills the tmux session, which unblocks the\n * poll and lets pueue mark the task done.\n *\n * Dismissal of startup dialogs (workspace trust / dev-channels /\n * MCP consent) is owned by the TS-side waitForChannelReady, which polls\n * the channel TCP port and injects \\`tmux send-keys ... Enter\\` each tick\n * the probe fails. That's adaptive (no fixed window) and self-terminating\n * (stops the moment the port binds).\n */\nconst RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Do not edit.\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME}\nLOG=\"$HOME/.synkro/cc_sessions/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\n# Pre-flight checks\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH. Install Claude Code first.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\n# Check claude is authenticated\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed. Is Claude Code installed correctly?\"\n exit 1\nfi\n\nlog \"Starting local-CC session...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\n# Kill any previous session so restarts come up clean.\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\n# Start claude inside a detached tmux session so it has a real pty.\n# Redirect stderr to the log so we can see why it dies.\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\n# Claude's --dangerously-load-development-channels shows a confirmation\n# prompt: option 1 = \"I am using this for local development\" (accept),\n# option 2 = \"Exit\". Auto-accept by sending '1' + Enter.\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n # Additional Enter for any follow-up prompts (workspace trust, MCP consent)\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to claude session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n log \"Try running claude manually to verify auth: claude --print 'say ok'\"\n exit 1\nfi\n\nlog \"tmux session started successfully.\"\n\n# Block on the tmux session so pueue's task lifetime tracks claude's.\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst RUN_SCRIPT_SOURCE_2 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Channel 2 (CWE scan, port ${CHANNEL_2_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_2}\nLOG=\"$HOME/.synkro/cc_sessions_2/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC channel 2 (port ${CHANNEL_2_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to channel 2 session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_2_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// General worker B (port CHANNEL_3_PORT) — dispatcher pairs this with cc_sessions on 8929.\nconst RUN_SCRIPT_SOURCE_3 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. General worker B (port ${CHANNEL_3_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_3}\nLOG=\"$HOME/.synkro/cc_sessions_3/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC general worker B (port ${CHANNEL_3_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to general worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_3_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// CWE worker B (port CHANNEL_4_PORT) — dispatcher pairs this with cc_sessions_2 on 8930.\nconst RUN_SCRIPT_SOURCE_4 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. CWE worker B (port ${CHANNEL_4_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_4}\nLOG=\"$HOME/.synkro/cc_sessions_4/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC CWE worker B (port ${CHANNEL_4_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to CWE worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_4_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst MCP_SERVER_NAME = 'synkro-local';\n\nconst PLUGIN_PACKAGE_JSON = JSON.stringify(\n {\n name: 'synkro-local-channel',\n private: true,\n version: '0.1.0',\n type: 'module',\n dependencies: {\n '@modelcontextprotocol/sdk': '^1.0.0',\n },\n },\n null,\n 2,\n) + '\\n';\n\nexport class LocalCCInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCInstallError';\n }\n}\n\ninterface ChannelInstallSlot {\n sessionDir: string;\n pluginPath: string;\n pluginPkgPath: string;\n pluginSettingsDir: string;\n pluginSettingsPath: string;\n projectMcpPath: string;\n runScriptPath: string;\n runScriptSource: string;\n}\n\nconst CHANNELS: readonly ChannelInstallSlot[] = [\n { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH, pluginPkgPath: PLUGIN_PKG_PATH, pluginSettingsDir: PLUGIN_SETTINGS_DIR, pluginSettingsPath: PLUGIN_SETTINGS_PATH, projectMcpPath: PROJECT_MCP_PATH, runScriptPath: RUN_SCRIPT_PATH, runScriptSource: RUN_SCRIPT_SOURCE },\n { sessionDir: SESSION_DIR_2, pluginPath: PLUGIN_PATH_2, pluginPkgPath: PLUGIN_PKG_PATH_2, pluginSettingsDir: PLUGIN_SETTINGS_DIR_2, pluginSettingsPath: PLUGIN_SETTINGS_PATH_2, projectMcpPath: PROJECT_MCP_PATH_2, runScriptPath: RUN_SCRIPT_PATH_2, runScriptSource: RUN_SCRIPT_SOURCE_2 },\n { sessionDir: SESSION_DIR_3, pluginPath: PLUGIN_PATH_3, pluginPkgPath: PLUGIN_PKG_PATH_3, pluginSettingsDir: PLUGIN_SETTINGS_DIR_3, pluginSettingsPath: PLUGIN_SETTINGS_PATH_3, projectMcpPath: PROJECT_MCP_PATH_3, runScriptPath: RUN_SCRIPT_PATH_3, runScriptSource: RUN_SCRIPT_SOURCE_3 },\n { sessionDir: SESSION_DIR_4, pluginPath: PLUGIN_PATH_4, pluginPkgPath: PLUGIN_PKG_PATH_4, pluginSettingsDir: PLUGIN_SETTINGS_DIR_4, pluginSettingsPath: PLUGIN_SETTINGS_PATH_4, projectMcpPath: PROJECT_MCP_PATH_4, runScriptPath: RUN_SCRIPT_PATH_4, runScriptSource: RUN_SCRIPT_SOURCE_4 },\n];\n\nfunction writePluginFiles(): void {\n for (const c of CHANNELS) {\n mkdirSync(c.sessionDir, { recursive: true });\n mkdirSync(c.pluginSettingsDir, { recursive: true });\n // Channel plugin source lives in the Docker image only (IP protection).\n // Workers inside the container use /app/channel-plugin.ts directly.\n writeFileSync(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, 'utf-8');\n writeFileSync(\n c.pluginSettingsPath,\n JSON.stringify({\n fastMode: true,\n enabledMcpjsonServers: ['synkro-local'],\n }, null, 2) + '\\n',\n 'utf-8',\n );\n writeFileSync(c.runScriptPath, c.runScriptSource, 'utf-8');\n chmodSync(c.runScriptPath, 0o755);\n }\n}\n\nfunction runBunInstall(): void {\n for (const c of CHANNELS) {\n const r = spawnSync('bun', ['install', '--silent'], {\n cwd: c.sessionDir,\n encoding: 'utf-8',\n timeout: 120_000,\n });\n if (r.status !== 0) {\n throw new LocalCCInstallError(\n `bun install failed in ${c.sessionDir}: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n }\n}\n\ninterface ClaudeJson {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n projects?: Record<string, Record<string, unknown>>;\n [k: string]: unknown;\n}\n\n/**\n * Safely apply `mutator` to ~/.claude.json with five layers of protection:\n * 1. Refuse to operate on malformed input — never try to \"fix\" a corrupted file.\n * 2. Take a rolling backup at ~/.claude.json.synkro-bak before any write.\n * 3. Validate the post-mutation result is still well-formed JSON and didn't\n * lose any top-level keys that existed in the original.\n * 4. Atomic write via tmp + fsync + rename(2) (POSIX-atomic).\n * 5. Restore from backup on any error in the write phase.\n *\n * If the file doesn't exist (no claude install on this machine), the mutation\n * is silently skipped — we don't create the file, since claude owns it.\n */\nfunction safelyMutateClaudeJson(mutator: (json: ClaudeJson) => boolean): void {\n if (!existsSync(CLAUDE_JSON_PATH)) {\n // claude.json doesn't exist; nothing to mutate. We don't create it.\n return;\n }\n\n // (1) Read + parse, refuse to proceed on malformed input.\n const originalText = readFileSync(CLAUDE_JSON_PATH, 'utf-8');\n let parsed: ClaudeJson;\n try {\n parsed = JSON.parse(originalText) as ClaudeJson;\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to modify malformed ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Please fix the JSON manually before retrying.`,\n err,\n );\n }\n\n const originalKeys = new Set(Object.keys(parsed));\n\n // (Mutator returns true if it changed anything; false means no-op.)\n const dirty = mutator(parsed);\n if (!dirty) return;\n\n // (3a) Validate the in-memory mutation didn't drop a top-level key.\n for (const k of originalKeys) {\n if (!(k in parsed)) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: mutator dropped top-level key \"${k}\". ` +\n `This is a bug — please report.`,\n );\n }\n }\n\n const newText = JSON.stringify(parsed, null, 2) + '\\n';\n\n // (3b) Round-trip parse to guarantee well-formed output before any write.\n try {\n JSON.parse(newText);\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: serialized result is not valid JSON. ` +\n `This is a bug — please report.`,\n err,\n );\n }\n\n // (2) Take a rolling backup of the *original* (pre-mutation) bytes.\n copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);\n\n // (4) Atomic write: tmp + fsync + rename. This guarantees a reader\n // never sees a half-written file — they see either the old or new.\n const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;\n try {\n writeFileSync(tmpPath, newText, 'utf-8');\n // fsync the tmp file so the bytes are durably on disk before rename.\n const fd = openSync(tmpPath, 'r');\n try { fsyncSync(fd); } finally { closeSync(fd); }\n renameSync(tmpPath, CLAUDE_JSON_PATH);\n } catch (err) {\n // (5) Best-effort recovery: restore the original from backup.\n try { unlinkSync(tmpPath); } catch { /* ignore */ }\n try { copyFileSync(CLAUDE_JSON_BACKUP_PATH, CLAUDE_JSON_PATH); } catch { /* ignore */ }\n throw new LocalCCInstallError(\n `failed to write ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Backup at ${CLAUDE_JSON_BACKUP_PATH} preserves the prior state.`,\n err,\n );\n }\n}\n\n/**\n * Register the synkro-local MCP server at PROJECT scope (.mcp.json inside\n * ~/.synkro/cc_sessions) — NOT at user scope in ~/.claude.json. User scope\n * would make every claude session on the machine spawn the plugin and race\n * to bind the UDS, which corrupts request routing.\n */\nfunction writeProjectMcpJson(): void {\n for (const c of CHANNELS) {\n const mcp = {\n mcpServers: {\n [MCP_SERVER_NAME]: {\n command: 'bun',\n args: [c.pluginPath],\n },\n },\n };\n writeFileSync(c.projectMcpPath, JSON.stringify(mcp, null, 2) + '\\n', 'utf-8');\n }\n}\n\n/**\n * Pre-accept the workspace trust dialog and approve the project-scoped MCP\n * server in ~/.claude.json so claude doesn't block on those prompts under\n * tmux. Also strips any stale top-level mcpServers[\"synkro-local\"] left over\n * from previous (incorrect) installs.\n *\n * All mutations go through safelyMutateClaudeJson — see that function for\n * the integrity guarantees.\n */\nfunction patchClaudeJson(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n\n // Remove any stale user-scope registration from previous installs.\n if (parsed.mcpServers && typeof parsed.mcpServers === 'object' && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n\n if (!parsed.projects || typeof parsed.projects !== 'object') {\n parsed.projects = {};\n }\n const projects = parsed.projects as Record<string, Record<string, unknown>>;\n\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n const existing = projects[dir] && typeof projects[dir] === 'object'\n ? projects[dir]\n : {};\n const wantEnabled = Array.from(new Set([\n ...((existing.enabledMcpjsonServers as string[] | undefined) ?? []),\n MCP_SERVER_NAME,\n ]));\n const next = {\n ...existing,\n hasTrustDialogAccepted: true,\n hasCompletedProjectOnboarding: true,\n enabledMcpjsonServers: wantEnabled,\n };\n if (\n existing.hasTrustDialogAccepted !== true ||\n existing.hasCompletedProjectOnboarding !== true ||\n JSON.stringify(existing.enabledMcpjsonServers ?? []) !== JSON.stringify(wantEnabled)\n ) {\n projects[dir] = next;\n dirty = true;\n }\n }\n return dirty;\n });\n}\n\n/**\n * Run the full local-CC install. Throws LocalCCInstallError on any failure\n * the caller is expected to surface. Does NOT start the pueue task — call\n * `pueue.ensureRunning()` after for that.\n */\nexport function installLocalCC(): { sessionDir: string; pluginPath: string } {\n // bun is required for the plugin runtime — auto-install if missing.\n let bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n if (process.platform === 'darwin') {\n console.log(' Installing bun via brew...');\n const brewR = spawnSync('brew', ['install', 'oven-sh/bun/bun'], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n if (brewR.status !== 0) {\n throw new LocalCCInstallError('bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash');\n }\n bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n throw new LocalCCInstallError('bun installed but not found on PATH. Restart your terminal and re-run install.');\n }\n } else {\n throw new LocalCCInstallError('bun is required. Install it: curl -fsSL https://bun.sh/install | bash');\n }\n }\n\n writePluginFiles();\n runBunInstall();\n writeProjectMcpJson();\n patchClaudeJson();\n\n return { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH };\n}\n\n/**\n * Strip every trace of local-cc from ~/.claude.json:\n * - user-scope mcpServers[\"synkro-local\"] (legacy from older installs)\n * - projects[SESSION_DIR] entry (workspace trust + enabledMcpjsonServers we added)\n *\n * Goes through safelyMutateClaudeJson for the same integrity guarantees as\n * patchClaudeJson — atomic write, backup, post-write validation. Errors are\n * surfaced so the caller sees them, not silently swallowed.\n *\n * Plugin files in ~/.synkro/cc_sessions/ are removed by the caller via\n * `rm -rf ~/.synkro` when uninstalling with --purge.\n */\nexport function uninstallLocalCC(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n if (parsed.mcpServers && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n if (parsed.projects && typeof parsed.projects === 'object' && parsed.projects[dir]) {\n delete parsed.projects[dir];\n dirty = true;\n }\n }\n return dirty;\n });\n}\n","// :)\n/**\n * synkro disconnect — remove all Synkro hook entries from agent settings.\n *\n * Preserves any other hooks the user has (Corridor, Noma, custom).\n *\n * Normal uninstall removes everything — hooks, configs, container, image, and\n * ~/.synkro — EXCEPT the user's scan data (~/.synkro/pgdata and pgdata-backups).\n * `--purge` wipes that data too, after an explicit typed confirmation, leaving\n * the machine as if Synkro was never installed.\n *\n * Order matters: tear down LIVE runtime first (pueue task + tmux session +\n * spawned bun plugin), THEN strip config, THEN remove files. Reversing the\n * order leaves orphaned processes holding the TCP port and a tmux session\n * with claude running from a deleted cwd.\n */\nimport { existsSync, rmSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { uninstallMcpConfig, uninstallCursorMcpConfig } from '../installer/mcpConfig.js';\nimport { uninstallLocalCC } from '../local-cc/install.js';\nimport { dockerRemove, dockerStatus, dockerSafeStop, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nasync function tearDownLocalCC(): Promise<void> {\n const docker = dockerStatus();\n if (docker.running) {\n await dockerSafeStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n dockerRemove();\n console.log('✓ removed synkro-server container');\n\n // Remove the Docker image too — a later `synkro install` just re-pulls it.\n try {\n const image = imageTag();\n const r = spawnSync('docker', ['rmi', '-f', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(r.status === 0 ? `✓ removed Docker image ${image}` : '· no Docker image to remove');\n } catch { /* docker may not be installed */ }\n\n if (needsKeychainBridge()) {\n try { uninstallRefreshAgent(); console.log('✓ launchd refresh agent removed'); } catch { /* best-effort */ }\n }\n\n uninstallLocalCC();\n console.log('✓ cleaned ~/.claude.json entries');\n}\n\nfunction confirmPurge(): Promise<boolean> {\n console.log('⚠ WARNING — synkro uninstall --purge');\n console.log(' This permanently deletes ALL Synkro data on this machine,');\n console.log(' including every scan finding, telemetry record, and backup in');\n console.log(' ~/.synkro/pgdata and ~/.synkro/pgdata-backups. It cannot be undone.\\n');\n if (!process.stdin.isTTY) {\n console.log(' Non-interactive shell — re-run in a terminal to confirm.');\n return Promise.resolve(false);\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\" Type 'yes' to wipe everything (anything else cancels): \", (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'yes');\n });\n });\n}\n\nexport async function disconnectCommand(args: string[] = []): Promise<void> {\n const purge = args.includes('--purge');\n\n if (purge && !(await confirmPurge())) {\n console.log('\\nAborted — nothing was removed.');\n return;\n }\n\n console.log('\\nSynkro uninstall starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n await tearDownLocalCC();\n\n const agents = detectAgents();\n let sawClaudeCode = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n sawClaudeCode = true;\n const removed = uninstallCCHooks(agent.settingsPath);\n console.log(`${removed ? '✓' : '·'} ${agent.name}: ${removed ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n } else if (agent.kind === 'cursor') {\n const removed = uninstallCursorHooks(agent.settingsPath);\n console.log(`${removed ? '✓' : '·'} ${agent.name}: ${removed ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n }\n }\n\n // Also remove the Synkro guardrails MCP server entries from config files\n // (this is the OTHER mcp server — the rule-suggestions one — distinct from\n // the local-cc channel server cleaned up above).\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails (CC): ${mcpRemoved ? 'removed from ~/.claude.json' : 'no entry found'}`);\n }\n {\n const cursorMcpRemoved = uninstallCursorMcpConfig();\n console.log(`${cursorMcpRemoved ? '✓' : '·'} MCP guardrails (Cursor): ${cursorMcpRemoved ? 'removed from ~/.cursor/mcp.json' : 'no entry found'}`);\n }\n\n // Filesystem cleanup. Normal uninstall removes everything except the user's\n // scan data (pgdata + pgdata-backups); --purge removes that too.\n if (existsSync(SYNKRO_DIR)) {\n if (purge) {\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n console.log(`✓ wiped ${SYNKRO_DIR} entirely — including all scan data and backups`);\n } else {\n const keep = new Set([join(SYNKRO_DIR, 'pgdata'), join(SYNKRO_DIR, 'pgdata-backups')]);\n const preserved: string[] = [];\n for (const entry of readdirSync(SYNKRO_DIR)) {\n const full = join(SYNKRO_DIR, entry);\n if (keep.has(full)) { preserved.push(entry); continue; }\n rmSync(full, { recursive: true, force: true });\n }\n if (preserved.length > 0) {\n console.log(`✓ removed Synkro config from ${SYNKRO_DIR} (kept your scan data: ${preserved.join(', ')})`);\n console.log(' run `synkro uninstall --purge` to delete that too');\n } else {\n console.log(`✓ removed ${SYNKRO_DIR}`);\n }\n }\n } else {\n console.log(`· ${SYNKRO_DIR} already gone`);\n }\n\n console.log(purge\n ? '\\nSynkro fully removed — this machine is clean, as if it was never installed.'\n : '\\nSynkro uninstalled. Your scan data is preserved.');\n}\n","/**\n * JSONL log of every channel turn (grade / classify) — one entry per line.\n *\n * Written by `submitToChannel` after each request completes (success or fail).\n * Read by `synkro local-cc logs` to show recent activity.\n *\n * Best-effort: any I/O error here is swallowed so logging never breaks the\n * actual grading path.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, closeSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { LocalCCRole } from './prompts.js';\n\nexport const TURN_LOG_PATH = join(homedir(), '.synkro', 'cc_sessions', 'turns.log');\n\nexport type TurnStatus = 'ok' | 'timeout' | 'error';\n\nexport interface TurnEntry {\n /** ISO timestamp of when the request started. */\n ts: string;\n role: LocalCCRole;\n /** Wall-clock ms from submit to result/error. */\n duration_ms: number;\n status: TurnStatus;\n /** Truncated request payload (first ~400 chars of caller-supplied text). */\n request_preview: string;\n /** Truncated response result (first ~400 chars). Empty on non-ok. */\n response_preview: string;\n /** Parsed severity if we can extract one from the verdict. Optional. */\n severity?: string;\n /** Error message if status is timeout/error. */\n error?: string;\n}\n\nconst PREVIEW_MAX = 400;\n\nfunction truncate(s: string, max = PREVIEW_MAX): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + '… [+' + (s.length - max) + ' chars]';\n}\n\n/** Best-effort extraction of severity from the result text. */\nfunction extractSeverity(result: string): string | undefined {\n // <synkro-verdict>{\"severity\":\"audit\",…}</synkro-verdict>\n const m = result.match(/<synkro-(?:verdict|intent)>([\\s\\S]*?)<\\/synkro-(?:verdict|intent)>/);\n if (!m) return undefined;\n try {\n const obj = JSON.parse(m[1]) as { severity?: string; verdict?: string; type?: string; ok?: boolean };\n if (obj.severity) return String(obj.severity);\n if (typeof obj.ok === 'boolean') return obj.ok ? 'ok' : 'violations';\n if (obj.type) return String(obj.type);\n if (obj.verdict) return String(obj.verdict);\n } catch {\n // not parseable JSON inside the tag — ignore\n }\n return undefined;\n}\n\n/** Append one turn to the JSONL log. Best-effort — never throws. */\nexport function appendTurn(args: {\n startedAt: number;\n role: LocalCCRole;\n request: string;\n result?: string;\n status: TurnStatus;\n error?: string;\n}): void {\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n const entry: TurnEntry = {\n ts: new Date(args.startedAt).toISOString(),\n role: args.role,\n duration_ms: Date.now() - args.startedAt,\n status: args.status,\n request_preview: truncate(args.request),\n response_preview: args.result ? truncate(args.result) : '',\n severity: args.result ? extractSeverity(args.result) : undefined,\n error: args.error,\n };\n appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // never let logging fail the call site\n }\n}\n\n/**\n * Read the most recent N turn entries (newest first). Returns an empty array\n * on any error.\n */\nexport function readRecentTurns(n = 20): TurnEntry[] {\n if (!existsSync(TURN_LOG_PATH)) return [];\n try {\n // For typical sizes this is fine; if the file grows huge we'd switch to\n // tail-style reverse reading. Cap by stat size for safety.\n const size = statSync(TURN_LOG_PATH).size;\n if (size === 0) return [];\n const text = readFileSync(TURN_LOG_PATH, 'utf-8');\n const lines = text.split('\\n').filter(Boolean);\n const lastN = lines.slice(-n).reverse();\n return lastN\n .map(line => {\n try { return JSON.parse(line) as TurnEntry; } catch { return null; }\n })\n .filter((x): x is TurnEntry => x !== null);\n } catch {\n return [];\n }\n}\n\n/**\n * Tail the turn log: invoke `onEntry` for each new entry appended after the\n * call. Handles file truncation/rotation by re-syncing offset to 0 when size\n * shrinks. Returns a `stop()` to detach the watcher.\n */\nexport function followTurns(onEntry: (t: TurnEntry) => void): () => void {\n // Make sure the file exists so watchFile has something to poll. Creating\n // an empty file is harmless if it already exists.\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n if (!existsSync(TURN_LOG_PATH)) {\n appendFileSync(TURN_LOG_PATH, '', 'utf-8');\n }\n } catch {\n // best-effort\n }\n\n let lastSize = (() => {\n try { return statSync(TURN_LOG_PATH).size; } catch { return 0; }\n })();\n let pendingPartial = ''; // bytes after the last \\n if a write split on a line boundary\n\n const drainNewBytes = (from: number, to: number): void => {\n if (to <= from) return;\n let fd: number | null = null;\n try {\n fd = openSync(TURN_LOG_PATH, 'r');\n const len = to - from;\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, from);\n const text = pendingPartial + buf.toString('utf-8');\n const lastNewline = text.lastIndexOf('\\n');\n if (lastNewline === -1) {\n pendingPartial = text;\n return;\n }\n const complete = text.slice(0, lastNewline);\n pendingPartial = text.slice(lastNewline + 1);\n for (const line of complete.split('\\n')) {\n if (!line) continue;\n try {\n onEntry(JSON.parse(line) as TurnEntry);\n } catch {\n // skip malformed lines\n }\n }\n } catch {\n // best-effort\n } finally {\n if (fd !== null) {\n try { closeSync(fd); } catch { /* noop */ }\n }\n }\n };\n\n // Poll-based watcher (cross-platform reliable). 250ms is responsive\n // enough for tail-f UX without burning CPU.\n watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {\n if (curr.size < lastSize) {\n // file truncated/rotated — reset.\n lastSize = 0;\n pendingPartial = '';\n }\n if (curr.size > lastSize) {\n drainNewBytes(lastSize, curr.size);\n lastSize = curr.size;\n }\n });\n\n return () => unwatchFile(TURN_LOG_PATH);\n}\n","/**\n * Client for the local-CC channel plugin.\n *\n * POSTs to a TCP port on 127.0.0.1 exposed by the channel plugin running\n * inside the pueue-managed `claude` process. Returns the raw text Claude\n * wrote into the `reply` tool's `result` argument.\n */\n\nimport { request as httpRequest } from 'node:http';\nimport { connect } from 'node:net';\nimport type { LocalCCRole } from './prompts.js';\nimport { appendTurn } from './turnLog.js';\n\nexport const CHANNEL_HOST = '127.0.0.1';\nexport const CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || '8929', 10);\n\nconst DEFAULT_TIMEOUT_MS = 90_000;\n\nexport class LocalCCError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCError';\n }\n}\n\nexport interface SubmitOptions {\n /** Timeout for the round-trip in ms (default 90s). */\n timeoutMs?: number;\n /** Override the channel port (default: CHANNEL_PORT / 8929). */\n port?: number;\n}\n\n/**\n * Send a request through the channel and wait for Claude's `reply` tool output.\n * Throws LocalCCError if the channel isn't reachable, the channel times out, or\n * the plugin returns a non-2xx status.\n */\nexport async function submitToChannel(\n role: LocalCCRole,\n payload: string,\n opts: SubmitOptions = {},\n): Promise<string> {\n const body = JSON.stringify({ role, payload, content: payload });\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const port = opts.port ?? CHANNEL_PORT;\n const startedAt = Date.now();\n\n try {\n const result = await new Promise<string>((resolve, reject) => {\n const req = httpRequest({\n host: CHANNEL_HOST,\n port,\n method: 'POST',\n path: '/submit',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n timeout: timeoutMs,\n }, res => {\n const chunks: Buffer[] = [];\n res.on('data', c => chunks.push(c));\n res.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (res.statusCode !== 200) {\n reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));\n return;\n }\n try {\n const parsed = JSON.parse(text) as { result?: string; error?: string };\n if (parsed.error) {\n reject(new LocalCCError(parsed.error));\n return;\n }\n resolve(String(parsed.result ?? ''));\n } catch (err) {\n reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));\n }\n });\n });\n req.on('timeout', () => {\n req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));\n });\n req.on('error', err => {\n const msg = (err as NodeJS.ErrnoException).code === 'ECONNREFUSED'\n ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)`\n : `channel request failed: ${(err as Error).message}`;\n reject(new LocalCCError(msg, err));\n });\n req.write(body);\n req.end();\n });\n appendTurn({ startedAt, role, request: payload, result, status: 'ok' });\n return result;\n } catch (err) {\n const message = (err as Error).message ?? String(err);\n const status = /timed out/i.test(message) ? 'timeout' : 'error';\n appendTurn({ startedAt, role, request: payload, status, error: message });\n throw err;\n }\n}\n\n/** Quick TCP probe — true if anything is listening on the given port (default: CHANNEL_PORT). */\nexport function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, CHANNEL_HOST);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n","/**\n * `synkro grade <edit|bash>` — replacement for the legacy\n * ~/.synkro/bin/grader_daemon.py. Reads a grading payload from stdin,\n * routes it through the local-CC channel, and writes the raw verdict\n * (including the <synkro-verdict>...</synkro-verdict> wrapper that the\n * shell hook scripts grep for) to stdout.\n *\n * Failures print to stderr and exit non-zero so the hook scripts fall\n * through to the cloud fast-tier path cleanly.\n */\n\nimport { submitToChannel, LocalCCError } from '../local-cc/client.js';\nimport type { LocalCCRole } from '../local-cc/prompts.js';\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', c => chunks.push(c));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nexport async function gradeCommand(args: string[]): Promise<void> {\n const mode = args[0] ?? '';\n let role: LocalCCRole;\n if (mode === 'edit') role = 'grade-edit';\n else if (mode === 'bash') role = 'grade-bash';\n else if (mode === 'plan') role = 'grade-plan';\n else if (mode === 'cwe') role = 'grade-cwe';\n else {\n console.error('Usage: synkro grade <edit|bash|plan|cwe>');\n process.exit(2);\n }\n\n const payload = await readStdin();\n if (!payload.trim()) {\n console.error('synkro grade: empty stdin');\n process.exit(2);\n }\n\n try {\n const result = await submitToChannel(role, payload, { timeoutMs: 60_000 });\n process.stdout.write(result);\n if (!result.endsWith('\\n')) process.stdout.write('\\n');\n } catch (err) {\n if (err instanceof LocalCCError) {\n console.error(`synkro grade: ${err.message}`);\n } else {\n console.error(`synkro grade: ${(err as Error).message}`);\n }\n process.exit(3);\n }\n}\n","/**\n * Pueue lifecycle for the local-CC `claude` process.\n *\n * One labelled pueue task per user, started at install (or on demand) and\n * managed via the `pueue` CLI. We assume `pueue` and `pueued` are installed\n * and the daemon is running — surface a friendly error otherwise.\n */\n\nimport { execFileSync, spawnSync, spawn } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { connect } from 'node:net';\n\nexport const TASK_LABEL = 'synkro-local-cc';\nexport const TMUX_SESSION = 'synkro-local-cc';\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\n\nexport const TASK_LABEL_2 = 'synkro-local-cc-2';\nexport const TMUX_SESSION_2 = 'synkro-local-cc-2';\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\n\nexport const TASK_LABEL_3 = 'synkro-local-cc-3';\nexport const TMUX_SESSION_3 = 'synkro-local-cc-3';\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\n\nexport const TASK_LABEL_4 = 'synkro-local-cc-4';\nexport const TMUX_SESSION_4 = 'synkro-local-cc-4';\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\n\nexport class PueueError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'PueueError';\n }\n}\n\ninterface PueueStatusEntry {\n id: number;\n label?: string;\n status: string | { Running?: unknown; Done?: { result?: string } };\n command: string;\n path: string;\n}\n\ninterface PueueStatus {\n tasks: Record<string, PueueStatusEntry>;\n}\n\nfunction pueueAvailable(): void {\n const r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.');\n }\n}\n\nfunction statusJson(): PueueStatus {\n pueueAvailable();\n const r = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || 'unknown error'} — is pueued running?`);\n }\n try {\n return JSON.parse(r.stdout) as PueueStatus;\n } catch (err) {\n throw new PueueError(`pueue status returned non-JSON output: ${r.stdout.slice(0, 200)}`, err);\n }\n}\n\nexport interface TaskInfo {\n id: number;\n label: string;\n status: string;\n command: string;\n cwd: string;\n}\n\nfunction statusName(s: PueueStatusEntry['status']): string {\n if (typeof s === 'string') return s;\n if (s && typeof s === 'object') {\n if ('Running' in s) return 'Running';\n if ('Done' in s) {\n const result = (s as { Done?: { result?: string | object } }).Done?.result;\n if (typeof result === 'string') return `Done (${result})`;\n if (result && typeof result === 'object') return `Done (${Object.keys(result)[0] ?? 'unknown'})`;\n return 'Done';\n }\n return Object.keys(s)[0] ?? 'unknown';\n }\n return 'unknown';\n}\n\nexport interface ChannelIdentity {\n taskLabel: string;\n tmuxSession: string;\n sessionDir: string;\n}\n\nexport const CHANNEL_PRIMARY: ChannelIdentity = { taskLabel: TASK_LABEL, tmuxSession: TMUX_SESSION, sessionDir: SESSION_DIR };\nexport const CHANNEL_SECONDARY: ChannelIdentity = { taskLabel: TASK_LABEL_2, tmuxSession: TMUX_SESSION_2, sessionDir: SESSION_DIR_2 };\nexport const CHANNEL_TERTIARY: ChannelIdentity = { taskLabel: TASK_LABEL_3, tmuxSession: TMUX_SESSION_3, sessionDir: SESSION_DIR_3 };\nexport const CHANNEL_QUATERNARY: ChannelIdentity = { taskLabel: TASK_LABEL_4, tmuxSession: TMUX_SESSION_4, sessionDir: SESSION_DIR_4 };\nexport const ALL_CHANNELS: readonly ChannelIdentity[] = [CHANNEL_PRIMARY, CHANNEL_SECONDARY, CHANNEL_TERTIARY, CHANNEL_QUATERNARY];\n\n/** Find a synkro local-cc task by label. Returns null if absent. */\nexport function findTask(channel: ChannelIdentity = CHANNEL_PRIMARY): TaskInfo | null {\n const data = statusJson();\n for (const [id, t] of Object.entries(data.tasks)) {\n if (t.label === channel.taskLabel) {\n return {\n id: Number(id),\n label: t.label,\n status: statusName(t.status),\n command: t.command,\n cwd: t.path,\n };\n }\n }\n return null;\n}\n\n/** True if a synkro local-cc task is currently Running. */\nexport function isRunning(channel: ChannelIdentity = CHANNEL_PRIMARY): boolean {\n const t = findTask(channel);\n return t?.status === 'Running';\n}\n\nexport interface StartOptions {\n /** Override the channel plugin script path. */\n pluginPath?: string;\n /** Working directory for the claude process. */\n cwd?: string;\n /** Which channel to start (default: primary). */\n channel?: ChannelIdentity;\n}\n\n/**\n * Add a fresh pueue task for the local-cc claude session. Removes any prior\n * task with the same label first (Done or otherwise) so subsequent calls\n * always replace cleanly.\n */\nexport function startTask(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const cwd = opts.cwd ?? ch.sessionDir;\n // Remove ALL tasks with this label (not just the first) to clear stale duplicates\n let existing = findTask(ch);\n while (existing) {\n if (existing.status === 'Running' || existing.status === 'Queued') {\n spawnSync('tmux', ['kill-session', '-t', `=${ch.tmuxSession}`], { encoding: 'utf-8' });\n spawnSync('pueue', ['kill', String(existing.id)], { encoding: 'utf-8' });\n for (let i = 0; i < 10; i++) {\n const check = findTask(ch);\n if (!check || check.id !== existing.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(existing.id)], { encoding: 'utf-8' });\n existing = findTask(ch);\n }\n\n const runScript = join(cwd, 'run-claude.sh');\n const args = [\n 'add',\n '--label', ch.taskLabel,\n '--working-directory', cwd,\n '--',\n 'bash', runScript,\n ];\n\n const r = spawnSync('pueue', args, { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);\n }\n const created = findTask(ch);\n if (!created) {\n throw new PueueError(`pueue add succeeded but no task with label ${ch.taskLabel} found`);\n }\n return created;\n}\n\n/** Stop and remove the synkro local-cc task. Also kills the tmux session\n * that owns the underlying claude process. No-op if neither is present. */\nexport function stopTask(channel: ChannelIdentity = CHANNEL_PRIMARY): void {\n spawnSync('tmux', ['kill-session', '-t', `=${channel.tmuxSession}`], { encoding: 'utf-8' });\n // Remove ALL tasks with this label, waiting for each kill to land\n let t = findTask(channel);\n while (t) {\n if (t.status === 'Running' || t.status === 'Queued') {\n spawnSync('pueue', ['kill', String(t.id)], { encoding: 'utf-8' });\n // Wait for the task to leave Running state before removing\n for (let i = 0; i < 10; i++) {\n const check = findTask(channel);\n if (!check || check.id !== t.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(t.id)], { encoding: 'utf-8' });\n t = findTask(channel);\n }\n}\n\n/** Print the last N log lines for the task. Returns the raw text. */\nexport function tailLogs(lines = 80, channel: ChannelIdentity = CHANNEL_PRIMARY): string {\n const t = findTask(channel);\n if (!t) return `(no ${channel.taskLabel} task)`;\n const r = spawnSync('pueue', ['log', '--lines', String(lines), String(t.id)], { encoding: 'utf-8' });\n return r.stdout || r.stderr || '(no output)';\n}\n\n/** Ensure the task is running; start it if not. Returns the task info. */\nexport function ensureRunning(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const t = findTask(ch);\n if (t && t.status === 'Running') return t;\n return startTask(opts);\n}\n\n/** Probe a TCP port — true if something accepts a connection within timeoutMs. */\nfunction probePort(host: string, port: number, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, host);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n\n/** Dismiss startup prompts in the tmux session. Best-effort.\n * Sends '1' (dev-channels accept) then Enter (other prompts). */\nfunction tmuxDismissPrompts(tmuxSession: string = TMUX_SESSION): void {\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, '1'], { encoding: 'utf-8' });\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, 'Enter'], { encoding: 'utf-8' });\n}\n\n/**\n * Wait up to `timeoutMs` for the channel TCP listener to come up. Each tick\n * the probe fails, send an Enter into the tmux session — that walks claude\n * past any startup confirmation dialogs (workspace trust, dev-channels,\n * MCP consent). The loop self-terminates the moment the port binds, so\n * we don't keep spamming Enters once claude is at the live `❯` prompt.\n */\nexport async function waitForChannelReady(\n port: number,\n timeoutMs = 60_000,\n host = '127.0.0.1',\n tmuxSession: string = TMUX_SESSION,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await probePort(host, port)) return true;\n tmuxDismissPrompts(tmuxSession);\n await new Promise(r => setTimeout(r, 1000));\n }\n return probePort(host, port);\n}\n\nfunction brewInstall(pkg: string): boolean {\n const brew = spawnSync('brew', ['--version'], { encoding: 'utf-8' });\n if (brew.status !== 0) return false;\n console.log(` Installing ${pkg} via brew...`);\n const r = spawnSync('brew', ['install', pkg], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n return r.status === 0;\n}\n\n/** Ensure pueue is installed and pueued daemon is running. Auto-installs on macOS. */\nexport function assertPueueInstalled(): void {\n let r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('pueue')) {\n r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('pueue install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue');\n }\n }\n // Ensure pueued daemon is running\n const status = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (status.status !== 0) {\n console.log(' Starting pueued daemon...');\n const child = spawn('pueued', ['-d'], { stdio: 'ignore', detached: true });\n child.unref();\n // Give the daemon a moment to bind its socket\n spawnSync('sleep', ['1']);\n const retry = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (retry.status !== 0) {\n throw new PueueError('pueue daemon not reachable after starting pueued. Check `pueued` manually.');\n }\n }\n spawnSync('pueue', ['parallel', '2'], { encoding: 'utf-8' });\n}\n\n/** Ensure claude CLI is installed. */\nexport function assertClaudeInstalled(): void {\n const r = spawnSync('claude', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code');\n }\n}\n\n/** Ensure tmux is installed. Auto-installs on macOS. */\nexport function assertTmuxInstalled(): void {\n let r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('tmux')) {\n r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('tmux install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)');\n }\n }\n}\n\n/** No-throw helper used by the install command for status messages. */\nexport function readVersion(bin: string): string | null {\n try {\n return execFileSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {\n return null;\n }\n}\n","/**\n * Inference provider detection.\n *\n * The source of truth is the user's inference_settings in TimescaleDB.\n * At install time, `/api/v1/cli/me` returns `local_inference: true` when\n * `gradingProvider = 'claude-code'`. The install flow writes\n * `SYNKRO_LOCAL_INFERENCE='yes'` into ~/.synkro/config.env.\n *\n * At runtime the hook scripts use a TCP probe (synkro_channel_up) to decide\n * routing — this module is only used by CLI commands and intent routers.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nexport function isLocalCCEnabled(): boolean {\n if (!existsSync(CONFIG_PATH)) return false;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);\n return match?.[1] === 'yes';\n } catch {\n return false;\n }\n}\n","/**\n * `synkro local-cc <subcommand>` — manage the local pueue-backed Claude Code\n * session that powers grading and intent classification when the inference\n * provider toggle is set to local-cc.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { installLocalCC, SESSION_DIR, PLUGIN_PATH, RUN_SCRIPT_PATH, PLUGIN_SETTINGS_PATH, CLAUDE_JSON_PATH, TMUX_SESSION_NAME, CHANNEL_2_PORT, TMUX_SESSION_NAME_2 } from '../local-cc/install.js';\nimport { readRecentTurns, followTurns, TURN_LOG_PATH, type TurnEntry } from '../local-cc/turnLog.js';\nimport {\n assertClaudeInstalled,\n assertPueueInstalled,\n assertTmuxInstalled,\n ensureRunning,\n findTask,\n startTask,\n stopTask,\n tailLogs,\n waitForChannelReady,\n CHANNEL_PRIMARY,\n CHANNEL_SECONDARY,\n} from '../local-cc/pueue.js';\nimport { isLocalCCEnabled } from '../local-cc/settings.js';\nimport { refreshCreds, needsKeychainBridge, credsAreStale, CLAUDE_CREDS_FILE } from '../local-cc/macKeychain.js';\nimport { dockerStatus, dockerStop, dockerUpdate, waitForContainerReady, waitForWorkersReady, resolveWorkerConfig } from '../local-cc/dockerInstall.js';\nimport { reconcileHarness, syncSkillFiles } from './install.js';\nimport { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from 'node:fs';\n\nconst SYNKRO_CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\n/**\n * Resolution order matches commands/install.ts: live env var first, then the\n * persisted config.env value, then 'bare-host'. Kept inline rather than\n * imported from install.ts so this module stays loadable when the install\n * command tree is pruned in future cleanups.\n */\nfunction deploymentMode(): 'bare-host' | 'docker' {\n const env = (process.env.SYNKRO_DEPLOYMENT_MODE || '').toLowerCase();\n if (env === 'docker') return 'docker';\n if (env === 'bare-host') return 'bare-host';\n try {\n if (fsExistsSync(SYNKRO_CONFIG_PATH)) {\n const m = fsReadFileSync(SYNKRO_CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n if (m && m[1] === 'docker') return 'docker';\n }\n } catch { /* fall through */ }\n return 'bare-host';\n}\n\nfunction inDockerMode(): boolean { return deploymentMode() === 'docker'; }\nimport { submitToChannel, isChannelAvailable, CHANNEL_HOST, CHANNEL_PORT } from '../local-cc/client.js';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\n\nfunction printHelp(): void {\n console.log(`synkro local-cc — manage the local Claude Code inference session\n\nOVERVIEW\n Routes Synkro's grading and intent-classification work through a long-running\n Claude Code session on this machine instead of remote Inngest+Gemini.\n\n When enabled, three call sites switch over:\n • security grading on edit/bash hooks\n • intent classification (agent input → structured intent)\n • remediate intent classification\n\n The session is hosted in a detached tmux session managed by a pueue task.\n The CLI talks to it via Claude Code's channels API: a Bun MCP plugin that\n pushes events into the session and receives Claude's responses through a\n \\`reply\\` MCP tool.\n\nUSAGE\n synkro local-cc <subcommand> [args]\n\nSUBCOMMANDS\n enable Install plugin, start pueue task, flip toggle to local-cc\n disable Flip toggle back to inngest (pueue task left running)\n status Show provider toggle, pueue task state, channel reachability\n start Idempotently bring up the pueue task + wait for the channel\n stop Kill the tmux session and remove the pueue task\n restart stop, then start\n install Regenerate ~/.synkro/cc_sessions/ files (plugin, runner, settings)\n logs [N] [--raw] [--live]\n Show the last N (default 20) channel turns: when, role,\n duration, severity, request preview.\n --raw / -r include full request/response payloads\n --live / -f tail the log; print new turns as they arrive\n (Ctrl-C to exit)\n --tmux escape hatch — print the raw pueue/tmux\n pane log instead\n attach [--readonly] Attach to the tmux session hosting claude (Ctrl-B D to detach;\n --readonly / -r to attach view-only)\n test Send a smoke-test classification through the channel\n help Show this message\n\nCONFIGURATION\n Provider toggle:\n Stored server-side in your inference settings (gradingProvider).\n Toggle via: synkro local-cc enable / disable\n Or via dashboard inference settings (set grading provider to claude-code).\n\n Claude Code session settings (scoped to ~/.synkro/cc_sessions only):\n ${PLUGIN_SETTINGS_PATH}\n Currently sets {\"fastMode\": true}. Edit to add other CC settings for this\n session without affecting your other CC projects.\n\n MCP server registration (for the channel plugin):\n ${CLAUDE_JSON_PATH}\n A 'synkro-local' entry under mcpServers, pointing at:\n bun ${PLUGIN_PATH}\n Workspace trust is also pre-accepted under projects[\\`${SESSION_DIR}\\`].\n\n Channel runtime files:\n ${RUN_SCRIPT_PATH}\n bash wrapper that pueue invokes; owns the tmux session lifecycle.\n ${PLUGIN_PATH}\n Bun MCP channel plugin (auto-generated, do not edit).\n 127.0.0.1:${CHANNEL_PORT}\n Loopback TCP endpoint the CLI POSTs to in order to submit a request.\n\nENVIRONMENT VARIABLES\n SYNKRO_CHANNEL_PORT Override the TCP port used by both the plugin and\n the CLI client (loopback only). Default: 8929\n SYNKRO_CHANNEL_TIMEOUT_MS Per-request timeout inside the channel plugin\n (default: 120000)\n\nREQUIRED TOOLS\n claude Claude Code CLI, authenticated to your subscription\n pueue pueue + pueued (https://github.com/Nukesor/pueue) running\n tmux For detached pty around claude\n bun Runtime for the MCP channel plugin\n\nTROUBLESHOOTING\n • Channel unreachable after \\`enable\\`?\n synkro local-cc logs # check pueue task output\n tmux attach -t ${TMUX_SESSION_NAME} # see what claude is showing\n • Stale state after a crash:\n synkro local-cc stop && synkro local-cc start\n • Want to inspect or interact with the live session:\n synkro local-cc attach\n`);\n}\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readGatewayUrl(): string {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);\n if (m) return m[1];\n }\n return 'https://api.synkro.sh';\n}\n\nfunction updateLocalInferenceFlag(enabled: boolean): void {\n if (!existsSync(CONFIG_PATH)) return;\n let content = readFileSync(CONFIG_PATH, 'utf-8');\n const flag = enabled ? 'yes' : 'no';\n if (content.includes('SYNKRO_LOCAL_INFERENCE=')) {\n content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);\n } else {\n content = content.trimEnd() + `\\nSYNKRO_LOCAL_INFERENCE='${flag}'\\n`;\n }\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\nasync function setServerGradingProvider(provider: 'claude-code' | null): Promise<void> {\n await ensureValidToken();\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated. Run `synkro install` first.');\n const gatewayUrl = readGatewayUrl();\n const body = provider\n ? { roles: { grading: { provider, model: 'default' } } }\n : { roles: { grading: { provider: null, model: null } } };\n const resp = await fetch(`${gatewayUrl}/api/settings/inference?scope=user`, {\n method: 'PUT',\n headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`Failed to update inference settings: ${resp.status} ${text.slice(0, 200)}`);\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n console.log(`Local inference: ${isLocalCCEnabled() ? 'enabled' : 'disabled'}`);\n console.log(`Deployment mode: ${deploymentMode()}`);\n\n if (inDockerMode()) {\n const status = dockerStatus();\n if (!status.running) {\n console.log('synkro-server container: not running');\n console.log('Run `synkro install` (with SYNKRO_DEPLOYMENT_MODE=docker) to provision.');\n } else {\n console.log(`synkro-server container: running (${status.image})`);\n try {\n const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3_000) });\n console.log(`Health probe: ${r.ok ? 'ok' : `HTTP ${r.status}`}`);\n } catch (err) {\n console.log(`Health probe: ${(err as Error).message}`);\n }\n }\n if (needsKeychainBridge()) {\n console.log(`Keychain creds: ${credsAreStale() ? 'STALE — run `synkro local-cc refresh-creds`' : 'fresh'}`);\n }\n return;\n }\n\n try {\n assertPueueInstalled();\n } catch (err) {\n console.log(`Pueue: NOT AVAILABLE (${(err as Error).message})`);\n return;\n }\n\n // Channel 1 (judge: bash + edit, port 8929)\n const t = findTask(CHANNEL_PRIMARY);\n if (!t) {\n console.log('Channel 1 (judge) pueue task: not present');\n } else {\n console.log(`Channel 1 (judge) pueue task: id=${t.id} status=${t.status}`);\n }\n const ch1Up = await isChannelAvailable();\n console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? 'reachable' : 'unreachable'}`);\n const tmux1 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? 'live' : 'absent'}`);\n\n // Channel 2 (CWE, port 8930)\n const t2 = findTask(CHANNEL_SECONDARY);\n if (!t2) {\n console.log('Channel 2 (CWE) pueue task: not present');\n } else {\n console.log(`Channel 2 (CWE) pueue task: id=${t2.id} status=${t2.status}`);\n }\n const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);\n console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? 'reachable' : 'unreachable'}`);\n const tmux2 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME_2}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? 'live' : 'absent'}`);\n}\n\nasync function cmdEnable(): Promise<void> {\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n console.log('Installing local-CC channel plugin...');\n const r = installLocalCC();\n console.log(` plugin: ${r.pluginPath}`);\n console.log(` cwd: ${r.sessionDir}`);\n console.log('Starting channel 1 (judge)...');\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(` task: id=${t1.id} status=${t1.status}`);\n console.log('Starting channel 2 (CWE)...');\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(` task: id=${t2.id} status=${t2.status}`);\n console.log('Waiting for channels (auto-confirming any CC prompts)...');\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n if (ready1) console.log(` channel 1 ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);\n else console.warn(` ⚠ channel 1 did not come up within 60s — check \\`synkro local-cc logs\\``);\n if (ready2) console.log(` channel 2 ready at ${CHANNEL_HOST}:${CHANNEL_2_PORT}`);\n else console.warn(` ⚠ channel 2 (CWE) did not come up within 60s`);\n console.log('Updating inference settings...');\n await setServerGradingProvider('claude-code');\n updateLocalInferenceFlag(true);\n console.log('Grading provider set to claude-code (local inference enabled).');\n}\n\nasync function cmdDisable(): Promise<void> {\n console.log('Updating inference settings...');\n await setServerGradingProvider(null);\n updateLocalInferenceFlag(false);\n console.log('Grading provider cleared (remote inference restored). Pueue task left running — use `synkro local-cc stop` to terminate.');\n}\n\nasync function warmChannels(ready1: boolean, ready2: boolean): Promise<void> {\n const warmups: Promise<void>[] = [];\n if (ready1) {\n warmups.push(\n submitToChannel('grade-bash', 'Proposed command: echo hello\\nUser intent: warmup\\nRecent user messages: []\\nRecent actions: []\\nOrg rules: []\\n', { timeoutMs: 30_000 })\n .then(() => console.log(' channel 1 warm.'))\n .catch(() => console.log(' channel 1 warmup skipped (non-fatal).'))\n );\n }\n if (ready2) {\n warmups.push(\n submitToChannel('grade-cwe', 'File: /tmp/warmup.ts\\nContent (first 4000 chars):\\nconsole.log(\"hello\");\\n\\nCWE rules to check against:\\n[]\\n', { timeoutMs: 30_000, port: CHANNEL_2_PORT })\n .then(() => console.log(' channel 2 warm.'))\n .catch(() => console.log(' channel 2 warmup skipped (non-fatal).'))\n );\n }\n if (warmups.length) {\n console.log('Warming up inference...');\n await Promise.all(warmups);\n }\n}\n\nasync function cmdStart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n // With worker flags, `start` (re)creates the container with the requested\n // pool config. Without flags it just ensures the container is running.\n if (rest.length > 0) {\n const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);\n console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n return;\n }\n // Container is normally managed by docker's `--restart unless-stopped`,\n // so \"start\" really just means: ensure it's running and healthy. If it's\n // not even installed, point the user at `synkro install`.\n const status = dockerStatus();\n if (!status.running) {\n console.warn('synkro-server container is not running. Run `synkro install` to provision it.');\n process.exitCode = 1;\n return;\n }\n console.log('synkro-server container already running — waiting for /healthz...');\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? `✓ container ready (${status.healthz})` : '⚠ container did not pass /healthz within 60s');\n return;\n }\n\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(`Channel 1 (judge): id=${t1.id} status=${t1.status}`);\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 2 (CWE): id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction cmdStop(): void {\n if (inDockerMode()) {\n dockerStop();\n console.log('synkro-server container stopped and removed.');\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n console.log('Both channels stopped.');\n}\n\nasync function cmdRestart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n const { explicit } = resolveWorkerConfig(rest);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (explicit) {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n } else {\n const reconciled = reconcileHarness();\n if (reconciled) {\n ({ claudeWorkers, cursorWorkers } = reconciled);\n } else {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n }\n }\n console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n if (ready) {\n const workersUp = await waitForWorkersReady(30_000);\n console.log(workersUp ? '✓ workers ready' : '⚠ workers did not register within 30s');\n if (workersUp) await syncSkillFiles();\n }\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n const t1 = startTask({ channel: CHANNEL_PRIMARY });\n const t2 = startTask({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 1 restarted: id=${t1.id} status=${t1.status}`);\n console.log(`Channel 2 restarted: id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction relativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (!Number.isFinite(ts)) return iso;\n const sec = Math.max(0, Math.round((Date.now() - ts) / 1000));\n if (sec < 60) return `${sec}s ago`;\n if (sec < 3600) return `${Math.round(sec / 60)}m ago`;\n if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;\n return `${Math.round(sec / 86400)}d ago`;\n}\n\nfunction colorize(s: string, code: number): string {\n if (!process.stdout.isTTY) return s;\n return `\u001b[${code}m${s}\u001b[0m`;\n}\n\nfunction statusGlyph(t: TurnEntry): string {\n if (t.status === 'ok') return colorize('✓', 32); // green check\n if (t.status === 'timeout') return colorize('⏱', 33); // yellow clock\n return colorize('✗', 31); // red X\n}\n\nfunction severityCell(t: TurnEntry): string {\n if (t.severity) {\n const sev = t.severity;\n if (sev === 'block' || sev === 'violations' || sev === 'unclear') return colorize(sev, 33);\n return colorize(sev, 36); // cyan for everything else\n }\n if (t.error) return colorize(t.error.slice(0, 40), 31);\n return '—';\n}\n\nfunction firstLine(s: string): string {\n return s.split('\\n').find(l => l.trim().length > 0)?.trim() ?? '';\n}\n\nfunction formatTurn(t: TurnEntry, raw: boolean): string {\n const when = relativeTime(t.ts).padEnd(8);\n const dur = (t.duration_ms < 1000\n ? `${t.duration_ms}ms`\n : `${(t.duration_ms / 1000).toFixed(1)}s`).padStart(7);\n const role = t.role.padEnd(24);\n const sev = severityCell(t).padEnd(20);\n const preview = (() => {\n const req = firstLine(t.request_preview).slice(0, 60);\n return colorize(req, 90);\n })();\n const head = `${statusGlyph(t)} ${when} ${dur} ${role} ${sev} ${preview}`;\n if (!raw) return head;\n // raw mode: include full request + response previews on subsequent lines\n const blocks: string[] = [head];\n blocks.push(colorize(' request:', 90));\n blocks.push(' ' + t.request_preview.replace(/\\n/g, '\\n '));\n if (t.response_preview) {\n blocks.push(colorize(' response:', 90));\n blocks.push(' ' + t.response_preview.replace(/\\n/g, '\\n '));\n }\n if (t.error) {\n blocks.push(colorize(' error:', 31) + ' ' + t.error);\n }\n return blocks.join('\\n');\n}\n\nfunction cmdLogs(rest: string[]): void | Promise<void> {\n let n = 20;\n let raw = false;\n let live = false;\n\n if (inDockerMode()) {\n // In docker mode the meaningful logs come from the container itself: MCP\n // boot, dispatcher routing, worker tmux output. Forward to `docker logs`\n // and let docker handle the --follow / --tail semantics.\n const followFlag = rest.includes('--live') || rest.includes('-f') ? ['--follow'] : [];\n const tailArg = (() => {\n for (const arg of rest) {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) return String(parsed);\n }\n return '200';\n })();\n spawnSync('docker', ['logs', '--tail', tailArg, ...followFlag, 'synkro-server'], { stdio: 'inherit' });\n return;\n }\n\n for (const arg of rest) {\n if (arg === '--raw' || arg === '-r') raw = true;\n else if (arg === '--live' || arg === '-f') live = true;\n else if (arg === '--tmux') {\n // Escape hatch: show the original raw pueue/tmux output for the task.\n console.log(tailLogs(80));\n return;\n } else {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) n = parsed;\n }\n }\n\n const header = ' ' + colorize('status when dur role severity request', 90);\n\n // Print backlog (newest first) — same as non-live mode.\n const turns = readRecentTurns(n);\n if (turns.length === 0) {\n if (!live) {\n console.log(`No turns logged yet at ${TURN_LOG_PATH}.`);\n console.log('Run a few requests through the channel (synkro local-cc test) and try again.');\n return;\n }\n console.log(`No turns logged yet at ${TURN_LOG_PATH} — waiting for new entries… (Ctrl-C to exit)`);\n } else {\n console.log(`Last ${turns.length} channel turn(s) (newest first):`);\n console.log(header);\n for (const t of turns) console.log(' ' + formatTurn(t, raw));\n }\n\n if (!live) {\n if (!raw) console.log(' ' + colorize('(use --raw / -r to see full payloads, --live / -f to follow)', 90));\n return;\n }\n\n // Live tail. Follow the JSONL log; print each new entry as it arrives.\n // Returning a Promise keeps the process alive until SIGINT.\n return new Promise<void>(resolve => {\n console.log(' ' + colorize('— following new turns (Ctrl-C to exit) —', 90));\n const stop = followTurns(t => {\n console.log(' ' + formatTurn(t, raw));\n });\n const onSigint = () => {\n stop();\n process.removeListener('SIGINT', onSigint);\n resolve();\n };\n process.on('SIGINT', onSigint);\n });\n}\n\nfunction cmdAttach(rest: string[]): void {\n assertTmuxInstalled();\n const readonly = rest.some(a => a === '--readonly' || a === '-r');\n\n // Verify the session exists before tmux drops the user into a confusing state.\n const has = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n if (has.status !== 0) {\n console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);\n process.exit(1);\n }\n\n if (!process.stdout.isTTY) {\n console.error('attach requires a TTY. Run this command directly in your terminal, not piped.');\n process.exit(1);\n }\n\n console.log(`Attaching to tmux session '${TMUX_SESSION_NAME}'${readonly ? ' (read-only)' : ''}.`);\n console.log('Detach with Ctrl-B then D. (Do not press Ctrl-C — that would interrupt claude.)');\n console.log();\n\n const args = readonly\n ? ['attach-session', '-r', '-t', TMUX_SESSION_NAME]\n : ['attach-session', '-t', TMUX_SESSION_NAME];\n const r = spawnSync('tmux', args, { stdio: 'inherit' });\n process.exit(r.status ?? 0);\n}\n\nasync function cmdTest(): Promise<void> {\n console.log('Sending smoke-test grading request through the channel...');\n const result = await submitToChannel(\n 'grade-bash',\n 'Command: echo \"hello world\"\\nIntent: testing channel connectivity',\n );\n console.log('Raw reply:');\n console.log(result);\n}\n\nfunction cmdInstall(): void {\n const r = installLocalCC();\n console.log(`Reinstalled plugin at ${r.pluginPath}`);\n}\n\n/**\n * Re-export Claude Code credentials from the macOS keychain into the\n * host-side file the container bind-mounts. Called by the launchd refresh\n * agent every 6 hours, and exposed as `synkro local-cc refresh-creds` for\n * manual invocation if the user just re-authed.\n *\n * On Linux this is a no-op — creds already live in a file the container can\n * read directly via bind mount.\n */\nfunction cmdRefreshCreds(): void {\n if (!needsKeychainBridge()) {\n console.log('No-op on this platform — credentials are already file-based.');\n return;\n }\n const refreshed = refreshCreds();\n if (refreshed) {\n console.log(`Exported claude credentials to ${CLAUDE_CREDS_FILE}`);\n } else {\n console.warn('No Claude Code credentials found in the keychain.');\n console.warn('Run `claude login` first, then re-run this command.');\n process.exitCode = 1;\n }\n if (credsAreStale()) {\n console.warn('⚠ Exported credentials are older than the refresh interval.');\n }\n}\n\nexport async function localCcCommand(args: string[]): Promise<void> {\n const sub = args[0] ?? '';\n try {\n switch (sub) {\n case 'enable': await cmdEnable(); break;\n case 'disable': cmdDisable(); break;\n case 'status': await cmdStatus(); break;\n case 'start': await cmdStart(args.slice(1)); break;\n case 'stop': cmdStop(); break;\n case 'restart': await cmdRestart(args.slice(1)); break;\n case 'install': cmdInstall(); break;\n case 'logs': await cmdLogs(args.slice(1)); break;\n case 'attach': cmdAttach(args.slice(1)); break;\n case 'test': await cmdTest(); break;\n case 'refresh-creds': cmdRefreshCreds(); break;\n case '':\n case 'help':\n case '--help':\n case '-h':\n printHelp(); break;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n printHelp();\n process.exit(1);\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n","/**\n * synkro stop / start / restart — safe container lifecycle commands.\n *\n * These ensure PGLite data is always persisted to the host before shutdown,\n * and verify integrity on start. Agents should use these instead of raw\n * docker commands to avoid data corruption.\n */\nimport {\n dockerSafeStop, dockerSafeStart, dockerSafeRestart, assertDockerAvailable,\n resolveWorkerConfig, dockerUpdate, waitForContainerReady, waitForWorkersReady, readContainerConfig,\n} from '../local-cc/dockerInstall.js';\nimport { detectGitRepo, reconcileHarness, syncSkillFiles } from './install.js';\n\n/**\n * The connected repo for a container recreate. start/restart/update all run\n * from inside the repo, so detect it live; fall back to whatever the existing\n * container recorded. Without this, recreating the container drops\n * SYNKRO_CONNECTED_REPO and the dashboard loses its project anchor.\n */\nfunction resolveConnectedRepo(): string | undefined {\n return detectGitRepo() ?? readContainerConfig()?.connectedRepo ?? undefined;\n}\n\nexport async function stopCommand(): Promise<void> {\n assertDockerAvailable();\n console.log('Synkro: stopping server\\n');\n const result = await dockerSafeStop();\n if (!result.ok) {\n console.error('\\nStop failed. Check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nServer stopped.');\n}\n\nexport async function startCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n // With --workers / --provider(s) flags, recreate the container with the\n // requested pool config (container env is fixed at `docker run` time, so a\n // plain `docker start` can't change it). Without flags, plain safe-start.\n const cfg = resolveWorkerConfig(rest);\n if (cfg.explicit) {\n console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer is running.');\n return;\n }\n console.log('Synkro: starting server\\n');\n const result = await dockerSafeStart();\n if (!result.ok) {\n console.error(`\\nStart failed: ${result.error}`);\n process.exit(1);\n }\n console.log('\\nServer is running.');\n}\n\nexport async function updateCommand(): Promise<void> {\n assertDockerAvailable();\n // Read the installed container's pool config so the rebuilt container keeps\n // the same worker split instead of resetting to defaults.\n const cfg = readContainerConfig();\n if (!cfg) {\n console.error('No synkro-server container found. Run `synkro install` first.');\n process.exit(1);\n }\n const claudeWorkers = cfg.claudeWorkers ?? 8;\n const cursorWorkers = cfg.cursorWorkers ?? 0;\n console.log('Synkro: updating to the latest container image');\n console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)\\n`);\n // dockerUpdate: graceful stop (snapshot + checkpoint) → remove → pull latest → recreate.\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(90_000);\n if (!ready) {\n console.error('\\n⚠ container did not pass its health check within 90s — check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nSynkro updated — now running the latest version.');\n}\n\nexport async function restartCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n const cfg = resolveWorkerConfig(rest);\n\n // Reconcile harness from .synkro (hooks, MCP, worker allocation)\n let claudeWorkers = cfg.claudeWorkers;\n let cursorWorkers = cfg.cursorWorkers;\n if (!cfg.explicit) {\n const reconciled = reconcileHarness();\n if (reconciled) {\n claudeWorkers = reconciled.claudeWorkers;\n cursorWorkers = reconciled.cursorWorkers;\n }\n }\n\n console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer restarted successfully.');\n\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log('✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn('⚠ workers did not register within 30s — skill sync skipped');\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isAuthenticated, getAccessToken } from '../auth/stub.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nfunction updateConfigValue(key: string, value: string): void {\n if (!existsSync(CONFIG_PATH)) {\n console.error('No config found. Run `synkro install` first.');\n process.exit(1);\n }\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\n');\n const pattern = new RegExp(`^${key}=`);\n let found = false;\n const updated = lines.map((line) => {\n if (pattern.test(line.trim())) {\n found = true;\n return `${key}='${value}'`;\n }\n return line;\n });\n if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);\n writeFileSync(CONFIG_PATH, updated.join('\\n'), 'utf-8');\n}\n\n// After a grading/storage change, bring the local container in line with the\n// new modes — this is what makes switching seamless (no separate install step).\n// Cloud-only (BYOK grading + cloud storage) needs no container; every other\n// combo needs it. Only acts when crossing that boundary; wrapped so a Docker\n// hiccup never fails the config write itself.\nasync function reconcileContainer(): Promise<void> {\n const cfg = readConfigEnv();\n const cloudOnly =\n (cfg.SYNKRO_GRADING_MODE || 'local') === 'byok' &&\n (cfg.SYNKRO_STORAGE_MODE || 'local') === 'cloud';\n\n try {\n const { dockerInstall, dockerSafeStop, readContainerConfig, assertDockerAvailable, waitForContainerReady } =\n await import('../local-cc/dockerInstall.js');\n const existing = readContainerConfig();\n\n if (cloudOnly && existing) {\n console.log('Cloud-only mode — the local container is no longer needed; stopping it...');\n await dockerSafeStop();\n console.log(' ✓ container stopped (grading → BYOK, telemetry → cloud).');\n } else if (!cloudOnly && !existing) {\n assertDockerAvailable();\n console.log('This mode needs the Synkro container — provisioning...');\n const { detectGitRepo } = await import('./install.js');\n await dockerInstall({ claudeWorkers: 8, cursorWorkers: 0, connectedRepo: detectGitRepo() ?? undefined });\n const ready = await waitForContainerReady(60_000);\n console.log(ready\n ? ' ✓ container running.'\n : ' ⚠ container did not pass its health check — see `docker logs synkro-server`.');\n }\n // else: the container's presence already matches the mode — nothing to do.\n } catch (err) {\n console.warn(` ⚠ Container reconcile skipped: ${(err as Error).message}`);\n console.warn(' Run `synkro install` to (re)provision the container if needed.');\n }\n}\n\nexport async function configCommand(args: string[]): Promise<void> {\n if (args.length === 0) {\n const config = readConfigEnv();\n console.log('Synkro config:\\n');\n console.log(` grading: ${config.SYNKRO_GRADING_MODE || 'local'}`);\n console.log(` storage: ${config.SYNKRO_STORAGE_MODE || 'local'}`);\n console.log(` inference: ${config.SYNKRO_INFERENCE || 'fast'}`);\n console.log(` tier: ${config.SYNKRO_TIER || 'pro'}`);\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh'}`);\n console.log(` version: ${config.SYNKRO_VERSION || '?'}`);\n console.log(`\\nTo change:`);\n console.log(` synkro config grading <local|byok> — where grading runs`);\n console.log(` synkro config storage <local|cloud> — where telemetry is stored`);\n console.log(` synkro config --inference fast|standard`);\n return;\n }\n\n // User-owned data axes — written to config.env; the hooks load config.env at\n // process start, so the change takes effect on the next graded tool call.\n if (args[0] === 'grading') {\n const value = args[1];\n if (value !== 'local' && value !== 'byok') {\n console.error('Usage: synkro config grading <local|byok>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_GRADING_MODE', value);\n console.log(`✓ Grading mode set to '${value}'.`);\n if (value === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n }\n await reconcileContainer();\n return;\n }\n if (args[0] === 'storage') {\n const value = args[1];\n if (value !== 'local' && value !== 'cloud') {\n console.error('Usage: synkro config storage <local|cloud>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_STORAGE_MODE', value);\n console.log(`✓ Storage mode set to '${value}'.`);\n await reconcileContainer();\n return;\n }\n\n let inferenceValue: string | undefined;\n for (const a of args) {\n if (a.startsWith('--inference=')) inferenceValue = a.slice('--inference='.length);\n else if (a === '--inference' && args.indexOf(a) + 1 < args.length) inferenceValue = args[args.indexOf(a) + 1];\n }\n\n if (!inferenceValue || !['fast', 'standard'].includes(inferenceValue)) {\n console.error('Usage: synkro config --inference fast|standard');\n process.exit(1);\n }\n\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro login` first.');\n process.exit(1);\n }\n\n const token = getAccessToken();\n const config = readConfigEnv();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ fast_inference: inferenceValue === 'fast' }),\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to reach server: ${(err as Error).message}`);\n process.exit(1);\n }\n\n updateConfigValue('SYNKRO_INFERENCE', inferenceValue);\n console.log(`✓ Inference set to '${inferenceValue}'.`);\n}\n","#!/usr/bin/env node\n/**\n * Synkro CLI bootstrap.\n *\n * Loads .env, then dispatches to the right subcommand.\n */\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst envCandidates = [\n resolve(process.env.HOME ?? '', '.synkro', 'config.env'),\n];\nfor (const envPath of envCandidates) {\n if (!existsSync(envPath)) continue;\n const envContent = readFileSync(envPath, 'utf-8');\n for (const line of envContent.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex <= 0) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n const value = trimmed.slice(eqIndex + 1).trim().replace(/^['\"]|['\"]$/g, '');\n if (!process.env[key] && !value.startsWith('op://')) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\n\nfunction printVersion() {\n console.log(__SYNKRO_CLI_VERSION__);\n}\n\nfunction printHelp() {\n console.log(`Synkro CLI — runtime safety for AI coding agents\n\nUsage:\n synkro <command> [options]\n\nCommands:\n install [--force] Install or update Synkro\n uninstall [--purge] Remove Synkro (keeps scan data; --purge wipes that too)\n stop Gracefully stop the server (snapshot + checkpoint)\n start [opts] Start the server (with pgdata integrity check)\n restart [opts] Safe restart (stop → start, data preserved)\n update Pull the latest container image and safely restart\n config Show or change grading + storage modes\n version Show version\n\n config:\n synkro config show current settings\n synkro config grading <local|byok> where grading runs\n synkro config storage <local|cloud> where telemetry is stored\n\n start/restart opts (recreate the worker pool):\n --workers N total grader workers (default 8, even-split)\n --providers a,b grading agents: claude, cursor (or both)\n e.g. synkro restart --workers 16 --providers claude,cursor\n\nQuick start:\n $ synkro install # one-time setup\n $ claude # use Claude Code normally; Synkro judges in real time\n`);\n}\n\nasync function main() {\n switch (cmd) {\n case 'install': {\n const { installCommand, parseArgs } = await import('./commands/install.js');\n await installCommand(parseArgs(subArgs));\n break;\n }\n case 'uninstall':\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n disconnectCommand(subArgs);\n break;\n }\n case 'login': {\n const { authenticate, isAuthenticated } = await import('./auth/stub.js');\n if (isAuthenticated()) {\n console.log('Already authenticated.');\n } else {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n if (status.phase === 'success') console.log(' ✓ Authenticated');\n else if (status.phase === 'error') console.error(' ✗ ' + status.message);\n });\n if (!result) { console.error('Authentication failed.'); process.exit(1); }\n }\n break;\n }\n case 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(subArgs);\n break;\n }\n case 'local-cc': {\n const { localCcCommand } = await import('./commands/localCc.js');\n await localCcCommand(subArgs);\n break;\n }\n case 'version':\n case '--version':\n case '-v': {\n printVersion();\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n case '': {\n printHelp();\n break;\n }\n case 'stop': {\n const { stopCommand } = await import('./commands/lifecycle.js');\n await stopCommand();\n break;\n }\n case 'start': {\n const { startCommand } = await import('./commands/lifecycle.js');\n await startCommand(args.slice(1));\n break;\n }\n case 'restart': {\n const { restartCommand } = await import('./commands/lifecycle.js');\n await restartCommand(args.slice(1));\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/lifecycle.js');\n await updateCommand();\n break;\n }\n case 'config': {\n const { configCommand } = await import('./commands/config.js');\n await configCommand(args.slice(1));\n break;\n }\n default: {\n console.error(`Unknown command: ${cmd}`);\n printHelp();\n process.exit(1);\n }\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA88EzB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuQzB,IAAM,kBAAkB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqjB/B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8RxB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0HxB,IAAM,gBAAgB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8S7B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgMvB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgMtB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwExB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+FzB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoEzB,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkE3B,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgD9B,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8S7B,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkI/B,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["cmd","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","dirname","homedir","isSynkroEntry","SYNKRO_MARKER","removeSynkroEntries","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","url","jwt","existsSync","resolve","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","join","dirname","args","resolve","existsSync","mkdirSync","writeFileSync","execSync","join","execSync","execFileSync","createServer","resolve","SYNKRO_WEB_AUTH_URL","openBrowser","execFile","RAW_WEB_AUTH_URL","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","platform","join","SYNKRO_DIR","existsSync","mkdirSync","readFileSync","readdirSync","homedir","join","execSync","spawnSync","which","args","createInterface","execSync","existsSync","readFileSync","unlinkSync","homedir","platform","join","execFile","resolve","openBrowser","args","SYNKRO_DIR","jwt","detectGitRepo","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","readdirSync","homedir","join","execSync","createInterface","ask","resolve","cursorApiKeyConfigured","writeCursorApiKey","validateCursorApiKey","SYNKRO_DIR","CONFIG_PATH","assertDockerAvailable","setupGithubCommand","parseSynkroYaml","cmd","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","copyFileSync","renameSync","unlinkSync","join","homedir","spawnSync","init_install","existsSync","readdirSync","homedir","join","spawnSync","createInterface","resolve","args","SYNKRO_DIR","init_install","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","resolve","args","execFileSync","spawnSync","homedir","join","connect","args","resolve","SESSION_DIR","SESSION_DIR_2","SESSION_DIR_3","SESSION_DIR_4","existsSync","readFileSync","homedir","join","CONFIG_PATH","spawnSync","homedir","join","existsSync","readFileSync","writeFileSync","CONFIG_PATH","jwt","ready","resolve","args","init_install","detectGitRepo","readFileSync","writeFileSync","existsSync","join","homedir","CONFIG_PATH","dockerInstall","dockerSafeStop","readContainerConfig","assertDockerAvailable","waitForContainerReady","detectGitRepo","args","config","SYNKRO_DIR","readFileSync","existsSync","resolve","printHelp","installCommand","parseArgs","disconnectCommand","authenticate","isAuthenticated","gradeCommand","localCcCommand","stopCommand","startCommand","restartCommand","updateCommand","configCommand"]}