@synkro-sh/cli 1.4.99 → 1.4.101

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/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/commands/setupGithub.ts","../cli/installer/promptFetcher.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.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/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' | 'codex' | '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\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary || existsSync(claudeConfigDir)) {\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: claudeBinary ? getVersion('claude') : undefined,\n });\n }\n\n // Codex (OpenAI's CLI)\n const codexBinary = which('codex');\n const codexConfigDir = join(home, '.codex');\n if (codexBinary || existsSync(codexConfigDir)) {\n agents.push({\n kind: 'codex',\n name: 'Codex',\n binaryPath: codexBinary,\n configDir: codexConfigDir,\n settingsPath: join(codexConfigDir, 'config.toml'),\n version: codexBinary ? getVersion('codex') : undefined,\n });\n }\n\n // Cursor\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n if (cursorBinary || existsSync(cursorConfigDir)) {\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version: cursorBinary ? getVersion('cursor') : undefined,\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 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/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 /** 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}\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];\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 h.beforeShellExecution = h.beforeShellExecution ?? [];\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 covers non-shell tools (Read, Edit, etc.) + dedup prevents double-grading shell commands\n h.preToolUse = h.preToolUse ?? [];\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 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","// :)\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_RULES_FILE=\"$HOME/.synkro/rules.json\"\n_SYNKRO_MCP_JWT_FILE=\"$HOME/.synkro/.mcp-jwt\"\n\nsynkro_load_config() {\n # Local-first: read from ~/.synkro/rules.json if it exists (zero latency, no network)\n if [ -f \"$_SYNKRO_RULES_FILE\" ]; then\n local rdata\n rdata=$(cat \"$_SYNKRO_RULES_FILE\" 2>/dev/null)\n if [ -n \"$rdata\" ]; then\n SYNKRO_CAPTURE_DEPTH=\"local_only\"\n SYNKRO_TIER=\"standard\"\n SYNKRO_SILENT=$(echo \"$rdata\" | jq -r '.config.silent // false' 2>/dev/null)\n local active_id\n active_id=$(echo \"$rdata\" | jq -r '.config.activePolicyId // empty' 2>/dev/null)\n if [ -n \"$active_id\" ]; then\n SYNKRO_POLICY_NAME=$(echo \"$rdata\" | jq -r --arg id \"$active_id\" '.policies[]? | select(.id == $id) | .name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$rdata\" | jq -c --arg id \"$active_id\" '[.policies[]? | select(.id == $id) | .rules[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n else\n SYNKRO_POLICY_NAME=$(echo \"$rdata\" | jq -r '.policies[0]?.name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$rdata\" | jq -c '[.policies[0]?.rules[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n fi\n return\n fi\n fi\n\n # Fallback: fetch from cloud API\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[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {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 with prompts from /v1/hook/config, send anonymized metadata to /v1/hook/capture.\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 } from 'node:fs';\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\n// ─── Logging ───\n\nexport function log(msg: string): void {\n process.stderr.write('[synkro] ' + msg + '\\\\n');\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\nexport function detectRepo(cwd: string, transcriptPath?: string): string {\n let resolvedCwd = cwd;\n if (!resolvedCwd && transcriptPath) {\n const m = transcriptPath.match(/\\\\/projects\\\\/(-[^/]+)\\\\//);\n if (m) resolvedCwd = '/' + m[1].slice(1).replace(/-/g, '/');\n }\n if (!resolvedCwd) resolvedCwd = process.cwd();\n try {\n const url = execSync('git remote get-url origin 2>/dev/null', { cwd: resolvedCwd, 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: resolvedCwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (root) return root.split('/').pop() || '';\n } catch {}\n return '';\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(500, () => done(false));\n });\n}\n\nexport async function cweChannelUp(): Promise<boolean> {\n return channelUp(18930);\n}\n\n// ─── Config Loading ───\n\nexport interface Rule {\n rule_id: string;\n text: string;\n severity: string;\n category: string;\n mode: string;\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}\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 };\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 = (policy.rules || [])\n .filter((r: any) => r.hook_stage === 'pre' || r.hook_stage === 'both' || r.hook_stage == null)\n .map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: r.mode || 'blocking',\n }));\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 // Fallback: fetch from cloud API\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 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)) {\n config.rules = data.rules\n .filter((r: any) => r.hook_stage === 'pre' || r.hook_stage === 'both' || r.hook_stage == null)\n .map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: r.mode || 'blocking',\n }));\n }\n } catch {}\n return config;\n}\n\n// ─── Routing ───\n\nexport async function route(config: HookConfig): Promise<'local' | '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): Promise<'local' | 'cloud'> {\n if (config.captureDepth === 'local_only') return 'local';\n if (await cweChannelUp()) return 'local';\n return 'cloud';\n}\n\n// ─── Tag Building ───\n\nexport function tag(rt: string, config: HookConfig): string {\n if (config.silent) return '[synkro:silent]';\n const rs = config.policyName || 'all';\n return '[synkro:' + rt + ':' + rs + ']';\n}\n\n// ─── Local Grading (direct channel call) ───\n\ntype GradeRole = 'grade-edit' | 'grade-bash' | 'grade-plan' | 'grade-cwe';\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 = 20000): Promise<string> {\n const body = JSON.stringify({ role, payload: prompt, content: prompt });\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\nexport async function localGrade(surface: string, prompt: string, timeoutMs = 20000): Promise<string> {\n if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs);\n}\n\nexport async function localGradeCwe(prompt: string): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade('grade-cwe', prompt, jwt, 18930, 45000);\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 try {\n mkdirSync(SESSIONS_DIR, { recursive: true });\n appendFileSync(logPath, JSON.stringify(entry) + '\\\\n', 'utf-8');\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 > 30) {\n const old = actions.slice(0, total - 30);\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 - 30);\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\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 },\n): void {\n // Fire-and-forget\n const eventId = 'evt_' + Date.now() + '_' + process.pid;\n const model = opts?.ccModel || 'unknown';\n const sendFull =\n captureDepth === 'full' ||\n (captureDepth === 'evidence_on_violation' && ['block', 'warning', 'deny'].includes(verdictStr));\n\n const body: Record<string, any> = {\n capture_type: 'local_verdict',\n event_id: eventId,\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 }\n appendLocalTelemetry(localBody);\n\n // local_only: no data leaves the machine\n if (captureDepth === 'local_only') return;\n\n if (sendFull && opts) {\n body.capture_depth = captureDepth;\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\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n}\n\nexport function appendLocalTelemetry(body: Record<string, any>): void {\n const event = { ...body, _ts: new Date().toISOString() };\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/ingest\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ data: event }),\n signal: AbortSignal.timeout(2000),\n }).catch(() => {});\n}\n\n// ─── Rule Mode Lookup ───\n\nexport function ruleMode(ruleId: string, rules: Rule[]): 'blocking' | 'audit' {\n if (!ruleId || !rules.length) return 'blocking';\n const matched = rules.filter(r => r.rule_id === ruleId);\n if (matched.some(r => r.mode === 'blocking')) return 'blocking';\n return (matched[0]?.mode as 'blocking' | 'audit') || 'blocking';\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 || '';\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 Extraction ───\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 (entry.type !== 'user') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content;\n else if (Array.isArray(content)) text = content.map((c: any) => c.text || '').join(' ');\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 if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content.slice(0, 500);\n else if (Array.isArray(content)) text = content.map((c: any) => (c.text || '').slice(0, 300)).join(' ');\n msgs.push({ type: entry.type, 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 (entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use') continue;\n actions.push({\n tool: block.name || '',\n input: JSON.stringify(block.input || {}).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 => e.type === '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// ─── Transcript Usage Aggregation ───\n\nexport function aggregateUsage(transcriptPath: string): { model: string; totals: Record<string, number> } {\n const result = { model: '', 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 lines = raw.split('\\\\n').filter(l => l.trim());\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n if (entry.type !== 'assistant') continue;\n result.model = entry.message?.model || result.model;\n const u = entry.message?.usage;\n if (u) {\n result.totals.in += u.input_tokens || 0;\n result.totals.out += u.output_tokens || 0;\n result.totals.cw += u.cache_creation_input_tokens || 0;\n result.totals.cr += u.cache_read_input_tokens || 0;\n }\n } catch {}\n }\n } catch {}\n return result;\n}\n\n// ─── Scan Finding Dispatch ───\n\nexport function dispatchFinding(\n jwt: string,\n finding: {\n session_id: string;\n file_path: 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 if (captureDepth === 'local_only') return;\n\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 };\n\n if (captureDepth === 'evidence_on_violation' || captureDepth === 'full') {\n cloudBody.file_path = finding.file_path;\n cloudBody.package_name = finding.package_name;\n cloudBody.package_version = finding.package_version;\n cloudBody.fixed_version = finding.fixed_version;\n cloudBody.aliases = finding.aliases;\n cloudBody.references = finding.references;\n cloudBody.cwe_name = finding.cwe_name;\n }\n if (captureDepth === 'full') {\n cloudBody.detail = finding.detail;\n cloudBody.description = finding.description;\n }\n\n fetch(GATEWAY_URL + '/api/v1/hook/finding', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(cloudBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\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';\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// ─── 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 type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\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);\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 // Read file before edit for cloud payload\n let fileBefore = '';\n if (toolName !== 'Write' && filePath && isPathUnder(filePath, cwd || '.') && existsSync(filePath)) {\n try { fileBefore = readFileSync(filePath, 'utf-8').slice(0, 65536); } catch {}\n }\n\n // Extract transcript context\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n // Load config and decide route\n const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 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(config.rules),\n ].join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' → local grader unavailable, skipped' });\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 = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n const denyReason = 'Guard: ' + guardReason + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the violation in code yourself.';\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules,\n ccModel: transcript.ccModel,\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 // Audit mode — warn but allow\n dispatchCapture(jwt, 'edit', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules,\n ccModel: transcript.ccModel,\n });\n outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' → warning: ' + guardReason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge (audit). ' + guardReason } });\n return;\n }\n\n // Clean\n dispatchCapture(jwt, 'edit', 'pass', 'audit', verdict.category || 'trivial_edit',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, violatedRules: [],\n ccModel: transcript.ccModel,\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_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 process.stderr.write('[synkro] editGuard error: ' + String(err) + '\\\\n');\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, hookSessionId, filePathFromToolInput, dispatchFinding, dispatchCapture, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { basename, extname, resolve, join, dirname } from 'node:path';\nimport { readFileSync, readdirSync, existsSync } from 'node:fs';\n\n\ninterface PackageCapability {\n name: string;\n description: string;\n capabilities: string[];\n sourceExcerpt: string;\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 if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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 const fileExt = extname(filePath).toLowerCase();\n\n // Skip prose / non-executable files — CWE scanning is for source code only.\n // Without this guard, prose that *mentions* a CWE (plans, notes, READMEs)\n // can trigger a false positive on the literal string.\n const 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 if (NON_CODE_EXTS.has(fileExt)) { outputEmpty(); return; }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\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 const config = await loadConfig(jwt);\n const rt = await cweRoute(config);\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 if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cweScan] ' + fileShort + ' \\u2192 skipped (silent mode)' });\n return;\n }\n\n const cweTag = '[synkro:' + rt + ':cweScan]';\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(toolName, toolInput, 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)),\n localGradeCwe(buildCwePrompt(chunk2)),\n ]);\n gradeResponses = [resp1, resp2];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 grader unavailable (' + reason + '), skipped' });\n return;\n }\n } else {\n try {\n gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 grader unavailable (' + reason + '), skipped' });\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 outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (exempted: ' + cweIds.join(', ') + ')' });\n return;\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 for (const cweId of activeCweIds) {\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: cweId,\n severity: verdict.severity || 'high',\n status: 'open',\n detail: verdict.reason || 'code weakness detected',\n cwe_name: cweNameMap.get(cweId.toUpperCase()) || undefined,\n }, config.captureDepth);\n }\n\n dispatchCapture(jwt, 'cwe', 'block', verdict.severity || 'high', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\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\n const cleanMsg = cweTag + ' ' + fileShort + ' \\u2192 clean' + (verdict.reason ? ' (' + verdict.reason + ')' : '');\n outputJson({ systemMessage: cleanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: cleanMsg } });\n return;\n }\n\n // Cloud path \\u2014 thin client, all grading logic server-side\n // Detect imports and scan capabilities for cloud grading\n let packageContext: PackageCapability[] | undefined;\n if (cwd) {\n const newImports = detectNewImports(toolName, toolInput, 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) scanBody.package_context = packageContext.map(c => ({\n name: c.name, description: c.description, capabilities: c.capabilities, source_excerpt: c.sourceExcerpt,\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 cweResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud grader 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')\n .map((f: any) => f.cwe)\n .filter((id: string) => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (exempted)' });\n return;\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 for (const cweId of activeCweIds) {\n const f = findings.find((x: any) => x.cwe === cweId);\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: cweId,\n severity: f?.severity || 'high',\n status: 'open',\n detail: f?.reason || 'code weakness detected',\n cwe_name: f?.name || undefined,\n }, config.captureDepth);\n }\n\n dispatchCapture(jwt, 'cwe', 'block', findings[0]?.severity || 'high', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\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\n const cleanMsg = cweTag + ' ' + fileShort + ' \\u2192 clean' + (cweResp?.summary ? ' (cloud)' : '');\n outputJson({ systemMessage: cleanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: cleanMsg } });\n } catch (err) {\n process.stderr.write('[synkro] cweGuard error: ' + String(err) + '\\n');\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, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\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 toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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\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 // 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 for (const f of findings.slice(0, 10)) {\n const cveId = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown';\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cve',\n finding_id: cveId,\n severity: f.severity || 'high',\n status: 'open',\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 }, config.captureDepth);\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 dispatchCapture(jwt, 'cve', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: top3,\n violatedRules: cveIds,\n });\n\n outputJson({\n systemMessage: cveMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → clean' });\n } catch (err) {\n process.stderr.write('[synkro] cveGuard error: ' + String(err) + '\\\\n');\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 type HookConfig, type Rule,\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 if (!isShellTool(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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\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 appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName, summary: command.slice(0, 120) });\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n // ─── Install protection: server-side pkg-scan (CVE + typosquat + tarball + reputation) ───\n let installScanMsg = '';\n if (toolName === 'Bash') {\n const pkgInstallMatch = command.match(\n /^(?:.*&&\\s*|.*;\\s*)?(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|(?:uv\\s+)?pip3?\\s+install|go\\s+get|cargo\\s+add|gem\\s+install|composer\\s+require)\\s+([^|;&><]+)/\n );\n const isPip = /(?:uv\\s+)?pip3?\\s+install/.test(command);\n if (pkgInstallMatch) {\n const rawArgs = pkgInstallMatch[1];\n const packages: Array<{ name: string; version: string; ecosystem: string }> = [];\n const tokens = rawArgs.split(/\\s+/);\n let skipNext = false;\n for (const token of tokens) {\n if (skipNext) { skipNext = false; continue; }\n if (!token || !/^[@a-zA-Z]/.test(token)) continue;\n if (token.startsWith('-')) {\n if (/^--(python|target|prefix|root|constraint|requirement|index-url|extra-index-url|find-links|build|src|cache-dir|filter|workspace)$/.test(token)) skipNext = true;\n continue;\n }\n const ecosystem = isPip ? 'PyPI' : 'npm';\n if (isPip) {\n const pipMatch = token.match(/^([a-zA-Z0-9_.-]+)(?:[=~!<>]=?(.+))?$/);\n if (pipMatch) {\n packages.push({ name: pipMatch[1], version: pipMatch[2]?.replace(/^=/, '') || '*', ecosystem });\n continue;\n }\n }\n const atIdx = token.lastIndexOf('@');\n if (atIdx > 0) {\n packages.push({ name: token.slice(0, atIdx), version: token.slice(atIdx + 1), ecosystem });\n } else {\n packages.push({ name: token, version: '*', ecosystem });\n }\n }\n\n if (packages.length > 0) {\n try {\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({ packages, command }),\n signal: AbortSignal.timeout(15000),\n }).then(r => r.json()) as any;\n\n const action = scanResp?.action || 'allow';\n const pkgResults = Array.isArray(scanResp?.packages) ? scanResp.packages : [];\n const summary = scanResp?.summary || '';\n\n if (action === 'block') {\n const blockSignals = pkgResults\n .flatMap((p: any) => (p.signals || []).filter((s: any) => s.severity === 'critical' || s.severity === 'high'))\n .slice(0, 5);\n const scanMsg = '[synkro:installScan] ' + cmdShort + ' → blocked';\n const details = blockSignals.map((s: any) => s.detail).join('\\n');\n const ctx = details + '\\nDo NOT install packages with security risks. Use a patched version or a different package.';\n\n const config = await loadConfig(jwt);\n for (const p of pkgResults) {\n for (const s of (p.signals || [])) {\n if (s.severity === 'critical' || s.severity === 'high') {\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;\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: command,\n finding_type: 'cve' as const,\n finding_id: advisoryId + ':' + p.name,\n severity: s.severity,\n status: 'open',\n detail: s.detail,\n package_name: p.name,\n package_version: p.version,\n }, config.captureDepth);\n }\n }\n }\n\n const violatedIds = blockSignals.map((s: any) => s.type + ':' + s.detail.slice(0, 40));\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command,\n reasoning: details.slice(0, 200),\n violatedRules: violatedIds,\n ccModel: transcript.ccModel,\n });\n\n outputJson({\n systemMessage: scanMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n if (action === 'warn') {\n installScanMsg = '[synkro:installScan] ' + summary;\n } else {\n const scannedPkgs = packages.map(p => p.name + '@' + p.version).join(', ');\n installScanMsg = '[synkro:installScan] ' + scannedPkgs + ' → clean';\n }\n } catch (e) {\n log('bashGuard pkg-scan failed: ' + String(e));\n }\n }\n }\n }\n\n const lastPrompt = readLastPrompt(sessionId);\n\n const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (config.silent) {\n const msg = (installScanMsg ? installScanMsg + '\\\\n' : '') + tagStr + ' bashGuard → 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 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(config.rules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' bashGuard → local grader unavailable, skipped' });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode === 'audit') {\n const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });\n dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({\n systemMessage: combined,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: combined },\n });\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n } else {\n const reason = tagStr + ' bashGuard → pass: ' + (verdict.reason || 'no policy violations detected');\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });\n dispatchCapture(jwt, 'bash', 'pass', 'audit', verdict.category || 'trivial_utility',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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_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('bashGuard ' + cmdShort + ' → error (timeout)');\n if (installScanMsg) {\n outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });\n } else { outputEmpty(); }\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n if (installScanMsg) {\n outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });\n } else { outputEmpty(); }\n return;\n }\n\n if (installScanMsg) {\n const existing = resp.hook_response.systemMessage || '';\n resp.hook_response.systemMessage = installScanMsg + (existing ? '\\\\n' + existing : '');\n if (resp.hook_response.hookSpecificOutput) {\n const existingCtx = resp.hook_response.hookSpecificOutput.additionalContext || '';\n resp.hook_response.hookSpecificOutput.additionalContext = installScanMsg + (existingCtx ? '\\\\n' + existingCtx : '');\n } else {\n resp.hook_response.hookSpecificOutput = { hookEventName: 'PreToolUse', additionalContext: resp.hook_response.systemMessage };\n }\n }\n outputJson(resp.hook_response);\n } catch (err) {\n process.stderr.write('[synkro] bashGuard error: ' + String(err) + '\\\\n');\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 type HookConfig, type Rule,\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 if (!isAgentTool(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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\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 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 const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 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(config.rules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' agentGuard → local grader unavailable, skipped' });\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 = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode === 'audit') {\n const reason = tagStr + ' agentGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');\n outputJson({ systemMessage: reason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason } });\n dispatchCapture(jwt, 'agent', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = 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: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\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', 'audit', verdict.category || 'subagent_spawn',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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_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 process.stderr.write('[synkro] agentGuard error: ' + String(err) + '\\\\n');\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} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\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 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 cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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 config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Plan:',\n plan.slice(0, 8000),\n 'Org rules: ' + JSON.stringify(config.rules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('plan', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' planReview → local grader unavailable, skipped' });\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: config.rules, violatedRules,\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', 'audit', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: reviewMsg,\n rulesChecked: config.rules, violatedRules: [],\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 process.stderr.write('[synkro] planReview error: ' + String(err) + '\\\\n');\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, appendLocalTelemetry, setupCursorHookSignals, 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 sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const cwd = payload.cwd || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n if (transcriptPath) {\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n const usageBody = {\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'stop',\n verdict: 'allow',\n severity: 'none',\n model: usage.model || 'unknown',\n cc_model: usage.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 ...(sessionId ? { session_id: sessionId } : {}),\n };\n appendLocalTelemetry(usageBody);\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(usageBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\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 process.stderr.write('[synkro] stopSummary error: ' + String(err) + '\\\\n');\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, GATEWAY_URL,\n 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 cwd = payload.cwd || '';\n const sessionId = hookSessionId(payload);\n const gitRepo = detectRepo(cwd);\n if (gitRepo) writeCachedRepo(gitRepo);\n\n let jwt = loadJwt();\n\n const isChannelUp = await channelUp();\n const rt = isChannelUp ? 'local' : 'cloud';\n\n let policyName = '';\n let silent = false;\n let openFindings = 0;\n\n 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: [] };\n const tagStr = tag(rt, fakeConfig);\n const routeLine = tagStr + ' inference: ' + (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 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 process.stderr.write('[synkro] sessionStart error: ' + String(err) + '\\\\n');\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, 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 (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\n const config = await loadConfig(jwt);\n if (config.captureDepth !== 'local_only') {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\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,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, 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\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n const transcriptPath = payload.transcript_path || '';\n const cwd = payload.cwd || '';\n\n if (!sessionId || !transcriptPath || !existsSync(transcriptPath)) {\n outputEmpty();\n return;\n }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n const usageBody = {\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'stop',\n verdict: 'allow',\n severity: 'none',\n model: usage.model || 'unknown',\n cc_model: usage.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 session_id: sessionId,\n };\n appendLocalTelemetry(usageBody);\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(usageBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n\n if (process.env.SYNKRO_TRANSCRIPT_CONSENT === 'no') { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd);\n if (!gitRepo) { outputEmpty(); return; }\n\n let captureDepth = 'local_only';\n try {\n const r = await fetch(GATEWAY_URL + '/api/v1/hook/config', {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n const data = await r.json() as any;\n captureDepth = data.capture_depth || 'local_only';\n } catch {}\n\n if (captureDepth === 'local_only') { outputEmpty(); return; }\n\n const offsetDir = join(homedir(), '.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\n if (totalLines <= offset) { outputEmpty(); return; }\n\n let startIdx = offset;\n const delta = totalLines - offset;\n if (delta > 200) startIdx = totalLines - 200;\n\n const messages: any[] = [];\n for (let i = startIdx; i < totalLines; i++) {\n try {\n const entry = JSON.parse(allLines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content.slice(0, 8000);\n else if (Array.isArray(content)) {\n text = content.map((c: any) => {\n if (typeof c === 'string') return c;\n if (c?.type === 'text') return c.text || '';\n return '';\n }).join(' ').slice(0, 8000);\n }\n\n const msg: any = { message_index: i, type: entry.type, content: text };\n if (entry.type === 'assistant') {\n const toolCalls = (Array.isArray(content) ? content : [])\n .filter((c: any) => c?.type === 'tool_use')\n .map((c: any) => ({ name: c.name, input: JSON.stringify(c.input || {}).slice(0, 500), id: c.id }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n msg.model = entry.message?.model || null;\n const u = entry.message?.usage;\n if (u) msg.usage = { input_tokens: u.input_tokens, output_tokens: u.output_tokens, cache_creation_input_tokens: u.cache_creation_input_tokens, cache_read_input_tokens: u.cache_read_input_tokens };\n }\n messages.push(msg);\n } catch {}\n }\n\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n\n if (messages.length === 0) { outputEmpty(); return; }\n\n const syncBody = {\n repo: gitRepo,\n sessions: [{ cc_session_id: sessionId, messages }],\n };\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(syncBody),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\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 } 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 transcriptPath = payload.transcript_path || '';\n if (sessionId && transcriptPath) {\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n appendLocalTelemetry({\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'prompt_submit',\n session_id: sessionId,\n model: usage.model || 'unknown',\n cc_model: usage.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 });\n }\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, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log, GATEWAY_URL,\n type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, writeFileSync, mkdirSync } from 'node:fs';\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 = 7500;\nconst CURSOR_CLOUD_TIMEOUT_MS = 6000;\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 cwd = typeof payload.cwd === 'string' ? payload.cwd : '';\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 appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName, summary: command.slice(0, 120) });\n\n const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const KNOWN_MODELS = new Set(['gpt-4', 'gpt-4o', 'gpt-4.1', 'gpt-5', 'o1', 'o3', 'o4-mini', 'claude-sonnet-4-5', 'claude-opus-4-5', 'sonnet-4', 'sonnet-4-thinking', 'gemini-2.5-pro', 'gemini-2.5-flash']);\n const model = rawModel ? (KNOWN_MODELS.has(rawModel) || rawModel.startsWith('claude-') || rawModel.startsWith('gpt-') || rawModel.startsWith('gemini-') || rawModel.startsWith('o1') || rawModel.startsWith('o3') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n const repo = detectRepo(cwd);\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\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 rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const rulesBlock = config.rules.map((r: Rule, i: number) =>\n (i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text\n ).join('\\\\n');\n\n const graderPrompt = [\n 'RULES:',\n rulesBlock || '(none)',\n '',\n sessionLog,\n 'COMMAND TO EVALUATE:',\n command,\n '',\n 'User intent (last human message): ' + (transcript.userIntent || lastPrompt || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS);\n } catch (e) {\n log('bashGuard ' + cmdShort + ' → pass (grade unavailable): ' + String(e));\n finishWith({ permission: 'allow' });\n }\n\n const verdict = parseVerdict(gradeResp);\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' bashGuard → block: ' + guardReason,\n agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),\n });\n }\n\n dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n log('bashGuard ' + cmdShort + ' → audit warning');\n finishWith({ permission: 'allow' });\n } else {\n dispatchCapture(jwt, 'bash', 'pass', 'audit', verdict.category || 'clean',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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 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_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,\n appendSessionAction, readSessionLog, compressSessionLog,\n log, GATEWAY_URL,\n type Rule,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\n\nconst CURSOR_GRADE_TIMEOUT_MS = 7500;\nconst CURSOR_CLOUD_TIMEOUT_MS = 8000;\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\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finishAllow();\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const toolInput = payload.tool_input || {};\n const cwd = payload.cwd || '';\n const sessionId = payload.conversation_id || '';\n\n const filePath = toolInput.file_path || toolInput.path || toolInput.target_file || '';\n const content = toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n if (!filePath) finishAllow();\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 repo = detectRepo(cwd);\n\n let jwt = loadJwt();\n if (!jwt) finishAllow();\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n if (config.silent) finishAllow();\n\n const rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const contentShort = content.slice(0, 4000);\n const rulesBlock = config.rules.map((r: Rule, i: number) =>\n (i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text\n ).join('\\\\n');\n\n const graderPrompt = [\n 'RULES:',\n rulesBlock || '(none)',\n '',\n sessionLog,\n 'FILE: ' + filePath,\n '',\n 'CONTENT TO EVALUATE (first 4000 chars):',\n contentShort,\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt, CURSOR_GRADE_TIMEOUT_MS);\n } catch (e) {\n log('editGuard ' + fileShort + ' → pass (grade unavailable): ' + String(e));\n finishWith({ permission: 'allow' });\n }\n\n const verdict = parseVerdict(gradeResp);\n const editContent = 'file=' + filePath + ' content=' + content.slice(0, 2000);\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' editGuard ' + fileShort + ' → block: ' + guardReason,\n agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),\n });\n }\n\n dispatchCapture(jwt, 'edit', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n });\n const warnReason = verdict.reason || guardReason;\n log('editGuard ' + fileShort + ' → audit warning: ' + warnReason);\n finishWith({ permission: 'allow' });\n }\n\n dispatchCapture(jwt, 'edit', 'pass', 'audit', verdict.category || 'trivial_edit',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, violatedRules: [],\n });\n const passReason = verdict.reason || 'no policy violations detected';\n log('editGuard ' + fileShort + ' → pass: ' + passReason);\n finishWith({ permission: 'allow' });\n }\n\n const body = {\n hook_event: 'PreToolUse',\n tool_name: toolName || 'Edit',\n tool_input: { file_path: filePath, content },\n file_path: filePath,\n content,\n response_format: 'cursor',\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('editGuard ' + fileShort + ' → 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('editGuard ' + fileShort + ' → pass (no hook_response)');\n finishAllow();\n } catch (e) {\n log('editGuard error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('editGuard fatal: ' + String(e));\n finishAllow();\n});`;\n\nexport const CURSOR_EDIT_CAPTURE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, readStdin,\n appendSessionAction, appendLocalTelemetry, log, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\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);\n const filePath = payload.file_path || payload.path || payload.target_file || '';\n if (!filePath) finish();\n\n const cwd = payload.cwd || payload.workspace_roots?.[0] || '';\n const sessionId = payload.conversation_id || '';\n const repo = detectRepo(cwd);\n\n log('editScan ' + basename(filePath));\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Edit', summary: 'wrote ' + basename(filePath), file: filePath, outcome: 'ok' });\n\n let jwt = loadJwt();\n if (!jwt) finish();\n jwt = await ensureFreshJwt(jwt);\n\n let fileContent = '';\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n try {\n if (existsSync(fullPath)) {\n const buf = readFileSync(fullPath);\n fileContent = buf.slice(0, 50000).toString('utf-8');\n }\n } catch {}\n\n let dependencies: Record<string, string> = {};\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 captureBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, content: fileContent },\n edit_verdict: { ok: true },\n dependencies,\n };\n if (sessionId) captureBody.session_id = sessionId;\n if (cwd) captureBody.cwd = cwd;\n if (repo) captureBody.repo = repo;\n\n const rulesPath = join(homedir(), '.synkro', 'rules.json');\n if (existsSync(rulesPath)) {\n appendLocalTelemetry(captureBody);\n } else {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(captureBody),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n appendLocalTelemetry(captureBody);\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_BASH_FOLLOWUP_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, readStdin, appendSessionAction, appendLocalTelemetry, log, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\n\nconst CONSENT_FILE = join(homedir(), '.synkro', '.local-consent');\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\nfunction hashCmd(cmd: string): string {\n return createHash('sha256').update(cmd).digest('hex').slice(0, 16);\n}\n\nfunction consentGrant(sid: string, hash: string): void {\n try {\n const dir = dirname(CONSENT_FILE);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(CONSENT_FILE, sid + '\\\\t' + hash + '\\\\tactive\\\\n', 'utf-8');\n } catch {}\n}\n\nfunction consentHasActive(sid: 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(sid + '\\\\t' + hash + '\\\\tactive');\n } catch {\n return false;\n }\n}\n\nfunction consentConsume(sid: string, hash: string): void {\n try {\n if (!existsSync(CONSENT_FILE)) return;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n const target = sid + '\\\\t' + hash + '\\\\tactive';\n const replacement = sid + '\\\\t' + hash + '\\\\tconsumed';\n const updated = content.split('\\\\n').map((l: string) => l === target ? replacement : l).join('\\\\n');\n writeFileSync(CONSENT_FILE, updated, 'utf-8');\n } catch {}\n}\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finish();\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n\n const shellTools = ['Shell', 'Bash', 'terminal', 'run_terminal_cmd', 'execute_command'];\n if (!shellTools.includes(toolName)) finish();\n\n const sessionId = payload.conversation_id || '';\n const toolUseId = payload.tool_use_id || '';\n const command = payload.tool_input?.command || '';\n\n let isError = false;\n try {\n const output = JSON.parse(payload.tool_output || '{}');\n isError = output.exitCode !== 0 || output.is_error === true;\n } catch { isError = false; }\n\n const cmdHash = command ? hashCmd(command) : '';\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: command.slice(0, 120), outcome: isError ? 'exit 1' : 'exit 0' });\n\n const captureBody: Record<string, any> = {\n capture_type: 'bash_followup',\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n is_error: isError,\n command_hash: cmdHash,\n };\n\n const rulesPath = join(homedir(), '.synkro', 'rules.json');\n if (existsSync(rulesPath)) {\n appendLocalTelemetry(captureBody);\n } else {\n const jwt = loadJwt();\n if (jwt && sessionId && toolUseId) {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(captureBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n appendLocalTelemetry(captureBody);\n }\n\n finish();\n } catch (e) {\n log('bashFollowup error: ' + String(e));\n finish();\n }\n}\n\nmain().catch((e) => {\n log('bashFollowup fatal: ' + String(e));\n finish();\n});`;\n\nexport const CURSOR_SESSION_START_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, loadConfig, readStdin, log,\n type HookConfig,\n} from './_synkro-common.ts';\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\nasync function main() {\n try {\n const input = await readStdin();\n\n let jwt = loadJwt();\n const config: HookConfig = jwt ? await loadConfig(jwt) : {\n captureDepth: 'local_only', tier: 'standard', silent: false,\n policyName: '', rules: [], scanExemptions: [],\n };\n\n const policyName = config.policyName || 'default';\n const ruleCount = config.rules.length;\n const mode = config.silent ? 'silent' : 'active';\n\n const context = [\n 'This session is monitored by Synkro (' + mode + ' mode, policy: \"' + policyName + '\", ' + ruleCount + ' rules).',\n 'Synkro enforces security and compliance rules on tool calls (shell commands, file edits).',\n 'If a tool call is blocked, Synkro will explain which rule was violated and why.',\n 'Do not suggest workarounds to bypass Synkro hooks — fix the underlying issue instead.',\n ].join(' ');\n\n finishWith({ additional_context: context });\n } catch (e) {\n log('sessionStart error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('sessionStart fatal: ' + String(e));\n finishAllow();\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 } from 'node:child_process';\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 }).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 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\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 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 existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !existingFullNames.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 } catch {\n rl.close();\n }\n console.log();\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 * 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// 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 * 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`;\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`. Returns true if\n * a refresh actually happened, false if there was nothing in the keychain to\n * export.\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/pgdata/ (bind-mounted, persistent telemetry)\n * ~/.synkro/rules.json (bind-mounted, policy edits)\n * ~/.synkro/.mcp-jwt (bind-mounted RO, hook auth)\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 { existsSync, mkdirSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport {\n CLAUDE_CREDS_DIR,\n exportKeychainCreds,\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 SYNKRO_CREDS_PATH = join(SYNKRO_DIR, 'credentials.json');\nconst PGDATA_PATH = join(SYNKRO_DIR, 'pgdata');\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);\nconst HOST_PG_PORT = parseInt(process.env.SYNKRO_HOST_PG_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\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 */\nexport async function dockerInstall(opts: { workersPerPool?: number } = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n const workers = String(opts.workersPerPool ?? 4);\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 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 // 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 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 const plist = writeRefreshAgent('/usr/local/bin/synkro');\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 // Stop existing container (idempotent).\n spawnSync('docker', ['rm', '-f', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\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_PG_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${MCP_JWT_PATH}:/data/.mcp-jwt:ro`,\n '-v', `${SYNKRO_CREDS_PATH}:/data/credentials.json:ro`,\n '-v', `${credsDir}:/home/synkro/.claude:rw`,\n '-v', `${join(homedir(), '.claude')}:/data/claude-host:ro`,\n '-v', `${join(homedir(), '.claude.json')}:/home/synkro/.claude.json:rw`,\n '-e', `WORKERS_PER_POOL=${workers}`,\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 };\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\n/**\n * Stop the container (idempotent). Used by `synkro disconnect` and during\n * cutover when switching back to bare-host mode.\n */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n spawnSync('docker', ['rm', '-f', 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(workersPerPool?: number): Promise<void> {\n dockerStop();\n await dockerInstall({ workersPerPool });\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 * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (CC, Codex), 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 { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig } from '../installer/mcpConfig.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 } 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';\nimport { connectGitHub } from './setupGithub.js';\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { dockerInstall, waitForContainerReady } from '../local-cc/dockerInstall.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: 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}\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 === '--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\nasync function promptTranscriptConsent(): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Would you like Synkro to use Claude Code session transcripts\\n' +\n 'to generate guardrail rules and policies for your team? (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\nfunction 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 cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\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 cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-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(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, 'utf-8');\n writeFileSync(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, 'utf-8');\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_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(cursorBashJudgePath, 0o755);\n chmodSync(cursorEditCapturePath, 0o755);\n chmodSync(mcpStdioProxyPath, 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 cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\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' }): 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 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(): 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 }).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 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): 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();\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 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 // Idempotent short-circuit. If creds are still valid AND every required\n // surface is in place, skip the heavy steps (auth, hooks, daemon) but\n // still check whether the current repo needs linking — the user may be\n // running install from a second repo folder.\n if (!opts.force && isAuthenticated() && isAlreadyInstalled()) {\n setApiBaseUrl(`${gatewayUrl}/api`);\n await ensureValidToken();\n const currentRepo = detectGitRepo();\n if (currentRepo) {\n try {\n const projects = await listProjects();\n const alreadyLinked = projects.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === currentRepo),\n );\n if (!alreadyLinked) {\n console.log(`Synkro is installed. This repo (${currentRepo}) is not linked yet.\\n`);\n await promptRepoConnection();\n return;\n }\n } catch {\n // API unreachable — fall through to normal \"already installed\" message\n }\n }\n console.log('✓ Synkro is already installed and configured.');\n console.log(' Run `synkro install --force` to reinstall from scratch.');\n return;\n }\n\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. Connect GitHub via WorkOS Pipes (optional — only if user wants PR scanning)\n let ghToken: string | null = null;\n if (process.stdin.isTTY) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const wantsPR = await new Promise<boolean>((resolve) => {\n rl.question('Would you like to enable GitHub PR scanning? (y/N) ', (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n resolve(trimmed === 'y' || trimmed === 'yes');\n });\n });\n if (wantsPR) {\n try {\n ghToken = await connectGitHub(gatewayUrl, token);\n } catch {}\n if (ghToken) {\n console.log();\n } else {\n console.log(' Skipped. Run `synkro install` again to enable PR scanning.\\n');\n }\n } else {\n console.log(' Skipped PR scanning.\\n');\n }\n }\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 agents = detectAgents();\n if (agents.length === 0) {\n console.error('No AI coding agents detected. Install Claude Code first: https://docs.claude.com/claude-code');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts:');\n console.log(` ${scripts.bashScript}`);\n console.log(` ${scripts.bashFollowupScript}`);\n console.log(` ${scripts.editPrecheckScript}`);\n console.log(` ${scripts.cwePrecheckScript}`);\n console.log(` ${scripts.cvePrecheckScript}`);\n console.log(` ${scripts.planJudgeScript}`);\n console.log(` ${scripts.agentJudgeScript}`);\n console.log(` ${scripts.stopSummaryScript}`);\n console.log(` ${scripts.sessionStartScript}`);\n console.log(` ${scripts.transcriptSyncScript}\\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 — disabled for now, will revisit.\n // let transcriptConsent = true;\n // if (process.stdin.isTTY) {\n // transcriptConsent = await promptTranscriptConsent();\n // if (transcriptConsent) {\n // console.log(' ✓ Transcript collection enabled\\n');\n // } else {\n // console.log(' ✗ Transcript collection disabled — skipping transcript sync\\n');\n // }\n // }\n const transcriptConsent = false;\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 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 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 });\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);\n const useLocalMcp = profile.captureDepth === 'local_only' || profile.localInference;\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 });\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 console.log();\n\n if (profile.localInference) {\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 console.log('Installing Synkro server container...');\n const workersPerPool = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '4', 10);\n const { image, hostMcpPort, hostGraderPort, hostCwePort } =\n await dockerInstall({ workersPerPool });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);\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 } 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. Ingest CC session transcripts (only if user consented).\n if (transcriptConsent) {\n try {\n const repo = detectGitRepo();\n if (repo) {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(`Indexed ${ingested} session insights from Claude Code history for ${repo}.`);\n console.log(' This helps the safety judge understand your workflow.\\n');\n }\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n\n // 7b. Bulk sync CC session transcripts into agent_sessions/agent_messages.\n try {\n const repo = detectGitRepo();\n if (repo) {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(`Synced ${result.sessions} sessions (${result.messages} messages) from Claude Code history.`);\n console.log(' This data will be used to suggest guardrail rules.\\n');\n }\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\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\nfunction detectGitRepo(): string | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n return match ? match[1] : null;\n } catch {\n return null;\n }\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 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 * Optionally also removes ~/.synkro/ entirely if --purge flag set.\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 } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\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 { dockerStop, dockerStatus, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nfunction tearDownLocalCC(purge: boolean): void {\n const docker = dockerStatus();\n if (docker.running) {\n dockerStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n\n if (purge) {\n try {\n const image = imageTag();\n spawnSync('docker', ['rmi', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(`✓ removed Docker image ${image}`);\n } catch { /* docker may not be installed */ }\n }\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\nexport function disconnectCommand(args: string[] = []): void {\n const purge = args.includes('--purge');\n\n console.log('Synkro disconnect starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n tearDownLocalCC(purge);\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 if (purge) {\n if (existsSync(SYNKRO_DIR)) {\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n console.log(`✓ Removed ${SYNKRO_DIR}`);\n } else {\n console.log(`· ${SYNKRO_DIR} already gone, nothing to remove`);\n }\n } else if (existsSync(SYNKRO_DIR)) {\n console.log(`Config preserved at ${SYNKRO_DIR}. Run with --purge to remove.`);\n }\n\n console.log('\\nSynkro disconnected.');\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","#!/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.cwd(), '.env'),\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 hooks (--purge also removes ~/.synkro)\n version Show version\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 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(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 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":";;;;;;;;;;;;;;;;;;AAMA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAazB,SAAS,MAAMA,MAAiC;AAC9C,MAAI;AACF,UAAM,SAAS,SAAS,SAASA,IAAG,IAAI,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AACpE,WAAO,UAAU;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAWA,MAAiC;AACnD,MAAI;AACF,UAAM,SAAS,SAAS,GAAGA,IAAG,mBAAmB,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AAC5F,WAAO,OAAO,MAAM,IAAI,EAAE,CAAC;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAgC;AAC9C,QAAM,SAA0B,CAAC;AACjC,QAAM,OAAO,QAAQ;AAGrB,QAAM,eAAe,MAAM,QAAQ;AACnC,QAAM,kBAAkB,KAAK,MAAM,SAAS;AAC5C,MAAI,gBAAgB,WAAW,eAAe,GAAG;AAC/C,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,cAAc,KAAK,iBAAiB,eAAe;AAAA,MACnD,SAAS,eAAe,WAAW,QAAQ,IAAI;AAAA,IACjD,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,MAAM,OAAO;AACjC,QAAM,iBAAiB,KAAK,MAAM,QAAQ;AAC1C,MAAI,eAAe,WAAW,cAAc,GAAG;AAC7C,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,cAAc,KAAK,gBAAgB,aAAa;AAAA,MAChD,SAAS,cAAc,WAAW,OAAO,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH;AAGA,QAAM,eAAe,MAAM,QAAQ;AACnC,QAAM,kBAAkB,KAAK,MAAM,SAAS;AAC5C,MAAI,gBAAgB,WAAW,eAAe,GAAG;AAC/C,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,cAAc,KAAK,iBAAiB,YAAY;AAAA,MAChD,SAAS,eAAe,WAAW,QAAQ,IAAI;AAAA,IACjD,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAvFA;AAAA;AAAA;AAAA;AAAA;;;ACOA,SAAS,cAAAC,aAAY,cAAc,eAAe,YAAY,iBAA6B;AAC3F,SAAS,eAAe;AAqCxB,SAAS,aAAa,MAA0B;AAC9C,MAAI,CAACA,YAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACF,UAAM,MAAM,aAAa,MAAM,OAAO;AACtC,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,mBAAmB,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,EACtE;AACF;AAEA,SAAS,oBAAoB,MAAc,UAA4B;AACrE,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,UAAU,GAAG,IAAI;AACvB,gBAAc,SAAS,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,MAAM,OAAO;AACxE,aAAW,SAAS,IAAI;AAC1B;AAEA,SAAS,cAAc,OAAqB;AAC1C,MAAI,QAAQ,aAAa,EAAG,QAAO;AACnC,QAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC3D,SAAO,MAAM;AAAA,IAAK,CAAC,MACjB,OAAO,GAAG,YAAY,YAAY,EAAE,QAAQ,SAAS,iBAAiB;AAAA,EACxE;AACF;AAEA,SAAS,oBAAoB,QAAyD,WAAyB;AAC7G,MAAI,CAAC,OAAQ;AACb,QAAM,MAAO,OAAe,SAAS;AACrC,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG;AACzB,EAAC,OAAe,SAAS,IAAI,IAAI,OAAO,CAAC,UAAe,CAAC,cAAc,KAAK,CAAC;AAC/E;AAMO,SAAS,eAAe,cAAsB,QAAgC;AACnF,QAAM,WAAW,aAAa,YAAY;AAC1C,WAAS,QAAQ,SAAS,SAAS,CAAC;AAGpC,sBAAoB,SAAS,OAAc,YAAY;AACvD,sBAAoB,SAAS,OAAc,aAAa;AACxD,sBAAoB,SAAS,OAAc,YAAY;AACvD,sBAAoB,SAAS,OAAc,cAAc;AACzD,sBAAoB,SAAS,OAAc,kBAAkB;AAE7D,sBAAoB,SAAS,OAAc,MAAM;AAEjD,WAAS,MAAM,aAAa,SAAS,MAAM,cAAc,CAAC;AAC1D,WAAS,MAAM,cAAc,SAAS,MAAM,eAAe,CAAC;AAC5D,WAAS,MAAM,aAAa,SAAS,MAAM,cAAc,CAAC;AAC1D,WAAS,MAAM,eAAe,SAAS,MAAM,gBAAgB,CAAC;AAC9D,WAAS,MAAM,mBAAoB,SAAS,MAAM,oBAA8B,CAAC;AAGjF,WAAS,MAAM,WAAW,KAAK;AAAA,IAC7B,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAMR,WAAS,MAAM,WAAW,KAAK;AAAA,IAC7B,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAGR,WAAS,MAAM,WAAW,KAAK;AAAA,IAC7B,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAGR,WAAS,MAAM,WAAW,KAAK;AAAA,IAC7B,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAOR,WAAS,MAAM,YAAY,KAAK;AAAA,IAC9B,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAMR,WAAS,MAAM,WAAW,KAAK;AAAA,IAC7B,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAGR,WAAS,MAAM,aAAa,KAAK;AAAA,IAC/B,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAIR,EAAC,SAAS,MAAM,iBAA2B,KAAK;AAAA,IAC9C,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAIR,WAAS,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AAC9C,sBAAoB,SAAS,OAAc,MAAM;AACjD,WAAS,MAAM,KAAK,KAAK;AAAA,IACvB,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,SAAS,OAAO;AAAA,QAChB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,CAAC,aAAa,GAAG;AAAA,EACnB,CAAQ;AAER,sBAAoB,cAAc,QAAQ;AAC5C;AAMO,SAAS,iBAAiB,cAA+B;AAC9D,MAAI,CAACA,YAAW,YAAY,EAAG,QAAO;AACtC,QAAM,WAAW,aAAa,YAAY;AAC1C,MAAI,CAAC,SAAS,MAAO,QAAO;AAE5B,QAAM,SAAS,CAAC,cAAc,eAAe,cAAc,gBAAgB,QAAQ,kBAAkB;AACrG,aAAW,OAAO,QAAQ;AACxB,wBAAoB,SAAS,OAAc,GAAG;AAAA,EAChD;AAGA,aAAW,OAAO,QAAQ;AACxB,QAAI,MAAM,QAAS,SAAS,MAAc,GAAG,CAAC,KAAM,SAAS,MAAc,GAAG,EAAE,WAAW,GAAG;AAC5F,aAAQ,SAAS,MAAc,GAAG;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,SAAS,KAAK,EAAE,WAAW,GAAG;AAC5C,WAAO,SAAS;AAAA,EAClB;AAEA,sBAAoB,cAAc,QAAQ;AAC1C,SAAO;AACT;AAxQA,IAyBM;AAzBN;AAAA;AAAA;AAyBA,IAAM,gBAAgB;AAAA;AAAA;;;ACjBtB,SAAS,gBAAAC,eAAc,iBAAAC,gBAAe,cAAAC,aAAY,aAAAC,kBAAiB;AACnE,SAAS,WAAAC,UAAS,SAAS,iBAAiB;AAC5C,SAAS,WAAAC,gBAAe;AAsBxB,SAAS,WAAW,GAAmB;AACrC,SAAO,MAAM,EAAE,QAAQ,MAAM,OAAO,IAAI;AAC1C;AAGA,SAAS,YAAY,YAA4B;AAC/C,SAAO,2CAA2C,WAAW,UAAU;AACzE;AAEA,SAAS,UAAU,YAA4B;AAC7C,SAAO,aAAa,WAAW,UAAU;AAC3C;AAOA,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,WAAW,QAAQ,UAAU,IAAI,CAAC;AACxC,MAAI,CAAC,oBAAoB,KAAK,SAAO,SAAS,WAAW,MAAM,GAAG,KAAK,aAAa,GAAG,GAAG;AACxF,UAAM,IAAI,MAAM,gEAAgE,QAAQ,EAAE;AAAA,EAC5F;AACA,SAAO;AACT;AAiBA,SAAS,cAAc,SAAkC;AACvD,QAAM,WAAW,kBAAkB,OAAO;AAC1C,MAAI;AACF,UAAM,MAAML,cAAa,UAAU,OAAO;AAC1C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAU;AACjB,QAAI,KAAK,SAAS,SAAU,QAAO,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AAC3D,UAAM,IAAI,MAAM,mBAAmB,QAAQ,KAAM,IAAc,OAAO,EAAE;AAAA,EAC1E;AACF;AAEA,SAAS,qBAAqB,SAAiB,MAA6B;AAC1E,QAAM,WAAW,kBAAkB,OAAO;AAC1C,EAAAG,WAAUC,SAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,UAAU,GAAG,QAAQ;AAC3B,EAAAH,eAAc,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AAC/F,EAAAC,YAAW,SAAS,QAAQ;AAC9B;AAEA,SAASI,eAAc,OAAqB;AAC1C,MAAI,QAAQC,cAAa,EAAG,QAAO;AACnC,SAAO,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,SAAS,iBAAiB;AACvF;AAQA,SAASC,qBAAoB,OAAiC,OAAqB;AACjF,MAAI,CAAC,MAAO;AACZ,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG;AACzB,QAAM,KAAK,IAAI,IAAI,OAAO,CAAC,UAAe,CAACF,eAAc,KAAK,CAAC;AACjE;AAEA,SAAS,WACP,OACA,OACA,YACA,MACM;AACN,QAAO,KAAK,IAAI,MAAO,KAAK,KAAK,CAAC;AAClC,QAAO,KAAK,EAAG,KAAK;AAAA,IAClB,SAAS,YAAY,UAAU;AAAA,IAC/B,SAAS,KAAK;AAAA,IACd,YAAY,KAAK,cAAc;AAAA,IAC/B,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAChD,CAACC,cAAa,GAAG;AAAA,EACnB,CAAC;AACH;AAEO,SAAS,mBAAmB,eAAuB,QAAgC;AACxF,QAAM,OAAO,cAAc,aAAa;AACxC,OAAK,UAAU,KAAK,WAAW;AAC/B,OAAK,QAAQ,KAAK,SAAS,CAAC;AAE5B,aAAW,OAAO,YAAY;AAC5B,IAAAC,qBAAoB,KAAK,OAAO,GAAG;AAAA,EACrC;AAEA,QAAM,IAAI,KAAK;AAEf,aAAW,GAAG,gBAAgB,OAAO,wBAAwB,EAAE,SAAS,EAAE,CAAC;AAC3E,aAAW,GAAG,cAAc,OAAO,uBAAuB,EAAE,SAAS,GAAG,CAAC;AACzE,aAAW,GAAG,sBAAsB,OAAO,4BAA4B,EAAE,SAAS,EAAE,CAAC;AACrF,aAAW,GAAG,QAAQ,OAAO,0BAA0B,EAAE,SAAS,EAAE,CAAC;AAErE,IAAE,uBAAuB,EAAE,wBAAwB,CAAC;AACpD,IAAE,qBAAqB,KAAK;AAAA,IAC1B,SAAS,UAAU,OAAO,mBAAmB;AAAA,IAC7C,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,CAACD,cAAa,GAAG;AAAA,EACnB,CAAC;AAED,aAAW,GAAG,uBAAuB,OAAO,wBAAwB,EAAE,SAAS,GAAG,CAAC;AAGnF,IAAE,aAAa,EAAE,cAAc,CAAC;AAChC,IAAE,WAAW,KAAK;AAAA,IAChB,SAAS,UAAU,OAAO,mBAAmB;AAAA,IAC7C,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,CAACA,cAAa,GAAG;AAAA,EACnB,CAAC;AAED,aAAW,GAAG,cAAc,OAAO,wBAAwB;AAAA,IACzD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,aAAW,GAAG,cAAc,OAAO,uBAAuB;AAAA,IACxD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,aAAW,GAAG,cAAc,OAAO,uBAAuB;AAAA,IACxD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,aAAW,GAAG,cAAc,OAAO,sBAAsB;AAAA,IACvD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,aAAW,GAAG,cAAc,OAAO,qBAAqB;AAAA,IACtD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AAED,IAAE,gBAAgB,EAAE,iBAAiB,CAAC;AACtC,IAAE,cAAc,KAAK;AAAA,IACnB,SAAS,UAAU,OAAO,qBAAqB;AAAA,IAC/C,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,CAACA,cAAa,GAAG;AAAA,EACnB,CAAC;AAED,aAAW,GAAG,eAAe,OAAO,wBAAwB;AAAA,IAC1D,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AAED,uBAAqB,eAAe,IAAI;AAC1C;AAEO,SAAS,qBAAqB,eAAgC;AACnE,MAAI;AACJ,MAAI;AACF,WAAO,cAAc,aAAa;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,KAAK,MAAO,QAAO;AAExB,aAAW,OAAO,YAAY;AAC5B,IAAAC,qBAAoB,KAAK,OAAO,GAAG;AAAA,EACrC;AAEA,aAAW,OAAO,YAAY;AAC5B,QAAI,MAAM,QAAQ,KAAK,MAAM,GAAG,CAAC,KAAK,KAAK,MAAM,GAAG,EAAE,WAAW,GAAG;AAClE,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB;AAAA,EACF;AACA,MAAI,OAAO,KAAK,KAAK,KAAK,EAAE,WAAW,GAAG;AACxC,WAAO,KAAK;AAAA,EACd;AAEA,uBAAqB,eAAe,IAAI;AACxC,SAAO;AACT;AA/NA,IA8BMD,gBAeA,qBAoDA;AAjGN;AAAA;AAAA;AA8BA,IAAMA,iBAAgB;AAetB,IAAM,sBAAsB;AAAA,MAC1B,QAAQF,SAAQ,GAAG,SAAS;AAAA,MAC5B,QAAQA,SAAQ,GAAG,WAAW,QAAQ;AAAA,IACxC;AAiDA,IAAM,aAAa;AAAA,MACjB;AAAA,MAAgB;AAAA,MAAc;AAAA,MAAsB;AAAA,MACpD;AAAA,MAAwB;AAAA,MACxB;AAAA,MAAc;AAAA,MAAiB;AAAA,IACjC;AAAA;AAAA;;;ACzFA,SAAS,cAAAI,aAAY,gBAAAC,eAAc,iBAAAC,gBAAe,cAAAC,aAAY,aAAAC,kBAAiB;AAC/E,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAsB9B,SAAS,iBAA6B;AACpC,MAAI,CAACP,YAAW,cAAc,EAAG,QAAO,CAAC;AACzC,MAAI;AACF,UAAM,MAAMC,cAAa,gBAAgB,OAAO;AAChD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,mBAAmB,cAAc,KAAM,IAAc,OAAO,EAAE;AAAA,EAChF;AACF;AAEA,SAAS,sBAAsB,QAA0B;AACvD,EAAAG,WAAUE,SAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,QAAM,UAAU,GAAG,cAAc;AACjC,EAAAJ,eAAc,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AACtE,EAAAC,YAAW,SAAS,cAAc;AACpC;AAgBO,SAAS,iBAAiB,MAAwD;AACvF,QAAM,SAAS,eAAe;AAC9B,SAAO,aAAa,OAAO,cAAc,CAAC;AAI1C,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAC7D,QAAI,QAAQK,cAAa,MAAM,KAAM,QAAO,OAAO,WAAW,IAAI;AAAA,EACpE;AAEA,MAAI,KAAK,OAAO;AACd,UAAM,cAAcD,MAAKF,SAAQ,GAAG,WAAW,SAAS,oBAAoB;AAC5E,WAAO,WAAW,kBAAkB,IAAI;AAAA,MACtC,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM,CAAC,OAAO,WAAW;AAAA,MACzB,CAACG,cAAa,GAAG;AAAA,IACnB;AACA,0BAAsB,MAAM;AAC5B,WAAO,EAAE,MAAM,gBAAgB,KAAK,WAAW,WAAW,GAAG;AAAA,EAC/D;AAEA,QAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,OAAO,EAAE,CAAC;AACjD,SAAO,WAAW,kBAAkB,IAAI;AAAA,IACtC,MAAM;AAAA,IACN;AAAA,IACA,SAAS,EAAE,eAAe,UAAU,KAAK,WAAW,GAAG;AAAA,IACvD,CAACA,cAAa,GAAG;AAAA,EACnB;AAEA,wBAAsB,MAAM;AAC5B,SAAO,EAAE,MAAM,gBAAgB,IAAI;AACrC;AAMO,SAAS,qBAA8B;AAC5C,MAAI,CAACR,YAAW,cAAc,EAAG,QAAO;AACxC,QAAM,SAAS,eAAe;AAC9B,MAAI,CAAC,OAAO,cAAc,OAAO,KAAK,OAAO,UAAU,EAAE,WAAW,EAAG,QAAO;AAE9E,MAAI,UAAU;AACd,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAC7D,QAAI,QAAQQ,cAAa,MAAM,MAAM;AACnC,aAAO,OAAO,WAAW,IAAI;AAC7B,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,OAAO,KAAK,OAAO,UAAU,EAAE,WAAW,EAAG,QAAO,OAAO;AAE/D,wBAAsB,MAAM;AAC5B,SAAO;AACT;AA8BA,SAAS,oBAAmC;AAC1C,MAAI,CAACR,YAAW,eAAe,EAAG,QAAO,CAAC;AAC1C,MAAI;AACF,UAAM,MAAMC,cAAa,iBAAiB,OAAO;AACjD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,mBAAmB,eAAe,KAAM,IAAc,OAAO,EAAE;AAAA,EACjF;AACF;AAEA,SAAS,yBAAyB,QAA6B;AAC7D,EAAAG,WAAUE,SAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,QAAM,UAAU,GAAG,eAAe;AAClC,EAAAJ,eAAc,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AACtE,EAAAC,YAAW,SAAS,eAAe;AACrC;AAMO,SAAS,uBAAuB,MAAwD;AAC7F,QAAM,SAAS,kBAAkB;AACjC,SAAO,aAAa,OAAO,cAAc,CAAC;AAE1C,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAC7D,QAAI,QAAQK,cAAa,MAAM,KAAM,QAAO,OAAO,WAAW,IAAI;AAAA,EACpE;AAEA,MAAI,KAAK,OAAO;AAGd,UAAM,OAAO,QAAQ,IAAI,mBAAmB;AAC5C,UAAMC,OAAM,oBAAoB,IAAI;AACpC,UAAM,UAAUF,MAAKF,SAAQ,GAAG,WAAW,UAAU;AACrD,QAAIK,OAAM;AACV,QAAI;AAAE,MAAAA,OAAMT,cAAa,SAAS,OAAO,EAAE,KAAK;AAAA,IAAG,QAAQ;AAAA,IAAC;AAC5D,WAAO,WAAW,kBAAkB,IAAI;AAAA,MACtC,KAAAQ;AAAA,MACA,GAAIC,OAAM,EAAE,SAAS,EAAE,eAAe,UAAUA,IAAG,GAAG,EAAE,IAAI,CAAC;AAAA,MAC7D,CAACF,cAAa,GAAG;AAAA,IACnB;AACA,6BAAyB,MAAM;AAC/B,WAAO,EAAE,MAAM,iBAAiB,KAAAC,KAAI;AAAA,EACtC;AAEA,QAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,OAAO,EAAE,CAAC;AACjD,SAAO,WAAW,kBAAkB,IAAI;AAAA,IACtC;AAAA,IACA,SAAS,EAAE,eAAe,UAAU,KAAK,WAAW,GAAG;AAAA,IACvD,CAACD,cAAa,GAAG;AAAA,EACnB;AAEA,2BAAyB,MAAM;AAC/B,SAAO,EAAE,MAAM,iBAAiB,IAAI;AACtC;AAKO,SAAS,2BAAoC;AAClD,MAAI,CAACR,YAAW,eAAe,EAAG,QAAO;AACzC,QAAM,SAAS,kBAAkB;AACjC,MAAI,CAAC,OAAO,cAAc,OAAO,KAAK,OAAO,UAAU,EAAE,WAAW,EAAG,QAAO;AAE9E,MAAI,UAAU;AACd,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,GAAG;AAC7D,QAAI,QAAQQ,cAAa,MAAM,MAAM;AACnC,aAAO,OAAO,WAAW,IAAI;AAC7B,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,OAAO,KAAK,OAAO,UAAU,EAAE,WAAW,EAAG,QAAO,OAAO;AAE/D,2BAAyB,MAAM;AAC/B,SAAO;AACT;AAxOA,IAgBMA,gBACA,oBACA,gBAiIA;AAnJN;AAAA;AAAA;AAgBA,IAAMA,iBAAgB;AACtB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiBD,MAAKF,SAAQ,GAAG,cAAc;AAiIrD,IAAM,kBAAkBE,MAAKF,SAAQ,GAAG,WAAW,UAAU;AAAA;AAAA;;;ACnJ7D,IASa;AATb;AAAA;AAAA;AASO,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;;;ACTpC,IASa,kBAuoCA,kBA8NA,iBAweA,iBAkKA,eA6SA,gBA6KA,eAiLA,iBA8FA,kBAsEA,kBA6EA,oBAmJA,uBA0DA,sBA0ZA;AA39Gb;AAAA;AAAA;AASO,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuoCzB,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;AA8NzB,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;AAwe/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;AAkKxB,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;AA6S7B,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;AA6KvB,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;AAiLtB,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;AA8FxB,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;AAsEzB,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;AA6EzB,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmJ3B,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0D9B,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;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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","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","createInterface","execSync","existsSync","readFileSync","unlinkSync","homedir","platform","join","execFile","resolve","openBrowser","args","jwt","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","platform","join","SYNKRO_DIR","SYNKRO_DIR","existsSync","mkdirSync","homedir","join","spawnSync","args","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","homedir","join","execSync","createInterface","SYNKRO_DIR","CONFIG_PATH","detectGitRepo","resolve","assertDockerAvailable","setupGithubCommand","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","renameSync","unlinkSync","join","homedir","spawnSync","init_install","existsSync","homedir","join","spawnSync","args","SYNKRO_DIR","init_install","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","resolve","args","readFileSync","existsSync","resolve","installCommand","parseArgs","disconnectCommand","gradeCommand"]}
1
+ {"version":3,"sources":["../cli/installer/agentDetect.ts","../cli/installer/ccHookConfig.ts","../cli/installer/cursorHookConfig.ts","../cli/installer/mcpConfig.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/commands/setupGithub.ts","../cli/installer/promptFetcher.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.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/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' | 'codex' | '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\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary || existsSync(claudeConfigDir)) {\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: claudeBinary ? getVersion('claude') : undefined,\n });\n }\n\n // Codex (OpenAI's CLI)\n const codexBinary = which('codex');\n const codexConfigDir = join(home, '.codex');\n if (codexBinary || existsSync(codexConfigDir)) {\n agents.push({\n kind: 'codex',\n name: 'Codex',\n binaryPath: codexBinary,\n configDir: codexConfigDir,\n settingsPath: join(codexConfigDir, 'config.toml'),\n version: codexBinary ? getVersion('codex') : undefined,\n });\n }\n\n // Cursor\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n if (cursorBinary || existsSync(cursorConfigDir)) {\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version: cursorBinary ? getVersion('cursor') : undefined,\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 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/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 /** 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}\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];\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 h.beforeShellExecution = h.beforeShellExecution ?? [];\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 covers non-shell tools (Read, Edit, etc.) + dedup prevents double-grading shell commands\n h.preToolUse = h.preToolUse ?? [];\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 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","// :)\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_RULES_FILE=\"$HOME/.synkro/rules.json\"\n_SYNKRO_MCP_JWT_FILE=\"$HOME/.synkro/.mcp-jwt\"\n\nsynkro_load_config() {\n # Local-first: read from ~/.synkro/rules.json if it exists (zero latency, no network)\n if [ -f \"$_SYNKRO_RULES_FILE\" ]; then\n local rdata\n rdata=$(cat \"$_SYNKRO_RULES_FILE\" 2>/dev/null)\n if [ -n \"$rdata\" ]; then\n SYNKRO_CAPTURE_DEPTH=\"local_only\"\n SYNKRO_TIER=\"standard\"\n SYNKRO_SILENT=$(echo \"$rdata\" | jq -r '.config.silent // false' 2>/dev/null)\n local active_id\n active_id=$(echo \"$rdata\" | jq -r '.config.activePolicyId // empty' 2>/dev/null)\n if [ -n \"$active_id\" ]; then\n SYNKRO_POLICY_NAME=$(echo \"$rdata\" | jq -r --arg id \"$active_id\" '.policies[]? | select(.id == $id) | .name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$rdata\" | jq -c --arg id \"$active_id\" '[.policies[]? | select(.id == $id) | .rules[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n else\n SYNKRO_POLICY_NAME=$(echo \"$rdata\" | jq -r '.policies[0]?.name // empty' 2>/dev/null)\n SYNKRO_RULES=$(echo \"$rdata\" | jq -c '[.policies[0]?.rules[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo \"[]\")\n fi\n return\n fi\n fi\n\n # Fallback: fetch from cloud API\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[]? | select(.hook_stage == \"pre\" or .hook_stage == \"both\" or .hook_stage == null) | {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 with prompts from /v1/hook/config, send anonymized metadata to /v1/hook/capture.\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 } from 'node:fs';\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\n// ─── Logging ───\n\nexport function log(msg: string): void {\n process.stderr.write('[synkro] ' + msg + '\\\\n');\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\nexport function detectRepo(cwd: string, transcriptPath?: string): string {\n let resolvedCwd = cwd;\n if (!resolvedCwd && transcriptPath) {\n const m = transcriptPath.match(/\\\\/projects\\\\/(-[^/]+)\\\\//);\n if (m) resolvedCwd = '/' + m[1].slice(1).replace(/-/g, '/');\n }\n if (!resolvedCwd) resolvedCwd = process.cwd();\n try {\n const url = execSync('git remote get-url origin 2>/dev/null', { cwd: resolvedCwd, 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: resolvedCwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (root) return root.split('/').pop() || '';\n } catch {}\n return '';\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// ─── Config Loading ───\n\nexport interface Rule {\n rule_id: string;\n text: string;\n severity: string;\n category: string;\n mode: string;\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}\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 };\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 = (policy.rules || [])\n .filter((r: any) => r.hook_stage === 'pre' || r.hook_stage === 'both' || r.hook_stage == null)\n .map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: r.mode || 'blocking',\n }));\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 // Fallback: fetch from cloud API\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 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)) {\n config.rules = data.rules\n .filter((r: any) => r.hook_stage === 'pre' || r.hook_stage === 'both' || r.hook_stage == null)\n .map((r: any) => ({\n rule_id: r.rule_id || '',\n text: r.text || '',\n severity: r.severity || '',\n category: r.category || '',\n mode: r.mode || 'blocking',\n }));\n }\n } catch {}\n return config;\n}\n\n// ─── Routing ───\n\nexport async function route(config: HookConfig): Promise<'local' | '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): Promise<'local' | 'cloud'> {\n if (config.captureDepth === 'local_only') return 'local';\n if (await cweChannelUp()) return 'local';\n return 'cloud';\n}\n\n// ─── Tag Building ───\n\nexport function tag(rt: string, config: HookConfig): string {\n if (config.silent) return '[synkro:silent]';\n const rs = config.policyName || 'all';\n return '[synkro:' + rt + ':' + rs + ']';\n}\n\n// ─── Local Grading (direct channel call) ───\n\ntype GradeRole = 'grade-edit' | 'grade-bash' | 'grade-plan' | 'grade-cwe';\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 = 20000): Promise<string> {\n const body = JSON.stringify({ role, payload: prompt, content: prompt });\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\nexport async function localGrade(surface: string, prompt: string, timeoutMs = 20000): Promise<string> {\n if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs);\n}\n\nexport async function localGradeCwe(prompt: string): Promise<string> {\n const jwt = loadJwt();\n if (!jwt) throw new Error('NO_JWT');\n return channelGrade('grade-cwe', prompt, jwt, 18930, 45000);\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 try {\n mkdirSync(SESSIONS_DIR, { recursive: true });\n appendFileSync(logPath, JSON.stringify(entry) + '\\\\n', 'utf-8');\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 > 30) {\n const old = actions.slice(0, total - 30);\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 - 30);\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\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 },\n): void {\n // Fire-and-forget\n const eventId = 'evt_' + Date.now() + '_' + process.pid;\n const model = opts?.ccModel || 'unknown';\n const sendFull =\n captureDepth === 'full' ||\n (captureDepth === 'evidence_on_violation' && ['block', 'warning', 'deny'].includes(verdictStr));\n\n const body: Record<string, any> = {\n capture_type: 'local_verdict',\n event_id: eventId,\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 }\n appendLocalTelemetry(localBody);\n\n // local_only: no data leaves the machine\n if (captureDepth === 'local_only') return;\n\n if (sendFull && opts) {\n body.capture_depth = captureDepth;\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\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n}\n\nexport function appendLocalTelemetry(body: Record<string, any>): void {\n const event = { ...body, _ts: new Date().toISOString() };\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/ingest\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },\n body: JSON.stringify({ data: event }),\n signal: AbortSignal.timeout(2000),\n }).catch(() => {});\n}\n\n// ─── Rule Mode Lookup ───\n\nexport function ruleMode(ruleId: string, rules: Rule[]): 'blocking' | 'audit' {\n if (!ruleId || !rules.length) return 'blocking';\n const matched = rules.filter(r => r.rule_id === ruleId);\n if (matched.some(r => r.mode === 'blocking')) return 'blocking';\n return (matched[0]?.mode as 'blocking' | 'audit') || 'blocking';\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 || '';\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 Extraction ───\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 (entry.type !== 'user') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content;\n else if (Array.isArray(content)) text = content.map((c: any) => c.text || '').join(' ');\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 if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content.slice(0, 500);\n else if (Array.isArray(content)) text = content.map((c: any) => (c.text || '').slice(0, 300)).join(' ');\n msgs.push({ type: entry.type, 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 (entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use') continue;\n actions.push({\n tool: block.name || '',\n input: JSON.stringify(block.input || {}).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 => e.type === '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// ─── Transcript Usage Aggregation ───\n\nexport function aggregateUsage(transcriptPath: string): { model: string; totals: Record<string, number> } {\n const result = { model: '', 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 lines = raw.split('\\\\n').filter(l => l.trim());\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n if (entry.type !== 'assistant') continue;\n result.model = entry.message?.model || result.model;\n const u = entry.message?.usage;\n if (u) {\n result.totals.in += u.input_tokens || 0;\n result.totals.out += u.output_tokens || 0;\n result.totals.cw += u.cache_creation_input_tokens || 0;\n result.totals.cr += u.cache_read_input_tokens || 0;\n }\n } catch {}\n }\n } catch {}\n return result;\n}\n\n// ─── Scan Finding Dispatch ───\n\nexport function dispatchFinding(\n jwt: string,\n finding: {\n session_id: string;\n file_path: 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 if (captureDepth === 'local_only') return;\n\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 };\n\n if (captureDepth === 'evidence_on_violation' || captureDepth === 'full') {\n cloudBody.file_path = finding.file_path;\n cloudBody.package_name = finding.package_name;\n cloudBody.package_version = finding.package_version;\n cloudBody.fixed_version = finding.fixed_version;\n cloudBody.aliases = finding.aliases;\n cloudBody.references = finding.references;\n cloudBody.cwe_name = finding.cwe_name;\n }\n if (captureDepth === 'full') {\n cloudBody.detail = finding.detail;\n cloudBody.description = finding.description;\n }\n\n fetch(GATEWAY_URL + '/api/v1/hook/finding', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(cloudBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\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';\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// ─── 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 type HookConfig, type Rule,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\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);\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 // Read file before edit for cloud payload\n let fileBefore = '';\n if (toolName !== 'Write' && filePath && isPathUnder(filePath, cwd || '.') && existsSync(filePath)) {\n try { fileBefore = readFileSync(filePath, 'utf-8').slice(0, 65536); } catch {}\n }\n\n // Extract transcript context\n const transcript = extractTranscript(transcriptPath);\n const lastPrompt = readLastPrompt(sessionId);\n\n // Load config and decide route\n const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 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(config.rules),\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 \"audit\". The enforcement layer handles audit vs blocking — your job is only to detect violations.',\n ].join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' → local grader unavailable, skipped' });\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 = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n const denyReason = 'Guard: ' + guardReason + '\\\\nFix all issues before retrying. Do NOT ask the user to make the edit manually — resolve the violation in code yourself.';\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules,\n ccModel: transcript.ccModel,\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 // Audit mode — warn but allow\n dispatchCapture(jwt, 'edit', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules,\n ccModel: transcript.ccModel,\n });\n outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' → warning: ' + guardReason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge (audit). ' + guardReason } });\n return;\n }\n\n // Clean\n dispatchCapture(jwt, 'edit', 'pass', 'audit', verdict.category || 'trivial_edit',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, violatedRules: [],\n ccModel: transcript.ccModel,\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_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 process.stderr.write('[synkro] editGuard error: ' + String(err) + '\\\\n');\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, hookSessionId, filePathFromToolInput, dispatchFinding, dispatchCapture, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { basename, extname, resolve, join, dirname } from 'node:path';\nimport { readFileSync, readdirSync, existsSync } from 'node:fs';\n\n\ninterface PackageCapability {\n name: string;\n description: string;\n capabilities: string[];\n sourceExcerpt: string;\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 if (!isEditTool(toolName)) {\n outputEmpty();\n return;\n }\n\n const toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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 const fileExt = extname(filePath).toLowerCase();\n\n // Skip prose / non-executable files — CWE scanning is for source code only.\n // Without this guard, prose that *mentions* a CWE (plans, notes, READMEs)\n // can trigger a false positive on the literal string.\n const 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 if (NON_CODE_EXTS.has(fileExt)) { outputEmpty(); return; }\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\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 const config = await loadConfig(jwt);\n const rt = await cweRoute(config);\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 if (config.silent) {\n outputJson({ systemMessage: '[synkro:' + rt + ':cweScan] ' + fileShort + ' \\u2192 skipped (silent mode)' });\n return;\n }\n\n const cweTag = '[synkro:' + rt + ':cweScan]';\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(toolName, toolInput, 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)),\n localGradeCwe(buildCwePrompt(chunk2)),\n ]);\n gradeResponses = [resp1, resp2];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 grader unavailable (' + reason + '), skipped' });\n return;\n }\n } else {\n try {\n gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];\n } catch (gradeErr: any) {\n const reason = gradeErr?.message || String(gradeErr);\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 grader unavailable (' + reason + '), skipped' });\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 outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (exempted: ' + cweIds.join(', ') + ')' });\n return;\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 for (const cweId of activeCweIds) {\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: cweId,\n severity: verdict.severity || 'high',\n status: 'open',\n detail: verdict.reason || 'code weakness detected',\n cwe_name: cweNameMap.get(cweId.toUpperCase()) || undefined,\n }, config.captureDepth);\n }\n\n dispatchCapture(jwt, 'cwe', 'block', verdict.severity || 'high', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\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\n const cleanMsg = cweTag + ' ' + fileShort + ' \\u2192 clean' + (verdict.reason ? ' (' + verdict.reason + ')' : '');\n outputJson({ systemMessage: cleanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: cleanMsg } });\n return;\n }\n\n // Cloud path \\u2014 thin client, all grading logic server-side\n // Detect imports and scan capabilities for cloud grading\n let packageContext: PackageCapability[] | undefined;\n if (cwd) {\n const newImports = detectNewImports(toolName, toolInput, 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) scanBody.package_context = packageContext.map(c => ({\n name: c.name, description: c.description, capabilities: c.capabilities, source_excerpt: c.sourceExcerpt,\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 cweResp = await resp.json();\n } catch {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 cloud grader 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')\n .map((f: any) => f.cwe)\n .filter((id: string) => !exemptedCwes.has(id.toUpperCase()));\n\n if (activeCweIds.length === 0) {\n outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (exempted)' });\n return;\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 for (const cweId of activeCweIds) {\n const f = findings.find((x: any) => x.cwe === cweId);\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cwe',\n finding_id: cweId,\n severity: f?.severity || 'high',\n status: 'open',\n detail: f?.reason || 'code weakness detected',\n cwe_name: f?.name || undefined,\n }, config.captureDepth);\n }\n\n dispatchCapture(jwt, 'cwe', 'block', findings[0]?.severity || 'high', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: denyDetail,\n violatedRules: activeCweIds,\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\n const cleanMsg = cweTag + ' ' + fileShort + ' \\u2192 clean' + (cweResp?.summary ? ' (cloud)' : '');\n outputJson({ systemMessage: cleanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: cleanMsg } });\n } catch (err) {\n process.stderr.write('[synkro] cweGuard error: ' + String(err) + '\\n');\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, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\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 toolInput = payload.tool_input || {};\n const sessionId = hookSessionId(payload);\n const cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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\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 // 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 for (const f of findings.slice(0, 10)) {\n const cveId = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown';\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: filePath,\n finding_type: 'cve',\n finding_id: cveId,\n severity: f.severity || 'high',\n status: 'open',\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 }, config.captureDepth);\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 dispatchCapture(jwt, 'cve', 'block', 'critical', 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: 'edit ' + filePath,\n reasoning: top3,\n violatedRules: cveIds,\n });\n\n outputJson({\n systemMessage: cveMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n outputJson({ systemMessage: cveTag + ' ' + fileShort + ' → clean' });\n } catch (err) {\n process.stderr.write('[synkro] cveGuard error: ' + String(err) + '\\\\n');\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 type HookConfig, type Rule,\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 if (!isShellTool(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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\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 appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName, summary: command.slice(0, 120) });\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n jwt = await ensureFreshJwt(jwt);\n\n // ─── Install protection: server-side pkg-scan (CVE + typosquat + tarball + reputation) ───\n let installScanMsg = '';\n if (toolName === 'Bash') {\n const pkgInstallMatch = command.match(\n /^(?:.*&&\\s*|.*;\\s*)?(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|(?:uv\\s+)?pip3?\\s+install|go\\s+get|cargo\\s+add|gem\\s+install|composer\\s+require)\\s+([^|;&><]+)/\n );\n const isPip = /(?:uv\\s+)?pip3?\\s+install/.test(command);\n if (pkgInstallMatch) {\n const rawArgs = pkgInstallMatch[1];\n const packages: Array<{ name: string; version: string; ecosystem: string }> = [];\n const tokens = rawArgs.split(/\\s+/);\n let skipNext = false;\n for (const token of tokens) {\n if (skipNext) { skipNext = false; continue; }\n if (!token || !/^[@a-zA-Z]/.test(token)) continue;\n if (token.startsWith('-')) {\n if (/^--(python|target|prefix|root|constraint|requirement|index-url|extra-index-url|find-links|build|src|cache-dir|filter|workspace)$/.test(token)) skipNext = true;\n continue;\n }\n const ecosystem = isPip ? 'PyPI' : 'npm';\n if (isPip) {\n const pipMatch = token.match(/^([a-zA-Z0-9_.-]+)(?:[=~!<>]=?(.+))?$/);\n if (pipMatch) {\n packages.push({ name: pipMatch[1], version: pipMatch[2]?.replace(/^=/, '') || '*', ecosystem });\n continue;\n }\n }\n const atIdx = token.lastIndexOf('@');\n if (atIdx > 0) {\n packages.push({ name: token.slice(0, atIdx), version: token.slice(atIdx + 1), ecosystem });\n } else {\n packages.push({ name: token, version: '*', ecosystem });\n }\n }\n\n if (packages.length > 0) {\n try {\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({ packages, command }),\n signal: AbortSignal.timeout(15000),\n }).then(r => r.json()) as any;\n\n const action = scanResp?.action || 'allow';\n const pkgResults = Array.isArray(scanResp?.packages) ? scanResp.packages : [];\n const summary = scanResp?.summary || '';\n\n if (action === 'block') {\n const blockSignals = pkgResults\n .flatMap((p: any) => (p.signals || []).filter((s: any) => s.severity === 'critical' || s.severity === 'high'))\n .slice(0, 5);\n const scanMsg = '[synkro:installScan] ' + cmdShort + ' → blocked';\n const details = blockSignals.map((s: any) => s.detail).join('\\n');\n const ctx = details + '\\nDo NOT install packages with security risks. Use a patched version or a different package.';\n\n const config = await loadConfig(jwt);\n for (const p of pkgResults) {\n for (const s of (p.signals || [])) {\n if (s.severity === 'critical' || s.severity === 'high') {\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;\n dispatchFinding(jwt, {\n session_id: sessionId,\n file_path: command,\n finding_type: 'cve' as const,\n finding_id: advisoryId + ':' + p.name,\n severity: s.severity,\n status: 'open',\n detail: s.detail,\n package_name: p.name,\n package_version: p.version,\n }, config.captureDepth);\n }\n }\n }\n\n const violatedIds = blockSignals.map((s: any) => s.type + ':' + s.detail.slice(0, 40));\n dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command,\n reasoning: details.slice(0, 200),\n violatedRules: violatedIds,\n ccModel: transcript.ccModel,\n });\n\n outputJson({\n systemMessage: scanMsg,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },\n });\n return;\n }\n\n if (action === 'warn') {\n installScanMsg = '[synkro:installScan] ' + summary;\n } else {\n const scannedPkgs = packages.map(p => p.name + '@' + p.version).join(', ');\n installScanMsg = '[synkro:installScan] ' + scannedPkgs + ' → clean';\n }\n } catch (e) {\n log('bashGuard pkg-scan failed: ' + String(e));\n }\n }\n }\n }\n\n const lastPrompt = readLastPrompt(sessionId);\n\n const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (config.silent) {\n const msg = (installScanMsg ? installScanMsg + '\\\\n' : '') + tagStr + ' bashGuard → 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 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(config.rules),\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 \"audit\". The enforcement layer handles audit vs blocking — your job is only to detect violations.',\n 'When passing (ok=true), cite which rules you checked and why they passed (e.g. \"R006 satisfied: typecheck found in session history\"). Be specific, not generic.',\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);\n } catch {\n outputJson({ systemMessage: tagStr + ' bashGuard → local grader unavailable, skipped' });\n return;\n }\n\n const verdict = parseVerdict(gradeResp);\n const violatedRules = verdict.ruleId ? [verdict.ruleId] : [];\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode === 'audit') {\n const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });\n dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({\n systemMessage: combined,\n hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: combined },\n });\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n }\n } else {\n const auditRuleIds = (config.rules || []).filter((r: any) => r.mode === 'audit').map((r: any) => r.rule_id || r.id).filter(Boolean);\n const auditNote = auditRuleIds.length > 0 ? ' (audit rules checked: ' + auditRuleIds.join(', ') + ')' : '';\n const reason = tagStr + ' bashGuard → pass: ' + (verdict.reason || 'no policy violations detected') + auditNote;\n const combined = (installScanMsg ? installScanMsg + '\\\\n' : '') + reason;\n outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });\n dispatchCapture(jwt, 'bash', 'pass', 'audit', verdict.category || 'trivial_utility',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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_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('bashGuard ' + cmdShort + ' → error (timeout)');\n if (installScanMsg) {\n outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });\n } else { outputEmpty(); }\n return;\n }\n\n if (!resp.hook_response || typeof resp.hook_response !== 'object') {\n log('bashGuard ' + cmdShort + ' → pass (no hook_response)');\n if (installScanMsg) {\n outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });\n } else { outputEmpty(); }\n return;\n }\n\n if (installScanMsg) {\n const existing = resp.hook_response.systemMessage || '';\n resp.hook_response.systemMessage = installScanMsg + (existing ? '\\\\n' + existing : '');\n if (resp.hook_response.hookSpecificOutput) {\n const existingCtx = resp.hook_response.hookSpecificOutput.additionalContext || '';\n resp.hook_response.hookSpecificOutput.additionalContext = installScanMsg + (existingCtx ? '\\\\n' + existingCtx : '');\n } else {\n resp.hook_response.hookSpecificOutput = { hookEventName: 'PreToolUse', additionalContext: resp.hook_response.systemMessage };\n }\n }\n outputJson(resp.hook_response);\n } catch (err) {\n process.stderr.write('[synkro] bashGuard error: ' + String(err) + '\\\\n');\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 type HookConfig, type Rule,\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 if (!isAgentTool(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 cwd = payload.cwd || '';\n const permissionMode = payload.permission_mode || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\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 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 const config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 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(config.rules),\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 \"audit\". The enforcement layer handles audit vs blocking — your job is only to detect violations.',\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' agentGuard → local grader unavailable, skipped' });\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 = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode === 'audit') {\n const reason = tagStr + ' agentGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');\n outputJson({ systemMessage: reason, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason } });\n dispatchCapture(jwt, 'agent', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: guardReason, rulesChecked: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\n } else {\n const reason = 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: config.rules, violatedRules,\n recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,\n });\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', 'audit', verdict.category || 'subagent_spawn',\n toolName, gitRepo, sessionId, config.captureDepth, {\n command: agentContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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_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 process.stderr.write('[synkro] agentGuard error: ' + String(err) + '\\\\n');\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} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\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 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 cwd = payload.cwd || '';\n const gitRepo = detectRepo(cwd);\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 config = await loadConfig(jwt);\n const rt = await route(config);\n const tagStr = tag(rt, config);\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 graderPrompt = [\n 'Working directory: ' + (cwd || '.'),\n 'Repo: ' + (gitRepo || 'unknown'),\n sessionLog,\n 'Plan:',\n plan.slice(0, 8000),\n 'Org rules: ' + JSON.stringify(config.rules),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('plan', graderPrompt);\n } catch {\n outputJson({ systemMessage: tagStr + ' planReview → local grader unavailable, skipped' });\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: config.rules, violatedRules,\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', 'audit', verdict.category || 'general',\n 'ExitPlanMode', gitRepo, sessionId, config.captureDepth, {\n command: planContent, reasoning: reviewMsg,\n rulesChecked: config.rules, violatedRules: [],\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 process.stderr.write('[synkro] planReview error: ' + String(err) + '\\\\n');\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, appendLocalTelemetry, setupCursorHookSignals, 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 sessionId = hookSessionId(payload);\n if (!sessionId) { outputEmpty(); return; }\n\n const cwd = payload.cwd || '';\n const transcriptPath = payload.transcript_path || '';\n const gitRepo = detectRepo(cwd);\n\n let jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n if (transcriptPath) {\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n const usageBody = {\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'stop',\n verdict: 'allow',\n severity: 'none',\n model: usage.model || 'unknown',\n cc_model: usage.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 ...(sessionId ? { session_id: sessionId } : {}),\n };\n appendLocalTelemetry(usageBody);\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(usageBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\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 process.stderr.write('[synkro] stopSummary error: ' + String(err) + '\\\\n');\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, GATEWAY_URL,\n 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 cwd = payload.cwd || '';\n const sessionId = hookSessionId(payload);\n const gitRepo = detectRepo(cwd);\n if (gitRepo) writeCachedRepo(gitRepo);\n\n let jwt = loadJwt();\n\n const isChannelUp = await channelUp();\n const rt = isChannelUp ? 'local' : 'cloud';\n\n let policyName = '';\n let silent = false;\n let openFindings = 0;\n\n 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: [] };\n const tagStr = tag(rt, fakeConfig);\n const routeLine = tagStr + ' inference: ' + (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 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 process.stderr.write('[synkro] sessionStart error: ' + String(err) + '\\\\n');\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, 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 (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\n const config = await loadConfig(jwt);\n if (config.captureDepth !== 'local_only') {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\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,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, 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\n const payload = JSON.parse(input);\n const sessionId = hookSessionId(payload);\n const transcriptPath = payload.transcript_path || '';\n const cwd = payload.cwd || '';\n\n if (!sessionId || !transcriptPath || !existsSync(transcriptPath)) {\n outputEmpty();\n return;\n }\n\n const jwt = loadJwt();\n if (!jwt) { outputEmpty(); return; }\n\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n const usageBody = {\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'stop',\n verdict: 'allow',\n severity: 'none',\n model: usage.model || 'unknown',\n cc_model: usage.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 session_id: sessionId,\n };\n appendLocalTelemetry(usageBody);\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(usageBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n\n if (process.env.SYNKRO_TRANSCRIPT_CONSENT === 'no') { outputEmpty(); return; }\n\n const gitRepo = detectRepo(cwd);\n if (!gitRepo) { outputEmpty(); return; }\n\n let captureDepth = 'local_only';\n try {\n const r = await fetch(GATEWAY_URL + '/api/v1/hook/config', {\n headers: { Authorization: 'Bearer ' + jwt },\n signal: AbortSignal.timeout(3000),\n });\n const data = await r.json() as any;\n captureDepth = data.capture_depth || 'local_only';\n } catch {}\n\n if (captureDepth === 'local_only') { outputEmpty(); return; }\n\n const offsetDir = join(homedir(), '.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\n if (totalLines <= offset) { outputEmpty(); return; }\n\n let startIdx = offset;\n const delta = totalLines - offset;\n if (delta > 200) startIdx = totalLines - 200;\n\n const messages: any[] = [];\n for (let i = startIdx; i < totalLines; i++) {\n try {\n const entry = JSON.parse(allLines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n const content = entry.message?.content;\n let text = '';\n if (typeof content === 'string') text = content.slice(0, 8000);\n else if (Array.isArray(content)) {\n text = content.map((c: any) => {\n if (typeof c === 'string') return c;\n if (c?.type === 'text') return c.text || '';\n return '';\n }).join(' ').slice(0, 8000);\n }\n\n const msg: any = { message_index: i, type: entry.type, content: text };\n if (entry.type === 'assistant') {\n const toolCalls = (Array.isArray(content) ? content : [])\n .filter((c: any) => c?.type === 'tool_use')\n .map((c: any) => ({ name: c.name, input: JSON.stringify(c.input || {}).slice(0, 500), id: c.id }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n msg.model = entry.message?.model || null;\n const u = entry.message?.usage;\n if (u) msg.usage = { input_tokens: u.input_tokens, output_tokens: u.output_tokens, cache_creation_input_tokens: u.cache_creation_input_tokens, cache_read_input_tokens: u.cache_read_input_tokens };\n }\n messages.push(msg);\n } catch {}\n }\n\n writeFileSync(offsetFile, String(totalLines), 'utf-8');\n\n if (messages.length === 0) { outputEmpty(); return; }\n\n const syncBody = {\n repo: gitRepo,\n sessions: [{ cc_session_id: sessionId, messages }],\n };\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(syncBody),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\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 } 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 transcriptPath = payload.transcript_path || '';\n if (sessionId && transcriptPath) {\n const usage = aggregateUsage(transcriptPath);\n if (usage.totals.in + usage.totals.out > 0) {\n appendLocalTelemetry({\n capture_type: 'usage_tick',\n event_id: 'usage_' + Date.now() + '_' + process.pid,\n hook_type: 'prompt_submit',\n session_id: sessionId,\n model: usage.model || 'unknown',\n cc_model: usage.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 });\n }\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, ruleMode, postWithRetry, readStdin,\n extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log, GATEWAY_URL,\n type Rule,\n} from './_synkro-common.ts';\nimport { createHash } from 'node:crypto';\nimport { existsSync, statSync, writeFileSync, mkdirSync } from 'node:fs';\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 = 7500;\nconst CURSOR_CLOUD_TIMEOUT_MS = 6000;\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 cwd = typeof payload.cwd === 'string' ? payload.cwd : '';\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 appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: toolName, summary: command.slice(0, 120) });\n\n const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';\n const rawModel = String(payload.model ?? payload.model_id ?? '');\n const KNOWN_MODELS = new Set(['gpt-4', 'gpt-4o', 'gpt-4.1', 'gpt-5', 'o1', 'o3', 'o4-mini', 'claude-sonnet-4-5', 'claude-opus-4-5', 'sonnet-4', 'sonnet-4-thinking', 'gemini-2.5-pro', 'gemini-2.5-flash']);\n const model = rawModel ? (KNOWN_MODELS.has(rawModel) || rawModel.startsWith('claude-') || rawModel.startsWith('gpt-') || rawModel.startsWith('gemini-') || rawModel.startsWith('o1') || rawModel.startsWith('o3') ? rawModel : 'cursor/' + rawModel) : 'cursor';\n const repo = detectRepo(cwd);\n\n const cmdShort = command.slice(0, 80);\n log('bashGuard checking: ' + cmdShort);\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 rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const rulesBlock = config.rules.map((r: Rule, i: number) =>\n (i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text\n ).join('\\\\n');\n\n const graderPrompt = [\n 'RULES:',\n rulesBlock || '(none)',\n '',\n sessionLog,\n 'COMMAND TO EVALUATE:',\n command,\n '',\n 'User intent (last human message): ' + (transcript.userIntent || lastPrompt || 'none stated'),\n 'Last user prompt: ' + (lastPrompt || 'none'),\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS);\n } catch (e) {\n log('bashGuard ' + cmdShort + ' → pass (grade unavailable): ' + String(e));\n finishWith({ permission: 'allow' });\n }\n\n const verdict = parseVerdict(gradeResp);\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' bashGuard → block: ' + guardReason,\n agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),\n });\n }\n\n dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n ccModel: model,\n });\n log('bashGuard ' + cmdShort + ' → audit warning');\n finishWith({ permission: 'allow' });\n } else {\n dispatchCapture(jwt, 'bash', 'pass', 'audit', verdict.category || 'clean',\n 'Bash', gitRepo, sessionId, config.captureDepth, {\n command, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, 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 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_PRECHECK_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,\n parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,\n appendSessionAction, readSessionLog, compressSessionLog,\n log, GATEWAY_URL,\n type Rule,\n} from './_synkro-common.ts';\nimport { basename } from 'node:path';\n\nconst CURSOR_GRADE_TIMEOUT_MS = 7500;\nconst CURSOR_CLOUD_TIMEOUT_MS = 8000;\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\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finishAllow();\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n const toolInput = payload.tool_input || {};\n const cwd = payload.cwd || '';\n const sessionId = payload.conversation_id || '';\n\n const filePath = toolInput.file_path || toolInput.path || toolInput.target_file || '';\n const content = toolInput.content || toolInput.new_string || toolInput.code_edit || '';\n if (!filePath) finishAllow();\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 repo = detectRepo(cwd);\n\n let jwt = loadJwt();\n if (!jwt) finishAllow();\n jwt = await ensureFreshJwt(jwt);\n\n const config = await loadConfig(jwt);\n if (config.silent) finishAllow();\n\n const rt = await route(config);\n const tagStr = tag(rt, config);\n\n if (rt === 'local') {\n const sessionLog = compressSessionLog(readSessionLog(sessionId));\n const contentShort = content.slice(0, 4000);\n const rulesBlock = config.rules.map((r: Rule, i: number) =>\n (i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text\n ).join('\\\\n');\n\n const graderPrompt = [\n 'RULES:',\n rulesBlock || '(none)',\n '',\n sessionLog,\n 'FILE: ' + filePath,\n '',\n 'CONTENT TO EVALUATE (first 4000 chars):',\n contentShort,\n ].filter(Boolean).join('\\\\n');\n\n let gradeResp: string;\n try {\n gradeResp = await localGrade('edit', graderPrompt, CURSOR_GRADE_TIMEOUT_MS);\n } catch (e) {\n log('editGuard ' + fileShort + ' → pass (grade unavailable): ' + String(e));\n finishWith({ permission: 'allow' });\n }\n\n const verdict = parseVerdict(gradeResp);\n const editContent = 'file=' + filePath + ' content=' + content.slice(0, 2000);\n\n if (!verdict.ok) {\n const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);\n const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');\n\n if (mode !== 'audit') {\n dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n });\n finishWith({\n permission: 'deny',\n user_message: tagStr + ' editGuard ' + fileShort + ' → block: ' + guardReason,\n agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),\n });\n }\n\n dispatchCapture(jwt, 'edit', 'warning', verdict.severity || 'medium', verdict.category || 'security',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: guardReason,\n rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],\n });\n const warnReason = verdict.reason || guardReason;\n log('editGuard ' + fileShort + ' → audit warning: ' + warnReason);\n finishWith({ permission: 'allow' });\n }\n\n dispatchCapture(jwt, 'edit', 'pass', 'audit', verdict.category || 'trivial_edit',\n toolName || 'Edit', repo, sessionId, config.captureDepth, {\n command: editContent, reasoning: verdict.reason || 'no policy violations detected',\n rulesChecked: config.rules, violatedRules: [],\n });\n const passReason = verdict.reason || 'no policy violations detected';\n log('editGuard ' + fileShort + ' → pass: ' + passReason);\n finishWith({ permission: 'allow' });\n }\n\n const body = {\n hook_event: 'PreToolUse',\n tool_name: toolName || 'Edit',\n tool_input: { file_path: filePath, content },\n file_path: filePath,\n content,\n response_format: 'cursor',\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('editGuard ' + fileShort + ' → 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('editGuard ' + fileShort + ' → pass (no hook_response)');\n finishAllow();\n } catch (e) {\n log('editGuard error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('editGuard fatal: ' + String(e));\n finishAllow();\n});`;\n\nexport const CURSOR_EDIT_CAPTURE_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, ensureFreshJwt, detectRepo, readStdin,\n appendSessionAction, appendLocalTelemetry, log, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\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);\n const filePath = payload.file_path || payload.path || payload.target_file || '';\n if (!filePath) finish();\n\n const cwd = payload.cwd || payload.workspace_roots?.[0] || '';\n const sessionId = payload.conversation_id || '';\n const repo = detectRepo(cwd);\n\n log('editScan ' + basename(filePath));\n\n appendSessionAction(sessionId, { ts: new Date().toISOString(), tool: 'Edit', summary: 'wrote ' + basename(filePath), file: filePath, outcome: 'ok' });\n\n let jwt = loadJwt();\n if (!jwt) finish();\n jwt = await ensureFreshJwt(jwt);\n\n let fileContent = '';\n const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);\n try {\n if (existsSync(fullPath)) {\n const buf = readFileSync(fullPath);\n fileContent = buf.slice(0, 50000).toString('utf-8');\n }\n } catch {}\n\n let dependencies: Record<string, string> = {};\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 captureBody: Record<string, any> = {\n capture_type: 'edit_scan',\n tool_input: { file_path: filePath, content: fileContent },\n edit_verdict: { ok: true },\n dependencies,\n };\n if (sessionId) captureBody.session_id = sessionId;\n if (cwd) captureBody.cwd = cwd;\n if (repo) captureBody.repo = repo;\n\n const rulesPath = join(homedir(), '.synkro', 'rules.json');\n if (existsSync(rulesPath)) {\n appendLocalTelemetry(captureBody);\n } else {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(captureBody),\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n appendLocalTelemetry(captureBody);\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_BASH_FOLLOWUP_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, readStdin, appendSessionAction, appendLocalTelemetry, log, GATEWAY_URL,\n} from './_synkro-common.ts';\nimport { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { homedir } from 'node:os';\n\nconst CONSENT_FILE = join(homedir(), '.synkro', '.local-consent');\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\nfunction hashCmd(cmd: string): string {\n return createHash('sha256').update(cmd).digest('hex').slice(0, 16);\n}\n\nfunction consentGrant(sid: string, hash: string): void {\n try {\n const dir = dirname(CONSENT_FILE);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(CONSENT_FILE, sid + '\\\\t' + hash + '\\\\tactive\\\\n', 'utf-8');\n } catch {}\n}\n\nfunction consentHasActive(sid: 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(sid + '\\\\t' + hash + '\\\\tactive');\n } catch {\n return false;\n }\n}\n\nfunction consentConsume(sid: string, hash: string): void {\n try {\n if (!existsSync(CONSENT_FILE)) return;\n const content = readFileSync(CONSENT_FILE, 'utf-8');\n const target = sid + '\\\\t' + hash + '\\\\tactive';\n const replacement = sid + '\\\\t' + hash + '\\\\tconsumed';\n const updated = content.split('\\\\n').map((l: string) => l === target ? replacement : l).join('\\\\n');\n writeFileSync(CONSENT_FILE, updated, 'utf-8');\n } catch {}\n}\n\nasync function main() {\n try {\n const input = await readStdin();\n if (!input.trim()) finish();\n\n const payload = JSON.parse(input);\n const toolName = payload.tool_name || '';\n\n const shellTools = ['Shell', 'Bash', 'terminal', 'run_terminal_cmd', 'execute_command'];\n if (!shellTools.includes(toolName)) finish();\n\n const sessionId = payload.conversation_id || '';\n const toolUseId = payload.tool_use_id || '';\n const command = payload.tool_input?.command || '';\n\n let isError = false;\n try {\n const output = JSON.parse(payload.tool_output || '{}');\n isError = output.exitCode !== 0 || output.is_error === true;\n } catch { isError = false; }\n\n const cmdHash = command ? hashCmd(command) : '';\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: command.slice(0, 120), outcome: isError ? 'exit 1' : 'exit 0' });\n\n const captureBody: Record<string, any> = {\n capture_type: 'bash_followup',\n session_id: sessionId || null,\n tool_use_id: toolUseId || null,\n is_error: isError,\n command_hash: cmdHash,\n };\n\n const rulesPath = join(homedir(), '.synkro', 'rules.json');\n if (existsSync(rulesPath)) {\n appendLocalTelemetry(captureBody);\n } else {\n const jwt = loadJwt();\n if (jwt && sessionId && toolUseId) {\n fetch(GATEWAY_URL + '/api/v1/hook/capture', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },\n body: JSON.stringify(captureBody),\n signal: AbortSignal.timeout(3000),\n }).catch(() => {});\n }\n appendLocalTelemetry(captureBody);\n }\n\n finish();\n } catch (e) {\n log('bashFollowup error: ' + String(e));\n finish();\n }\n}\n\nmain().catch((e) => {\n log('bashFollowup fatal: ' + String(e));\n finish();\n});`;\n\nexport const CURSOR_SESSION_START_TS = `#!/usr/bin/env bun\nimport {\n loadJwt, loadConfig, readStdin, log,\n type HookConfig,\n} from './_synkro-common.ts';\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\nasync function main() {\n try {\n const input = await readStdin();\n\n let jwt = loadJwt();\n const config: HookConfig = jwt ? await loadConfig(jwt) : {\n captureDepth: 'local_only', tier: 'standard', silent: false,\n policyName: '', rules: [], scanExemptions: [],\n };\n\n const policyName = config.policyName || 'default';\n const ruleCount = config.rules.length;\n const mode = config.silent ? 'silent' : 'active';\n\n const context = [\n 'This session is monitored by Synkro (' + mode + ' mode, policy: \"' + policyName + '\", ' + ruleCount + ' rules).',\n 'Synkro enforces security and compliance rules on tool calls (shell commands, file edits).',\n 'If a tool call is blocked, Synkro will explain which rule was violated and why.',\n 'Do not suggest workarounds to bypass Synkro hooks — fix the underlying issue instead.',\n ].join(' ');\n\n finishWith({ additional_context: context });\n } catch (e) {\n log('sessionStart error: ' + String(e));\n finishAllow();\n }\n}\n\nmain().catch((e) => {\n log('sessionStart fatal: ' + String(e));\n finishAllow();\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 } from 'node:child_process';\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 }).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 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\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 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 existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !existingFullNames.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 } catch {\n rl.close();\n }\n console.log();\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 * 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// 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 * 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`;\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`. Returns true if\n * a refresh actually happened, false if there was nothing in the keychain to\n * export.\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/pgdata/ (bind-mounted, persistent telemetry)\n * ~/.synkro/rules.json (bind-mounted, policy edits)\n * ~/.synkro/.mcp-jwt (bind-mounted RO, hook auth)\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 } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport {\n CLAUDE_CREDS_DIR,\n exportKeychainCreds,\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 SYNKRO_CREDS_PATH = join(SYNKRO_DIR, 'credentials.json');\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);\nconst HOST_PG_PORT = parseInt(process.env.SYNKRO_HOST_PG_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\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 */\nexport async function dockerInstall(opts: { workersPerPool?: number } = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n const workers = String(opts.workersPerPool ?? 8);\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\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 // 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 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 const plist = writeRefreshAgent('/usr/local/bin/synkro');\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 // Stop existing container (idempotent).\n spawnSync('docker', ['rm', '-f', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\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_PG_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${MCP_JWT_PATH}:/data/.mcp-jwt:ro`,\n '-v', `${SYNKRO_CREDS_PATH}:/data/credentials.json: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 '-e', `WORKERS_PER_POOL=${workers}`,\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 };\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\n/**\n * Stop the container (idempotent). Used by `synkro disconnect` and during\n * cutover when switching back to bare-host mode.\n */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n spawnSync('docker', ['rm', '-f', 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(workersPerPool?: number): Promise<void> {\n dockerStop();\n await dockerInstall({ workersPerPool });\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 * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (CC, Codex), 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 { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig } from '../installer/mcpConfig.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 } 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';\nimport { connectGitHub } from './setupGithub.js';\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { dockerInstall, waitForContainerReady } from '../local-cc/dockerInstall.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: 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}\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 === '--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\nasync function promptTranscriptConsent(): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Would you like Synkro to use Claude Code session transcripts\\n' +\n 'to generate guardrail rules and policies for your team? (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\nfunction 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 cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\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 cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-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(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, 'utf-8');\n writeFileSync(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, 'utf-8');\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_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(cursorBashJudgePath, 0o755);\n chmodSync(cursorEditCapturePath, 0o755);\n chmodSync(mcpStdioProxyPath, 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 cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\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' }): 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 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(): 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 }).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 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): 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();\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 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 // Idempotent short-circuit. If creds are still valid AND every required\n // surface is in place, skip the heavy steps (auth, hooks, daemon) but\n // still check whether the current repo needs linking — the user may be\n // running install from a second repo folder.\n if (!opts.force && isAuthenticated() && isAlreadyInstalled()) {\n setApiBaseUrl(`${gatewayUrl}/api`);\n await ensureValidToken();\n const currentRepo = detectGitRepo();\n if (currentRepo) {\n try {\n const projects = await listProjects();\n const alreadyLinked = projects.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === currentRepo),\n );\n if (!alreadyLinked) {\n console.log(`Synkro is installed. This repo (${currentRepo}) is not linked yet.\\n`);\n await promptRepoConnection();\n return;\n }\n } catch {\n // API unreachable — fall through to normal \"already installed\" message\n }\n }\n console.log('✓ Synkro is already installed and configured.');\n console.log(' Run `synkro install --force` to reinstall from scratch.');\n return;\n }\n\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. Connect GitHub via WorkOS Pipes (optional — only if user wants PR scanning)\n let ghToken: string | null = null;\n if (process.stdin.isTTY) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const wantsPR = await new Promise<boolean>((resolve) => {\n rl.question('Would you like to enable GitHub PR scanning? (y/N) ', (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n resolve(trimmed === 'y' || trimmed === 'yes');\n });\n });\n if (wantsPR) {\n try {\n ghToken = await connectGitHub(gatewayUrl, token);\n } catch {}\n if (ghToken) {\n console.log();\n } else {\n console.log(' Skipped. Run `synkro install` again to enable PR scanning.\\n');\n }\n } else {\n console.log(' Skipped PR scanning.\\n');\n }\n }\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 agents = detectAgents();\n if (agents.length === 0) {\n console.error('No AI coding agents detected. Install Claude Code first: https://docs.claude.com/claude-code');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts:');\n console.log(` ${scripts.bashScript}`);\n console.log(` ${scripts.bashFollowupScript}`);\n console.log(` ${scripts.editPrecheckScript}`);\n console.log(` ${scripts.cwePrecheckScript}`);\n console.log(` ${scripts.cvePrecheckScript}`);\n console.log(` ${scripts.planJudgeScript}`);\n console.log(` ${scripts.agentJudgeScript}`);\n console.log(` ${scripts.stopSummaryScript}`);\n console.log(` ${scripts.sessionStartScript}`);\n console.log(` ${scripts.transcriptSyncScript}\\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 — disabled for now, will revisit.\n // let transcriptConsent = true;\n // if (process.stdin.isTTY) {\n // transcriptConsent = await promptTranscriptConsent();\n // if (transcriptConsent) {\n // console.log(' ✓ Transcript collection enabled\\n');\n // } else {\n // console.log(' ✗ Transcript collection disabled — skipping transcript sync\\n');\n // }\n // }\n const transcriptConsent = false;\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 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 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 });\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);\n const useLocalMcp = profile.captureDepth === 'local_only' || profile.localInference;\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 });\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 console.log();\n\n if (profile.localInference) {\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 console.log('Installing Synkro server container...');\n const workersPerPool = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '4', 10);\n const { image, hostMcpPort, hostGraderPort, hostCwePort } =\n await dockerInstall({ workersPerPool });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);\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 } 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. Ingest CC session transcripts (only if user consented).\n if (transcriptConsent) {\n try {\n const repo = detectGitRepo();\n if (repo) {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(`Indexed ${ingested} session insights from Claude Code history for ${repo}.`);\n console.log(' This helps the safety judge understand your workflow.\\n');\n }\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n\n // 7b. Bulk sync CC session transcripts into agent_sessions/agent_messages.\n try {\n const repo = detectGitRepo();\n if (repo) {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(`Synced ${result.sessions} sessions (${result.messages} messages) from Claude Code history.`);\n console.log(' This data will be used to suggest guardrail rules.\\n');\n }\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\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\nfunction detectGitRepo(): string | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n return match ? match[1] : null;\n } catch {\n return null;\n }\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 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 * Optionally also removes ~/.synkro/ entirely if --purge flag set.\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 } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\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 { dockerStop, dockerStatus, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nfunction tearDownLocalCC(purge: boolean): void {\n const docker = dockerStatus();\n if (docker.running) {\n dockerStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n\n if (purge) {\n try {\n const image = imageTag();\n spawnSync('docker', ['rmi', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(`✓ removed Docker image ${image}`);\n } catch { /* docker may not be installed */ }\n }\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\nexport function disconnectCommand(args: string[] = []): void {\n const purge = args.includes('--purge');\n\n console.log('Synkro disconnect starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n tearDownLocalCC(purge);\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 if (purge) {\n if (existsSync(SYNKRO_DIR)) {\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n console.log(`✓ Removed ${SYNKRO_DIR}`);\n } else {\n console.log(`· ${SYNKRO_DIR} already gone, nothing to remove`);\n }\n } else if (existsSync(SYNKRO_DIR)) {\n console.log(`Config preserved at ${SYNKRO_DIR}. Run with --purge to remove.`);\n }\n\n console.log('\\nSynkro disconnected.');\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","#!/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.cwd(), '.env'),\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 hooks (--purge also removes ~/.synkro)\n version Show version\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 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(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 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;AAuoCzB,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;AA+NzB,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;AAwe/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;AAkKxB,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;AAAA;AAAA;AAAA;AAAA;AAkT7B,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;AA8KvB,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;AAiLtB,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;AA8FxB,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;AAsEzB,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;AA6EzB,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmJ3B,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0D9B,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;AA0Z7B,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;;;AC39GtC,SAAS,oBAAqD;AAC9D,SAAS,iBAAAM,gBAAe,gBAAAC,eAAc,cAAAC,aAAY,aAAAC,YAAW,cAAAC,mBAAkB;AAC/E,SAAS,WAAAC,UAAS,gBAAgB;AAClC,SAAS,QAAAC,OAAM,WAAAC,gBAAe;AAC9B,SAAS,gBAAgB;AACzB,OAAO,SAAS;AAmKhB,SAAS,YAAY,KAAmB;AACtC,QAAM,KAAK,SAAS;AACpB,MAAI;AACJ,MAAIC;AAEJ,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,YAAM;AACN,MAAAA,QAAO,CAAC,GAAG;AACX;AAAA,IACF,KAAK;AAIH,YAAM;AACN,MAAAA,QAAO,CAAC,MAAM,SAAS,IAAI,GAAG;AAC9B;AAAA,IACF;AACE,YAAM;AACN,MAAAA,QAAO,CAAC,GAAG;AAAA,EACf;AAKA,WAAS,KAAKA,OAAM,CAAC,UAAU;AAC7B,QAAI,OAAO;AACT,cAAQ,MAAM,uCAAuC;AACrD,cAAQ,IAAI,kCAAkC,GAAG,EAAE;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAKO,SAAS,gBAAgB,MAA6B;AAC3D,QAAM,MAAMD,SAAQ,SAAS;AAE7B,MAAI,CAACL,YAAW,GAAG,GAAG;AACpB,IAAAC,WAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EACjD;AAEA,EAAAH,eAAc,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACzE;AAKO,SAAS,kBAA0C;AACxD,MAAI,CAACE,YAAW,SAAS,GAAG;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAUD,cAAa,WAAW,MAAM;AAC9C,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,SAAS,OAAO;AACd,WAAO;AAAA,EACT;AACF;AAWA,SAAS,uBAAiD;AAMxD,QAAM,eAAe;AAAA,IACnB,+BAA+B;AAAA,IAC/B,gCAAgC;AAAA,IAChC,gCAAgC;AAAA,IAChC,QAAQ;AAAA,EACV;AAEA,SAAO,IAAI,QAAQ,CAACQ,UAAS,WAAW;AACtC,UAAM,SAAS,aAAa,CAAC,KAAsB,QAAwB;AAGzE,UAAI,IAAI,WAAW,WAAW;AAC5B,cAAM,SAAS,IAAI,QAAQ;AAC3B,YAAI,WAAW,qBAAqB;AAClC,cAAI,UAAU,KAAK,YAAY;AAAA,QACjC,OAAO;AACL,cAAI,UAAU,KAAK;AAAA,YACjB,gCAAgC;AAAA,YAChC,gCAAgC;AAAA,YAChC,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AACA,YAAI,IAAI;AACR;AAAA,MACF;AAKA,YAAM,YAAY,IAAI,QAAQ;AAC9B,UAAI,aAAa,cAAc,qBAAqB;AAClD,YAAI,UAAU,KAAK,EAAE,QAAQ,SAAS,CAAC;AACvC,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,CAAC,IAAI,KAAK;AACZ,YAAI,UAAU,KAAK,YAAY;AAC/B,YAAI,IAAI;AACR;AAAA,MACF;AAEA,YAAM,MAAM,IAAI,IAAI,IAAI,KAAK,oBAAoB,IAAI,EAAE;AAEvD,UAAI,IAAI,aAAa,SAAS;AAC5B,YAAI,UAAU,KAAK,YAAY;AAC/B,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,IAAI,WAAW,QAAQ;AAIzB,YAAI,UAAU,KAAK,EAAE,GAAG,cAAc,SAAS,iBAAiB,gBAAgB,YAAY,CAAC;AAC7F,YAAI,IAAI,UAAU;AAClB;AAAA,MACF;AAGA,YAAM,WAAW,KAAK;AACtB,YAAM,SAAmB,CAAC;AAC1B,UAAI,QAAQ;AACZ,UAAI,UAAU;AACd,UAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,YAAI,QAAS;AACb,iBAAS,MAAM;AACf,YAAI,QAAQ,UAAU;AACpB,oBAAU;AACV,cAAI,UAAU,KAAK,YAAY;AAC/B,cAAI,IAAI;AACR;AAAA,QACF;AACA,eAAO,KAAK,KAAK;AAAA,MACnB,CAAC;AACD,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI,QAAS;AACb,YAAI;AACJ,YAAI;AACF,mBAAS,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,QAC5D,QAAQ;AACN,cAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACjD,qBAAW,MAAM;AACf,mBAAO,MAAM;AACb,mBAAO,IAAI,MAAM,0CAA0C,CAAC;AAAA,UAC9D,GAAG,GAAG;AACN;AAAA,QACF;AAEA,cAAM,QAA4B,QAAQ;AAC1C,YAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,cAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,gBAAgB,CAAC,CAAC;AAClD,qBAAW,MAAM;AACf,mBAAO,MAAM;AACb,mBAAO,IAAI,MAAM,sCAAsC,CAAC;AAAA,UAC1D,GAAG,GAAG;AACN;AAAA,QACF;AAEA,cAAM,WAA4B;AAAA,UAChC,cAAc;AAAA,UACd,eAAe,OAAO,OAAO,kBAAkB,WAAW,OAAO,gBAAgB;AAAA,UACjF,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,UAC/D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,UACzD,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,UAC5D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,QAC3D;AAEA,YAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AAEpC,mBAAW,MAAM;AACf,iBAAO,MAAM;AACb,UAAAA,SAAQ,QAAQ;AAAA,QAClB,GAAG,GAAG;AAAA,MACR,CAAC;AACD,UAAI,GAAG,SAAS,CAAC,MAAM;AACrB,YAAI,QAAS;AACb,kBAAU;AACV,YAAI;AAAE,cAAI,UAAU,KAAK,YAAY;AAAG,cAAI,IAAI;AAAA,QAAG,QAAQ;AAAA,QAAC;AAC5D,mBAAW,MAAM;AACf,iBAAO,MAAM;AACb,iBAAO,CAAC;AAAA,QACV,GAAG,GAAG;AAAA,MACR,CAAC;AAAA,IACH,CAAC;AAED,WAAO,OAAO,IAAI;AAElB,WAAO,GAAG,SAAS,CAAC,UAAiC;AACnD,UAAI,MAAM,SAAS,cAAc;AAC/B;AAAA,UACE,IAAI;AAAA,YACF,QAAQ,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF,OAAO;AACL,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAYA,eAAsB,aACpB,UACiC;AACjC,QAAM,OAAO,aAAa,MAAM;AAAA,EAAC;AAEjC,MAAI;AACF,SAAK,EAAE,OAAO,WAAW,CAAC;AAG1B,UAAM,gBAAgB,qBAAqB;AAG3C,UAAM,UAAU,GAAG,mBAAmB,kBAAkB,IAAI;AAC5D,gBAAY,OAAO;AAEnB,SAAK,EAAE,OAAO,kBAAkB,KAAK,QAAQ,CAAC;AAC9C,SAAK,EAAE,OAAO,UAAU,CAAC;AAGzB,UAAM,OAAO,MAAM;AAEnB,SAAK,EAAE,OAAO,UAAU,CAAC;AACzB,oBAAgB,IAAI;AACpB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAK,EAAE,OAAO,SAAS,QAAQ,CAAC;AAChC,WAAO;AAAA,EACT;AACF;AAKO,SAAS,kBAA2B;AACzC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,MAAO,QAAO;AAGnB,MAAI;AACF,UAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,QAAI,CAAC,SAAS,IAAK,QAAO;AAG1B,WAAO,KAAK,IAAI,IAAI,QAAQ,MAAM;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAsBO,SAAS,cAAwB;AACtC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAKA,MAAI,MAAM,SAAS;AACjB,WAAO;AAAA,MACL,IAAI,MAAM;AAAA,MACV,OAAO,MAAM,SAAS;AAAA,MACtB,QAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,SAAO;AAAA,IACL,IAAI,QAAQ;AAAA,IACZ,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ;AAAA,EAClB;AACF;AAKO,SAAS,iBAAgC;AAC9C,QAAM,QAAQ,gBAAgB;AAC9B,SAAO,OAAO,gBAAgB;AAChC;AAKO,SAAS,iBAA0B;AACxC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI;AACF,UAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,QAAI,CAAC,SAAS,IAAK,QAAO;AAG1B,UAAM,YAAY,QAAQ,MAAM;AAChC,UAAM,SAAS,IAAI,KAAK;AACxB,WAAO,KAAK,IAAI,IAAI,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,eAAiC;AACrD,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO,cAAe,QAAO;AAElC,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG,cAAc,qBAAqB;AAAA,MACjE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,eAAe,MAAM,cAAc,CAAC;AAAA,IAC7D,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,cAAc;AACrB,sBAAgB;AAAA,QACd,GAAG;AAAA,QACH,cAAc,KAAK;AAAA,QACnB,eAAe,KAAK,iBAAiB,MAAM;AAAA,MAC7C,CAAC;AACD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,mBAAqC;AACzD,MAAI,CAAC,gBAAgB,EAAG,QAAO;AAE/B,MAAI,eAAe,GAAG;AACpB,QAAI,CAAC,gBAAgB;AACnB,uBAAiB,aAAa,EAAE,QAAQ,MAAM;AAAE,yBAAiB;AAAA,MAAM,CAAC;AAAA,IAC1E;AACA,UAAM,YAAY,MAAM;AACxB,QAAI,CAAC,WAAW;AACd,uBAAiB;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,mBAAyB;AACvC,MAAIP,YAAW,SAAS,GAAG;AACzB,IAAAE,YAAW,SAAS;AAAA,EACtB;AACF;AAnlBA,IA8BM,MAKA,kBACA,qBAGA,WACA,aACA,gBA0FA,YAmbF;AAtjBJ;AAAA;AAAA;AA8BA,IAAM,OAAO;AAKb,IAAM,mBAAmB,QAAQ,IAAI;AACrC,IAAM,sBAAuB,oBAAoB,eAAe,KAAK,gBAAgB,IACjF,mBACA;AACJ,IAAM,YAAY,QAAQ,IAAI,oBAAoBE,MAAKD,SAAQ,GAAG,WAAW,kBAAkB;AAC/F,IAAM,cAAc,QAAQ,IAAI,mBAAmB,QAAQ,IAAI;AAC/D,IAAM,iBAAkB,eAAe,eAAe,KAAK,WAAW,IAClE,cACA;AAwFJ,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmbnB,IAAI,iBAA0C;AAAA;AAAA;;;ACtjB9C;AAAA;AAAA;AAMA;AAAA;AAAA;;;ACMO,SAAS,cAAc,KAAmB;AAC/C,YAAU;AACZ;AAkDA,eAAe,QACb,QACA,UACA,MACY;AACZ,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,QAAM,MAAM,GAAG,OAAO,GAAG,QAAQ;AACjC,QAAM,iBAAiB;AACvB,QAAM,cAAc,eAAe;AAEnC,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAEA,MAAI,aAAa;AACf,YAAQ,eAAe,IAAI,UAAU,WAAW;AAAA,EAClD;AAEA,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA;AAAA,IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EACtC,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,QAAQ,SAAS,WAAW,EAAE;AACjF,UAAM,IAAI,MAAM,MAAM,UAAU,cAAc,SAAS,MAAM,EAAE;AAAA,EACjE;AAEA,SAAO,SAAS,KAAK;AACvB;AAyBA,eAAsB,cACpB,MACA,OACgC;AAChC,QAAM,OAAgC,EAAE,KAAK;AAC7C,MAAI,SAAS,MAAM,SAAS,EAAG,MAAK,QAAQ;AAC5C,SAAO,QAA+B,QAAQ,aAAa,IAAI;AACjE;AAkBA,eAAsB,eAAmC;AACvD,SAAO,QAAmB,OAAO,WAAW;AAC9C;AArJA,IAUI;AAVJ;AAAA;AAAA;AAQA;AAEA,IAAI,UAAU;AAAA;AAAA;;;ACVd,IAaa,sBAoDA;AAjEb;AAAA;AAAA;AAaO,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;AAoD7B,IAAM,gBAAgB;AAAA;AAAA;;;AC1D7B,SAAS,cAAAK,aAAY,aAAAC,YAAW,iBAAAC,sBAAqB;AACrD,SAAS,YAAAC,iBAAgB;AACzB,SAAS,QAAAC,aAAY;AAOrB,SAAS,YAAY,OAAe,OAAe,MAAc,MAAc,OAAqB;AAClG,EAAAD,UAAS,iBAAiB,IAAI,WAAW,KAAK,IAAI,IAAI,aAAa;AAAA,IACjE,OAAO;AAAA,IACP,KAAK,EAAE,GAAG,QAAQ,KAAK,UAAU,MAAM;AAAA,IACvC,OAAO,CAAC,QAAQ,UAAU,MAAM;AAAA,IAChC,SAAS;AAAA,EACX,CAAC;AACH;AAKA,eAAsB,oBAAoB,MAA6F;AACrI,QAAM,QAAmE,CAAC;AAC1E,MAAI,OAAO;AACX,SAAO,QAAQ,GAAG;AAChB,UAAM,MAAM,uDAAuD,IAAI;AACvE,UAAM,OAAO,MAAM,MAAM,KAAK;AAAA,MAC5B,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,KAAK;AAAA,QACnC,QAAQ;AAAA,QACR,wBAAwB;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,cAAc,KAAK,MAAM,gBAAgB;AAAA,IAC3D;AACA,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,WAAW,EAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,MAAM,EAAE,MAAM,WAAW,EAAE,UAAU,CAAC;AAAA,IAC3E;AACA,QAAI,KAAK,SAAS,IAAK;AACvB;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,kBACpB,MACA,OACA,MACA,SACe;AACf,MAAI;AAAE,IAAAA,UAAS,gBAAgB,EAAE,OAAO,UAAU,SAAS,IAAK,CAAC;AAAA,EAAG,QAAQ;AAC1E,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,QAAQ,sBAAsB;AAChC,gBAAY,KAAK,OAAO,OAAO,MAAM,2BAA2B,QAAQ,oBAAoB;AAAA,EAC9F;AACA,cAAY,KAAK,OAAO,OAAO,MAAM,kBAAkB,QAAQ,YAAY;AAC7E;AAMO,SAAS,kBAAkB,cAAqC;AACrE,QAAM,cAAcC,MAAK,cAAc,WAAW,WAAW;AAC7D,EAAAH,WAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,eAAeG,MAAK,aAAa,YAAY;AACnD,EAAAF,eAAc,cAAc,sBAAsB,OAAO;AACzD,SAAO;AACT;AAKO,SAAS,YAAY,UAAiC;AAC3D,MAAI,MAAM;AACV,SAAO,OAAO,QAAQ,KAAK;AACzB,QAAIF,YAAWI,MAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAC1C,UAAM,SAASA,MAAK,KAAK,IAAI;AAC7B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AA7FA,IA+Fa,cAKA;AApGb;AAAA;AAAA;AAUA;AAqFO,IAAM,eAAe;AAAA,MAC1B,cAAc;AAAA,MACd,gBAAgB;AAAA,IAClB;AAEO,IAAM,yBAAyB;AAAA;AAAA;;;AC5FtC,SAAS,YAAAC,iBAAgB;AACzB,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,uBAAuB;AAUhC,SAAS,gBAAgE;AACvE,MAAI;AACF,UAAM,YAAYD,UAAS,6BAA6B,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AAEnG,UAAM,WAAW,UAAU,MAAM,6BAA6B;AAC9D,UAAM,YAAY,UAAU,MAAM,qCAAqC;AACvE,UAAM,QAAQ,YAAY;AAC1B,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,WAAW,MAAM,CAAC;AACxB,WAAO,EAAE,UAAU,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK,SAAS;AAAA,EACtE,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,SAAS,IAAI,IAAwC,UAAmC;AACtF,SAAO,IAAI,QAAQ,CAACE,aAAY,GAAG,SAAS,UAAUA,QAAO,CAAC;AAChE;AAEA,SAAS,qBAAsC;AAC7C,SAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,UAAM,SAASD,cAAa,CAAC,KAAK,QAAQ;AACxC,UAAI,IAAI,WAAW,WAAW;AAC5B,YAAI,UAAU,KAAK;AAAA,UACjB,+BAA+BE;AAAA,UAC/B,gCAAgC;AAAA,UAChC,gCAAgC;AAAA,QAClC,CAAC;AACD,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,IAAI,QAAQ,WAAW,IAAI,WAAW,QAAQ;AAChD,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,OAAO;AACX,UAAI,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAQ;AAAA,MAAO,CAAC;AAC5C,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,cAAI,CAAC,OAAO,cAAc;AACxB,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,+BAA+BA;AAAA,YACjC,CAAC;AACD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC,CAAC;AACzD;AAAA,UACF;AACA,cAAI,UAAU,KAAK;AAAA,YACjB,gBAAgB;AAAA,YAChB,+BAA+BA;AAAA,UACjC,CAAC;AACD,cAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AACpC,qBAAW,MAAM,OAAO,MAAM,GAAG,GAAG;AACpC,UAAAD,SAAQ,OAAO,YAAY;AAAA,QAC7B,QAAQ;AACN,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AAAA,QACnD;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAA+B;AACjD,UAAI,IAAI,SAAS,cAAc;AAC7B,eAAO,IAAI,MAAM,QAAQ,WAAW,kDAAkD,CAAC;AAAA,MACzF,OAAO;AACL,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAED,WAAO,OAAO,WAAW;AAAA,EAC3B,CAAC;AACH;AAEA,SAASE,aAAY,KAAmB;AACtC,QAAM,EAAE,UAAAC,UAAS,IAAI,UAAQ,eAAoB;AACjD,QAAM,OAAO,QAAQ;AACrB,QAAM,KAAK,CAAC,QAAsB;AAChC,QAAI,IAAK,SAAQ,IAAI,6BAA6B,GAAG,EAAE;AAAA,EACzD;AACA,MAAI,SAAS,SAAU,CAAAA,UAAS,QAAQ,CAAC,GAAG,GAAG,EAAE;AAAA,WACxC,SAAS,QAAS,CAAAA,UAAS,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,GAAG,EAAE;AAAA,MAClE,CAAAA,UAAS,YAAY,CAAC,GAAG,GAAG,EAAE;AACrC;AAEA,eAAe,8BAAqE;AAClF,QAAM,MAAM,GAAGF,oBAAmB,oBAAoB,WAAW;AACjE,UAAQ,IAAI,+CAA+C;AAC3D,EAAAC,aAAY,GAAG;AAEf,UAAQ,IAAI,uCAAuC;AACnD,QAAM,UAAU,MAAM,mBAAmB;AACzC,UAAQ,IAAI,6BAAwB;AAEpC,QAAM,QAAQ,MAAM,oBAAoB,EAAE,OAAO,QAAQ,CAAC;AAC1D,MAAI,MAAM,WAAW,GAAG;AACtB,YAAQ,IAAI,wCAAwC;AACpD,WAAO,CAAC;AAAA,EACV;AAEA,UAAQ,IAAI,WAAW,MAAM,MAAM;AAAA,CAAW;AAC9C,QAAM,QAAQ,CAAC,GAAQ,MAAc;AACnC,YAAQ,IAAI,OAAO,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE;AAAA,EAChE,CAAC;AACD,UAAQ,IAAI;AAEZ,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,MAAI;AACF,UAAM,YAAY,MAAM,IAAI,IAAI,wDAAwD;AACxF,UAAM,UAAU,UACb,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,SAAS,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,EACrC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,IAAI,MAAM,MAAM;AAExD,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,sBAAsB;AAClC,aAAO,CAAC;AAAA,IACV;AAEA,WAAO,QAAQ,IAAI,CAAC,OAAO,EAAE,WAAW,MAAM,CAAC,EAAE,UAAU,EAAE;AAAA,EAC/D,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAEA,eAAsB,qBAAqB,MAA8C;AACvF,QAAM,YAAY,cAAc;AAEhC,MAAI,MAAM,YAAY,WAAW;AAC/B,YAAQ,IAAI,4BAA4B;AACxC,QAAI;AACF,YAAM,WAAW,MAAM,aAAa;AACpC,YAAM,gBAAgB,SAAS;AAAA,QAAK,CAAC,MACnC,EAAE,OAAO,KAAK,CAAC,MAAW,EAAE,cAAc,UAAU,QAAQ;AAAA,MAC9D;AACA,UAAI,CAAC,eAAe;AAClB,cAAM,cAAc,UAAU,WAAW,CAAC,EAAE,WAAW,UAAU,SAAS,CAAC,CAAC;AAC5E,gBAAQ,IAAI,6BAAwB,UAAU,SAAS,eAAe,UAAU,QAAQ,EAAE;AAAA,MAC5F,OAAO;AACL,gBAAQ,IAAI,YAAO,UAAU,QAAQ,yCAAyC;AAAA,MAChF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,iCAA6B,IAAc,OAAO,EAAE;AAAA,IACnE;AACA,YAAQ,IAAI;AACZ;AAAA,EACF;AAEA,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,MAAI;AACF,YAAQ,IAAI,4BAA4B;AACxC,UAAM,UAAoB,CAAC;AAC3B,QAAI,WAAW;AACb,cAAQ,KAAK,mBAAmB,UAAU,QAAQ,GAAG;AAAA,IACvD;AACA,YAAQ,KAAK,gCAAgC;AAC7C,YAAQ,KAAK,cAAc;AAE3B,YAAQ,QAAQ,CAAC,KAAK,MAAM;AAC1B,cAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,GAAG,EAAE;AAAA,IAClC,CAAC;AACD,YAAQ,IAAI;AAEZ,UAAM,SAAS,MAAM,IAAI,IAAI,qBAAqB;AAClD,UAAM,YAAY,SAAS,OAAO,KAAK,GAAG,EAAE;AAC5C,YAAQ,IAAI;AACZ,OAAG,MAAM;AAET,UAAM,WAAW,YAAY,IAAI;AACjC,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,UAAU,YAAY,IAAI;AAEhC,QAAI,cAAc,YAAY,WAAW;AACvC,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AACpC,cAAM,gBAAgB,SAAS;AAAA,UAAK,CAAC,MACnC,EAAE,OAAO,KAAK,CAAC,MAAW,EAAE,cAAc,UAAU,QAAQ;AAAA,QAC9D;AACA,YAAI,CAAC,eAAe;AAClB,gBAAM,cAAc,UAAU,WAAW,CAAC,EAAE,WAAW,UAAU,SAAS,CAAC,CAAC;AAC5E,kBAAQ,IAAI,6BAAwB,UAAU,SAAS,eAAe,UAAU,QAAQ,EAAE;AAAA,QAC5F,OAAO;AACL,kBAAQ,IAAI,YAAO,UAAU,QAAQ,yCAAyC;AAAA,QAChF;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,KAAK,iCAA6B,IAAc,OAAO,EAAE;AAAA,MACnE;AAAA,IACF,WAAW,cAAc,WAAW;AAClC,YAAM,gBAAgB,MAAM,4BAA4B;AACxD,UAAI,cAAc,SAAS,GAAG;AAC5B,YAAI;AACF,gBAAM,WAAW,MAAM,aAAa;AACpC,gBAAM,oBAAoB,IAAI;AAAA,YAC5B,SAAS,QAAQ,CAAC,OAAY,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAW,EAAE,SAAS,CAAC;AAAA,UAC3E;AACA,gBAAM,WAAW,cAAc,OAAO,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,SAAS,CAAC;AAEhF,cAAI,SAAS,WAAW,GAAG;AACzB,oBAAQ,IAAI,iDAA4C;AAAA,UAC1D,OAAO;AACL,kBAAM,cAAc,SAAS,WAAW,IACpC,SAAS,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK,YAC1C;AACJ,kBAAM,cAAc,aAAa,QAAQ;AACzC,oBAAQ,IAAI,mBAAc,SAAS,MAAM,wBAAwB,WAAW,GAAG;AAAA,UACjF;AAAA,QACF,SAAS,KAAK;AACZ,kBAAQ,KAAK,kCAA8B,IAAc,OAAO,EAAE;AAAA,QACpE;AAAA,MACF;AAAA,IACF,WAAW,cAAc,SAAS;AAChC,cAAQ,IAAI,sDAAsD;AAAA,IACpE,OAAO;AACL,cAAQ,IAAI,6CAA6C;AAAA,IAC3D;AAAA,EACF,QAAQ;AACN,OAAG,MAAM;AAAA,EACX;AACA,UAAQ,IAAI;AACd;AAjPA,IAcME,mBACAH,sBAGA;AAlBN;AAAA;AAAA;AAWA;AACA;AAEA,IAAMG,oBAAmB,QAAQ,IAAI;AACrC,IAAMH,uBAAuBG,qBAAoB,eAAe,KAAKA,iBAAgB,IACjFA,oBACA;AACJ,IAAM,cAAc;AAAA;AAAA;;;AClBpB;AAAA;AAAA;AAAA;AAAA;AAcA,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,SAAS,OAAO,UAAU,cAAc;AACjD,SAAS,YAAAC,WAAU,SAAS,iBAAiB;AAC7C,SAAS,cAAAC,aAAY,gBAAAC,eAAc,cAAAC,mBAAkB;AACrD,SAAS,WAAAC,UAAS,YAAAC,iBAAgB;AAClC,SAAS,QAAAC,aAAY;AACrB,SAAS,YAAAC,iBAAgB;AAczB,SAAS,aAAqC;AAC5C,MAAI,CAACN,YAAW,WAAW,EAAG,QAAO,CAAC;AACtC,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQC,cAAa,aAAa,OAAO,EAAE,MAAM,IAAI,GAAG;AACjE,UAAM,IAAI,KAAK,KAAK;AACpB,QAAI,CAAC,KAAK,EAAE,WAAW,GAAG,EAAG;AAC7B,UAAM,KAAK,EAAE,QAAQ,GAAG;AACxB,QAAI,KAAK,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AAAA,EAC5F;AACA,SAAO;AACT;AAEA,eAAe,OAAO,IAAwC,GAAW,OAA6B,CAAC,GAAoB;AACzH,MAAI,KAAK,QAAQ;AACf,YAAQ,OAAO,MAAM,CAAC;AACtB,UAAM,SAAU,QAAQ,MAAc;AACtC,QAAK,QAAQ,MAAc,WAAY,CAAC,QAAQ,MAAc,WAAW,IAAI;AAC7E,WAAO,MAAM,IAAI,QAAgB,CAACM,aAAY;AAC5C,UAAI,QAAQ;AACZ,YAAM,SAAS,CAAC,SAAiB;AAC/B,cAAM,IAAI,KAAK,SAAS,OAAO;AAC/B,YAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ;AAC5C,kBAAQ,MAAM,eAAe,QAAQ,MAAM;AAC3C,cAAK,QAAQ,MAAc,WAAY,CAAC,QAAQ,MAAc,WAAW,UAAU,KAAK;AACxF,kBAAQ,OAAO,MAAM,IAAI;AACzB,UAAAA,SAAQ,KAAK;AACb;AAAA,QACF;AACA,YAAI,MAAM,IAAK,SAAQ,KAAK,GAAG;AAC/B,YAAI,MAAM,UAAO,MAAM,MAAM;AAAE,kBAAQ,MAAM,MAAM,GAAG,EAAE;AAAG;AAAA,QAAQ;AACnE,iBAAS;AAAA,MACX;AACA,cAAQ,MAAM,GAAG,QAAQ,MAAM;AAAA,IACjC,CAAC;AAAA,EACH;AACA,SAAO,MAAM,GAAG,SAAS,CAAC;AAC5B;AAEA,SAASC,aAAY,KAAmB;AACtC,QAAM,KAAKJ,UAAS;AACpB,MAAI;AACJ,MAAIK;AACJ,UAAQ,IAAI;AAAA,IACV,KAAK;AAAU,YAAM;AAAQ,MAAAA,QAAO,CAAC,GAAG;AAAG;AAAA,IAC3C,KAAK;AAAS,YAAM;AAAO,MAAAA,QAAO,CAAC,MAAM,SAAS,IAAI,GAAG;AAAG;AAAA,IAC5D;AAAS,YAAM;AAAY,MAAAA,QAAO,CAAC,GAAG;AAAG;AAAA,EAC3C;AACA,EAAAH,UAAS,KAAKG,OAAM,MAAM;AAAA,EAAC,CAAC;AAC9B;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,OAAK,WAAW,GAAG,EAAE,CAAC;AAC3C;AAEA,SAAS,0BAA2C;AAClD,QAAM,UAAUJ,MAAK,YAAY,iBAAiB,KAAK,IAAI,CAAC,MAAM;AAClE,SAAO,IAAI,QAAQ,CAACE,UAAS,WAAW;AACtC,UAAM,OAAO,UAAU,UAAU,CAAC,MAAM,SAAS,UAAU,aAAa,GAAG;AAAA,MACzE,OAAO;AAAA,IACT,CAAC;AACD,SAAK,GAAG,SAAS,CAAC,QAAQ,OAAO,IAAI,MAAM,uCAAuC,IAAI,OAAO,EAAE,CAAC,CAAC;AACjG,SAAK,GAAG,SAAS,CAAC,SAAS;AACzB,UAAI,MAAM;AACV,UAAI;AAAE,cAAMN,cAAa,SAAS,OAAO;AAAA,MAAG,SAAS,GAAG;AACtD,eAAO,IAAI,MAAM,sCAAuC,EAAY,OAAO,EAAE,CAAC;AAC9E;AAAA,MACF;AACA,UAAI;AAAE,QAAAC,YAAW,OAAO;AAAA,MAAG,QAAQ;AAAA,MAAC;AACpC,UAAI,SAAS,GAAG;AAAE,eAAO,IAAI,MAAM,uCAAuC,IAAI,EAAE,CAAC;AAAG;AAAA,MAAQ;AAE5F,YAAM,WAAW;AACjB,UAAI,SAAS;AACb,UAAI;AACJ,cAAQ,IAAI,SAAS,KAAK,GAAG,OAAO,KAAM,WAAU,EAAE,CAAC;AACvD,YAAM,QAAQ,OAAO,QAAQ,OAAO,EAAE,EAAE,MAAM,6BAA6B;AAC3E,UAAI,CAAC,OAAO;AAAE,eAAO,IAAI,MAAM,2DAA2D,IAAI,MAAM,aAAa,OAAO,MAAM,IAAI,CAAC;AAAG;AAAA,MAAQ;AAC9I,MAAAK,SAAQ,MAAM,CAAC,CAAC;AAAA,IAClB,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAe,QAAiB,YAAoBG,MAAa,MAAc,OAAoB,CAAC,GAAe;AACjH,QAAM,OAAO,MAAM,MAAM,GAAG,UAAU,GAAG,IAAI,IAAI;AAAA,IAC/C,GAAG;AAAA,IACH,SAAS;AAAA,MACP,iBAAiB,UAAUA,IAAG;AAAA,MAC9B,gBAAgB;AAAA,MAChB,GAAI,KAAK,WAAW,CAAC;AAAA,IACvB;AAAA,EACF,CAAC;AACD,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,OAAO,MAAM,KAAK,KAAK,EAAE,MAAM,MAAM,EAAE;AAC7C,UAAM,IAAI,MAAM,OAAO,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC7D;AACA,SAAO,KAAK,KAAK;AACnB;AAMA,eAAsB,cAAc,YAAoBA,MAAa,OAA6B,CAAC,GAA2B;AAE5H,MAAI;AACF,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MAAYA;AAAA,MAAK;AAAA,IACnB;AACA,QAAI,OAAO,aAAa,OAAO,OAAO;AACpC,UAAI,CAAC,KAAK,OAAQ,SAAQ,IAAI,+CAA0C;AACxE,aAAO,OAAO;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAAC;AAGT,MAAI,CAAC,KAAK,OAAQ,SAAQ,IAAI,0CAA0C;AACxE,MAAI;AACF,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MAAYA;AAAA,MAAK;AAAA,MAAsC,EAAE,QAAQ,QAAQ,MAAM,KAAK;AAAA,IACtF;AACA,IAAAF,aAAY,SAAS,GAAG;AACxB,QAAI,CAAC,KAAK,OAAQ,SAAQ,IAAI,gCAAgC;AAAA,EAChE,SAAS,KAAK;AACZ,QAAI,CAAC,KAAK,OAAQ,SAAQ,MAAM,2CAA4C,IAAc,OAAO,EAAE;AACnG,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,MAAM,GAAI;AAChB,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,QAAYE;AAAA,QAAK;AAAA,MACnB;AACA,UAAI,OAAO,aAAa,OAAO,OAAO;AACpC,YAAI,CAAC,KAAK,OAAQ,SAAQ,IAAI,8BAAyB;AACvD,eAAO,OAAO;AAAA,MAChB;AAAA,IACF,QAAQ;AAAA,IAAC;AACT,QAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,GAAG;AAAA,EAC5C;AACA,MAAI,CAAC,KAAK,OAAQ,SAAQ,MAAM,iDAAiD;AACjF,SAAO;AACT;AAQA,eAAsB,mBAAmB,OAA2B,CAAC,GAAkB;AACrF,MAAI,CAAC,gBAAgB,GAAG;AACtB,YAAQ,MAAM,kDAAkD;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,SAAS,WAAW;AAC1B,QAAM,cAAc,OAAO,sBAAsB,QAAQ,IAAI,sBAAsB,yBAAyB,QAAQ,OAAO,EAAE;AAC7H,QAAMA,OAAM,eAAe;AAC3B,MAAI,CAACA,MAAK;AACR,YAAQ,MAAM,sFAAsF;AACpG,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,UAAQ,IAAI,sCAAsC;AAClD,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MAAYA;AAAA,MAAK;AAAA,MAA0B,EAAE,QAAQ,QAAQ,MAAM,KAAK;AAAA,IAC1E;AACA,qBAAiB,OAAO;AACxB,YAAQ,IAAI,2BAAsB,eAAe,MAAM,GAAG,EAAE,CAAC,oBAAe,OAAO,WAAW,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EAC9G,SAAS,KAAK;AACZ,YAAQ,MAAM,8BAA+B,IAAc,OAAO,EAAE;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI;AAEJ,MAAI,KAAK,aAAa;AACpB,cAAU,KAAK;AAAA,EACjB,WAAW,KAAK,gBAAgB;AAE9B,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,QAAYA;AAAA,QAAK;AAAA,MACnB;AACA,UAAI,OAAO,aAAa,OAAO,OAAO;AACpC,kBAAU,OAAO;AAAA,MACnB,OAAO;AACL,cAAM,IAAI,MAAM,eAAe;AAAA,MACjC;AAAA,IACF,QAAQ;AACN,UAAI;AACF,kBAAUX,UAAS,iBAAiB,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AAAA,MACjF,QAAQ;AACN,gBAAQ,MAAM,+EAA+E;AAC7F;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AAEL,YAAQ,IAAI,2BAA2B;AACvC,UAAM,QAAQ,MAAM,cAAc,YAAYW,IAAG;AACjD,QAAI,CAAC,OAAO;AACV,cAAQ,MAAM,sCAAsC;AACpD,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,cAAU;AACV,YAAQ,IAAI;AAAA,EACd;AAGA,MAAI;AACJ,MAAI,CAAC,KAAK,iBAAiB;AACzB,YAAQ,IAAI,uCAAuC;AACnD,YAAQ,IAAI,2EAAsE;AAClF,QAAI;AACF,oBAAc,MAAM,wBAAwB;AAAA,IAC9C,SAAS,KAAK;AACZ,cAAQ,MAAM,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC/F,UAAI,KAAK,eAAgB;AACzB,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,QAAI,CAAC,YAAY,WAAW,eAAe,GAAG;AAC5C,cAAQ,MAAM,6EAA6E;AAC3F,UAAI,KAAK,eAAgB;AACzB,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,IAAI,uBAAuB;AACnC,QAAI;AACF,YAAM,iBAAiBX;AAAA,QACrB;AAAA,QACA,EAAE,KAAK,EAAE,GAAG,QAAQ,KAAK,yBAAyB,YAAY,GAAG,UAAU,SAAS,SAAS,KAAQ,OAAO,CAAC,UAAU,QAAQ,MAAM,EAAE;AAAA,MACzI;AACA,YAAM,SAAS,KAAK,MAAM,cAAc;AACxC,UAAI,OAAO,SAAU,OAAM,IAAI,MAAM,OAAO,UAAU,aAAa;AACnE,cAAQ,IAAI,6BAAwB;AAAA,IACtC,SAAS,KAAK;AACZ,cAAQ,MAAM,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC5F,UAAI,KAAK,eAAgB;AACzB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,KAAK,gBAAgB;AACvB,QAAI,kBAAiC;AACrC,QAAI;AACF,YAAM,YAAYA,UAAS,6BAA6B,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AACnG,YAAM,IAAI,UAAU,MAAM,qCAAqC;AAC/D,UAAI,EAAG,mBAAkB,EAAE,CAAC;AAAA,IAC9B,QAAQ;AAAA,IAAC;AACT,QAAI,CAAC,iBAAiB;AACpB,cAAQ,KAAK,wDAAmD;AAChE;AAAA,IACF;AACA,UAAM,CAAC,OAAO,IAAI,IAAI,gBAAgB,MAAM,GAAG;AAC/C,eAAW,CAAC,EAAE,OAAO,MAAM,WAAW,gBAAgB,CAAC;AACvD,YAAQ,IAAI,yBAAyB,eAAe,EAAE;AAAA,EACxD,OAAO;AACL,YAAQ,IAAI,8BAA8B;AAC1C,UAAM,QAAQ,MAAM,oBAAoB,EAAE,OAAO,QAAS,CAAC;AAC3D,QAAI,MAAM,WAAW,GAAG;AACtB,cAAQ,MAAM,2DAA2D;AACzE,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,IAAI;AAAA,QAAW,MAAM,MAAM;AAAA,CAAwB;AAC3D,UAAM,MAAM,GAAG,GAAG,EAAE,QAAQ,CAAC,GAAG,MAAM;AACpC,cAAQ,IAAI,KAAK,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE;AAAA,IAC/D,CAAC;AACD,YAAQ,IAAI;AACZ,UAAM,MAAMD,iBAAgB,EAAE,OAAO,OAAO,CAAC;AAC7C,UAAM,eAAe,MAAM,OAAO,KAAK,gEAAgE;AACvG,UAAM,cAAc,aACjB,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,SAAS,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,EACrC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,IAAI,MAAM,MAAM;AACxD,QAAI,YAAY,WAAW,GAAG;AAC5B,cAAQ,MAAM,sBAAsB;AACpC,UAAI,MAAM;AACV,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,eAAW,YAAY,IAAI,CAAC,MAAM,MAAM,CAAC,CAAC;AAC1C,YAAQ,IAAI;AAAA,uBAA0B,SAAS,MAAM,WAAW;AAChE,eAAW,KAAK,SAAU,SAAQ,IAAI,YAAO,EAAE,SAAS,EAAE;AAC1D,YAAQ,IAAI;AACZ,UAAM,WAAW,MAAM,OAAO,KAAK,sBAAsB,GAAG,KAAK,EAAE,YAAY;AAC/E,QAAI,YAAY,SAAS,YAAY,KAAK;AACxC,cAAQ,IAAI,YAAY;AACxB,UAAI,MAAM;AACV,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,QAAI,MAAM;AAAA,EACZ;AAGA,UAAQ,IAAI;AACZ,aAAW,KAAK,UAAU;AACxB,YAAQ,OAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM;AAC5D,QAAI;AACF,YAAM;AAAA,QACJ,EAAE,OAAO,QAAS;AAAA,QAClB,EAAE;AAAA,QACF,EAAE;AAAA,QACF;AAAA,UACE,sBAAsB;AAAA,UACtB,cAAc;AAAA,QAChB;AAAA,MACF;AACA,cAAQ,IAAI,QAAG;AAAA,IACjB,SAAS,KAAK;AACZ,cAAQ,IAAI,WAAO,IAAc,OAAO,GAAG;AAAA,IAC7C;AAAA,EACF;AAGA,UAAQ,IAAI;AACZ,QAAM,UAAU,YAAY,QAAQ,IAAI,CAAC;AACzC,MAAI,SAAS;AACX,UAAM,UAAU,kBAAkB,OAAO;AACzC,QAAI,SAAS;AACX,cAAQ,IAAI,mBAAmB,OAAO,EAAE;AACxC,cAAQ,IAAI,2CAA2C;AAAA,IACzD;AAAA,EACF,OAAO;AACL,YAAQ,IAAI,oEAAoE;AAChF,YAAQ,IAAI,WAAW,sBAAsB,EAAE;AAC/C,YAAQ,IAAI,yFAAyF;AAAA,EACvG;AAEA,UAAQ,IAAI;AACZ,UAAQ,IAAI,gCAA2B;AACvC,UAAQ,IAAI,mBAAmB,aAAa,YAAY,KAAK,aAAa,cAAc,EAAE;AAC1F,UAAQ,IAAI,mEAAmE;AACjF;AApXA,IA+BM,YACA;AAhCN;AAAA;AAAA;AAqBA;AAQA;AAEA,IAAM,aAAaO,MAAKF,SAAQ,GAAG,SAAS;AAC5C,IAAM,cAAcE,MAAK,YAAY,YAAY;AAAA;AAAA;;;ACtBjD,eAAsB,kBAAkB,MAMrC;AACD,QAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,OAAO,EAAE,CAAC;AACjD,QAAM,OAAO,MAAM,MAAM,KAAK;AAAA,IAC5B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,iBAAiB,UAAU,KAAK,GAAG;AAAA,MACnC,cAAc;AAAA,IAChB;AAAA,IACA,QAAQ,YAAY,QAAQ,GAAI;AAAA,EAClC,CAAC;AAED,MAAI,CAAC,KAAK,IAAI;AACZ,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AAEA,QAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,SAAO,EAAE,SAAS,KAAK,SAAS,WAAW,UAAU;AACvD;AAjCA;AAAA;AAAA;AAAA;AAAA;;;ACqBA,SAAS,cAAAM,aAAY,aAAAC,YAAW,iBAAAC,gBAAe,WAAW,gBAAAC,eAAc,gBAAgB;AACxF,SAAS,WAAAC,UAAS,YAAAC,iBAAgB;AAClC,SAAS,QAAAC,aAAY;AACrB,SAAS,iBAAiB;AA2BnB,SAAS,sBAA+B;AAC7C,SAAOD,UAAS,MAAM;AACxB;AAQO,SAAS,oBAAmC;AACjD,MAAIA,UAAS,MAAM,SAAU,QAAO;AACpC,QAAM,IAAI,UAAU,YAAY,CAAC,yBAAyB,MAAM,kBAAkB,IAAI,GAAG;AAAA,IACvF,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,MAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,QAAM,QAAQ,EAAE,UAAU,IAAI,KAAK;AACnC,SAAO,QAAQ;AACjB;AAWO,SAAS,sBAAqC;AACnD,QAAM,OAAO,kBAAkB;AAC/B,MAAI,CAAC,KAAM,QAAO;AAClB,EAAAJ,WAAU,kBAAkB,EAAE,WAAW,KAAK,CAAC;AAC/C,YAAU,kBAAkB,GAAK;AACjC,EAAAC,eAAc,mBAAmB,MAAM,OAAO;AAC9C,YAAU,mBAAmB,GAAK;AAClC,SAAO;AACT;AA0BO,SAAS,kBAAkB,eAA+B;AAC/D,MAAIG,UAAS,MAAM,UAAU;AAC3B,UAAM,IAAI,oBAAoB,kCAAkC;AAAA,EAClE;AACA,EAAAJ,WAAUK,MAAKF,SAAQ,GAAG,WAAW,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;AAKzE,QAAM,WAAW,4DAA4D,aAAa;AAE1F,QAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,YAKJ,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,cAKX,QAAQ;AAAA;AAAA;AAAA,aAGT,wBAAwB;AAAA;AAAA;AAAA;AAAA,YAIzBE,MAAKC,aAAY,0BAA0B,CAAC;AAAA;AAAA,YAE5CD,MAAKC,aAAY,0BAA0B,CAAC;AAAA;AAAA;AAAA;AAItD,EAAAL,eAAc,eAAe,OAAO,OAAO;AAC3C,SAAO;AACT;AAKO,SAAS,mBAAyB;AACvC,MAAIG,UAAS,MAAM,SAAU;AAG7B,YAAU,aAAa,CAAC,WAAW,OAAO,QAAQ,SAAS,KAAK,GAAG,IAAI,aAAa,GAAG;AAAA,IACrF,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,QAAM,IAAI,UAAU,aAAa,CAAC,aAAa,OAAO,QAAQ,SAAS,KAAK,GAAG,IAAI,aAAa,GAAG;AAAA,IACjG,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,MAAI,EAAE,WAAW,GAAG;AAClB,UAAM,IAAI;AAAA,MACR,+BAA+B,EAAE,UAAU,EAAE,UAAU,SAAS;AAAA,IAClE;AAAA,EACF;AACF;AAMO,SAAS,wBAA8B;AAC5C,MAAIA,UAAS,MAAM,SAAU;AAC7B,YAAU,aAAa,CAAC,WAAW,OAAO,QAAQ,SAAS,KAAK,GAAG,IAAI,aAAa,GAAG;AAAA,IACrF,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,MAAI;AACF,QAAIL,YAAW,aAAa,GAAG;AAC7B,gBAAQ,IAAS,EAAE,WAAW,aAAa;AAAA,IAC7C;AAAA,EACF,QAAQ;AAAA,EAAoB;AAC9B;AA9LA,IA0BaO,aACA,kBACA,mBAKP,kBAGA,eACA,eACA,0BAEO;AAxCb;AAAA;AAAA;AA0BO,IAAMA,cAAaD,MAAKF,SAAQ,GAAG,SAAS;AAC5C,IAAM,mBAAmBE,MAAKC,aAAY,cAAc;AACxD,IAAM,oBAAoBD,MAAK,kBAAkB,mBAAmB;AAK3E,IAAM,mBAAmB;AAGzB,IAAM,gBAAgB;AACtB,IAAM,gBAAgBA,MAAKF,SAAQ,GAAG,WAAW,gBAAgB,GAAG,aAAa,QAAQ;AACzF,IAAM,2BAA2B,KAAK;AAE/B,IAAM,sBAAN,cAAkC,MAAM;AAAA,MAC7C,YAAY,SAAiC,OAAiB;AAC5D,cAAM,OAAO;AAD8B;AAE3C,aAAK,OAAO;AAAA,MACd;AAAA,MAH6C;AAAA,IAI/C;AAAA;AAAA;;;AC7CA;AAAA;AAAA;AAAA,oBAAAI;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,SAAS,cAAc,cAAAC,aAAY,aAAAC,kBAAiB;AACpD,SAAS,WAAAC,gBAAyB;AAClC,SAAS,QAAAC,aAAY;AACrB,SAAS,aAAAC,kBAAiB;AAyCnB,SAAS,WAAmB;AACjC,QAAM,WAAW,QAAQ,IAAI,yBAAyB;AACtD,QAAM,MAAM,QAAQ,IAAI,oBAAoB;AAC5C,SAAO,WAAW,GAAG,SAAS,QAAQ,QAAQ,EAAE,CAAC,IAAI,IAAI,QAAQ,SAAS,EAAE,CAAC,KAAK;AACpF;AAGO,SAAS,wBAA8B;AAC5C,QAAM,IAAIA,WAAU,UAAU,CAAC,WAAW,YAAY,qBAAqB,GAAG;AAAA,IAC5E,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,MAAI,EAAE,WAAW,GAAG;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;AAMA,SAAS,qBAA6B;AACpC,MAAI,oBAAoB,EAAG,QAAO;AAClC,SAAOD,MAAKD,SAAQ,GAAG,SAAS;AAClC;AASA,eAAsB,cAAc,OAAoC,CAAC,GAKtE;AACD,wBAAsB;AAEtB,QAAM,QAAQ,SAAS;AACvB,QAAM,UAAU,OAAO,KAAK,kBAAkB,CAAC;AAK/C,EAAAD,WAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAK1C,EAAAA,WAAU,uBAAuB,EAAE,WAAW,KAAK,CAAC;AACpD,QAAM,iBAAiBE,MAAKD,SAAQ,GAAG,cAAc;AACrD,MAAIF,YAAW,cAAc,GAAG;AAC9B,iBAAa,gBAAgB,sBAAsB;AAAA,EACrD;AACA,MAAI,CAACA,YAAW,YAAY,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR,sBAAsB,YAAY;AAAA,IACpC;AAAA,EACF;AAIA,MAAI,oBAAoB,GAAG;AACzB,UAAM,OAAO,oBAAoB;AACjC,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,kBAAkB,uBAAuB;AACvD,QAAI;AAAE,uBAAiB;AAAA,IAAG,SAAS,KAAK;AACtC,cAAQ,KAAK,8CAA0C,IAAc,OAAO,EAAE;AAC9E,cAAQ,KAAK,wBAAwB,KAAK,4DAAuD;AAAA,IACnG;AAAA,EACF,OAAO;AAEL,IAAAC,WAAUE,MAAKD,SAAQ,GAAG,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3D;AAGA,UAAQ,IAAI,aAAa,KAAK,KAAK;AACnC,QAAM,OAAOE,WAAU,UAAU,CAAC,QAAQ,KAAK,GAAG,EAAE,UAAU,SAAS,OAAO,WAAW,SAAS,IAAQ,CAAC;AAC3G,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI,mBAAmB,eAAe,KAAK,SAAS;AAAA,EAC5D;AAGA,EAAAA,WAAU,UAAU,CAAC,MAAM,MAAM,cAAc,GAAG,EAAE,UAAU,SAAS,SAAS,IAAO,CAAC;AAIxF,QAAM,WAAW,mBAAmB;AACpC,QAAMC,QAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IAAU;AAAA,IACV;AAAA,IAAa;AAAA,IACb;AAAA,IAAM,aAAa,aAAa;AAAA,IAChC;AAAA,IAAM,aAAa,gBAAgB;AAAA,IACnC;AAAA,IAAM,aAAa,aAAa;AAAA,IAChC;AAAA,IAAM,aAAa,YAAY;AAAA,IAC/B;AAAA,IAAM,GAAG,WAAW;AAAA,IACpB;AAAA,IAAM,GAAG,YAAY;AAAA,IACrB;AAAA,IAAM,GAAG,iBAAiB;AAAA,IAC1B;AAAA,IAAM,GAAG,QAAQ;AAAA,IACjB;AAAA,IAAM,GAAGF,MAAKD,SAAQ,GAAG,SAAS,CAAC;AAAA,IACnC;AAAA,IAAM,GAAG,qBAAqB;AAAA,IAC9B;AAAA,IAAM,oBAAoB,OAAO;AAAA,IACjC;AAAA,EACF;AACA,QAAM,MAAME,WAAU,UAAUC,OAAM,EAAE,UAAU,SAAS,OAAO,WAAW,SAAS,IAAO,CAAC;AAC9F,MAAI,IAAI,WAAW,GAAG;AACpB,UAAM,IAAI,mBAAmB,4BAA4B,KAAK,GAAG;AAAA,EACnE;AAEA,SAAO,EAAE,OAAO,aAAa,eAAe,gBAAgB,kBAAkB,aAAa,cAAc;AAC3G;AAOA,eAAsB,sBAAsB,YAAY,KAA0B;AAChF,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAM,oBAAoB,aAAa;AAC7C,SAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,QAAI;AACF,YAAM,IAAI,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,GAAK,EAAE,CAAC;AACjE,UAAI,EAAE,GAAI,QAAO;AAAA,IACnB,QAAQ;AAAA,IAAsB;AAC9B,UAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAK,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAMO,SAAS,aAAmB;AACjC,EAAAD,WAAU,UAAU,CAAC,QAAQ,cAAc,GAAG,EAAE,UAAU,SAAS,SAAS,IAAO,CAAC;AACpF,EAAAA,WAAU,UAAU,CAAC,MAAM,MAAM,cAAc,GAAG,EAAE,UAAU,SAAS,SAAS,IAAO,CAAC;AAC1F;AAMA,eAAsB,aAAa,gBAAwC;AACzE,aAAW;AACX,QAAM,cAAc,EAAE,eAAe,CAAC;AACxC;AAGO,SAAS,eAAuE;AACrF,QAAM,IAAIA,WAAU,UAAU,CAAC,WAAW,YAAY,qBAAqB,cAAc,GAAG;AAAA,IAC1F,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,QAAM,UAAU,EAAE,UAAU,IAAI,KAAK;AACrC,MAAI,WAAW,UAAW,QAAO,EAAE,SAAS,MAAM;AAClD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,SAAS;AAAA,IAChB,SAAS,oBAAoB,aAAa;AAAA,EAC5C;AACF;AA/OA,IAkCaL,aACP,cACA,mBACA,aAIA,uBACA,wBAKA,eACA,kBACA,eACA,cAEA,gBAIA,eAEO;AA1Db;AAAA;AAAA;AA0BA;AAQO,IAAMA,cAAaI,MAAKD,SAAQ,GAAG,SAAS;AACnD,IAAM,eAAeC,MAAKJ,aAAY,UAAU;AAChD,IAAM,oBAAoBI,MAAKJ,aAAY,kBAAkB;AAC7D,IAAM,cAAcI,MAAKJ,aAAY,QAAQ;AAI7C,IAAM,wBAAwBI,MAAKJ,aAAY,mBAAmB;AAClE,IAAM,yBAAyBI,MAAK,uBAAuB,cAAc;AAKzE,IAAM,gBAAgB,SAAS,QAAQ,IAAI,wBAAwB,SAAS,EAAE;AAC9E,IAAM,mBAAmB,SAAS,QAAQ,IAAI,2BAA2B,SAAS,EAAE;AACpF,IAAM,gBAAgB,SAAS,QAAQ,IAAI,wBAAwB,SAAS,EAAE;AAC9E,IAAM,eAAe,SAAS,QAAQ,IAAI,uBAAuB,SAAS,EAAE;AAE5E,IAAM,iBAAiB;AAIvB,IAAM,gBAAgB;AAEf,IAAM,qBAAN,cAAiC,MAAM;AAAA,MAC5C,YAAY,SAAiC,OAAiB;AAC5D,cAAM,OAAO;AAD8B;AAE3C,aAAK,OAAO;AAAA,MACd;AAAA,MAH6C;AAAA,IAI/C;AAAA;AAAA;;;AC/DA;AAAA;AAAA;AAAA;AAAA;AASA,SAAS,cAAAG,aAAY,aAAAC,YAAW,iBAAAC,gBAAe,aAAAC,YAAW,gBAAAC,eAAc,mBAAmB;AAC3F,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,YAAAC,iBAAgB;AACzB,SAAS,mBAAAC,wBAAuB;AAoFhC,SAAS,yBAAyB,KAA6C;AAC7E,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,eAAe,KAAK,GAAG,IAAI,MAAM;AAC1C;AAEO,SAAS,UAAU,MAAgC;AACxD,QAAM,OAAuB,CAAC;AAC9B,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,WAAW,YAAY,EAAG,MAAK,SAAS,EAAE,MAAM,aAAa,MAAM;AAAA,aAChE,EAAE,WAAW,YAAY,EAAG,MAAK,aAAa,EAAE,MAAM,aAAa,MAAM;AAAA,aACzE,MAAM,cAAe,MAAK,WAAW;AAAA,aACrC,MAAM,WAAY,MAAK,QAAQ;AAAA,aAC/B,MAAM,aAAa,MAAM,KAAM,MAAK,QAAQ;AAAA,aAC5C,MAAM,cAAe,MAAK,WAAW;AAAA,EAChD;AACA,MAAI,CAAC,KAAK,YAAY;AACpB,UAAM,UAAU,yBAAyB,QAAQ,IAAI,kBAAkB;AACvE,QAAI,QAAS,MAAK,aAAa;AAAA,EACjC;AAIA,SAAO;AACT;AAmBA,SAAS,kBAAwB;AAC/B,EAAAP,WAAUQ,aAAY,EAAE,WAAW,KAAK,CAAC;AACzC,EAAAR,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,EAAAA,WAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtC,EAAAA,WAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,EAAAA,WAAUK,MAAKG,aAAY,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7D;AAEA,SAAS,mBAcP;AACA,QAAM,iBAAiBH,MAAK,WAAW,kBAAkB;AACzD,QAAM,yBAAyBA,MAAK,WAAW,qBAAqB;AACpE,QAAM,yBAAyBA,MAAK,WAAW,qBAAqB;AACpE,QAAM,wBAAwBA,MAAK,WAAW,oBAAoB;AAClE,QAAM,wBAAwBA,MAAK,WAAW,oBAAoB;AAClE,QAAM,sBAAsBA,MAAK,WAAW,kBAAkB;AAC9D,QAAM,uBAAuBA,MAAK,WAAW,mBAAmB;AAChE,QAAM,wBAAwBA,MAAK,WAAW,oBAAoB;AAClE,QAAM,yBAAyBA,MAAK,WAAW,qBAAqB;AACpE,QAAM,2BAA2BA,MAAK,WAAW,uBAAuB;AACxE,QAAM,6BAA6BA,MAAK,WAAW,0BAA0B;AAC7E,QAAM,mBAAmBA,MAAK,WAAW,mBAAmB;AAC5D,QAAM,uBAAuBA,MAAK,WAAW,mBAAmB;AAChE,QAAM,sBAAsBA,MAAK,WAAW,sBAAsB;AAClE,QAAM,wBAAwBA,MAAK,WAAW,wBAAwB;AACtE,QAAM,oBAAoBA,MAAK,WAAW,oBAAoB;AAE9D,EAAAJ,eAAc,gBAAgB,eAAe,OAAO;AACpD,EAAAA,eAAc,wBAAwB,kBAAkB,OAAO;AAC/D,EAAAA,eAAc,wBAAwB,kBAAkB,OAAO;AAC/D,EAAAA,eAAc,uBAAuB,iBAAiB,OAAO;AAC7D,EAAAA,eAAc,uBAAuB,iBAAiB,OAAO;AAC7D,EAAAA,eAAc,qBAAqB,eAAe,OAAO;AACzD,EAAAA,eAAc,sBAAsB,gBAAgB,OAAO;AAC3D,EAAAA,eAAc,uBAAuB,iBAAiB,OAAO;AAC7D,EAAAA,eAAc,wBAAwB,kBAAkB,OAAO;AAC/D,EAAAA,eAAc,0BAA0B,oBAAoB,OAAO;AACnE,EAAAA,eAAc,4BAA4B,uBAAuB,OAAO;AACxE,EAAAA,eAAc,kBAAkB,kBAAkB,OAAO;AACzD,EAAAA,eAAc,sBAAsB,sBAAsB,OAAO;AACjE,EAAAA,eAAc,qBAAqB,sBAAsB,OAAO;AAChE,EAAAA,eAAc,uBAAuB,wBAAwB,OAAO;AACpE,EAAAA,eAAc,mBAAmB,qBAAqB,OAAO;AAE7D,EAAAC,WAAU,gBAAgB,GAAK;AAC/B,EAAAA,WAAU,wBAAwB,GAAK;AACvC,EAAAA,WAAU,wBAAwB,GAAK;AACvC,EAAAA,WAAU,uBAAuB,GAAK;AACtC,EAAAA,WAAU,uBAAuB,GAAK;AACtC,EAAAA,WAAU,qBAAqB,GAAK;AACpC,EAAAA,WAAU,sBAAsB,GAAK;AACrC,EAAAA,WAAU,uBAAuB,GAAK;AACtC,EAAAA,WAAU,wBAAwB,GAAK;AACvC,EAAAA,WAAU,0BAA0B,GAAK;AACzC,EAAAA,WAAU,4BAA4B,GAAK;AAC3C,EAAAA,WAAU,kBAAkB,GAAK;AACjC,EAAAA,WAAU,sBAAsB,GAAK;AACrC,EAAAA,WAAU,qBAAqB,GAAK;AACpC,EAAAA,WAAU,uBAAuB,GAAK;AACtC,EAAAA,WAAU,mBAAmB,GAAK;AAElC,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,sBAAsB;AAAA,IACtB,wBAAwB;AAAA,IACxB,uBAAuB;AAAA,IACvB,yBAAyB;AAAA,EAC3B;AACF;AAUA,SAAS,oBAAoB,KAAyB,SAAS,KAAa;AAC1E,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IACJ,QAAQ,iBAAiB,EAAE,EAC3B,MAAM,GAAG,MAAM;AACpB;AAEA,SAAS,iBAAiB,OAAuB;AAG/C,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAUA,SAAS,sBAAqC;AAC5C,QAAM,aAAa,QAAQ,KAAK,CAAC;AACjC,MAAI,cAAcH,YAAW,UAAU,EAAG,QAAO;AACjD,SAAO;AACT;AAEA,SAAS,eAAe,MAAmP;AACzQ,QAAM,YAAYM,MAAKG,aAAY,kBAAkB;AACrD,QAAM,cAAc,oBAAoB,KAAK,UAAU;AACvD,QAAM,aAAa,oBAAoB,KAAK,MAAM;AAClD,QAAM,YAAY,oBAAoB,KAAK,KAAK;AAChD,QAAM,YAAY,oBAAoB,KAAK,KAAK;AAChD,QAAM,WAAW,oBAAoB,KAAK,QAAQ,OAAO,EAAE;AAC3D,QAAM,gBAAgB,oBAAoB,KAAK,aAAa,QAAQ,EAAE;AAEtE,QAAM,gBAAgB,oBAAoB,KAAK,aAAa,IAAI,IAAI;AACpE,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,sBAAsB,iBAAiB,WAAW,CAAC;AAAA,IACnD,2BAA2B,iBAAiB,SAAS,CAAC;AAAA,IACtD,eAAe,iBAAiB,QAAQ,CAAC;AAAA,IACzC,oBAAoB,iBAAiB,aAAa,CAAC;AAAA,IACnD,kBAAkB,iBAAiB,SAAsB,CAAC;AAAA,EAC5D;AACA,MAAI,cAAe,OAAM,KAAK,kBAAkB,iBAAiB,aAAa,CAAC,EAAE;AACjF,MAAI,WAAY,OAAM,KAAK,kBAAkB,iBAAiB,UAAU,CAAC,EAAE;AAC3E,MAAI,UAAW,OAAM,KAAK,iBAAiB,iBAAiB,SAAS,CAAC,EAAE;AACxE,MAAI,UAAW,OAAM,KAAK,gBAAgB,iBAAiB,SAAS,CAAC,EAAE;AACvE,MAAI,KAAK,sBAAsB,QAAW;AACxC,UAAM,KAAK,6BAA6B,iBAAiB,KAAK,oBAAoB,QAAQ,IAAI,CAAC,EAAE;AAAA,EACnG;AACA,QAAM,KAAK,0BAA0B,iBAAiB,KAAK,iBAAiB,QAAQ,IAAI,CAAC,EAAE;AAE3F,QAAM,WAAW,oBAAoB,KAAK,kBAAkB,UAAU,EAAE;AACxE,QAAM,KAAK,0BAA0B,iBAAiB,QAAQ,CAAC,EAAE;AACjE,QAAM,KAAK,EAAE;AACb,EAAAP,eAAcQ,cAAa,MAAM,KAAK,IAAI,GAAG,OAAO;AACpD,EAAAP,WAAUO,cAAa,GAAK;AAC9B;AASA,SAAS,wBAAgD;AACvD,QAAM,cAAc,QAAQ,IAAI,wBAAwB,YAAY;AACpE,MAAI,gBAAgB,eAAe,gBAAgB,SAAU,QAAO;AACpE,MAAI;AACF,QAAIV,YAAWU,YAAW,GAAG;AAC3B,YAAM,IAAIN,cAAaM,cAAa,OAAO,EAAE,MAAM,oCAAoC;AACvF,YAAM,MAAM,IAAI,CAAC,GAAG,YAAY;AAChC,UAAI,QAAQ,eAAe,QAAQ,SAAU,QAAO;AAAA,IACtD;AAAA,EACF,QAAQ;AAAA,EAAgC;AACxC,SAAO;AACT;AAEA,SAAS,uBAAgD;AACvD,QAAM,OAAgC,EAAE,UAAU,QAAQ,SAAS;AACnE,MAAI;AACF,SAAK,eAAeH,UAAS,wBAAwB,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AAAA,EAClG,QAAQ;AAAA,EAAC;AACT,MAAI;AACF,UAAM,SAASA,UAAS,6BAA6B,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AAChG,UAAM,WAAW,OAAO,MAAM,6BAA6B;AAC3D,UAAM,YAAY,OAAO,MAAM,qCAAqC;AACpE,UAAM,IAAI,YAAY;AACtB,QAAI,EAAG,MAAK,cAAc,EAAE,CAAC;AAAA,EAC/B,QAAQ;AAAA,EAAC;AACT,MAAI;AACF,SAAK,aAAaA,UAAS,oBAAoB,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA,EAC3G,QAAQ;AAAA,EAAC;AAET,QAAM,YAAYD,MAAKD,SAAQ,GAAG,SAAS;AAE3C,MAAI;AACF,UAAM,WAAW,KAAK,MAAMD,cAAaE,MAAK,WAAW,eAAe,GAAG,OAAO,CAAC;AACnF,UAAM,UAAU,OAAO,KAAK,SAAS,kBAAkB,CAAC,CAAC,EAAE,OAAO,OAAK,SAAS,eAAe,CAAC,CAAC;AACjG,QAAI,QAAQ,OAAQ,MAAK,kBAAkB;AAC3C,QAAI,SAAS,aAAa,YAAa,MAAK,mBAAmB,SAAS,YAAY;AAAA,EACtF,QAAQ;AAAA,EAAC;AAET,MAAI;AACF,UAAM,WAAW,KAAK,MAAMF,cAAaE,MAAK,WAAW,2BAA2B,GAAG,OAAO,CAAC;AAC/F,UAAM,WAAW,OAAO,KAAK,QAAQ;AACrC,QAAI,SAAS,OAAQ,MAAK,cAAc;AAAA,EAC1C,QAAQ;AAAA,EAAC;AAET,MAAI;AACF,UAAM,UAAUC,UAAS,+BAA+B,EAAE,UAAU,SAAS,SAAS,IAAM,CAAC;AAC7F,UAAM,YAAY,QAAQ,MAAM,IAAI,EACjC,OAAO,OAAK,EAAE,SAAS,WAAW,CAAC,EACnC,IAAI,OAAK,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAC/B,OAAO,OAAO;AACjB,QAAI,UAAU,OAAQ,MAAK,wBAAwB;AAAA,EACrD,QAAQ;AAAA,EAAC;AAET,MAAI;AACF,UAAM,cAAcD,MAAK,WAAW,UAAU;AAC9C,UAAM,QAAQ,YAAY,WAAW,EAAE,OAAO,OAAK,EAAE,SAAS,OAAO,CAAC,EAAE,MAAM,EAAE;AAChF,eAAW,KAAK,OAAO;AACrB,YAAM,IAAI,KAAK,MAAMF,cAAaE,MAAK,aAAa,CAAC,GAAG,OAAO,CAAC;AAChE,UAAI,EAAE,SAAS;AAAE,aAAK,aAAa,KAAK,cAAc,EAAE;AAAS;AAAA,MAAO;AAAA,IAC1E;AAAA,EACF,QAAQ;AAAA,EAAC;AAET,SAAO;AACT;AAEA,eAAe,iBAAiB,YAAoB,OAA4G;AAC9J,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG,UAAU,kBAAkB;AAAA,MACtD,SAAS,EAAE,iBAAiB,UAAU,KAAK,GAAG;AAAA,IAChD,CAAC;AACD,QAAI,CAAC,KAAK,GAAI,QAAO,EAAE,MAAM,OAAO,WAAW,QAAQ,gBAAgB,OAAO,cAAc,OAAO;AACnG,UAAM,OAAO,MAAM,KAAK,KAAK;AAE7B,UAAM,OAAO,qBAAqB;AAClC,UAAM,GAAG,UAAU,kBAAkB;AAAA,MACnC,QAAQ;AAAA,MACR,SAAS,EAAE,iBAAiB,UAAU,KAAK,IAAI,gBAAgB,mBAAmB;AAAA,MAClF,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAEjB,WAAO;AAAA,MACL,MAAM,KAAK,aAAa;AAAA,MACxB,WAAW,KAAK,iBAAiB,SAAS;AAAA,MAC1C,gBAAgB,CAAC,CAAC,KAAK;AAAA,MACvB,cAAc,KAAK,iBAAiB;AAAA,IACtC;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,MAAM,OAAO,WAAW,QAAQ,gBAAgB,OAAO,cAAc,OAAO;AAAA,EACvF;AACF;AAYA,SAAS,qBAAqB,YAA0B;AACtD,MAAI;AACJ,MAAI;AAAE,aAAS,IAAI,IAAI,UAAU;AAAA,EAAG,QAC9B;AAAE,UAAM,IAAI,MAAM,wBAAwB,UAAU,EAAE;AAAA,EAAG;AAC/D,QAAM,QAAQ,OAAO;AACrB,QAAM,OAAO,OAAO;AACpB,MAAI,UAAU,WAAW,UAAU,UAAU;AAC3C,UAAM,IAAI,MAAM,oCAAoC,KAAK,EAAE;AAAA,EAC7D;AACA,QAAM,cAAc,SAAS,eAAe,SAAS,eAAe,SAAS;AAC7E,QAAM,WAAW,SAAS,eAAe,KAAK,SAAS,YAAY;AACnE,MAAI,UAAU,WAAW,CAAC,aAAa;AACrC,UAAM,IAAI,MAAM,0DAA0D,UAAU,EAAE;AAAA,EACxF;AACA,MAAI,CAAC,eAAe,CAAC,UAAU;AAC7B,UAAM,IAAI,MAAM,6DAA6D,IAAI,EAAE;AAAA,EACrF;AACF;AAOA,SAAS,qBAA8B;AACrC,QAAM,kBAAkB;AAAA,IACtBA,MAAK,WAAW,kBAAkB;AAAA,IAClCA,MAAK,WAAW,qBAAqB;AAAA,IACrCA,MAAK,WAAW,qBAAqB;AAAA,IACrCA,MAAK,WAAW,oBAAoB;AAAA,IACpCA,MAAK,WAAW,kBAAkB;AAAA,IAClCA,MAAK,WAAW,oBAAoB;AAAA,IACpCA,MAAK,WAAW,qBAAqB;AAAA,EACvC;AACA,MAAI,CAAC,gBAAgB,MAAM,CAAC,MAAMN,YAAW,CAAC,CAAC,EAAG,QAAO;AACzD,MAAI,CAACA,YAAWU,YAAW,EAAG,QAAO;AAErC,QAAM,eAAeJ,MAAKD,SAAQ,GAAG,WAAW,eAAe;AAC/D,MAAI,CAACL,YAAW,YAAY,EAAG,QAAO;AACtC,MAAI;AACF,UAAM,WAAW,KAAK,MAAMI,cAAa,cAAc,OAAO,CAAC;AAC/D,UAAM,QAAQ,UAAU;AACxB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,UAAM,aAAa,CAAC,SAClB,MAAM,QAAQ,MAAM,IAAI,CAAC,KACzB,MAAM,IAAI,EAAE,KAAK,CAAC,UAAmC,OAAO,uBAAuB,IAAI;AACzF,QAAI,CAAC,WAAW,YAAY,EAAG,QAAO;AACtC,QAAI,CAAC,WAAW,aAAa,EAAG,QAAO;AACvC,QAAI,CAAC,WAAW,YAAY,EAAG,QAAO;AACtC,QAAI,CAAC,WAAW,cAAc,EAAG,QAAO;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,eAAsB,eAAe,OAAuB,CAAC,GAAkB;AAC7E,QAAM,aAAa,KAAK,cACnB,yBAAyB,QAAQ,IAAI,kBAAkB,KACvD;AAGL,MAAI;AACF,yBAAqB,UAAU;AAAA,EACjC,SAAS,KAAK;AACZ,YAAQ,MAAO,IAAc,OAAO;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAMA,MAAI,CAAC,KAAK,SAAS,gBAAgB,KAAK,mBAAmB,GAAG;AAC5D,kBAAc,GAAG,UAAU,MAAM;AACjC,UAAM,iBAAiB;AACvB,UAAM,cAAcO,eAAc;AAClC,QAAI,aAAa;AACf,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AACpC,cAAM,gBAAgB,SAAS;AAAA,UAAK,CAAC,MACnC,EAAE,OAAO,KAAK,CAAC,MAAW,EAAE,cAAc,WAAW;AAAA,QACvD;AACA,YAAI,CAAC,eAAe;AAClB,kBAAQ,IAAI,mCAAmC,WAAW;AAAA,CAAwB;AAClF,gBAAM,qBAAqB;AAC3B;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,YAAQ,IAAI,oDAA+C;AAC3D,YAAQ,IAAI,2DAA2D;AACvE;AAAA,EACF;AAEA,UAAQ,IAAI,8BAA8B;AAM1C,MAAI,CAAC,gBAAgB,GAAG;AACtB,YAAQ,IAAI,oCAAoC;AAChD,UAAM,SAAS,MAAM,aAAa,CAAC,WAAW;AAC5C,cAAQ,OAAO,OAAO;AAAA,QACpB,KAAK;AAAkB,kBAAQ,IAAI,qCAAqC;AAAG;AAAA,QAC3E,KAAK;AAAkB,kBAAQ,IAAI,qBAAqB,OAAO,GAAG,EAAE;AAAG;AAAA,QACvE,KAAK;AAAkB,kBAAQ,IAAI,2CAA2C;AAAG;AAAA,QACjF,KAAK;AAAkB,kBAAQ,IAAI,wBAAmB;AAAG;AAAA,QACzD,KAAK;AAAkB,kBAAQ,MAAM,YAAO,OAAO,OAAO,EAAE;AAAG;AAAA,MACjE;AAAA,IACF,CAAC;AACD,QAAI,CAAC,QAAQ;AACX,cAAQ,MAAM,2GAA2G;AACzH,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACA,QAAM,QAAQ,eAAe;AAC7B,MAAI,CAAC,OAAO;AACV,YAAQ,MAAM,uCAAuC;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,UAAyB;AAC7B,MAAI,QAAQ,MAAM,OAAO;AACvB,UAAM,KAAKH,iBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,UAAM,UAAU,MAAM,IAAI,QAAiB,CAACI,aAAY;AACtD,SAAG,SAAS,uDAAuD,CAAC,WAAW;AAC7E,WAAG,MAAM;AACT,cAAM,UAAU,OAAO,KAAK,EAAE,YAAY;AAC1C,QAAAA,SAAQ,YAAY,OAAO,YAAY,KAAK;AAAA,MAC9C,CAAC;AAAA,IACH,CAAC;AACD,QAAI,SAAS;AACX,UAAI;AACF,kBAAU,MAAM,cAAc,YAAY,KAAK;AAAA,MACjD,QAAQ;AAAA,MAAC;AACT,UAAI,SAAS;AACX,gBAAQ,IAAI;AAAA,MACd,OAAO;AACL,gBAAQ,IAAI,gEAAgE;AAAA,MAC9E;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,0BAA0B;AAAA,IACxC;AAAA,EACF;AAGA,gBAAc,GAAG,UAAU,MAAM;AACjC,QAAM,qBAAqB,EAAE,UAAU,KAAK,SAAS,CAAC;AAGtD,QAAM,SAAS,aAAa;AAC5B,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,MAAM,8FAA8F;AAC5G,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,IAAI,kBAAkB;AAC9B,aAAW,KAAK,QAAQ;AACtB,YAAQ,IAAI,YAAO,EAAE,IAAI,GAAG,EAAE,UAAU,KAAK,EAAE,OAAO,MAAM,EAAE,EAAE;AAAA,EAClE;AACA,UAAQ,IAAI;AAGZ,kBAAgB;AAChB,QAAM,UAAU,iBAAiB;AACjC,UAAQ,IAAI,qBAAqB;AACjC,UAAQ,IAAI,KAAK,QAAQ,UAAU,EAAE;AACrC,UAAQ,IAAI,KAAK,QAAQ,kBAAkB,EAAE;AAC7C,UAAQ,IAAI,KAAK,QAAQ,kBAAkB,EAAE;AAC7C,UAAQ,IAAI,KAAK,QAAQ,iBAAiB,EAAE;AAC5C,UAAQ,IAAI,KAAK,QAAQ,iBAAiB,EAAE;AAC5C,UAAQ,IAAI,KAAK,QAAQ,eAAe,EAAE;AAC1C,UAAQ,IAAI,KAAK,QAAQ,gBAAgB,EAAE;AAC3C,UAAQ,IAAI,KAAK,QAAQ,iBAAiB,EAAE;AAC5C,UAAQ,IAAI,KAAK,QAAQ,kBAAkB,EAAE;AAC7C,UAAQ,IAAI,KAAK,QAAQ,oBAAoB;AAAA,CAAI;AAIjD,aAAW,QAAQ,CAAC,QAAQ,MAAM,GAAG;AACnC,UAAM,UAAUN,MAAKG,aAAY,UAAU,MAAM,YAAY;AAC7D,QAAI;AACF,YAAM,MAAM,SAASL,cAAa,SAAS,OAAO,EAAE,KAAK,GAAG,EAAE;AAC9D,UAAI,MAAM,GAAG;AACX,gBAAQ,KAAK,KAAK,SAAS;AAC3B,gBAAQ,IAAI,iBAAiB,IAAI,uBAAuB,GAAG,GAAG;AAAA,MAChE;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAaA,QAAM,oBAAoB;AAS1B,MAAI,gBAAgB;AACpB,MAAI,YAAY;AAChB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAe;AAChC,sBAAgB;AAChB,qBAAe,MAAM,cAAc;AAAA,QACjC,qBAAqB,QAAQ;AAAA,QAC7B,wBAAwB,QAAQ;AAAA,QAChC,wBAAwB,QAAQ;AAAA,QAChC,uBAAuB,QAAQ;AAAA,QAC/B,uBAAuB,QAAQ;AAAA,QAC/B,qBAAqB,QAAQ;AAAA,QAC7B,sBAAsB,QAAQ;AAAA,QAC9B,uBAAuB,QAAQ;AAAA,QAC/B,wBAAwB,QAAQ;AAAA,QAChC,0BAA0B,QAAQ;AAAA,QAClC,4BAA4B,QAAQ;AAAA,QACpC,oBAAoB,CAAC;AAAA,MACvB,CAAC;AACD,cAAQ,IAAI,cAAc,MAAM,IAAI,aAAa,MAAM,YAAY,EAAE;AAAA,IACvE,WAAW,MAAM,SAAS,UAAU;AAClC,kBAAY;AACZ,yBAAmB,MAAM,cAAc;AAAA,QACrC,qBAAqB,QAAQ;AAAA,QAC7B,uBAAuB,QAAQ;AAAA,QAC/B,wBAAwB,QAAQ;AAAA,QAChC,wBAAwB,QAAQ;AAAA,QAChC,uBAAuB,QAAQ;AAAA,QAC/B,uBAAuB,QAAQ;AAAA,QAC/B,qBAAqB,QAAQ;AAAA,QAC7B,sBAAsB,QAAQ;AAAA,QAC9B,uBAAuB,QAAQ;AAAA,QAC/B,wBAAwB,QAAQ;AAAA,QAChC,4BAA4B,QAAQ;AAAA,QACpC,0BAA0B,QAAQ;AAAA,MACpC,CAAC;AACD,cAAQ,IAAI,cAAc,MAAM,IAAI,aAAa,MAAM,YAAY,EAAE;AAAA,IACvE;AAAA,EACF;AACA,UAAQ,IAAI;AAGZ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,YAAY;AACzB,aAAS,KAAK;AACd,YAAQ,KAAK;AACb,YAAQ,KAAK;AAAA,EACf,QAAQ;AAAA,EAER;AACA,QAAM,UAAU,MAAM,iBAAiB,YAAY,KAAK;AACxD,QAAM,cAAc,QAAQ,iBAAiB,gBAAgB,QAAQ;AAKrE,MAAI,iBAAiB,CAAC,KAAK,OAAO;AAChC,QAAI,aAAa;AACf,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,UAAU,yBAAyB;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS,EAAE,iBAAiB,UAAU,KAAK,IAAI,gBAAgB,mBAAmB;AAAA,UAClF,MAAM;AAAA,QACR,CAAC;AACD,YAAI,SAAS;AACb,YAAI,SAAS,IAAI;AACf,gBAAM,SAAS,MAAM,SAAS,KAAK;AACnC,mBAAS,OAAO;AAChB,UAAAF,eAAcI,MAAKG,aAAY,UAAU,GAAG,SAAS,MAAM,EAAE,MAAM,IAAM,CAAC;AAAA,QAC5E,OAAO;AACL,kBAAQ,KAAK,gGAAsF;AAAA,QACrG;AACA,cAAM,MAAM,iBAAiB,EAAE,YAAY,aAAa,QAAQ,OAAO,KAAK,CAAC;AAC7E,gBAAQ,IAAI,6CAA6C,IAAI,IAAI,EAAE;AACnE,gBAAQ,IAAI,iBAAiB,IAAI,GAAG,EAAE;AACtC,gBAAQ,IAAI;AAAA,MACd,SAAS,KAAK;AACZ,gBAAQ,KAAK,oCAAgC,IAAc,OAAO,EAAE;AACpE,gBAAQ,KAAK,gEAAgE;AAC7E,gBAAQ,IAAI;AAAA,MACd;AAAA,IACF,OAAO;AACL,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,UAAU,yBAAyB;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK;AAAA,YAChC,gBAAgB;AAAA,UAClB;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AACD,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,UAAU,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACpD,gBAAM,IAAI,MAAM,0BAA0B,SAAS,MAAM,MAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,QACxF;AACA,cAAM,SAAS,MAAM,SAAS,KAAK;AACnC,QAAAP,eAAcI,MAAKG,aAAY,UAAU,GAAG,OAAO,QAAQ,MAAM,EAAE,MAAM,IAAM,CAAC;AAChF,cAAM,MAAM,iBAAiB,EAAE,YAAY,aAAa,OAAO,MAAM,CAAC;AACtE,gBAAQ,IAAI,8CAA8C,IAAI,IAAI,EAAE;AACpE,gBAAQ,IAAI,iBAAiB,IAAI,GAAG,EAAE;AACtC,gBAAQ,IAAI,iBAAiB,OAAO,UAAU,YAAY;AAC1D,gBAAQ,IAAI,4DAA4D;AACxE,gBAAQ,IAAI;AAAA,MACd,SAAS,KAAK;AACZ,gBAAQ,KAAK,qCAAiC,IAAc,OAAO,EAAE;AACrE,gBAAQ,KAAK,0EAA0E;AACvF,gBAAQ,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa,CAAC,KAAK,OAAO;AAC5B,QAAI;AACF,UAAI,aAAa;AAEf,cAAM,UAAUH,MAAKG,aAAY,UAAU;AAC3C,YAAI,CAACT,YAAW,OAAO,GAAG;AACxB,gBAAM,WAAW,MAAM,MAAM,GAAG,UAAU,yBAAyB;AAAA,YACjE,QAAQ;AAAA,YACR,SAAS,EAAE,iBAAiB,UAAU,KAAK,IAAI,gBAAgB,mBAAmB;AAAA,YAClF,MAAM;AAAA,UACR,CAAC;AACD,cAAI,SAAS,IAAI;AACf,kBAAM,SAAS,MAAM,SAAS,KAAK;AACnC,YAAAE,eAAc,SAAS,OAAO,QAAQ,MAAM,EAAE,MAAM,IAAM,CAAC;AAAA,UAC7D;AAAA,QACF;AACA,cAAM,MAAM,uBAAuB,EAAE,YAAY,aAAa,IAAI,OAAO,KAAK,CAAC;AAC/E,gBAAQ,IAAI,6CAA6C,IAAI,IAAI,EAAE;AACnE,gBAAQ,IAAI,iBAAiB,IAAI,GAAG,EAAE;AAAA,MACxC,OAAO;AACL,cAAM,WAAW,MAAM,MAAM,GAAG,UAAU,yBAAyB;AAAA,UACjE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK;AAAA,YAChC,gBAAgB;AAAA,UAClB;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AACD,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,UAAU,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACpD,gBAAM,IAAI,MAAM,0BAA0B,SAAS,MAAM,MAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,QACxF;AACA,cAAM,SAAS,MAAM,SAAS,KAAK;AACnC,QAAAA,eAAcI,MAAKG,aAAY,UAAU,GAAG,OAAO,QAAQ,MAAM,EAAE,MAAM,IAAM,CAAC;AAChF,cAAM,MAAM,uBAAuB,EAAE,YAAY,aAAa,OAAO,MAAM,CAAC;AAC5E,gBAAQ,IAAI,8CAA8C,IAAI,IAAI,EAAE;AACpE,gBAAQ,IAAI,iBAAiB,IAAI,GAAG,EAAE;AAAA,MACxC;AACA,cAAQ,IAAI;AAAA,IACd,SAAS,KAAK;AACZ,cAAQ,KAAK,4CAAwC,IAAc,OAAO,EAAE;AAC5E,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAKA,QAAM,eAAe,oBAAoB;AAGzC,QAAM,gBAAgB,sBAAsB;AAC5C,iBAAe,EAAE,YAAY,QAAQ,OAAO,OAAO,MAAM,QAAQ,MAAM,WAAW,QAAQ,WAAW,WAAW,cAAc,mBAAmB,gBAAgB,QAAQ,gBAAgB,gBAAgB,cAAc,CAAC;AACxN,UAAQ,IAAI,mBAAmBC,YAAW,EAAE;AAC5C,UAAQ,IAAI,gBAAgB,QAAQ,SAAS,wBAAwB;AACrE,MAAI,QAAQ,eAAgB,SAAQ,IAAI,0DAA0D;AAClG,MAAI,aAAc,SAAQ,IAAI,oBAAoB,YAAY,EAAE;AAAA,MAC3D,SAAQ,KAAK,iGAA4F;AAE9G,MAAI;AACF,UAAM,UAAU,MAAM,kBAAkB,EAAE,YAAY,KAAK,MAAM,CAAC;AAClE,YAAQ,IAAI,cAAc,QAAQ,OAAO,SAAS;AAAA,EACpD,SAAS,KAAK;AACZ,YAAQ,KAAK,2CAAuC,IAAc,OAAO,EAAE;AAAA,EAC7E;AACA,UAAQ,IAAI;AAEZ,MAAI,QAAQ,gBAAgB;AAC1B,UAAM,EAAE,uBAAAG,uBAAsB,IAAI,MAAM;AACxC,QAAI;AACF,MAAAA,uBAAsB;AAAA,IACxB,SAAS,KAAK;AACZ,cAAQ,MAAM;AAAA,SAAQ,IAAc,OAAO,EAAE;AAC7C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,uCAAuC;AACnD,UAAM,iBAAiB,SAAS,QAAQ,IAAI,2BAA2B,KAAK,EAAE;AAC9E,UAAM,EAAE,OAAO,aAAa,gBAAgB,YAAY,IACtD,MAAM,cAAc,EAAE,eAAe,CAAC;AACxC,YAAQ,IAAI,mBAAc,KAAK,EAAE;AACjC,YAAQ,IAAI,kCAA6B,WAAW,YAAY,cAAc,QAAQ,WAAW,EAAE;AACnG,YAAQ,IAAI,wCAAwC;AACpD,UAAM,QAAQ,MAAM,sBAAsB,GAAM;AAChD,QAAI,OAAO;AACT,cAAQ,IAAI,0BAAqB;AAAA,IACnC,OAAO;AACL,cAAQ,MAAM,sDAAiD;AAC/D,cAAQ,MAAM,+CAA+C;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,IAAI;AAAA,EACd;AAGA,MAAI,mBAAmB;AACrB,QAAI;AACF,YAAM,OAAOF,eAAc;AAC3B,UAAI,MAAM;AACR,cAAM,WAAW,MAAM,yBAAyB,YAAY,OAAO,IAAI;AACvE,YAAI,WAAW,GAAG;AAChB,kBAAQ,IAAI,WAAW,QAAQ,kDAAkD,IAAI,GAAG;AACxF,kBAAQ,IAAI,2DAA2D;AAAA,QACzE;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,sCAAkC,IAAc,OAAO;AAAA,CAAI;AAAA,IAC1E;AAGA,QAAI;AACF,YAAM,OAAOA,eAAc;AAC3B,UAAI,MAAM;AACR,cAAM,SAAS,MAAM,oBAAoB,YAAY,OAAO,IAAI;AAChE,YAAI,OAAO,WAAW,GAAG;AACvB,kBAAQ,IAAI,UAAU,OAAO,QAAQ,cAAc,OAAO,QAAQ,sCAAsC;AACxG,kBAAQ,IAAI,wDAAwD;AAAA,QACtE;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,qCAAiC,IAAc,OAAO;AAAA,CAAI;AAAA,IACzE;AAAA,EACF;AAGA,MAAI,SAAS;AACX,UAAM,EAAE,oBAAAG,oBAAmB,IAAI,MAAM;AACrC,UAAMA,oBAAmB,EAAE,gBAAgB,MAAM,aAAa,QAAQ,CAAC;AAAA,EACzE;AAGA,UAAQ,IAAI,0BAAqB;AACnC;AAEA,SAASH,iBAA+B;AACtC,MAAI;AACF,UAAM,YAAYJ,UAAS,6BAA6B,EAAE,UAAU,SAAS,SAAS,IAAK,CAAC,EAAE,KAAK;AACnG,UAAM,WAAW,UAAU,MAAM,6BAA6B;AAC9D,UAAM,YAAY,UAAU,MAAM,qCAAqC;AACvE,UAAM,QAAQ,YAAY;AAC1B,WAAO,QAAQ,MAAM,CAAC,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,0BAAyC;AAChD,QAAM,MAAM,QAAQ,IAAI;AAGxB,QAAM,YAAY,MAAM,IAAI,QAAQ,OAAO,GAAG;AAC9C,QAAM,cAAcD,MAAKD,SAAQ,GAAG,WAAW,YAAY,SAAS;AACpE,SAAOL,YAAW,WAAW,IAAI,cAAc;AACjD;AASA,SAAS,uBAAuB,aAAuC;AACrE,QAAM,WAA6B,CAAC;AACpC,QAAM,QAAQ,YAAY,WAAW,EAAE,OAAO,OAAK,EAAE,SAAS,QAAQ,CAAC;AAEvE,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,KAAK,QAAQ,UAAU,EAAE;AAC3C,UAAM,WAAWM,MAAK,aAAa,IAAI;AAEvC,QAAI;AACF,YAAM,UAAUF,cAAa,UAAU,OAAO;AAC9C,YAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,OAAO,OAAO;AAGhD,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI;AACF,gBAAM,QAAQ,KAAK,MAAM,MAAM,CAAC,CAAC;AACjC,cAAI,MAAM,SAAS,UACf,OAAO,MAAM,SAAS,YAAY,YAClC,MAAM,QAAQ,QAAQ,WAAW,iCAAiC,GAAG;AACvE,qBAAS,KAAK;AAAA,cACZ,YAAY;AAAA,cACZ,cAAc;AAAA,cACd,SAAS,MAAM,QAAQ,QAAQ,MAAM,GAAG,GAAI;AAAA,cAC5C,UAAU,EAAE,QAAQ,qBAAqB;AAAA,YAC3C,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AAAA,QAAC;AAAA,MACX;AAGA,YAAM,eAAyB,CAAC;AAChC,eAAS,IAAI,MAAM,SAAS,GAAG,KAAK,KAAK,aAAa,SAAS,IAAI,KAAK;AACtE,YAAI;AACF,gBAAM,QAAQ,KAAK,MAAM,MAAM,CAAC,CAAC;AACjC,cAAI,MAAM,SAAS,QAAQ;AACzB,kBAAM,OAAO,OAAO,MAAM,SAAS,YAAY,WAC3C,MAAM,QAAQ,UACd,MAAM,QAAQ,MAAM,SAAS,OAAO,IAClC,MAAM,QAAQ,QAAQ,IAAI,CAAC,MAAW,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAW,OAAO,MAAM,QAAQ,EAAE,KAAK,GAAG,IACrG;AACN,gBAAI,QAAQ,KAAK,SAAS,MAAM,KAAK,SAAS,OAAQ,CAAC,KAAK,WAAW,iCAAiC,GAAG;AACzG,2BAAa,KAAK,IAAI;AAAA,YACxB;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAAC;AAAA,MACX;AACA,iBAAW,OAAO,aAAa,QAAQ,GAAG;AACxC,iBAAS,KAAK;AAAA,UACZ,YAAY;AAAA,UACZ,cAAc;AAAA,UACd,SAAS,IAAI,MAAM,GAAG,GAAI;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,SAAO;AACT;AAEA,eAAe,yBAAyB,YAAoB,OAAe,MAA+B;AACxG,QAAM,cAAc,wBAAwB;AAC5C,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,WAAW,uBAAuB,WAAW;AACnD,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,UAAQ,IAAI,SAAS,SAAS,MAAM,+CAA+C;AAGnF,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,KAAK;AAC7C,UAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,GAAG;AACvC,QAAI;AACF,YAAM,OAAO,MAAM,MAAM,GAAG,UAAU,+BAA+B;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,iBAAiB,UAAU,KAAK;AAAA,UAChC,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,CAAC;AAAA,MAChD,CAAC;AACD,UAAI,KAAK,IAAI;AACX,cAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,iBAAS,OAAO;AAAA,MAClB;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,SAAO;AACT;AAgBA,SAAS,mBAAmB,SAAsB;AAChD,MAAI,OAAO,YAAY,SAAU,QAAO,QAAQ,MAAM,GAAG,GAAI;AAC7D,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QACJ,OAAO,CAAC,MAAW,OAAO,MAAM,YAAa,GAAG,SAAS,MAAO,EAChE,IAAI,CAAC,MAAW,OAAO,MAAM,WAAW,IAAK,GAAG,QAAQ,EAAG,EAC3D,KAAK,GAAG,EACR,MAAM,GAAG,GAAI;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAuC;AAClE,QAAM,UAAUA,cAAa,UAAU,OAAO;AAC9C,QAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,OAAO,OAAO;AAChD,QAAM,WAAgC,CAAC;AAEvC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI;AACF,YAAM,QAAQ,KAAK,MAAM,MAAM,CAAC,CAAC;AACjC,UAAI,MAAM,SAAS,UAAU,MAAM,SAAS,YAAa;AAEzD,YAAM,MAAyB;AAAA,QAC7B,eAAe;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,SAAS,mBAAmB,MAAM,SAAS,OAAO;AAAA,MACpD;AAEA,UAAI,MAAM,SAAS,aAAa;AAC9B,YAAI,MAAM,QAAQ,MAAM,SAAS,OAAO,GAAG;AACzC,gBAAM,YAAY,MAAM,QAAQ,QAC7B,OAAO,CAAC,MAAW,GAAG,SAAS,UAAU,EACzC,IAAI,CAAC,OAAY;AAAA,YAChB,MAAM,EAAE,QAAQ;AAAA,YAChB,OAAO,KAAK,UAAU,EAAE,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG;AAAA,YACjD,IAAI,EAAE,MAAM;AAAA,UACd,EAAE;AACJ,cAAI,UAAU,SAAS,EAAG,KAAI,aAAa;AAAA,QAC7C;AACA,YAAI,MAAM,SAAS,MAAO,KAAI,QAAQ,MAAM,QAAQ;AACpD,YAAI,MAAM,SAAS,OAAO;AACxB,cAAI,QAAQ;AAAA,YACV,cAAc,MAAM,QAAQ,MAAM;AAAA,YAClC,eAAe,MAAM,QAAQ,MAAM;AAAA,YACnC,6BAA6B,MAAM,QAAQ,MAAM;AAAA,YACjD,yBAAyB,MAAM,QAAQ,MAAM;AAAA,UAC/C;AAAA,QACF;AAAA,MACF;AAEA,UAAI,IAAI,QAAQ,SAAS,EAAG,UAAS,KAAK,GAAG;AAAA,IAC/C,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,SAAO;AACT;AAEA,eAAe,oBAAoB,YAAoB,OAAe,MAA+D;AACnI,QAAM,cAAc,wBAAwB;AAC5C,MAAI,CAAC,YAAa,QAAO,EAAE,UAAU,GAAG,UAAU,EAAE;AAEpD,QAAM,QAAQ,YAAY,WAAW,EAAE,OAAO,OAAK,EAAE,SAAS,QAAQ,CAAC;AACvE,MAAI,MAAM,WAAW,EAAG,QAAO,EAAE,UAAU,GAAG,UAAU,EAAE;AAE1D,UAAQ,IAAI,SAAS,MAAM,MAAM,qCAAqC;AAEtE,QAAM,wBAAwB;AAC9B,MAAI,gBAAgB;AACpB,MAAI,gBAAgB;AAGpB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,CAAC;AAClC,UAAM,WAA4E,CAAC;AAEnF,eAAW,QAAQ,OAAO;AACxB,YAAM,YAAY,KAAK,QAAQ,UAAU,EAAE;AAC3C,YAAM,WAAWE,MAAK,aAAa,IAAI;AAEvC,UAAI;AACF,cAAM,cAAc,oBAAoB,QAAQ;AAChD,cAAM,WAAW,YAAY,SAAS,wBAClC,YAAY,MAAM,CAAC,qBAAqB,IACxC;AAEJ,YAAI,SAAS,SAAS,GAAG;AACvB,mBAAS,KAAK,EAAE,eAAe,WAAW,SAAS,CAAC;AAAA,QACtD;AAAA,MACF,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,QAAI,SAAS,WAAW,EAAG;AAE3B,QAAI;AACF,YAAM,OAAO,MAAM,MAAM,GAAG,UAAU,gCAAgC;AAAA,QACpE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,iBAAiB,UAAU,KAAK;AAAA,UAChC,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAAA,MACzC,CAAC;AACD,UAAI,KAAK,IAAI;AACX,cAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,yBAAiB,OAAO;AACxB,yBAAiB,OAAO;AAAA,MAC1B;AAAA,IACF,QAAQ;AAAA,IAAC;AAGT,eAAW,QAAQ,OAAO;AACxB,YAAM,YAAY,KAAK,QAAQ,UAAU,EAAE;AAC3C,YAAM,WAAWA,MAAK,aAAa,IAAI;AACvC,UAAI;AACF,cAAM,UAAUF,cAAa,UAAU,OAAO;AAC9C,cAAM,YAAY,QAAQ,MAAM,IAAI,EAAE,OAAO,OAAO,EAAE;AACtD,QAAAF,eAAcI,MAAK,aAAa,SAAS,GAAG,OAAO,SAAS,GAAG,OAAO;AAAA,MACxE,QAAQ;AAAA,MAAC;AAAA,IACX;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,eAAe,UAAU,cAAc;AAC5D;AAxmCA,IA8BMG,aACA,WACA,SACAC,cAEA,qBAsGA;AAzIN;AAAA;AAAA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA,IAAMD,cAAaH,MAAKD,SAAQ,GAAG,SAAS;AAC5C,IAAM,YAAYC,MAAKG,aAAY,OAAO;AAC1C,IAAM,UAAUH,MAAKG,aAAY,KAAK;AACtC,IAAMC,eAAcJ,MAAKG,aAAY,YAAY;AAEjD,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsG5B,IAAM,cAAcH,MAAKG,aAAY,qBAAqB;AAAA;AAAA;;;AC7H1D,SAAS,cAAAM,cAAY,aAAAC,YAAW,iBAAAC,gBAAe,gBAAAC,eAAc,aAAAC,YAAW,gBAAAC,eAAc,cAAAC,aAAY,cAAAC,aAAY,UAAU,WAAW,iBAAiB;AACpJ,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,aAAAC,kBAAiB;AAgZ1B,SAAS,uBAAuB,SAA8C;AAC5E,MAAI,CAACV,aAAW,gBAAgB,GAAG;AAEjC;AAAA,EACF;AAGA,QAAM,eAAeG,cAAa,kBAAkB,OAAO;AAC3D,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,YAAY;AAAA,EAClC,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,gCAAgC,gBAAgB,KAAM,IAAc,OAAO;AAAA,MAE3E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AAGhD,QAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,CAAC,MAAO;AAGZ,aAAW,KAAK,cAAc;AAC5B,QAAI,EAAE,KAAK,SAAS;AAClB,YAAM,IAAI;AAAA,QACR,qBAAqB,gBAAgB,oCAAoC,CAAC;AAAA,MAE5E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAGlD,MAAI;AACF,SAAK,MAAM,OAAO;AAAA,EACpB,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,qBAAqB,gBAAgB;AAAA,MAErC;AAAA,IACF;AAAA,EACF;AAGA,EAAAE,cAAa,kBAAkB,uBAAuB;AAItD,QAAM,UAAU,GAAG,gBAAgB,eAAe,QAAQ,GAAG;AAC7D,MAAI;AACF,IAAAH,eAAc,SAAS,SAAS,OAAO;AAEvC,UAAM,KAAK,SAAS,SAAS,GAAG;AAChC,QAAI;AAAE,gBAAU,EAAE;AAAA,IAAG,UAAE;AAAU,gBAAU,EAAE;AAAA,IAAG;AAChD,IAAAI,YAAW,SAAS,gBAAgB;AAAA,EACtC,SAAS,KAAK;AAEZ,QAAI;AAAE,MAAAC,YAAW,OAAO;AAAA,IAAG,QAAQ;AAAA,IAAe;AAClD,QAAI;AAAE,MAAAF,cAAa,yBAAyB,gBAAgB;AAAA,IAAG,QAAQ;AAAA,IAAe;AACtF,UAAM,IAAI;AAAA,MACR,mBAAmB,gBAAgB,KAAM,IAAc,OAAO,eACjD,uBAAuB;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACF;AAqHO,SAAS,mBAAyB;AACvC,yBAAuB,YAAU;AAC/B,QAAI,QAAQ;AACZ,QAAI,OAAO,cAAc,OAAO,WAAW,eAAe,GAAG;AAC3D,aAAO,OAAO,WAAW,eAAe;AACxC,cAAQ;AAAA,IACV;AACA,eAAW,OAAO,SAAS,IAAI,OAAK,EAAE,UAAU,GAAG;AACjD,UAAI,OAAO,YAAY,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,GAAG,GAAG;AAClF,eAAO,OAAO,SAAS,GAAG;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAzmBA,IAiBa,yBAEA,aACA,aACA,iBACA,qBACA,sBACA,kBACA,kBACA,iBACA,mBAIA,gBAEA,eACA,eACA,mBACA,uBACA,wBACA,oBACA,mBACA,qBAEA,gBAEA,eACA,eACA,mBACA,uBACA,wBACA,oBACA,mBACA,qBACA,gBAEA,eACA,eACA,mBACA,uBACA,wBACA,oBACA,mBACA,qBACA,gBAkBP,mBAoEA,qBA0DA,qBA0DA,qBAyDA,iBAEA,qBAcO,qBAkBP;AAnWN,IAAAM,gBAAA;AAAA;AAAA;AAiBO,IAAM,0BAA0BH,MAAKC,SAAQ,GAAG,yBAAyB;AAEzE,IAAM,cAAcD,MAAKC,SAAQ,GAAG,WAAW,aAAa;AAC5D,IAAM,cAAcD,MAAK,aAAa,mBAAmB;AACzD,IAAM,kBAAkBA,MAAK,aAAa,cAAc;AACxD,IAAM,sBAAsBA,MAAK,aAAa,SAAS;AACvD,IAAM,uBAAuBA,MAAK,qBAAqB,eAAe;AACtE,IAAM,mBAAmBA,MAAK,aAAa,WAAW;AACtD,IAAM,mBAAmBA,MAAKC,SAAQ,GAAG,cAAc;AACvD,IAAM,kBAAkBD,MAAK,aAAa,eAAe;AACzD,IAAM,oBAAoB;AAI1B,IAAM,iBAAiB;AAEvB,IAAM,gBAAgBA,MAAKC,SAAQ,GAAG,WAAW,eAAe;AAChE,IAAM,gBAAgBD,MAAK,eAAe,mBAAmB;AAC7D,IAAM,oBAAoBA,MAAK,eAAe,cAAc;AAC5D,IAAM,wBAAwBA,MAAK,eAAe,SAAS;AAC3D,IAAM,yBAAyBA,MAAK,uBAAuB,eAAe;AAC1E,IAAM,qBAAqBA,MAAK,eAAe,WAAW;AAC1D,IAAM,oBAAoBA,MAAK,eAAe,eAAe;AAC7D,IAAM,sBAAsB;AAE5B,IAAM,iBAAiB;AAEvB,IAAM,gBAAgBA,MAAKC,SAAQ,GAAG,WAAW,eAAe;AAChE,IAAM,gBAAgBD,MAAK,eAAe,mBAAmB;AAC7D,IAAM,oBAAoBA,MAAK,eAAe,cAAc;AAC5D,IAAM,wBAAwBA,MAAK,eAAe,SAAS;AAC3D,IAAM,yBAAyBA,MAAK,uBAAuB,eAAe;AAC1E,IAAM,qBAAqBA,MAAK,eAAe,WAAW;AAC1D,IAAM,oBAAoBA,MAAK,eAAe,eAAe;AAC7D,IAAM,sBAAsB;AAC5B,IAAM,iBAAiB;AAEvB,IAAM,gBAAgBA,MAAKC,SAAQ,GAAG,WAAW,eAAe;AAChE,IAAM,gBAAgBD,MAAK,eAAe,mBAAmB;AAC7D,IAAM,oBAAoBA,MAAK,eAAe,cAAc;AAC5D,IAAM,wBAAwBA,MAAK,eAAe,SAAS;AAC3D,IAAM,yBAAyBA,MAAK,uBAAuB,eAAe;AAC1E,IAAM,qBAAqBA,MAAK,eAAe,WAAW;AAC1D,IAAM,oBAAoBA,MAAK,eAAe,eAAe;AAC7D,IAAM,sBAAsB;AAC5B,IAAM,iBAAiB;AAkB9B,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA,UAIhB,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,yBA+BF,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCvC,IAAM,sBAAsB;AAAA,oEACwC,cAAc;AAAA;AAAA;AAAA,UAGxE,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCAoBY,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAM9B,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAkBQ,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU7D,IAAM,sBAAsB;AAAA,iEACqC,cAAc;AAAA;AAAA;AAAA,UAGrE,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gDAoBmB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAMrC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAkBQ,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU7D,IAAM,sBAAsB;AAAA,6DACiC,cAAc;AAAA;AAAA;AAAA,UAGjE,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4CAoBe,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAMjC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAkBQ,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS7D,IAAM,kBAAkB;AAExB,IAAM,sBAAsB,KAAK;AAAA,MAC/B;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,QACT,MAAM;AAAA,QACN,cAAc;AAAA,UACZ,6BAA6B;AAAA,QAC/B;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAEG,IAAM,sBAAN,cAAkC,MAAM;AAAA,MAC7C,YAAY,SAAiC,OAAiB;AAC5D,cAAM,OAAO;AAD8B;AAE3C,aAAK,OAAO;AAAA,MACd;AAAA,MAH6C;AAAA,IAI/C;AAaA,IAAM,WAA0C;AAAA,MAC9C,EAAE,YAAY,aAAe,YAAY,aAAe,eAAe,iBAAmB,mBAAmB,qBAAuB,oBAAoB,sBAAwB,gBAAgB,kBAAoB,eAAe,iBAAmB,iBAAiB,kBAAoB;AAAA,MAC3R,EAAE,YAAY,eAAe,YAAY,eAAe,eAAe,mBAAmB,mBAAmB,uBAAuB,oBAAoB,wBAAwB,gBAAgB,oBAAoB,eAAe,mBAAmB,iBAAiB,oBAAoB;AAAA,MAC3R,EAAE,YAAY,eAAe,YAAY,eAAe,eAAe,mBAAmB,mBAAmB,uBAAuB,oBAAoB,wBAAwB,gBAAgB,oBAAoB,eAAe,mBAAmB,iBAAiB,oBAAoB;AAAA,MAC3R,EAAE,YAAY,eAAe,YAAY,eAAe,eAAe,mBAAmB,mBAAmB,uBAAuB,oBAAoB,wBAAwB,gBAAgB,oBAAoB,eAAe,mBAAmB,iBAAiB,oBAAoB;AAAA,IAC7R;AAAA;AAAA;;;ACxWA;AAAA;AAAA;AAAA;AAYA,SAAS,cAAAI,cAAY,cAAc;AACnC,SAAS,WAAAC,iBAAe;AACxB,SAAS,QAAAC,cAAY;AACrB,SAAS,aAAAC,kBAAiB;AAW1B,SAAS,gBAAgB,OAAsB;AAC7C,QAAM,SAAS,aAAa;AAC5B,MAAI,OAAO,SAAS;AAClB,eAAW;AACX,YAAQ,IAAI,wCAAmC;AAAA,EACjD,OAAO;AACL,YAAQ,IAAI,yCAAsC;AAAA,EACpD;AAEA,MAAI,OAAO;AACT,QAAI;AACF,YAAM,QAAQ,SAAS;AACvB,MAAAA,WAAU,UAAU,CAAC,OAAO,KAAK,GAAG,EAAE,UAAU,SAAS,SAAS,IAAO,CAAC;AAC1E,cAAQ,IAAI,+BAA0B,KAAK,EAAE;AAAA,IAC/C,QAAQ;AAAA,IAAoC;AAAA,EAC9C;AAEA,MAAI,oBAAoB,GAAG;AACzB,QAAI;AAAE,4BAAsB;AAAG,cAAQ,IAAI,sCAAiC;AAAA,IAAG,QAAQ;AAAA,IAAoB;AAAA,EAC7G;AAEA,mBAAiB;AACjB,UAAQ,IAAI,uCAAkC;AAChD;AAEO,SAAS,kBAAkBC,QAAiB,CAAC,GAAS;AAC3D,QAAM,QAAQA,MAAK,SAAS,SAAS;AAErC,UAAQ,IAAI,iCAAiC;AAG7C,kBAAgB,KAAK;AAErB,QAAM,SAAS,aAAa;AAC5B,MAAI,gBAAgB;AACpB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAe;AAChC,sBAAgB;AAChB,YAAM,UAAU,iBAAiB,MAAM,YAAY;AACnD,cAAQ,IAAI,GAAG,UAAU,WAAM,MAAG,IAAI,MAAM,IAAI,KAAK,UAAU,gCAAgC,uBAAuB,EAAE;AAAA,IAC1H,WAAW,MAAM,SAAS,UAAU;AAClC,YAAM,UAAU,qBAAqB,MAAM,YAAY;AACvD,cAAQ,IAAI,GAAG,UAAU,WAAM,MAAG,IAAI,MAAM,IAAI,KAAK,UAAU,gCAAgC,uBAAuB,EAAE;AAAA,IAC1H;AAAA,EACF;AAKA,MAAI,eAAe;AACjB,UAAM,aAAa,mBAAmB;AACtC,YAAQ,IAAI,GAAG,aAAa,WAAM,MAAG,yBAAyB,aAAa,gCAAgC,gBAAgB,EAAE;AAAA,EAC/H;AACA;AACE,UAAM,mBAAmB,yBAAyB;AAClD,YAAQ,IAAI,GAAG,mBAAmB,WAAM,MAAG,6BAA6B,mBAAmB,oCAAoC,gBAAgB,EAAE;AAAA,EACnJ;AAEA,MAAI,OAAO;AACT,QAAIJ,aAAWK,WAAU,GAAG;AAC1B,aAAOA,aAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,cAAQ,IAAI,kBAAaA,WAAU,EAAE;AAAA,IACvC,OAAO;AACL,cAAQ,IAAI,QAAKA,WAAU,kCAAkC;AAAA,IAC/D;AAAA,EACF,WAAWL,aAAWK,WAAU,GAAG;AACjC,YAAQ,IAAI,uBAAuBA,WAAU,+BAA+B;AAAA,EAC9E;AAEA,UAAQ,IAAI,wBAAwB;AACtC;AAhGA,IAwBMA;AAxBN;AAAA;AAAA;AAgBA;AACA;AACA;AACA;AACA,IAAAC;AACA;AACA;AAEA,IAAMD,cAAaH,OAAKD,UAAQ,GAAG,SAAS;AAAA;AAAA;;;ACd5C,SAAS,gBAAgB,cAAAM,cAAY,aAAAC,aAAW,YAAAC,WAAU,gBAAAC,eAAc,UAAU,aAAAC,YAAW,YAAAC,WAAU,WAAW,mBAAmB;AACrI,SAAS,WAAAC,UAAS,QAAAC,cAAY;AAC9B,SAAS,WAAAC,iBAAe;AA0BxB,SAAS,SAAS,GAAW,MAAM,aAAqB;AACtD,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,SAAO,EAAE,MAAM,GAAG,GAAG,IAAI,eAAU,EAAE,SAAS,OAAO;AACvD;AAGA,SAAS,gBAAgB,QAAoC;AAE3D,QAAM,IAAI,OAAO,MAAM,oEAAoE;AAC3F,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;AAC3B,QAAI,IAAI,SAAU,QAAO,OAAO,IAAI,QAAQ;AAC5C,QAAI,OAAO,IAAI,OAAO,UAAW,QAAO,IAAI,KAAK,OAAO;AACxD,QAAI,IAAI,KAAM,QAAO,OAAO,IAAI,IAAI;AACpC,QAAI,IAAI,QAAS,QAAO,OAAO,IAAI,OAAO;AAAA,EAC5C,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGO,SAAS,WAAWC,OAOlB;AACP,MAAI;AACF,IAAAR,YAAUK,SAAQ,aAAa,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAM,QAAmB;AAAA,MACvB,IAAI,IAAI,KAAKG,MAAK,SAAS,EAAE,YAAY;AAAA,MACzC,MAAMA,MAAK;AAAA,MACX,aAAa,KAAK,IAAI,IAAIA,MAAK;AAAA,MAC/B,QAAQA,MAAK;AAAA,MACb,iBAAiB,SAASA,MAAK,OAAO;AAAA,MACtC,kBAAkBA,MAAK,SAAS,SAASA,MAAK,MAAM,IAAI;AAAA,MACxD,UAAUA,MAAK,SAAS,gBAAgBA,MAAK,MAAM,IAAI;AAAA,MACvD,OAAOA,MAAK;AAAA,IACd;AACA,mBAAe,eAAe,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AACF;AArFA,IAea,eAqBP;AApCN;AAAA;AAAA;AAeO,IAAM,gBAAgBF,OAAKC,UAAQ,GAAG,WAAW,eAAe,WAAW;AAqBlF,IAAM,cAAc;AAAA;AAAA;;;AC5BpB,SAAS,WAAW,mBAAmB;AACvC,SAAS,eAAe;AA4BxB,eAAsB,gBACpB,MACA,SACA,OAAsB,CAAC,GACN;AACjB,QAAM,OAAO,KAAK,UAAU,EAAE,MAAM,SAAS,SAAS,QAAQ,CAAC;AAC/D,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,YAAY,KAAK,IAAI;AAE3B,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,QAAgB,CAACE,UAAS,WAAW;AAC5D,YAAM,MAAM,YAAY;AAAA,QACtB,MAAM;AAAA,QACN;AAAA,QACA,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,kBAAkB,OAAO,WAAW,IAAI;AAAA,QAC1C;AAAA,QACA,SAAS;AAAA,MACX,GAAG,SAAO;AACR,cAAM,SAAmB,CAAC;AAC1B,YAAI,GAAG,QAAQ,OAAK,OAAO,KAAK,CAAC,CAAC;AAClC,YAAI,GAAG,OAAO,MAAM;AAClB,gBAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO;AACnD,cAAI,IAAI,eAAe,KAAK;AAC1B,mBAAO,IAAI,aAAa,oBAAoB,IAAI,UAAU,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AACpF;AAAA,UACF;AACA,cAAI;AACF,kBAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,gBAAI,OAAO,OAAO;AAChB,qBAAO,IAAI,aAAa,OAAO,KAAK,CAAC;AACrC;AAAA,YACF;AACA,YAAAA,SAAQ,OAAO,OAAO,UAAU,EAAE,CAAC;AAAA,UACrC,SAAS,KAAK;AACZ,mBAAO,IAAI,aAAa,+BAA+B,KAAK,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC;AAAA,UACnF;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AACD,UAAI,GAAG,WAAW,MAAM;AACtB,YAAI,QAAQ,IAAI,aAAa,mCAAmC,SAAS,IAAI,CAAC;AAAA,MAChF,CAAC;AACD,UAAI,GAAG,SAAS,SAAO;AACrB,cAAM,MAAO,IAA8B,SAAS,iBAChD,iCAAiC,YAAY,IAAI,YAAY,kCAC7D,2BAA4B,IAAc,OAAO;AACrD,eAAO,IAAI,aAAa,KAAK,GAAG,CAAC;AAAA,MACnC,CAAC;AACD,UAAI,MAAM,IAAI;AACd,UAAI,IAAI;AAAA,IACV,CAAC;AACD,eAAW,EAAE,WAAW,MAAM,SAAS,SAAS,QAAQ,QAAQ,KAAK,CAAC;AACtE,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAW,IAAc,WAAW,OAAO,GAAG;AACpD,UAAM,SAAS,aAAa,KAAK,OAAO,IAAI,YAAY;AACxD,eAAW,EAAE,WAAW,MAAM,SAAS,SAAS,QAAQ,OAAO,QAAQ,CAAC;AACxE,UAAM;AAAA,EACR;AACF;AApGA,IAaa,cACA,cAEP,oBAEO;AAlBb;AAAA;AAAA;AAWA;AAEO,IAAM,eAAe;AACrB,IAAM,eAAe,SAAS,QAAQ,IAAI,uBAAuB,QAAQ,EAAE;AAElF,IAAM,qBAAqB;AAEpB,IAAM,eAAN,cAA2B,MAAM;AAAA,MACtC,YAAY,SAAiC,OAAiB;AAC5D,cAAM,OAAO;AAD8B;AAE3C,aAAK,OAAO;AAAA,MACd;AAAA,MAH6C;AAAA,IAI/C;AAAA;AAAA;;;ACvBA;AAAA;AAAA;AAAA;AAcA,eAAe,YAA6B;AAC1C,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,YAAQ,MAAM,GAAG,QAAQ,OAAK,OAAO,KAAK,CAAC,CAAC;AAC5C,YAAQ,MAAM,GAAG,OAAO,MAAMA,SAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC;AAC9E,YAAQ,MAAM,GAAG,SAAS,MAAM;AAAA,EAClC,CAAC;AACH;AAEA,eAAsB,aAAaC,OAA+B;AAChE,QAAM,OAAOA,MAAK,CAAC,KAAK;AACxB,MAAI;AACJ,MAAI,SAAS,OAAQ,QAAO;AAAA,WACnB,SAAS,OAAQ,QAAO;AAAA,WACxB,SAAS,OAAQ,QAAO;AAAA,WACxB,SAAS,MAAO,QAAO;AAAA,OAC3B;AACH,YAAQ,MAAM,0CAA0C;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,MAAM,UAAU;AAChC,MAAI,CAAC,QAAQ,KAAK,GAAG;AACnB,YAAQ,MAAM,2BAA2B;AACzC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACF,UAAM,SAAS,MAAM,gBAAgB,MAAM,SAAS,EAAE,WAAW,IAAO,CAAC;AACzE,YAAQ,OAAO,MAAM,MAAM;AAC3B,QAAI,CAAC,OAAO,SAAS,IAAI,EAAG,SAAQ,OAAO,MAAM,IAAI;AAAA,EACvD,SAAS,KAAK;AACZ,QAAI,eAAe,cAAc;AAC/B,cAAQ,MAAM,iBAAiB,IAAI,OAAO,EAAE;AAAA,IAC9C,OAAO;AACL,cAAQ,MAAM,iBAAkB,IAAc,OAAO,EAAE;AAAA,IACzD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AArDA;AAAA;AAAA;AAWA;AAAA;AAAA;;;ACLA,SAAS,gBAAAC,gBAAc,cAAAC,oBAAkB;AACzC,SAAS,WAAAC,gBAAe;AAExB,IAAM,gBAAgB;AAAA,EACpBA,SAAQ,QAAQ,IAAI,GAAG,MAAM;AAAA,EAC7BA,SAAQ,QAAQ,IAAI,QAAQ,IAAI,WAAW,YAAY;AACzD;AACA,WAAW,WAAW,eAAe;AACnC,MAAI,CAACD,aAAW,OAAO,EAAG;AAC1B,QAAM,aAAaD,eAAa,SAAS,OAAO;AAChD,aAAW,QAAQ,WAAW,MAAM,IAAI,GAAG;AACzC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,QAAI,WAAW,EAAG;AAClB,UAAM,MAAM,QAAQ,MAAM,GAAG,OAAO,EAAE,KAAK;AAC3C,UAAM,QAAQ,QAAQ,MAAM,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AAC1E,QAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,CAAC,MAAM,WAAW,OAAO,EAAG,SAAQ,IAAI,GAAG,IAAI;AAAA,EAC1E;AACF;AAEA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAM,MAAM,KAAK,CAAC,KAAK;AACvB,IAAM,UAAU,KAAK,MAAM,CAAC;AAE5B,SAAS,eAAe;AACtB,UAAQ,IAAI,SAAsB;AACpC;AAEA,SAAS,YAAY;AACnB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAab;AACD;AAEA,eAAe,OAAO;AACpB,UAAQ,KAAK;AAAA,IACX,KAAK,WAAW;AACd,YAAM,EAAE,gBAAAG,iBAAgB,WAAAC,WAAU,IAAI,MAAM;AAC5C,YAAMD,gBAAeC,WAAU,OAAO,CAAC;AACvC;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK,cAAc;AACjB,YAAM,EAAE,mBAAAC,mBAAkB,IAAI,MAAM;AACpC,MAAAA,mBAAkB,OAAO;AACzB;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,EAAE,cAAAC,cAAa,IAAI,MAAM;AAC/B,YAAMA,cAAa,OAAO;AAC1B;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,MAAM;AACT,mBAAa;AACb;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,IAAI;AACP,gBAAU;AACV;AAAA,IACF;AAAA,IACA,SAAS;AACP,cAAQ,MAAM,oBAAoB,GAAG,EAAE;AACvC,gBAAU;AACV,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,GAAG;AACjB,UAAQ,KAAK,CAAC;AAChB,CAAC;","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","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","createInterface","execSync","existsSync","readFileSync","unlinkSync","homedir","platform","join","execFile","resolve","openBrowser","args","jwt","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","platform","join","SYNKRO_DIR","SYNKRO_DIR","existsSync","mkdirSync","homedir","join","spawnSync","args","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","homedir","join","execSync","createInterface","SYNKRO_DIR","CONFIG_PATH","detectGitRepo","resolve","assertDockerAvailable","setupGithubCommand","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","copyFileSync","renameSync","unlinkSync","join","homedir","spawnSync","init_install","existsSync","homedir","join","spawnSync","args","SYNKRO_DIR","init_install","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","resolve","args","readFileSync","existsSync","resolve","installCommand","parseArgs","disconnectCommand","gradeCommand"]}