@synkro-sh/cli 1.0.12 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../cli/installer/agentDetect.ts","../cli/installer/ccHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/hookScripts.ts","../cli/installer/graderDaemon.ts","../cli/auth/stub.ts","../cli/commands/install.ts","../cli/commands/login.ts","../cli/commands/logout.ts","../cli/commands/status.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/setupGithub.ts","../cli/commands/scanPr.ts","../cli/commands/update.ts","../cli/commands/disconnect.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';\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 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 * 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; // Absolute path to ~/.synkro/hooks/cc-bash-judge.sh\n bashFollowupScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-bash-followup.sh\n editCaptureScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-edit-capture.sh\n stopSummaryScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-stop-summary.sh\n sessionStartScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-session-start.sh\n // Absolute path to ~/.synkro/hooks/cc-edit-precheck.sh — the PreToolUse\n // Edit/Write hook that POSTs proposed content to /api/v1/precheck-edit and\n // echoes the deterministic CC-hook JSON the server returns. No LLM in the\n // hook path; the agent retries with a safer version on its own inference\n // when it reads the deny reason.\n editPrecheckScriptPath: string;\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 [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 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) => !entry?.[SYNKRO_MARKER]);\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 // 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\n // PreToolUse Bash → command hook script (Cerebras-judged dangerous bash)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → command hook that calls /api/v1/precheck-edit.\n // Server cosines the proposed content against the org's Timescale rules,\n // returns the deterministic CC hook JSON shape (deny + retry guidance, or\n // empty allow). Agent reads the deny reason and retries on its own inference.\n // No LLM in the hook path — no Sonnet narration drift, no broken JSON parser.\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PostToolUse Edit/Write → command hook only (announcement + telemetry).\n // No LLM call here — the verdict already came from PreToolUse.\n settings.hooks.PostToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editCaptureScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\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 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'] 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 * 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}\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 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/**\n * Hook scripts that get written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Two scripts:\n * cc-bash-judge.sh — PreToolUse Bash hook. Reads stdin payload, parses\n * transcript context, POSTs to /v1/judge, renders systemMessage JSON.\n *\n * cc-edit-capture.sh — PostToolUse Edit/Write capture hook. Runs in parallel\n * with the prompt hook. Reads transcript for the prompt hook's verdict,\n * POSTs to /v1/events/edit-scan. Async / fire-and-forget.\n *\n * Both scripts are POSIX bash with `jq` and `curl` dependencies (standard on\n * macOS + most Linux). They fail open: if anything goes wrong, exit 0 and\n * let the user's CC session continue.\n */\n\nexport const CC_BASH_JUDGE_SCRIPT = `#!/bin/bash\n# Synkro PreToolUse Bash judge hook\n# Reads CC's hook payload from stdin, judges via Synkro gateway, returns verdict.\n# Auth: reads access_token from ~/.synkro/credentials.json, sends Authorization: Bearer.\nset -e\n\n# Load config\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\n# Fail open if not authed\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Read hook payload from stdin (CC sends a single JSON line)\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Only run on Bash tool calls\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then\n echo '{}'\n exit 0\nfi\n\nCOMMAND=$(echo \"$PAYLOAD\" | jq -r '.tool_input.command // empty' 2>/dev/null)\nif [ -z \"$COMMAND\" ]; then\n echo '{}'\n exit 0\nfi\n\n# NO hard regex gate — server-side bashShapes runs the universal pattern set\n# (including HTTP-payload destructive shapes like graphql_destructive_mutation\n# and http_method_delete) and returns a \"trivial\" fast-path verdict for boring\n# read-only commands (ls, pwd, git status, --version, etc.). Anything ambiguous\n# goes to Cerebras for grading. This closes the gap that verb-only filters miss\n# (e.g. a curl POST whose JSON body contains a destructive mutation).\n\n# Extract context from the transcript file\nTRANSCRIPT_PATH=$(echo \"$PAYLOAD\" | jq -r '.transcript_path // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\n# Headless detection — when no human is in the loop, ASK is a no-op so we\n# fail-closed by upgrading high-tier findings to deny.\nPERMISSION_MODE=$(echo \"$PAYLOAD\" | jq -r '.permission_mode // empty' 2>/dev/null)\nIS_HEADLESS=0\ncase \"$PERMISSION_MODE\" in\n acceptEdits|bypassPermissions|plan|auto) IS_HEADLESS=1 ;;\nesac\nif [ \"\\${SYNKRO_HEADLESS:-0}\" = \"1\" ]; then IS_HEADLESS=1; fi\n\nUSER_INTENT=\"\"\nRECENT_USER_MESSAGES=\"[]\"\nRECENT_ACTIONS=\"[]\"\nif [ -n \"$TRANSCRIPT_PATH\" ] && [ -f \"$TRANSCRIPT_PATH\" ]; then\n # Last 5 user-role messages, oldest first. Lets the grader see consent\n # that carried over from a recent prior turn — saying \"i consent\" two\n # turns ago should not require re-prompting on this turn's command.\n RECENT_USER_MESSAGES=$(tail -400 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"user\")\n | (.message.content\n | if type == \"string\" then .\n else (map(.text? // \"\") | join(\" \"))\n end)\n | select(. != null and . != \"\")\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\n USER_INTENT=$(echo \"$RECENT_USER_MESSAGES\" | jq -r '.[-1] // \"\"' 2>/dev/null || echo \"\")\n # Recent agent actions (last 5 tool_use blocks)\n RECENT_ACTIONS=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"assistant\")\n | .message.content[]?\n | select(.type == \"tool_use\")\n | { tool: .name, input: (.input // {} | tostring | .[0:200]) }\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\nfi\n\n# Build POST body — always emit all fields (use null for empty optionals)\n# Earlier version used \\`select(length > 0)\\` which made the entire object\n# evaluate to nothing when any optional was empty. Don't do that.\nBODY=$(jq -n \\\\\n --argjson tool_input \"$TOOL_INPUT\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_user_messages \"$RECENT_USER_MESSAGES\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n kind: \"bash_judge\",\n tool_input: $tool_input,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_user_messages: $recent_user_messages,\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\n# Helper: refresh JWT via /api/auth/refresh and rewrite credentials.json.\n# Called when the gateway returns 401 (token expired).\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then\n return 1\n fi\n local refresh_resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n refresh_resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 4 2>/dev/null)\n local new_access\n new_access=$(echo \"$refresh_resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then\n return 1\n fi\n # Atomically rewrite credentials.json with the new tokens\n local new_refresh\n new_refresh=$(echo \"$refresh_resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then\n new_refresh=\"$refresh_token\"\n fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\n# Resolve tier (cached 60 min) — server is canonical via /cli/me; fallback free.\nTIER_CACHE_FILE=\"$HOME/.synkro/.tier-cache-\\${SYNKRO_USER_ID:-default}\"\nSYNKRO_INFERENCE_TIER=\"\"\nif find \"$TIER_CACHE_FILE\" -mmin -60 2>/dev/null | grep -q .; then\n SYNKRO_INFERENCE_TIER=$(cat \"$TIER_CACHE_FILE\" 2>/dev/null)\nfi\nif [ -z \"$SYNKRO_INFERENCE_TIER\" ]; then\n ME_RESP=$(curl -sS \"\\${GATEWAY_URL}/api/v1/cli/me\" -H \"Authorization: Bearer $JWT\" --max-time 2 2>/dev/null || echo \"\")\n if [ -n \"$ME_RESP\" ]; then\n SYNKRO_INFERENCE_TIER=$(echo \"$ME_RESP\" | jq -r '.tier // empty' 2>/dev/null)\n [ -n \"$SYNKRO_INFERENCE_TIER\" ] && printf '%s' \"$SYNKRO_INFERENCE_TIER\" > \"$TIER_CACHE_FILE\" 2>/dev/null || true\n fi\nfi\nSYNKRO_INFERENCE_TIER=\"\\${SYNKRO_INFERENCE_TIER:-free}\"\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (mode=bash). ───\n GRADER_PROMPT_FILE=$(mktemp -t synkro-bash-prompt.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'Proposed command: %s\\\\n' \"$COMMAND\" > \"$GRADER_PROMPT_FILE\"\n printf 'User intent: %s\\\\n' \"\\${USER_INTENT:-none stated}\" >> \"$GRADER_PROMPT_FILE\"\n printf 'Recent actions: %s\\\\n' \"$RECENT_ACTIONS\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-bash.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode bash grade \"$HOME/.synkro/grader-primer-bash.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n V_INNER=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n if [ -z \"$V_INNER\" ] || ! echo \"$V_INNER\" | jq -e '.severity' >/dev/null 2>&1; then\n echo '{}'\n exit 0\n fi\n VERDICT=\"$V_INNER\"\nelse\n # ─── FAST TIER: server-side Cerebras grading. ───\n VERDICT=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 6 2>/dev/null || echo \"\")\n\n if echo \"$VERDICT\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n VERDICT=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 6 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\nif [ -z \"$VERDICT\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Parse verdict — fail open on any parse error\nSEVERITY=$(echo \"$VERDICT\" | jq -r '.severity // \"medium\"' 2>/dev/null)\nVERDICT_KIND=$(echo \"$VERDICT\" | jq -r '.verdict // \"warn\"' 2>/dev/null)\nREASONING=$(echo \"$VERDICT\" | jq -r '.reasoning // \"matched dangerous-verb regex\"' 2>/dev/null)\nALTERNATIVE=$(echo \"$VERDICT\" | jq -r '.alternative // \"\"' 2>/dev/null)\nCATEGORY=$(echo \"$VERDICT\" | jq -r '.category // \"destructive_command\"' 2>/dev/null)\n\n# Severity-driven surfacing:\n# critical → permissionDecision: \"deny\" — block outright\n# high → permissionDecision: \"ask\" — force user confirmation\n# medium → silent allow (echo {}) — Cerebras saw it, judged the\n# intent fits, no surface\n# low → silent allow (echo {}) — same\n#\n# The grader is fully context-aware now (intent + recent_actions + shape\n# labels), so its severity grade is trustworthy. Low/medium decisions don't\n# need to interrupt the user — surfacing them creates alert fatigue and\n# trains the user to click-through on warnings that turn out to be benign.\n\ncase \"$SEVERITY\" in\n critical)\n SYS_MSG=\"[synkro] bashGuard → critical: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n SYS_MSG=\"\\${SYS_MSG}\\\\n[synkro] suggested → \\${ALTERNATIVE}\"\n fi\n ADDITIONAL_CTX=\"Synkro safety judge flagged this command (severity: critical, category: \\${CATEGORY}). Reasoning: \\${REASONING}.\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n ADDITIONAL_CTX=\"\\${ADDITIONAL_CTX} Suggested alternative: \\${ALTERNATIVE}.\"\n fi\n PERMISSION_REASON=\"[synkro] BLOCKED — \\${REASONING}. Severity: critical. Override only if you are certain.\"\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n --arg reason \"$PERMISSION_REASON\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: $reason,\n additionalContext: $ctx\n }\n }'\n ;;\n high)\n SYS_MSG=\"[synkro] bashGuard → high: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n SYS_MSG=\"\\${SYS_MSG}\\\\n[synkro] suggested → \\${ALTERNATIVE}\"\n fi\n ADDITIONAL_CTX=\"Synkro safety judge flagged this command (severity: high, category: \\${CATEGORY}). Reasoning: \\${REASONING}.\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n ADDITIONAL_CTX=\"\\${ADDITIONAL_CTX} Suggested alternative: \\${ALTERNATIVE}.\"\n fi\n PERMISSION_REASON=\"[synkro] high: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n PERMISSION_REASON=\"\\${PERMISSION_REASON} Alternative: \\${ALTERNATIVE}\"\n fi\n # Headless? Upgrade ask → deny so we fail-closed.\n if [ \"$IS_HEADLESS\" = \"1\" ]; then DECISION=\"deny\"; else DECISION=\"ask\"; fi\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n --arg reason \"$PERMISSION_REASON\" \\\\\n --arg decision \"$DECISION\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: $decision,\n permissionDecisionReason: $reason,\n additionalContext: $ctx\n }\n }'\n ;;\n *)\n # low / medium / anything else → silent allow\n echo '{}'\n ;;\nesac\n\nexit 0\n`;\n\nexport const CC_EDIT_PRECHECK_SCRIPT = `#!/bin/bash\n# Synkro PreToolUse Edit/Write/MultiEdit/NotebookEdit pre-check hook.\n#\n# Steering-at-violation: thin shim that POSTs the proposed file content to\n# /api/v1/precheck-edit. The server cosines it against the org's active\n# agent_runtime rules in Timescale and returns the CC hook JSON shape\n# directly (permissionDecision: \"deny\" + retry guidance, or empty {} to allow).\n#\n# No LLM in this path — just embedding + cosine. Customer's CC pays for the\n# retry generation when the agent reads the denial reason and rewrites.\n# Synkro's COGS = ~$0.00001 per edit (one Gemini embedding call).\n#\n# Always exits 0 with valid JSON. Fails open on any error.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nTRANSCRIPT_PATH=$(echo \"$PAYLOAD\" | jq -r '.transcript_path // empty' 2>/dev/null)\n# Headless / non-interactive detection — when CC won't actually prompt the\n# human, our \"ask\" verdict is a no-op. Server uses these to fall back to\n# \"deny\" so we fail-closed instead of silently letting findings through.\nPERMISSION_MODE=$(echo \"$PAYLOAD\" | jq -r '.permission_mode // empty' 2>/dev/null)\nHEADLESS_FLAG=\"\\${SYNKRO_HEADLESS:-0}\"\n\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Pull conversation context from the transcript file. CC writes one JSON line\n# per message; we read the tail and extract the most recent user message + the\n# last 5 tool_use blocks. The server uses these as anchors for cosine ranking\n# AND as a suppression signal when the user explicitly asked for the unsafe\n# pattern. Same trick the bash judge uses.\nUSER_INTENT=\"\"\nRECENT_ACTIONS=\"[]\"\nif [ -n \"$TRANSCRIPT_PATH\" ] && [ -f \"$TRANSCRIPT_PATH\" ]; then\n USER_INTENT=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -r 'select(.type == \"user\") | .message.content | if type == \"string\" then . else (map(.text? // \"\") | join(\" \")) end' 2>/dev/null | tail -1 || echo \"\")\n RECENT_ACTIONS=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"assistant\")\n | .message.content[]?\n | select(.type == \"tool_use\")\n | { tool: .name, input: (.input // {} | tostring | .[0:200]) }\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\nfi\n\n# Read the on-disk file FIRST so the Edit/MultiEdit branches below can\n# reconstruct the full post-edit file (file_before with the diff applied).\n# Cap at 64KB. Must run before the case statement so PROPOSED can include\n# whole-file context, not just the diff hunk.\nFILE_BEFORE=\"\"\nif [ \"$TOOL_NAME\" != \"Write\" ] && [ -n \"$FILE_PATH\" ] && [ -f \"$FILE_PATH\" ]; then\n FILE_BEFORE=$(head -c 65536 \"$FILE_PATH\" 2>/dev/null || echo \"\")\nfi\n\n# Pull proposed content. Edit/MultiEdit reconstruct the full file via\n# bash parameter expansion against FILE_BEFORE; Write/NotebookEdit pass\n# the new content directly.\ncase \"$TOOL_NAME\" in\n Write)\n # Write replaces the entire file — content IS the full post-edit file.\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.content // \"\"' 2>/dev/null) ;;\n Edit|MultiEdit)\n # Reconstruct the full post-edit file by applying the diff to file_before.\n # Sending only new_string (the diff hunk) blinds the local grader to\n # violations elsewhere in the file — the grader needs whole-file context\n # to identify multi-violation edits and cross-line patterns.\n #\n # We use python (already a daemon dependency) instead of bash parameter\n # expansion because macOS ships bash 3.2, where the quoted-pattern form\n # of \\${var//PAT/REPL} leaks the quote characters into the result.\n # Python's str.replace() handles arbitrary strings cleanly. Args go via\n # env vars (not argv) so 64 KB file content doesn't trip ARG_MAX limits.\n if [ -n \"$FILE_BEFORE\" ] && command -v python3 >/dev/null 2>&1; then\n PROPOSED=$(FILE_BEFORE_LITERAL=\"$FILE_BEFORE\" TOOL_INPUT_LITERAL=\"$TOOL_INPUT\" python3 -c '\nimport os, json, sys\nfb = os.environ.get(\"FILE_BEFORE_LITERAL\", \"\")\nti = json.loads(os.environ.get(\"TOOL_INPUT_LITERAL\", \"{}\"))\nresult = fb\nif \"old_string\" in ti and \"new_string\" in ti:\n if ti[\"old_string\"]:\n result = result.replace(ti[\"old_string\"], ti[\"new_string\"], 1)\nelif \"edits\" in ti and isinstance(ti[\"edits\"], list):\n for e in ti[\"edits\"]:\n old = e.get(\"old_string\", \"\") if isinstance(e, dict) else \"\"\n new = e.get(\"new_string\", \"\") if isinstance(e, dict) else \"\"\n if old:\n result = result.replace(old, new, 1)\nsys.stdout.write(result)\n' 2>/dev/null)\n fi\n # Fall back to the diff-hunk-only shape if reconstruction failed or\n # file_before was empty (new file via Edit, etc.).\n if [ -z \"$PROPOSED\" ]; then\n if [ \"$TOOL_NAME\" = \"MultiEdit\" ]; then\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '[.edits[]?.new_string // \"\"] | join(\"\\\\n\\\\n--- chunk ---\\\\n\\\\n\")' 2>/dev/null)\n else\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.new_string // \"\"' 2>/dev/null)\n fi\n fi\n ;;\n NotebookEdit)\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.new_source // \"\"' 2>/dev/null) ;;\nesac\n\nif [ -z \"$PROPOSED\" ]; then\n echo '{}'\n exit 0\nfi\n\nDIFF_FIELD=$(echo \"$TOOL_INPUT\" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null)\nif [ -z \"$DIFF_FIELD\" ] || [ \"$DIFF_FIELD\" = \"null\" ] || [ \"$DIFF_FIELD\" = \"{}\" ]; then\n DIFF_FIELD=\"null\"\nfi\n\n# FILE_BEFORE was read above (before the case statement) so this body\n# construction just references it; the duplicate read used to live here.\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg content \"$PROPOSED\" \\\\\n --arg file_before \"$FILE_BEFORE\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg permission_mode \"$PERMISSION_MODE\" \\\\\n --arg headless_flag \"$HEADLESS_FLAG\" \\\\\n '{\n file_path: $file_path,\n tool_name: $tool_name,\n content: $content,\n file_before: (if ($file_before | length) > 0 then $file_before else null end),\n diff: $diff,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),\n headless: ($headless_flag == \"1\")\n }')\n\n# Refresh JWT on 401 (mirrors the bash judge pattern).\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then return 1; fi\n local resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 3 2>/dev/null)\n local new_access\n new_access=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then return 1; fi\n local new_refresh\n new_refresh=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then new_refresh=\"$refresh_token\"; fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (Python helper).\n # The daemon at \\$HOME/.synkro/bin/grader_daemon.py keeps one\n # \\`claude --print --input-format=stream-json\\` process alive, primed once\n # with the role/format. Hook ships a thin grading prompt over Unix socket\n # and gets the verdict text back. Steady-state ~1.5–3s per grading vs\n # ~14s for cold \\`claude --print\\`. Falls back to direct \\`claude --print\\`\n # if the daemon binary or primer is missing.\n GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'File: %s\\\\n' \"$FILE_PATH\" > \"$GRADER_PROMPT_FILE\"\n printf 'User intent: %s\\\\n\\\\n' \"\\${USER_INTENT:-none stated}\" >> \"$GRADER_PROMPT_FILE\"\n printf 'Diff:\\\\n' >> \"$GRADER_PROMPT_FILE\"\n printf '%s\\\\n' \"$PROPOSED\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-edit.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode edit grade \"$HOME/.synkro/grader-primer-edit.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n\n VERDICT_JSON=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n\n if [ -z \"$VERDICT_JSON\" ]; then\n echo '{}'\n exit 0\n fi\n\n LOCAL_BODY=$(jq -n \\\\\n --argjson verdict \"$VERDICT_JSON\" \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg content \"$PROPOSED\" \\\\\n --arg file_before \"$FILE_BEFORE\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg permission_mode \"$PERMISSION_MODE\" \\\\\n --arg headless_flag \"$HEADLESS_FLAG\" \\\\\n '{\n verdict: $verdict,\n file_path: $file_path,\n tool_name: $tool_name,\n content: $content,\n file_before: (if ($file_before | length) > 0 then $file_before else null end),\n diff: $diff,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),\n headless: ($headless_flag == \"1\")\n }')\n\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$LOCAL_BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$LOCAL_BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n fi\n fi\nelse\n # ─── FAST TIER: server-side Cerebras grading (~500ms) ───\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 3 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 3 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\n# Server returns the literal CC hook JSON shape ({} for allow, or\n# hookSpecificOutput.permissionDecision: \"deny\" + reason). If the call failed\n# entirely or returned non-JSON, fail open with empty {}.\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Cheap validation — only forward if it parses as a JSON object.\nif ! echo \"$RESP\" | jq -e 'type == \"object\"' >/dev/null 2>&1; then\n echo '{}'\n exit 0\nfi\n\necho \"$RESP\"\nexit 0\n`;\n\nexport const CC_EDIT_CAPTURE_SCRIPT = `#!/bin/bash\n# Synkro PostToolUse Edit/Write afterFileEdit hook.\n#\n# Reads the file from disk after the edit lands, sends to /api/v1/judge-edit\n# for Cerebras grading against the org's rules. On ok=false, emits a system\n# message that surfaces inline in CC (\"[synkro] afterFileEdit → Finding: …\").\n# The agent reads the finding via transcript and re-edits on its own\n# inference — same retry pattern as PreToolUse precheck, looser/asynchronous.\n#\n# Complementary to the PreToolUse precheck: precheck blocks unsafe writes\n# before disk; this hook catches issues that only become visible AFTER the\n# edit lands (cross-file shapes, real disk state, post-edit semantic effects).\n#\n# Always exits 0 with valid JSON — never breaks CC's flow.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\n\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ] || [ ! -f \"$FILE_PATH\" ]; then\n echo '{}'\n exit 0\nfi\n\nBASENAME=$(basename \"$FILE_PATH\")\n\n# Read post-edit file content (cap 64KB).\nFILE_CONTENT=$(head -c 65536 \"$FILE_PATH\" 2>/dev/null || echo \"\")\nif [ -z \"$FILE_CONTENT\" ]; then\n echo '{}'\n exit 0\nfi\n\nDIFF_FIELD=$(echo \"$TOOL_INPUT\" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null)\nif [ -z \"$DIFF_FIELD\" ] || [ \"$DIFF_FIELD\" = \"null\" ] || [ \"$DIFF_FIELD\" = \"{}\" ]; then\n DIFF_FIELD=\"null\"\nfi\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg content \"$FILE_CONTENT\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n file_path: $file_path,\n content: $content,\n diff: $diff,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then return 1; fi\n local resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 3 2>/dev/null)\n local new_access\n new_access=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then return 1; fi\n local new_refresh\n new_refresh=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then new_refresh=\"$refresh_token\"; fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\n# Fire-and-forget correction-followup. PostToolUse means the edit landed →\n# flip any pending precheck-correction for this tool_use_id to 'allow'.\nif [ -n \"$SESSION_ID\" ] && [ -n \"$TOOL_USE_ID\" ]; then\n FOLLOWUP_BODY=$(jq -n \\\\\n --arg sid \"$SESSION_ID\" \\\\\n --arg tid \"$TOOL_USE_ID\" \\\\\n '{session_id: $sid, tool_use_id: $tid, decision: \"allow\"}')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$FOLLOWUP_BODY\" \\\\\n --max-time 2 \\\\\n >/dev/null 2>&1 || true\nfi\n\n# Resolve tier (cached 60 min) — same logic as the other hooks.\nTIER_CACHE_FILE=\"$HOME/.synkro/.tier-cache-\\${SYNKRO_USER_ID:-default}\"\nSYNKRO_INFERENCE_TIER=\"\"\nif find \"$TIER_CACHE_FILE\" -mmin -60 2>/dev/null | grep -q .; then\n SYNKRO_INFERENCE_TIER=$(cat \"$TIER_CACHE_FILE\" 2>/dev/null)\nfi\nif [ -z \"$SYNKRO_INFERENCE_TIER\" ]; then\n ME_RESP=$(curl -sS \"\\${GATEWAY_URL}/api/v1/cli/me\" -H \"Authorization: Bearer $JWT\" --max-time 2 2>/dev/null || echo \"\")\n if [ -n \"$ME_RESP\" ]; then\n SYNKRO_INFERENCE_TIER=$(echo \"$ME_RESP\" | jq -r '.tier // empty' 2>/dev/null)\n [ -n \"$SYNKRO_INFERENCE_TIER\" ] && printf '%s' \"$SYNKRO_INFERENCE_TIER\" > \"$TIER_CACHE_FILE\" 2>/dev/null || true\n fi\nfi\nSYNKRO_INFERENCE_TIER=\"\\${SYNKRO_INFERENCE_TIER:-free}\"\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (mode=edit). ───\n GRADER_PROMPT_FILE=$(mktemp -t synkro-edit-capture.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'File: %s\\\\n\\\\nContent:\\\\n' \"$FILE_PATH\" > \"$GRADER_PROMPT_FILE\"\n printf '%s\\\\n' \"$FILE_CONTENT\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-edit.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode edit grade \"$HOME/.synkro/grader-primer-edit.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n V_INNER=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n if [ -n \"$V_INNER\" ] && echo \"$V_INNER\" | jq -e 'has(\"ok\")' >/dev/null 2>&1; then\n RESP=\"$V_INNER\"\n else\n RESP=\"\"\n fi\nelse\n # ─── FAST TIER: server-side Cerebras (~500ms). ───\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\nif [ -z \"$RESP\" ] || ! echo \"$RESP\" | jq -e 'type == \"object\"' >/dev/null 2>&1; then\n if [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → scanned \\${BASENAME} (grader unavailable)\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\n fi\n echo '{}'\n exit 0\nfi\n\nOK=$(echo \"$RESP\" | jq -r 'if .ok == false then \"false\" else \"true\" end' 2>/dev/null)\nSEVERITY=$(echo \"$RESP\" | jq -r '.severity // \"low\"' 2>/dev/null)\nCATEGORY=$(echo \"$RESP\" | jq -r '.category // \"unspecified\"' 2>/dev/null)\nREASON=$(echo \"$RESP\" | jq -r '.reason // \"\"' 2>/dev/null)\n\nif [ \"$OK\" = \"false\" ] && [ -n \"$REASON\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → Finding in \\${BASENAME}: \\${REASON}\"\n ADDITIONAL_CTX=\"Synkro post-edit grader flagged \\${BASENAME} (severity: \\${SEVERITY}, category: \\${CATEGORY}). Re-edit the file applying the retry guidance: \\${REASON}\"\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PostToolUse\",\n additionalContext: $ctx\n }\n }'\n exit 0\nfi\n\nif [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → no issues in \\${BASENAME}\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\nfi\n\necho '{}'\nexit 0\n`;\n\n// ────────────────────────────────────────────────────────────────────────\n// Legacy CC_EDIT_CAPTURE_SCRIPT (telemetry-only) — kept here briefly for\n// reference. The new version above replaces the dedicated /v1/events/edit-scan\n// telemetry call with a real grading call against /api/v1/judge-edit. The\n// judge-edit route fires the same inngest events for dashboard rollups, so\n// no telemetry is lost.\n// ────────────────────────────────────────────────────────────────────────\n\nconst _CC_EDIT_CAPTURE_SCRIPT_LEGACY = `#!/bin/bash\n# Synkro PostToolUse Edit/Write capture hook — legacy telemetry-only version.\n#\n# Architecture (as designed):\n# - The actual security verdict comes from a SIBLING type:'prompt' hook\n# in ccHookConfig.ts. That hook runs CC's own Sonnet inference on the\n# customer's CC subscription (zero cost to Synkro) using the editScanPrompt\n# fetched at install time. It returns {ok, reason} and CC feeds the reason\n# back to the agent on ok:false so the agent re-edits.\n#\n# - This command hook is a thin parallel: it (a) fires-and-forgets a POST\n# to /v1/events/edit-scan so we capture the edit for backend logs/dashboard\n# and (b) optionally emits a \"[synkro] afterFileEdit → scanned <file>\"\n# systemMessage when SYNKRO_VERBOSE=1. NO LLM CALL FROM US.\n#\n# Always exits 0 with valid JSON — never affects CC's flow.\nset -e\n\n# Load config\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\n\n# file_path is on Edit/Write/MultiEdit; .notebook_path on NotebookEdit.\n# We never read this from disk — the verdict comes from the sibling type:'prompt'\n# hook which runs CC inference internally. We only use the path for the\n# user-visible \"scanned <basename>\" message.\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nBASENAME=\"\"\nif [ -n \"$FILE_PATH\" ]; then\n BASENAME=$(basename \"$FILE_PATH\")\nfi\n\n# Build telemetry body — sent fire-and-forget to /v1/events/edit-scan so the\n# dashboard sees every edit. We do NOT include file content here; the type:'prompt'\n# sibling hook is the one running CC inference and producing the verdict, and\n# its result is fed to the agent by CC directly.\nTELEMETRY_BODY=$(jq -n \\\\\n --argjson tool_input \"$TOOL_INPUT\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n tool_name: $tool_name,\n tool_input: $tool_input,\n verdict: { ok: true, reason: \"\" },\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\n# Fire-and-forget telemetry POST. Capped tight so a slow API can't stall the\n# agent. We do NOT call any LLM or judge endpoint from here — that's the\n# type:'prompt' sibling hook's job.\ncurl -sS -X POST \"\\${GATEWAY_URL}/api/v1/events/edit-scan\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$TELEMETRY_BODY\" \\\\\n --max-time 1 \\\\\n >/dev/null 2>&1 || true\n\n# Inline announcement, opt-in via SYNKRO_VERBOSE=1 in ~/.synkro/config.env.\n# The user sees: \"[synkro] afterFileEdit → scanned <file>\"\n# The actual verdict ([synkro] Finding: …) is rendered by CC itself when the\n# sibling type:'prompt' hook returns ok:false — its reason text is fed to\n# the agent and visible in the conversation.\nif [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ] && [ -n \"$BASENAME\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → scanned \\${BASENAME} (CC inference verdict pending)\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\nfi\n\necho '{}'\nexit 0\n`;\n\nexport const CC_STOP_SUMMARY_SCRIPT = `#!/bin/bash\n# Synkro Stop hook — emits \"[synkro] stop → N findings: X auto-fixed, Y open\"\n# as a final summary line when the CC session ends. Reads guard_checks rows\n# for the session via /api/v1/cli/session-summary.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nif [ -z \"$SESSION_ID\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Tight timeout — the user already finished their session, don't make them wait.\nRESP=$(curl -sS -G \"\\${GATEWAY_URL}/api/v1/cli/session-summary\" \\\\\n --data-urlencode \"session_id=$SESSION_ID\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n --max-time 2 2>/dev/null || echo \"\")\n\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\nEDITS=$(echo \"$RESP\" | jq -r '.edits_scanned // 0' 2>/dev/null)\nFINDINGS=$(echo \"$RESP\" | jq -r '.findings // 0' 2>/dev/null)\nAUTO_FIXED=$(echo \"$RESP\" | jq -r '.auto_fixed // 0' 2>/dev/null)\nOPEN=$(echo \"$RESP\" | jq -r '.open // 0' 2>/dev/null)\n\n# Stay silent if the session never touched files we scanned.\nif [ \"$EDITS\" = \"0\" ] || [ -z \"$EDITS\" ]; then\n echo '{}'\n exit 0\nfi\n\nif [ \"$FINDINGS\" = \"0\" ] || [ -z \"$FINDINGS\" ]; then\n SYS_MSG=\"[synkro] stop → 0 issues across \\${EDITS} edit(s), session complete\"\nelse\n SYS_MSG=\"[synkro] stop → \\${FINDINGS} finding(s): \\${AUTO_FIXED} auto-fixed, \\${OPEN} open\"\nfi\n\njq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\nexit 0\n`;\n\nexport const CC_SESSION_START_SCRIPT = `#!/bin/bash\n# Synkro SessionStart hook — when the user opens a fresh CC session in a repo\n# we have findings on, surface \"[synkro] session start → N open finding(s) in\n# this repo\" so they have context. Silent when there's nothing to report.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nif [ -z \"$CWD\" ]; then\n echo '{}'\n exit 0\nfi\n\nRESP=$(curl -sS -G \"\\${GATEWAY_URL}/api/v1/cli/session-context\" \\\\\n --data-urlencode \"cwd=$CWD\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n --max-time 2 2>/dev/null || echo \"\")\n\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\nOPEN=$(echo \"$RESP\" | jq -r '.open_count // 0' 2>/dev/null)\nif [ \"$OPEN\" = \"0\" ] || [ -z \"$OPEN\" ]; then\n echo '{}'\n exit 0\nfi\n\nif [ \"$OPEN\" = \"1\" ]; then\n SYS_MSG=\"[synkro] session start → 1 open finding in this repo from a prior session\"\nelse\n SYS_MSG=\"[synkro] session start → \\${OPEN} open findings in this repo from prior sessions\"\nfi\n\njq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\nexit 0\n`;\n\n// PostToolUse Bash followup — fires after the agent actually executed a Bash\n// command. The PreToolUse bash judge persists a 'pending' precheck_corrections\n// row when the verdict was high/critical (severity gate); this hook flips that\n// row to 'allow' once the user clicked through and the command ran. Without\n// this, all bash precedents stay 'pending' forever and the dashboard's\n// \"approved vs rejected\" trendline is blank.\nexport const CC_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash\n# Synkro PostToolUse Bash hook — minimal correction-followup fire.\n# No grading happens here; verdict already came from PreToolUse. This is just\n# the \"user approved + agent ran it\" capture.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then echo '{}'; exit 0; fi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then echo '{}'; exit 0; fi\n\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nif [ -z \"$SESSION_ID\" ] || [ -z \"$TOOL_USE_ID\" ]; then echo '{}'; exit 0; fi\n\nBODY=$(jq -n --arg sid \"$SESSION_ID\" --arg tid \"$TOOL_USE_ID\" \\\\\n '{session_id: $sid, tool_use_id: $tid, decision: \"allow\"}')\n\ncurl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 2 \\\\\n >/dev/null 2>&1 || true\n\necho '{}'\nexit 0\n`;\n","// :)\n/**\n * Synkro grader daemon — embedded as string constants so `synkro install`\n * can write them to `~/.synkro/bin/grader_daemon.py` and\n * `~/.synkro/grader-primer-edit.txt` without bundling extra files.\n *\n * The daemon keeps one `claude --print --input-format=stream-json` process\n * alive, primed once with the role + format. Hook scripts ship thin grading\n * prompts to it via Unix socket and get back the assistant's text. After\n * one ~3.5s boot tax, gradings run at 1.5–3s steady-state vs ~14s for cold\n * `claude --print`.\n *\n * Session bloat is bounded — daemon rotates the underlying claude\n * subprocess every 30 calls or 1 hour. Rotation eats a one-time ~5s primer\n * cost; calls in between are fast.\n */\n\nexport const GRADER_DAEMON_PY = `#!/usr/bin/env python3\n\"\"\"\nSynkro grader daemon — long-lived \\`claude --print\\` process with stream-json\nIPC, fronted by a Unix socket. Hook scripts ship grading prompts to it; it\nreturns the assistant's response text. ONE CC startup (~3.5s) amortizes\nacross N gradings.\n\nSession bloat is bounded: the daemon rotates its claude subprocess every\nROTATION_CALLS (default 30) gradings or ROTATION_AGE_SEC (default 1h),\nwhichever comes first. Each rotation eats a one-time ~5s primer cost; calls\nin between target ~2-3s steady-state.\n\nCommands:\n start [primer-path] - bring up daemon if not running\n grade [primer-path] - read prompt from stdin, write verdict text to stdout\n stop - terminate daemon\n status - print \"running\"/\"stopped\"\n\"\"\"\n\nimport os, sys, json, socket, time, atexit, signal, fcntl, re\nimport subprocess, threading\nfrom pathlib import Path\n\n# Each \"mode\" gets its own daemon process: separate socket, pid, log.\n# Modes: \"edit\" (precheck + post-edit, schema {ok, severity, ...}) and \"bash\"\n# (schema {verdict, severity, ...}). Selected via --mode <name>; default \"edit\".\nALLOWED_MODE_RE = re.compile(r\"^[a-z][a-z0-9_-]{0,30}$\")\nDAEMON_BASE = Path.home() / \".synkro\" / \"daemon\"\nDAEMON_BASE.mkdir(parents=True, exist_ok=True, mode=0o700)\n\ndef mode_paths(mode):\n d = DAEMON_BASE / mode\n d.mkdir(parents=True, exist_ok=True, mode=0o700)\n return d / \"daemon.pid\", d / \"daemon.sock\", d / \"daemon.log\"\n\nMODE = \"edit\"\nPID_FILE, SOCK_PATH, LOG_FILE = mode_paths(MODE)\n\nROTATION_CALLS = int(os.environ.get(\"SYNKRO_DAEMON_ROTATE_CALLS\", \"30\"))\nROTATION_AGE_SEC = int(os.environ.get(\"SYNKRO_DAEMON_ROTATE_AGE\", \"3600\"))\nGRADE_TIMEOUT_SEC = 60\nDEFAULT_MODEL = os.environ.get(\"SYNKRO_DAEMON_MODEL\", \"claude-sonnet-4-6\")\nMAX_PROMPT_BYTES = 4 * 1024 * 1024\n\n\ndef log(msg):\n try:\n with open(LOG_FILE, \"a\") as f:\n f.write(f\"[{time.strftime('%H:%M:%S')} pid={os.getpid()}] {msg}\\\\n\")\n except Exception:\n pass\n\n\nclass GraderDaemon:\n def __init__(self, primer):\n self.primer = primer or \"\"\n self.proc = None\n self.calls = 0\n self.start_time = 0.0\n self.lock = threading.Lock()\n self._spawn()\n\n def _spawn(self):\n if self.proc and self.proc.poll() is None:\n try: self.proc.terminate(); self.proc.wait(timeout=3)\n except Exception: self.proc.kill()\n log(\"spawning claude subprocess\")\n self.proc = subprocess.Popen(\n [\n \"claude\", \"--print\", \"--model\", DEFAULT_MODEL,\n \"--input-format=stream-json\",\n \"--output-format=stream-json\",\n \"--verbose\",\n \"--no-session-persistence\",\n ],\n stdin=subprocess.PIPE, stdout=subprocess.PIPE,\n stderr=subprocess.DEVNULL, text=True, bufsize=1,\n )\n if self.primer:\n self._send(self.primer)\n primer_resp = self._recv()\n log(f\"primer ack: {primer_resp[:80]!r}\")\n self.calls = 0\n self.start_time = time.time()\n\n def _send(self, text):\n msg = json.dumps({\n \"type\": \"user\",\n \"message\": {\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": text}]},\n \"parent_tool_use_id\": None,\n \"session_id\": \"\",\n })\n try:\n self.proc.stdin.write(msg + \"\\\\n\")\n self.proc.stdin.flush()\n except (BrokenPipeError, OSError) as e:\n log(f\"send broke: {e}; respawn\")\n self._spawn()\n self.proc.stdin.write(msg + \"\\\\n\")\n self.proc.stdin.flush()\n\n def _recv(self):\n acc = []\n deadline = time.time() + GRADE_TIMEOUT_SEC\n while True:\n if time.time() > deadline:\n log(\"recv timeout; respawn\")\n self._spawn()\n return \"\"\n line = self.proc.stdout.readline()\n if not line:\n log(\"subprocess closed stdout; respawn\")\n self._spawn()\n return \"\"\n try:\n obj = json.loads(line)\n except json.JSONDecodeError:\n continue\n t = obj.get(\"type\")\n if t == \"assistant\":\n for c in obj.get(\"message\", {}).get(\"content\", []):\n if c.get(\"type\") == \"text\":\n acc.append(c[\"text\"])\n elif t == \"result\":\n return \"\".join(acc)\n\n def grade(self, prompt):\n with self.lock:\n age = time.time() - self.start_time\n if self.calls >= ROTATION_CALLS or age >= ROTATION_AGE_SEC:\n log(f\"rotating: calls={self.calls} age={age:.0f}s\")\n self._spawn()\n t0 = time.time()\n self._send(prompt)\n resp = self._recv()\n elapsed = (time.time() - t0) * 1000\n self.calls += 1\n log(f\"grade #{self.calls} elapsed={elapsed:.0f}ms resp_chars={len(resp)}\")\n return resp\n\n\ndef serve(primer):\n pid_fd = os.open(str(PID_FILE), os.O_RDWR | os.O_CREAT, 0o644)\n try:\n fcntl.flock(pid_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)\n except BlockingIOError:\n log(\"another daemon already holds the pid file; exiting\")\n sys.exit(0)\n os.ftruncate(pid_fd, 0)\n os.write(pid_fd, f\"{os.getpid()}\\\\n\".encode())\n os.fsync(pid_fd)\n\n daemon = GraderDaemon(primer)\n\n if SOCK_PATH.exists():\n SOCK_PATH.unlink()\n sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n sock.bind(str(SOCK_PATH))\n sock.listen(8)\n os.chmod(SOCK_PATH, 0o600)\n\n def cleanup(*_):\n try: SOCK_PATH.unlink()\n except Exception: pass\n try: PID_FILE.unlink()\n except Exception: pass\n try: daemon.proc and daemon.proc.terminate()\n except Exception: pass\n sys.exit(0)\n signal.signal(signal.SIGTERM, cleanup)\n signal.signal(signal.SIGINT, cleanup)\n\n log(f\"daemon ready model={DEFAULT_MODEL} sock={SOCK_PATH}\")\n\n while True:\n try:\n conn, _ = sock.accept()\n threading.Thread(target=_handle_conn, args=(conn, daemon), daemon=True).start()\n except Exception as e:\n log(f\"accept error: {e}\")\n time.sleep(0.1)\n\n\ndef _handle_conn(conn, daemon):\n try:\n with conn:\n length_bytes = b\"\"\n while len(length_bytes) < 8:\n chunk = conn.recv(8 - len(length_bytes))\n if not chunk: return\n length_bytes += chunk\n length = int.from_bytes(length_bytes, \"big\")\n if length <= 0 or length > MAX_PROMPT_BYTES:\n log(f\"reject oversized prompt length={length}\")\n return\n prompt = b\"\"\n while len(prompt) < length:\n chunk = conn.recv(min(65536, length - len(prompt)))\n if not chunk: break\n prompt += chunk\n response = daemon.grade(prompt.decode(\"utf-8\", errors=\"replace\"))\n resp_bytes = response.encode(\"utf-8\")\n conn.sendall(len(resp_bytes).to_bytes(8, \"big\"))\n conn.sendall(resp_bytes)\n except Exception as e:\n log(f\"conn error: {e}\")\n\n\ndef daemon_running():\n if not SOCK_PATH.exists():\n return False\n try:\n s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n s.settimeout(0.5)\n s.connect(str(SOCK_PATH))\n s.close()\n return True\n except Exception:\n return False\n\n\ndef ensure_daemon_running(primer_path):\n if daemon_running():\n return True\n primer = Path(primer_path).read_text() if primer_path else \"\"\n pid = os.fork()\n if pid == 0:\n os.setsid()\n pid2 = os.fork()\n if pid2 == 0:\n null = os.open(os.devnull, os.O_RDWR)\n os.dup2(null, 0); os.dup2(null, 1); os.dup2(null, 2)\n serve(primer)\n else:\n os._exit(0)\n else:\n os.waitpid(pid, 0)\n for _ in range(150):\n time.sleep(0.1)\n if daemon_running():\n return True\n return False\n\n\ndef client_grade(prompt):\n s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n s.settimeout(GRADE_TIMEOUT_SEC + 5)\n s.connect(str(SOCK_PATH))\n pb = prompt.encode(\"utf-8\")\n s.sendall(len(pb).to_bytes(8, \"big\"))\n s.sendall(pb)\n length_bytes = b\"\"\n while len(length_bytes) < 8:\n chunk = s.recv(8 - len(length_bytes))\n if not chunk: return \"\"\n length_bytes += chunk\n length = int.from_bytes(length_bytes, \"big\")\n resp = b\"\"\n while len(resp) < length:\n chunk = s.recv(min(65536, length - len(resp)))\n if not chunk: break\n resp += chunk\n s.close()\n return resp.decode(\"utf-8\", errors=\"replace\")\n\n\ndef main():\n global MODE, PID_FILE, SOCK_PATH, LOG_FILE\n args = list(sys.argv[1:])\n if len(args) >= 2 and args[0] == \"--mode\":\n candidate = args[1]\n if not ALLOWED_MODE_RE.match(candidate):\n print(f\"invalid mode: {candidate}\", file=sys.stderr); sys.exit(1)\n MODE = candidate\n PID_FILE, SOCK_PATH, LOG_FILE = mode_paths(MODE)\n args = args[2:]\n\n if len(args) < 1:\n print(\"usage: grader_daemon.py [--mode <name>] {start|grade|stop|status} [primer-path]\", file=sys.stderr)\n sys.exit(1)\n cmd = args[0]\n primer_path = args[1] if len(args) > 1 else None\n\n if cmd == \"start\":\n if ensure_daemon_running(primer_path):\n print(\"daemon up\")\n else:\n print(\"daemon failed to start\", file=sys.stderr); sys.exit(1)\n elif cmd == \"grade\":\n ensure_daemon_running(primer_path)\n prompt = sys.stdin.read()\n try:\n print(client_grade(prompt), end=\"\")\n except Exception as e:\n print(f\"daemon-error: {e}\", file=sys.stderr); sys.exit(2)\n elif cmd == \"stop\":\n if PID_FILE.exists():\n try:\n pid = int(PID_FILE.read_text().strip())\n os.kill(pid, signal.SIGTERM)\n print(f\"sent SIGTERM to {pid}\")\n except Exception: pass\n for p in (SOCK_PATH, PID_FILE):\n try: p.unlink()\n except Exception: pass\n elif cmd == \"status\":\n print(\"running\" if daemon_running() else \"stopped\")\n else:\n print(f\"unknown command: {cmd}\", file=sys.stderr); sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n`;\n\nexport const GRADER_PRIMER_EDIT = `You are Synkro's security pre-check grader. You will be given proposed file diffs from an AI coding agent. For each one, decide whether it has security issues — possibly multiple distinct ones in the same diff.\n\nOUTPUT RULES — strictest possible, no exceptions:\n\n1. NO reasoning. NO preamble. NO commentary.\n2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.\n3. JSON shape:\n {\"ok\": true | false,\n \"violations\": [\n {\"rule_id\": \"<short snake_case slug, e.g. no-hardcoded-secrets>\",\n \"severity\": \"low\" | \"medium\" | \"high\" | \"critical\",\n \"category\": \"<short snake_case>\",\n \"reason\": \"<= 25 words, file:line + issue + fix\",\n \"confidence\": 0.0-1.0}\n ]}\n\nok=true → \"violations\": [] (empty array). Safe diffs (placeholder, fixture, sandboxed, clean) are ok=true.\n\nok=false → list EVERY distinct violation. A diff that hardcodes a Stripe key AND uses eval AND has SQL injection produces THREE entries — one per independently-fixable issue. Don't collapse multiple issues into one \"composite\" finding; the agent retries based on this list and missing entries become extra round trips.\n\nONE VIOLATION = ONE ENTRY. If the same line/issue can be described multiple ways (\"uses concat\", \"missing $1 placeholder\", \"not parameterized\"), pick the most specific rule_id and write a single entry. Multiple entries are warranted only when the diff has multiple INDEPENDENTLY-FIXABLE issues.\n\nReply with exactly: <synkro-verdict>{\"ok\":true,\"violations\":[]}</synkro-verdict>\n`;\n\nexport const GRADER_PRIMER_BASH = `You are Synkro's bash command safety judge for AI coding agents. You will be given proposed shell commands + the user's stated intent + recent agent actions. Decide whether to allow or warn.\n\nOUTPUT RULES — strictest possible, no exceptions:\n\n1. NO reasoning. NO preamble. NO commentary.\n2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.\n3. JSON shape: {\"verdict\": \"warn\"|\"allow\", \"severity\": \"low|medium|high|critical\", \"category\": \"snake_case\", \"reasoning\": \"<= 25 words, cites intent + match/mismatch\", \"alternative\": \"safer command or null\"}\n\nRules:\n- WARN if destructive/irreversible AND not aligned with user intent, OR has wildly disproportionate blast radius vs the request.\n- ALLOW if consistent with user intent and either reversible or low-blast-radius.\n- The same command can be allow OR warn depending on context. Match against the user's verbatim intent.\n- Be conservative: when uncertain, warn medium.\n- Token-scope check: if recent_actions shows a Read of a credentials file (e.g. \".env.deploy\", \"domain-token.txt\", \"deploy-key\") and the proposed command uses an Authorization Bearer header, flag token_scope_mismatch HIGH if the operation is broader than the token's apparent scope.\n\nReply with exactly: <synkro-verdict>{\"verdict\":\"allow\",\"severity\":\"low\",\"category\":\"primer_ack\",\"reasoning\":\"primer received\",\"alternative\":null}</synkro-verdict>\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\");\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_WEB_AUTH_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 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/**\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 } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks } from '../installer/ccHookConfig.js';\nimport { installMcpConfig } from '../installer/mcpConfig.js';\nimport { CC_BASH_JUDGE_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT } from '../installer/hookScripts.js';\nimport { GRADER_DAEMON_PY, GRADER_PRIMER_EDIT, GRADER_PRIMER_BASH } from '../installer/graderDaemon.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo } from '../auth/stub.js';\n\n// Replaced by tsup `define` at build time with the value from package.json.\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');\nconst GRADER_DAEMON_PATH = join(BIN_DIR, 'grader_daemon.py');\nconst GRADER_PRIMER_EDIT_PATH = join(SYNKRO_DIR, 'grader-primer-edit.txt');\nconst GRADER_PRIMER_BASH_PATH = join(SYNKRO_DIR, 'grader-primer-bash.txt');\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}\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 }\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\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n}\n\n// Write the persistent claude grader daemon + primer used by the local-tier\n// hook path. The daemon keeps one `claude --print --input-format=stream-json`\n// process alive, primed once, so each grading runs at ~1.5–3s steady-state\n// vs ~14s for cold `claude --print`.\nfunction writeGraderDaemon(): void {\n writeFileSync(GRADER_DAEMON_PATH, GRADER_DAEMON_PY, 'utf-8');\n chmodSync(GRADER_DAEMON_PATH, 0o755);\n writeFileSync(GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_EDIT, 'utf-8');\n chmodSync(GRADER_PRIMER_EDIT_PATH, 0o644);\n writeFileSync(GRADER_PRIMER_BASH_PATH, GRADER_PRIMER_BASH, 'utf-8');\n chmodSync(GRADER_PRIMER_BASH_PATH, 0o644);\n}\n\nfunction writeHookScripts(): {\n bashScript: string;\n bashFollowupScript: string;\n editCaptureScript: string;\n editPrecheckScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n} {\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.sh');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.sh');\n const editCaptureScriptPath = join(HOOKS_DIR, 'cc-edit-capture.sh');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.sh');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.sh');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.sh');\n\n writeFileSync(bashScriptPath, CC_BASH_JUDGE_SCRIPT, 'utf-8');\n writeFileSync(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, 'utf-8');\n writeFileSync(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, 'utf-8');\n writeFileSync(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, 'utf-8');\n writeFileSync(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, 'utf-8');\n writeFileSync(sessionStartScriptPath, CC_SESSION_START_SCRIPT, 'utf-8');\n\n chmodSync(bashScriptPath, 0o755);\n chmodSync(bashFollowupScriptPath, 0o755);\n chmodSync(editCaptureScriptPath, 0o755);\n chmodSync(editPrecheckScriptPath, 0o755);\n chmodSync(stopSummaryScriptPath, 0o755);\n chmodSync(sessionStartScriptPath, 0o755);\n\n return {\n bashScript: bashScriptPath,\n bashFollowupScript: bashFollowupScriptPath,\n editCaptureScript: editCaptureScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\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\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string }): 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 // Inference tier comes from env override only — allowlist to {free, fast}.\n const tierEnv = process.env.SYNKRO_INFERENCE_TIER;\n const safeInferenceTier = tierEnv === 'fast' ? 'fast' : 'free';\n\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_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\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 // Inference tier: 'free' = grade locally with claude --print (slow but $0\n // to Synkro and customer pays only Sonnet via their CC subscription).\n // 'fast' = server-side Cerebras (~500ms, Synkro-billed). New installs land\n // on free; pro plans flip to fast via the dashboard.\n lines.push(`SYNKRO_INFERENCE_TIER=${shellQuoteSingle(safeInferenceTier)}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\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.sh'),\n join(HOOKS_DIR, 'cc-bash-followup.sh'),\n join(HOOKS_DIR, 'cc-edit-precheck.sh'),\n join(HOOKS_DIR, 'cc-edit-capture.sh'),\n join(HOOKS_DIR, 'cc-stop-summary.sh'),\n join(HOOKS_DIR, 'cc-session-start.sh'),\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\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, exit early instead of re-running every step. The\n // user is most likely just retyping the command. Use --force to reinstall.\n if (!opts.force && isAuthenticated() && isAlreadyInstalled()) {\n console.log('✓ Synkro is already installed and configured.');\n console.log(' Run `synkro update` to refresh hook scripts and judge prompts.');\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 // 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.editCaptureScript}`);\n console.log(` ${scripts.editPrecheckScript}`);\n console.log(` ${scripts.stopSummaryScript}`);\n console.log(` ${scripts.sessionStartScript}\\n`);\n\n writeGraderDaemon();\n console.log('Wrote local-tier grader daemon:');\n console.log(` ${GRADER_DAEMON_PATH}`);\n console.log(` ${GRADER_PRIMER_EDIT_PATH}`);\n console.log(` ${GRADER_PRIMER_BASH_PATH}\\n`);\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n 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 editCaptureScriptPath: scripts.editCaptureScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Register the Synkro Guardrails MCP server in ~/.claude.json so coding\n // agents can pull org-specific rules on demand. Skip when --no-mcp is\n // set or when no CC install was detected.\n //\n // We mint a long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails\n // and write THAT into ~/.claude.json — never the WorkOS access token,\n // which expires in 5 min and silently kills the MCP connection mid-session.\n if (hasClaudeCode && !opts.noMcp) {\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 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 // 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 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 writeConfigEnv({ gatewayUrl, userId, orgId, email });\n console.log(`Wrote config to ${CONFIG_PATH}\\n`);\n\n // 7. Done — print next steps\n console.log('✓ Synkro installed.');\n console.log();\n console.log('Next steps:');\n console.log(' • synkro setup-github (enable PR scanning)');\n console.log(' • synkro status (check what is configured)');\n}\n","/**\n * synkro login — browser OAuth via WorkOS (reuses existing auth flow).\n */\nimport { authenticate, isAuthenticated, getUserInfo } from '../auth/stub.js';\n\nexport async function loginCommand(args: string[] = []): Promise<void> {\n const force = args.includes('--force') || args.includes('-f');\n if (isAuthenticated() && !force) {\n const info = getUserInfo();\n console.log(`Already authenticated as ${info?.email ?? 'unknown'}.`);\n console.log('Use --force to re-authenticate.');\n return;\n }\n console.log('Opening browser for Synkro login...');\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('Login failed. Make sure the Synkro web app is running.');\n process.exit(1);\n }\n const info = getUserInfo();\n console.log(`✓ Logged in as ${info?.email ?? 'unknown'}.`);\n}\n","/**\n * synkro logout — clear local credentials.\n */\nimport { isAuthenticated, clearCredentials } from '../auth/stub.js';\n\nexport function logoutCommand(): void {\n if (!isAuthenticated()) {\n console.log('Not authenticated.');\n return;\n }\n clearCredentials();\n console.log('Logged out.');\n}\n","/**\n * synkro status — show current setup state.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { isAuthenticated, getUserInfo } from '../auth/stub.js';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { inspectCCHooks } from '../installer/ccHookConfig.js';\nimport { inspectMcpConfig } from '../installer/mcpConfig.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n const raw = readFileSync(CONFIG_PATH, 'utf-8');\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eq = trimmed.indexOf('=');\n if (eq > 0) {\n const k = trimmed.slice(0, eq).trim();\n const v = trimmed.slice(eq + 1).trim();\n out[k] = v;\n }\n }\n return out;\n}\n\nexport function statusCommand(): void {\n console.log('Synkro CLI status\\n');\n\n // Auth\n if (isAuthenticated()) {\n const info = getUserInfo();\n console.log(`Authentication: ✓ logged in as ${info?.email ?? 'unknown'}`);\n if (info?.org_id) console.log(` org_id: ${info.org_id}`);\n if (info?.id) console.log(` user_id: ${info.id}`);\n } else {\n console.log('Authentication: ✗ not logged in (run: synkro login)');\n }\n console.log();\n\n // Config\n const config = readConfigEnv();\n console.log('Config:');\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL ?? '(unset)'}`);\n console.log(` credentials: ${config.SYNKRO_CREDENTIALS_PATH ?? '(unset)'}`);\n console.log(` tier: ${config.SYNKRO_TIER ?? '(unset)'}`);\n console.log(` version: ${config.SYNKRO_VERSION ?? '(unset)'}`);\n console.log();\n\n // Agents\n const agents = detectAgents();\n console.log('Detected agents:');\n if (agents.length === 0) {\n console.log(' (none — install Claude Code first)');\n } else {\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n console.log(` settings: ${a.settingsPath}`);\n if (a.kind === 'claude_code') {\n const hooks = inspectCCHooks(a.settingsPath);\n console.log(` hooks installed: ${hooks.installed ? '✓' : '✗'}`);\n if (hooks.installed) {\n console.log(` • PreToolUse Bash: ${hooks.preToolUseBash ? '✓' : '✗'}`);\n console.log(` • PostToolUse Edit: ${hooks.postToolUseEdit ? '✓' : '✗'}`);\n console.log(` • SessionEnd summary: ${hooks.sessionEnd ? '✓' : '✗'}`);\n console.log(` • SessionStart: ${hooks.sessionStart ? '✓' : '✗'}`);\n }\n }\n }\n }\n console.log();\n\n // Hook scripts\n const bashScript = join(SYNKRO_DIR, 'hooks', 'cc-bash-judge.sh');\n const bashFollowupScript = join(SYNKRO_DIR, 'hooks', 'cc-bash-followup.sh');\n const editPrecheckScript = join(SYNKRO_DIR, 'hooks', 'cc-edit-precheck.sh');\n const editCaptureScript = join(SYNKRO_DIR, 'hooks', 'cc-edit-capture.sh');\n const stopSummaryScript = join(SYNKRO_DIR, 'hooks', 'cc-stop-summary.sh');\n const sessionStartScript = join(SYNKRO_DIR, 'hooks', 'cc-session-start.sh');\n console.log('Hook scripts:');\n console.log(` ${existsSync(bashScript) ? '✓' : '✗'} ${bashScript}`);\n console.log(` ${existsSync(bashFollowupScript) ? '✓' : '✗'} ${bashFollowupScript}`);\n console.log(` ${existsSync(editPrecheckScript) ? '✓' : '✗'} ${editPrecheckScript}`);\n console.log(` ${existsSync(editCaptureScript) ? '✓' : '✗'} ${editCaptureScript}`);\n console.log(` ${existsSync(stopSummaryScript) ? '✓' : '✗'} ${stopSummaryScript}`);\n console.log(` ${existsSync(sessionStartScript) ? '✓' : '✗'} ${sessionStartScript}`);\n console.log();\n\n // MCP guardrails server registration (CC's ~/.claude.json)\n const mcp = inspectMcpConfig();\n console.log('Guardrails MCP server (Claude Code):');\n if (mcp.installed) {\n console.log(` ✓ registered in ${mcp.configPath}`);\n console.log(` url: ${mcp.url}`);\n } else {\n console.log(` ✗ not registered (run: synkro install)`);\n console.log(` expected at ${mcp.configPath} → mcpServers.synkro-guardrails`);\n }\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\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: read\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\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 \"~/.npm-global/bin\" >> $GITHUB_PATH\n\n - name: Run Synkro PR scan\n run: synkro 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: \\${{ github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_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 the user's personal access token (or App installation token from OAuth)\n * to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY as repo secrets, then writes\n * .github/workflows/synkro.yml to the local clone if present.\n *\n * Secrets are encrypted with the repo's public key using libsodium sealed_box\n * (GitHub's required format).\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\ninterface RepoPublicKey {\n key_id: string;\n key: string; // base64\n}\n\n/**\n * Encrypt a secret value with the repo's public key.\n * GitHub requires libsodium sealed_box (Curve25519 + XSalsa20Poly1305).\n */\nasync function encryptSecret(publicKeyBase64: string, secret: string): Promise<string> {\n // Use libsodium-wrappers (pure-JS implementation; ~200KB)\n const sodium = await import('libsodium-wrappers').then((m) => m.default ?? m);\n await (sodium as any).ready;\n\n const keyBytes = (sodium as any).from_base64(publicKeyBase64, (sodium as any).base64_variants.ORIGINAL);\n const messageBytes = (sodium as any).from_string(secret);\n const encryptedBytes = (sodium as any).crypto_box_seal(messageBytes, keyBytes);\n return (sodium as any).to_base64(encryptedBytes, (sodium as any).base64_variants.ORIGINAL);\n}\n\nexport interface GitHubAuthOptions {\n /** Personal access token, OAuth token, or App installation token. */\n token: string;\n}\n\nexport async function getRepoPublicKey(opts: GitHubAuthOptions, owner: string, repo: string): Promise<RepoPublicKey> {\n const url = `https://api.github.com/repos/${owner}/${repo}/actions/secrets/public-key`;\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 const text = await resp.text().catch(() => '');\n throw new Error(`GitHub API ${resp.status} fetching public key for ${owner}/${repo}: ${text.slice(0, 200)}`);\n }\n return await resp.json() as RepoPublicKey;\n}\n\nexport async function putRepoSecret(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secretName: string,\n secretValue: string,\n publicKey: RepoPublicKey,\n): Promise<void> {\n const encryptedValue = await encryptSecret(publicKey.key, secretValue);\n const url = `https://api.github.com/repos/${owner}/${repo}/actions/secrets/${encodeURIComponent(secretName)}`;\n const resp = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n encrypted_value: encryptedValue,\n key_id: publicKey.key_id,\n }),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`GitHub API ${resp.status} setting secret ${secretName}: ${text.slice(0, 200)}`);\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\n/**\n * Push both Synkro secrets to a repo.\n */\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken: string; synkroApiKey: string },\n): Promise<void> {\n const pubkey = await getRepoPublicKey(opts, owner, repo);\n await putRepoSecret(opts, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken, pubkey);\n await putRepoSecret(opts, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey, pubkey);\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 * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Prompt for a GitHub personal access token (or App installation token).\n * For v1 we ask for a PAT to avoid the App OAuth dance — the user creates\n * a fine-grained token at github.com/settings/tokens with `repo` +\n * `actions:write` scopes for the repos they want to enable.\n * 2. Prompt the user to run `claude setup-token` in another terminal and\n * paste the resulting sk-ant-oat01-... long-lived OAuth token.\n * 3. List accessible repos via GitHub API.\n * 4. Interactive multi-select (simple numbered prompt).\n * 5. For each selected repo: encrypt secrets with libsodium and PUT them.\n * 6. 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 { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken } 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();\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 // Best-effort password masking — works on most terminals\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') { // Ctrl-C\n process.exit(130);\n }\n if (s === '\\u007f' || s === '\\b') { // backspace\n chunk = chunk.slice(0, -1);\n return;\n }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nexport async function setupGithubCommand(): Promise<void> {\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro 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 login`.');\n process.exit(1);\n }\n\n // Mint a CI-scoped API key on demand. We push this into the repo secret as\n // SYNKRO_API_KEY so scan-pr can authenticate against the gateway from the\n // GH Actions runner. Never persisted on the user's disk.\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ci-api-key`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n const minted = await resp.json() as { api_key: string; expires_at: string; project_id: string };\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 const rl = createInterface({ input, output });\n\n console.log('Synkro PR scan setup\\n');\n console.log('Requirements:');\n console.log(' • Claude Code Pro or Max subscription (for `claude setup-token`)');\n console.log(' • A GitHub personal access token with `repo` scope');\n console.log(' (create at https://github.com/settings/tokens?type=beta)\\n');\n\n // 1. GitHub PAT\n const ghToken = (await prompt(rl, 'GitHub token (paste): ', { silent: true })).trim();\n if (!ghToken || (!ghToken.startsWith('ghp_') && !ghToken.startsWith('github_pat_'))) {\n console.error('Invalid GitHub token format. Expected ghp_... or github_pat_...');\n rl.close();\n process.exit(1);\n }\n\n // 2. Claude Code OAuth token\n console.log('\\nNow get your Claude Code OAuth token:');\n console.log(' 1. In another terminal, run: claude setup-token');\n console.log(' 2. Complete the browser flow');\n console.log(' 3. Copy the resulting sk-ant-oat01-... token\\n');\n const claudeToken = (await prompt(rl, 'Claude Code OAuth token (paste): ', { silent: true })).trim();\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token. Expected sk-ant-oat01-... — generate one with `claude setup-token`.');\n rl.close();\n process.exit(1);\n }\n\n // 3. List repos\n console.log('\\nFetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.error('No accessible repos found. Verify the GitHub token has `repo` scope.');\n rl.close();\n process.exit(1);\n }\n\n // 4. Multi-select prompt\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 selectionRaw = await prompt(rl, '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\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl.close();\n process.exit(1);\n }\n\n const 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(rl, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl.close();\n process.exit(0);\n }\n\n rl.close();\n\n // 5. Push secrets to each repo\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 to local repo if cwd is one\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: see https://docs.synkro.sh/cli/scan-pr or run \\`synkro setup-github\\` from inside a repo`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro scan-pr — runs inside a GitHub Actions runner.\n *\n * Reads PR context from env (set by the workflow YAML), fetches the PR diff\n * via `gh` CLI, spawns `claude --print` per changed file with the customer's\n * CLAUDE_CODE_OAUTH_TOKEN, aggregates findings, posts inline review comments,\n * sets a status check, and POSTs aggregate to /v1/events/pr-scan for logging.\n *\n * Auth chain (set by workflow YAML):\n * CLAUDE_CODE_OAUTH_TOKEN — long-lived OAuth token from `claude setup-token`\n * SYNKRO_API_KEY — auth for our backend\n * GH_TOKEN — GitHub-injected, for posting comments + checks\n * SYNKRO_PR_NUMBER, SYNKRO_REPO, SYNKRO_SHA — PR context\n */\nimport { execSync, spawn } from 'node:child_process';\n\nconst SKIP_FILE_PATTERNS = [\n /\\.lock$/i,\n /\\.min\\./i,\n /\\.map$/i,\n /^dist\\//,\n /^build\\//,\n /^vendor\\//,\n /^node_modules\\//,\n /^\\.next\\//,\n /package-lock\\.json$/,\n /yarn\\.lock$/,\n /pnpm-lock\\.yaml$/,\n /Cargo\\.lock$/,\n /go\\.sum$/,\n];\n\nconst MAX_DIFF_LINES_PER_FILE = 1000;\nconst MAX_PARALLEL_FILES = 5;\n\ninterface PrFile {\n filename: string;\n status: string; // 'added' | 'modified' | 'removed' | 'renamed'\n additions: number;\n deletions: number;\n patch?: string;\n}\n\ninterface Finding {\n file: string;\n line: number;\n severity: 'low' | 'medium' | 'high' | 'critical';\n category: string;\n description: string;\n fix: string;\n}\n\ninterface OrgRule {\n rule_id: string;\n text: string;\n category: string;\n severity: string;\n mode: 'audit' | 'literal_match';\n condition: string;\n}\n\ninterface LiteralMatchSpec {\n selector: string;\n requires: string;\n position: 'start' | 'anywhere';\n negate?: boolean;\n}\n\nfunction parseMatchSpec(condition: string): LiteralMatchSpec | null {\n if (!condition.startsWith('match_spec:')) return null;\n try {\n const parsed = JSON.parse(condition.slice('match_spec:'.length)) as Partial<LiteralMatchSpec>;\n if (\n typeof parsed?.selector !== 'string' ||\n typeof parsed?.requires !== 'string' ||\n (parsed.position !== 'start' && parsed.position !== 'anywhere')\n ) return null;\n return {\n selector: parsed.selector,\n requires: parsed.requires,\n position: parsed.position,\n negate: parsed.negate === true,\n };\n } catch {\n return null;\n }\n}\n\nfunction selectorMatches(selector: string, filePath: string): boolean {\n const m = selector.match(/^\\*\\*\\/\\*\\.([a-z0-9]+)$/i);\n if (!m) return false;\n return filePath.toLowerCase().endsWith('.' + m[1].toLowerCase());\n}\n\nasync function fetchOrgRules(gatewayUrl: string, apiKey: string): Promise<OrgRule[]> {\n try {\n const resp = await fetch(`${gatewayUrl.replace(/\\/$/, '')}/api/v1/cli/pr-rules`, {\n headers: { 'x-synkro-api-key': apiKey },\n });\n if (!resp.ok) {\n console.warn(`[scan-pr] failed to fetch org rules: HTTP ${resp.status}`);\n return [];\n }\n const data = await resp.json() as { rules?: OrgRule[] };\n return Array.isArray(data?.rules) ? data.rules : [];\n } catch (err) {\n console.warn(`[scan-pr] could not fetch org rules: ${(err as Error).message}`);\n return [];\n }\n}\n\n// Deterministic literal_match enforcement on the patch's added lines.\n// We can only check the diff (not the full file), so this catches\n// negative-form rules (\"Never use X\") cleanly. Positive-form rules\n// (\"must contain X\") need the full file content and are deferred to\n// edit-time hooks for v1 — we surface a note rather than skip silently.\nfunction applyLiteralMatchNegative(rules: OrgRule[], file: PrFile): Finding[] {\n if (!file.patch) return [];\n const findings: Finding[] = [];\n const lines = file.patch.split('\\n');\n let currentNewLine = 0;\n for (const line of lines) {\n if (line.startsWith('@@')) {\n const m = line.match(/\\+(\\d+)(?:,\\d+)?/);\n if (m) currentNewLine = parseInt(m[1], 10);\n continue;\n }\n if (line.startsWith('+++') || line.startsWith('---')) continue;\n if (!line.startsWith('+')) {\n // context or removed line: only NEW-file line counter advances on context\n if (!line.startsWith('-')) currentNewLine++;\n continue;\n }\n const addedContent = line.slice(1); // strip leading '+'\n for (const r of rules) {\n if (r.mode !== 'literal_match') continue;\n const spec = parseMatchSpec(r.condition);\n if (!spec || !spec.negate) continue; // only negative rules in PR scan v1\n if (!selectorMatches(spec.selector, file.filename)) continue;\n if (!addedContent.includes(spec.requires)) continue;\n findings.push({\n file: file.filename,\n line: currentNewLine,\n severity: (r.severity as Finding['severity']) ?? 'high',\n category: r.category || 'literal_match',\n description: r.text,\n fix: `Remove \\`${spec.requires}\\` from ${file.filename} (rule ${r.rule_id}).`,\n });\n }\n currentNewLine++;\n }\n return findings;\n}\n\nfunction buildPrPrompt(orgAuditRules: OrgRule[]): string {\n const orgRulesBlock = orgAuditRules.length === 0\n ? ''\n : `\\nORG-SPECIFIC RULES (these are the customer's policies — flag any violation found in the diff):\\n` +\n orgAuditRules\n .map((r, i) => ` ${i + 1}. [${r.severity}/${r.category}] ${r.text}`)\n .join('\\n') +\n '\\n';\n\n return `You are a security code reviewer analyzing a pull request diff for one file. Identify security issues + org-policy violations introduced by this diff.\n\nOutput ONLY a JSON object (no prose, no markdown fences):\n{\n \"findings\": [\n {\n \"line\": <int — line number in the NEW file, prefixed with L in the diff>,\n \"severity\": \"low\" | \"medium\" | \"high\" | \"critical\",\n \"category\": \"<snake_case>\",\n \"description\": \"<1 sentence>\",\n \"fix\": \"<concrete suggestion>\"\n }\n ],\n \"summary\": \"<one-line: 'X findings' or 'clean'>\"\n}\n\nBaseline security categories: hardcoded_secret, sql_injection, insecure_crypto, eval_exec, unsafe_deserialization, missing_validation, exposed_internal, missing_auth_check, cors_misconfig, path_traversal, command_injection, weak_random, broken_jwt.\n${orgRulesBlock}\nRules:\n- Only flag NEW issues (lines starting with +).\n- Use the L<num> line numbers I prefixed.\n- Be specific. If clean, return {\"findings\": [], \"summary\": \"clean\"}.\n\n`;\n}\n\nfunction shouldSkipFile(filename: string): boolean {\n return SKIP_FILE_PATTERNS.some((p) => p.test(filename));\n}\n\nfunction ghJson<T>(args: string[]): T {\n const out = execSync(`gh ${args.map((a) => `'${a.replace(/'/g, \"'\\\\''\")}'`).join(' ')}`, {\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return JSON.parse(out) as T;\n}\n\nfunction getPrFiles(repo: string, prNumber: number): PrFile[] {\n // gh api returns paginated results; for v1 we get the first page (250 files)\n const data = ghJson<PrFile[]>([\n 'api',\n `/repos/${repo}/pulls/${prNumber}/files?per_page=250`,\n ]);\n return data;\n}\n\nfunction getFileDiffWithLines(file: PrFile): { hunks: string; newFileLineMap: Map<number, number> } {\n // For each hunk, build a map of \"position in patch\" → \"line number in NEW file\"\n // so claude can reference correct line numbers.\n if (!file.patch) return { hunks: '', newFileLineMap: new Map() };\n\n const lines = file.patch.split('\\n');\n const annotated: string[] = [];\n const lineMap = new Map<number, number>();\n let currentNewLine = 0;\n let patchIndex = 0;\n\n for (const line of lines) {\n patchIndex++;\n if (line.startsWith('@@')) {\n // Parse hunk header: @@ -A,B +C,D @@ ...\n const match = line.match(/\\+(\\d+)(?:,\\d+)?/);\n if (match) {\n currentNewLine = parseInt(match[1], 10);\n }\n annotated.push(line);\n continue;\n }\n if (line.startsWith('+') && !line.startsWith('+++')) {\n annotated.push(`L${currentNewLine}: ${line}`);\n lineMap.set(patchIndex, currentNewLine);\n currentNewLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n annotated.push(line);\n // line was removed; don't increment new file line counter\n } else if (!line.startsWith('---') && !line.startsWith('+++')) {\n annotated.push(`L${currentNewLine}: ${line}`);\n currentNewLine++;\n } else {\n annotated.push(line);\n }\n }\n\n return { hunks: annotated.join('\\n'), newFileLineMap: lineMap };\n}\n\nfunction spawnClaudeJudge(file: PrFile, claudeToken: string, promptHeader: string): Promise<{ findings: Finding[]; latencyMs: number }> {\n const { hunks } = getFileDiffWithLines(file);\n const userMessage = `File: ${file.filename}\\n\\nDiff:\\n${hunks}`;\n const fullPrompt = promptHeader + userMessage;\n\n return new Promise((resolve) => {\n const t0 = Date.now();\n const proc = spawn(\n 'claude',\n ['--print', '--model', 'claude-sonnet-4-6', '--output-format', 'json', '--no-session-persistence', fullPrompt],\n {\n env: {\n ...process.env,\n CLAUDE_CODE_OAUTH_TOKEN: claudeToken,\n },\n timeout: 120_000,\n },\n );\n let stdout = '';\n proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });\n proc.stderr.on('data', () => { /* swallow */ });\n proc.on('close', (code) => {\n const latencyMs = Date.now() - t0;\n if (code !== 0) {\n resolve({ findings: [], latencyMs });\n return;\n }\n try {\n const wrapper = JSON.parse(stdout);\n const responseText = (wrapper.result || wrapper.response || wrapper.text || '').trim();\n let txt = responseText;\n if (txt.startsWith('```')) {\n txt = txt.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```\\s*$/, '').trim();\n }\n const verdict = JSON.parse(txt);\n const findings = (verdict.findings || []).map((f: any) => ({\n file: file.filename,\n line: f.line,\n severity: f.severity,\n category: f.category,\n description: f.description,\n fix: f.fix,\n })) as Finding[];\n resolve({ findings, latencyMs });\n } catch {\n resolve({ findings: [], latencyMs });\n }\n });\n });\n}\n\nasync function processInBatches<T, R>(items: T[], batchSize: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const results: R[] = [];\n for (let i = 0; i < items.length; i += batchSize) {\n const batch = items.slice(i, i + batchSize);\n const batchResults = await Promise.all(batch.map(fn));\n results.push(...batchResults);\n }\n return results;\n}\n\nfunction postPrComment(repo: string, prNumber: number, sha: string, finding: Finding): void {\n const body = `🔒 **Synkro [${finding.severity}]: ${finding.category}**\\n\\n${finding.description}\\n\\n**Fix:** ${finding.fix}`;\n try {\n execSync(\n `gh api -X POST /repos/${repo}/pulls/${prNumber}/comments ` +\n `-f body=${JSON.stringify(body)} ` +\n `-f commit_id=${sha} ` +\n `-f path=${JSON.stringify(finding.file)} ` +\n `-F line=${finding.line} ` +\n `-f side=RIGHT`,\n { encoding: 'utf-8', stdio: ['ignore', 'ignore', 'pipe'] },\n );\n } catch (err) {\n console.warn(`Failed to post comment on ${finding.file}:${finding.line}:`, (err as Error).message);\n }\n}\n\nfunction postCheckRun(repo: string, sha: string, conclusion: 'success' | 'failure', findings: Finding[]): void {\n const summary = findings.length === 0\n ? 'No security findings.'\n : `${findings.length} finding(s):\\n` + findings.slice(0, 20).map((f) => `- **${f.severity}**: ${f.file}:${f.line} — ${f.description}`).join('\\n');\n const body = JSON.stringify({\n name: 'Synkro Security Review',\n head_sha: sha,\n status: 'completed',\n conclusion,\n output: {\n title: findings.length === 0 ? 'No issues found' : `${findings.length} security finding(s)`,\n summary,\n },\n });\n try {\n execSync(`gh api -X POST /repos/${repo}/check-runs --input -`, {\n encoding: 'utf-8',\n input: body,\n stdio: ['pipe', 'ignore', 'pipe'],\n });\n } catch (err) {\n console.warn('Failed to post check run:', (err as Error).message);\n }\n}\n\nfunction shouldFail(findings: Finding[], threshold: 'critical' | 'high' | 'medium' | 'low'): boolean {\n const order = ['low', 'medium', 'high', 'critical'];\n const thresholdIdx = order.indexOf(threshold);\n return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);\n}\n\nasync function postEventToBackend(opts: {\n gatewayUrl: string;\n apiKey: string;\n repo: string;\n prNumber: number;\n sha: string;\n findings: Finding[];\n filesScanned: number;\n totalLatencyMs: number;\n}): Promise<void> {\n try {\n await fetch(`${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/events/pr-scan`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-synkro-api-key': opts.apiKey,\n },\n body: JSON.stringify({\n repo: opts.repo,\n pr_number: opts.prNumber,\n sha: opts.sha,\n findings: opts.findings,\n summary: opts.findings.length === 0 ? 'clean' : `${opts.findings.length} findings`,\n files_scanned: opts.filesScanned,\n total_latency_ms: opts.totalLatencyMs,\n }),\n });\n } catch (err) {\n console.warn('Failed to log scan to Synkro backend:', (err as Error).message);\n }\n}\n\nexport async function scanPrCommand(): Promise<void> {\n const repo = process.env.SYNKRO_REPO || process.env.GITHUB_REPOSITORY || '';\n const prNumberStr = process.env.SYNKRO_PR_NUMBER || '';\n const sha = process.env.SYNKRO_SHA || process.env.GITHUB_SHA || '';\n const claudeToken = process.env.CLAUDE_CODE_OAUTH_TOKEN || '';\n const synkroApiKey = process.env.SYNKRO_API_KEY || '';\n const gatewayUrl = process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh';\n const failThreshold = (process.env.SYNKRO_FAIL_THRESHOLD || 'high') as 'critical' | 'high' | 'medium' | 'low';\n\n if (!repo || !prNumberStr || !sha || !claudeToken || !synkroApiKey) {\n console.error('Missing required env vars: SYNKRO_REPO, SYNKRO_PR_NUMBER, SYNKRO_SHA, CLAUDE_CODE_OAUTH_TOKEN, SYNKRO_API_KEY');\n process.exit(2);\n }\n const prNumber = parseInt(prNumberStr, 10);\n if (!prNumber) {\n console.error('SYNKRO_PR_NUMBER is not a valid number:', prNumberStr);\n process.exit(2);\n }\n\n console.log(`Synkro scan-pr: ${repo}#${prNumber} @ ${sha.slice(0, 7)}\\n`);\n\n // Fetch the org's active runtime rules so we enforce them in PR review\n // — same rules that fire on edit-time hooks. Without this, scan-pr would\n // ignore every rule the customer added via create_guardrail / dashboard.\n const orgRules = await fetchOrgRules(gatewayUrl, synkroApiKey);\n const auditRules = orgRules.filter((r) => r.mode === 'audit');\n const literalNegativeRules = orgRules.filter((r) => {\n const spec = parseMatchSpec(r.condition);\n return r.mode === 'literal_match' && spec?.negate === true;\n });\n console.log(`Loaded ${orgRules.length} org rule(s): ${auditRules.length} audit, ${literalNegativeRules.length} literal_match (negative).`);\n const promptHeader = buildPrPrompt(auditRules);\n\n // Fetch PR file list\n let files: PrFile[];\n try {\n files = getPrFiles(repo, prNumber);\n } catch (err) {\n console.error('Failed to fetch PR files:', (err as Error).message);\n process.exit(2);\n }\n\n // Filter\n const eligible = files.filter((f) => {\n if (f.status === 'removed') return false;\n if (shouldSkipFile(f.filename)) return false;\n if ((f.additions + f.deletions) > MAX_DIFF_LINES_PER_FILE) return false;\n if (!f.patch) return false;\n return true;\n });\n\n console.log(`${files.length} files in PR, ${eligible.length} eligible for scan.\\n`);\n\n if (eligible.length === 0) {\n postCheckRun(repo, sha, 'success', []);\n await postEventToBackend({\n gatewayUrl, apiKey: synkroApiKey, repo, prNumber, sha,\n findings: [], filesScanned: 0, totalLatencyMs: 0,\n });\n console.log('No eligible files. Exiting.');\n return;\n }\n\n // Scan in parallel batches. Each file gets:\n // 1. Deterministic literal_match-negative checks on the patch's added\n // lines (no LLM, no cost, fires on rules like \"Never use `useEffect`\n // in tsx files\"). Positive literal_match rules need full file content\n // and are deferred to edit-time hooks for v1.\n // 2. LLM-based audit pass for everything else, with the org's audit rules\n // injected into the prompt so customer policies actually surface.\n const t0 = Date.now();\n const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file) => {\n process.stdout.write(`Scanning ${file.filename}...`);\n const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);\n const llmResult = await spawnClaudeJudge(file, claudeToken, promptHeader);\n const merged = [...literalFindings, ...llmResult.findings];\n console.log(` ${merged.length} finding(s) (${literalFindings.length} literal, ${llmResult.findings.length} llm; ${llmResult.latencyMs}ms)`);\n return { findings: merged, latencyMs: llmResult.latencyMs };\n });\n const totalLatencyMs = Date.now() - t0;\n\n const allFindings: Finding[] = results.flatMap((r) => r.findings);\n console.log(`\\nTotal: ${allFindings.length} finding(s) across ${eligible.length} file(s) in ${totalLatencyMs}ms\\n`);\n\n // Post inline comments\n for (const finding of allFindings) {\n postPrComment(repo, prNumber, sha, finding);\n }\n\n // Post status check\n const conclusion = shouldFail(allFindings, failThreshold) ? 'failure' : 'success';\n postCheckRun(repo, sha, conclusion, allFindings);\n\n // Log to backend\n await postEventToBackend({\n gatewayUrl, apiKey: synkroApiKey, repo, prNumber, sha,\n findings: allFindings, filesScanned: eligible.length, totalLatencyMs,\n });\n\n console.log(`\\n✓ Scan complete. Status: ${conclusion}.`);\n\n // Exit non-zero if we want to fail the workflow on findings\n if (conclusion === 'failure') {\n process.exit(1);\n }\n}\n","/**\n * synkro update — pull latest CLI binary + refresh hook configs and prompts.\n *\n * For now this just re-runs install (which fetches latest prompt + rewrites\n * hook scripts). The actual binary self-update would invoke the install.sh\n * shell script from get.synkro.sh — punted to v1.1 distribution work.\n */\nimport { installCommand } from './install.js';\n\nexport async function updateCommand(): Promise<void> {\n console.log('Refreshing Synkro hook configs and prompts...\\n');\n await installCommand();\n console.log('\\n✓ Synkro updated.');\n console.log('To update the CLI binary itself, run: curl -fsSL https://get.synkro.sh | bash');\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 */\nimport { existsSync, rmSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallMcpConfig } from '../installer/mcpConfig.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nexport function disconnectCommand(args: string[] = []): void {\n const purge = args.includes('--purge');\n\n console.log('Synkro disconnect starting...\\n');\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 }\n }\n\n // Also remove the Synkro MCP server entry from ~/.claude.json so the agent\n // stops calling get_guardrails during the A/B baseline.\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails server: ${mcpRemoved ? 'removed entry from ~/.claude.json' : 'no Synkro MCP 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","#!/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\n// Load env vars synchronously from a few candidate paths\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();\n if (!process.env[key]) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\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 Synkro hooks for detected agents (Claude Code, etc.)\n login Authenticate with Synkro (browser OAuth via WorkOS)\n logout Clear local credentials\n status Show current setup state\n setup-github Configure GitHub PR scanning (push secrets + workflow file)\n scan-pr Run a PR scan (used by GitHub Actions, not for direct invocation)\n update Refresh hook configs and judge prompts\n disconnect [--purge] Remove Synkro hooks from agents (--purge also removes ~/.synkro)\n help Show this message\n\nQuick start:\n $ synkro install # one-time setup\n $ synkro setup-github # enable PR scanning (optional)\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 'login':\n case 'auth': {\n const { loginCommand } = await import('./commands/login.js');\n await loginCommand(subArgs);\n break;\n }\n case 'logout': {\n const { logoutCommand } = await import('./commands/logout.js');\n logoutCommand();\n break;\n }\n case 'status': {\n const { statusCommand } = await import('./commands/status.js');\n statusCommand();\n break;\n }\n case 'setup-github': {\n const { setupGithubCommand } = await import('./commands/setupGithub.js');\n await setupGithubCommand();\n break;\n }\n case 'scan-pr': {\n const { scanPrCommand } = await import('./commands/scanPr.js');\n await scanPrCommand();\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/update.js');\n await updateCommand();\n break;\n }\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n disconnectCommand(subArgs);\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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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuS7B,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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+ThC,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsV/B,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;AAkE/B,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["cmd","existsSync","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","join","dirname","args","resolve","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","join","args","info","existsSync","readFileSync","homedir","join","CONFIG_PATH","SYNKRO_DIR","existsSync","mkdirSync","writeFileSync","join","existsSync","readFileSync","homedir","join","CONFIG_PATH","resolve","jwt","SYNKRO_DIR","execSync","args","resolve","existsSync","homedir","join","args","SYNKRO_DIR","readFileSync","existsSync","installCommand","parseArgs","loginCommand","logoutCommand","statusCommand","setupGithubCommand","scanPrCommand","updateCommand","disconnectCommand"]}
1
+ {"version":3,"sources":["../cli/installer/agentDetect.ts","../cli/installer/ccHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/hookScripts.ts","../cli/installer/graderDaemon.ts","../cli/auth/stub.ts","../cli/commands/install.ts","../cli/commands/login.ts","../cli/commands/logout.ts","../cli/commands/status.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/setupGithub.ts","../cli/commands/scanPr.ts","../cli/commands/update.ts","../cli/commands/disconnect.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';\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 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 * 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; // Absolute path to ~/.synkro/hooks/cc-bash-judge.sh\n bashFollowupScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-bash-followup.sh\n editCaptureScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-edit-capture.sh\n stopSummaryScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-stop-summary.sh\n sessionStartScriptPath: string; // Absolute path to ~/.synkro/hooks/cc-session-start.sh\n // Absolute path to ~/.synkro/hooks/cc-edit-precheck.sh — the PreToolUse\n // Edit/Write hook that POSTs proposed content to /api/v1/precheck-edit and\n // echoes the deterministic CC-hook JSON the server returns. No LLM in the\n // hook path; the agent retries with a safer version on its own inference\n // when it reads the deny reason.\n editPrecheckScriptPath: string;\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 [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 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) => !entry?.[SYNKRO_MARKER]);\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 // 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\n // PreToolUse Bash → command hook script (Cerebras-judged dangerous bash)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → command hook that calls /api/v1/precheck-edit.\n // Server cosines the proposed content against the org's Timescale rules,\n // returns the deterministic CC hook JSON shape (deny + retry guidance, or\n // empty allow). Agent reads the deny reason and retries on its own inference.\n // No LLM in the hook path — no Sonnet narration drift, no broken JSON parser.\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PostToolUse Edit/Write → command hook only (announcement + telemetry).\n // No LLM call here — the verdict already came from PreToolUse.\n settings.hooks.PostToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editCaptureScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\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 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'] 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 * 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}\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 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/**\n * Hook scripts that get written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Two scripts:\n * cc-bash-judge.sh — PreToolUse Bash hook. Reads stdin payload, parses\n * transcript context, POSTs to /v1/judge, renders systemMessage JSON.\n *\n * cc-edit-capture.sh — PostToolUse Edit/Write capture hook. Runs in parallel\n * with the prompt hook. Reads transcript for the prompt hook's verdict,\n * POSTs to /v1/events/edit-scan. Async / fire-and-forget.\n *\n * Both scripts are POSIX bash with `jq` and `curl` dependencies (standard on\n * macOS + most Linux). They fail open: if anything goes wrong, exit 0 and\n * let the user's CC session continue.\n */\n\nexport const CC_BASH_JUDGE_SCRIPT = `#!/bin/bash\n# Synkro PreToolUse Bash judge hook\n# Reads CC's hook payload from stdin, judges via Synkro gateway, returns verdict.\n# Auth: reads access_token from ~/.synkro/credentials.json, sends Authorization: Bearer.\nset -e\n\n# Load config\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\n# Fail open if not authed\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Read hook payload from stdin (CC sends a single JSON line)\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Only run on Bash tool calls\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then\n echo '{}'\n exit 0\nfi\n\nCOMMAND=$(echo \"$PAYLOAD\" | jq -r '.tool_input.command // empty' 2>/dev/null)\nif [ -z \"$COMMAND\" ]; then\n echo '{}'\n exit 0\nfi\n\n# NO hard regex gate — server-side bashShapes runs the universal pattern set\n# (including HTTP-payload destructive shapes like graphql_destructive_mutation\n# and http_method_delete) and returns a \"trivial\" fast-path verdict for boring\n# read-only commands (ls, pwd, git status, --version, etc.). Anything ambiguous\n# goes to Cerebras for grading. This closes the gap that verb-only filters miss\n# (e.g. a curl POST whose JSON body contains a destructive mutation).\n\n# Extract context from the transcript file\nTRANSCRIPT_PATH=$(echo \"$PAYLOAD\" | jq -r '.transcript_path // empty' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\n# Headless detection — when no human is in the loop, ASK is a no-op so we\n# fail-closed by upgrading high-tier findings to deny.\nPERMISSION_MODE=$(echo \"$PAYLOAD\" | jq -r '.permission_mode // empty' 2>/dev/null)\nIS_HEADLESS=0\ncase \"$PERMISSION_MODE\" in\n acceptEdits|bypassPermissions|plan|auto) IS_HEADLESS=1 ;;\nesac\nif [ \"\\${SYNKRO_HEADLESS:-0}\" = \"1\" ]; then IS_HEADLESS=1; fi\n\nUSER_INTENT=\"\"\nRECENT_USER_MESSAGES=\"[]\"\nRECENT_ACTIONS=\"[]\"\nif [ -n \"$TRANSCRIPT_PATH\" ] && [ -f \"$TRANSCRIPT_PATH\" ]; then\n # Last 5 user-role messages, oldest first. Lets the grader see consent\n # that carried over from a recent prior turn — saying \"i consent\" two\n # turns ago should not require re-prompting on this turn's command.\n RECENT_USER_MESSAGES=$(tail -400 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"user\")\n | (.message.content\n | if type == \"string\" then .\n else (map(.text? // \"\") | join(\" \"))\n end)\n | select(. != null and . != \"\")\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\n USER_INTENT=$(echo \"$RECENT_USER_MESSAGES\" | jq -r '.[-1] // \"\"' 2>/dev/null || echo \"\")\n # Recent agent actions (last 5 tool_use blocks)\n RECENT_ACTIONS=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"assistant\")\n | .message.content[]?\n | select(.type == \"tool_use\")\n | { tool: .name, input: (.input // {} | tostring | .[0:200]) }\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\nfi\n\n# Build POST body — always emit all fields (use null for empty optionals)\n# Earlier version used \\`select(length > 0)\\` which made the entire object\n# evaluate to nothing when any optional was empty. Don't do that.\nBODY=$(jq -n \\\\\n --argjson tool_input \"$TOOL_INPUT\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_user_messages \"$RECENT_USER_MESSAGES\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n kind: \"bash_judge\",\n tool_input: $tool_input,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_user_messages: $recent_user_messages,\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\n# Helper: refresh JWT via /api/auth/refresh and rewrite credentials.json.\n# Called when the gateway returns 401 (token expired).\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then\n return 1\n fi\n local refresh_resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n refresh_resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 4 2>/dev/null)\n local new_access\n new_access=$(echo \"$refresh_resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then\n return 1\n fi\n # Atomically rewrite credentials.json with the new tokens\n local new_refresh\n new_refresh=$(echo \"$refresh_resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then\n new_refresh=\"$refresh_token\"\n fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\n# Resolve tier (cached 60 min) — server is canonical via /cli/me; fallback free.\nTIER_CACHE_FILE=\"$HOME/.synkro/.tier-cache-\\${SYNKRO_USER_ID:-default}\"\nSYNKRO_INFERENCE_TIER=\"\"\nif find \"$TIER_CACHE_FILE\" -mmin -60 2>/dev/null | grep -q .; then\n SYNKRO_INFERENCE_TIER=$(cat \"$TIER_CACHE_FILE\" 2>/dev/null)\nfi\nif [ -z \"$SYNKRO_INFERENCE_TIER\" ]; then\n ME_RESP=$(curl -sS \"\\${GATEWAY_URL}/api/v1/cli/me\" -H \"Authorization: Bearer $JWT\" --max-time 2 2>/dev/null || echo \"\")\n if [ -n \"$ME_RESP\" ]; then\n SYNKRO_INFERENCE_TIER=$(echo \"$ME_RESP\" | jq -r '.tier // empty' 2>/dev/null)\n [ -n \"$SYNKRO_INFERENCE_TIER\" ] && printf '%s' \"$SYNKRO_INFERENCE_TIER\" > \"$TIER_CACHE_FILE\" 2>/dev/null || true\n fi\nfi\nSYNKRO_INFERENCE_TIER=\"\\${SYNKRO_INFERENCE_TIER:-free}\"\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (mode=bash). ───\n GRADER_PROMPT_FILE=$(mktemp -t synkro-bash-prompt.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'Proposed command: %s\\\\n' \"$COMMAND\" > \"$GRADER_PROMPT_FILE\"\n printf 'User intent: %s\\\\n' \"\\${USER_INTENT:-none stated}\" >> \"$GRADER_PROMPT_FILE\"\n printf 'Recent user messages: %s\\\\n' \"$RECENT_USER_MESSAGES\" >> \"$GRADER_PROMPT_FILE\"\n printf 'Recent actions: %s\\\\n' \"$RECENT_ACTIONS\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-bash.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode bash grade \"$HOME/.synkro/grader-primer-bash.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n V_INNER=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n if [ -z \"$V_INNER\" ] || ! echo \"$V_INNER\" | jq -e '.severity' >/dev/null 2>&1; then\n echo '{}'\n exit 0\n fi\n VERDICT=\"$V_INNER\"\nelse\n # ─── FAST TIER: server-side Cerebras grading. ───\n VERDICT=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 6 2>/dev/null || echo \"\")\n\n if echo \"$VERDICT\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n VERDICT=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 6 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\nif [ -z \"$VERDICT\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Parse verdict — fail open on any parse error\nSEVERITY=$(echo \"$VERDICT\" | jq -r '.severity // \"medium\"' 2>/dev/null)\nVERDICT_KIND=$(echo \"$VERDICT\" | jq -r '.verdict // \"warn\"' 2>/dev/null)\nREASONING=$(echo \"$VERDICT\" | jq -r '.reasoning // \"matched dangerous-verb regex\"' 2>/dev/null)\nALTERNATIVE=$(echo \"$VERDICT\" | jq -r '.alternative // \"\"' 2>/dev/null)\nCATEGORY=$(echo \"$VERDICT\" | jq -r '.category // \"destructive_command\"' 2>/dev/null)\n\n# Severity-driven surfacing:\n# critical → permissionDecision: \"deny\" — block outright\n# high → permissionDecision: \"ask\" — force user confirmation\n# medium → silent allow (echo {}) — Cerebras saw it, judged the\n# intent fits, no surface\n# low → silent allow (echo {}) — same\n#\n# The grader is fully context-aware now (intent + recent_actions + shape\n# labels), so its severity grade is trustworthy. Low/medium decisions don't\n# need to interrupt the user — surfacing them creates alert fatigue and\n# trains the user to click-through on warnings that turn out to be benign.\n\ncase \"$SEVERITY\" in\n critical)\n SYS_MSG=\"[synkro] bashGuard → critical: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n SYS_MSG=\"\\${SYS_MSG}\\\\n[synkro] suggested → \\${ALTERNATIVE}\"\n fi\n ADDITIONAL_CTX=\"Synkro safety judge flagged this command (severity: critical, category: \\${CATEGORY}). Reasoning: \\${REASONING}.\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n ADDITIONAL_CTX=\"\\${ADDITIONAL_CTX} Suggested alternative: \\${ALTERNATIVE}.\"\n fi\n PERMISSION_REASON=\"[synkro] BLOCKED — \\${REASONING}. Severity: critical. Override only if you are certain.\"\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n --arg reason \"$PERMISSION_REASON\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: $reason,\n additionalContext: $ctx\n }\n }'\n ;;\n high)\n SYS_MSG=\"[synkro] bashGuard → high: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n SYS_MSG=\"\\${SYS_MSG}\\\\n[synkro] suggested → \\${ALTERNATIVE}\"\n fi\n ADDITIONAL_CTX=\"Synkro safety judge flagged this command (severity: high, category: \\${CATEGORY}). Reasoning: \\${REASONING}.\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n ADDITIONAL_CTX=\"\\${ADDITIONAL_CTX} Suggested alternative: \\${ALTERNATIVE}.\"\n fi\n PERMISSION_REASON=\"[synkro] high: \\${REASONING}\"\n if [ -n \"$ALTERNATIVE\" ] && [ \"$ALTERNATIVE\" != \"null\" ]; then\n PERMISSION_REASON=\"\\${PERMISSION_REASON} Alternative: \\${ALTERNATIVE}\"\n fi\n # Headless? Upgrade ask → deny so we fail-closed.\n if [ \"$IS_HEADLESS\" = \"1\" ]; then DECISION=\"deny\"; else DECISION=\"ask\"; fi\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n --arg reason \"$PERMISSION_REASON\" \\\\\n --arg decision \"$DECISION\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: $decision,\n permissionDecisionReason: $reason,\n additionalContext: $ctx\n }\n }'\n ;;\n *)\n # low / medium / anything else → silent allow\n echo '{}'\n ;;\nesac\n\nexit 0\n`;\n\nexport const CC_EDIT_PRECHECK_SCRIPT = `#!/bin/bash\n# Synkro PreToolUse Edit/Write/MultiEdit/NotebookEdit pre-check hook.\n#\n# Steering-at-violation: thin shim that POSTs the proposed file content to\n# /api/v1/precheck-edit. The server cosines it against the org's active\n# agent_runtime rules in Timescale and returns the CC hook JSON shape\n# directly (permissionDecision: \"deny\" + retry guidance, or empty {} to allow).\n#\n# No LLM in this path — just embedding + cosine. Customer's CC pays for the\n# retry generation when the agent reads the denial reason and rewrites.\n# Synkro's COGS = ~$0.00001 per edit (one Gemini embedding call).\n#\n# Always exits 0 with valid JSON. Fails open on any error.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nTRANSCRIPT_PATH=$(echo \"$PAYLOAD\" | jq -r '.transcript_path // empty' 2>/dev/null)\n# Headless / non-interactive detection — when CC won't actually prompt the\n# human, our \"ask\" verdict is a no-op. Server uses these to fall back to\n# \"deny\" so we fail-closed instead of silently letting findings through.\nPERMISSION_MODE=$(echo \"$PAYLOAD\" | jq -r '.permission_mode // empty' 2>/dev/null)\nHEADLESS_FLAG=\"\\${SYNKRO_HEADLESS:-0}\"\n\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Pull conversation context from the transcript file. CC writes one JSON line\n# per message; we read the tail and extract the most recent user message + the\n# last 5 tool_use blocks. The server uses these as anchors for cosine ranking\n# AND as a suppression signal when the user explicitly asked for the unsafe\n# pattern. Same trick the bash judge uses.\nUSER_INTENT=\"\"\nRECENT_ACTIONS=\"[]\"\nif [ -n \"$TRANSCRIPT_PATH\" ] && [ -f \"$TRANSCRIPT_PATH\" ]; then\n USER_INTENT=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -r 'select(.type == \"user\") | .message.content | if type == \"string\" then . else (map(.text? // \"\") | join(\" \")) end' 2>/dev/null | tail -1 || echo \"\")\n RECENT_ACTIONS=$(tail -200 \"$TRANSCRIPT_PATH\" | jq -c -s '\n [.[]\n | select(.type == \"assistant\")\n | .message.content[]?\n | select(.type == \"tool_use\")\n | { tool: .name, input: (.input // {} | tostring | .[0:200]) }\n ] | .[-5:]' 2>/dev/null || echo \"[]\")\nfi\n\n# Read the on-disk file FIRST so the Edit/MultiEdit branches below can\n# reconstruct the full post-edit file (file_before with the diff applied).\n# Cap at 64KB. Must run before the case statement so PROPOSED can include\n# whole-file context, not just the diff hunk.\nFILE_BEFORE=\"\"\nif [ \"$TOOL_NAME\" != \"Write\" ] && [ -n \"$FILE_PATH\" ] && [ -f \"$FILE_PATH\" ]; then\n FILE_BEFORE=$(head -c 65536 \"$FILE_PATH\" 2>/dev/null || echo \"\")\nfi\n\n# Pull proposed content. Edit/MultiEdit reconstruct the full file via\n# bash parameter expansion against FILE_BEFORE; Write/NotebookEdit pass\n# the new content directly.\ncase \"$TOOL_NAME\" in\n Write)\n # Write replaces the entire file — content IS the full post-edit file.\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.content // \"\"' 2>/dev/null) ;;\n Edit|MultiEdit)\n # Reconstruct the full post-edit file by applying the diff to file_before.\n # Sending only new_string (the diff hunk) blinds the local grader to\n # violations elsewhere in the file — the grader needs whole-file context\n # to identify multi-violation edits and cross-line patterns.\n #\n # We use python (already a daemon dependency) instead of bash parameter\n # expansion because macOS ships bash 3.2, where the quoted-pattern form\n # of \\${var//PAT/REPL} leaks the quote characters into the result.\n # Python's str.replace() handles arbitrary strings cleanly. Args go via\n # env vars (not argv) so 64 KB file content doesn't trip ARG_MAX limits.\n if [ -n \"$FILE_BEFORE\" ] && command -v python3 >/dev/null 2>&1; then\n PROPOSED=$(FILE_BEFORE_LITERAL=\"$FILE_BEFORE\" TOOL_INPUT_LITERAL=\"$TOOL_INPUT\" python3 -c '\nimport os, json, sys\nfb = os.environ.get(\"FILE_BEFORE_LITERAL\", \"\")\nti = json.loads(os.environ.get(\"TOOL_INPUT_LITERAL\", \"{}\"))\nresult = fb\nif \"old_string\" in ti and \"new_string\" in ti:\n if ti[\"old_string\"]:\n result = result.replace(ti[\"old_string\"], ti[\"new_string\"], 1)\nelif \"edits\" in ti and isinstance(ti[\"edits\"], list):\n for e in ti[\"edits\"]:\n old = e.get(\"old_string\", \"\") if isinstance(e, dict) else \"\"\n new = e.get(\"new_string\", \"\") if isinstance(e, dict) else \"\"\n if old:\n result = result.replace(old, new, 1)\nsys.stdout.write(result)\n' 2>/dev/null)\n fi\n # Fall back to the diff-hunk-only shape if reconstruction failed or\n # file_before was empty (new file via Edit, etc.).\n if [ -z \"$PROPOSED\" ]; then\n if [ \"$TOOL_NAME\" = \"MultiEdit\" ]; then\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '[.edits[]?.new_string // \"\"] | join(\"\\\\n\\\\n--- chunk ---\\\\n\\\\n\")' 2>/dev/null)\n else\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.new_string // \"\"' 2>/dev/null)\n fi\n fi\n ;;\n NotebookEdit)\n PROPOSED=$(echo \"$TOOL_INPUT\" | jq -r '.new_source // \"\"' 2>/dev/null) ;;\nesac\n\nif [ -z \"$PROPOSED\" ]; then\n echo '{}'\n exit 0\nfi\n\nDIFF_FIELD=$(echo \"$TOOL_INPUT\" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null)\nif [ -z \"$DIFF_FIELD\" ] || [ \"$DIFF_FIELD\" = \"null\" ] || [ \"$DIFF_FIELD\" = \"{}\" ]; then\n DIFF_FIELD=\"null\"\nfi\n\n# FILE_BEFORE was read above (before the case statement) so this body\n# construction just references it; the duplicate read used to live here.\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg content \"$PROPOSED\" \\\\\n --arg file_before \"$FILE_BEFORE\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg permission_mode \"$PERMISSION_MODE\" \\\\\n --arg headless_flag \"$HEADLESS_FLAG\" \\\\\n '{\n file_path: $file_path,\n tool_name: $tool_name,\n content: $content,\n file_before: (if ($file_before | length) > 0 then $file_before else null end),\n diff: $diff,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),\n headless: ($headless_flag == \"1\")\n }')\n\n# Refresh JWT on 401 (mirrors the bash judge pattern).\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then return 1; fi\n local resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 3 2>/dev/null)\n local new_access\n new_access=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then return 1; fi\n local new_refresh\n new_refresh=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then new_refresh=\"$refresh_token\"; fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (Python helper).\n # The daemon at \\$HOME/.synkro/bin/grader_daemon.py keeps one\n # \\`claude --print --input-format=stream-json\\` process alive, primed once\n # with the role/format. Hook ships a thin grading prompt over Unix socket\n # and gets the verdict text back. Steady-state ~1.5–3s per grading vs\n # ~14s for cold \\`claude --print\\`. Falls back to direct \\`claude --print\\`\n # if the daemon binary or primer is missing.\n GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'File: %s\\\\n' \"$FILE_PATH\" > \"$GRADER_PROMPT_FILE\"\n printf 'User intent: %s\\\\n\\\\n' \"\\${USER_INTENT:-none stated}\" >> \"$GRADER_PROMPT_FILE\"\n printf 'Diff:\\\\n' >> \"$GRADER_PROMPT_FILE\"\n printf '%s\\\\n' \"$PROPOSED\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-edit.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode edit grade \"$HOME/.synkro/grader-primer-edit.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n\n VERDICT_JSON=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n\n if [ -z \"$VERDICT_JSON\" ]; then\n echo '{}'\n exit 0\n fi\n\n LOCAL_BODY=$(jq -n \\\\\n --argjson verdict \"$VERDICT_JSON\" \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg content \"$PROPOSED\" \\\\\n --arg file_before \"$FILE_BEFORE\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg user_intent \"$USER_INTENT\" \\\\\n --argjson recent_actions \"$RECENT_ACTIONS\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n --arg permission_mode \"$PERMISSION_MODE\" \\\\\n --arg headless_flag \"$HEADLESS_FLAG\" \\\\\n '{\n verdict: $verdict,\n file_path: $file_path,\n tool_name: $tool_name,\n content: $content,\n file_before: (if ($file_before | length) > 0 then $file_before else null end),\n diff: $diff,\n user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),\n recent_actions: $recent_actions,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end),\n permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),\n headless: ($headless_flag == \"1\")\n }')\n\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$LOCAL_BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$LOCAL_BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n fi\n fi\nelse\n # ─── FAST TIER: server-side Cerebras grading (~500ms) ───\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 3 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 3 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\n# Server returns the literal CC hook JSON shape ({} for allow, or\n# hookSpecificOutput.permissionDecision: \"deny\" + reason). If the call failed\n# entirely or returned non-JSON, fail open with empty {}.\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Cheap validation — only forward if it parses as a JSON object.\nif ! echo \"$RESP\" | jq -e 'type == \"object\"' >/dev/null 2>&1; then\n echo '{}'\n exit 0\nfi\n\necho \"$RESP\"\nexit 0\n`;\n\nexport const CC_EDIT_CAPTURE_SCRIPT = `#!/bin/bash\n# Synkro PostToolUse Edit/Write afterFileEdit hook.\n#\n# Reads the file from disk after the edit lands, sends to /api/v1/judge-edit\n# for Cerebras grading against the org's rules. On ok=false, emits a system\n# message that surfaces inline in CC (\"[synkro] afterFileEdit → Finding: …\").\n# The agent reads the finding via transcript and re-edits on its own\n# inference — same retry pattern as PreToolUse precheck, looser/asynchronous.\n#\n# Complementary to the PreToolUse precheck: precheck blocks unsafe writes\n# before disk; this hook catches issues that only become visible AFTER the\n# edit lands (cross-file shapes, real disk state, post-edit semantic effects).\n#\n# Always exits 0 with valid JSON — never breaks CC's flow.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\n\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nif [ -z \"$FILE_PATH\" ] || [ ! -f \"$FILE_PATH\" ]; then\n echo '{}'\n exit 0\nfi\n\nBASENAME=$(basename \"$FILE_PATH\")\n\n# Read post-edit file content (cap 64KB).\nFILE_CONTENT=$(head -c 65536 \"$FILE_PATH\" 2>/dev/null || echo \"\")\nif [ -z \"$FILE_CONTENT\" ]; then\n echo '{}'\n exit 0\nfi\n\nDIFF_FIELD=$(echo \"$TOOL_INPUT\" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null)\nif [ -z \"$DIFF_FIELD\" ] || [ \"$DIFF_FIELD\" = \"null\" ] || [ \"$DIFF_FIELD\" = \"{}\" ]; then\n DIFF_FIELD=\"null\"\nfi\n\nBODY=$(jq -n \\\\\n --arg file_path \"$FILE_PATH\" \\\\\n --arg content \"$FILE_CONTENT\" \\\\\n --argjson diff \"$DIFF_FIELD\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n file_path: $file_path,\n content: $content,\n diff: $diff,\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\nrefresh_jwt() {\n local refresh_token\n refresh_token=$(jq -r '.refresh_token // empty' \"$CREDS_PATH\" 2>/dev/null)\n if [ -z \"$refresh_token\" ]; then return 1; fi\n local resp\n local refresh_body\n refresh_body=$(jq -n --arg rt \"$refresh_token\" '{refresh_token:$rt}')\n resp=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/auth/refresh\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"$refresh_body\" \\\\\n --max-time 3 2>/dev/null)\n local new_access\n new_access=$(echo \"$resp\" | jq -r '.access_token // empty' 2>/dev/null)\n if [ -z \"$new_access\" ]; then return 1; fi\n local new_refresh\n new_refresh=$(echo \"$resp\" | jq -r '.refresh_token // empty' 2>/dev/null)\n if [ -z \"$new_refresh\" ]; then new_refresh=\"$refresh_token\"; fi\n local tmp=\"\\${CREDS_PATH}.synkro.tmp\"\n jq --arg at \"$new_access\" --arg rt \"$new_refresh\" \\\\\n '. + {access_token: $at, refresh_token: $rt}' \\\\\n \"$CREDS_PATH\" > \"$tmp\" 2>/dev/null && mv \"$tmp\" \"$CREDS_PATH\"\n JWT=\"$new_access\"\n return 0\n}\n\n# Fire-and-forget correction-followup. PostToolUse means the edit landed →\n# flip any pending precheck-correction for this tool_use_id to 'allow'.\nif [ -n \"$SESSION_ID\" ] && [ -n \"$TOOL_USE_ID\" ]; then\n FOLLOWUP_BODY=$(jq -n \\\\\n --arg sid \"$SESSION_ID\" \\\\\n --arg tid \"$TOOL_USE_ID\" \\\\\n '{session_id: $sid, tool_use_id: $tid, decision: \"allow\"}')\n curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$FOLLOWUP_BODY\" \\\\\n --max-time 2 \\\\\n >/dev/null 2>&1 || true\nfi\n\n# Resolve tier (cached 60 min) — same logic as the other hooks.\nTIER_CACHE_FILE=\"$HOME/.synkro/.tier-cache-\\${SYNKRO_USER_ID:-default}\"\nSYNKRO_INFERENCE_TIER=\"\"\nif find \"$TIER_CACHE_FILE\" -mmin -60 2>/dev/null | grep -q .; then\n SYNKRO_INFERENCE_TIER=$(cat \"$TIER_CACHE_FILE\" 2>/dev/null)\nfi\nif [ -z \"$SYNKRO_INFERENCE_TIER\" ]; then\n ME_RESP=$(curl -sS \"\\${GATEWAY_URL}/api/v1/cli/me\" -H \"Authorization: Bearer $JWT\" --max-time 2 2>/dev/null || echo \"\")\n if [ -n \"$ME_RESP\" ]; then\n SYNKRO_INFERENCE_TIER=$(echo \"$ME_RESP\" | jq -r '.tier // empty' 2>/dev/null)\n [ -n \"$SYNKRO_INFERENCE_TIER\" ] && printf '%s' \"$SYNKRO_INFERENCE_TIER\" > \"$TIER_CACHE_FILE\" 2>/dev/null || true\n fi\nfi\nSYNKRO_INFERENCE_TIER=\"\\${SYNKRO_INFERENCE_TIER:-free}\"\n\nif [ \"$SYNKRO_INFERENCE_TIER\" = \"free\" ] && command -v claude >/dev/null 2>&1; then\n # ─── FREE TIER: grade via the persistent claude daemon (mode=edit). ───\n GRADER_PROMPT_FILE=$(mktemp -t synkro-edit-capture.XXXXXX)\n trap \"rm -f \\\\\"$GRADER_PROMPT_FILE\\\\\"\" EXIT\n printf 'File: %s\\\\n\\\\nContent:\\\\n' \"$FILE_PATH\" > \"$GRADER_PROMPT_FILE\"\n printf '%s\\\\n' \"$FILE_CONTENT\" >> \"$GRADER_PROMPT_FILE\"\n\n if [ -x \"$HOME/.synkro/bin/grader_daemon.py\" ] && [ -f \"$HOME/.synkro/grader-primer-edit.txt\" ] && command -v python3 >/dev/null 2>&1; then\n CC_RESP=$(python3 \"$HOME/.synkro/bin/grader_daemon.py\" --mode edit grade \"$HOME/.synkro/grader-primer-edit.txt\" < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n else\n CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < \"$GRADER_PROMPT_FILE\" 2>/dev/null || echo \"\")\n fi\n V_INNER=$(printf '%s' \"$CC_RESP\" | tr '\\\\n' ' ' | grep -oE '<synkro-verdict>[^<]*</synkro-verdict>' | tail -1 | sed -E 's|^<synkro-verdict>||; s|</synkro-verdict>$||')\n if [ -n \"$V_INNER\" ] && echo \"$V_INNER\" | jq -e 'has(\"ok\")' >/dev/null 2>&1; then\n RESP=\"$V_INNER\"\n else\n RESP=\"\"\n fi\nelse\n # ─── FAST TIER: server-side Cerebras (~500ms). ───\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n\n if echo \"$RESP\" | grep -qE '\"detail\":\"Token has expired|\"detail\":\"Invalid or expired token'; then\n if refresh_jwt; then\n RESP=$(curl -sS -X POST \"\\${GATEWAY_URL}/api/v1/judge-edit\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 5 2>/dev/null || echo \"\")\n fi\n fi\nfi\n\nif [ -z \"$RESP\" ] || ! echo \"$RESP\" | jq -e 'type == \"object\"' >/dev/null 2>&1; then\n if [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → scanned \\${BASENAME} (grader unavailable)\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\n fi\n echo '{}'\n exit 0\nfi\n\nOK=$(echo \"$RESP\" | jq -r 'if .ok == false then \"false\" else \"true\" end' 2>/dev/null)\nSEVERITY=$(echo \"$RESP\" | jq -r '.severity // \"low\"' 2>/dev/null)\nCATEGORY=$(echo \"$RESP\" | jq -r '.category // \"unspecified\"' 2>/dev/null)\nREASON=$(echo \"$RESP\" | jq -r '.reason // \"\"' 2>/dev/null)\n\nif [ \"$OK\" = \"false\" ] && [ -n \"$REASON\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → Finding in \\${BASENAME}: \\${REASON}\"\n ADDITIONAL_CTX=\"Synkro post-edit grader flagged \\${BASENAME} (severity: \\${SEVERITY}, category: \\${CATEGORY}). Re-edit the file applying the retry guidance: \\${REASON}\"\n jq -n \\\\\n --arg sys_msg \"$SYS_MSG\" \\\\\n --arg ctx \"$ADDITIONAL_CTX\" \\\\\n '{\n systemMessage: $sys_msg,\n hookSpecificOutput: {\n hookEventName: \"PostToolUse\",\n additionalContext: $ctx\n }\n }'\n exit 0\nfi\n\nif [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → no issues in \\${BASENAME}\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\nfi\n\necho '{}'\nexit 0\n`;\n\n// ────────────────────────────────────────────────────────────────────────\n// Legacy CC_EDIT_CAPTURE_SCRIPT (telemetry-only) — kept here briefly for\n// reference. The new version above replaces the dedicated /v1/events/edit-scan\n// telemetry call with a real grading call against /api/v1/judge-edit. The\n// judge-edit route fires the same inngest events for dashboard rollups, so\n// no telemetry is lost.\n// ────────────────────────────────────────────────────────────────────────\n\nconst _CC_EDIT_CAPTURE_SCRIPT_LEGACY = `#!/bin/bash\n# Synkro PostToolUse Edit/Write capture hook — legacy telemetry-only version.\n#\n# Architecture (as designed):\n# - The actual security verdict comes from a SIBLING type:'prompt' hook\n# in ccHookConfig.ts. That hook runs CC's own Sonnet inference on the\n# customer's CC subscription (zero cost to Synkro) using the editScanPrompt\n# fetched at install time. It returns {ok, reason} and CC feeds the reason\n# back to the agent on ok:false so the agent re-edits.\n#\n# - This command hook is a thin parallel: it (a) fires-and-forgets a POST\n# to /v1/events/edit-scan so we capture the edit for backend logs/dashboard\n# and (b) optionally emits a \"[synkro] afterFileEdit → scanned <file>\"\n# systemMessage when SYNKRO_VERBOSE=1. NO LLM CALL FROM US.\n#\n# Always exits 0 with valid JSON — never affects CC's flow.\nset -e\n\n# Load config\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nif [ -z \"$PAYLOAD\" ]; then\n echo '{}'\n exit 0\nfi\n\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\ncase \"$TOOL_NAME\" in\n Edit|Write|MultiEdit|NotebookEdit) ;;\n *) echo '{}'; exit 0 ;;\nesac\n\nTOOL_INPUT=$(echo \"$PAYLOAD\" | jq -c '.tool_input // {}' 2>/dev/null)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\n\n# file_path is on Edit/Write/MultiEdit; .notebook_path on NotebookEdit.\n# We never read this from disk — the verdict comes from the sibling type:'prompt'\n# hook which runs CC inference internally. We only use the path for the\n# user-visible \"scanned <basename>\" message.\nFILE_PATH=$(echo \"$TOOL_INPUT\" | jq -r '.file_path // .notebook_path // .path // empty' 2>/dev/null)\nBASENAME=\"\"\nif [ -n \"$FILE_PATH\" ]; then\n BASENAME=$(basename \"$FILE_PATH\")\nfi\n\n# Build telemetry body — sent fire-and-forget to /v1/events/edit-scan so the\n# dashboard sees every edit. We do NOT include file content here; the type:'prompt'\n# sibling hook is the one running CC inference and producing the verdict, and\n# its result is fed to the agent by CC directly.\nTELEMETRY_BODY=$(jq -n \\\\\n --argjson tool_input \"$TOOL_INPUT\" \\\\\n --arg tool_name \"$TOOL_NAME\" \\\\\n --arg session_id \"$SESSION_ID\" \\\\\n --arg tool_use_id \"$TOOL_USE_ID\" \\\\\n --arg cwd \"$CWD\" \\\\\n '{\n tool_name: $tool_name,\n tool_input: $tool_input,\n verdict: { ok: true, reason: \"\" },\n session_id: (if ($session_id | length) > 0 then $session_id else null end),\n tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),\n cwd: (if ($cwd | length) > 0 then $cwd else null end)\n }')\n\n# Fire-and-forget telemetry POST. Capped tight so a slow API can't stall the\n# agent. We do NOT call any LLM or judge endpoint from here — that's the\n# type:'prompt' sibling hook's job.\ncurl -sS -X POST \"\\${GATEWAY_URL}/api/v1/events/edit-scan\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$TELEMETRY_BODY\" \\\\\n --max-time 1 \\\\\n >/dev/null 2>&1 || true\n\n# Inline announcement, opt-in via SYNKRO_VERBOSE=1 in ~/.synkro/config.env.\n# The user sees: \"[synkro] afterFileEdit → scanned <file>\"\n# The actual verdict ([synkro] Finding: …) is rendered by CC itself when the\n# sibling type:'prompt' hook returns ok:false — its reason text is fed to\n# the agent and visible in the conversation.\nif [ \"\\${SYNKRO_VERBOSE:-0}\" = \"1\" ] && [ -n \"$BASENAME\" ]; then\n SYS_MSG=\"[synkro] afterFileEdit → scanned \\${BASENAME} (CC inference verdict pending)\"\n jq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\n exit 0\nfi\n\necho '{}'\nexit 0\n`;\n\nexport const CC_STOP_SUMMARY_SCRIPT = `#!/bin/bash\n# Synkro Stop hook — emits \"[synkro] stop → N findings: X auto-fixed, Y open\"\n# as a final summary line when the CC session ends. Reads guard_checks rows\n# for the session via /api/v1/cli/session-summary.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nif [ -z \"$SESSION_ID\" ]; then\n echo '{}'\n exit 0\nfi\n\n# Tight timeout — the user already finished their session, don't make them wait.\nRESP=$(curl -sS -G \"\\${GATEWAY_URL}/api/v1/cli/session-summary\" \\\\\n --data-urlencode \"session_id=$SESSION_ID\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n --max-time 2 2>/dev/null || echo \"\")\n\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\nEDITS=$(echo \"$RESP\" | jq -r '.edits_scanned // 0' 2>/dev/null)\nFINDINGS=$(echo \"$RESP\" | jq -r '.findings // 0' 2>/dev/null)\nAUTO_FIXED=$(echo \"$RESP\" | jq -r '.auto_fixed // 0' 2>/dev/null)\nOPEN=$(echo \"$RESP\" | jq -r '.open // 0' 2>/dev/null)\n\n# Stay silent if the session never touched files we scanned.\nif [ \"$EDITS\" = \"0\" ] || [ -z \"$EDITS\" ]; then\n echo '{}'\n exit 0\nfi\n\nif [ \"$FINDINGS\" = \"0\" ] || [ -z \"$FINDINGS\" ]; then\n SYS_MSG=\"[synkro] stop → 0 issues across \\${EDITS} edit(s), session complete\"\nelse\n SYS_MSG=\"[synkro] stop → \\${FINDINGS} finding(s): \\${AUTO_FIXED} auto-fixed, \\${OPEN} open\"\nfi\n\njq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\nexit 0\n`;\n\nexport const CC_SESSION_START_SCRIPT = `#!/bin/bash\n# Synkro SessionStart hook — when the user opens a fresh CC session in a repo\n# we have findings on, surface \"[synkro] session start → N open finding(s) in\n# this repo\" so they have context. Silent when there's nothing to report.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then\n echo '{}'\n exit 0\nfi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then\n echo '{}'\n exit 0\nfi\n\nPAYLOAD=$(cat)\nCWD=$(echo \"$PAYLOAD\" | jq -r '.cwd // empty' 2>/dev/null)\nif [ -z \"$CWD\" ]; then\n echo '{}'\n exit 0\nfi\n\nRESP=$(curl -sS -G \"\\${GATEWAY_URL}/api/v1/cli/session-context\" \\\\\n --data-urlencode \"cwd=$CWD\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n --max-time 2 2>/dev/null || echo \"\")\n\nif [ -z \"$RESP\" ]; then\n echo '{}'\n exit 0\nfi\n\nOPEN=$(echo \"$RESP\" | jq -r '.open_count // 0' 2>/dev/null)\nif [ \"$OPEN\" = \"0\" ] || [ -z \"$OPEN\" ]; then\n echo '{}'\n exit 0\nfi\n\nif [ \"$OPEN\" = \"1\" ]; then\n SYS_MSG=\"[synkro] session start → 1 open finding in this repo from a prior session\"\nelse\n SYS_MSG=\"[synkro] session start → \\${OPEN} open findings in this repo from prior sessions\"\nfi\n\njq -n --arg sys_msg \"$SYS_MSG\" '{ systemMessage: $sys_msg }'\nexit 0\n`;\n\n// PostToolUse Bash followup — fires after the agent actually executed a Bash\n// command. The PreToolUse bash judge persists a 'pending' precheck_corrections\n// row when the verdict was high/critical (severity gate); this hook flips that\n// row to 'allow' once the user clicked through and the command ran. Without\n// this, all bash precedents stay 'pending' forever and the dashboard's\n// \"approved vs rejected\" trendline is blank.\nexport const CC_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash\n# Synkro PostToolUse Bash hook — minimal correction-followup fire.\n# No grading happens here; verdict already came from PreToolUse. This is just\n# the \"user approved + agent ran it\" capture.\nset -e\n\nCONFIG_FILE=\"$HOME/.synkro/config.env\"\nif [ -f \"$CONFIG_FILE\" ]; then\n set -a\n # shellcheck disable=SC1090\n . \"$CONFIG_FILE\"\n set +a\nfi\n\nGATEWAY_URL=\"\\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}\"\nCREDS_PATH=\"\\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}\"\n\nif [ ! -f \"$CREDS_PATH\" ]; then echo '{}'; exit 0; fi\nJWT=$(jq -r '.access_token // empty' \"$CREDS_PATH\" 2>/dev/null)\nif [ -z \"$JWT\" ]; then echo '{}'; exit 0; fi\n\nPAYLOAD=$(cat)\nTOOL_NAME=$(echo \"$PAYLOAD\" | jq -r '.tool_name // empty' 2>/dev/null)\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then echo '{}'; exit 0; fi\n\nSESSION_ID=$(echo \"$PAYLOAD\" | jq -r '.session_id // empty' 2>/dev/null)\nTOOL_USE_ID=$(echo \"$PAYLOAD\" | jq -r '.tool_use_id // empty' 2>/dev/null)\nif [ -z \"$SESSION_ID\" ] || [ -z \"$TOOL_USE_ID\" ]; then echo '{}'; exit 0; fi\n\nBODY=$(jq -n --arg sid \"$SESSION_ID\" --arg tid \"$TOOL_USE_ID\" \\\\\n '{session_id: $sid, tool_use_id: $tid, decision: \"allow\"}')\n\ncurl -sS -X POST \"\\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"Authorization: Bearer $JWT\" \\\\\n -d \"$BODY\" \\\\\n --max-time 2 \\\\\n >/dev/null 2>&1 || true\n\necho '{}'\nexit 0\n`;\n","// :)\n/**\n * Synkro grader daemon — embedded as string constants so `synkro install`\n * can write them to `~/.synkro/bin/grader_daemon.py` and\n * `~/.synkro/grader-primer-edit.txt` without bundling extra files.\n *\n * The daemon keeps one `claude --print --input-format=stream-json` process\n * alive, primed once with the role + format. Hook scripts ship thin grading\n * prompts to it via Unix socket and get back the assistant's text. After\n * one ~3.5s boot tax, gradings run at 1.5–3s steady-state vs ~14s for cold\n * `claude --print`.\n *\n * Session bloat is bounded — daemon rotates the underlying claude\n * subprocess every 30 calls or 1 hour. Rotation eats a one-time ~5s primer\n * cost; calls in between are fast.\n */\n\nexport const GRADER_DAEMON_PY = `#!/usr/bin/env python3\n\"\"\"\nSynkro grader daemon — long-lived \\`claude --print\\` process with stream-json\nIPC, fronted by a Unix socket. Hook scripts ship grading prompts to it; it\nreturns the assistant's response text. ONE CC startup (~3.5s) amortizes\nacross N gradings.\n\nSession bloat is bounded: the daemon rotates its claude subprocess every\nROTATION_CALLS (default 30) gradings or ROTATION_AGE_SEC (default 1h),\nwhichever comes first. Each rotation eats a one-time ~5s primer cost; calls\nin between target ~2-3s steady-state.\n\nCommands:\n start [primer-path] - bring up daemon if not running\n grade [primer-path] - read prompt from stdin, write verdict text to stdout\n stop - terminate daemon\n status - print \"running\"/\"stopped\"\n\"\"\"\n\nimport os, sys, json, socket, time, atexit, signal, fcntl, re\nimport subprocess, threading\nfrom pathlib import Path\n\n# Each \"mode\" gets its own daemon process: separate socket, pid, log.\n# Modes: \"edit\" (precheck + post-edit, schema {ok, severity, ...}) and \"bash\"\n# (schema {verdict, severity, ...}). Selected via --mode <name>; default \"edit\".\nALLOWED_MODE_RE = re.compile(r\"^[a-z][a-z0-9_-]{0,30}$\")\nDAEMON_BASE = Path.home() / \".synkro\" / \"daemon\"\nDAEMON_BASE.mkdir(parents=True, exist_ok=True, mode=0o700)\n\ndef mode_paths(mode):\n d = DAEMON_BASE / mode\n d.mkdir(parents=True, exist_ok=True, mode=0o700)\n return d / \"daemon.pid\", d / \"daemon.sock\", d / \"daemon.log\"\n\nMODE = \"edit\"\nPID_FILE, SOCK_PATH, LOG_FILE = mode_paths(MODE)\n\nROTATION_CALLS = int(os.environ.get(\"SYNKRO_DAEMON_ROTATE_CALLS\", \"30\"))\nROTATION_AGE_SEC = int(os.environ.get(\"SYNKRO_DAEMON_ROTATE_AGE\", \"3600\"))\nGRADE_TIMEOUT_SEC = 60\nDEFAULT_MODEL = os.environ.get(\"SYNKRO_DAEMON_MODEL\", \"claude-sonnet-4-6\")\nMAX_PROMPT_BYTES = 4 * 1024 * 1024\n\n\ndef log(msg):\n try:\n with open(LOG_FILE, \"a\") as f:\n f.write(f\"[{time.strftime('%H:%M:%S')} pid={os.getpid()}] {msg}\\\\n\")\n except Exception:\n pass\n\n\nclass GraderDaemon:\n def __init__(self, primer):\n self.primer = primer or \"\"\n self.proc = None\n self.calls = 0\n self.start_time = 0.0\n self.lock = threading.Lock()\n self._spawn()\n\n def _spawn(self):\n if self.proc and self.proc.poll() is None:\n try: self.proc.terminate(); self.proc.wait(timeout=3)\n except Exception: self.proc.kill()\n log(\"spawning claude subprocess\")\n self.proc = subprocess.Popen(\n [\n \"claude\", \"--print\", \"--model\", DEFAULT_MODEL,\n \"--input-format=stream-json\",\n \"--output-format=stream-json\",\n \"--verbose\",\n \"--no-session-persistence\",\n ],\n stdin=subprocess.PIPE, stdout=subprocess.PIPE,\n stderr=subprocess.DEVNULL, text=True, bufsize=1,\n )\n if self.primer:\n self._send(self.primer)\n primer_resp = self._recv()\n log(f\"primer ack: {primer_resp[:80]!r}\")\n self.calls = 0\n self.start_time = time.time()\n\n def _send(self, text):\n msg = json.dumps({\n \"type\": \"user\",\n \"message\": {\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": text}]},\n \"parent_tool_use_id\": None,\n \"session_id\": \"\",\n })\n try:\n self.proc.stdin.write(msg + \"\\\\n\")\n self.proc.stdin.flush()\n except (BrokenPipeError, OSError) as e:\n log(f\"send broke: {e}; respawn\")\n self._spawn()\n self.proc.stdin.write(msg + \"\\\\n\")\n self.proc.stdin.flush()\n\n def _recv(self):\n acc = []\n deadline = time.time() + GRADE_TIMEOUT_SEC\n while True:\n if time.time() > deadline:\n log(\"recv timeout; respawn\")\n self._spawn()\n return \"\"\n line = self.proc.stdout.readline()\n if not line:\n log(\"subprocess closed stdout; respawn\")\n self._spawn()\n return \"\"\n try:\n obj = json.loads(line)\n except json.JSONDecodeError:\n continue\n t = obj.get(\"type\")\n if t == \"assistant\":\n for c in obj.get(\"message\", {}).get(\"content\", []):\n if c.get(\"type\") == \"text\":\n acc.append(c[\"text\"])\n elif t == \"result\":\n return \"\".join(acc)\n\n def grade(self, prompt):\n with self.lock:\n age = time.time() - self.start_time\n if self.calls >= ROTATION_CALLS or age >= ROTATION_AGE_SEC:\n log(f\"rotating: calls={self.calls} age={age:.0f}s\")\n self._spawn()\n t0 = time.time()\n self._send(prompt)\n resp = self._recv()\n elapsed = (time.time() - t0) * 1000\n self.calls += 1\n log(f\"grade #{self.calls} elapsed={elapsed:.0f}ms resp_chars={len(resp)}\")\n return resp\n\n\ndef serve(primer):\n pid_fd = os.open(str(PID_FILE), os.O_RDWR | os.O_CREAT, 0o644)\n try:\n fcntl.flock(pid_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)\n except BlockingIOError:\n log(\"another daemon already holds the pid file; exiting\")\n sys.exit(0)\n os.ftruncate(pid_fd, 0)\n os.write(pid_fd, f\"{os.getpid()}\\\\n\".encode())\n os.fsync(pid_fd)\n\n daemon = GraderDaemon(primer)\n\n if SOCK_PATH.exists():\n SOCK_PATH.unlink()\n sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n sock.bind(str(SOCK_PATH))\n sock.listen(8)\n os.chmod(SOCK_PATH, 0o600)\n\n def cleanup(*_):\n try: SOCK_PATH.unlink()\n except Exception: pass\n try: PID_FILE.unlink()\n except Exception: pass\n try: daemon.proc and daemon.proc.terminate()\n except Exception: pass\n sys.exit(0)\n signal.signal(signal.SIGTERM, cleanup)\n signal.signal(signal.SIGINT, cleanup)\n\n log(f\"daemon ready model={DEFAULT_MODEL} sock={SOCK_PATH}\")\n\n while True:\n try:\n conn, _ = sock.accept()\n threading.Thread(target=_handle_conn, args=(conn, daemon), daemon=True).start()\n except Exception as e:\n log(f\"accept error: {e}\")\n time.sleep(0.1)\n\n\ndef _handle_conn(conn, daemon):\n try:\n with conn:\n length_bytes = b\"\"\n while len(length_bytes) < 8:\n chunk = conn.recv(8 - len(length_bytes))\n if not chunk: return\n length_bytes += chunk\n length = int.from_bytes(length_bytes, \"big\")\n if length <= 0 or length > MAX_PROMPT_BYTES:\n log(f\"reject oversized prompt length={length}\")\n return\n prompt = b\"\"\n while len(prompt) < length:\n chunk = conn.recv(min(65536, length - len(prompt)))\n if not chunk: break\n prompt += chunk\n response = daemon.grade(prompt.decode(\"utf-8\", errors=\"replace\"))\n resp_bytes = response.encode(\"utf-8\")\n conn.sendall(len(resp_bytes).to_bytes(8, \"big\"))\n conn.sendall(resp_bytes)\n except Exception as e:\n log(f\"conn error: {e}\")\n\n\ndef daemon_running():\n if not SOCK_PATH.exists():\n return False\n try:\n s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n s.settimeout(0.5)\n s.connect(str(SOCK_PATH))\n s.close()\n return True\n except Exception:\n return False\n\n\ndef ensure_daemon_running(primer_path):\n if daemon_running():\n return True\n primer = Path(primer_path).read_text() if primer_path else \"\"\n pid = os.fork()\n if pid == 0:\n os.setsid()\n pid2 = os.fork()\n if pid2 == 0:\n null = os.open(os.devnull, os.O_RDWR)\n os.dup2(null, 0); os.dup2(null, 1); os.dup2(null, 2)\n serve(primer)\n else:\n os._exit(0)\n else:\n os.waitpid(pid, 0)\n for _ in range(150):\n time.sleep(0.1)\n if daemon_running():\n return True\n return False\n\n\ndef client_grade(prompt):\n s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n s.settimeout(GRADE_TIMEOUT_SEC + 5)\n s.connect(str(SOCK_PATH))\n pb = prompt.encode(\"utf-8\")\n s.sendall(len(pb).to_bytes(8, \"big\"))\n s.sendall(pb)\n length_bytes = b\"\"\n while len(length_bytes) < 8:\n chunk = s.recv(8 - len(length_bytes))\n if not chunk: return \"\"\n length_bytes += chunk\n length = int.from_bytes(length_bytes, \"big\")\n resp = b\"\"\n while len(resp) < length:\n chunk = s.recv(min(65536, length - len(resp)))\n if not chunk: break\n resp += chunk\n s.close()\n return resp.decode(\"utf-8\", errors=\"replace\")\n\n\ndef main():\n global MODE, PID_FILE, SOCK_PATH, LOG_FILE\n args = list(sys.argv[1:])\n if len(args) >= 2 and args[0] == \"--mode\":\n candidate = args[1]\n if not ALLOWED_MODE_RE.match(candidate):\n print(f\"invalid mode: {candidate}\", file=sys.stderr); sys.exit(1)\n MODE = candidate\n PID_FILE, SOCK_PATH, LOG_FILE = mode_paths(MODE)\n args = args[2:]\n\n if len(args) < 1:\n print(\"usage: grader_daemon.py [--mode <name>] {start|grade|stop|status} [primer-path]\", file=sys.stderr)\n sys.exit(1)\n cmd = args[0]\n primer_path = args[1] if len(args) > 1 else None\n\n if cmd == \"start\":\n if ensure_daemon_running(primer_path):\n print(\"daemon up\")\n else:\n print(\"daemon failed to start\", file=sys.stderr); sys.exit(1)\n elif cmd == \"grade\":\n ensure_daemon_running(primer_path)\n prompt = sys.stdin.read()\n try:\n print(client_grade(prompt), end=\"\")\n except Exception as e:\n print(f\"daemon-error: {e}\", file=sys.stderr); sys.exit(2)\n elif cmd == \"stop\":\n if PID_FILE.exists():\n try:\n pid = int(PID_FILE.read_text().strip())\n os.kill(pid, signal.SIGTERM)\n print(f\"sent SIGTERM to {pid}\")\n except Exception: pass\n for p in (SOCK_PATH, PID_FILE):\n try: p.unlink()\n except Exception: pass\n elif cmd == \"status\":\n print(\"running\" if daemon_running() else \"stopped\")\n else:\n print(f\"unknown command: {cmd}\", file=sys.stderr); sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n`;\n\nexport const GRADER_PRIMER_EDIT = `You are Synkro's security pre-check grader. You will be given proposed file diffs from an AI coding agent. For each one, decide whether it has security issues — possibly multiple distinct ones in the same diff.\n\nOUTPUT RULES — strictest possible, no exceptions:\n\n1. NO reasoning. NO preamble. NO commentary.\n2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.\n3. JSON shape:\n {\"ok\": true | false,\n \"violations\": [\n {\"rule_id\": \"<short snake_case slug, e.g. no-hardcoded-secrets>\",\n \"severity\": \"low\" | \"medium\" | \"high\" | \"critical\",\n \"category\": \"<short snake_case>\",\n \"reason\": \"<= 25 words, file:line + issue + fix\",\n \"confidence\": 0.0-1.0}\n ]}\n\nok=true → \"violations\": [] (empty array). Safe diffs (placeholder, fixture, sandboxed, clean) are ok=true.\n\nok=false → list EVERY distinct violation. A diff that hardcodes a Stripe key AND uses eval AND has SQL injection produces THREE entries — one per independently-fixable issue. Don't collapse multiple issues into one \"composite\" finding; the agent retries based on this list and missing entries become extra round trips.\n\nONE VIOLATION = ONE ENTRY. If the same line/issue can be described multiple ways (\"uses concat\", \"missing $1 placeholder\", \"not parameterized\"), pick the most specific rule_id and write a single entry. Multiple entries are warranted only when the diff has multiple INDEPENDENTLY-FIXABLE issues.\n\nReply with exactly: <synkro-verdict>{\"ok\":true,\"violations\":[]}</synkro-verdict>\n`;\n\nexport const GRADER_PRIMER_BASH = `You are Synkro's bash command safety judge for AI coding agents. You will be given a proposed shell command, the user's most recent stated intent, the last 3-5 user-role messages from the chat (oldest first, JSON array under \"Recent user messages\"), and recent agent actions. Decide whether to allow or warn.\n\nOUTPUT RULES — strictest possible, no exceptions:\n\n1. NO reasoning. NO preamble. NO commentary.\n2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.\n3. JSON shape: {\"verdict\": \"warn\"|\"allow\", \"severity\": \"low|medium|high|critical\", \"category\": \"snake_case\", \"reasoning\": \"<= 25 words, cites intent + match/mismatch\", \"alternative\": \"safer command or null\"}\n\nRules:\n- WARN if destructive/irreversible AND not aligned with user intent, OR has wildly disproportionate blast radius vs the request.\n- ALLOW if consistent with user intent and either reversible or low-blast-radius.\n- The same command can be allow OR warn depending on context. Match against the user's verbatim intent.\n- Be conservative: when uncertain, warn medium.\n- Token-scope check: if recent_actions shows a Read of a credentials file (e.g. \".env.deploy\", \"domain-token.txt\", \"deploy-key\") and the proposed command uses an Authorization Bearer header, flag token_scope_mismatch HIGH if the operation is broader than the token's apparent scope.\n\nCONSENT CARRYOVER — IMPORTANT:\nTreat \"Recent user messages\" as the consent context, not just the last entry. If ANY entry contains explicit affirmative authorization that covers this action class — phrasings like \"yes do it\", \"go ahead\", \"i consent\", \"ship it\", \"apply the migration\", \"run the script\", \"deploy\", \"i'm certain\" — and the proposed command falls within that authorization's scope, ALLOW. Re-blocking the same action class after consent already granted is a UX failure, not a safety win. Cite the verbatim consent quote in the reasoning.\n\nConsent does NOT carry forward when:\n1. The action escalates beyond what was authorized — different DB / host / file / wider blast / production-vs-test scope mismatch.\n2. The user said something contradictory afterward — \"stop\", \"wait\", \"cancel\", \"actually don't\".\n3. The action involves credentials / secrets / supply-chain risk that prior consent didn't cover.\n4. The most recent message is a fresh task unrelated to the authorized action class.\n\nReply with exactly: <synkro-verdict>{\"verdict\":\"allow\",\"severity\":\"low\",\"category\":\"primer_ack\",\"reasoning\":\"primer received\",\"alternative\":null}</synkro-verdict>\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\");\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_WEB_AUTH_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 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/**\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 } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { installCCHooks } from '../installer/ccHookConfig.js';\nimport { installMcpConfig } from '../installer/mcpConfig.js';\nimport { CC_BASH_JUDGE_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT } from '../installer/hookScripts.js';\nimport { GRADER_DAEMON_PY, GRADER_PRIMER_EDIT, GRADER_PRIMER_BASH } from '../installer/graderDaemon.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo } from '../auth/stub.js';\n\n// Replaced by tsup `define` at build time with the value from package.json.\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');\nconst GRADER_DAEMON_PATH = join(BIN_DIR, 'grader_daemon.py');\nconst GRADER_PRIMER_EDIT_PATH = join(SYNKRO_DIR, 'grader-primer-edit.txt');\nconst GRADER_PRIMER_BASH_PATH = join(SYNKRO_DIR, 'grader-primer-bash.txt');\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}\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 }\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\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n}\n\n// Write the persistent claude grader daemon + primer used by the local-tier\n// hook path. The daemon keeps one `claude --print --input-format=stream-json`\n// process alive, primed once, so each grading runs at ~1.5–3s steady-state\n// vs ~14s for cold `claude --print`.\nfunction writeGraderDaemon(): void {\n writeFileSync(GRADER_DAEMON_PATH, GRADER_DAEMON_PY, 'utf-8');\n chmodSync(GRADER_DAEMON_PATH, 0o755);\n writeFileSync(GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_EDIT, 'utf-8');\n chmodSync(GRADER_PRIMER_EDIT_PATH, 0o644);\n writeFileSync(GRADER_PRIMER_BASH_PATH, GRADER_PRIMER_BASH, 'utf-8');\n chmodSync(GRADER_PRIMER_BASH_PATH, 0o644);\n}\n\nfunction writeHookScripts(): {\n bashScript: string;\n bashFollowupScript: string;\n editCaptureScript: string;\n editPrecheckScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n} {\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.sh');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.sh');\n const editCaptureScriptPath = join(HOOKS_DIR, 'cc-edit-capture.sh');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.sh');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.sh');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.sh');\n\n writeFileSync(bashScriptPath, CC_BASH_JUDGE_SCRIPT, 'utf-8');\n writeFileSync(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, 'utf-8');\n writeFileSync(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, 'utf-8');\n writeFileSync(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, 'utf-8');\n writeFileSync(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, 'utf-8');\n writeFileSync(sessionStartScriptPath, CC_SESSION_START_SCRIPT, 'utf-8');\n\n chmodSync(bashScriptPath, 0o755);\n chmodSync(bashFollowupScriptPath, 0o755);\n chmodSync(editCaptureScriptPath, 0o755);\n chmodSync(editPrecheckScriptPath, 0o755);\n chmodSync(stopSummaryScriptPath, 0o755);\n chmodSync(sessionStartScriptPath, 0o755);\n\n return {\n bashScript: bashScriptPath,\n bashFollowupScript: bashFollowupScriptPath,\n editCaptureScript: editCaptureScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\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\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string }): 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 // Inference tier comes from env override only — allowlist to {free, fast}.\n const tierEnv = process.env.SYNKRO_INFERENCE_TIER;\n const safeInferenceTier = tierEnv === 'fast' ? 'fast' : 'free';\n\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_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\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 // Inference tier: 'free' = grade locally with claude --print (slow but $0\n // to Synkro and customer pays only Sonnet via their CC subscription).\n // 'fast' = server-side Cerebras (~500ms, Synkro-billed). New installs land\n // on free; pro plans flip to fast via the dashboard.\n lines.push(`SYNKRO_INFERENCE_TIER=${shellQuoteSingle(safeInferenceTier)}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\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.sh'),\n join(HOOKS_DIR, 'cc-bash-followup.sh'),\n join(HOOKS_DIR, 'cc-edit-precheck.sh'),\n join(HOOKS_DIR, 'cc-edit-capture.sh'),\n join(HOOKS_DIR, 'cc-stop-summary.sh'),\n join(HOOKS_DIR, 'cc-session-start.sh'),\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\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, exit early instead of re-running every step. The\n // user is most likely just retyping the command. Use --force to reinstall.\n if (!opts.force && isAuthenticated() && isAlreadyInstalled()) {\n console.log('✓ Synkro is already installed and configured.');\n console.log(' Run `synkro-cli update` to refresh hook scripts and judge prompts.');\n console.log(' Run `synkro-cli 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 // 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.editCaptureScript}`);\n console.log(` ${scripts.editPrecheckScript}`);\n console.log(` ${scripts.stopSummaryScript}`);\n console.log(` ${scripts.sessionStartScript}\\n`);\n\n writeGraderDaemon();\n console.log('Wrote local-tier grader daemon:');\n console.log(` ${GRADER_DAEMON_PATH}`);\n console.log(` ${GRADER_PRIMER_EDIT_PATH}`);\n console.log(` ${GRADER_PRIMER_BASH_PATH}\\n`);\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n 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 editCaptureScriptPath: scripts.editCaptureScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Register the Synkro Guardrails MCP server in ~/.claude.json so coding\n // agents can pull org-specific rules on demand. Skip when --no-mcp is\n // set or when no CC install was detected.\n //\n // We mint a long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails\n // and write THAT into ~/.claude.json — never the WorkOS access token,\n // which expires in 5 min and silently kills the MCP connection mid-session.\n if (hasClaudeCode && !opts.noMcp) {\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 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-cli install` to retry MCP setup.');\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 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 writeConfigEnv({ gatewayUrl, userId, orgId, email });\n console.log(`Wrote config to ${CONFIG_PATH}\\n`);\n\n // 7. Done — print next steps\n console.log('✓ Synkro installed.');\n console.log();\n console.log('Next steps:');\n console.log(' • synkro-cli setup-github (enable PR scanning)');\n console.log(' • synkro-cli status (check what is configured)');\n}\n","/**\n * synkro login — browser OAuth via WorkOS (reuses existing auth flow).\n */\nimport { authenticate, isAuthenticated, getUserInfo } from '../auth/stub.js';\n\nexport async function loginCommand(args: string[] = []): Promise<void> {\n const force = args.includes('--force') || args.includes('-f');\n if (isAuthenticated() && !force) {\n const info = getUserInfo();\n console.log(`Already authenticated as ${info?.email ?? 'unknown'}.`);\n console.log('Use --force to re-authenticate.');\n return;\n }\n console.log('Opening browser for Synkro login...');\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('Login failed. Make sure the Synkro web app is running.');\n process.exit(1);\n }\n const info = getUserInfo();\n console.log(`✓ Logged in as ${info?.email ?? 'unknown'}.`);\n}\n","/**\n * synkro logout — clear local credentials.\n */\nimport { isAuthenticated, clearCredentials } from '../auth/stub.js';\n\nexport function logoutCommand(): void {\n if (!isAuthenticated()) {\n console.log('Not authenticated.');\n return;\n }\n clearCredentials();\n console.log('Logged out.');\n}\n","// :)\n/**\n * synkro status — show current setup state.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { isAuthenticated, getUserInfo } from '../auth/stub.js';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { inspectCCHooks } from '../installer/ccHookConfig.js';\nimport { inspectMcpConfig } from '../installer/mcpConfig.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n const raw = readFileSync(CONFIG_PATH, 'utf-8');\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eq = trimmed.indexOf('=');\n if (eq > 0) {\n const k = trimmed.slice(0, eq).trim();\n const v = trimmed.slice(eq + 1).trim();\n out[k] = v;\n }\n }\n return out;\n}\n\nexport function statusCommand(): void {\n console.log('Synkro CLI status\\n');\n\n // Auth\n if (isAuthenticated()) {\n const info = getUserInfo();\n console.log(`Authentication: ✓ logged in as ${info?.email ?? 'unknown'}`);\n if (info?.org_id) console.log(` org_id: ${info.org_id}`);\n if (info?.id) console.log(` user_id: ${info.id}`);\n } else {\n console.log('Authentication: ✗ not logged in (run: synkro-cli login)');\n }\n console.log();\n\n // Config\n const config = readConfigEnv();\n console.log('Config:');\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL ?? '(unset)'}`);\n console.log(` credentials: ${config.SYNKRO_CREDENTIALS_PATH ?? '(unset)'}`);\n console.log(` tier: ${config.SYNKRO_TIER ?? '(unset)'}`);\n console.log(` version: ${config.SYNKRO_VERSION ?? '(unset)'}`);\n console.log();\n\n // Agents\n const agents = detectAgents();\n console.log('Detected agents:');\n if (agents.length === 0) {\n console.log(' (none — install Claude Code first)');\n } else {\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n console.log(` settings: ${a.settingsPath}`);\n if (a.kind === 'claude_code') {\n const hooks = inspectCCHooks(a.settingsPath);\n console.log(` hooks installed: ${hooks.installed ? '✓' : '✗'}`);\n if (hooks.installed) {\n console.log(` • PreToolUse Bash: ${hooks.preToolUseBash ? '✓' : '✗'}`);\n console.log(` • PostToolUse Edit: ${hooks.postToolUseEdit ? '✓' : '✗'}`);\n console.log(` • SessionEnd summary: ${hooks.sessionEnd ? '✓' : '✗'}`);\n console.log(` • SessionStart: ${hooks.sessionStart ? '✓' : '✗'}`);\n }\n }\n }\n }\n console.log();\n\n // Hook scripts\n const bashScript = join(SYNKRO_DIR, 'hooks', 'cc-bash-judge.sh');\n const bashFollowupScript = join(SYNKRO_DIR, 'hooks', 'cc-bash-followup.sh');\n const editPrecheckScript = join(SYNKRO_DIR, 'hooks', 'cc-edit-precheck.sh');\n const editCaptureScript = join(SYNKRO_DIR, 'hooks', 'cc-edit-capture.sh');\n const stopSummaryScript = join(SYNKRO_DIR, 'hooks', 'cc-stop-summary.sh');\n const sessionStartScript = join(SYNKRO_DIR, 'hooks', 'cc-session-start.sh');\n console.log('Hook scripts:');\n console.log(` ${existsSync(bashScript) ? '✓' : '✗'} ${bashScript}`);\n console.log(` ${existsSync(bashFollowupScript) ? '✓' : '✗'} ${bashFollowupScript}`);\n console.log(` ${existsSync(editPrecheckScript) ? '✓' : '✗'} ${editPrecheckScript}`);\n console.log(` ${existsSync(editCaptureScript) ? '✓' : '✗'} ${editCaptureScript}`);\n console.log(` ${existsSync(stopSummaryScript) ? '✓' : '✗'} ${stopSummaryScript}`);\n console.log(` ${existsSync(sessionStartScript) ? '✓' : '✗'} ${sessionStartScript}`);\n console.log();\n\n // MCP guardrails server registration (CC's ~/.claude.json)\n const mcp = inspectMcpConfig();\n console.log('Guardrails MCP server (Claude Code):');\n if (mcp.installed) {\n console.log(` ✓ registered in ${mcp.configPath}`);\n console.log(` url: ${mcp.url}`);\n } else {\n console.log(` ✗ not registered (run: synkro-cli install)`);\n console.log(` expected at ${mcp.configPath} → mcpServers.synkro-guardrails`);\n }\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\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: read\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\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 \"~/.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: \\${{ github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_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 the user's personal access token (or App installation token from OAuth)\n * to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY as repo secrets, then writes\n * .github/workflows/synkro.yml to the local clone if present.\n *\n * Secrets are encrypted with the repo's public key using libsodium sealed_box\n * (GitHub's required format).\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\ninterface RepoPublicKey {\n key_id: string;\n key: string; // base64\n}\n\n/**\n * Encrypt a secret value with the repo's public key.\n * GitHub requires libsodium sealed_box (Curve25519 + XSalsa20Poly1305).\n */\nasync function encryptSecret(publicKeyBase64: string, secret: string): Promise<string> {\n // Use libsodium-wrappers (pure-JS implementation; ~200KB)\n const sodium = await import('libsodium-wrappers').then((m) => m.default ?? m);\n await (sodium as any).ready;\n\n const keyBytes = (sodium as any).from_base64(publicKeyBase64, (sodium as any).base64_variants.ORIGINAL);\n const messageBytes = (sodium as any).from_string(secret);\n const encryptedBytes = (sodium as any).crypto_box_seal(messageBytes, keyBytes);\n return (sodium as any).to_base64(encryptedBytes, (sodium as any).base64_variants.ORIGINAL);\n}\n\nexport interface GitHubAuthOptions {\n /** Personal access token, OAuth token, or App installation token. */\n token: string;\n}\n\nexport async function getRepoPublicKey(opts: GitHubAuthOptions, owner: string, repo: string): Promise<RepoPublicKey> {\n const url = `https://api.github.com/repos/${owner}/${repo}/actions/secrets/public-key`;\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 const text = await resp.text().catch(() => '');\n throw new Error(`GitHub API ${resp.status} fetching public key for ${owner}/${repo}: ${text.slice(0, 200)}`);\n }\n return await resp.json() as RepoPublicKey;\n}\n\nexport async function putRepoSecret(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secretName: string,\n secretValue: string,\n publicKey: RepoPublicKey,\n): Promise<void> {\n const encryptedValue = await encryptSecret(publicKey.key, secretValue);\n const url = `https://api.github.com/repos/${owner}/${repo}/actions/secrets/${encodeURIComponent(secretName)}`;\n const resp = await fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n encrypted_value: encryptedValue,\n key_id: publicKey.key_id,\n }),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`GitHub API ${resp.status} setting secret ${secretName}: ${text.slice(0, 200)}`);\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\n/**\n * Push both Synkro secrets to a repo.\n */\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken: string; synkroApiKey: string },\n): Promise<void> {\n const pubkey = await getRepoPublicKey(opts, owner, repo);\n await putRepoSecret(opts, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken, pubkey);\n await putRepoSecret(opts, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey, pubkey);\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 * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Prompt for a GitHub personal access token (or App installation token).\n * For v1 we ask for a PAT to avoid the App OAuth dance — the user creates\n * a fine-grained token at github.com/settings/tokens with `repo` +\n * `actions:write` scopes for the repos they want to enable.\n * 2. Prompt the user to run `claude setup-token` in another terminal and\n * paste the resulting sk-ant-oat01-... long-lived OAuth token.\n * 3. List accessible repos via GitHub API.\n * 4. Interactive multi-select (simple numbered prompt).\n * 5. For each selected repo: encrypt secrets with libsodium and PUT them.\n * 6. 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 { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken } 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();\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 // Best-effort password masking — works on most terminals\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') { // Ctrl-C\n process.exit(130);\n }\n if (s === '\\u007f' || s === '\\b') { // backspace\n chunk = chunk.slice(0, -1);\n return;\n }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nexport async function setupGithubCommand(): 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 // Mint a CI-scoped API key on demand. We push this into the repo secret as\n // SYNKRO_API_KEY so scan-pr can authenticate against the gateway from the\n // GH Actions runner. Never persisted on the user's disk.\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ci-api-key`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n const minted = await resp.json() as { api_key: string; expires_at: string; project_id: string };\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 const rl = createInterface({ input, output });\n\n console.log('Synkro PR scan setup\\n');\n console.log('Requirements:');\n console.log(' • Claude Code Pro or Max subscription (for `claude setup-token`)');\n console.log(' • A GitHub personal access token with `repo` scope');\n console.log(' (create at https://github.com/settings/tokens?type=beta)\\n');\n\n // 1. GitHub PAT\n const ghToken = (await prompt(rl, 'GitHub token (paste): ', { silent: true })).trim();\n if (!ghToken || (!ghToken.startsWith('ghp_') && !ghToken.startsWith('github_pat_'))) {\n console.error('Invalid GitHub token format. Expected ghp_... or github_pat_...');\n rl.close();\n process.exit(1);\n }\n\n // 2. Claude Code OAuth token\n console.log('\\nNow get your Claude Code OAuth token:');\n console.log(' 1. In another terminal, run: claude setup-token');\n console.log(' 2. Complete the browser flow');\n console.log(' 3. Copy the resulting sk-ant-oat01-... token\\n');\n const claudeToken = (await prompt(rl, 'Claude Code OAuth token (paste): ', { silent: true })).trim();\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token. Expected sk-ant-oat01-... — generate one with `claude setup-token`.');\n rl.close();\n process.exit(1);\n }\n\n // 3. List repos\n console.log('\\nFetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.error('No accessible repos found. Verify the GitHub token has `repo` scope.');\n rl.close();\n process.exit(1);\n }\n\n // 4. Multi-select prompt\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 selectionRaw = await prompt(rl, '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\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl.close();\n process.exit(1);\n }\n\n const 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(rl, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl.close();\n process.exit(0);\n }\n\n rl.close();\n\n // 5. Push secrets to each repo\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 to local repo if cwd is one\n console.log();\n const gitRoot = findGitRoot(process.cwd());\n if (gitRoot) {\n const written = writeWorkflowFile(gitRoot);\n if (written) {\n console.log(`Wrote workflow: ${written}`);\n console.log('Commit and push it to enable PR scanning.');\n }\n } else {\n console.log('Not in a git repo. To enable scanning, add this file to your repo:');\n console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);\n console.log(` Content: run \\`synkro-cli setup-github\\` from inside a repo to write it automatically`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro scan-pr — runs inside a GitHub Actions runner.\n *\n * Reads PR context from env (set by the workflow YAML), fetches the PR diff\n * via `gh` CLI, spawns `claude --print` per changed file with the customer's\n * CLAUDE_CODE_OAUTH_TOKEN, aggregates findings, posts inline review comments,\n * sets a status check, and POSTs aggregate to /v1/events/pr-scan for logging.\n *\n * Auth chain (set by workflow YAML):\n * CLAUDE_CODE_OAUTH_TOKEN — long-lived OAuth token from `claude setup-token`\n * SYNKRO_API_KEY — auth for our backend\n * GH_TOKEN — GitHub-injected, for posting comments + checks\n * SYNKRO_PR_NUMBER, SYNKRO_REPO, SYNKRO_SHA — PR context\n */\nimport { execSync, spawn } from 'node:child_process';\n\nconst SKIP_FILE_PATTERNS = [\n /\\.lock$/i,\n /\\.min\\./i,\n /\\.map$/i,\n /^dist\\//,\n /^build\\//,\n /^vendor\\//,\n /^node_modules\\//,\n /^\\.next\\//,\n /package-lock\\.json$/,\n /yarn\\.lock$/,\n /pnpm-lock\\.yaml$/,\n /Cargo\\.lock$/,\n /go\\.sum$/,\n];\n\nconst MAX_DIFF_LINES_PER_FILE = 1000;\nconst MAX_PARALLEL_FILES = 5;\n\ninterface PrFile {\n filename: string;\n status: string; // 'added' | 'modified' | 'removed' | 'renamed'\n additions: number;\n deletions: number;\n patch?: string;\n}\n\ninterface Finding {\n file: string;\n line: number;\n severity: 'low' | 'medium' | 'high' | 'critical';\n category: string;\n description: string;\n fix: string;\n}\n\ninterface OrgRule {\n rule_id: string;\n text: string;\n category: string;\n severity: string;\n mode: 'audit' | 'literal_match';\n condition: string;\n}\n\ninterface LiteralMatchSpec {\n selector: string;\n requires: string;\n position: 'start' | 'anywhere';\n negate?: boolean;\n}\n\nfunction parseMatchSpec(condition: string): LiteralMatchSpec | null {\n if (!condition.startsWith('match_spec:')) return null;\n try {\n const parsed = JSON.parse(condition.slice('match_spec:'.length)) as Partial<LiteralMatchSpec>;\n if (\n typeof parsed?.selector !== 'string' ||\n typeof parsed?.requires !== 'string' ||\n (parsed.position !== 'start' && parsed.position !== 'anywhere')\n ) return null;\n return {\n selector: parsed.selector,\n requires: parsed.requires,\n position: parsed.position,\n negate: parsed.negate === true,\n };\n } catch {\n return null;\n }\n}\n\nfunction selectorMatches(selector: string, filePath: string): boolean {\n const m = selector.match(/^\\*\\*\\/\\*\\.([a-z0-9]+)$/i);\n if (!m) return false;\n return filePath.toLowerCase().endsWith('.' + m[1].toLowerCase());\n}\n\nasync function fetchOrgRules(gatewayUrl: string, apiKey: string): Promise<OrgRule[]> {\n try {\n const resp = await fetch(`${gatewayUrl.replace(/\\/$/, '')}/api/v1/cli/pr-rules`, {\n headers: { 'x-synkro-api-key': apiKey },\n });\n if (!resp.ok) {\n console.warn(`[scan-pr] failed to fetch org rules: HTTP ${resp.status}`);\n return [];\n }\n const data = await resp.json() as { rules?: OrgRule[] };\n return Array.isArray(data?.rules) ? data.rules : [];\n } catch (err) {\n console.warn(`[scan-pr] could not fetch org rules: ${(err as Error).message}`);\n return [];\n }\n}\n\n// Deterministic literal_match enforcement on the patch's added lines.\n// We can only check the diff (not the full file), so this catches\n// negative-form rules (\"Never use X\") cleanly. Positive-form rules\n// (\"must contain X\") need the full file content and are deferred to\n// edit-time hooks for v1 — we surface a note rather than skip silently.\nfunction applyLiteralMatchNegative(rules: OrgRule[], file: PrFile): Finding[] {\n if (!file.patch) return [];\n const findings: Finding[] = [];\n const lines = file.patch.split('\\n');\n let currentNewLine = 0;\n for (const line of lines) {\n if (line.startsWith('@@')) {\n const m = line.match(/\\+(\\d+)(?:,\\d+)?/);\n if (m) currentNewLine = parseInt(m[1], 10);\n continue;\n }\n if (line.startsWith('+++') || line.startsWith('---')) continue;\n if (!line.startsWith('+')) {\n // context or removed line: only NEW-file line counter advances on context\n if (!line.startsWith('-')) currentNewLine++;\n continue;\n }\n const addedContent = line.slice(1); // strip leading '+'\n for (const r of rules) {\n if (r.mode !== 'literal_match') continue;\n const spec = parseMatchSpec(r.condition);\n if (!spec || !spec.negate) continue; // only negative rules in PR scan v1\n if (!selectorMatches(spec.selector, file.filename)) continue;\n if (!addedContent.includes(spec.requires)) continue;\n findings.push({\n file: file.filename,\n line: currentNewLine,\n severity: (r.severity as Finding['severity']) ?? 'high',\n category: r.category || 'literal_match',\n description: r.text,\n fix: `Remove \\`${spec.requires}\\` from ${file.filename} (rule ${r.rule_id}).`,\n });\n }\n currentNewLine++;\n }\n return findings;\n}\n\nfunction buildPrPrompt(orgAuditRules: OrgRule[]): string {\n const orgRulesBlock = orgAuditRules.length === 0\n ? ''\n : `\\nORG-SPECIFIC RULES (these are the customer's policies — flag any violation found in the diff):\\n` +\n orgAuditRules\n .map((r, i) => ` ${i + 1}. [${r.severity}/${r.category}] ${r.text}`)\n .join('\\n') +\n '\\n';\n\n return `You are a security code reviewer analyzing a pull request diff for one file. Identify security issues + org-policy violations introduced by this diff.\n\nOutput ONLY a JSON object (no prose, no markdown fences):\n{\n \"findings\": [\n {\n \"line\": <int — line number in the NEW file, prefixed with L in the diff>,\n \"severity\": \"low\" | \"medium\" | \"high\" | \"critical\",\n \"category\": \"<snake_case>\",\n \"description\": \"<1 sentence>\",\n \"fix\": \"<concrete suggestion>\"\n }\n ],\n \"summary\": \"<one-line: 'X findings' or 'clean'>\"\n}\n\nBaseline security categories: hardcoded_secret, sql_injection, insecure_crypto, eval_exec, unsafe_deserialization, missing_validation, exposed_internal, missing_auth_check, cors_misconfig, path_traversal, command_injection, weak_random, broken_jwt.\n${orgRulesBlock}\nRules:\n- Only flag NEW issues (lines starting with +).\n- Use the L<num> line numbers I prefixed.\n- Be specific. If clean, return {\"findings\": [], \"summary\": \"clean\"}.\n\n`;\n}\n\nfunction shouldSkipFile(filename: string): boolean {\n return SKIP_FILE_PATTERNS.some((p) => p.test(filename));\n}\n\nfunction ghJson<T>(args: string[]): T {\n const out = execSync(`gh ${args.map((a) => `'${a.replace(/'/g, \"'\\\\''\")}'`).join(' ')}`, {\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return JSON.parse(out) as T;\n}\n\nfunction getPrFiles(repo: string, prNumber: number): PrFile[] {\n // gh api returns paginated results; for v1 we get the first page (250 files)\n const data = ghJson<PrFile[]>([\n 'api',\n `/repos/${repo}/pulls/${prNumber}/files?per_page=250`,\n ]);\n return data;\n}\n\nfunction getFileDiffWithLines(file: PrFile): { hunks: string; newFileLineMap: Map<number, number> } {\n // For each hunk, build a map of \"position in patch\" → \"line number in NEW file\"\n // so claude can reference correct line numbers.\n if (!file.patch) return { hunks: '', newFileLineMap: new Map() };\n\n const lines = file.patch.split('\\n');\n const annotated: string[] = [];\n const lineMap = new Map<number, number>();\n let currentNewLine = 0;\n let patchIndex = 0;\n\n for (const line of lines) {\n patchIndex++;\n if (line.startsWith('@@')) {\n // Parse hunk header: @@ -A,B +C,D @@ ...\n const match = line.match(/\\+(\\d+)(?:,\\d+)?/);\n if (match) {\n currentNewLine = parseInt(match[1], 10);\n }\n annotated.push(line);\n continue;\n }\n if (line.startsWith('+') && !line.startsWith('+++')) {\n annotated.push(`L${currentNewLine}: ${line}`);\n lineMap.set(patchIndex, currentNewLine);\n currentNewLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n annotated.push(line);\n // line was removed; don't increment new file line counter\n } else if (!line.startsWith('---') && !line.startsWith('+++')) {\n annotated.push(`L${currentNewLine}: ${line}`);\n currentNewLine++;\n } else {\n annotated.push(line);\n }\n }\n\n return { hunks: annotated.join('\\n'), newFileLineMap: lineMap };\n}\n\nfunction spawnClaudeJudge(file: PrFile, claudeToken: string, promptHeader: string): Promise<{ findings: Finding[]; latencyMs: number }> {\n const { hunks } = getFileDiffWithLines(file);\n const userMessage = `File: ${file.filename}\\n\\nDiff:\\n${hunks}`;\n const fullPrompt = promptHeader + userMessage;\n\n return new Promise((resolve) => {\n const t0 = Date.now();\n const proc = spawn(\n 'claude',\n ['--print', '--model', 'claude-sonnet-4-6', '--output-format', 'json', '--no-session-persistence', fullPrompt],\n {\n env: {\n ...process.env,\n CLAUDE_CODE_OAUTH_TOKEN: claudeToken,\n },\n timeout: 120_000,\n },\n );\n let stdout = '';\n proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });\n proc.stderr.on('data', () => { /* swallow */ });\n proc.on('close', (code) => {\n const latencyMs = Date.now() - t0;\n if (code !== 0) {\n resolve({ findings: [], latencyMs });\n return;\n }\n try {\n const wrapper = JSON.parse(stdout);\n const responseText = (wrapper.result || wrapper.response || wrapper.text || '').trim();\n let txt = responseText;\n if (txt.startsWith('```')) {\n txt = txt.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```\\s*$/, '').trim();\n }\n const verdict = JSON.parse(txt);\n const findings = (verdict.findings || []).map((f: any) => ({\n file: file.filename,\n line: f.line,\n severity: f.severity,\n category: f.category,\n description: f.description,\n fix: f.fix,\n })) as Finding[];\n resolve({ findings, latencyMs });\n } catch {\n resolve({ findings: [], latencyMs });\n }\n });\n });\n}\n\nasync function processInBatches<T, R>(items: T[], batchSize: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const results: R[] = [];\n for (let i = 0; i < items.length; i += batchSize) {\n const batch = items.slice(i, i + batchSize);\n const batchResults = await Promise.all(batch.map(fn));\n results.push(...batchResults);\n }\n return results;\n}\n\nfunction postPrComment(repo: string, prNumber: number, sha: string, finding: Finding): void {\n const body = `🔒 **Synkro [${finding.severity}]: ${finding.category}**\\n\\n${finding.description}\\n\\n**Fix:** ${finding.fix}`;\n try {\n execSync(\n `gh api -X POST /repos/${repo}/pulls/${prNumber}/comments ` +\n `-f body=${JSON.stringify(body)} ` +\n `-f commit_id=${sha} ` +\n `-f path=${JSON.stringify(finding.file)} ` +\n `-F line=${finding.line} ` +\n `-f side=RIGHT`,\n { encoding: 'utf-8', stdio: ['ignore', 'ignore', 'pipe'] },\n );\n } catch (err) {\n console.warn(`Failed to post comment on ${finding.file}:${finding.line}:`, (err as Error).message);\n }\n}\n\nfunction postCheckRun(repo: string, sha: string, conclusion: 'success' | 'failure', findings: Finding[]): void {\n const summary = findings.length === 0\n ? 'No security findings.'\n : `${findings.length} finding(s):\\n` + findings.slice(0, 20).map((f) => `- **${f.severity}**: ${f.file}:${f.line} — ${f.description}`).join('\\n');\n const body = JSON.stringify({\n name: 'Synkro Security Review',\n head_sha: sha,\n status: 'completed',\n conclusion,\n output: {\n title: findings.length === 0 ? 'No issues found' : `${findings.length} security finding(s)`,\n summary,\n },\n });\n try {\n execSync(`gh api -X POST /repos/${repo}/check-runs --input -`, {\n encoding: 'utf-8',\n input: body,\n stdio: ['pipe', 'ignore', 'pipe'],\n });\n } catch (err) {\n console.warn('Failed to post check run:', (err as Error).message);\n }\n}\n\nfunction shouldFail(findings: Finding[], threshold: 'critical' | 'high' | 'medium' | 'low'): boolean {\n const order = ['low', 'medium', 'high', 'critical'];\n const thresholdIdx = order.indexOf(threshold);\n return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);\n}\n\nasync function postEventToBackend(opts: {\n gatewayUrl: string;\n apiKey: string;\n repo: string;\n prNumber: number;\n sha: string;\n findings: Finding[];\n filesScanned: number;\n totalLatencyMs: number;\n}): Promise<void> {\n try {\n await fetch(`${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/events/pr-scan`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-synkro-api-key': opts.apiKey,\n },\n body: JSON.stringify({\n repo: opts.repo,\n pr_number: opts.prNumber,\n sha: opts.sha,\n findings: opts.findings,\n summary: opts.findings.length === 0 ? 'clean' : `${opts.findings.length} findings`,\n files_scanned: opts.filesScanned,\n total_latency_ms: opts.totalLatencyMs,\n }),\n });\n } catch (err) {\n console.warn('Failed to log scan to Synkro backend:', (err as Error).message);\n }\n}\n\nexport async function scanPrCommand(): Promise<void> {\n const repo = process.env.SYNKRO_REPO || process.env.GITHUB_REPOSITORY || '';\n const prNumberStr = process.env.SYNKRO_PR_NUMBER || '';\n const sha = process.env.SYNKRO_SHA || process.env.GITHUB_SHA || '';\n const claudeToken = process.env.CLAUDE_CODE_OAUTH_TOKEN || '';\n const synkroApiKey = process.env.SYNKRO_API_KEY || '';\n const gatewayUrl = process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh';\n const failThreshold = (process.env.SYNKRO_FAIL_THRESHOLD || 'high') as 'critical' | 'high' | 'medium' | 'low';\n\n if (!repo || !prNumberStr || !sha || !claudeToken || !synkroApiKey) {\n console.error('Missing required env vars: SYNKRO_REPO, SYNKRO_PR_NUMBER, SYNKRO_SHA, CLAUDE_CODE_OAUTH_TOKEN, SYNKRO_API_KEY');\n process.exit(2);\n }\n const prNumber = parseInt(prNumberStr, 10);\n if (!prNumber) {\n console.error('SYNKRO_PR_NUMBER is not a valid number:', prNumberStr);\n process.exit(2);\n }\n\n console.log(`Synkro scan-pr: ${repo}#${prNumber} @ ${sha.slice(0, 7)}\\n`);\n\n // Fetch the org's active runtime rules so we enforce them in PR review\n // — same rules that fire on edit-time hooks. Without this, scan-pr would\n // ignore every rule the customer added via create_guardrail / dashboard.\n const orgRules = await fetchOrgRules(gatewayUrl, synkroApiKey);\n const auditRules = orgRules.filter((r) => r.mode === 'audit');\n const literalNegativeRules = orgRules.filter((r) => {\n const spec = parseMatchSpec(r.condition);\n return r.mode === 'literal_match' && spec?.negate === true;\n });\n console.log(`Loaded ${orgRules.length} org rule(s): ${auditRules.length} audit, ${literalNegativeRules.length} literal_match (negative).`);\n const promptHeader = buildPrPrompt(auditRules);\n\n // Fetch PR file list\n let files: PrFile[];\n try {\n files = getPrFiles(repo, prNumber);\n } catch (err) {\n console.error('Failed to fetch PR files:', (err as Error).message);\n process.exit(2);\n }\n\n // Filter\n const eligible = files.filter((f) => {\n if (f.status === 'removed') return false;\n if (shouldSkipFile(f.filename)) return false;\n if ((f.additions + f.deletions) > MAX_DIFF_LINES_PER_FILE) return false;\n if (!f.patch) return false;\n return true;\n });\n\n console.log(`${files.length} files in PR, ${eligible.length} eligible for scan.\\n`);\n\n if (eligible.length === 0) {\n postCheckRun(repo, sha, 'success', []);\n await postEventToBackend({\n gatewayUrl, apiKey: synkroApiKey, repo, prNumber, sha,\n findings: [], filesScanned: 0, totalLatencyMs: 0,\n });\n console.log('No eligible files. Exiting.');\n return;\n }\n\n // Scan in parallel batches. Each file gets:\n // 1. Deterministic literal_match-negative checks on the patch's added\n // lines (no LLM, no cost, fires on rules like \"Never use `useEffect`\n // in tsx files\"). Positive literal_match rules need full file content\n // and are deferred to edit-time hooks for v1.\n // 2. LLM-based audit pass for everything else, with the org's audit rules\n // injected into the prompt so customer policies actually surface.\n const t0 = Date.now();\n const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file) => {\n process.stdout.write(`Scanning ${file.filename}...`);\n const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);\n const llmResult = await spawnClaudeJudge(file, claudeToken, promptHeader);\n const merged = [...literalFindings, ...llmResult.findings];\n console.log(` ${merged.length} finding(s) (${literalFindings.length} literal, ${llmResult.findings.length} llm; ${llmResult.latencyMs}ms)`);\n return { findings: merged, latencyMs: llmResult.latencyMs };\n });\n const totalLatencyMs = Date.now() - t0;\n\n const allFindings: Finding[] = results.flatMap((r) => r.findings);\n console.log(`\\nTotal: ${allFindings.length} finding(s) across ${eligible.length} file(s) in ${totalLatencyMs}ms\\n`);\n\n // Post inline comments\n for (const finding of allFindings) {\n postPrComment(repo, prNumber, sha, finding);\n }\n\n // Post status check\n const conclusion = shouldFail(allFindings, failThreshold) ? 'failure' : 'success';\n postCheckRun(repo, sha, conclusion, allFindings);\n\n // Log to backend\n await postEventToBackend({\n gatewayUrl, apiKey: synkroApiKey, repo, prNumber, sha,\n findings: allFindings, filesScanned: eligible.length, totalLatencyMs,\n });\n\n console.log(`\\n✓ Scan complete. Status: ${conclusion}.`);\n\n // Exit non-zero if we want to fail the workflow on findings\n if (conclusion === 'failure') {\n process.exit(1);\n }\n}\n","/**\n * synkro update — pull latest CLI binary + refresh hook configs and prompts.\n *\n * For now this just re-runs install (which fetches latest prompt + rewrites\n * hook scripts). The actual binary self-update would invoke the install.sh\n * shell script from get.synkro.sh — punted to v1.1 distribution work.\n */\nimport { installCommand } from './install.js';\n\nexport async function updateCommand(): Promise<void> {\n console.log('Refreshing Synkro hook configs and prompts...\\n');\n await installCommand();\n console.log('\\n✓ Synkro updated.');\n console.log('To update the CLI binary itself, run: curl -fsSL https://get.synkro.sh | bash');\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 */\nimport { existsSync, rmSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallMcpConfig } from '../installer/mcpConfig.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nexport function disconnectCommand(args: string[] = []): void {\n const purge = args.includes('--purge');\n\n console.log('Synkro disconnect starting...\\n');\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 }\n }\n\n // Also remove the Synkro MCP server entry from ~/.claude.json so the agent\n // stops calling get_guardrails during the A/B baseline.\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails server: ${mcpRemoved ? 'removed entry from ~/.claude.json' : 'no Synkro MCP 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","#!/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\n// Load env vars synchronously from a few candidate paths\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();\n if (!process.env[key]) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\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 Synkro hooks for detected agents (Claude Code, etc.)\n login Authenticate with Synkro (browser OAuth via WorkOS)\n logout Clear local credentials\n status Show current setup state\n setup-github Configure GitHub PR scanning (push secrets + workflow file)\n scan-pr Run a PR scan (used by GitHub Actions, not for direct invocation)\n update Refresh hook configs and judge prompts\n disconnect [--purge] Remove Synkro hooks from agents (--purge also removes ~/.synkro)\n help Show this message\n\nQuick start:\n $ synkro-cli install # one-time setup\n $ synkro-cli setup-github # enable PR scanning (optional)\n $ claude # use Claude Code normally; Synkro judges in real time\n (\\`synkro\\` also works as an alias unless something else on your $PATH shadows it)\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 'login':\n case 'auth': {\n const { loginCommand } = await import('./commands/login.js');\n await loginCommand(subArgs);\n break;\n }\n case 'logout': {\n const { logoutCommand } = await import('./commands/logout.js');\n logoutCommand();\n break;\n }\n case 'status': {\n const { statusCommand } = await import('./commands/status.js');\n statusCommand();\n break;\n }\n case 'setup-github': {\n const { setupGithubCommand } = await import('./commands/setupGithub.js');\n await setupGithubCommand();\n break;\n }\n case 'scan-pr': {\n const { scanPrCommand } = await import('./commands/scanPr.js');\n await scanPrCommand();\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/update.js');\n await updateCommand();\n break;\n }\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n disconnectCommand(subArgs);\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;AAEA,SAAO;AACT;AAzEA;AAAA;AAAA;AAAA;AAAA;;;ACMA,SAAS,cAAAC,aAAY,cAAc,eAAe,YAAY,iBAA6B;AAC3F,SAAS,eAAe;AAmCxB,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,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,QAAQ,aAAa,CAAC;AACjF;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;AAEzD,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;AAG9D,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,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;AAIR,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;AAKR,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;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,MAAM;AACjF,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;AAMO,SAAS,eAAe,cAM7B;AACA,MAAI,CAACA,YAAW,YAAY,GAAG;AAC7B,WAAO,EAAE,WAAW,OAAO,gBAAgB,OAAO,iBAAiB,OAAO,YAAY,OAAO,cAAc,MAAM;AAAA,EACnH;AACA,QAAM,WAAW,aAAa,YAAY;AAC1C,QAAM,MAAO,SAAS,OAAe,cAAc,CAAC;AACpD,QAAM,OAAQ,SAAS,OAAe,eAAe,CAAC;AACtD,QAAM,kBAAmB,SAAS,OAAe,cAAc,CAAC;AAChE,QAAM,oBAAqB,SAAS,OAAe,gBAAgB,CAAC;AACpE,QAAM,iBAAiB,IAAI,KAAK,CAAC,MAAW,IAAI,aAAa,MAAM,IAAI;AACvE,QAAM,kBAAkB,KAAK,KAAK,CAAC,MAAW,IAAI,aAAa,MAAM,IAAI;AACzE,QAAM,aAAa,gBAAgB,KAAK,CAAC,MAAW,IAAI,aAAa,MAAM,IAAI;AAC/E,QAAM,eAAe,kBAAkB,KAAK,CAAC,MAAW,IAAI,aAAa,MAAM,IAAI;AACnF,SAAO;AAAA,IACL,WAAW,kBAAkB,mBAAmB,cAAc;AAAA,IAC9D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAvOA,IAuBM;AAvBN;AAAA;AAAA;AAuBA,IAAM,gBAAgB;AAAA;AAAA;;;ACXtB,SAAS,cAAAC,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;AAeO,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,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;AAKO,SAAS,mBAId;AACA,MAAI,CAACR,YAAW,cAAc,GAAG;AAC/B,WAAO,EAAE,WAAW,OAAO,YAAY,eAAe;AAAA,EACxD;AACA,QAAM,SAAS,eAAe;AAC9B,QAAM,QAAQ,OAAO,aAAa,kBAAkB;AACpD,MAAI,CAAC,SAAS,MAAMQ,cAAa,MAAM,MAAM;AAC3C,WAAO,EAAE,WAAW,OAAO,YAAY,eAAe;AAAA,EACxD;AACA,SAAO,EAAE,WAAW,MAAM,YAAY,gBAAgB,KAAK,MAAM,IAAI;AACvE;AAlIA,IAgBMA,gBACA,oBACA;AAlBN;AAAA;AAAA;AAgBA,IAAMA,iBAAgB;AACtB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiBD,MAAKF,SAAQ,GAAG,cAAc;AAAA;AAAA;;;AClBrD,IAiBa,sBAwSA,yBA+TA,wBAsVA,wBAkEA,yBAkEA;AAllCb;AAAA;AAAA;AAiBO,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwS7B,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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+ThC,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsV/B,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;AAkE/B,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkEhC,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACllCvC,IAiBa,kBA2TA,oBAyBA;AArWb;AAAA;AAAA;AAiBO,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;AA2TzB,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;AAyB3B,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;;;AC9VlC,SAAS,oBAAqD;AAC9D,SAA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","names":["cmd","existsSync","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","join","dirname","args","resolve","existsSync","mkdirSync","writeFileSync","readFileSync","homedir","join","args","info","existsSync","readFileSync","homedir","join","CONFIG_PATH","SYNKRO_DIR","existsSync","mkdirSync","writeFileSync","join","existsSync","readFileSync","homedir","join","CONFIG_PATH","resolve","jwt","SYNKRO_DIR","execSync","args","resolve","existsSync","homedir","join","args","SYNKRO_DIR","readFileSync","existsSync","installCommand","parseArgs","loginCommand","logoutCommand","statusCommand","setupGithubCommand","scanPrCommand","updateCommand","disconnectCommand"]}