@synkro-sh/cli 1.7.71 → 1.7.73
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.
- package/dist/bootstrap.js +18 -5
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../cli/telemetry/metaCache.ts","../cli/telemetry/identity.ts","../cli/telemetry/emit.ts","../cli/telemetry/db.ts","../cli/telemetry/drain.ts","../cli/telemetry/optout.ts","../cli/telemetry/wire.ts","../cli/telemetry/flush.ts","../cli/telemetry/stats.ts","../cli/telemetry/purge.ts","../cli/telemetry/client.ts","../cli/telemetry/export.ts","../cli/telemetry/index.ts","../cli/installer/agentDetect.ts","../cli/installer/platform.ts","../cli/installer/ccHookConfig.ts","../cli/installer/cursorHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/skillParser.ts","../cli/installer/hookScriptsTs.ts","../cli/auth/stub.ts","../cli/auth/index.ts","../cli/api/projects.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/repoConnect.ts","../cli/installer/promptFetcher.ts","../cli/installer/claudeDesktopTap.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.ts","../cli/local-cc/setupToken.ts","../cli/commands/setupGithub.ts","../cli/commands/install.ts","../cli/local-cc/install.ts","../cli/commands/disconnect.ts","../cli/local-cc/turnLog.ts","../cli/local-cc/client.ts","../cli/commands/grade.ts","../cli/local-cc/pueue.ts","../cli/local-cc/settings.ts","../cli/commands/localCc.ts","../cli/commands/import.ts","../cli/installer/packVerify.ts","../cli/installer/lockfile.ts","../cli/commands/sync.ts","../cli/commands/lifecycle.ts","../cli/commands/config.ts","../cli/commands/telemetry.ts","../cli/bootstrap.js"],"sourcesContent":["/**\n * Synchronous mirror of telemetry_meta for use on the hot emit() path.\n *\n * Why a separate JSON file:\n * emit() is sync (callable from any code path / runtime, including bun\n * hooks). The authoritative meta store lives in pglite inside the docker\n * container, which is async (postgres.js) and may be unreachable. We can't\n * open it on every emit. So:\n *\n * - setTelemetryState() and ingest from drain.ts write to BOTH this file\n * AND telemetry_meta in pglite.\n * - emit() only reads this file. If the file is missing, defaults apply.\n *\n * Layout: ~/.synkro/telemetry-meta.json — single object, no schema migration\n * needed because the keys are small and forward-compatible (unknown keys\n * ignored).\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nexport interface TelemetryMeta {\n install_id: string;\n enabled: boolean;\n remote_flush_enabled: boolean;\n /** ISO timestamp of last successful drain into local pglite (best-effort). */\n last_drained_at?: string;\n /**\n * Flush bookkeeping. Lives here (not in pglite) so the throttle + flush\n * state work in CLOUD mode, where there is no local pglite at all — the\n * remote flush reads its cadence from this file, independent of any DB.\n */\n last_flush_at?: string;\n last_flush_ok_at?: string;\n last_flush_error?: string;\n}\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst META_PATH = join(SYNKRO_DIR, 'telemetry-meta.json');\n\nconst DEFAULTS: TelemetryMeta = {\n install_id: '',\n enabled: true,\n remote_flush_enabled: true,\n};\n\nlet cached: TelemetryMeta | null = null;\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) {\n mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n }\n}\n\n/**\n * Read the meta cache, generating an install_id on first read.\n * Synchronous and very fast (single small JSON file).\n * Cached for the rest of the process; call invalidateMetaCache() after a write.\n */\nexport function readMetaCache(): TelemetryMeta {\n if (cached) return cached;\n let meta: TelemetryMeta = { ...DEFAULTS };\n if (existsSync(META_PATH)) {\n try {\n const raw = readFileSync(META_PATH, 'utf-8');\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') {\n meta = { ...DEFAULTS, ...(parsed as Partial<TelemetryMeta>) };\n }\n } catch {\n // unparseable — fall through to defaults; we'll rewrite below.\n }\n }\n if (!meta.install_id) {\n meta.install_id = randomUUID();\n writeMetaCache(meta);\n }\n cached = meta;\n return cached;\n}\n\n/**\n * Atomically replace the meta file. Cheap; do not call from emit() hot path.\n */\nexport function writeMetaCache(meta: TelemetryMeta): void {\n try {\n ensureDir();\n const tmp = `${META_PATH}.tmp`;\n writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 0o600 });\n renameSync(tmp, META_PATH);\n cached = { ...meta };\n } catch {\n // best-effort — caller already has the new state in memory if they need it\n }\n}\n\nexport function invalidateMetaCache(): void {\n cached = null;\n}\n\n/** Convenience: update one or more fields atomically. */\nexport function patchMetaCache(patch: Partial<TelemetryMeta>): TelemetryMeta {\n const current = readMetaCache();\n const next: TelemetryMeta = { ...current, ...patch };\n writeMetaCache(next);\n return next;\n}\n","/**\n * Per-process identity context for telemetry — DESIGN.md §3.\n *\n * Resolves install_id (persisted in metaCache, generated once), user/org/email\n * (from the WorkOS JWT in ~/.synkro/credentials.json), and machine info\n * (cli_version, platform, hostname_hash, node_version). Cached once per process.\n *\n * Synchronous — emit() must not await for identity. The JWT is decoded inline\n * to avoid pulling jsonwebtoken/jose just for the claim grab.\n *\n * Never throws — partial identity is acceptable.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir, hostname, platform } from 'node:os';\nimport { join } from 'node:path';\n\nimport { readMetaCache } from './metaCache.js';\n\n// Injected by tsup at build time (see tsup.config.ts).\ndeclare const __SYNKRO_CLI_VERSION__: string;\n\nconst CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH\n || join(homedir(), '.synkro', 'credentials.json');\n\nexport interface IdentityContext {\n install_id: string;\n user_id?: string;\n org_id?: string;\n email?: string;\n cli_version: string;\n platform: string;\n hostname_hash: string;\n node_version: string;\n}\n\nlet cached: IdentityContext | null = null;\n\ninterface WorkOSClaims {\n sub?: string;\n org_id?: string;\n email?: string;\n}\n\ninterface StoredCreds {\n access_token?: string;\n user_id?: string;\n org_id?: string;\n email?: string;\n}\n\nfunction decodeJwtPayload(token: string): WorkOSClaims | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const json = Buffer.from(parts[1], 'base64url').toString('utf-8');\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === 'object') return parsed as WorkOSClaims;\n return null;\n } catch {\n return null;\n }\n}\n\nfunction loadCredentialsIdentity(): { user_id?: string; org_id?: string; email?: string } {\n if (!existsSync(CREDS_PATH)) return {};\n let raw: string;\n try {\n raw = readFileSync(CREDS_PATH, 'utf-8');\n } catch {\n return {};\n }\n\n let creds: StoredCreds;\n try {\n creds = JSON.parse(raw) as StoredCreds;\n } catch {\n return {};\n }\n\n const out: { user_id?: string; org_id?: string; email?: string } = {};\n if (creds.user_id) out.user_id = creds.user_id;\n if (creds.org_id) out.org_id = creds.org_id;\n if (creds.email) out.email = creds.email;\n\n if (creds.access_token && (!out.user_id || !out.org_id || !out.email)) {\n const claims = decodeJwtPayload(creds.access_token);\n if (claims) {\n if (!out.user_id && claims.sub) out.user_id = claims.sub;\n if (!out.org_id && claims.org_id) out.org_id = claims.org_id;\n if (!out.email && claims.email) out.email = claims.email;\n }\n }\n\n return out;\n}\n\nfunction hostnameHash(): string {\n try {\n return createHash('sha256').update(hostname()).digest('hex').slice(0, 16);\n } catch {\n return '';\n }\n}\n\nexport function getIdentity(): IdentityContext {\n if (cached) return cached;\n\n let cliVersion = '0.0.0';\n try {\n cliVersion = __SYNKRO_CLI_VERSION__;\n } catch { /* declare-only at type-check time, defined at build/runtime */ }\n\n const creds = loadCredentialsIdentity();\n const meta = readMetaCache();\n\n cached = {\n install_id: meta.install_id,\n user_id: creds.user_id,\n org_id: creds.org_id,\n email: creds.email,\n cli_version: cliVersion,\n platform: platform(),\n hostname_hash: hostnameHash(),\n node_version: process.version,\n };\n return cached;\n}\n","/**\n * Synchronous telemetry emit.\n *\n * Appends one JSON line per event to ~/.synkro/telemetry-pending.jsonl.\n * Sync, fast, works in any runtime (node, bun) and tolerates a missing\n * pglite — drain.ts later batches these into pglite via postgres.js.\n *\n * appendFileSync with flag:'a' is atomic per call for writes <= PIPE_BUF\n * (≥4KB on POSIX) so concurrent emits from multiple hook processes don't\n * tear lines. Events are kept compact (single-line JSON, no pretty-print)\n * to stay under that threshold.\n *\n * Lock-free BY DESIGN: hooks and the compiled binary depend on emit staying a\n * plain synchronous appendFileSync — no async, no locking. flush() owns ALL\n * queue rotation/locking. When flush rotates the live queue out from under us\n * (renames it to a .processing sibling), O_APPEND makes this safe with zero\n * coordination: an emit that already opened the old inode lands in the rotated\n * file (which flush processes), and any later emit reopens the path fresh,\n * recreating an empty live queue. Either way, no event is lost.\n *\n * Never throws — telemetry must not take the CLI or hooks down.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { execFileSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { getIdentity } from './identity.js';\nimport { readMetaCache } from './metaCache.js';\nimport type { EmitOptions, EventContext, EventType } from './types.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nexport const PENDING_PATH = join(SYNKRO_DIR, 'telemetry-pending.jsonl');\n\ninterface GitInfo {\n branch: string | null;\n sha: string | null;\n}\n\n// One git-exec per (process, cwd) — keeps a tight loop of hook emits fast.\nconst gitCache = new Map<string, GitInfo>();\n\nfunction deriveGit(cwd: string): GitInfo {\n const cached = gitCache.get(cwd);\n if (cached) return cached;\n const info: GitInfo = { branch: null, sha: null };\n try {\n const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 500, encoding: 'utf-8',\n }).trim();\n if (branch) info.branch = branch;\n } catch {}\n try {\n const sha = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 500, encoding: 'utf-8',\n }).trim();\n if (sha) info.sha = sha;\n } catch {}\n gitCache.set(cwd, info);\n return info;\n}\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) {\n mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n }\n}\n\n// PRIVACY BOUNDARY: telemetry NEVER collects the grader's free-text explanations\n// — they can quote the user's rule text, which is IP. As a guard independent of\n// any caller, drop `reasoning`/`raw_response` outright and truncate `reason` to a\n// short reason CODE, so no free-text rule content can reach the queue (and thus\n// the cloud). Emit sites already pass constant reason codes; this makes it\n// impossible to regress.\nconst REASON_CODE_MAX = 200;\nfunction redactContext(context: unknown): unknown {\n if (!context || typeof context !== 'object' || Array.isArray(context)) return context;\n const c = context as Record<string, unknown>;\n if (!('reasoning' in c) && !('raw_response' in c) && typeof c.reason !== 'string') return context;\n const out: Record<string, unknown> = { ...c };\n delete out.reasoning;\n delete out.raw_response;\n if (typeof out.reason === 'string' && out.reason.length > REASON_CODE_MAX) {\n out.reason = out.reason.slice(0, REASON_CODE_MAX);\n }\n return out;\n}\n\nexport function emit<T extends EventType>(\n eventType: T,\n context: EventContext[T],\n opts?: EmitOptions,\n): void {\n try {\n const meta = readMetaCache();\n if (!meta.enabled) return;\n\n const identity = getIdentity();\n const cwd = opts?.cwd ?? process.cwd();\n const git = deriveGit(cwd);\n const emitter = process.env.SYNKRO_TELEMETRY_EMITTER || 'bootstrap';\n\n const row = {\n client_event_id: randomUUID(),\n event_type: eventType,\n occurred_at: new Date().toISOString(),\n\n install_id: identity.install_id,\n user_id: identity.user_id ?? null,\n org_id: identity.org_id ?? null,\n email: identity.email ?? null,\n\n cli_version: identity.cli_version,\n emitter,\n\n agent_kind: opts?.agent_kind ?? null,\n cc_session_id: opts?.cc_session_id ?? null,\n cc_tool_use_id: opts?.cc_tool_use_id ?? null,\n cc_turn_id: opts?.cc_turn_id ?? null,\n cc_model: opts?.cc_model ?? null,\n permission_mode: opts?.permission_mode ?? null,\n\n cwd,\n repo: opts?.repo ?? null,\n git_branch: git.branch,\n git_sha: git.sha,\n\n policy_name: opts?.policy_name ?? null,\n capture_depth: opts?.capture_depth ?? null,\n\n platform: identity.platform,\n hostname_hash: identity.hostname_hash || null,\n node_version: identity.node_version,\n\n context: redactContext(context),\n };\n\n ensureDir();\n appendFileSync(PENDING_PATH, JSON.stringify(row) + '\\n', { mode: 0o600 });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry emit(${eventType}) failed: ${msg}\\n`);\n }\n}\n","/**\n * Telemetry store — pglite via postgres.js.\n *\n * The actual database is the long-running PGlite instance inside the\n * synkro-server docker container (`pglite-bootstrap.ts`). We connect over\n * the container's host-mapped TCP port (default 127.0.0.1:15433) using\n * postgres.js — same wire protocol as a real Postgres.\n *\n * Implication: when `synkro start` hasn't run (or the container is down),\n * connectDb() returns null. Telemetry collection still works — emit()\n * writes JSONL to disk; drain.ts ships those rows once the container is\n * back up.\n *\n * Tables: telemetry_events, telemetry_meta. Live alongside synkro-server's\n * own tables (guard_checks, scans, …) in the same `postgres` database.\n */\n\nimport postgres from 'postgres';\n\nconst PGLITE_HOST = process.env.SYNKRO_PGLITE_HOST || '127.0.0.1';\nconst PGLITE_PORT = parseInt(\n process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || '15433',\n 10,\n);\nconst PGLITE_USER = process.env.SYNKRO_PGLITE_USER || 'postgres';\nconst PGLITE_DB = process.env.SYNKRO_PGLITE_DB || 'postgres';\n\nconst SCHEMA_VERSION = 1;\n\nexport type Sql = ReturnType<typeof postgres>;\n\nlet cached: Sql | null = null;\nlet migrated = false;\n\n// A failed probe latches so we don't hammer a truly-down port on every emit —\n// but only for INIT_RETRY_MS. A long-lived process (REPL, daemon) that first\n// touched pglite while the container was down still reconnects once it's back.\nconst INIT_RETRY_MS = 30_000;\nlet initFailedAt = 0;\n\nconst MIGRATION_V1 = `\n CREATE TABLE IF NOT EXISTS telemetry_events (\n id BIGSERIAL PRIMARY KEY,\n client_event_id TEXT NOT NULL UNIQUE,\n\n event_type TEXT NOT NULL,\n occurred_at TIMESTAMPTZ NOT NULL,\n\n install_id TEXT NOT NULL,\n user_id TEXT,\n org_id TEXT,\n email TEXT,\n\n cli_version TEXT NOT NULL,\n emitter TEXT NOT NULL,\n\n agent_kind TEXT,\n cc_session_id TEXT,\n cc_tool_use_id TEXT,\n cc_turn_id TEXT,\n cc_model TEXT,\n permission_mode TEXT,\n\n cwd TEXT,\n repo TEXT,\n git_branch TEXT,\n git_sha TEXT,\n\n policy_name TEXT,\n capture_depth TEXT,\n\n platform TEXT NOT NULL,\n hostname_hash TEXT,\n node_version TEXT,\n\n context JSONB NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS telemetry_events_by_type_time\n ON telemetry_events (event_type, occurred_at DESC);\n CREATE INDEX IF NOT EXISTS telemetry_events_by_session\n ON telemetry_events (cc_session_id, occurred_at) WHERE cc_session_id IS NOT NULL;\n\n CREATE TABLE IF NOT EXISTS telemetry_meta (\n k TEXT PRIMARY KEY,\n v TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS sessions_command_history (\n id BIGSERIAL PRIMARY KEY,\n command TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n\n CREATE TABLE IF NOT EXISTS sessions_project_keys (\n slug TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n project_name TEXT NOT NULL,\n api_key TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n`;\n\n/**\n * Open / return the postgres.js client pointed at the container's PGlite.\n * Returns null if the container is unreachable; callers (drain, stats, …)\n * surface that condition explicitly. Never throws.\n */\nexport async function connectDb(): Promise<Sql | null> {\n if (cached) return cached;\n if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;\n try {\n const sql = postgres({\n host: PGLITE_HOST,\n port: PGLITE_PORT,\n username: PGLITE_USER,\n database: PGLITE_DB,\n // max:1 mirrors synkro-server — PGlite is single-threaded; multiple\n // sockets just queue at the multiplexer.\n max: 1,\n connect_timeout: 2,\n idle_timeout: 30,\n onnotice: () => {},\n });\n // Probe with a no-op so a dead port fails fast instead of on first query.\n await sql`SELECT 1`;\n cached = sql;\n initFailedAt = 0;\n if (!migrated) await runMigrations(sql);\n return cached;\n } catch (err) {\n initFailedAt = Date.now();\n // Container-down is the common case — silent by default. Surface only\n // when explicitly debugging: SYNKRO_TELEMETRY_DEBUG=1.\n if (process.env.SYNKRO_TELEMETRY_DEBUG === '1') {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry pglite unreachable at ${PGLITE_HOST}:${PGLITE_PORT}: ${msg}\\n`);\n }\n return null;\n }\n}\n\nasync function runMigrations(sql: Sql): Promise<void> {\n try {\n await sql.unsafe(MIGRATION_V1);\n const rows = await sql`SELECT v FROM telemetry_meta WHERE k = 'schema_version'`;\n const current = rows.length ? Number(rows[0].v) : 0;\n if (current !== SCHEMA_VERSION) {\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('schema_version', ${String(SCHEMA_VERSION)})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n }\n migrated = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry migration failed: ${msg}\\n`);\n }\n}\n\n/** Close the cached connection. Call from process exit handlers. */\nexport async function closeDb(): Promise<void> {\n if (!cached) return;\n try { await cached.end({ timeout: 1 }); } catch {}\n cached = null;\n}\n\n/**\n * Reset module state — for tests / dev. Drops cached connection without\n * closing it (caller's responsibility if needed).\n */\nexport function resetDbForTests(): void {\n cached = null;\n initFailedAt = 0;\n migrated = false;\n}\n","/**\n * The telemetry queue: ~/.synkro/telemetry-pending.jsonl.\n *\n * The JSONL file is the single source of truth for undelivered events. Two\n * independent sinks consume it:\n *\n * 1. REMOTE flush (flush.ts) — POST to /api/v1/telemetry/flush (central\n * Timescale). This is the authoritative store. Works with no local\n * container, so it's what gives cloud mode telemetry parity.\n * 2. LOCAL pglite (this file, drainToPglite) — an OPTIONAL, idempotent,\n * NON-destructive copy into the local container's pglite, purely so the\n * local Deno dashboard has something to read. Only happens when a local\n * container is up (local mode). It never removes lines from the queue —\n * flush owns line removal once an event is delivered to every available\n * destination.\n *\n * So: drainToPglite() = \"mirror the queue into the local DB for the dashboard\",\n * flush() = \"deliver the queue to its real destinations and prune what landed\".\n *\n * Nothing here throws; callers get structured results.\n */\n\nimport { openSync, closeSync, fstatSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, mkdirSync, renameSync, existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { connectDb, type Sql } from './db.js';\nimport { PENDING_PATH } from './emit.js';\nimport { patchMetaCache } from './metaCache.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst QUEUE_LOCK = join(SYNKRO_DIR, '.telemetry-queue.lock');\n\n// Rotate target for flush: the live queue is atomically renamed here, processed,\n// then unlinked. A crash leaves it orphaned; the next flush folds it back in\n// (recoverProcessing). Sibling of PENDING_PATH so it shares the same dir/perms.\nconst PROCESSING_PATH = `${PENDING_PATH}.processing`;\n\n// Insert chunk size — keeps a single multi-row INSERT modest for PGlite's\n// single-threaded executor and well under Postgres' parameter ceiling.\nconst CHUNK = 500;\nconst STALE_LOCK_MS = 60_000;\n// Safety cap on the queue. If no store is reachable for a long stretch the queue\n// would otherwise grow without bound; past this many rows the OLDEST are dropped\n// (once, non-silently) rather than risk an unbounded pending file.\nconst MAX_QUEUE_ROWS = 50_000;\n\nexport interface PendingRow {\n client_event_id: string;\n event_type: string;\n occurred_at: string;\n install_id: string;\n user_id: string | null;\n org_id: string | null;\n email: string | null;\n cli_version: string;\n emitter: string;\n agent_kind: string | null;\n cc_session_id: string | null;\n cc_tool_use_id: string | null;\n cc_turn_id: string | null;\n cc_model: string | null;\n permission_mode: string | null;\n cwd: string | null;\n repo: string | null;\n git_branch: string | null;\n git_sha: string | null;\n policy_name: string | null;\n capture_depth: string | null;\n platform: string;\n hostname_hash: string | null;\n node_version: string;\n context: unknown;\n}\n\nexport interface DrainResult {\n ok: boolean;\n ingested: number;\n pending: number;\n reason?: 'db_unavailable' | 'no_pending' | 'error';\n error?: string;\n}\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n}\n\n/**\n * Acquire the queue lock. Only writers that REWRITE the queue (flush) need it;\n * non-destructive readers (drainToPglite) don't.\n *\n * Race-free by construction: we always try the atomic exclusive create FIRST\n * (openSync 'wx'). Only when that fails do we consider stealing a stale lock,\n * and the steal re-attempts the SAME atomic 'wx' create — which is the single\n * arbiter, so at most one caller wins. There is no existsSync/stat → act\n * window (CWE-362): every decision is made from the failure of an atomic op,\n * and staleness is read from the lock's own fd (fstat), not a path re-stat.\n */\nexport function acquireQueueLock(): number | null {\n ensureDir();\n try {\n return openSync(QUEUE_LOCK, 'wx');\n } catch {\n // Lock already exists. Inspect it via its own fd (no path TOCTOU).\n let fd: number;\n try {\n fd = openSync(QUEUE_LOCK, 'r');\n } catch {\n // Vanished between our create-attempt and open — retry the create once.\n try { return openSync(QUEUE_LOCK, 'wx'); } catch { return null; }\n }\n let stale = false;\n try { stale = Date.now() - fstatSync(fd).mtimeMs > STALE_LOCK_MS; } catch {}\n try { closeSync(fd); } catch {}\n if (!stale) return null;\n // Stale holder (crashed >60s ago). Remove and re-create atomically; the\n // 'wx' create is the arbiter, so concurrent stealers can't both win. A rare\n // double-steal is benign here — flush rewrites the queue atomically and the\n // server dedups on client_event_id, so at worst a few events re-send.\n try { unlinkSync(QUEUE_LOCK); } catch {}\n try { return openSync(QUEUE_LOCK, 'wx'); } catch { return null; }\n }\n}\n\nexport function releaseQueueLock(fd: number): void {\n try { closeSync(fd); } catch {}\n try { unlinkSync(QUEUE_LOCK); } catch {}\n}\n\n/** Parse a JSONL queue file into valid rows + a count of malformed lines. */\nfunction parseQueueFile(path: string): { rows: PendingRow[]; malformed: number } {\n if (!existsSync(path)) return { rows: [], malformed: 0 };\n let raw: string;\n try { raw = readFileSync(path, 'utf-8'); } catch { return { rows: [], malformed: 0 }; }\n if (!raw) return { rows: [], malformed: 0 };\n const rows: PendingRow[] = [];\n let malformed = 0;\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const r = JSON.parse(line) as PendingRow;\n if (r && r.client_event_id && r.event_type) rows.push(r);\n else malformed++;\n } catch {\n malformed++;\n }\n }\n return { rows, malformed };\n}\n\n/** Parse the live JSONL queue. Returns valid rows + a count of malformed lines. */\nexport function readQueue(): { rows: PendingRow[]; malformed: number } {\n return parseQueueFile(PENDING_PATH);\n}\n\n/** Atomically rewrite the queue with exactly `rows` (empty → empty file). */\nexport function rewriteQueue(rows: PendingRow[]): void {\n ensureDir();\n const body = rows.length ? rows.map((r) => JSON.stringify(r)).join('\\n') + '\\n' : '';\n const tmp = `${PENDING_PATH}.tmp`;\n try {\n writeFileSync(tmp, body, { mode: 0o600 });\n renameSync(tmp, PENDING_PATH);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry queue rewrite failed: ${msg}\\n`);\n }\n}\n\n// ── Rotate-then-process (flush queue integrity) ──────────────────────────────\n// flush must NOT overwrite events emitted while it works. Rather than reading a\n// snapshot and rewriting the whole file at the end (which destroys concurrent\n// appends), it ROTATES the live queue into PROCESSING_PATH, works only from\n// there, then appends any undelivered survivors back. All three helpers assume\n// the caller holds the queue lock; emit() stays lock-free and just keeps\n// appending to a fresh PENDING_PATH that O_APPEND recreates for it.\n\n/**\n * Crash recovery: if a previous flush rotated the queue into .processing but\n * died before clearing it, fold those rows back onto the live queue so the next\n * cycle reprocesses them. No-op when nothing is orphaned. MUST run BEFORE\n * rotateQueueForProcessing so the orphan isn't clobbered by the rename.\n * Duplicates are harmless (server dedups on client_event_id; pglite ON CONFLICT).\n */\nexport function recoverProcessing(): void {\n if (!existsSync(PROCESSING_PATH)) return;\n ensureDir();\n let body: string;\n try { body = readFileSync(PROCESSING_PATH, 'utf-8'); } catch { return; }\n try {\n if (body.trim()) appendFileSync(PENDING_PATH, body.endsWith('\\n') ? body : body + '\\n', { mode: 0o600 });\n unlinkSync(PROCESSING_PATH);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry queue recovery failed: ${msg}\\n`);\n }\n}\n\n/**\n * Atomically rotate the live queue into .processing and return its rows. A\n * concurrent lock-free emit either already opened the old inode (its line lands\n * in the rotated file we process) or reopens the path afterwards (O_APPEND\n * recreates an empty live queue) — so nothing emitted mid-cycle is lost. Returns\n * an empty set when there is nothing to rotate. Enforces the max-queue cap: past\n * MAX_QUEUE_ROWS the OLDEST rows are dropped with one non-silent warning.\n */\nexport function rotateQueueForProcessing(): { rows: PendingRow[]; malformed: number } {\n ensureDir();\n if (!existsSync(PENDING_PATH)) return { rows: [], malformed: 0 };\n try {\n renameSync(PENDING_PATH, PROCESSING_PATH);\n } catch {\n return { rows: [], malformed: 0 }; // vanished — treat as empty this cycle\n }\n const { rows, malformed } = parseQueueFile(PROCESSING_PATH);\n if (rows.length > MAX_QUEUE_ROWS) {\n const dropped = rows.length - MAX_QUEUE_ROWS;\n process.stderr.write(`[synkro] telemetry queue over ${MAX_QUEUE_ROWS}-row cap: dropping ${dropped} oldest event(s)\\n`);\n return { rows: rows.slice(dropped), malformed };\n }\n return { rows, malformed };\n}\n\n/**\n * Close out a flush cycle: append undelivered survivors back onto the LIVE queue\n * (which may have grown from concurrent emits), then drop the processed file.\n * Order is crash-safe — survivors are persisted BEFORE the unlink, so a crash in\n * between just re-folds (and idempotently re-processes) them next run rather than\n * losing them. Pass [] to simply discard a fully-drained processing file. The\n * append is a single call, matching emit()'s own atomicity contract.\n */\nexport function finalizeProcessing(survivors: PendingRow[]): void {\n if (survivors.length) {\n ensureDir();\n const body = survivors.map((r) => JSON.stringify(r)).join('\\n') + '\\n';\n try {\n appendFileSync(PENDING_PATH, body, { mode: 0o600 });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry survivor re-append failed: ${msg}\\n`);\n return; // keep the processing file if survivors couldn't be saved\n }\n }\n try { unlinkSync(PROCESSING_PATH); } catch {}\n}\n\n/**\n * Bulk-insert rows into telemetry_events, idempotent on client_event_id.\n * Chunked. Returns the number of NEW rows inserted (conflicts skipped).\n *\n * Resilient per chunk: a poison chunk/row is logged to stderr and SKIPPED so it\n * can't fail the whole batch — one bad row must not wedge queue removal for\n * every other row (see flush.ts). When `mirroredIds` is supplied, the id of\n * every row in a chunk that landed is added to it, so a caller for which pglite\n * is the ONLY store (local-only mode) can tell which rows are safe to drop.\n */\nexport async function insertEvents(sql: Sql, rows: PendingRow[], mirroredIds?: Set<string>): Promise<number> {\n let inserted = 0;\n for (let i = 0; i < rows.length; i += CHUNK) {\n const batch = rows.slice(i, i + CHUNK);\n const payload = batch.map((r) => ({\n client_event_id: r.client_event_id,\n event_type: r.event_type,\n occurred_at: r.occurred_at,\n install_id: r.install_id,\n user_id: r.user_id,\n org_id: r.org_id,\n email: r.email,\n cli_version: r.cli_version,\n emitter: r.emitter,\n agent_kind: r.agent_kind,\n cc_session_id: r.cc_session_id,\n cc_tool_use_id: r.cc_tool_use_id,\n cc_turn_id: r.cc_turn_id,\n cc_model: r.cc_model,\n permission_mode: r.permission_mode,\n cwd: r.cwd,\n repo: r.repo,\n git_branch: r.git_branch,\n git_sha: r.git_sha,\n policy_name: r.policy_name,\n capture_depth: r.capture_depth,\n platform: r.platform,\n hostname_hash: r.hostname_hash,\n node_version: r.node_version,\n // Pass the object, NOT a pre-stringified string: postgres.js JSON-encodes\n // jsonb-bound values itself; stringifying would double-encode into a jsonb\n // string scalar (context->>'k' would then return null for every key).\n // Cast narrows `unknown ?? {}` (which TS widens to the universal `{}`) to a\n // concrete JSON object so postgres.js's helper accepts it as a jsonb value.\n context: (r.context ?? {}) as Record<string, unknown>,\n }));\n // postgres.js's multi-row insert helper resolves its row generic to `never`\n // for a heterogeneous nullable shape, so the columns-typed overload won't\n // match our concrete payload. Cast just the rows arg to `any` — the helper\n // still returns a proper Helper for the template tag. Runtime is correct and\n // smoke-tested; only the static overload is bypassed.\n try {\n const res = await sql`\n INSERT INTO telemetry_events ${sql(payload as unknown as Record<string, never>[],\n 'client_event_id', 'event_type', 'occurred_at',\n 'install_id', 'user_id', 'org_id', 'email',\n 'cli_version', 'emitter',\n 'agent_kind', 'cc_session_id', 'cc_tool_use_id', 'cc_turn_id', 'cc_model', 'permission_mode',\n 'cwd', 'repo', 'git_branch', 'git_sha',\n 'policy_name', 'capture_depth',\n 'platform', 'hostname_hash', 'node_version',\n 'context'\n )}\n ON CONFLICT (client_event_id) DO NOTHING\n `;\n inserted += res.count ?? batch.length;\n // Chunk landed: every row is now present in pglite (freshly inserted or\n // already there via ON CONFLICT), so it's safe to mark as mirrored.\n if (mirroredIds) for (const r of batch) mirroredIds.add(r.client_event_id);\n } catch (err) {\n // Poison chunk — skip it and keep going; its rows stay unmarked so flush\n // keeps them queued for a later retry instead of failing the whole batch.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry local mirror chunk failed (skipped ${batch.length}): ${msg}\\n`);\n }\n }\n return inserted;\n}\n\n/**\n * NON-destructive mirror of the queue into local pglite, for the dashboard.\n * No-op (db_unavailable) when there's no local container — that's expected in\n * cloud mode, where the dashboard reads central instead. Never removes queue\n * lines (flush owns that). Safe to call repeatedly (idempotent insert).\n */\nexport async function drainToPglite(): Promise<DrainResult> {\n const { rows } = readQueue();\n if (rows.length === 0) return { ok: true, ingested: 0, pending: 0, reason: 'no_pending' };\n const sql = await connectDb();\n if (!sql) return { ok: false, ingested: 0, pending: rows.length, reason: 'db_unavailable' };\n try {\n const ingested = await insertEvents(sql, rows);\n patchMetaCache({ last_drained_at: new Date().toISOString() });\n return { ok: true, ingested, pending: rows.length };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { ok: false, ingested: 0, pending: rows.length, reason: 'error', error: msg };\n }\n}\n","/**\n * Telemetry opt-out toggles.\n *\n * Two places stay in sync:\n * 1. metaCache.json — sync source for emit() hot path; the authoritative\n * copy for the opt-out flags.\n * 2. config.env — shell-sourced by hooks (informational; hooks still emit\n * via JSONL even if the env flag is set, so the gate is metaCache).\n *\n * Defaults: both flags 'yes'. Each toggle emits a `config_change` event\n * BEFORE applying so disabling telemetry still records the disable event.\n */\n\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { emit } from './emit.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nconst KEY_ENABLED = 'SYNKRO_TELEMETRY_ENABLED';\nconst KEY_REMOTE = 'SYNKRO_TELEMETRY_REMOTE_FLUSH';\n\nexport interface TelemetryState {\n enabled: boolean;\n remoteFlushEnabled: boolean;\n}\n\nfunction sanitize(raw: string, maxLen = 256): string {\n return raw.replace(/[^\\x20-\\x7E]/g, '').slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nfunction writeConfigEnvFlag(key: string, value: string): void {\n const safe = sanitize(value, 8);\n const line = `${key}=${shellQuoteSingle(safe)}`;\n let content = '';\n if (existsSync(CONFIG_PATH)) {\n try { content = readFileSync(CONFIG_PATH, 'utf-8'); } catch { content = ''; }\n }\n const re = new RegExp(`^${key}=.*$`, 'm');\n if (re.test(content)) {\n content = content.replace(re, line);\n } else {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n';\n content += `${line}\\n`;\n }\n try {\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n try { chmodSync(CONFIG_PATH, 0o600); } catch {}\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry config write failed: ${msg}\\n`);\n }\n}\n\n/**\n * Current state — reads metaCache (sync, always available), the authoritative\n * copy for the opt-out flags.\n */\nexport async function getTelemetryState(): Promise<TelemetryState> {\n const meta = readMetaCache();\n return {\n enabled: meta.enabled,\n remoteFlushEnabled: meta.remote_flush_enabled,\n };\n}\n\n/** Sync variant for the emit() path — never awaits pglite. */\nexport function getTelemetryStateSync(): TelemetryState {\n const meta = readMetaCache();\n return {\n enabled: meta.enabled,\n remoteFlushEnabled: meta.remote_flush_enabled,\n };\n}\n\nexport async function setTelemetryState(opts: {\n enabled?: boolean;\n remoteFlushEnabled?: boolean;\n}): Promise<void> {\n const current = await getTelemetryState();\n\n const patch: { enabled?: boolean; remote_flush_enabled?: boolean } = {};\n\n if (opts.enabled !== undefined && opts.enabled !== current.enabled) {\n emit('config_change', {\n setting: 'telemetry_enabled',\n from: current.enabled ? 'yes' : 'no',\n to: opts.enabled ? 'yes' : 'no',\n source: 'cli',\n });\n patch.enabled = opts.enabled;\n writeConfigEnvFlag(KEY_ENABLED, opts.enabled ? 'yes' : 'no');\n }\n\n if (opts.remoteFlushEnabled !== undefined\n && opts.remoteFlushEnabled !== current.remoteFlushEnabled) {\n emit('config_change', {\n setting: 'remote_flush_enabled',\n from: current.remoteFlushEnabled ? 'yes' : 'no',\n to: opts.remoteFlushEnabled ? 'yes' : 'no',\n source: 'cli',\n });\n patch.remote_flush_enabled = opts.remoteFlushEnabled;\n writeConfigEnvFlag(KEY_REMOTE, opts.remoteFlushEnabled ? 'yes' : 'no');\n }\n\n if (Object.keys(patch).length > 0) patchMetaCache(patch);\n}\n","/**\n * The telemetry WIRE CONTRACT — the single source of truth for the event shape,\n * the POST /flush request/response, the batching limits, and the retry policy,\n * shared by every hop of the pipeline:\n *\n * emit() (CLI hooks) → flush() (CLI host) → cloud flush (container) → ingest (Worker)\n *\n * Zero runtime deps and no imports, so the SAME file can be consumed by:\n * - the CLI (bundled by tsup into the binary),\n * - the synkro-server container (bun; Dockerfile-copied + relative-imported),\n * - the Cloudflare Worker (workerd; bundled by wrangler/esbuild).\n *\n * Anything about \"what a telemetry event looks like on the wire\" or \"how a flush\n * is batched/retried\" lives HERE and nowhere else — previously these were\n * re-declared in flush.ts, telemetryCloudFlush.ts, and telemetryWorker.ts, and\n * drifted (the container's batching dropped the byte cap + unrecoverable-drop).\n */\n\n/** Ingest route, relative to the gateway/worker base URL. */\nexport const TELEMETRY_FLUSH_PATH = '/api/v1/telemetry/flush';\n\n// ── client-side batching (producer → POST) ──────────────────────────────────\n/** Max rows per POST batch. */\nexport const BATCH_LIMIT = 200;\n/** Max serialized bytes per POST batch (kept under the server body cap). */\nexport const BATCH_BYTES = 1024 * 1024;\n/** Per-request timeout for a flush POST. */\nexport const REQUEST_TIMEOUT_MS = 15_000;\n\n// ── server-side ingest limits (must stay ≥ the client's batch sizing) ────────\n/** Max events accepted in one /flush request. */\nexport const MAX_EVENTS = 500;\n/** Body byte cap the ingest rejects above (413). */\nexport const TELEMETRY_BODY_MAX = 4 * 1024 * 1024;\n/** Rows per D1 sub-batch (one implicit transaction). */\nexport const D1_BATCH = 50;\n\n// ── retry policy ─────────────────────────────────────────────────────────────\n/**\n * HTTP statuses where retrying can NEVER succeed — the payload is poison, so the\n * producer must DROP the batch (advance past it) rather than wedge the queue on\n * it forever. 400 malformed, 409 content-conflict (an event id already exists\n * with DIFFERENT content — retrying sends the same conflicting bytes), 413\n * too-large, 422 unprocessable.\n */\nexport const UNRECOVERABLE_STATUSES: readonly number[] = [400, 409, 413, 422];\nexport function isUnrecoverable(status: number): boolean {\n return UNRECOVERABLE_STATUSES.includes(status);\n}\n\n// ── the event, on the wire ───────────────────────────────────────────────────\n/**\n * Canonical, order-stable field list. Producers map their rows to these keys;\n * the ingest reads them; the container INSERTs exactly this set. Keeping one\n * list means adding a column is a one-line change here, not four.\n */\nexport const TELEMETRY_WIRE_FIELDS = [\n 'client_event_id', 'install_id', 'user_id', 'org_id', 'email', 'event_type',\n 'occurred_at', 'cli_version', 'emitter', 'agent_kind', 'cc_session_id',\n 'cc_tool_use_id', 'cc_turn_id', 'cc_model', 'permission_mode', 'cwd', 'repo',\n 'git_branch', 'git_sha', 'policy_name', 'capture_depth', 'platform',\n 'hostname_hash', 'node_version', 'context',\n] as const;\nexport type TelemetryWireField = typeof TELEMETRY_WIRE_FIELDS[number];\n\n/**\n * One telemetry event on the wire. Everything is nullable except the idempotency\n * key (`install_id` + `client_event_id`), `event_type`, and `occurred_at`.\n * NOTE: `org_id` here is ADVISORY (client-reported) — the ingest ALWAYS stamps\n * the stored org from the authenticated principal, never from this field.\n */\nexport interface TelemetryEventWire {\n client_event_id: string;\n install_id: string;\n event_type: string;\n occurred_at: string; // ISO-8601; ingest normalizes to UTC\n user_id?: string | null;\n org_id?: string | null; // advisory only — see note above\n email?: string | null;\n cli_version?: string | null;\n emitter?: string | null;\n agent_kind?: string | null;\n cc_session_id?: string | null;\n cc_tool_use_id?: string | null;\n cc_turn_id?: string | null;\n cc_model?: string | null;\n permission_mode?: string | null;\n cwd?: string | null;\n repo?: string | null;\n git_branch?: string | null;\n git_sha?: string | null;\n policy_name?: string | null;\n capture_depth?: string | null;\n platform?: string | null;\n hostname_hash?: string | null;\n node_version?: string | null;\n context?: unknown; // object; the ingest JSON.stringifies it once\n}\n\n/** POST /api/v1/telemetry/flush request body. */\nexport interface FlushRequest {\n install_id?: string;\n cli_version?: string;\n platform?: string;\n events: TelemetryEventWire[];\n}\n\n/** POST /api/v1/telemetry/flush response body. */\nexport interface FlushResponse {\n accepted: number; // events in the request that passed validation\n inserted: number; // newly stored (not previously seen)\n duplicates?: number; // id already present with IDENTICAL content — skipped, idempotent\n conflicts?: string[]; // client_event_ids present with DIFFERENT content (409); NOT stored\n error?: string; // set on a non-2xx (e.g. 'content_conflict')\n}\n\n/**\n * Split rows into POST batches bounded by BOTH row count and serialized bytes.\n * Shared so the host flush and the container flush can't drift again. `sizeOf`\n * returns the serialized byte length of one row's wire form (e.g.\n * `JSON.stringify(rowToWire(r)).length`).\n */\nexport function batchBySizeAndCount<T>(\n rows: readonly T[],\n sizeOf: (row: T) => number,\n opts?: { limit?: number; maxBytes?: number },\n): T[][] {\n const limit = opts?.limit ?? BATCH_LIMIT;\n const maxBytes = opts?.maxBytes ?? BATCH_BYTES;\n const batches: T[][] = [];\n let cur: T[] = [];\n let bytes = 0;\n for (const r of rows) {\n const sz = sizeOf(r);\n if (cur.length > 0 && (cur.length >= limit || bytes + sz > maxBytes)) {\n batches.push(cur);\n cur = [];\n bytes = 0;\n }\n cur.push(r);\n bytes += sz;\n }\n if (cur.length) batches.push(cur);\n return batches;\n}\n","/**\n * Telemetry flush — the queue processor.\n *\n * ROTATE-THEN-PROCESS: under the queue lock, flush atomically rotates the live\n * JSONL queue aside (drain.rotateQueueForProcessing) and works ONLY from the\n * rotated copy, so concurrent lock-free emit()s keep appending to a fresh queue\n * and nothing emitted mid-cycle is lost. Undelivered survivors are appended back\n * onto the live queue at the end (drain.finalizeProcessing). A crash between\n * rotate and finalize is recovered on the next run (drain.recoverProcessing).\n *\n * Destinations for each event:\n * - REMOTE: POST /api/v1/telemetry/flush (central Timescale). The\n * AUTHORITATIVE store. Needs no local container, so this is what gives\n * cloud mode parity with local mode.\n * - LOCAL pglite: best-effort idempotent mirror for the local dashboard. It is\n * a CACHE, not a gate — it NEVER blocks removal of a remote-confirmed row.\n *\n * Survivor rule (a row stays only while it still owes a store it hasn't reached):\n * remote on → survives until the remote 2xx-confirms it (pglite ignored).\n * remote off → survives until pglite (then the only store) has mirrored it.\n * no store → survives (nothing reachable; retry next cycle).\n *\n * Mode matrix:\n * cloud (remote on, no pglite) → POST → remove. dashboard = central\n * local (remote on, pglite up) → POST + mirror → remove. dashboard = local or central\n * local-only (remote off, pglite)→ mirror → remove. dashboard = local\n * offline (remote off, no pglite)→ keep, retry later.\n *\n * Throttle (60s) gates only the REMOTE POST and keys off the last SUCCESSFUL\n * delivery (last_flush_ok_at), so a failed cycle never starves the next retry;\n * the local mirror + removal always run so local-only mode drains promptly.\n * Never throws.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { connectDb } from './db.js';\nimport {\n acquireQueueLock, releaseQueueLock, insertEvents,\n recoverProcessing, rotateQueueForProcessing, finalizeProcessing,\n type PendingRow,\n} from './drain.js';\nimport { getIdentity } from './identity.js';\nimport { getTelemetryState } from './optout.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n// The shared wire contract — event shape, batch limits, ingest path, and the\n// unrecoverable-status set — lives in one file so the host flush, the container\n// flush, and the Worker ingest can never drift. See wire.ts.\nimport {\n TELEMETRY_FLUSH_PATH, REQUEST_TIMEOUT_MS, batchBySizeAndCount, isUnrecoverable,\n type TelemetryEventWire, type FlushRequest,\n} from './wire.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\nconst CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(SYNKRO_DIR, 'credentials.json');\n\nconst THROTTLE_MS = 60_000;\n\nexport interface FlushResult {\n sent: number; // delivered to remote (2xx)\n failed: number; // left in the queue (remote/local delivery incomplete)\n mirrored: number; // copied into local pglite for the dashboard\n skipped_reason?: string;\n}\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n let raw: string;\n try { raw = readFileSync(CONFIG_PATH, 'utf-8'); } catch { return {}; }\n for (const line of raw.split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq <= 0) continue;\n const k = t.slice(0, eq).trim();\n let v = t.slice(eq + 1).trim();\n if ((v.startsWith(\"'\") && v.endsWith(\"'\")) || (v.startsWith('\"') && v.endsWith('\"'))) v = v.slice(1, -1);\n out[k] = v;\n }\n return out;\n}\n\nconst DEFAULT_GATEWAY = 'https://api.synkro.sh';\n\n// SSRF guard (CWE-918): config.env / env can be poisoned (stray exports, a\n// hostile op:// ref, etc.), and this POST carries the Bearer JWT — so the host\n// MUST be allowlisted before we send it anywhere. Mirrors assertGatewayAllowed\n// in commands/install.ts: only synkro.sh / *.synkro.sh, plus localhost for dev\n// (and http is permitted ONLY for localhost).\nfunction isLocalhost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1';\n}\n\nfunction isGatewayHostAllowed(host: string): boolean {\n const h = host.toLowerCase();\n return isLocalhost(h) || h === 'synkro.sh' || h.endsWith('.synkro.sh');\n}\n\nfunction resolveGatewayUrl(): string {\n const cfg = readConfigEnv();\n const raw = (cfg.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || '').trim();\n if (!raw) return DEFAULT_GATEWAY;\n let u: URL;\n try { u = new URL(raw); } catch { return DEFAULT_GATEWAY; }\n if (!isGatewayHostAllowed(u.hostname)) return DEFAULT_GATEWAY;\n if (u.protocol === 'https:') return raw.replace(/\\/$/, '');\n if (u.protocol === 'http:' && isLocalhost(u.hostname)) return raw.replace(/\\/$/, '');\n return DEFAULT_GATEWAY; // http to a non-localhost host → reject\n}\n\nfunction loadJwt(): string | null {\n if ((process.env.SYNKRO_STORAGE_MODE || '') === 'cloud') {\n try {\n const mcp = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcp) return mcp;\n } catch {}\n }\n try {\n if (!existsSync(CREDS_PATH)) return null;\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8')) as { access_token?: string };\n return creds.access_token || null;\n } catch { return null; }\n}\n\nfunction rowToWire(r: PendingRow): TelemetryEventWire {\n return {\n client_event_id: r.client_event_id,\n event_type: r.event_type,\n occurred_at: r.occurred_at,\n install_id: r.install_id,\n user_id: r.user_id,\n org_id: r.org_id,\n email: r.email,\n cli_version: r.cli_version,\n emitter: r.emitter,\n agent_kind: r.agent_kind,\n cc_session_id: r.cc_session_id,\n cc_tool_use_id: r.cc_tool_use_id,\n cc_turn_id: r.cc_turn_id,\n cc_model: r.cc_model,\n permission_mode: r.permission_mode,\n cwd: r.cwd,\n repo: r.repo,\n git_branch: r.git_branch,\n git_sha: r.git_sha,\n policy_name: r.policy_name,\n capture_depth: r.capture_depth,\n platform: r.platform,\n hostname_hash: r.hostname_hash,\n node_version: r.node_version,\n context: r.context ?? {},\n };\n}\n\n// Split rows into POST batches bounded by count and serialized bytes, using the\n// shared batcher so the host and container can't drift on their sizing rules.\nfunction batchRows(rows: PendingRow[]): PendingRow[][] {\n return batchBySizeAndCount(rows, (r) => JSON.stringify(rowToWire(r)).length);\n}\n\nexport async function flush(opts?: { force?: boolean }): Promise<FlushResult> {\n const state = await getTelemetryState();\n if (!state.enabled) return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'disabled' };\n\n const lockFd = acquireQueueLock();\n if (lockFd === null) return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'locked' };\n\n try {\n // Crash recovery first: fold any orphaned .processing file (a prior run that\n // rotated then died) back into the live queue so it isn't clobbered below.\n recoverProcessing();\n\n // Rotate-then-process: atomically move the live queue aside and work ONLY\n // from the rotated copy. Concurrent lock-free emits keep appending to a\n // fresh live queue, so nothing emitted during this multi-second cycle is\n // destroyed (fixes the old read-snapshot → rewrite-whole-file data loss).\n const { rows } = rotateQueueForProcessing();\n if (rows.length === 0) {\n finalizeProcessing([]); // drop the (empty) rotated file if one was created\n return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'no_pending' };\n }\n\n const jwt = loadJwt();\n const remoteEnabled = state.remoteFlushEnabled && !!jwt;\n\n // Throttle gates only the remote POST. It keys off last_flush_OK_at (the\n // last SUCCESSFUL delivery), not last_flush_at (last attempt), so a failed\n // cycle never advances the throttle and can't starve the next automatic\n // retry. force bypasses it.\n const meta = readMetaCache();\n let throttled = false;\n if (!opts?.force && meta.last_flush_ok_at) {\n const ms = Date.parse(meta.last_flush_ok_at);\n if (Number.isFinite(ms) && Date.now() - ms < THROTTLE_MS) throttled = true;\n }\n const doRemote = remoteEnabled && !throttled;\n\n // ── Local pglite mirror (best-effort cache; NEVER gates queue removal) ──\n // insertEvents is resilient per chunk and records which rows actually\n // landed in pgliteMirrored, so a poison chunk can't wedge removal of the\n // rows that DID land / that the remote already accepted.\n const sql = await connectDb();\n const pgliteMirrored = new Set<string>();\n let mirrored = 0;\n if (sql) {\n try {\n mirrored = await insertEvents(sql, rows, pgliteMirrored);\n } catch (err) {\n const m = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry local mirror failed: ${m}\\n`);\n }\n }\n\n // ── Remote delivery ───────────────────────────────────────────────────\n const remoteResolved = new Set<string>(); // delivered (2xx) OR dropped (unrecoverable 4xx)\n let sent = 0;\n let remoteErr = '';\n if (doRemote) {\n const endpoint = `${resolveGatewayUrl()}${TELEMETRY_FLUSH_PATH}`;\n const identity = getIdentity();\n for (const batch of batchRows(rows)) {\n const body = JSON.stringify({\n install_id: identity.install_id,\n cli_version: identity.cli_version,\n platform: identity.platform,\n events: batch.map(rowToWire),\n } satisfies FlushRequest);\n let resp: Response | null = null;\n try {\n resp = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwt}` },\n body,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (err) {\n remoteErr = err instanceof Error ? err.message : String(err);\n break; // network error — stop; keep this and remaining batches for retry\n }\n if (resp.ok) {\n for (const r of batch) remoteResolved.add(r.client_event_id);\n sent += batch.length;\n } else if (isUnrecoverable(resp.status)) {\n // Poison batch — will never succeed; drop it so it can't wedge the queue.\n for (const r of batch) remoteResolved.add(r.client_event_id);\n remoteErr = `HTTP ${resp.status} (dropped ${batch.length})`;\n process.stderr.write(`[synkro] telemetry flush dropped ${batch.length} unrecoverable event(s): ${remoteErr}\\n`);\n } else {\n remoteErr = `HTTP ${resp.status}`;\n break; // retryable (5xx/401/429/…) — keep this + remaining for next cycle\n }\n }\n }\n\n // ── Compute survivors (rows to append back onto the live queue) ───────\n // Remote is the authoritative store; local pglite is a best-effort cache\n // that must NEVER block removal of a remote-confirmed row. So a row survives\n // only while it still owes a store it hasn't reached:\n // remote on → until the remote 2xx-confirms it (pglite ignored).\n // remote off → until pglite (then the only store) has mirrored it.\n // no store → always (nothing reachable; retry next cycle).\n const survivors = rows.filter((r) =>\n remoteEnabled\n ? !remoteResolved.has(r.client_event_id)\n : !pgliteMirrored.has(r.client_event_id),\n );\n finalizeProcessing(survivors);\n\n // ── Bookkeeping (in metaCache so it works with no pglite) ─────────────\n if (doRemote) {\n const now = new Date().toISOString();\n const patch: Record<string, string> = { last_flush_at: now };\n if (sent > 0) patch.last_flush_ok_at = now;\n if (remoteErr) patch.last_flush_error = remoteErr;\n patchMetaCache(patch);\n }\n\n const failed = survivors.length;\n let skipped: string | undefined;\n if (!doRemote) skipped = remoteEnabled ? 'throttled' : (state.remoteFlushEnabled ? 'no_jwt' : 'remote_disabled');\n return { sent, failed, mirrored, skipped_reason: skipped };\n } finally {\n releaseQueueLock(lockFd);\n }\n}\n\n/**\n * Spawn a detached child that runs flush() and exits. Re-entrant guard via\n * SYNKRO_TELEMETRY_DETACHED so the post-dispatch flush in bootstrap doesn't\n * recurse through its own `telemetry flush` child.\n */\nexport function flushDetached(): void {\n if (process.env.SYNKRO_TELEMETRY_DETACHED === '1') return;\n try {\n const node = process.execPath;\n const script = process.argv[1];\n if (!script) return;\n const child = spawn(node, [script, 'telemetry', 'flush', '--detached'], {\n stdio: 'ignore',\n detached: true,\n env: { ...process.env, SYNKRO_TELEMETRY_EMITTER: 'flush-detached', SYNKRO_TELEMETRY_DETACHED: '1' },\n });\n child.unref();\n } catch {}\n}\n","/**\n * Read-side queries against pglite — all async because pglite is async.\n *\n * Every entry point drains the JSONL queue first so reads reflect any\n * events that hadn't been committed yet. When the container is down,\n * stats still returns local counts (queue depth from JSONL); tail and\n * exportEvents return whatever was already in pglite.\n */\n\nimport { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\n\nimport { connectDb } from './db.js';\nimport { drainToPglite } from './drain.js';\nimport { PENDING_PATH } from './emit.js';\nimport { getIdentity } from './identity.js';\nimport { getTelemetryState } from './optout.js';\nimport { readMetaCache } from './metaCache.js';\nimport type { EventRow } from './types.js';\n\nexport interface TelemetryStats {\n /** Events visible in the LOCAL pglite mirror (0 in cloud mode — query central). */\n total_events: number;\n /** Events queued in the JSONL not yet delivered to all destinations. */\n pending_local: number;\n by_type: Record<string, number>;\n newest?: string;\n pglite_reachable: boolean;\n install_id: string;\n enabled: boolean;\n remote_flush_enabled: boolean;\n last_drained_at?: string;\n last_flush_at?: string;\n last_flush_ok_at?: string;\n last_flush_error?: string;\n}\n\nfunction countPending(): number {\n if (!existsSync(PENDING_PATH)) return 0;\n try {\n const raw = readFileSync(PENDING_PATH, 'utf-8');\n if (!raw) return 0;\n let n = 0;\n for (const line of raw.split('\\n')) if (line.trim()) n++;\n return n;\n } catch { return 0; }\n}\n\nexport async function getStats(): Promise<TelemetryStats> {\n const identity = getIdentity();\n const state = await getTelemetryState();\n\n // Refresh the local pglite mirror so the counts below reflect the queue.\n // No-op in cloud mode (no local container) — pending_local still shows depth.\n await drainToPglite();\n\n // Read meta AFTER draining so last_drained_at reflects the drain we just ran.\n const meta = readMetaCache();\n\n const base: TelemetryStats = {\n total_events: 0,\n pending_local: countPending(),\n by_type: {},\n pglite_reachable: false,\n install_id: identity.install_id,\n enabled: state.enabled,\n remote_flush_enabled: state.remoteFlushEnabled,\n last_drained_at: meta.last_drained_at,\n // Flush state lives in metaCache, so it's available even with no pglite.\n last_flush_at: meta.last_flush_at,\n last_flush_ok_at: meta.last_flush_ok_at,\n last_flush_error: meta.last_flush_error,\n };\n\n const sql = await connectDb();\n if (!sql) return base;\n base.pglite_reachable = true;\n\n try {\n const total = await sql`SELECT COUNT(*)::int AS c FROM telemetry_events`;\n base.total_events = Number(total[0]?.c ?? 0);\n\n const types = await sql`\n SELECT event_type, COUNT(*)::int AS c\n FROM telemetry_events\n GROUP BY event_type\n ORDER BY c DESC\n `;\n for (const row of types) base.by_type[String(row.event_type)] = Number(row.c);\n\n const newest = await sql`\n SELECT occurred_at FROM telemetry_events ORDER BY occurred_at DESC LIMIT 1\n `;\n if (newest.length) base.newest = new Date(newest[0].occurred_at).toISOString();\n } catch {\n // Partial stats; container path partially broken. Keep going.\n }\n\n return base;\n}\n\nexport interface TailOptions {\n limit?: number;\n eventType?: string;\n sessionId?: string;\n}\n\nexport async function tail(opts: TailOptions = {}): Promise<EventRow[]> {\n await drainToPglite();\n const sql = await connectDb();\n if (!sql) return [];\n\n const limit = Math.max(1, Math.min(opts.limit ?? 20, 1000));\n\n try {\n if (opts.eventType && opts.sessionId) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE event_type = ${opts.eventType}\n AND cc_session_id = ${opts.sessionId}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n if (opts.eventType) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE event_type = ${opts.eventType}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n if (opts.sessionId) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE cc_session_id = ${opts.sessionId}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n } catch { return []; }\n}\n\nexport async function exportEvents(path: string): Promise<void> {\n await drainToPglite();\n const sql = await connectDb();\n if (!sql) {\n writeFileSync(path, '', 'utf-8');\n return;\n }\n const lines: string[] = [];\n try {\n const rows = await sql`SELECT * FROM telemetry_events ORDER BY occurred_at ASC`;\n for (const row of rows) {\n // pglite returns jsonb columns as parsed values already.\n lines.push(JSON.stringify(row));\n }\n } catch {}\n writeFileSync(path, lines.join('\\n') + (lines.length > 0 ? '\\n' : ''), 'utf-8');\n}\n\n/** Best-effort disk usage hint for stats — pglite stores under the container's\n * /data volume which is opaque from the host; we just report queue size. */\nexport function pendingFileSize(): number {\n try { return existsSync(PENDING_PATH) ? statSync(PENDING_PATH).size : 0; }\n catch { return 0; }\n}\n","/**\n * Wipe local telemetry — both the pglite tables and the JSONL queue.\n *\n * Default: keeps install_id so casual cleanups don't rotate the user's\n * machine identifier. `keepInstallId: false` regenerates install_id on next\n * emit. `synkro uninstall --purge` calls with keepInstallId:false; the\n * `telemetry purge` subcommand defaults to true.\n */\n\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\n\nimport { connectDb } from './db.js';\nimport { PENDING_PATH } from './emit.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n\nexport interface PurgeResult {\n removed_events: number;\n removed_meta: boolean;\n removed_pending: number;\n pglite_reachable: boolean;\n}\n\nfunction unlinkPendingCount(): number {\n if (!existsSync(PENDING_PATH)) return 0;\n let n = 0;\n try {\n const raw = readFileSync(PENDING_PATH, 'utf-8') as string;\n for (const line of raw.split('\\n')) if (line.trim()) n++;\n } catch {}\n try { unlinkSync(PENDING_PATH); } catch {}\n return n;\n}\n\nexport async function purge(opts: { keepInstallId?: boolean } = {}): Promise<PurgeResult> {\n const keep = opts.keepInstallId ?? true;\n const pending = unlinkPendingCount();\n const sql = await connectDb();\n if (!sql) {\n // Container down — we wiped the JSONL queue; mark the meta accordingly.\n if (!keep) {\n patchMetaCache({ install_id: randomUUID() });\n }\n return {\n removed_events: pending,\n removed_meta: !keep,\n removed_pending: pending,\n pglite_reachable: false,\n };\n }\n\n let removedEvents = 0;\n try {\n const [{ c }] = (await sql`SELECT COUNT(*)::int AS c FROM telemetry_events`) as unknown as Array<{ c: number }>;\n removedEvents = Number(c ?? 0);\n await sql`TRUNCATE TABLE telemetry_events RESTART IDENTITY`;\n } catch {}\n\n let removedMeta = false;\n try {\n if (keep) {\n // Preserve install_id + schema_version; drop everything else.\n const meta = readMetaCache();\n await sql`DELETE FROM telemetry_meta WHERE k NOT IN ('schema_version')`;\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('install_id', ${meta.install_id})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n removedMeta = true;\n } else {\n const newId = randomUUID();\n await sql`DELETE FROM telemetry_meta WHERE k NOT IN ('schema_version')`;\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('install_id', ${newId})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n patchMetaCache({ install_id: newId });\n removedMeta = true;\n }\n } catch {}\n\n return {\n removed_events: removedEvents,\n removed_meta: removedMeta,\n removed_pending: pending,\n pglite_reachable: true,\n };\n}\n","/**\n * Telemetry client — no-op stub.\n *\n * All Supabase telemetry has been removed. Methods return dummy values\n * so existing call sites in loop.ts continue to work without changes.\n */\n\nimport { randomUUID } from 'crypto';\n\nexport type TelemetryEventType =\n | 'session_start'\n | 'extraction_complete'\n | 'scenarios_complete'\n | 'hitl_checkpoint_shown'\n | 'hitl_approved'\n | 'hitl_feedback'\n | 'hitl_refinement_complete'\n | 'pipeline_complete';\n\ninterface PreferencePairData {\n sessionId: string;\n eventId: string;\n section: string;\n rejectedState: unknown;\n feedback: string;\n}\n\nclass TelemetryClient {\n async track(\n _eventType: TelemetryEventType,\n _data: Record<string, unknown>\n ): Promise<string> {\n return randomUUID();\n }\n\n async createPreferencePair(_data: PreferencePairData): Promise<string> {\n return randomUUID();\n }\n\n async updatePreferencePair(_id: string, _acceptedState: unknown): Promise<void> {\n // no-op\n }\n}\n\n// Singleton instance\nexport const telemetry = new TelemetryClient();\n","/**\n * Legacy RLHF telemetry exporters — no-op stubs kept so cli/agent/loop.ts\n * keeps compiling. The original implementation read preference_pairs +\n * telemetry_events tables out of ~/.synkro/sessions.db (better-sqlite3); both\n * the tables and the SQLite dep are gone with the pglite migration.\n *\n * Anyone who actually needs these features should rebuild against the new\n * pglite-backed telemetry_events table.\n */\n\nimport { writeFileSync } from 'node:fs';\n\nexport async function exportPreferencePairs(outputPath: string): Promise<number> {\n writeFileSync(outputPath, '');\n return 0;\n}\n\nexport async function exportCompletions(outputPath: string): Promise<number> {\n writeFileSync(outputPath, '');\n return 0;\n}\n\nexport async function getTelemetryStats(): Promise<{\n totalEvents: number;\n totalPreferencePairs: number;\n completePreferencePairs: number;\n pipelineCompletions: number;\n}> {\n return {\n totalEvents: 0,\n totalPreferencePairs: 0,\n completePreferencePairs: 0,\n pipelineCompletions: 0,\n };\n}\n","/**\n * Public telemetry module exports.\n */\n\nexport { emit, PENDING_PATH } from './emit.js';\nexport { getIdentity, type IdentityContext } from './identity.js';\nexport { connectDb, closeDb, type Sql } from './db.js';\nexport {\n drainToPglite, readQueue, rewriteQueue, insertEvents,\n acquireQueueLock, releaseQueueLock,\n type DrainResult, type PendingRow,\n} from './drain.js';\nexport {\n getTelemetryState, getTelemetryStateSync, setTelemetryState,\n type TelemetryState,\n} from './optout.js';\nexport { flush, flushDetached, type FlushResult } from './flush.js';\nexport {\n getStats, tail, exportEvents, pendingFileSize,\n type TelemetryStats, type TailOptions,\n} from './stats.js';\nexport { purge, type PurgeResult } from './purge.js';\nexport { readMetaCache, writeMetaCache, patchMetaCache, type TelemetryMeta } from './metaCache.js';\nexport type { EventType, EventContext, EmitOptions, EventRow } from './types.js';\n\n// Legacy no-op stubs retained so older call sites (cli/agent/loop.ts) keep\n// compiling. New code should not import these.\nexport { telemetry, type TelemetryEventType } from './client.js';\nexport {\n exportPreferencePairs,\n exportCompletions,\n getTelemetryStats,\n} from './export.js';\n","/**\n * Detect which AI coding agents are installed on the user's machine.\n *\n * Returns a list of agents with their config paths so the installer\n * knows where to write hook configs.\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execFileSync, execSync } from 'node:child_process';\n\nexport type AgentKind = 'claude_code' | 'cursor';\n\nexport interface DetectedAgent {\n kind: AgentKind;\n name: string;\n binaryPath?: string;\n configDir: string;\n settingsPath: string;\n version?: string;\n}\n\nfunction which(cmd: string): string | undefined {\n try {\n const result = execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();\n return result || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getVersion(cmd: string): string | undefined {\n try {\n const result = execFileSync(cmd, ['--version'], { encoding: 'utf-8', timeout: 5000 }).trim();\n const line = result.split('\\n')[0];\n return line.replace(/\\s*\\(Claude Code\\)/, '');\n } catch {\n return undefined;\n }\n}\n\nexport function detectAgents(): DetectedAgent[] {\n const agents: DetectedAgent[] = [];\n const home = homedir();\n\n // Claude Code — require the binary on PATH; ~/.claude/ alone is not enough\n // (Synkro and other tools create that directory).\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary) {\n agents.push({\n kind: 'claude_code',\n name: 'Claude Code',\n binaryPath: claudeBinary,\n configDir: claudeConfigDir,\n settingsPath: join(claudeConfigDir, 'settings.json'),\n version: getVersion('claude'),\n });\n }\n\n // Cursor — GUI app, rarely has a `cursor` binary on PATH. Check the app\n // bundle (/Applications/Cursor.app on macOS) and ~/.cursor/ (created by\n // Cursor on first launch) as additional signals.\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n const cursorApp = process.platform === 'darwin' && existsSync('/Applications/Cursor.app');\n if (cursorBinary || cursorApp || existsSync(cursorConfigDir)) {\n let version: string | undefined;\n if (cursorBinary) {\n version = getVersion('cursor');\n } else if (cursorApp) {\n try {\n const plist = execSync('defaults read /Applications/Cursor.app/Contents/Info CFBundleShortVersionString 2>/dev/null', { encoding: 'utf-8', timeout: 3000 }).trim();\n if (plist) version = plist;\n } catch {}\n }\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version,\n });\n }\n\n return agents;\n}\n\nexport function findClaudeAuth(): { path: string; exists: boolean } {\n // CC stores OAuth token in macOS keychain (item: \"Claude Code-credentials\")\n // or in ~/.claude/auth.json on Linux. We don't extract it; we ask user to\n // run `claude setup-token` for headless use.\n const authJsonPath = join(homedir(), '.claude', 'auth.json');\n return { path: authJsonPath, exists: existsSync(authJsonPath) };\n}\n","// :)\n/**\n * Cross-platform helpers for hook installation.\n *\n * The hook scripts (~/.synkro/hooks/cc-*.ts) start with `#!/usr/bin/env bun`, and on\n * macOS/Linux Claude Code runs them by their bare path — the OS honors the shebang. Windows\n * has NO shebang mechanism, so a bare `.ts` path never reaches bun (cmd hands it to a file\n * association or errors). The fix: on Windows, register the hook command as `<bun.exe> <script>`\n * — invoke bun explicitly. macOS/Linux keep the bare-path (shebang) form, unchanged.\n *\n * Cloud grading needs NO local container — the stub hooks POST straight to the gateway — so\n * getting these thin stubs to LAUNCH is the only thing standing between cloud mode and native\n * Windows. (Local grading still needs Docker; a Windows user on local mode should use WSL2.)\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\n/**\n * Absolute path to the `bun` executable. Two callers need it resolved to an absolute path:\n * GUI apps (Claude Desktop) don't inherit the login-shell PATH, and Windows hook commands\n * must name bun explicitly (no shebang). Probe PATH first, then well-known install locations,\n * then fall back to a bare name (works if PATH is set at runtime).\n */\nexport function resolveBunBin(): string {\n const finder = IS_WINDOWS ? 'where' : 'which';\n const r = spawnSync(finder, ['bun'], { encoding: 'utf-8', timeout: 5_000 });\n // `where` can return several lines (bun, bun.exe, bun.cmd); take the first non-empty.\n const resolved = (r.stdout || '').split(/\\r?\\n/).map(s => s.trim()).find(Boolean);\n if (resolved) return resolved;\n const home = process.env.USERPROFILE || homedir();\n const candidates = IS_WINDOWS\n ? [join(home, '.bun', 'bin', 'bun.exe'), 'C:\\\\Program Files\\\\bun\\\\bin\\\\bun.exe']\n : ['/opt/homebrew/bin/bun', '/usr/local/bin/bun', join(home, '.bun', 'bin', 'bun')];\n for (const p of candidates) if (existsSync(p)) return p;\n return IS_WINDOWS ? 'bun.exe' : 'bun';\n}\n\n/** Quote a path for embedding in a hook `command` string. */\nfunction quoteArg(s: string): string {\n // Windows cmd: double-quote (paths have spaces + backslashes; backslash is literal in quotes).\n // POSIX sh: single-quote with the standard '\\'' escape.\n return IS_WINDOWS ? `\"${s}\"` : \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/**\n * The `command` string Claude Code should run for a hook SCRIPT PATH.\n * - macOS/Linux: the bare path — the script's `#!/usr/bin/env bun` shebang executes it (unchanged).\n * - Windows: `<bun.exe> <script>` — Windows ignores the shebang, so bun is invoked explicitly.\n * Idempotent on already-wrapped commands (only wraps a plain path).\n */\nexport function ccHookCommand(scriptPath: string): string {\n if (!IS_WINDOWS) return scriptPath;\n return `${quoteArg(resolveBunBin())} ${quoteArg(scriptPath)}`;\n}\n\n/**\n * True if a hook `command` string points at a Synkro-managed hook script. Used to recognize\n * (and clean up) our own entries on reinstall/uninstall. MUST be separator-agnostic: on Windows\n * the path is `...\\.synkro\\hooks\\...`, so a raw `.includes('/.synkro/hooks/')` never matches and\n * Synkro fails to recognize its own hooks → duplicates on reinstall, orphans on uninstall.\n */\nexport function isSynkroHookCommand(command: unknown): boolean {\n if (typeof command !== 'string') return false;\n return command.replace(/\\\\/g, '/').includes('/.synkro/hooks/');\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.claude/settings.json.\n *\n * Preserves any other hooks the user has configured (Corridor, Noma, custom\n * scripts, etc.) — we only add our entries, never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { ccHookCommand, isSynkroHookCommand } from './platform.js';\n\nexport interface SynkroHookConfig {\n bashJudgeScriptPath: string;\n skillJudgeScriptPath?: string;\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n transcriptSyncScriptPath: string;\n userPromptSubmitScriptPath: string;\n installScanScriptPath: string;\n taskActivateIntentScriptPath?: string;\n mcpGateScriptPath?: string;\n skipTranscriptSync?: boolean;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\n// LEGACY: prior versions pointed the coding agent's ANTHROPIC_BASE_URL at an in-container\n// usage proxy. That was a hot-path SPOF and has been removed in favor of an out-of-band\n// tap (the container reads account-wide utilization itself). This URL is retained ONLY so\n// install/uninstall can recognize and remove a value we set before — never to set one.\nconst USAGE_PROXY_URL = `http://127.0.0.1:${process.env.SYNKRO_HOST_USAGE_PROXY_PORT || '18927'}`;\n\ninterface HookEntry {\n matcher?: string;\n hooks: Array<Record<string, unknown>>;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CCSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n PostToolUse?: HookEntry[];\n SessionEnd?: HookEntry[];\n SessionStart?: HookEntry[];\n Stop?: HookEntry[];\n [k: string]: unknown;\n };\n env?: Record<string, string>;\n [k: string]: unknown;\n}\n\nfunction readSettings(path: string): CCSettings {\n if (!existsSync(path)) return {};\n try {\n const raw = readFileSync(path, 'utf-8');\n return JSON.parse(raw) as CCSettings;\n } catch (err) {\n throw new Error(`Failed to parse ${path}: ${(err as Error).message}`);\n }\n}\n\nfunction writeSettingsAtomic(path: string, settings: CCSettings): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmpPath = `${path}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, path);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];\n // Separator-agnostic: on Windows the command is `<bun.exe> ...\\.synkro\\hooks\\...` — a raw\n // '/.synkro/hooks/' check misses it, so old entries would never be cleaned up (dupes/orphans).\n return hooks.some((h: any) => isSynkroHookCommand(h?.command));\n}\n\n// Rewrite every Synkro-managed hook's `command` to the platform-correct launch form. The push\n// sites above store the bare SCRIPT PATH; on macOS/Linux that's the final command (shebang runs\n// it), on Windows this wraps it as `<bun.exe> <script>` (no shebang there). One pass over the\n// freshly-added entries keeps the 16 push sites simple and the platform branch in ONE place.\nfunction finalizeHookCommands(hooks: NonNullable<CCSettings['hooks']>): void {\n for (const evt of Object.keys(hooks)) {\n const arr = (hooks as any)[evt];\n if (!Array.isArray(arr)) continue;\n for (const entry of arr) {\n if (!entry?.[SYNKRO_MARKER] || !Array.isArray(entry.hooks)) continue;\n for (const h of entry.hooks) {\n if (typeof h?.command === 'string') h.command = ccHookCommand(h.command);\n }\n }\n }\n}\n\nfunction removeSynkroEntries(events: CCSettings['hooks'] extends infer H ? H : never, eventName: string): void {\n if (!events) return;\n const arr = (events as any)[eventName];\n if (!Array.isArray(arr)) return;\n (events as any)[eventName] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\n/**\n * Merge Synkro hooks into the settings file. Idempotent — replaces any\n * existing Synkro-managed entries with the new versions.\n */\nexport function installCCHooks(settingsPath: string, config: SynkroHookConfig): void {\n const settings = readSettings(settingsPath);\n settings.hooks = settings.hooks ?? {};\n\n // Remove any prior Synkro entries (to support `synkro update`)\n removeSynkroEntries(settings.hooks as any, 'PreToolUse');\n removeSynkroEntries(settings.hooks as any, 'PostToolUse');\n removeSynkroEntries(settings.hooks as any, 'SessionEnd');\n removeSynkroEntries(settings.hooks as any, 'SessionStart');\n removeSynkroEntries(settings.hooks as any, 'UserPromptSubmit');\n // Also clean up any older `Stop`-event entry from earlier v1.6 builds.\n removeSynkroEntries(settings.hooks as any, 'Stop');\n\n settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];\n settings.hooks.PostToolUse = settings.hooks.PostToolUse ?? [];\n settings.hooks.SessionEnd = settings.hooks.SessionEnd ?? [];\n settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];\n settings.hooks.UserPromptSubmit = (settings.hooks.UserPromptSubmit as any[]) ?? [];\n\n // PreToolUse Bash → install scan (caches result for bash judge to read)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.installScanScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Bash/Read/Grep/Glob → command hook script (Cerebras-judged)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash|Read|Grep|Glob',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 50,\n },\n // skill-judge shares this entry so CC runs it in parallel and waits for it\n // before processing denies — its verdict is its OWN system-message block,\n // separate from bashGuard. No-ops on pure reads (empty command).\n ...(config.skillJudgeScriptPath ? [{\n type: 'command',\n command: config.skillJudgeScriptPath,\n timeout: 50,\n }] : []),\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → two hooks in ONE entry so CC waits for all\n // before processing deny decisions. Each hook runs in parallel:\n // 1. edit-precheck: org rules grading on channel 1 (port 8929)\n // 2. cve-precheck: CVE/OSV dependency scan (curl, no LLM)\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 50,\n },\n {\n type: 'command',\n command: config.cwePrecheckScriptPath,\n timeout: 50,\n },\n {\n type: 'command',\n command: config.cvePrecheckScriptPath,\n timeout: 10,\n },\n // skill-judge — its OWN system-message block, parallel with edit-precheck.\n ...(config.skillJudgeScriptPath ? [{\n type: 'command',\n command: config.skillJudgeScriptPath,\n timeout: 50,\n }] : []),\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Agent → scan subagent prompts against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'Agent',\n hooks: [\n {\n type: 'command',\n command: config.agentJudgeScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse ExitPlanMode → advisory plan review against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'ExitPlanMode',\n hooks: [\n {\n type: 'command',\n command: config.planJudgeScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse on any mcp__* tool → MCP server governance (allow/blocklist +\n // grade allowed calls). Blocking: a blocklisted/un-allowlisted server is denied.\n // Synkro's own guardrails server is skipped inside the handler, never blocked.\n if (config.mcpGateScriptPath) {\n settings.hooks.PreToolUse.push({\n matcher: 'mcp__.*',\n hooks: [\n {\n type: 'command',\n command: config.mcpGateScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n }\n\n // PreToolUse on the activate_standard MCP tool → stamp the activating session\n // BEFORE the tool runs, so the task binds to THIS exact session (reliable —\n // not the most-recent-session heuristic). Matcher is a literal MCP tool name.\n if (config.taskActivateIntentScriptPath) {\n settings.hooks.PreToolUse.push({\n matcher: 'mcp__synkro-guardrails__activate_standard',\n hooks: [\n {\n type: 'command',\n command: config.taskActivateIntentScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n }\n\n // PostToolUse Edit/Write removed — PreToolUse hooks handle grading now.\n\n // PostToolUse Bash → flips pending precheck_corrections row to 'allow'\n // once the bash command actually executed. Required for the bash trendline\n // (approved-vs-rejected) the dashboard reads from precheck_corrections.\n settings.hooks.PostToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashFollowupScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionEnd → end-of-session summary line (`[synkro] stop → N findings: ...`).\n // We use SessionEnd, not Stop, because Stop fires after every agent turn\n // (which would spam the user in interactive mode); SessionEnd fires once\n // when the session itself terminates.\n settings.hooks.SessionEnd.push({\n hooks: [\n {\n type: 'command',\n command: config.stopSummaryScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionStart → \"[synkro] session start → N open findings in this repo\" if any.\n settings.hooks.SessionStart.push({\n hooks: [\n {\n type: 'command',\n command: config.sessionStartScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // UserPromptSubmit → captures explicit consent keywords from user messages.\n // Writes a file-based grant so the next blocked tool call is allowed through.\n (settings.hooks.UserPromptSubmit as any[]).push({\n hooks: [\n {\n type: 'command',\n command: config.userPromptSubmitScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Stop → usage telemetry + optional transcript sync (fires after every agent turn).\n // Always installed: usage tracking is ungated; transcript sync is gated inside the script.\n settings.hooks.Stop = settings.hooks.Stop ?? [];\n removeSynkroEntries(settings.hooks as any, 'Stop');\n settings.hooks.Stop.push({\n hooks: [\n {\n type: 'command',\n command: config.transcriptSyncScriptPath,\n timeout: 3,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Usage telemetry is now an OUT-OF-BAND tap inside the container (it makes its own\n // account-wide utilization read), so we no longer route the coding agent through a\n // proxy — that was a hot-path single point of failure. Proactively remove any\n // ANTHROPIC_BASE_URL a prior version set, but ONLY if it's still ours; never touch a\n // custom base URL the user set themselves.\n if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {\n delete settings.env.ANTHROPIC_BASE_URL;\n if (Object.keys(settings.env).length === 0) delete settings.env;\n }\n\n // Windows: wrap each hook's bare script path as `<bun.exe> <script>` (no shebang on Windows).\n // No-op on macOS/Linux (bare path + shebang, unchanged). Runs after all entries are staged.\n finalizeHookCommands(settings.hooks as NonNullable<CCSettings['hooks']>);\n\n writeSettingsAtomic(settingsPath, settings);\n}\n\n/**\n * Remove all Synkro-managed hook entries from settings.json.\n * Used by `synkro disconnect`.\n */\nexport function uninstallCCHooks(settingsPath: string): boolean {\n if (!existsSync(settingsPath)) return false;\n const settings = readSettings(settingsPath);\n if (!settings.hooks) return false;\n\n const events = ['PreToolUse', 'PostToolUse', 'SessionEnd', 'SessionStart', 'Stop', 'UserPromptSubmit'] as const;\n for (const evt of events) {\n removeSynkroEntries(settings.hooks as any, evt);\n }\n\n // If a hook event array is now empty, delete it\n for (const evt of events) {\n if (Array.isArray((settings.hooks as any)[evt]) && (settings.hooks as any)[evt].length === 0) {\n delete (settings.hooks as any)[evt];\n }\n }\n // If hooks object is now empty, delete it\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // Drop our usage-proxy base URL — but ONLY if it's still ours (never touch a value\n // the user set to their own gateway).\n if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {\n delete settings.env.ANTHROPIC_BASE_URL;\n if (Object.keys(settings.env).length === 0) delete settings.env;\n }\n\n writeSettingsAtomic(settingsPath, settings);\n return true;\n}\n\n/**\n * Check whether Synkro hooks are currently installed in settings.json.\n * Used by `synkro status`.\n */\nexport function inspectCCHooks(settingsPath: string): {\n installed: boolean;\n preToolUseBash: boolean;\n postToolUseEdit: boolean;\n sessionEnd: boolean;\n sessionStart: boolean;\n} {\n if (!existsSync(settingsPath)) {\n return { installed: false, preToolUseBash: false, postToolUseEdit: false, sessionEnd: false, sessionStart: false };\n }\n const settings = readSettings(settingsPath);\n const pre = (settings.hooks as any)?.PreToolUse ?? [];\n const post = (settings.hooks as any)?.PostToolUse ?? [];\n const sessionEndHooks = (settings.hooks as any)?.SessionEnd ?? [];\n const sessionStartHooks = (settings.hooks as any)?.SessionStart ?? [];\n const preToolUseBash = pre.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const postToolUseEdit = post.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionEnd = sessionEndHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionStart = sessionStartHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n return {\n installed: preToolUseBash || postToolUseEdit || sessionEnd || sessionStart,\n preToolUseBash,\n postToolUseEdit,\n sessionEnd,\n sessionStart,\n };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.cursor/hooks.json.\n *\n * Cursor hooks reuse the same TypeScript scripts as Claude Code (cc-*.ts),\n * run with SYNKRO_HOOK_FORMAT=cursor so _synkro-common translates CC JSON output\n * to Cursor's permission JSON for preToolUse. CC install is unchanged.\n */\nimport { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve, normalize } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isSynkroHookCommand } from './platform.js';\n\nexport interface CursorHookConfig {\n /** Cursor-specific bash/shell judge (preToolUse with Shell|Bash matcher) */\n bashJudgeScriptPath: string;\n skillJudgeScriptPath?: string;\n /** Cursor-specific post-edit capture (afterFileEdit event shape) */\n editCaptureScriptPath: string;\n /** Capture afterAgentThought/afterAgentResponse before transcript redaction */\n agentCaptureScriptPath: string;\n /** Shared cc-*.ts scripts (invoked with SYNKRO_HOOK_FORMAT=cursor) */\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n userPromptSubmitScriptPath: string;\n transcriptSyncScriptPath: string;\n installScanScriptPath: string;\n /** MCP server governance (beforeMCPExecution): allow/blocklist + grade calls */\n mcpGateScriptPath: string;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Run a shared cc-*.ts hook with Cursor output translation. */\nfunction cursorCcCmd(scriptPath: string): string {\n return 'env SYNKRO_HOOK_FORMAT=cursor bun run ' + shellQuote(scriptPath);\n}\n\nfunction bunRunCmd(scriptPath: string): string {\n return 'bun run ' + shellQuote(scriptPath);\n}\n\nconst ALLOWED_PARENT_DIRS = [\n resolve(homedir(), '.cursor'),\n resolve(homedir(), '.config', 'cursor'),\n];\n\nfunction validateHooksPath(path: string): string {\n const resolved = resolve(normalize(path));\n if (!ALLOWED_PARENT_DIRS.some(dir => resolved.startsWith(dir + '/') || resolved === dir)) {\n throw new Error(`Hooks path must be under ~/.cursor or ~/.config/cursor, got: ${resolved}`);\n }\n return resolved;\n}\n\ninterface CursorHookEntry {\n command: string;\n timeout?: number;\n failClosed?: boolean;\n matcher?: string;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CursorHooksFile {\n version?: number;\n hooks?: {\n [event: string]: CursorHookEntry[];\n };\n}\n\nfunction readHooksFile(rawPath: string): CursorHooksFile {\n const safePath = validateHooksPath(rawPath);\n try {\n const raw = readFileSync(safePath, 'utf-8');\n return JSON.parse(raw) as CursorHooksFile;\n } catch (err: any) {\n if (err?.code === 'ENOENT') return { version: 1, hooks: {} };\n throw new Error(`Failed to parse ${safePath}: ${(err as Error).message}`);\n }\n}\n\nfunction writeHooksFileAtomic(rawPath: string, data: CursorHooksFile): void {\n const safePath = validateHooksPath(rawPath);\n mkdirSync(dirname(safePath), { recursive: true });\n const tmpPath = `${safePath}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, safePath);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n // Separator-agnostic so Windows (`...\\.synkro\\hooks\\...`) entries are still recognized.\n return isSynkroHookCommand(entry?.command);\n}\n\nconst ALL_EVENTS = [\n 'sessionStart', 'sessionEnd', 'beforeSubmitPrompt', 'stop',\n 'beforeShellExecution', 'afterShellExecution',\n 'preToolUse', 'afterFileEdit', 'postToolUse',\n 'beforeMCPExecution',\n 'afterAgentThought', 'afterAgentResponse',\n];\n\nfunction removeSynkroEntries(hooks: CursorHooksFile['hooks'], event: string): void {\n if (!hooks) return;\n const arr = hooks[event];\n if (!Array.isArray(arr)) return;\n hooks[event] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\nfunction pushCcHook(\n hooks: CursorHooksFile['hooks'],\n event: string,\n scriptPath: string,\n opts: { timeout: number; matcher?: string; failClosed?: boolean },\n): void {\n hooks![event] = hooks![event] ?? [];\n hooks![event]!.push({\n command: cursorCcCmd(scriptPath),\n timeout: opts.timeout,\n failClosed: opts.failClosed ?? false,\n ...(opts.matcher ? { matcher: opts.matcher } : {}),\n [SYNKRO_MARKER]: true,\n });\n}\n\nexport function installCursorHooks(hooksJsonPath: string, config: CursorHookConfig): void {\n const file = readHooksFile(hooksJsonPath);\n file.version = file.version ?? 1;\n file.hooks = file.hooks ?? {};\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n const h = file.hooks;\n\n pushCcHook(h, 'sessionStart', config.sessionStartScriptPath, { timeout: 5 });\n pushCcHook(h, 'sessionEnd', config.stopSummaryScriptPath, { timeout: 10 });\n pushCcHook(h, 'beforeSubmitPrompt', config.userPromptSubmitScriptPath, { timeout: 5 });\n pushCcHook(h, 'stop', config.transcriptSyncScriptPath, { timeout: 3 });\n\n // beforeShellExecution: install scan runs first (caches result for bash judge)\n h.beforeShellExecution = h.beforeShellExecution ?? [];\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n // skill-judge — its own message, parallel with the bash judge (own hook).\n if (config.skillJudgeScriptPath) h.beforeShellExecution.push({\n command: bunRunCmd(config.skillJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'afterShellExecution', config.bashFollowupScriptPath, { timeout: 10 });\n\n // preToolUse: install scan before bash judge (caches result for bash judge)\n h.preToolUse = h.preToolUse ?? [];\n h.preToolUse.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|Read|ReadFile|Grep|Glob|terminal|run_terminal_cmd|execute_command|read_file|grep_search|file_search|list_dir|codebase_search|delete_file',\n [SYNKRO_MARKER]: true,\n });\n\n // skill-judge — its own system message, both on bash and on edit matchers.\n if (config.skillJudgeScriptPath) h.preToolUse.push({\n command: bunRunCmd(config.skillJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'preToolUse', config.editPrecheckScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n if (config.skillJudgeScriptPath) pushCcHook(h, 'preToolUse', config.skillJudgeScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cwePrecheckScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cvePrecheckScriptPath, {\n timeout: 10,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.agentJudgeScriptPath, {\n timeout: 30,\n matcher: 'Agent|Task',\n });\n pushCcHook(h, 'preToolUse', config.planJudgeScriptPath, {\n timeout: 45,\n matcher: 'ExitPlanMode|SwitchMode|CreatePlan',\n });\n\n h.afterFileEdit = h.afterFileEdit ?? [];\n h.afterFileEdit.push({\n command: bunRunCmd(config.editCaptureScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'postToolUse', config.bashFollowupScriptPath, {\n timeout: 10,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command|delete_file',\n });\n\n // beforeMCPExecution: MCP server governance — fires once per MCP tool call.\n // Same shared cc-mcp-gate.ts as Claude Code (allow/blocklist + grade allowed\n // calls + emit connect/tool_call/blocked events). Blocking: a blocklisted /\n // un-allowlisted server is denied. No matcher — this event is MCP-only.\n pushCcHook(h, 'beforeMCPExecution', config.mcpGateScriptPath, { timeout: 30 });\n\n h.afterAgentThought = h.afterAgentThought ?? [];\n h.afterAgentThought.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n h.afterAgentResponse = h.afterAgentResponse ?? [];\n h.afterAgentResponse.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n writeHooksFileAtomic(hooksJsonPath, file);\n}\n\nexport function uninstallCursorHooks(hooksJsonPath: string): boolean {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return false;\n }\n if (!file.hooks) return false;\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n for (const evt of ALL_EVENTS) {\n if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {\n delete file.hooks[evt];\n }\n }\n if (Object.keys(file.hooks).length === 0) {\n delete file.hooks;\n }\n\n writeHooksFileAtomic(hooksJsonPath, file);\n return true;\n}\n\nfunction preToolUseUsesScript(hooks: CursorHookEntry[] | undefined, scriptBasename: string): boolean {\n return (hooks ?? []).some((e) =>\n isSynkroEntry(e) && typeof e.command === 'string' && e.command.includes(scriptBasename),\n );\n}\n\nexport function inspectCursorHooks(hooksJsonPath: string): {\n installed: boolean;\n sessionStart: boolean;\n sessionEnd: boolean;\n beforeSubmitPrompt: boolean;\n stop: boolean;\n beforeShellExecution: boolean;\n afterShellExecution: boolean;\n preToolUse: boolean;\n preToolUseBash: boolean;\n preToolUseEdit: boolean;\n preToolUseCwe: boolean;\n preToolUseCve: boolean;\n preToolUseAgent: boolean;\n preToolUsePlan: boolean;\n afterFileEdit: boolean;\n postToolUse: boolean;\n beforeMCPExecution: boolean;\n} {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return {\n installed: false,\n sessionStart: false, sessionEnd: false, beforeSubmitPrompt: false, stop: false,\n beforeShellExecution: false, afterShellExecution: false,\n preToolUse: false, preToolUseBash: false, preToolUseEdit: false,\n preToolUseCwe: false, preToolUseCve: false, preToolUseAgent: false, preToolUsePlan: false,\n afterFileEdit: false, postToolUse: false, beforeMCPExecution: false,\n };\n }\n const h = file.hooks ?? {};\n const sessionStart = (h.sessionStart ?? []).some((e) => isSynkroEntry(e));\n const sessionEnd = (h.sessionEnd ?? []).some((e) => isSynkroEntry(e));\n const beforeSubmitPrompt = (h.beforeSubmitPrompt ?? []).some((e) => isSynkroEntry(e));\n const stop = (h.stop ?? []).some((e) => isSynkroEntry(e));\n const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry(e));\n const afterShellExecution = (h.afterShellExecution ?? []).some((e) => isSynkroEntry(e));\n const pre = h.preToolUse ?? [];\n const preToolUseBash = preToolUseUsesScript(pre, 'cc-bash-judge') || preToolUseUsesScript(pre, 'cursor-bash-judge');\n const preToolUseEdit = preToolUseUsesScript(pre, 'cc-edit-precheck') || preToolUseUsesScript(pre, 'cursor-edit-precheck');\n const preToolUseCwe = preToolUseUsesScript(pre, 'cc-cwe-precheck');\n const preToolUseCve = preToolUseUsesScript(pre, 'cc-cve-precheck');\n const preToolUseAgent = preToolUseUsesScript(pre, 'cc-agent-judge');\n const preToolUsePlan = preToolUseUsesScript(pre, 'cc-plan-judge');\n const preToolUse = preToolUseBash || preToolUseEdit || preToolUseCwe || preToolUseCve || preToolUseAgent || preToolUsePlan;\n const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry(e));\n const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry(e));\n const beforeMCPExecution = (h.beforeMCPExecution ?? []).some((e) => isSynkroEntry(e));\n return {\n installed: sessionStart || sessionEnd || beforeSubmitPrompt || stop\n || beforeShellExecution || afterShellExecution || preToolUse || afterFileEdit || postToolUse\n || beforeMCPExecution,\n sessionStart, sessionEnd, beforeSubmitPrompt, stop,\n beforeShellExecution, afterShellExecution,\n preToolUse, preToolUseBash, preToolUseEdit, preToolUseCwe, preToolUseCve, preToolUseAgent, preToolUsePlan,\n afterFileEdit, postToolUse, beforeMCPExecution,\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';\nimport { randomBytes } from 'node:crypto';\nimport { resolveBunBin as resolveBunBinShared } from './platform.js';\n\nconst SYNKRO_MARKER = '__synkro_managed__';\nconst SYNKRO_SERVER_NAME = 'synkro-guardrails';\nconst CC_CONFIG_PATH = join(homedir(), '.claude.json');\n\ninterface ClaudeJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\ninterface McpServerEntry {\n type?: 'http' | 'stdio' | 'sse';\n url?: string;\n command?: string;\n args?: string[];\n headers?: Record<string, string>;\n env?: Record<string, string>;\n [SYNKRO_MARKER]?: boolean;\n [k: string]: unknown;\n}\n\nfunction readClaudeJson(): ClaudeJson {\n if (!existsSync(CC_CONFIG_PATH)) return {};\n try {\n const raw = readFileSync(CC_CONFIG_PATH, 'utf-8');\n return JSON.parse(raw) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeClaudeJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CC_CONFIG_PATH), { recursive: true });\n const tmpPath = `${CC_CONFIG_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CC_CONFIG_PATH);\n}\n\nexport interface InstallMcpOptions {\n gatewayUrl: string; // e.g. http://localhost:8788\n // Long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails. Minted by\n // POST /api/v1/cli/mcp-token during install. We deliberately do NOT write\n // the WorkOS access token here anymore — that one expires in 5 min and\n // silently breaks the CC MCP connection.\n bearerToken: string;\n local?: boolean;\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.claude.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readClaudeJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Remove any prior Synkro-managed entry (so re-running install picks up\n // a refreshed JWT). Leave non-Synkro entries alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n const proxyScript = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'stdio',\n command: 'bun',\n args: ['run', proxyScript],\n [SYNKRO_MARKER]: true,\n };\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url: `stdio://${proxyScript}` };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'http',\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.claude.json.\n * Returns true if anything was removed.\n */\nexport function uninstallMcpConfig(): boolean {\n if (!existsSync(CC_CONFIG_PATH)) return false;\n const config = readClaudeJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n // If the mcpServers object is now empty, drop it to keep the file tidy.\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeClaudeJsonAtomic(config);\n return true;\n}\n\n/**\n * Inspect whether the Synkro MCP server entry is currently registered.\n */\nexport function inspectMcpConfig(): {\n installed: boolean;\n configPath: string;\n url?: string;\n} {\n if (!existsSync(CC_CONFIG_PATH)) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n const config = readClaudeJson();\n const entry = config.mcpServers?.[SYNKRO_SERVER_NAME];\n if (!entry || entry[SYNKRO_MARKER] !== true) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n return { installed: true, configPath: CC_CONFIG_PATH, url: entry.url };\n}\n\n// ─── Cursor MCP Config ───\n\nconst CURSOR_MCP_PATH = join(homedir(), '.cursor', 'mcp.json');\n\ninterface CursorMcpJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\nfunction readCursorMcpJson(): CursorMcpJson {\n if (!existsSync(CURSOR_MCP_PATH)) return {};\n try {\n const raw = readFileSync(CURSOR_MCP_PATH, 'utf-8');\n return JSON.parse(raw) as CursorMcpJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeCursorMcpJsonAtomic(config: CursorMcpJson): void {\n mkdirSync(dirname(CURSOR_MCP_PATH), { recursive: true });\n const tmpPath = `${CURSOR_MCP_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CURSOR_MCP_PATH);\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.cursor/mcp.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installCursorMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readCursorMcpJson();\n config.mcpServers = config.mcpServers ?? {};\n\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n // Honors SYNKRO_MCP_PORT so the containerised deployment can register\n // its host-mapped port (e.g. 18931) instead of the bare-host default.\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n const url = `http://127.0.0.1:${port}/`;\n const jwtPath = join(homedir(), '.synkro', '.mcp-jwt');\n let jwt = '';\n try { jwt = readFileSync(jwtPath, 'utf-8').trim(); } catch {}\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n ...(jwt ? { headers: { Authorization: `Bearer ${jwt}` } } : {}),\n [SYNKRO_MARKER]: true,\n };\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.cursor/mcp.json.\n */\nexport function uninstallCursorMcpConfig(): boolean {\n if (!existsSync(CURSOR_MCP_PATH)) return false;\n const config = readCursorMcpJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeCursorMcpJsonAtomic(config);\n return true;\n}\n\n// ─── Claude Desktop MCP Config ───\n//\n// Claude Desktop reads MCP servers from claude_desktop_config.json and (unlike\n// Cursor / Claude Code) supports ONLY stdio command servers there — no `url`\n// transport. So we register the SAME stdio proxy Claude Code's local mode uses\n// (~/.synkro/hooks/mcp-stdio-proxy.ts), which bridges stdio ⇄ the local\n// synkro-server on :18931 and reads the JWT from ~/.synkro/.mcp-jwt itself (no\n// secret in the config). This lets the user manage Synkro standards\n// (rules/tasks/findings) from Claude Desktop, not just Claude Code.\n\nconst CLAUDE_DESKTOP_CONFIG_PATH = join(\n homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json',\n);\nconst MCP_STDIO_PROXY_PATH = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n\n/**\n * Absolute path to `bun`. Claude Desktop is a GUI app and does NOT inherit the\n * login-shell PATH, so a bare \"bun\" command fails to spawn. Resolve it to an\n * absolute path at config-write time. Platform-aware (Windows probes `where` +\n * %USERPROFILE%\\.bun) via the shared installer helper.\n */\nfunction resolveBunBin(): string {\n return resolveBunBinShared();\n}\n\nfunction readDesktopJson(): ClaudeJson {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) return {};\n try {\n return JSON.parse(readFileSync(CLAUDE_DESKTOP_CONFIG_PATH, 'utf-8')) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeDesktopJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CLAUDE_DESKTOP_CONFIG_PATH), { recursive: true });\n // Unique per-write tmp name (pid + random) so concurrent writers can't race\n // on a shared tmpfile between write and rename (CWE-362). rename(2) onto the\n // final path stays atomic regardless of how many writers there are.\n const tmpPath = `${CLAUDE_DESKTOP_CONFIG_PATH}.synkro.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CLAUDE_DESKTOP_CONFIG_PATH);\n}\n\nexport interface InstallDesktopMcpResult {\n ok: boolean;\n path: string;\n proxy: string;\n reason?: string;\n}\n\n/**\n * Register the Synkro guardrails MCP server in Claude Desktop's config.\n * Idempotent. Returns ok:false (without writing) if the stdio proxy isn't on\n * disk yet — that means `synkro install` hasn't run, so there's nothing to\n * bridge to.\n */\nexport function installClaudeDesktopMcpConfig(): InstallDesktopMcpResult {\n if (!existsSync(MCP_STDIO_PROXY_PATH)) {\n return {\n ok: false,\n path: CLAUDE_DESKTOP_CONFIG_PATH,\n proxy: MCP_STDIO_PROXY_PATH,\n reason: 'stdio proxy not found — run `synkro install` first',\n };\n }\n const config = readDesktopJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Replace any prior Synkro-managed entry; leave the user's other servers alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n command: resolveBunBin(),\n args: ['run', MCP_STDIO_PROXY_PATH],\n env: { SYNKRO_MCP_PORT: process.env.SYNKRO_MCP_PORT || '18931' },\n [SYNKRO_MARKER]: true,\n };\n\n writeDesktopJsonAtomic(config);\n return { ok: true, path: CLAUDE_DESKTOP_CONFIG_PATH, proxy: MCP_STDIO_PROXY_PATH };\n}\n\n/** Remove all Synkro-managed MCP entries from Claude Desktop's config. */\nexport function uninstallClaudeDesktopMcpConfig(): boolean {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) return false;\n const config = readDesktopJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n writeDesktopJsonAtomic(config);\n return true;\n}\n\n/** Whether the Synkro MCP entry is registered in Claude Desktop's config. */\nexport function inspectClaudeDesktopMcpConfig(): { installed: boolean; configPath: string } {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) {\n return { installed: false, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n }\n try {\n const entry = readDesktopJson().mcpServers?.[SYNKRO_SERVER_NAME];\n return { installed: entry?.[SYNKRO_MARKER] === true, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n } catch {\n return { installed: false, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n }\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport function resolveSkillPaths(skills: string[], repoRoot: string): string[] {\n return skills\n .filter(s => s.endsWith('.md'))\n .map(s => resolve(repoRoot, s))\n .filter(p => existsSync(p));\n}\n","// :)\n/**\n * TypeScript hook scripts for Bun runtime — written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Each export is the full source code of a TypeScript file that runs via `#!/usr/bin/env bun`.\n * All grading, classification, CVE scanning, and persistence lives server-side.\n * Local mode: grade via local-cc channel; rules + embeddings from local PGLite only.\n */\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STUB HOOKS (`synkro install --mode stub`)\n//\n// In stub mode every hook becomes ~3 lines that delegate to _synkro-stub-common.ts.\n// The common file is pure PLUMBING — no scanning logic, no regexes, no verdict\n// parsing. It reads the host-only inputs the container can't see (target file\n// bytes, transcript tail, the repo's .synkro), POSTs a fixed envelope to the\n// local server's /api/scan/{surface}, and prints the reply. All the brains live\n// in the container (cli/scanning/*), so logic changes ship via a GHCR push with\n// no CLI re-install. Authored with String.raw + string concatenation (no ${}, no\n// inner backticks, no regex) so escapes survive materialization verbatim.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID, createHash } from 'node:crypto';\n\nconst HOME = homedir();\nconst PORT = process.env.SYNKRO_MCP_PORT || '18931';\n\n// ─── Telemetry: sync JSONL append, drained later by the CLI binary ──────────\n// Matches packages/cli/cli/telemetry/emit.ts. Inlined here so each bun hook\n// is self-contained (no path-relative import to a sibling module that may not\n// exist post-install). Writes to the same ~/.synkro/telemetry-pending.jsonl\n// the CLI binary appends to; ON CONFLICT in drain.ts dedupes.\nconst PENDING_JSONL = join(HOME, '.synkro', 'telemetry-pending.jsonl');\nconst META_JSON = join(HOME, '.synkro', 'telemetry-meta.json');\nconst CREDS_JSON = join(HOME, '.synkro', 'credentials.json');\n\ninterface TelemMeta { install_id?: string; enabled?: boolean }\nlet _telemMetaCache: TelemMeta | null = null;\nfunction telemMeta(): TelemMeta {\n if (_telemMetaCache) return _telemMetaCache;\n try {\n _telemMetaCache = JSON.parse(readFileSync(META_JSON, 'utf-8')) as TelemMeta;\n } catch { _telemMetaCache = {}; }\n return _telemMetaCache;\n}\n\ninterface TelemCreds { user_id?: string; org_id?: string; email?: string }\nlet _telemCredsCache: TelemCreds | null = null;\nfunction telemCreds(): TelemCreds {\n if (_telemCredsCache) return _telemCredsCache;\n try {\n const c = JSON.parse(readFileSync(CREDS_JSON, 'utf-8')) as TelemCreds;\n _telemCredsCache = { user_id: c.user_id, org_id: c.org_id, email: c.email };\n } catch { _telemCredsCache = {}; }\n return _telemCredsCache;\n}\n\ninterface HookEmitOpts {\n agent_kind?: 'claude_code' | 'cursor';\n cc_session_id?: string;\n cc_tool_use_id?: string;\n cc_model?: string;\n permission_mode?: string;\n cwd?: string;\n repo?: string;\n}\n\nexport function hookEmit(eventType: string, context: unknown, opts: HookEmitOpts = {}): void {\n try {\n const meta = telemMeta();\n if (meta.enabled === false) return;\n // Never ship rows with an empty install_id — they can't be attributed. An\n // absent meta file means the CLI hasn't bootstrapped telemetry yet; skip\n // rather than emit an orphan row (the CLI metaCache mints the id on install).\n if (!meta.install_id) return;\n const creds = telemCreds();\n const row = {\n client_event_id: randomUUID(),\n event_type: eventType,\n occurred_at: new Date().toISOString(),\n install_id: meta.install_id,\n user_id: creds.user_id ?? null,\n org_id: creds.org_id ?? null,\n email: creds.email ?? null,\n cli_version: process.env.SYNKRO_VERSION || '',\n emitter: process.env.SYNKRO_TELEMETRY_EMITTER || 'hook',\n agent_kind: opts.agent_kind ?? null,\n cc_session_id: opts.cc_session_id ?? null,\n cc_tool_use_id: opts.cc_tool_use_id ?? null,\n cc_turn_id: null,\n cc_model: opts.cc_model ?? null,\n permission_mode: opts.permission_mode ?? null,\n cwd: opts.cwd ?? null,\n repo: opts.repo ?? null,\n git_branch: null,\n git_sha: null,\n policy_name: null,\n capture_depth: null,\n platform: process.platform,\n hostname_hash: null,\n node_version: process.version || '',\n context,\n };\n try { mkdirSync(join(HOME, '.synkro'), { recursive: true }); } catch {}\n appendFileSync(PENDING_JSONL, JSON.stringify(row) + '\\n');\n } catch { /* never let telemetry block a hook */ }\n}\n\n// Surface name → telemetry event type. One-shot lookup for the runStub wrap.\nfunction eventForSurface(surface: string): { type: string; sessionPhase?: 'start' | 'end' } {\n switch (surface) {\n case 'session-start': return { type: 'session_lifecycle', sessionPhase: 'start' };\n case 'stop-summary': return { type: 'session_lifecycle', sessionPhase: 'end' };\n case 'prompt-submit': return { type: 'human_interaction' };\n case 'bash-followup': return { type: 'human_interaction' };\n case 'mcp-gate': return { type: 'mcp_tool_use' };\n case 'plan-judge': return { type: 'plan_review_result' };\n case 'transcript-sync': return { type: 'tool_call' };\n default: return { type: 'tool_call' };\n }\n}\n\n// Inspect the verdict text returned by the local server to derive a coarse\n// verdict for the telemetry row. Both CC and Cursor return JSON; we parse\n// lightly and ignore failures (default 'pass').\nfunction parseVerdict(text: string, harness: string): 'pass' | 'deny' | 'allow' | 'warning' | 'error' | 'ask' {\n if (!text) return 'pass';\n try {\n const j = JSON.parse(text);\n if (harness === 'cursor') {\n const p = j?.permission;\n if (p === 'deny') return 'deny';\n if (p === 'allow') return 'allow';\n return 'pass';\n }\n const dec = j?.hookSpecificOutput?.permissionDecision;\n if (dec === 'deny') return 'deny';\n // 'ask' is a consent prompt, NOT a block — keep it distinct so it doesn't\n // manufacture spurious violation rows (only genuine deny/block violates).\n if (dec === 'ask') return 'ask';\n if (dec === 'allow') return 'allow';\n return 'pass';\n } catch { return 'pass'; }\n}\n\nfunction loadMcpJwt(): string {\n try { return readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; }\n}\n\nlet _cfgCache: Record<string, string> | null = null;\nfunction cfgVal(key: string): string {\n // ~/.synkro/config.env (written by \"synkro install\") is AUTHORITATIVE for the keys it\n // defines. This hook runs under bun, which AUTO-LOADS a .env/.env.local from the repo\n // cwd into process.env — so a repo's own dev vars (e.g. SYNKRO_GATEWAY_URL=localhost for a\n // local \"wrangler dev\") would otherwise hijack the installed config and silently redirect\n // cloud grades to a dead gateway (fail-open, \"nothing fires\"). So read config.env FIRST and\n // only fall back to process.env for keys config.env doesn't set (e.g. SYNKRO_HOOK_FORMAT,\n // which is passed on the cursor hook command, never written to config.env).\n if (!_cfgCache) {\n _cfgCache = {};\n try {\n const txt = readFileSync(join(HOME, '.synkro', 'config.env'), 'utf-8');\n for (const line of txt.split('\\n')) {\n const mm = line.match(/^([A-Z0-9_]+)=['\"]?(.*?)['\"]?\\s*$/);\n if (mm) _cfgCache[mm[1]] = mm[2];\n }\n } catch {}\n }\n if (_cfgCache[key]) return _cfgCache[key];\n return process.env[key] || '';\n}\nfunction deployIsCloud(): boolean { return cfgVal('SYNKRO_DEPLOY_LOCATION') === 'cloud'; }\n\n// Cloud MCP gate: in cloud mode the local server (:18931) isn't running, so the\n// gate asks the org policy directly (api /api/mcp/gate, authed by .mcp-jwt).\n// Fail-open by contract — any error or non-block verdict allows the call.\nasync function cloudMcpGate(payload: any, harness: string): Promise<string> {\n try {\n const toolName = String(payload.tool_name || payload.tool || '');\n const parts = toolName.split('__');\n const server = (parts.length >= 3 && parts[0] === 'mcp') ? parts[1] : '';\n if (!server) return failOpen(harness);\n const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\\/+$/, '');\n const resp = await fetch(base + '/api/mcp/gate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },\n body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),\n signal: AbortSignal.timeout(5000),\n });\n if (!resp.ok) return failOpen(harness);\n const d = await resp.json() as any;\n if (d && d.decision === 'block') {\n const reason = String(d.reason || ('MCP server \"' + server + '\" blocked by policy'));\n if (harness === 'cursor') return JSON.stringify({ permission: 'deny', user_message: reason });\n return JSON.stringify({ systemMessage: '[synkro:cloud:mcp] ' + reason, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: reason } });\n }\n return failOpen(harness);\n } catch { return failOpen(harness); }\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n return Buffer.concat(chunks).toString('utf-8');\n}\n\nfunction isCursor(harnessOpt?: string): boolean {\n return harnessOpt === 'cursor' || process.argv.includes('--cursor') || process.env.SYNKRO_HOOK_FORMAT === 'cursor';\n}\n\nfunction failOpen(harness: string): string {\n return harness === 'cursor' ? '{\"permission\":\"allow\"}' : '{}';\n}\n\nfunction out(s: string): void { try { process.stdout.write(s + '\\n'); } catch {} }\n\nfunction gitRoot(cwd: string): string {\n if (!cwd) return '';\n try {\n return execSync('git rev-parse --show-toplevel', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch { return ''; }\n}\n\n// Once-per-session onboarding hint for repos with no synkro.toml file.\nfunction noSynkroHint(sessionId: string): string | null {\n const dir = join(HOME, '.synkro', '.no-synkro-hint');\n try {\n mkdirSync(dir, { recursive: true });\n const key = (sessionId || 'nosession').replace(/[^a-zA-Z0-9_-]/g, '_');\n const marker = join(dir, key);\n if (existsSync(marker)) return null;\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return '[synkro] No synkro.toml config in this repo — grading skipped. Run synkro install here to enable Synkro.';\n}\n\nfunction filePathFromToolInput(ti: any): string {\n if (!ti || typeof ti !== 'object') return '';\n return ti.file_path || ti.notebook_path || ti.path || ti.target_file || '';\n}\n\ninterface StubOpts {\n needsFile?: boolean;\n needsTranscript?: boolean;\n // Ship the WHOLE transcript (not just the 200KB tail). Sync surfaces need it\n // so the server can compute stable absolute message indices + total usage.\n fullTranscript?: boolean;\n needsPlan?: boolean;\n telemetry?: boolean;\n harness?: string;\n}\n\nexport async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {\n const harness = isCursor(opts.harness) ? 'cursor' : 'cc';\n const startedAt = Date.now();\n process.env.SYNKRO_TELEMETRY_EMITTER = process.env.SYNKRO_TELEMETRY_EMITTER || ('hook:' + surface);\n let telemPayload: any = null;\n let telemCwd = '';\n let telemSessionId = '';\n try {\n const input = await readStdin();\n if (!input.trim()) { out(failOpen(harness)); return; }\n const payload = JSON.parse(input);\n telemPayload = payload;\n telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';\n telemSessionId = String(payload?.session_id || payload?.conversation_id || '');\n\n // Cloud MCP enforcement: the gate can't reach the local server, so decide\n // against the org policy directly. Fail-open inside cloudMcpGate. Emit the\n // same mcp_tool_use (+ violation on block) the local path does, for parity.\n if (surface === 'mcp-gate' && deployIsCloud()) {\n const cloudResp = await cloudMcpGate(payload, harness);\n out(cloudResp);\n emitStubTelemetry(surface, harness, payload, cloudResp, Date.now() - startedAt, telemCwd, telemSessionId);\n return;\n }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const sessionId = String(payload.session_id || payload.conversation_id || '');\n const root = gitRoot(cwd);\n\n // Dormancy: a repo is onboarded only if it has a synkro.toml FILE at its git\n // root. Guard root !== HOME — ~/.synkro is the config DIRECTORY, so without\n // this a home-rooted cwd (dotfiles in git, non-git dir under home) could\n // look onboarded.\n let synkroFileText = '';\n if (root && root !== HOME && existsSync(join(root, 'synkro.toml'))) {\n try { synkroFileText = readFileSync(join(root, 'synkro.toml'), 'utf-8'); } catch {}\n }\n if (!synkroFileText) {\n // Repo not onboarded — emit a minimal tool_call so usage analytics still\n // sees the hook fire, then short-circuit. No verdict, no command body.\n // transcript-sync is a background sync, not a tool invocation — omit it so\n // it doesn't inflate tool_call counts. No model derivation on this hot path.\n if (surface !== 'transcript-sync') {\n hookEmit('tool_call', {\n tool_name: String(payload?.tool_name || ''),\n hook_event: surface === 'bash-followup' ? 'PostToolUse' : 'PreToolUse',\n tool_input: {},\n verdict: 'pass',\n route: 'local',\n route_fallback_reason: 'repo_not_onboarded',\n silent: false,\n duration_ms: Date.now() - startedAt,\n }, telemPayloadOpts(payload, harness, telemCwd, telemSessionId));\n }\n if (opts.telemetry) { out(failOpen(harness)); return; }\n const hint = noSynkroHint(sessionId);\n if (!hint) { out(failOpen(harness)); return; }\n if (harness === 'cursor') out(JSON.stringify({ permission: 'allow', user_message: hint }));\n else out(JSON.stringify({ systemMessage: hint, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: hint } }));\n return;\n }\n\n // Gather host-only inputs the container can't read.\n const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };\n\n if (opts.needsFile) {\n const fp = filePathFromToolInput(payload.tool_input || {});\n if (fp && existsSync(fp)) {\n try { envelope.baseContent = readFileSync(fp, 'utf-8').slice(0, 65536); } catch {}\n }\n }\n if (opts.needsTranscript) {\n const tp = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';\n if (tp && existsSync(tp)) {\n try {\n const t = readFileSync(tp, 'utf-8');\n envelope.transcript = (opts.fullTranscript || t.length <= 200000) ? t : t.slice(t.length - 200000);\n } catch {}\n }\n }\n if (opts.needsPlan) {\n const ti = payload.tool_input || {};\n envelope.planText = String(ti.plan || ti.content || payload.plan || '');\n }\n\n // bash-followup is telemetry, but it can carry the INLINE \"task complete ✓ N/N\"\n // result (the server waits up to 30s for the completion grade), so it needs a\n // wait above that budget — not the 6s telemetry default — or the stub abandons\n // the request before the result lands and the completion shows up a hook late.\n // prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from\n // being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)\n // and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.\n const timeoutMs = surface === 'bash-followup' ? 32000\n : surface === 'prompt-submit' ? 3500\n : (opts.telemetry ? 6000 : 28000);\n // Cloud has no local container to reach. Post the SAME envelope to the org's grader\n // via the gateway's /grade/submit as role 'scan:<surface>'; the container runs\n // scanRouter (runScan) server-side and returns the SAME shaped decision — so cloud\n // and local share ONE grading implementation, no client-side parsing. The gateway\n // accepts the durable 1yr MCP token (verifyOrg → org-derived, tenant-safe).\n const cloud = deployIsCloud();\n // Regex-free trailing-slash trim — this is inside a template literal, where a\n // regex like /\\\\/+$/ mis-escapes into broken emitted code (smoke:hooks catches it).\n let gwBase = cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh';\n while (gwBase.endsWith('/')) gwBase = gwBase.slice(0, -1);\n const url = cloud\n ? gwBase + '/grade/submit'\n : 'http://127.0.0.1:' + PORT + '/api/scan/' + surface + (harness === 'cursor' ? '?harness=cursor' : '');\n const body = cloud\n ? JSON.stringify({ role: 'scan:' + surface, payload: JSON.stringify(envelope), content: '' })\n : JSON.stringify(envelope);\n const headers = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() };\n // Telemetry hooks (bash-followup, transcript-sync, session telemetry) IGNORE the\n // response and the server processes them DETACHED, so delivery RELIABILITY beats\n // latency: retry a few times with backoff. Right after a server restart the\n // event loop can be briefly busy / not-yet-listening and a single POST silently\n // drops the event (this was the followup flakiness). Double-delivery is safe here\n // — the server side is idempotent-ish (completion in-flight guard, dup evidence\n // is harmless). Blocking hooks make ONE attempt: their response IS the verdict\n // and a retry would double-grade. Normal case: first attempt wins, no delay.\n // prompt-submit is EXCLUDED from the retry: it runs on the interactive prompt\n // path under a 5s hook timeout, and 3×6s+backoff (19.5s) blew that budget and\n // fired \"hook timed out after 5s\" on every prompt whenever the server was slow.\n // Dropping an occasional prompt tick is harmless; stalling the user is not.\n const attempts = (opts.telemetry && surface !== 'prompt-submit') ? 3 : 1;\n let text = '';\n for (let i = 0; i < attempts; i++) {\n try {\n const resp = await fetch(url, { method: 'POST', headers, body, signal: AbortSignal.timeout(timeoutMs) });\n if (resp.ok) { text = (await resp.text()).trim(); break; }\n } catch (e) { /* connection refused / timeout → retry below */ }\n if (i < attempts - 1) await new Promise((r) => setTimeout(r, 500 * (i + 1)));\n }\n const responseText = text || failOpen(harness);\n out(responseText);\n emitStubTelemetry(surface, harness, telemPayload, responseText, Date.now() - startedAt, telemCwd, telemSessionId);\n } catch (err) {\n out(failOpen(harness));\n const msg = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? (err.stack || '') : '';\n hookEmit('error', {\n category: 'hook_runtime',\n code: 'STUB_RUNSTUB_THREW',\n message: msg.slice(0, 4096),\n stack: stack.slice(0, 16384),\n emitter_context: 'runStub:' + surface,\n }, telemPayloadOpts(telemPayload, harness, telemCwd, telemSessionId));\n }\n}\n\n// ─── Bounded transcript tail read + model derivation ────────────────────────\n// The CC model lives in the LAST assistant message, always near the end of the\n// transcript. Read at most the last 200KB off disk instead of slurping+splitting\n// the whole file (transcripts reach 100s of MB → O(n^2) per session on the\n// verdict hot path). Only ever called on the ACTIVE path, never when dormant.\nconst TRANSCRIPT_TAIL_BYTES = 200000;\nfunction readTranscriptTail(path: string): string {\n try {\n const size = statSync(path).size;\n if (size <= TRANSCRIPT_TAIL_BYTES) return readFileSync(path, 'utf-8');\n const fd = openSync(path, 'r');\n try {\n const buf = Buffer.allocUnsafe(TRANSCRIPT_TAIL_BYTES);\n const n = readSync(fd, buf, 0, TRANSCRIPT_TAIL_BYTES, size - TRANSCRIPT_TAIL_BYTES);\n return buf.subarray(0, n).toString('utf-8');\n } finally { closeSync(fd); }\n } catch { return ''; }\n}\nfunction deriveCcModel(payload: any): string {\n const tp = (payload && payload.transcript_path) || '';\n if (!tp || !existsSync(tp)) return '';\n const lines = readTranscriptTail(tp).split('\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n if (!lines[i].includes('\"type\":\"assistant\"')) continue;\n try {\n const obj = JSON.parse(lines[i]);\n if (obj?.message?.model) return String(obj.message.model);\n } catch { /* torn line (tail may start mid-record) — skip, keep scanning */ }\n }\n return '';\n}\n\n// ─── Telemetry payload hygiene: scrub + byte-cap + summarize ────────────────\n// Self-contained port of _synkro-common's scrubSecrets — stub mode DELETES\n// _synkro-common.ts, so the stub cannot import it. Same hard-secret patterns +\n// credential key/value redaction; the entropy heuristic is omitted since these\n// rows never carry file bodies and every field is additionally byte-capped.\nconst TE_SECRET_PATTERNS: Array<{ type: string; re: RegExp }> = [\n { type: 'anthropic-key', re: /sk-ant-[A-Za-z0-9_-]{24,}/g },\n { type: 'openai-key', re: /sk-(?:proj-)?[A-Za-z0-9]{32,}/g },\n { type: 'aws-access-key', re: /AKIA[0-9A-Z]{16}/g },\n { type: 'github-token', re: /gh[posru]_[A-Za-z0-9]{36,}/g },\n { type: 'google-api-key', re: /AIza[0-9A-Za-z_-]{35}/g },\n { type: 'slack-token', re: /xox[baprs]-[0-9A-Za-z-]{10,}/g },\n { type: 'stripe-key', re: /[rs]k_(?:live|test)_[A-Za-z0-9]{20,}/g },\n { type: 'jwt', re: /eyJ[A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}/g },\n { type: 'private-key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },\n { type: 'bearer-token', re: /[Bb]earer[ ]+[A-Za-z0-9._-]{20,}/g },\n];\nfunction isPlaceholderSecret(v: string): boolean {\n if (/[$<>{}]/.test(v)) return true;\n const tail = v.replace(/^(sk-ant-|[rs]k_(?:live|test)_|sk-|AKIA|gh[posru]_|AIza|xox[baprs]-|Bearer[ ]+)/, '');\n return /^(x+|0+|a+|placeholder|example|changeme|your[-_a-z0-9]*|dummy|test|fake|redacted|sample)$/i.test(tail);\n}\nfunction scrubSecrets(text: string): string {\n if (!text) return text;\n let out = text;\n for (const { type, re } of TE_SECRET_PATTERNS) {\n out = out.replace(re, (m) => isPlaceholderSecret(m) ? m : '<redacted:' + type + '>');\n }\n out = out.replace(/((?:password|passwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|private[_-]?key)[\"' ]*[=:][ ]*[\"']?)([^ \"'\\n]{12,})/gi, (full, pfx, val) => {\n if (/[$<>{}]/.test(val)) return full;\n return pfx + '<redacted:credential>';\n });\n return out;\n}\n// Byte-cap (not char-cap): multi-byte content must not push a JSONL row past the\n// ~4KB append-atomicity window, above which concurrent hook appends tear.\nfunction cap(s: string, maxBytes: number): string {\n if (!s) return s;\n if (Buffer.byteLength(s, 'utf-8') <= maxBytes) return s;\n let out = s.slice(0, maxBytes);\n while (out && Buffer.byteLength(out, 'utf-8') > maxBytes) out = out.slice(0, -1);\n return out;\n}\n// Field byte budgets. A row's fixed envelope (ids, timestamps, cwd, model) runs\n// ~0.8-1.2KB, so no single free-text field may approach 4KB or the whole JSONL\n// row overruns the append-atomicity window. These leave margin even together.\nconst TE_CMD_CAP = 1200;\nconst TE_REASON_CAP = 800;\nconst TE_EXCERPT_CAP = 1200;\nconst TE_PROMPT_CAP = 2400;\n// Never embed a raw tool_input — Write/Edit carry whole file bodies, Bash the raw\n// command, so verbatim rows exfiltrate secrets AND overrun the 4KB row budget.\n// Emit a fixed-size fingerprint: tool name, top-level keys, serialized byte\n// length, short content hash. Bash is the one surface where the command text is\n// itself the signal, so include it scrubbed + byte-capped when asked.\nfunction toolInputSummary(toolName: string, toolInput: any, includeCommand: boolean): Record<string, any> {\n let ser = '';\n try { ser = JSON.stringify(toolInput == null ? {} : toolInput); } catch { ser = String(toolInput); }\n const isObj = !!toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput);\n const summary: Record<string, any> = {\n tool_name: toolName || undefined,\n keys: isObj ? Object.keys(toolInput).slice(0, 24) : [],\n bytes: Buffer.byteLength(ser, 'utf-8'),\n hash: createHash('sha256').update(ser).digest('hex').slice(0, 12),\n };\n if (includeCommand && isObj && typeof toolInput.command === 'string' && toolInput.command) {\n summary.command = cap(scrubSecrets(toolInput.command), TE_CMD_CAP);\n }\n return summary;\n}\n\n// cc_model is derived by the caller (bounded tail read) and passed in; this stays\n// allocation-only so the dormant hot path never touches the transcript.\nfunction telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {\n return {\n agent_kind: harness === 'cursor' ? 'cursor' : 'claude_code',\n cc_session_id: sessionId || undefined,\n cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,\n cc_model: ccModel || undefined,\n permission_mode: payload?.permission_mode ? String(payload.permission_mode) : undefined,\n cwd: cwd || undefined,\n };\n}\n\nfunction emitStubTelemetry(\n surface: string, harness: string, payload: any, responseText: string,\n durationMs: number, cwd: string, sessionId: string,\n): void {\n // transcript-sync is a background sync, not a tool invocation — no tool_call\n // row (keeps tool_call counts accurate); its usage lands via session_lifecycle.\n // Bail before any transcript read so it stays cheap.\n if (surface === 'transcript-sync') return;\n\n const opts = telemPayloadOpts(payload, harness, cwd, sessionId, deriveCcModel(payload));\n const verdict = parseVerdict(responseText, harness);\n const meta = eventForSurface(surface);\n\n if (meta.sessionPhase === 'start') {\n hookEmit('session_lifecycle', {\n phase: 'start', route: 'local-cc', channel_port: Number(PORT) || 18931,\n channel_reachable: true, open_findings: 0,\n transcript_path: payload?.transcript_path || undefined,\n }, opts);\n return;\n }\n if (meta.sessionPhase === 'end') {\n hookEmit('session_lifecycle', {\n phase: 'end', route: 'local-cc', channel_port: Number(PORT) || 18931,\n channel_reachable: true, open_findings: 0,\n transcript_path: payload?.transcript_path || undefined,\n }, opts);\n return;\n }\n if (meta.type === 'human_interaction') {\n const kind = surface === 'prompt-submit' ? 'prompt_submit' : 'consent_consume';\n const promptText = surface === 'prompt-submit'\n ? cap(String(payload?.prompt || payload?.message || payload?.content || ''), TE_PROMPT_CAP)\n : undefined;\n hookEmit('human_interaction', { kind, prompt_text: promptText }, opts);\n return;\n }\n if (meta.type === 'mcp_tool_use') {\n const toolName = String(payload?.tool_name || payload?.tool || '');\n hookEmit('mcp_tool_use', {\n tool_name: toolName,\n args_summary: toolInputSummary(toolName, payload?.tool_input, true),\n duration_ms: durationMs,\n success: verdict !== 'deny' && verdict !== 'error',\n }, opts);\n if (verdict === 'deny') {\n hookEmit('violation', {\n hook_type: 'bash', rule_id: '', rule_mode: 'blocking', severity: 'high',\n category: 'mcp_governance', reason: 'mcp_blocked',\n rules_checked: [], violated_rules: [],\n command: cap(scrubSecrets(toolName), TE_CMD_CAP),\n tool_name: toolName, hook_event: 'PreToolUse', tool_input: toolInputSummary(toolName, payload?.tool_input, false),\n verdict: 'block', route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n }\n return;\n }\n if (meta.type === 'plan_review_result') {\n hookEmit('plan_review_result', {\n verdict: verdict === 'deny' ? 'advisory' : 'clean',\n reason: '', plan_excerpt: cap(scrubSecrets(String(payload?.tool_input?.plan || '')), TE_EXCERPT_CAP),\n rule_ids_relevant: [], route: 'local',\n }, opts);\n return;\n }\n // Default: tool_call. Add a violation row ONLY on a genuine denial ('ask' is a\n // consent prompt, not a block). tool_input is summarized, never embedded raw.\n const toolName = String(payload?.tool_name || '');\n hookEmit('tool_call', {\n tool_name: toolName,\n hook_event: surface === 'bash-followup' ? 'PostToolUse' : 'PreToolUse',\n tool_input: toolInputSummary(toolName, payload?.tool_input, true),\n verdict, route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n if (verdict === 'deny') {\n let hookType: 'bash' | 'edit' | 'plan' | 'cve_scan' | 'cwe_scan' = 'bash';\n if (surface === 'edit-precheck') hookType = 'edit';\n else if (surface === 'cwe-precheck') hookType = 'cwe_scan';\n else if (surface === 'cve-precheck') hookType = 'cve_scan';\n else if (surface === 'plan-judge') hookType = 'plan';\n hookEmit('violation', {\n hook_type: hookType,\n rule_id: '', rule_mode: 'blocking', severity: 'high', category: 'policy',\n reason: cap('server-denied', TE_REASON_CAP),\n rules_checked: [], violated_rules: [],\n command: cap(scrubSecrets(String(payload?.tool_input?.command || filePathFromToolInput(payload?.tool_input) || '')), TE_CMD_CAP),\n tool_name: toolName, hook_event: 'PreToolUse', tool_input: toolInputSummary(toolName, payload?.tool_input, false),\n verdict: 'block', route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n }\n}\n`;\n\nfunction stubHook(surface: string, optsLiteral: string): string {\n return '#!/usr/bin/env bun\\n' +\n \"import { runStub } from './_synkro-stub-common.ts';\\n\" +\n 'runStub(' + JSON.stringify(surface) + ', ' + optsLiteral + ');\\n';\n}\n\n// edit/bash/cwe/cve/install ship the transcript tail too: the server reads the\n// model (last assistant message) + user intent / consent history from it — the\n// same context the old fat hooks extracted. Stub code stays 3 lines; this is\n// just a flag telling the plumbing common to attach the tail.\nexport const STUB_EDIT_PRECHECK_TS = stubHook('edit-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_CWE_PRECHECK_TS = stubHook('cwe-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_CVE_PRECHECK_TS = stubHook('cve-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_BASH_JUDGE_TS = stubHook('bash-judge', '{ needsTranscript: true }');\n// SKILL judge — its OWN hook so skillGuard is its OWN PreToolUse system-message block\n// (CC renders one systemMessage per hook). Registered on the same Bash + Edit matchers\n// as bash-judge/edit-precheck (runs in parallel, waits for all → a skill deny still\n// blocks). needsFile: reconstruct edit content; needsTranscript: user intent.\nexport const STUB_SKILL_JUDGE_TS = stubHook('skill-judge', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_INSTALL_SCAN_TS = stubHook('install-scan', '{ needsTranscript: true }');\nexport const STUB_AGENT_JUDGE_TS = stubHook('agent-judge', '{ needsTranscript: true }');\n// PreToolUse on mcp__* tools → MCP server governance (allow/blocklist + grade).\n// Blocking, like the bash judge: the server's deny response stops the call.\nexport const STUB_MCP_GATE_TS = stubHook('mcp-gate', '{ needsTranscript: true }');\nexport const STUB_PLAN_JUDGE_TS = stubHook('plan-judge', '{ needsPlan: true }');\n// NOT telemetry: the Stop hook also runs the task-adherence completion gate,\n// which needs the full grading timeout and may return {decision:\"block\"} (passed\n// through verbatim to CC). When no task is active the handler returns instantly,\n// so stop stays fast in the common case.\nexport const STUB_STOP_SUMMARY_TS = stubHook('stop-summary', '{ needsTranscript: true, fullTranscript: true }');\nexport const STUB_SESSION_START_TS = stubHook('session-start', '{ telemetry: true }');\nexport const STUB_TRANSCRIPT_SYNC_TS = stubHook('transcript-sync', '{ needsTranscript: true, fullTranscript: true, telemetry: true }');\nexport const STUB_USER_PROMPT_SUBMIT_TS = stubHook('prompt-submit', '{ telemetry: true }');\nexport const STUB_BASH_FOLLOWUP_TS = stubHook('bash-followup', '{ telemetry: true }');\n\n// PreToolUse hook on the activate_standard MCP tool. Fires IN the activating\n// session (carrying its real session_id) just BEFORE the tool runs, and stamps\n// the activation intent on the server so the task binds to THIS exact session —\n// no most-recent-session race. Self-contained (doesn't need _synkro-stub-common);\n// always allow ('{}'), never blocks the tool.\nexport const STUB_TASK_ACTIVATE_INTENT_TS = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nconst PORT = process.env.SYNKRO_MCP_PORT || '18931';\nfunction mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }\nconst chunks: Buffer[] = [];\nfor await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\ntry {\n const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');\n await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },\n body: JSON.stringify({ session_id: payload.session_id, tool_input: payload.tool_input }),\n signal: AbortSignal.timeout(5000),\n });\n} catch {}\nprocess.stdout.write('{}\\\\n');\n`;\nexport const STUB_CURSOR_BASH_JUDGE_TS = stubHook('bash-judge', \"{ needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_SKILL_JUDGE_TS = stubHook('skill-judge', \"{ needsFile: true, needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_EDIT_CAPTURE_TS = stubHook('edit-precheck', \"{ needsFile: true, needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_AGENT_CAPTURE_TS = stubHook('agent-judge', \"{ needsTranscript: true, harness: 'cursor' }\");\n","/**\n * Synkro CLI Authentication\n *\n * OAuth-style authentication flow for CLI integration with Synkro web platform.\n * Mirrors the Node.js reference implementation.\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from \"node:http\";\nimport { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport jwt from \"jsonwebtoken\";\n\n// Types\ninterface WorkOSJwtPayload {\n iss: string; // \"https://api.workos.com/\"\n sub: string; // user ID\n aud?: string; // client ID\n exp: number;\n iat: number;\n email?: string;\n org_id?: string;\n role?: string;\n permissions?: string[];\n sid?: string; // session ID\n}\n\n// Configuration — matches the desktop app pattern (packages/desktop/src-tauri/src/auth.rs).\n// Dev dashboard runs on :4322; CLI listens on 8100 for the OAuth callback.\nconst PORT = 8100;\n// Same poisoning concern as the gateway URL: a developer's local .env or\n// op:// expansion can land in SYNKRO_WEB_AUTH_URL. Only honor http(s) values;\n// fall through to the prod dashboard otherwise so the OAuth callback always\n// has a real origin to open the browser at.\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : \"https://app.synkro.sh\";\nconst AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join(homedir(), \".synkro\", \"credentials.json\");\nconst RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;\nconst SYNKRO_API_URL = (RAW_API_URL && /^https?:\\/\\//.test(RAW_API_URL))\n ? RAW_API_URL\n : \"https://api.synkro.sh\";\n\n// Types — matches the AuthCredentials shape returned by the dashboard's\n// /api/auth/cli-callback (see packages/app/src/pages/api/auth/cli-callback.ts).\nexport interface AuthCredentials {\n access_token: string;\n refresh_token: string;\n user_id?: string;\n email?: string;\n org_id?: string;\n state?: string;\n}\n\nexport interface UserInfo {\n id: string;\n email: string;\n org_id?: string;\n}\n\n// HTML responses\nconst SUCCESS_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Successful - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n .checkmark {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #10b981;\n margin: 0 auto 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .checkmark svg {\n width: 50px;\n height: 50px;\n stroke: white;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n .close-note {\n margin-top: 1.5rem;\n font-size: 0.875rem;\n color: #9ca3af;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"checkmark\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\"></path>\n </svg>\n </div>\n <h1>Authentication Successful!</h1>\n <p>Your Synkro CLI has been authenticated.</p>\n <p class=\"close-note\">You can close this window and return to your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\nconst ERROR_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Failed - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Authentication Failed</h1>\n <p>Please try again from your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\n/**\n * Open URL in default browser\n */\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n\n switch (os) {\n case \"darwin\":\n bin = \"open\";\n args = [url];\n break;\n case \"win32\":\n // `start` is a cmd built-in, so we host it via cmd /c, but pass the URL\n // as a literal arg through execFile (no shell parsing) to keep shell\n // metacharacters from being interpreted.\n bin = \"cmd\";\n args = [\"/c\", \"start\", \"\", url];\n break;\n default:\n bin = \"xdg-open\";\n args = [url];\n }\n\n // execFile (vs exec) does NOT spawn a shell, so url is treated as a single\n // argv entry regardless of contents. Removes shell-injection risk if url\n // ever ends up containing $/`/;/&/etc.\n execFile(bin, args, (error) => {\n if (error) {\n console.error(\"Failed to open browser automatically.\");\n console.log(`Please open this URL manually: ${url}`);\n }\n });\n}\n\n/**\n * Save authentication credentials to file\n */\nexport function saveCredentials(data: AuthCredentials): void {\n const dir = dirname(AUTH_FILE);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Load saved authentication credentials\n */\nexport function loadCredentials(): AuthCredentials | null {\n if (!existsSync(AUTH_FILE)) {\n return null;\n }\n\n try {\n const content = readFileSync(AUTH_FILE, \"utf8\");\n return JSON.parse(content);\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Create HTTP server to receive OAuth callback.\n *\n * Mirrors packages/desktop/src-tauri/src/auth.rs:\n * - Listens on PORT\n * - Handles OPTIONS preflight (CORS)\n * - Catches /auth?token=...&refresh_token=...&user_id=...&email=...&org_id=...&state=...\n * - Returns success HTML on token receipt\n */\nfunction createCallbackServer(): Promise<AuthCredentials> {\n // Tokens land via POST body (JSON), never query params, so they don't get\n // logged into req.url, browser DevTools URL bars, server access logs, or\n // tooling that snapshots URLs. CORS origin is pinned to the configured\n // dashboard so a random page on a different origin can't post forged\n // credentials at the local listener.\n const CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": SYNKRO_WEB_AUTH_URL,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n };\n\n return new Promise((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n // CORS preflight — only echo the pinned origin if the request actually\n // comes from there; otherwise omit ACAO so the browser blocks the call.\n if (req.method === \"OPTIONS\") {\n const origin = req.headers.origin;\n if (origin === SYNKRO_WEB_AUTH_URL) {\n res.writeHead(204, CORS_HEADERS);\n } else {\n res.writeHead(204, {\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n });\n }\n res.end();\n return;\n }\n\n // Reject requests whose Origin header isn't the dashboard. Browsers\n // send Origin on cross-origin POSTs, so this stops a hostile page from\n // forging credentials at our local listener even if it bypasses CORS.\n const reqOrigin = req.headers.origin;\n if (reqOrigin && reqOrigin !== SYNKRO_WEB_AUTH_URL) {\n res.writeHead(403, { \"Vary\": \"Origin\" });\n res.end();\n return;\n }\n\n if (!req.url) {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n const url = new URL(req.url, `http://localhost:${PORT}`);\n\n if (url.pathname !== \"/auth\") {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n if (req.method !== \"POST\") {\n // Reject GET so a stale/older dashboard build can't deliver tokens\n // via query-string. Forces upgrade. CLI 1.0.4+ requires the v1.6+\n // dashboard build.\n res.writeHead(405, { ...CORS_HEADERS, \"Allow\": \"POST, OPTIONS\", \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n return;\n }\n\n // Cap body at 16 KB — JWT pairs are ~4–8 KB; anything bigger is junk.\n const MAX_BODY = 16 * 1024;\n const chunks: Buffer[] = [];\n let total = 0;\n let aborted = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > MAX_BODY) {\n aborted = true;\n res.writeHead(413, CORS_HEADERS);\n res.end();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (aborted) return;\n let parsed: any;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"invalid_json\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: invalid JSON body\"));\n }, 200);\n return;\n }\n\n const token: string | undefined = parsed?.token;\n if (!token || typeof token !== \"string\") {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing_token\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: missing token\"));\n }, 200);\n return;\n }\n\n const authData: AuthCredentials = {\n access_token: token,\n refresh_token: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : \"\",\n user_id: typeof parsed.user_id === \"string\" ? parsed.user_id : undefined,\n email: typeof parsed.email === \"string\" ? parsed.email : undefined,\n org_id: typeof parsed.org_id === \"string\" ? parsed.org_id : undefined,\n state: typeof parsed.state === \"string\" ? parsed.state : undefined,\n };\n\n res.writeHead(200, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n\n setTimeout(() => {\n server.close();\n resolve(authData);\n }, 200);\n });\n req.on(\"error\", (e) => {\n if (aborted) return;\n aborted = true;\n try { res.writeHead(500, CORS_HEADERS); res.end(); } catch {}\n setTimeout(() => {\n server.close();\n reject(e);\n }, 200);\n });\n });\n\n server.listen(PORT);\n\n server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${PORT} is already in use. Close any other Synkro CLI instance and retry.`,\n ),\n );\n } else {\n reject(error);\n }\n });\n });\n}\n\nexport type AuthStatus =\n | { phase: 'starting' }\n | { phase: 'browser-opened'; url: string }\n | { phase: 'waiting' }\n | { phase: 'success' }\n | { phase: 'error'; message: string };\n\n/**\n * Initiate the OAuth-style authentication flow\n */\nexport async function authenticate(\n onStatus?: (status: AuthStatus) => void,\n): Promise<AuthCredentials | null> {\n const emit = onStatus || (() => {});\n\n try {\n emit({ phase: 'starting' });\n\n // Start local server to receive the callback\n const serverPromise = createCallbackServer();\n\n // Open browser to the CLI auth page\n const authUrl = `${SYNKRO_WEB_AUTH_URL}/cli-auth?port=${PORT}`;\n openBrowser(authUrl);\n\n emit({ phase: 'browser-opened', url: authUrl });\n emit({ phase: 'waiting' });\n\n // Wait for authentication callback\n const data = await serverPromise;\n\n emit({ phase: 'success' });\n saveCredentials(data);\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emit({ phase: 'error', message });\n return null;\n }\n}\n\n/**\n * Check if user is authenticated (credentials exist and token not expired)\n */\nexport function isAuthenticated(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Also check token expiry\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true; // Can't decode, assume valid (refresh will handle it)\n\n // Consider expired if past expiration (no buffer here — ensureValidToken handles refresh buffer)\n return Date.now() < decoded.exp * 1000;\n } catch {\n return true; // Decode failed, let ensureValidToken handle it\n }\n}\n\n/**\n * Get current user ID from JWT token\n */\nexport function getCurrentUserId(): string {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded?.sub) {\n throw new Error(\"Invalid token\");\n }\n\n return decoded.sub;\n}\n\n/**\n * Get user info from JWT\n */\nexport function getUserInfo(): UserInfo {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n // Prefer the explicit user_id/email/org_id stashed during the OAuth callback\n // (the dashboard returns them as query params). Fall back to JWT decode if\n // we somehow received older creds without those fields.\n if (creds.user_id) {\n return {\n id: creds.user_id,\n email: creds.email ?? '',\n org_id: creds.org_id,\n };\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n\n return {\n id: decoded.sub,\n email: decoded.email ?? '',\n org_id: decoded.org_id,\n };\n}\n\n/**\n * Get access token for API calls\n */\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\n/**\n * Check if token is expired (with 5 min buffer)\n */\nexport function isTokenExpired(): boolean {\n const creds = loadCredentials();\n if (!creds) return true;\n\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true;\n\n // Expired if less than 5 minutes remaining\n const expiresAt = decoded.exp * 1000;\n const buffer = 5 * 60 * 1000; // 5 minutes\n return Date.now() > expiresAt - buffer;\n } catch {\n return true;\n }\n}\n\n/**\n * Refresh the access token using refresh_token\n */\nexport async function refreshToken(): Promise<boolean> {\n const creds = loadCredentials();\n if (!creds?.refresh_token) return false;\n\n try {\n const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: creds.refresh_token }),\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (data.access_token) {\n saveCredentials({\n ...creds,\n access_token: data.access_token,\n refresh_token: data.refresh_token || creds.refresh_token,\n });\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n// Mutex: prevent concurrent token refresh races\nlet refreshPromise: Promise<boolean> | null = null;\n\n/**\n * Ensure we have a valid token, refreshing if needed\n */\nexport async function ensureValidToken(): Promise<boolean> {\n // PRESENCE check only — do NOT gate on isAuthenticated(). isAuthenticated() reports an EXPIRED\n // token as \"not authenticated\", so gating on it here bails BEFORE the refresh that would fix the\n // expiry — the token then 401s forever and every sync silently dies until a manual `synkro login`.\n // We only need credentials that can be refreshed.\n const creds = loadCredentials();\n if (!creds?.access_token && !creds?.refresh_token) return false;\n\n if (isTokenExpired()) {\n if (!creds.refresh_token) return false; // expired and nothing to refresh with\n if (!refreshPromise) {\n refreshPromise = refreshToken().finally(() => { refreshPromise = null; });\n }\n const refreshed = await refreshPromise;\n if (!refreshed) {\n // Do NOT wipe credentials on a failed refresh. A transient network error or a brief gateway\n // blip would otherwise log the user out mid-session and silently kill every sync until a\n // manual re-login. Keep them; the next call retries. A genuinely revoked refresh_token just\n // keeps returning false and the caller surfaces it.\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Fetch a gateway endpoint with the login token, self-healing on expiry: ensure a valid token\n * first, and if the call still 401s (mid-flight expiry / clock skew), force ONE refresh and retry.\n * This is what makes long-running / repeated sync calls survive the short access-token TTL instead\n * of silently 401'ing forever (the transcript-sync failure mode).\n */\nexport async function authedFetch(url: string, init: RequestInit = {}): Promise<Response> {\n const withToken = (): RequestInit => ({\n ...init,\n headers: { ...(init.headers as Record<string, string> | undefined), Authorization: `Bearer ${getAccessToken() ?? ''}` },\n });\n await ensureValidToken();\n let res = await fetch(url, withToken());\n if (res.status === 401 && await refreshToken()) {\n res = await fetch(url, withToken());\n }\n return res;\n}\n\n/**\n * Clear saved credentials (logout)\n */\nexport function clearCredentials(): void {\n if (existsSync(AUTH_FILE)) {\n unlinkSync(AUTH_FILE);\n }\n}\n\n/**\n * Get secrets for a user's integrations\n * In production, this would fetch from Infisical vault using the access token\n *\n * These are secrets the USER provides for THEIR integrations:\n * - AWS credentials (for their S3 buckets)\n * - HuggingFace token (for their datasets)\n * - Langsmith API key (for their projects)\n */\nexport async function getSecrets(\n userId: string,\n integrationId: string,\n): Promise<Record<string, string>> {\n // TODO: In production, use access token to fetch from Infisical\n // For now, return from environment (dev mode)\n return {\n AWS_ACCESS_KEY_ID: process.env.USER_AWS_KEY || \"\",\n AWS_SECRET_ACCESS_KEY: process.env.USER_AWS_SECRET || \"\",\n AWS_REGION: process.env.USER_AWS_REGION || \"us-east-1\",\n HF_TOKEN: process.env.USER_HF_TOKEN || \"\",\n LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || \"\",\n };\n}\n","/**\n * Synkro CLI Authentication Module\n *\n * Exports authentication functions for use throughout the CLI.\n */\n\nexport {\n authenticate,\n isAuthenticated,\n getCurrentUserId,\n getUserInfo,\n getAccessToken,\n loadCredentials,\n saveCredentials,\n clearCredentials,\n getSecrets,\n isTokenExpired,\n refreshToken,\n ensureValidToken,\n} from './stub.js';\n\nexport type { AuthCredentials, UserInfo, AuthStatus } from './stub.js';\n","// :)\n/**\n * CLI API client for project and violation endpoints.\n *\n * Calls the Synkro API (Hono/Cloudflare Workers) for project management, violations, and policy upsert.\n * SYNKRO_CRUD_URL points to the API server (defaults to http://localhost:8788/api).\n */\n\nimport { getAccessToken, ensureValidToken } from '../auth/index.js';\n\nlet API_URL = 'https://api.synkro.sh/api';\n\nexport function setApiBaseUrl(url: string): void {\n API_URL = url;\n}\n\n// Types\n\nexport interface Project {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n api_key_count: number;\n created_at: string | null;\n repos?: Array<{ id: string; github_repo_id: number | null; full_name: string }>;\n}\n\nexport interface Violation {\n id: string | null;\n run_id: string | null;\n score: number | null;\n severity: string | null;\n issues: string[];\n rules_violated: string[];\n messages: Array<{ role: string; content: string }>;\n model: string | null;\n comment: string | null;\n created_at: string | null;\n}\n\nexport interface ViolationsResponse {\n violations: Violation[];\n total: number;\n project_id: string;\n project_slug: string | null;\n policy_text: string | null;\n rules: Array<Record<string, unknown>>;\n}\n\nexport interface GetViolationsOptions {\n limit?: number;\n offset?: number;\n severity?: string;\n}\n\nexport interface PolicyUpsertResponse {\n ok: boolean;\n policy_id: string;\n}\n\n// API helper\n\nasync function callApi<T>(\n method: string,\n endpoint: string,\n body?: Record<string, unknown>\n): Promise<T> {\n if (!API_URL) {\n throw new Error('API URL not configured. Run `synkro install` first.');\n }\n\n const url = `${API_URL}${endpoint}`;\n await ensureValidToken();\n const accessToken = getAccessToken();\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (accessToken) {\n headers['Authorization'] = `Bearer ${accessToken}`;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ detail: response.statusText }));\n throw new Error(error.detail || `API error: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport interface CreateProjectResponse {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n created_at: string | null;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n key_prefix: string;\n name: string;\n project_id: string;\n created_at: string | null;\n}\n\n// Project API functions\n\n/**\n * Create a new project.\n */\nexport async function createProject(\n name: string,\n repos?: Array<{ github_repo_id?: number; full_name: string; default_branch?: string; private?: boolean }>,\n): Promise<CreateProjectResponse> {\n const body: Record<string, unknown> = { name };\n if (repos && repos.length > 0) body.repos = repos;\n return callApi<CreateProjectResponse>('POST', '/projects', body);\n}\n\n/**\n * Create an API key for a project. Returns plaintext key (shown once).\n */\nexport async function createApiKey(\n projectId: string,\n name?: string\n): Promise<CreateApiKeyResponse> {\n return callApi<CreateApiKeyResponse>('POST', '/api-keys', {\n project_id: projectId,\n name: name || 'CLI Key',\n });\n}\n\n/**\n * List all projects for the authenticated user.\n */\nexport async function listProjects(): Promise<Project[]> {\n return callApi<Project[]>('GET', '/projects');\n}\n\n/**\n * Get violations for a project with optional filtering.\n * Also returns the active policy text and rules.\n */\nexport async function getViolations(\n projectId: string,\n options: GetViolationsOptions = {}\n): Promise<ViolationsResponse> {\n const params = new URLSearchParams();\n if (options.limit) params.set('limit', String(options.limit));\n if (options.offset) params.set('offset', String(options.offset));\n if (options.severity) params.set('severity', options.severity);\n\n const qs = params.toString();\n const endpoint = `/projects/${projectId}/violations${qs ? `?${qs}` : ''}`;\n return callApi<ViolationsResponse>('GET', endpoint);\n}\n\n/**\n * Upsert a policy for a project (deploy rules to gateway).\n */\nexport async function upsertPolicy(\n projectId: string,\n policyText: string,\n rules: Array<Record<string, unknown>>,\n ruleCount: number\n): Promise<PolicyUpsertResponse> {\n return callApi<PolicyUpsertResponse>('POST', `/projects/${projectId}/policies`, {\n policy_text: policyText,\n rules,\n rule_count: ruleCount,\n });\n}\n\n/**\n * Update a project's configuration.\n */\nexport async function updateProject(\n projectId: string,\n updates: {\n name?: string;\n provider?: string;\n langsmith_project?: string;\n is_active?: boolean;\n }\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('PATCH', `/projects/${projectId}`, updates as Record<string, unknown>);\n}\n\n/**\n * Unlink a repo from a project.\n */\nexport async function unlinkRepo(\n projectId: string,\n repoId: string,\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('DELETE', `/projects/${projectId}/repos/${repoId}`);\n}\n\n/**\n * Resolve a project by name or slug (fuzzy match against user's projects).\n * Returns the first matching project or null.\n */\nexport async function resolveProject(nameOrSlug: string): Promise<Project | null> {\n const projects = await listProjects();\n const query = nameOrSlug.toLowerCase();\n\n // Exact slug match\n const exactSlug = projects.find(p => p.slug === nameOrSlug);\n if (exactSlug) return exactSlug;\n\n // Exact name match (case-insensitive)\n const exactName = projects.find(p => p.name.toLowerCase() === query);\n if (exactName) return exactName;\n\n // Partial name match\n const partial = projects.find(p => p.name.toLowerCase().includes(query));\n if (partial) return partial;\n\n // Partial slug match\n const partialSlug = projects.find(p => p.slug.includes(query));\n if (partialSlug) return partialSlug;\n\n return null;\n}\n","// :)\n/**\n * GitHub Actions workflow YAML template for Synkro PR scanning.\n *\n * Customer commits this to .github/workflows/synkro.yml. Triggers on\n * pull_request open/synchronize/reopened. Runs `synkro scan-pr` which\n * spawns claude --print with their CLAUDE_CODE_OAUTH_TOKEN per file.\n *\n * Both CLIs install via npm (npm registry attestations + signed packages\n * are verified by npm itself). No curl-pipe-to-bash; we publish to npm\n * specifically so CI can install us through a trusted package manager.\n */\n\nexport const SYNKRO_WORKFLOW_YAML = `name: Synkro Security Review\non:\n pull_request:\n types: [opened, synchronize, reopened]\n workflow_dispatch:\n inputs:\n pr_number:\n description: PR number to scan\n required: true\n sha:\n description: Commit SHA to scan\n required: true\n\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: write\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n ref: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n\n - name: Cache npm globals\n id: cache-npm-global\n uses: actions/cache@v4\n with:\n path: ~/.npm-global\n key: synkro-cli-\\${{ runner.os }}-v1\n\n - name: Install Synkro CLI + Claude Code CLI\n run: |\n npm config set prefix ~/.npm-global\n npm install -g @synkro-sh/cli @anthropic-ai/claude-code\n echo \"$HOME/.npm-global/bin\" >> $GITHUB_PATH\n\n - name: Run Synkro PR scan\n run: synkro-cli scan-pr\n env:\n CLAUDE_CODE_OAUTH_TOKEN: \\${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}\n SYNKRO_API_KEY: \\${{ secrets.SYNKRO_API_KEY }}\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n SYNKRO_PR_NUMBER: \\${{ inputs.pr_number || github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_SHA: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n SYNKRO_GATEWAY_URL: \\${{ vars.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh' }}\n`;\n\nexport const WORKFLOW_FILENAME = 'synkro.yml';\nexport const WORKFLOW_PATH = '.github/workflows/synkro.yml';\n","/**\n * GitHub repo setup for PR scanning.\n *\n * Uses `gh secret set` to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY\n * as repo secrets, then writes .github/workflows/synkro.yml to the local\n * clone if present.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\nexport interface GitHubAuthOptions {\n token: string;\n}\n\nfunction ghSecretSet(token: string, owner: string, repo: string, name: string, value: string): void {\n execSync(`gh secret set ${name} --repo ${owner}/${repo} --body -`, {\n input: value,\n env: { ...process.env, GH_TOKEN: token },\n stdio: ['pipe', 'ignore', 'pipe'],\n timeout: 30_000,\n });\n}\n\n/**\n * List repos the token has access to.\n */\nexport async function listAccessibleRepos(opts: GitHubAuthOptions): Promise<Array<{ owner: string; repo: string; full_name: string }>> {\n const repos: Array<{ owner: string; repo: string; full_name: string }> = [];\n let page = 1;\n while (page <= 5) { // cap pagination\n const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator`;\n const resp = await fetch(url, {\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n if (!resp.ok) {\n throw new Error(`GitHub API ${resp.status} listing repos`);\n }\n const data = await resp.json() as Array<{ full_name: string; owner: { login: string }; name: string }>;\n if (data.length === 0) break;\n for (const r of data) {\n repos.push({ owner: r.owner.login, repo: r.name, full_name: r.full_name });\n }\n if (data.length < 100) break;\n page++;\n }\n return repos;\n}\n\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken?: string; synkroApiKey: string },\n): Promise<void> {\n try { execSync('gh --version', { stdio: 'ignore', timeout: 5000 }); } catch {\n throw new Error('GitHub CLI (gh) not found. Install it: https://cli.github.com');\n }\n if (secrets.claudeCodeOauthToken) {\n ghSecretSet(opts.token, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken);\n }\n ghSecretSet(opts.token, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey);\n}\n\n/**\n * Write the workflow YAML to a local repo clone (if the user is in one).\n * Returns the absolute path written, or null if cwd isn't a git repo.\n */\nexport function writeWorkflowFile(repoRootPath: string): string | null {\n const workflowDir = join(repoRootPath, '.github', 'workflows');\n mkdirSync(workflowDir, { recursive: true });\n const workflowFile = join(workflowDir, 'synkro.yml');\n writeFileSync(workflowFile, SYNKRO_WORKFLOW_YAML, 'utf-8');\n return workflowFile;\n}\n\n/**\n * Find the git repo root for a given cwd. Returns null if not in a git repo.\n */\nexport function findGitRoot(startCwd: string): string | null {\n let cur = startCwd;\n while (cur && cur !== '/') {\n if (existsSync(join(cur, '.git'))) return cur;\n const parent = join(cur, '..');\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nexport const SECRET_NAMES = {\n CLAUDE_OAUTH: 'CLAUDE_CODE_OAUTH_TOKEN',\n SYNKRO_API_KEY: 'SYNKRO_API_KEY',\n} as const;\n\nexport const WORKFLOW_RELATIVE_PATH = WORKFLOW_PATH;\n","// :)\n/**\n * Shared helpers for repo connection during install and `synkro link`.\n *\n * Two paths:\n * 1. Local git repo — detect from `git remote get-url origin`, any provider\n * 2. GitHub OAuth — browser flow, list repos, interactive picker\n */\nimport { execSync, execFileSync } from 'node:child_process';\nimport { readdirSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { createProject, listProjects } from '../api/projects.js';\nimport { listAccessibleRepos } from '../installer/githubSetup.js';\n\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : 'https://app.synkro.sh';\nconst GITHUB_PORT = 8101;\n\nfunction detectGitRepo(): { fullName: string; shortName: string } | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n // Match any git host — github, gitlab, bitbucket, self-hosted, etc.\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (!match) return null;\n const fullName = match[1];\n return { fullName, shortName: fullName.split('/').pop() || fullName };\n } catch { return null; }\n}\n\nfunction detectSubdirRepos(): Array<{ fullName: string; shortName: string }> {\n try {\n const entries = readdirSync('.', { withFileTypes: true });\n const repos: Array<{ fullName: string; shortName: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n try {\n const remoteUrl = execFileSync('git', ['-C', entry.name, 'remote', 'get-url', 'origin'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (match) {\n const fullName = match[1];\n repos.push({ fullName, shortName: fullName.split('/').pop() || fullName });\n }\n } catch { /* not a git repo or no remote */ }\n }\n return repos;\n } catch { return []; }\n}\n\nfunction ask(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nfunction waitForGithubToken(): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n });\n res.end();\n return;\n }\n\n if (req.url !== '/auth' || req.method !== 'POST') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const parsed = JSON.parse(body);\n if (!parsed.github_token) {\n res.writeHead(400, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ error: 'missing github_token' }));\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ ok: true }));\n setTimeout(() => server.close(), 200);\n resolve(parsed.github_token);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'invalid json' }));\n }\n });\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${GITHUB_PORT} is in use. Close other processes and try again.`));\n } else {\n reject(err);\n }\n });\n\n server.listen(GITHUB_PORT);\n });\n}\n\nfunction openBrowser(url: string): void {\n const { execFile } = require('node:child_process');\n const plat = process.platform;\n const cb = (err: Error | null) => {\n if (err) console.log(` Open this URL manually: ${url}`);\n };\n if (plat === 'darwin') execFile('open', [url], cb);\n else if (plat === 'win32') execFile('cmd', ['/c', 'start', '', url], cb);\n else execFile('xdg-open', [url], cb);\n}\n\nasync function connectGithubAndSelectRepos(): Promise<Array<{ full_name: string }>> {\n const url = `${SYNKRO_WEB_AUTH_URL}/cli-github?port=${GITHUB_PORT}`;\n console.log(' Opening browser for GitHub authorization...');\n openBrowser(url);\n\n console.log(' Waiting for GitHub authorization...');\n const ghToken = await waitForGithubToken();\n console.log(' ✓ GitHub connected\\n');\n\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.log(' No accessible repos found on GitHub.');\n return [];\n }\n\n console.log(` Found ${repos.length} repos:\\n`);\n repos.forEach((r: any, i: number) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const selection = await ask(rl, ' Select repos (comma-separated numbers, e.g. 1,3,5): ');\n const indices = selection\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n\n if (indices.length === 0) {\n console.log(' No repos selected.');\n return [];\n }\n\n return indices.map((i) => ({ full_name: repos[i].full_name }));\n } finally {\n rl.close();\n }\n}\n\nexport async function promptRepoConnection(opts?: { linkRepo?: boolean }): Promise<void> {\n const localRepo = detectGitRepo();\n const subdirRepos = !localRepo ? detectSubdirRepos() : [];\n\n if (opts?.linkRepo && localRepo) {\n console.log('Connect repos to Synkro:\\n');\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n console.log();\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n try {\n console.log('Connect repos to Synkro:\\n');\n\n let existingFullNames = new Set<string>();\n if (subdirRepos.length > 0) {\n try {\n const existing = await listProjects();\n existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n } catch {}\n }\n\n if (subdirRepos.length > 0) {\n console.log(` Found ${subdirRepos.length} repo(s) in this directory:\\n`);\n subdirRepos.forEach((r, i) => {\n const linked = existingFullNames.has(r.fullName);\n console.log(` ${String(i + 1).padStart(3)}. ${r.fullName}${linked ? ' ✓ linked' : ''}`);\n });\n const ghIdx = subdirRepos.length + 1;\n const skipIdx = subdirRepos.length + 2;\n console.log(` ${String(ghIdx).padStart(3)}. Connect GitHub to select repos`);\n console.log(` ${String(skipIdx).padStart(3)}. Skip for now`);\n console.log();\n\n const selection = await ask(rl, ' Select repos to link (comma-separated numbers, e.g. 1,3): ');\n const nums = selection.split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));\n console.log();\n rl.close();\n\n if (nums.includes(ghIdx)) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n const newRepos = selectedRepos.filter((r) => !existingFullNames.has(r.full_name));\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n }\n } else if (nums.includes(skipIdx) || nums.length === 0) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n const repoIndices = nums.map((n) => n - 1).filter((n) => n >= 0 && n < subdirRepos.length);\n const toLink = repoIndices.map((i) => subdirRepos[i]).filter((r) => !existingFullNames.has(r.fullName));\n if (toLink.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n for (const repo of toLink) {\n try {\n await createProject(repo.shortName, [{ full_name: repo.fullName }]);\n console.log(` ✓ Created project \"${repo.shortName}\" linked to ${repo.fullName}`);\n } catch (err) {\n console.warn(` ⚠ Could not link ${repo.fullName}: ${(err as Error).message}`);\n }\n }\n }\n }\n } else {\n const options: string[] = [];\n if (localRepo) {\n options.push(`Link this repo (${localRepo.fullName})`);\n }\n options.push('Connect GitHub to select repos');\n options.push('Skip for now');\n\n options.forEach((opt, i) => {\n console.log(` ${i + 1}. ${opt}`);\n });\n console.log();\n\n const choice = await ask(rl, ' Choose (number): ');\n const choiceNum = parseInt(choice.trim(), 10);\n console.log();\n rl.close();\n\n const localIdx = localRepo ? 1 : -1;\n const githubIdx = localRepo ? 2 : 1;\n const skipIdx = localRepo ? 3 : 2;\n\n if (choiceNum === localIdx && localRepo) {\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n } else if (choiceNum === githubIdx) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n try {\n const existing = await listProjects();\n const linkedNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !linkedNames.has(r.full_name));\n\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repos: ${(err as Error).message}`);\n }\n }\n } else if (choiceNum === skipIdx) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n console.log(' Invalid choice. Skipping repo connection.');\n }\n }\n } catch {\n rl.close();\n }\n console.log();\n}\n","/**\n * Fetch judge prompt metadata from the Synkro API.\n * Prompts are fetched live on every grade call — never cached to disk.\n * This module only fetches the version string for display during install.\n */\n\ninterface PromptsMetaResponse {\n version: string;\n}\n\nexport async function fetchJudgePrompts(opts: {\n gatewayUrl: string;\n jwt: string;\n forceRefresh?: boolean;\n}): Promise<{\n version: string;\n}> {\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/hook/config`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${opts.jwt}`,\n 'User-Agent': 'synkro-cli/1.0',\n },\n signal: AbortSignal.timeout(5000),\n });\n\n if (!resp.ok) {\n return { version: 'unknown' };\n }\n\n const data = await resp.json() as { prompts?: { version?: string } };\n return { version: data.prompts?.version ?? 'unknown' };\n}\n","/**\n * Claude Desktop observability (local) — session-scoped MITM tap.\n *\n * Wraps Anthropic's Claude Desktop in a local mitmproxy that taps its\n * `chat_conversations` traffic and POSTs each turn to the local container's\n * `/api/local/capture`, which stores it in PGLite so the conversations show up\n * in the dashboard Events page. Store-only — no blocking/grading (deferred).\n *\n * This is the LOCAL counterpart of the cloud tap embedded in\n * packages/api/src/routes/proxyTokens.ts. It captures the full per-turn\n * trajectory — user + assistant text, tool calls, tool results (incl. web\n * search), and thinking. (It omits only the cloud version's account-identity and\n * file-upload metadata, which local single-user observability doesn't need.)\n * KEEP THE PARSING IN SYNC with proxyTokens.ts when Claude Desktop's internal\n * API changes — that's the single biggest drift risk.\n *\n * macOS only (keychain trust + Claude.app + osascript). Invoked from\n * `synkro install` behind an explicit consent prompt.\n *\n * Security: capture URL + auth token are passed via ENV VARS (never interpolated\n * into the addon source) so config can't become executable Python (CWE-94). The\n * addon is fully STATELESS — each capture is built from one conversation GET\n * response, with no globals/class attributes shared across flows (CWE-362,\n * CWE-200). For local single-user use the captured turn carries no extracted\n * account identity; the server attributes it to the local identity.\n */\nimport { spawn } from 'node:child_process';\nimport { writeFileSync, mkdtempSync, mkdirSync, readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { installClaudeDesktopMcpConfig } from './mcpConfig.js';\n\n// The container's local REST port (matches LOCAL_API_BASE in the dashboard's\n// local-fetch.ts). The capture route lives behind the SERVER_TOKEN Bearer gate.\nconst CAPTURE_URL = 'http://127.0.0.1:18931/api/local/capture';\nconst SCAN_URL = 'http://127.0.0.1:18931/api/local/scan-upload';\nconst SCAN_TURN_URL = 'http://127.0.0.1:18931/api/local/scan-turn';\nconst DLP_POLICY_URL = 'http://127.0.0.1:18931/api/local/dlp-policy';\nconst TURN_VERDICTS_URL = 'http://127.0.0.1:18931/api/local/turn-verdicts';\nconst TURN_VERDICT_URL = 'http://127.0.0.1:18931/api/local/turn-verdict';\nconst MCP_EVENT_URL = 'http://127.0.0.1:18931/api/local/mcp/event';\nconst JWT_PATH = join(homedir(), '.synkro', '.mcp-jwt');\n\n/** Is Claude Desktop installed? (cheap check for the common install path) */\nexport function claudeDesktopInstalled(): boolean {\n return process.platform === 'darwin'\n && existsSync('/Applications/Claude.app/Contents/MacOS/Claude');\n}\n\n// The mitmproxy addon — STATIC, STATELESS Python source. Config (capture URL +\n// token) is read from the environment at runtime, NOT interpolated in (CWE-94).\n// No globals or class attributes persist across flows: each capture is built\n// entirely from one conversation GET response (CWE-362, CWE-200).\nconst ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib\nfrom mitmproxy import http\n\ndef _safe_url(u):\n # CWE-918: these URLs come from the environment the installer sets — accept ONLY\n # the loopback container endpoints, never an arbitrary host from a tampered env.\n return u if (u.startswith(\"http://127.0.0.1:\") or u.startswith(\"http://localhost:\")) else \"\"\n\nCAPTURE_URL = _safe_url(os.environ.get(\"SYNKRO_CAPTURE_URL\", \"\"))\nTOKEN = os.environ.get(\"SYNKRO_TAP_TOKEN\", \"\")\nSCAN_URL = _safe_url(os.environ.get(\"SYNKRO_SCAN_URL\", \"\"))\nSCAN_TURN_URL = _safe_url(os.environ.get(\"SYNKRO_SCAN_TURN_URL\", \"\"))\nPOLICY_URL = _safe_url(os.environ.get(\"SYNKRO_DLP_POLICY_URL\", \"\"))\nTURN_VERDICTS_URL = _safe_url(os.environ.get(\"SYNKRO_TURN_VERDICTS_URL\", \"\"))\nTURN_VERDICT_URL = _safe_url(os.environ.get(\"SYNKRO_TURN_VERDICT_URL\", \"\"))\nMCP_EVENT_URL = _safe_url(os.environ.get(\"SYNKRO_MCP_EVENT_URL\", \"\"))\n# History backfill is OPT-IN: only when the user runs synkro claude-desktop with\n# --backfill. Otherwise importing past conversations on every tap launch is wasted\n# work (and re-floods the feed). When set, it also bypasses the 12h throttle marker.\nBACKFILL_ENABLED = os.environ.get(\"SYNKRO_CD_BACKFILL\", \"\") == \"1\"\n# Token file path — the SERVER_TOKEN rotates whenever the container is recreated,\n# so we re-read it from disk on a 401 instead of needing a tap restart.\nTOKEN_FILE = os.environ.get(\"SYNKRO_TAP_TOKEN_FILE\", \"\")\n_token_lock = threading.Lock()\n\ndef _reload_token():\n global TOKEN\n if not TOKEN_FILE: return\n try:\n with open(TOKEN_FILE) as f:\n t = f.read().strip()\n if t:\n with _token_lock: TOKEN = t\n print(\"[synkro] auth token reloaded after 401 (container likely recreated)\")\n except Exception:\n pass\n\ndef _post_json(url, payload_bytes, timeout):\n # POST JSON with auto-reauth: on a 401 (token rotated by a container recreate),\n # re-read the token file and retry ONCE. CWE-362: TOKEN is always snapshotted\n # under the lock before use, never read bare.\n for attempt in range(2):\n with _token_lock: tok = TOKEN\n try:\n req = urllib.request.Request(url, data=payload_bytes, headers={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer \" + tok})\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n except urllib.error.HTTPError as e:\n if e.code == 401 and attempt == 0:\n _reload_token(); continue\n raise\n return None\n\n# Governance signals parsed from the completion SSE (usage window + web-search\n# sources), keyed by conversation id. The completion POST always precedes the\n# tree GET for the same turn, so the tree capture below reads + clears this.\n# Guarded by a lock — tap callbacks can run concurrently across flows (CWE-362).\n_sse_meta = {}\n_sse_lock = threading.Lock()\n# Session-global MCP server registry (uuid -> {name,url,connected,tools}), parsed\n# from the mcp/v2/bootstrap SSE. All access guarded by _mcp_lock (CWE-362).\n_mcp_registry = {}\n_mcp_lock = threading.Lock()\n\ndef _fmt_tool_name(raw):\n # Claude Desktop names connector tool_use blocks as \"<ServerName><tool>\" with\n # no delimiter (e.g. \"Linearlist_issues\"). Split it back to \"Server:tool\" using\n # the captured MCP registry so the dashboard can resolve the server favicon +\n # render a clean label. Exact server+tool match only — never a fuzzy guess.\n if not raw or \":\" in raw: return raw\n try:\n with _mcp_lock:\n servers = list(_mcp_registry.values())\n for s in servers:\n sname = s.get(\"name\", \"\")\n if not sname or not raw.startswith(sname): continue\n rest = raw[len(sname):]\n for t in s.get(\"tools\", []):\n tn = t.get(\"name\", \"\")\n if tn and rest == tn:\n return sname + \":\" + tn\n except Exception:\n pass\n return raw\n\ndef _map_full_conversation(all_msgs):\n # Full conversation [{role, content, tool_calls?}] + 'tool' result messages, in\n # order, for the session-log tables (the Session Timeline card on the event page).\n # Lighter than the per-turn capture: role + text + tool_calls + tool results.\n out = []\n for msg in all_msgs:\n sender = msg.get(\"sender\", \"\")\n role = \"user\" if sender == \"human\" else \"assistant\"\n blocks = msg.get(\"content\", [])\n texts = []; tcs = []; results = []\n if isinstance(blocks, list):\n for b in blocks:\n if isinstance(b, str): texts.append(b); continue\n if not isinstance(b, dict): continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n texts.append(b.get(\"text\", \"\"))\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n tcs.append({\"name\": _fmt_tool_name(b.get(\"name\", \"\")), \"id\": b.get(\"id\", \"\"), \"arguments\": json.dumps(b.get(\"input\", {}))[:2000]})\n elif bt.endswith(\"_tool_result\") or bt == \"tool_result\":\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n rc = \" | \".join(str(x.get(\"text\") or x.get(\"content\") or \"\") for x in rc if isinstance(x, dict))\n if rc and str(rc).strip():\n results.append({\"role\": \"tool\", \"content\": str(rc)[:8000], \"name\": _fmt_tool_name(b.get(\"name\", \"\"))})\n elif isinstance(blocks, str):\n texts.append(blocks)\n content = \" \".join(t for t in texts if t).strip()\n if content or tcs:\n e = {\"role\": role, \"content\": content}\n if tcs: e[\"tool_calls\"] = tcs\n out.append(e)\n for r in results: out.append(r)\n if len(out) >= 2000: break\n return out\n\ndef _emit_mcp_event(server, tool, ev_type, eid, convo_id=\"\"):\n # Post a Claude Desktop connector (MCP) lifecycle/tool_call event to the same\n # mcp_events store the CC/Cursor mcp-gate writes, so Desktop connectors show on\n # the MCP Servers page. eid is deterministic so re-captures dedupe (server\n # ON CONFLICT DO NOTHING). Fire-and-forget; never blocks the proxied request.\n if not (MCP_EVENT_URL and TOKEN and server): return\n try:\n payload = json.dumps({\n \"event_id\": eid,\n \"session_id\": (str(convo_id)[:80] or None),\n \"server_name\": str(server)[:120],\n \"event_type\": ev_type,\n \"tool_name\": (str(tool)[:120] if tool else None),\n \"decision\": (\"allowed\" if ev_type == \"tool_call\" else None),\n \"repo\": \"claude-desktop\",\n }).encode(\"utf-8\")\n _post_json(MCP_EVENT_URL, payload, 5)\n except Exception:\n pass\n\n# ── History backfill ──────────────────────────────────────────────────────────\n# One-time pull of the user's past claude.ai conversations into the local store, so\n# the dashboard shows full Desktop history (like CC's transcript import) — not just\n# turns tapped live. Auth (session cookie + org uuid) is captured from a live request\n# and kept IN MEMORY (never written to disk). Store-only: backfilled turns are not\n# graded (backfill=True). Runs at most once per 12h via a timestamp marker.\n#\n# SSRF (CWE-918): the only host we ever fetch is claude.ai, and org/conversation ids\n# must be UUID-shaped — both validated before any URL is built, so no untrusted value\n# can redirect the fetch to an arbitrary host/path.\n_backfill_lock = threading.Lock()\n_backfill_started = False\n_BACKFILL_MARKER = os.path.expanduser(\"~/.synkro/.cd-backfill-done\")\n\ndef _is_claude_host(h):\n return h == \"claude.ai\" or (isinstance(h, str) and h.endswith(\".claude.ai\"))\n\ndef _is_uuidish(s):\n return bool(re.match(r\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\", s or \"\"))\n\ndef _http_get(url, headers, timeout=25):\n req = urllib.request.Request(url, headers=headers, method=\"GET\")\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n\ndef _post_backfill_convo(convo, cid):\n all_msgs = convo.get(\"chat_messages\", [])\n if not all_msgs: return\n model = convo.get(\"model\") or \"claude-unknown\"\n if not model.startswith(\"claude-\"): model = \"claude-\" + model\n session_messages = _map_full_conversation(all_msgs)\n if not session_messages: return\n msgs = []\n for e in session_messages:\n mm = {\"role\": e.get(\"role\", \"user\"), \"content\": e.get(\"content\", \"\")}\n if e.get(\"tool_calls\"): mm[\"tool_calls\"] = e[\"tool_calls\"]\n msgs.append(mm)\n # Real conversation timestamp (last activity, fallback created) so the server\n # stamps created_at to when the convo HAPPENED — not \"now\". Without this every\n # backfill run re-stamps all history to the present and floods it to the top of\n # the feed. Must be ISO8601; the server re-validates and only honors it for\n # backfill rows.\n cts = \"\"\n for k in (\"updated_at\", \"created_at\"):\n v = convo.get(k) if isinstance(convo, dict) else None\n if isinstance(v, str) and re.match(r\"^\\d{4}-\\d{2}-\\d{2}T\", v):\n cts = v; break\n data = {\"request\": {\"model\": model, \"messages\": msgs[-200:]}, \"conversation_id\": cid,\n \"session_messages\": session_messages, \"turn_id\": \"cdbf_\" + cid, \"backfill\": True}\n if cts: data[\"event_ts\"] = cts\n _post_json(CAPTURE_URL, json.dumps(data).encode(\"utf-8\"), 8)\n\ndef _backfill(host, org, cookie):\n if not (_is_claude_host(host) and _is_uuidish(org)): return # defence in depth\n base = \"https://\" + host\n headers = {\"Cookie\": cookie, \"Accept\": \"application/json\", \"User-Agent\": \"synkro-tap\"}\n try:\n convos = json.loads(_http_get(base + \"/api/organizations/\" + org + \"/chat_conversations?limit=200\", headers, 25))\n if not isinstance(convos, list): return\n except Exception:\n with _backfill_lock:\n globals()[\"_backfill_started\"] = False # allow a later request to retry\n return\n print(\"[synkro] backfill: importing up to \" + str(min(len(convos), 200)) + \" conversations\")\n n = 0\n for c in convos[:200]:\n cid = c.get(\"uuid\", \"\") if isinstance(c, dict) else \"\"\n if not _is_uuidish(cid): continue\n try:\n convo = json.loads(_http_get(base + \"/api/organizations/\" + org + \"/chat_conversations/\" + cid + \"?tree=True&rendering_mode=messages&render_all_tools=true\", headers, 25))\n _post_backfill_convo(convo, cid)\n n += 1\n except Exception:\n pass\n time.sleep(0.6)\n try: open(_BACKFILL_MARKER, \"w\").write(str(int(time.time())))\n except Exception: pass\n print(\"[synkro] backfill done: \" + str(n) + \" conversations imported\")\n\ndef _maybe_backfill(host, org, cookie):\n # Opt-in only: skip entirely unless the user launched with --backfill.\n if not BACKFILL_ENABLED: return\n if not (_is_claude_host(host) and _is_uuidish(org) and cookie and CAPTURE_URL and TOKEN): return\n with _backfill_lock:\n global _backfill_started\n if _backfill_started: return\n _backfill_started = True\n threading.Thread(target=_backfill, args=(host, org, cookie), daemon=True).start()\n\n# ── M3 live-hold state ────────────────────────────────────────────────────────\n# Whether to HOLD connector turns (buffer the live completion until graded).\n# Fetched once from the server: True only when a BLOCK-mode rule exists (+ not\n# silent). Audit-only orgs get False → stream + async audit, ZERO added latency.\n_dlp_hold = None\n_dlp_lock = threading.Lock()\n# Shown live in place of a held connector turn while the single grader (the tree\n# GET) runs; replaced by the real content (if clean) or the block notice.\nREVIEWING_NOTICE = \"\\\\U0001F6E1\\\\uFE0F Synkro is reviewing this response for policy compliance\\\\u2026\"\n\ndef _should_hold():\n global _dlp_hold\n with _dlp_lock:\n if _dlp_hold is not None:\n return _dlp_hold\n val = False\n if POLICY_URL:\n try:\n with urllib.request.urlopen(urllib.request.Request(POLICY_URL), timeout=4) as resp:\n val = bool(json.loads(resp.read().decode(\"utf-8\", \"replace\")).get(\"hold\"))\n except Exception:\n val = False\n with _dlp_lock:\n _dlp_hold = val\n return val\n\ndef _scan_turn_call(cid, msgs, connector):\n # Synchronous DLP grade. Returns the block reason (str) on BLOCK, else None.\n # Fail-OPEN — a scan error never blocks the turn.\n if not (SCAN_TURN_URL and TOKEN): return None\n try:\n vb = _post_json(SCAN_TURN_URL, json.dumps({\"conversation_id\": str(cid)[:80], \"messages\": msgs, \"connector\": connector}).encode(), 18)\n vd = json.loads((vb or b\"{}\").decode(\"utf-8\", \"replace\"))\n if vd.get(\"block\"):\n return str(vd.get(\"reason\", \"restricted data\"))[:300]\n except Exception as e:\n print(\"[synkro] turn scan error (fail-open): \" + str(e))\n return None\n\ndef _scan_sse_line(line, st):\n # Extract governance metadata (usage window + web-search sources/citations)\n # from one completion SSE line. Shared by the streaming + buffered paths.\n if st[\"usage\"] is None:\n um = re.search(r'\"5h\":\\\\{\"status\":\"([^\"]*)\",\"resets_at\":(\\\\d+),\"utilization\":([0-9.]+)', line)\n if um:\n st[\"usage\"] = {\"window\": \"5h\", \"status\": um.group(1), \"resets_at\": int(um.group(2)), \"utilization\": float(um.group(3))}\n if len(st[\"sources\"]) < 50:\n for sm in re.finditer(r'\"webpage_metadata\",\"site_domain\":\"([^\"]*)\"(?:,\"favicon_url\":\"[^\"]*\")?,\"site_name\":\"([^\"]*)\"', line):\n d = sm.group(1)\n if d and d not in st[\"seen\"] and len(st[\"sources\"]) < 50:\n st[\"seen\"].add(d)\n st[\"sources\"].append({\"domain\": d, \"name\": sm.group(2)})\n if \"citation_start_delta\" in line and line.startswith(\"data: \") and len(st[\"citations\"]) < 50:\n try:\n cit = json.loads(line[6:]).get(\"delta\", {}).get(\"citation\", {})\n url = cit.get(\"url\", \"\")\n if url and url not in st[\"cseen\"]:\n st[\"cseen\"].add(url)\n md = cit.get(\"metadata\") or {}\n st[\"citations\"].append({\"title\": cit.get(\"title\", \"\"), \"url\": url, \"domain\": md.get(\"site_domain\", \"\")})\n except Exception:\n pass\n\ndef _new_scan_state():\n return {\"carry\": \"\", \"usage\": None, \"sources\": [], \"seen\": set(), \"citations\": [], \"cseen\": set()}\n\ndef _extract_sse_text(body):\n # Pull all visible text out of a buffered completion SSE (assistant text +\n # tool_result content) for grading, and detect whether a tool/connector ran.\n parts = []\n tool = (\"tool_use\" in body) or (\"tool_result\" in body)\n total = 0\n for line in body.split(\"\\\\n\"):\n if total > 2000000: break\n if not line.startswith(\"data: \"): continue\n try: obj = json.loads(line[6:])\n except Exception: continue\n d = obj.get(\"delta\")\n if isinstance(d, dict) and isinstance(d.get(\"text\"), str):\n parts.append(d[\"text\"]); total += len(d[\"text\"])\n cb = obj.get(\"content_block\")\n if isinstance(cb, dict):\n if isinstance(cb.get(\"text\"), str): parts.append(cb[\"text\"]); total += len(cb[\"text\"])\n c = cb.get(\"content\")\n if isinstance(c, str): parts.append(c); total += len(c)\n elif isinstance(c, list):\n for x in c:\n if isinstance(x, dict) and isinstance(x.get(\"text\"), str): parts.append(x[\"text\"]); total += len(x[\"text\"])\n return (\"\".join(parts)[:2000000], tool)\n\ndef _neutralize_body(body, notice):\n # Buffered BLOCK render of a COMPLETE response: replace the answer with the notice\n # (first text delta, rest blanked), blank text/content content_blocks (incl. the\n # retrieved doc), leave tool_use + thinking + every structural event intact (the\n # tree GET scrubs thinking durably). Rewriting a WHOLE buffered body — not the\n # live stream — is what avoids the \"couldn't finish\" fragility.\n out = []\n injected = [False]\n for line in body.split(\"\\\\n\"):\n if not line.startswith(\"data: \"):\n out.append(line); continue\n try: obj = json.loads(line[6:])\n except Exception:\n out.append(line); continue\n changed = False\n d = obj.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n d[\"text\"] = (notice if not injected[0] else \"\")\n injected[0] = True; changed = True\n cb = obj.get(\"content_block\")\n if isinstance(cb, dict):\n if isinstance(cb.get(\"text\"), str) and cb[\"text\"]:\n cb[\"text\"] = \"\"; changed = True\n if cb.get(\"content\"):\n cb[\"content\"] = \"\"; changed = True\n out.append((\"data: \" + json.dumps(obj)) if changed else line)\n return \"\\\\n\".join(out)\n\n# Per-conversation DLP verdict cached by the held completion so the tree GET reuses\n# it (durable scrub) instead of re-grading. Bounded (CWE-400), lock-guarded (CWE-362).\n_turn_verdicts = {}\n_tv_lock = threading.Lock()\n\ndef _cache_turn_verdict(cid, verdict):\n if not cid: return\n with _tv_lock:\n if len(_turn_verdicts) > 200: _turn_verdicts.clear()\n _turn_verdicts[cid] = verdict\n\ndef _pop_turn_verdict(cid):\n with _tv_lock:\n return _turn_verdicts.pop(cid, None)\n\n# ── Per-turn durable verdict helpers (PGLite-backed via local endpoints) ──────\ndef _msg_blocks(msg):\n b = msg.get(\"content\", [])\n return b if isinstance(b, list) else []\n\ndef _msg_key(msg):\n # Stable per-turn id. Prefer the message uuid; fall back to a content hash so the\n # verdict still keys consistently across reopens even if uuid is ever absent.\n u = msg.get(\"uuid\") or msg.get(\"id\")\n if isinstance(u, str) and u:\n return u\n try:\n raw = json.dumps(msg.get(\"content\", \"\"), sort_keys=True).encode(\"utf-8\", \"replace\")\n return \"h:\" + hashlib.sha256(raw).hexdigest()[:40]\n except Exception:\n return \"\"\n\ndef _msg_is_connector(msg):\n # A turn that pulled content via a connector → has a tool_result block (the doc).\n for b in _msg_blocks(msg):\n if isinstance(b, dict) and (b.get(\"type\", \"\") == \"tool_result\" or b.get(\"type\", \"\").endswith(\"_tool_result\")):\n return True\n return False\n\ndef _msg_grade_payload(msg):\n # Extract (assistant_text, tool_result_text, connector) from a message's blocks.\n texts = []; tools = []; connector = \"\"\n for b in _msg_blocks(msg):\n if isinstance(b, str):\n texts.append(b); continue\n if not isinstance(b, dict):\n continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n texts.append(b.get(\"text\", \"\") or \"\")\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n nm = b.get(\"name\", \"\") or \"\"\n if nm and not connector:\n connector = nm.split(\":\")[0] if \":\" in nm else nm\n elif bt == \"tool_result\" or bt.endswith(\"_tool_result\"):\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n for x in rc:\n if isinstance(x, dict):\n if isinstance(x.get(\"text\"), str): tools.append(x[\"text\"])\n elif x.get(\"content\"): tools.append(str(x.get(\"content\")))\n elif isinstance(rc, str):\n tools.append(rc)\n ic = b.get(\"integration_name\") or b.get(\"name\") or \"\"\n if ic and not connector:\n connector = ic\n return (\"\\\\n\".join(texts), \"\\\\n\".join(tools), connector)\n\ndef _get_turn_verdicts(uuids):\n # Batch lookup of durable verdicts from PGLite (one round-trip). {uuid: {blocked,reason}}.\n if not (TURN_VERDICTS_URL and TOKEN and uuids):\n return {}\n try:\n resp = _post_json(TURN_VERDICTS_URL, json.dumps({\"uuids\": uuids[:500]}).encode(\"utf-8\"), 8)\n if resp:\n o = json.loads(resp.decode(\"utf-8\", \"replace\"))\n v = o.get(\"verdicts\")\n return v if isinstance(v, dict) else {}\n except Exception as e:\n print(\"[synkro] verdict lookup error: \" + str(e))\n return {}\n\ndef _put_turn_verdict(uuid, convo_id, blocked, reason):\n # Persist one graded turn (fire-and-forget, off the tree-GET critical path).\n if not (TURN_VERDICT_URL and TOKEN and uuid):\n return\n def _send():\n try:\n _post_json(TURN_VERDICT_URL, json.dumps({\"message_uuid\": uuid, \"convo_id\": convo_id, \"blocked\": bool(blocked), \"reason\": reason or \"\"}).encode(\"utf-8\"), 8)\n except Exception as e:\n print(\"[synkro] verdict persist error: \" + str(e))\n threading.Thread(target=_send, daemon=True).start()\n\ndef _intent_before(all_msgs, idx):\n # Most recent human message text before idx — the turn's user intent for grading.\n for j in range(idx - 1, -1, -1):\n if all_msgs[j].get(\"sender\") == \"human\":\n for b in _msg_blocks(all_msgs[j]):\n if isinstance(b, dict) and b.get(\"type\") == \"text\": return (b.get(\"text\") or \"\")[:2000]\n if isinstance(b, str): return b[:2000]\n return \"\"\n return \"\"\n\ndef _apply_turn_verdicts(all_msgs, convo_id):\n # WHOLE-CONVERSATION per-turn DLP scrub. Mutates all_msgs in place: replaces every\n # BLOCKED assistant turn's content with the notice. Returns True if anything was\n # blocked. Verdicts are durable (PGLite via the local endpoints); each connector\n # turn (a tool_result is present) is graded once on a miss then persisted; pure-chat\n # turns are skipped. Scrubs EVERY blocked turn — not just the latest — so a follow-up\n # can never resurrect an earlier blocked turn. Fail-OPEN.\n asst_idx = [i for i in range(len(all_msgs)) if all_msgs[i].get(\"sender\") != \"human\"]\n keys = {i: _msg_key(all_msgs[i]) for i in asst_idx}\n ids = [k for k in keys.values() if k]\n verdicts = _get_turn_verdicts(ids) if ids else {}\n do_grade = _should_hold() and SCAN_TURN_URL and TOKEN\n any_blocked = False\n for i in asst_idx:\n key = keys.get(i, \"\")\n v = verdicts.get(key) if key else None\n if v is None and do_grade and key and _msg_is_connector(all_msgs[i]):\n text, tools, connector = _msg_grade_payload(all_msgs[i])\n if tools.strip() or text.strip():\n scan_msgs = [\n {\"role\": \"user\", \"content\": _intent_before(all_msgs, i), \"name\": \"\"},\n {\"role\": \"tool\", \"content\": tools[:500000], \"name\": connector[:200]},\n {\"role\": \"assistant\", \"content\": text[:8000], \"name\": \"\"},\n ]\n reason = _scan_turn_call(convo_id, scan_msgs, connector)\n v = {\"blocked\": reason is not None, \"reason\": reason or \"\"}\n _put_turn_verdict(key, convo_id, v[\"blocked\"], v[\"reason\"])\n if v and v.get(\"blocked\"):\n all_msgs[i][\"content\"] = [{\"type\": \"text\", \"text\": \"[Blocked by Synkro] \" + (v.get(\"reason\") or \"restricted data\")}]\n any_blocked = True\n return any_blocked\n\ndef _feed_answer(chunk, st):\n # Accumulate the assistant's ANSWER text (text_delta) as the held stream flows,\n # using a carry for SSE lines split across chunks. The answer reveals the doc's\n # classification, so once enough has arrived we grade it for an early verdict.\n txt = st[\"carry\"] + chunk\n lines = txt.split(\"\\\\n\")\n st[\"carry\"] = lines[-1][-100000:] # bound carried partial line (CWE-400)\n for ln in lines[:-1]:\n if not ln.startswith(\"data: \"):\n continue\n try:\n obj = json.loads(ln[6:])\n except Exception:\n continue\n d = obj.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n if len(st[\"answer\"]) < 200000: # bound (CWE-400)\n st[\"answer\"] += d[\"text\"]\n\ndef _grade_worker(st, ans):\n # Runs in a BACKGROUND THREAD (off mitmproxy's event loop). The urllib grade\n # releases the GIL during I/O, so the loop keeps flushing keep-alive pings.\n # CWE-362: ans is a snapshot PASSED BY the loop thread (the sole writer of\n # st[\"answer\"]) at thread-creation — the worker never reads shared mutable\n # st[\"answer\"], so there is no data race. It writes only verdict/graded, which the\n # loop reads AFTER graded.is_set() provides the happens-before barrier.\n try:\n ans = ans[:200000]\n reason = _scan_turn_call(st[\"cid\"], [{\"role\": \"assistant\", \"content\": ans, \"name\": \"\"}], \"\") if ans.strip() else None\n except Exception:\n reason = None\n st[\"verdict\"] = {\"block\": reason is not None, \"reason\": reason or \"\"}\n _cache_turn_verdict(st[\"cid\"], st[\"verdict\"])\n st[\"graded\"].set()\n\ndef _classify_frame(fr):\n # Inspect one CRLF SSE frame's data line → 'tool_use_start' | 'text_start' | 'other'\n # based on the content_block_start type. Used to find where the FINAL answer begins.\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n o = json.loads(ln[6:])\n except Exception:\n return \"other\"\n if o.get(\"type\") == \"content_block_start\":\n cb = o.get(\"content_block\")\n if isinstance(cb, dict):\n t = cb.get(\"type\")\n if t == \"tool_use\":\n return \"tool_use_start\"\n if t == \"text\":\n return \"text_start\"\n return \"other\"\n return \"other\"\n\ndef _frame_answer_text(fr, st):\n # Accumulate text_delta text from a HELD frame into st[\"answer\"] (for the grade).\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n d = json.loads(ln[6:]).get(\"delta\")\n except Exception:\n continue\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n if len(st[\"answer\"]) < 200000: # CWE-400 bound\n st[\"answer\"] += d[\"text\"]\n\ndef _block_held(frames, notice):\n # Frame-aware, CRLF-PRESERVING block render of the held final-answer frames: replace\n # the answer's text_delta(s) (first→notice, rest→\"\") and leave every structural frame\n # (content_block_stop / message_delta / message_stop) intact so the message still\n # finalizes cleanly. Operates per-frame on \\\\r\\\\n lines → no \\\\n/\\\\r\\\\n mixing, so the\n # framing stays byte-clean (the thing _neutralize_body's \\\\n split would have broken).\n inj = [False]\n out = []\n for fr in frames:\n nl = []\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n o = json.loads(ln[6:])\n d = o.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n o[\"delta\"][\"text\"] = notice if not inj[0] else \"\"\n inj[0] = True\n ln = \"data: \" + json.dumps(o)\n except Exception:\n pass\n nl.append(ln)\n out.append(\"\\\\r\\\\n\".join(nl))\n return (\"\\\\r\\\\n\\\\r\\\\n\".join(out) + \"\\\\r\\\\n\\\\r\\\\n\") if out else \"\"\n\ndef _make_hold_stream(cid):\n # Pass-through-until-FINAL-ANSWER hold for the claude.ai completion (CRLF SSE; frames\n # split by \\\\r\\\\n\\\\r\\\\n). A connector turn is: thinking → text(preamble) → tool_use →\n # tool_result → … → text(ANSWER). Only the FINAL answer is in the user's face, so we\n # STREAM every earlier frame VERBATIM (natural thinking/search activity + tool chips,\n # zero mutation) and HOLD only the final answer — the text block that opens AFTER a\n # tool_use — through message_stop. Buffer it (no leak), grade off-thread, heartbeat to\n # keep Desktop alive, then emit the real answer (clean) or a frame-aware block rewrite\n # (restricted). CWE-362: the grade thread gets an immutable answer snapshot; the\n # verdict returns via the Event barrier; the loop is the sole writer of held/answer.\n st = {\"cid\": cid, \"carry\": \"\", \"holding\": False, \"seen_tool\": False,\n \"held\": [], \"held_size\": 0, \"answer\": \"\", \"started\": False, \"meta\": _new_scan_state(),\n \"graded\": threading.Event(), \"verdict\": {\"block\": False, \"reason\": \"\"}}\n def _hold(data, _st=st):\n if data:\n txt = _st[\"carry\"] + data.decode(\"utf-8\", \"replace\")\n frames = txt.split(\"\\\\r\\\\n\\\\r\\\\n\")\n _st[\"carry\"] = frames[-1][-200000:] # CWE-400 bounded carry (last partial frame)\n out = []\n for fr in frames[:-1]:\n for ln in fr.split(\"\\\\r\\\\n\"):\n _scan_sse_line(ln, _st[\"meta\"]) # governance metadata, all frames\n if not _st[\"holding\"]:\n k = _classify_frame(fr)\n if k == \"tool_use_start\":\n _st[\"seen_tool\"] = True\n if k == \"text_start\" and _st[\"seen_tool\"]:\n _st[\"holding\"] = True # the FINAL answer begins here → hold from now\n else:\n out.append(fr) # thinking / preamble / tool_use / tool_result → verbatim\n continue\n if _st[\"held_size\"] < 8000000: # CWE-400 bound before append\n _st[\"held\"].append(fr); _st[\"held_size\"] += len(fr)\n _frame_answer_text(fr, _st)\n if _st[\"holding\"] and not _st[\"started\"] and len(_st[\"answer\"]) >= 1200:\n _st[\"started\"] = True\n threading.Thread(target=_grade_worker, args=(_st, _st[\"answer\"][:200000]), daemon=True).start()\n if out:\n return (\"\\\\r\\\\n\\\\r\\\\n\".join(out) + \"\\\\r\\\\n\\\\r\\\\n\").encode(\"utf-8\") # stream activity verbatim\n return b\": synkro\\\\n\\\\n\" # answer held → heartbeat (keeps Desktop alive, PROVEN)\n # upstream ended\n m = _st[\"meta\"]\n if (m[\"usage\"] or m[\"sources\"] or m[\"citations\"]) and _st[\"cid\"]:\n with _sse_lock:\n _sse_meta[_st[\"cid\"]] = {\"usage\": m[\"usage\"], \"sources\": m[\"sources\"], \"citations\": m[\"citations\"]}\n if not _st[\"holding\"]: # no final-answer block detected — everything already streamed\n return _st[\"carry\"].encode(\"utf-8\") if _st[\"carry\"] else b\"\"\n if _st[\"carry\"]:\n _st[\"held\"].append(_st[\"carry\"]); _frame_answer_text(_st[\"carry\"], _st); _st[\"carry\"] = \"\"\n if not _st[\"started\"] and _st[\"answer\"].strip(): # short answer never hit the 1200 trigger\n _st[\"started\"] = True\n threading.Thread(target=_grade_worker, args=(_st, _st[\"answer\"][:200000]), daemon=True).start()\n held_frames = list(_st[\"held\"])\n def _grade_window(_st2=_st, _frames=held_frames):\n waited = 0.0\n while not _st2[\"graded\"].is_set() and waited < 60.0:\n yield b\": synkro\\\\n\\\\n\" # heartbeat WHILE grading — keeps Desktop alive (PROVEN)\n time.sleep(0.5)\n waited += 0.5\n v = _st2[\"verdict\"]\n if _st2[\"graded\"].is_set() and v[\"block\"]:\n print(\"[synkro] HELD+BLOCKED live (reason=\" + v[\"reason\"][:60] + \")\")\n yield _block_held(_frames, \"[Blocked by Synkro] \" + v[\"reason\"]).encode(\"utf-8\")\n elif _st2[\"graded\"].is_set():\n yield (\"\\\\r\\\\n\\\\r\\\\n\".join(_frames) + \"\\\\r\\\\n\\\\r\\\\n\").encode(\"utf-8\") # clean → real answer\n else: # grade never landed in 60s — never leak; tree GET backstop finalizes\n print(\"[synkro] hold: grade timeout → placeholder + tree-GET backstop\")\n yield _block_held(_frames, \"[Synkro is reviewing this response…]\").encode(\"utf-8\")\n return _grade_window()\n return _hold\n\nclass SynkroTap:\n def request(self, flow):\n # ── Upload DLP gate ── Scan file uploads BEFORE they reach claude.ai and\n # refuse a blocked one (the file never enters the model). SYNCHRONOUS: this\n # upload waits for the verdict. Fail-OPEN — a scan error never breaks an\n # upload. CWE-200: never print the filename/content (size/rule only).\n r = flow.request\n if not SCAN_URL or not TOKEN: return\n if \"claude.ai\" not in (r.pretty_host or \"\"): return\n if r.method != \"POST\": return\n ct = r.headers.get(\"content-type\", \"\")\n path = r.path or \"\"\n is_upload = (\"multipart/form-data\" in ct) or bool(re.search(r\"/(files|upload|convert)(/|$|\\\\?)\", path))\n if not is_upload: return\n # CWE-400: bound everything. Oversized uploads scan on name/type only — we\n # never pull a multi-MB body into memory or forward it to the scanner.\n MAX_RAW = 8 * 1024 * 1024\n try: clen = int(r.headers.get(\"content-length\", \"0\") or \"0\")\n except Exception: clen = 0\n if clen and clen > MAX_RAW:\n payload = {\"file_name\": \"\", \"size\": clen, \"content_b64\": \"\"}\n else:\n try: body = r.get_content() or b\"\"\n except Exception: return\n if not body or len(body) > MAX_RAW: return\n fname = \"\"\n fm = re.search(rb'filename=\"([^\"]*)\"', body)\n if fm:\n try: fname = fm.group(1).decode(\"utf-8\", \"replace\")[:300]\n except Exception: fname = \"\"\n # Extract the uploaded file's RAW bytes from the multipart body so the\n # grader Reads the real file (a PDF/image must be intact, not the\n # multipart envelope). Fall back to the whole body if no part is found.\n file_bytes = None\n bm = re.search(r\"boundary=([^;]+)\", ct)\n if bm and fm:\n boundary = (\"--\" + bm.group(1).strip().strip('\"')).encode()\n hdr_end = body.find(b\"\\\\r\\\\n\\\\r\\\\n\", fm.start())\n if hdr_end != -1:\n s = hdr_end + 4\n e = body.find(b\"\\\\r\\\\n\" + boundary, s)\n if e != -1: file_bytes = body[s:e]\n if file_bytes is None: file_bytes = body\n try: b64 = base64.b64encode(file_bytes).decode(\"ascii\")\n except Exception: return\n payload = {\"file_name\": fname, \"size\": len(file_bytes), \"content_b64\": b64}\n try:\n respb = _post_json(SCAN_URL, json.dumps(payload).encode(), 55)\n verdict = json.loads((respb or b\"{}\").decode(\"utf-8\", \"replace\"))\n except Exception as e:\n print(\"[synkro] upload scan error (fail-open): \" + str(e))\n return\n if verdict.get(\"block\"):\n reason = str(verdict.get(\"reason\", \"Blocked by Synkro policy\"))[:300]\n msg = \"Blocked by Synkro: \" + reason\n # Return a TERMINAL 400 (invalid_request_error), NOT 403. Claude Desktop's\n # upload client treats 403 as transient/auth and auto-RETRIES it — that's the\n # loop the user sees. A 400 invalid_request_error is non-retryable (the SDK/UI\n # only retry 408/409/429/5xx), so the client stops and surfaces the message.\n # Anthropic's standard error envelope ({\"type\":\"error\",\"error\":{...}}) is what\n # the UI knows how to render, so the user sees the reason instead of a spinner.\n flow.response = http.Response.make(\n 400,\n json.dumps({\"type\": \"error\", \"error\": {\"type\": \"invalid_request_error\", \"message\": msg}}).encode(),\n {\"Content-Type\": \"application/json\"},\n )\n print(\"[synkro] BLOCKED upload (size=\" + str(payload.get(\"size\", 0)) + \" rule=\" + str(verdict.get(\"ruleId\", \"\")) + \")\")\n else:\n print(\"[synkro] upload scanned -> allow (size=\" + str(payload.get(\"size\", 0)) + \")\")\n\n def responseheaders(self, flow):\n # The answer the user watches TYPE OUT comes from claude.ai's completion — THAT\n # is the stream we HOLD (buffer + heartbeat → no leak, no disconnect; see\n # _make_hold_stream). a-api.anthropic.com is a background call that doesn't carry\n # the answer, so it streams through untouched (default below). The tree GET is\n # buffered for the dashboard + the durable block scrub.\n r = flow.request\n host = r.pretty_host or \"\"\n if \"claude.ai\" in host and \"chat_conversations\" in r.path:\n if r.method == \"GET\" and \"tree\" in r.pretty_url:\n return\n if r.method == \"POST\" and \"completion\" in r.path:\n cm = re.search(r\"chat_conversations/([0-9a-fA-F-]+)\", r.path)\n cid = cm.group(1) if cm else \"\"\n with _mcp_lock:\n has_servers = len(_mcp_registry) > 0\n # HOLD when a BLOCK-mode rule exists AND connectors are connected — buffer\n # the answer, grade off-thread, heartbeat through the grade, emit the verdict.\n if has_servers and _should_hold():\n flow.response.stream = _make_hold_stream(cid)\n return\n # Otherwise just TAP for governance metadata (no hold; audit stays async).\n st = _new_scan_state()\n def _tap(data, _st=st, _cid=cid):\n if data:\n try:\n txt = _st[\"carry\"] + data.decode(\"utf-8\", \"replace\")\n except Exception:\n txt = _st[\"carry\"]\n lines = txt.split(\"\\\\n\")\n _st[\"carry\"] = lines[-1][-200000:] # bound the carried partial line (CWE-400)\n for ln in lines[:-1]:\n _scan_sse_line(ln, _st)\n else: # end of stream — flush carry + publish\n _scan_sse_line(_st[\"carry\"], _st)\n if (_st[\"usage\"] or _st[\"sources\"] or _st[\"citations\"]) and _cid:\n with _sse_lock:\n _sse_meta[_cid] = {\"usage\": _st[\"usage\"], \"sources\": _st[\"sources\"], \"citations\": _st[\"citations\"]}\n return data\n flow.response.stream = _tap\n return\n if \"claude.ai\" in (r.pretty_host or \"\") and \"mcp\" in r.path and \"bootstrap\" in r.path:\n return # buffer the MCP registry bootstrap (parsed in response())\n flow.response.stream = True\n\n def response(self, flow):\n r = flow.request\n if \"claude.ai\" not in (r.pretty_host or \"\"): return\n # MCP server registry from the bootstrap SSE stream.\n if \"mcp\" in r.path and \"bootstrap\" in r.path and flow.response:\n try:\n servers = {}\n for line in (flow.response.get_text() or \"\").split(\"\\\\n\"):\n if not line.startswith(\"data: \"): continue\n try: dd = json.loads(line[6:])\n except: continue\n if isinstance(dd.get(\"servers\"), list):\n for s in dd[\"servers\"]:\n sid = s.get(\"uuid\", \"\")\n if sid: servers[sid] = {\"name\": s.get(\"name\", \"\"), \"url\": s.get(\"url\", \"\"), \"connected\": False, \"tools\": []}\n if \"uuid\" in dd and \"connected\" in dd and dd.get(\"uuid\") in servers:\n servers[dd[\"uuid\"]][\"connected\"] = dd.get(\"connected\", False)\n if \"server_uuid\" in dd and isinstance(dd.get(\"tools\"), list) and dd.get(\"server_uuid\") in servers:\n for t in dd[\"tools\"]:\n servers[dd[\"server_uuid\"]][\"tools\"].append({\"name\": t.get(\"name\", \"\"), \"description\": (t.get(\"description\") or \"\")[:200]})\n if servers:\n with _mcp_lock:\n _mcp_registry.clear()\n _mcp_registry.update(servers)\n print(\"[synkro] MCP servers: \" + str(len(servers)))\n # Surface each connector on the MCP Servers page (connect/disconnect).\n for sid, s in servers.items():\n nm = s.get(\"name\", \"\")\n if nm:\n ev = \"connect\" if s.get(\"connected\") else \"disconnect\"\n _emit_mcp_event(nm, \"\", ev, \"cdmcp_\" + ev + \"_\" + str(sid))\n except Exception:\n pass\n return\n if not (\"chat_conversations\" in r.path and r.method == \"GET\" and \"tree\" in r.pretty_url and flow.response): return\n if not CAPTURE_URL or not TOKEN: return\n try: convo = json.loads(flow.response.get_text())\n except: return\n m = re.search(r\"chat_conversations/([0-9a-fA-F-]+)\", r.path)\n convo_id = m.group(1) if m else \"\"\n # One-time history backfill using THIS request's session auth (in-memory only).\n try:\n _om = re.search(r\"/organizations/([0-9a-fA-F-]+)/\", r.path)\n _maybe_backfill(r.pretty_host, _om.group(1) if _om else \"\", r.headers.get(\"cookie\", \"\") or r.headers.get(\"Cookie\", \"\"))\n except Exception:\n pass\n model = convo.get(\"model\", \"unknown\")\n settings = convo.get(\"settings\") or {}\n emt = settings.get(\"enabled_mcp_tools\")\n enabled_mcp = list(emt.keys()) if isinstance(emt, dict) else []\n web_search_on = bool(settings.get(\"enabled_web_search\", False))\n platform = convo.get(\"platform\", \"\")\n all_msgs = convo.get(\"chat_messages\", [])\n if not all_msgs: return\n # Current turn only: last human message and everything after it.\n last_human = -1\n for i in range(len(all_msgs) - 1, -1, -1):\n if all_msgs[i].get(\"sender\") == \"human\": last_human = i; break\n chat = all_msgs[last_human:] if last_human >= 0 else all_msgs[-2:]\n messages = []\n for msg in chat:\n sender = msg.get(\"sender\", \"\")\n role = \"user\" if sender == \"human\" else \"assistant\"\n blocks = msg.get(\"content\", [])\n text_parts = []\n tool_calls = []\n tool_results = []\n thinking = \"\"\n if isinstance(blocks, list):\n for b in blocks:\n if isinstance(b, str): text_parts.append(b); continue\n if not isinstance(b, dict): continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n text_parts.append(b.get(\"text\", \"\"))\n elif bt == \"thinking\":\n thinking += b.get(\"thinking\", \"\") or b.get(\"text\", \"\")\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n _nm = _fmt_tool_name(b.get(\"name\", \"\"))\n tool_calls.append({\"name\": _nm, \"id\": b.get(\"id\", \"\"), \"arguments\": json.dumps(b.get(\"input\", {}))})\n # A \"Server:tool\" name means a hosted MCP connector ran → log it\n # on the MCP Servers page (\":\" only present for registry matches,\n # so built-in tools like web_search are excluded).\n if \":\" in _nm:\n _srv, _, _tl = _nm.partition(\":\")\n _emit_mcp_event(_srv, _tl, \"tool_call\", \"cdmcp_tc_\" + (b.get(\"id\", \"\") or hashlib.sha256((convo_id + _nm).encode()).hexdigest()[:24]), convo_id)\n elif bt.endswith(\"_tool_result\") or bt == \"tool_result\":\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n rparts = []\n for x in rc:\n if not isinstance(x, dict): continue\n if x.get(\"type\") == \"web_search_result\":\n sn = x.get(\"page_content\", \"\") or x.get(\"snippet\", \"\") or x.get(\"content\", \"\") or \"\"\n if isinstance(sn, dict): sn = sn.get(\"text\", \"\")\n rparts.append((x.get(\"title\", \"\") + \" (\" + x.get(\"url\", \"\") + \")\" + ((\" - \" + str(sn)[:200]) if sn else \"\")).strip())\n elif x.get(\"text\"):\n rparts.append(x[\"text\"])\n elif isinstance(x.get(\"content\"), str):\n rparts.append(x[\"content\"])\n rc = \" | \".join(p for p in rparts if p and p.strip())\n dc = b.get(\"display_content\")\n if isinstance(dc, dict):\n rc = rc or dc.get(\"json_block\", \"\") or dc.get(\"text\", \"\")\n # web_search results are server-side (content:[]); we surface the\n # sources via sse_meta instead. Only record a non-empty result.\n if rc and str(rc).strip():\n tool_results.append({\"name\": _fmt_tool_name(b.get(\"name\", \"\")), \"content\": str(rc)})\n elif isinstance(blocks, str):\n text_parts.append(blocks)\n text = \" \".join(p for p in text_parts if p).strip()\n if role == \"assistant\":\n am = {\"role\": \"assistant\", \"content\": text}\n if tool_calls: am[\"tool_calls\"] = tool_calls\n if thinking: am[\"thinking\"] = thinking\n if text or tool_calls or thinking: messages.append(am)\n for tr in tool_results:\n messages.append({\"role\": \"tool\", \"content\": tr[\"content\"], \"name\": tr.get(\"name\", \"\")})\n elif role == \"user\" and text:\n messages.append({\"role\": \"user\", \"content\": text})\n if not messages: return\n data = {\"request\": {\"model\": model, \"messages\": messages}, \"conversation_id\": convo_id}\n # Full conversation for the session-log tables (Session Timeline card).\n data[\"session_messages\"] = _map_full_conversation(all_msgs)\n # PER-TURN key: the current turn's assistant message id (same key used for\n # dlp_turn_verdicts). Lets the server store each turn as its OWN event so a\n # blocked turn isn't overwritten/deleted by a later passing turn.\n turn_id = \"\"\n for _m in reversed(chat):\n if _m.get(\"sender\") != \"human\":\n turn_id = _msg_key(_m); break\n if turn_id: data[\"turn_id\"] = turn_id\n meta = {}\n if platform: meta[\"platform\"] = platform\n if web_search_on: meta[\"web_search_enabled\"] = True\n if enabled_mcp: meta[\"enabled_mcp_tools\"] = enabled_mcp[:20]\n if meta: data[\"conversation_metadata\"] = meta\n with _sse_lock:\n sm = _sse_meta.pop(convo_id, None)\n if sm: data[\"sse_meta\"] = sm\n with _mcp_lock:\n reg = list(_mcp_registry.values())\n if reg: data[\"mcp_servers\"] = reg\n ntools = 0\n for mm in messages:\n ntools += len(mm.get(\"tool_calls\") or [])\n nsrc = len((sm or {}).get(\"sources\") or [])\n print(\"[synkro] captured chat (msgs=\" + str(len(messages)) + \" tools=\" + str(ntools) + \" sources=\" + str(nsrc) + \" model=\" + model + \")\")\n\n # ── Tier-2 connector DLP gate (WHOLE-CONVERSATION, per-turn, PERSISTENT) ──\n # Scrub every blocked assistant turn (durable PGLite verdicts; connector turns\n # graded once on miss). See _apply_turn_verdicts. Fail-OPEN on any error.\n try:\n any_blocked = _apply_turn_verdicts(all_msgs, convo_id)\n except Exception as e:\n any_blocked = False\n print(\"[synkro] turn-verdict scrub error (fail-open): \" + str(e))\n\n # Capture the REAL content for audit regardless of the block decision.\n def send(_d=data):\n try:\n _post_json(CAPTURE_URL, json.dumps(_d).encode(), 5)\n except Exception as e:\n print(\"[synkro] capture error: \" + str(e))\n threading.Thread(target=send, daemon=True).start()\n\n # Re-serialize the tree so the renderer never sees any blocked turn's content.\n if any_blocked:\n try:\n flow.response.text = json.dumps(convo)\n print(\"[synkro] BLOCKED turn view (whole-convo per-turn, durable)\")\n except Exception as e:\n print(\"[synkro] block scrub failed (fail-open): \" + str(e))\n\naddons = [SynkroTap()]\n`;\n\n// ── MCP stdio proxy (local) ───────────────────────────────────────────────\n// Ported from the cloud tap (packages/api/src/routes/proxyTokens.ts): a stdio\n// passthrough that wraps each configured MCP server, taps JSON-RPC tools/call\n// requests + responses, and POSTs {tool, args, result, latency} to the local\n// capture endpoint with model \"mcp:<server>\". Bearer auth (vs the cloud's\n// x-proxy-token). MCP results are client-side, so this catches what the\n// conversation tree may miss. The wrapped command comes from the user's own\n// Claude Desktop config (spawning it is the proxy's purpose — CWE-78 exempt);\n// the pending map is lock-guarded (CWE-362) and the line buffer bounded (CWE-400).\nconst MCP_PROXY_PY = `#!/usr/bin/env python3\n\"\"\"Synkro MCP Proxy (local) — stdio passthrough that taps MCP tool calls.\"\"\"\nimport sys, os, json, subprocess, threading, urllib.request, urllib.error, time\n\nCAPTURE_URL = os.environ.get(\"SYNKRO_CAPTURE_URL\", \"\")\nTOKEN = os.environ.get(\"SYNKRO_TAP_TOKEN\", \"\")\nSERVER_NAME = os.environ.get(\"SYNKRO_MCP_SERVER\", \"unknown\")\n# The SERVER_TOKEN rotates on container recreate; re-read it from disk on a 401.\nTOKEN_FILE = os.environ.get(\"SYNKRO_TAP_TOKEN_FILE\", \"\") or os.path.expanduser(\"~/.synkro/.mcp-jwt\")\n_token_lock = threading.Lock()\n\ndef _reload_token():\n global TOKEN\n try:\n with open(TOKEN_FILE) as f:\n t = f.read().strip()\n if t:\n with _token_lock: TOKEN = t\n except Exception:\n pass\n\ndef _post_json(url, payload_bytes, timeout):\n # CWE-362: TOKEN snapshotted under the lock. Auto-reauth retry once on 401.\n for attempt in range(2):\n with _token_lock: tok = TOKEN\n try:\n req = urllib.request.Request(url, data=payload_bytes, headers={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer \" + tok})\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n except urllib.error.HTTPError as e:\n if e.code == 401 and attempt == 0:\n _reload_token(); continue\n raise\n return None\n\npending = {}\npending_lock = threading.Lock()\n\ndef send_capture(tool_name, tool_args, tool_result, latency_ms):\n if not CAPTURE_URL or not TOKEN: return\n try:\n payload = json.dumps({\n \"request\": {\n \"model\": \"mcp:\" + SERVER_NAME,\n \"messages\": [\n {\"role\": \"assistant\", \"content\": \"\", \"tool_calls\": [{\"name\": tool_name, \"arguments\": json.dumps(tool_args)}]},\n {\"role\": \"tool\", \"content\": json.dumps(tool_result), \"name\": tool_name},\n ]\n },\n \"mcp\": {\"server\": SERVER_NAME, \"tool\": tool_name, \"latency_ms\": latency_ms}\n }).encode()\n _post_json(CAPTURE_URL, payload, 5)\n except Exception as e:\n print(\"[synkro-mcp] capture error: \" + str(e), file=sys.stderr)\n\ndef tap_message(data, direction):\n if not isinstance(data, dict): return\n method = data.get(\"method\")\n msg_id = data.get(\"id\")\n if direction == \"request\" and method == \"tools/call\":\n params = data.get(\"params\", {})\n tool_name = params.get(\"name\", \"unknown\")\n tool_args = params.get(\"arguments\", {})\n if msg_id is not None:\n with pending_lock:\n if len(pending) < 1000:\n pending[msg_id] = {\"name\": tool_name, \"args\": tool_args, \"start\": time.time()}\n print(\"[synkro-mcp] \" + SERVER_NAME + \".\" + str(tool_name), file=sys.stderr)\n if direction == \"response\" and msg_id is not None:\n with pending_lock:\n call = pending.pop(msg_id, None)\n if call:\n latency_ms = int((time.time() - call[\"start\"]) * 1000)\n result = data.get(\"result\", data.get(\"error\", {}))\n threading.Thread(target=send_capture, args=(call[\"name\"], call[\"args\"], result, latency_ms), daemon=True).start()\n\ndef read_jsonrpc(stream):\n buf = b\"\"\n while True:\n chunk = stream.read(1)\n if not chunk: return None\n if len(buf) < 5000000: # bound the line buffer (CWE-400)\n buf += chunk\n if chunk == b\"\\\\n\":\n line = buf.strip()\n buf = b\"\"\n if not line: continue\n try: return json.loads(line)\n except: continue\n\ndef forward_stdin(proc):\n while True:\n msg = read_jsonrpc(sys.stdin.buffer)\n if msg is None: break\n tap_message(msg, \"request\")\n line = json.dumps(msg) + \"\\\\n\"\n try:\n proc.stdin.write(line.encode())\n proc.stdin.flush()\n except: break\n\ndef main():\n args = sys.argv[1:]\n if \"--\" in args:\n sep = args.index(\"--\")\n args = args[sep + 1:]\n if not args:\n print(\"Usage: mcp_proxy.py -- <command> [args...]\", file=sys.stderr)\n sys.exit(1)\n # Spawn the user's own configured MCP server (from claude_desktop_config.json).\n proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr)\n t = threading.Thread(target=forward_stdin, args=(proc,), daemon=True)\n t.start()\n while True:\n msg = read_jsonrpc(proc.stdout)\n if msg is None: break\n tap_message(msg, \"response\")\n line = json.dumps(msg) + \"\\\\n\"\n try:\n sys.stdout.buffer.write(line.encode())\n sys.stdout.buffer.flush()\n except: break\n proc.wait()\n sys.exit(proc.returncode)\n\nif __name__ == \"__main__\":\n main()\n`;\n\n// Patches Claude Desktop's claude_desktop_config.json to wrap each mcpServers\n// entry with the proxy above (command=python3, args=[proxy, --, orig...] + the\n// capture env). Idempotent via the _synkro_original marker; the runner backs the\n// config up first and restores it on exit.\nconst MCP_PATCH_PY = `import json, sys\nconfig_path, proxy_path, capture_url, token = sys.argv[1:5]\nwith open(config_path) as f: config = json.load(f)\nservers = config.get(\"mcpServers\", {})\nif not servers:\n print(\"No MCP servers configured\")\n sys.exit(1)\npatched = 0\nfor name, server in servers.items():\n if server.get(\"_synkro_original\"): continue\n orig_cmd = server.get(\"command\", \"\")\n orig_args = server.get(\"args\", [])\n if not orig_cmd: continue\n server[\"_synkro_original\"] = {\"command\": orig_cmd, \"args\": orig_args}\n server[\"command\"] = \"python3\"\n server[\"args\"] = [proxy_path, \"--\", orig_cmd] + orig_args\n env = server.get(\"env\", {})\n env[\"SYNKRO_CAPTURE_URL\"] = capture_url\n env[\"SYNKRO_TAP_TOKEN\"] = token\n env[\"SYNKRO_MCP_SERVER\"] = name\n server[\"env\"] = env\n patched += 1\nwith open(config_path, \"w\") as f: json.dump(config, f, indent=2)\nprint(\"Patched \" + str(patched) + \" MCP servers\")\n`;\n\n// The bash harness: install mitmproxy if needed, generate + trust a session CA,\n// run mitmdump with the addon, launch Claude Desktop through it, and tear it all\n// down on exit. Uses SIGTERM (default kill) — never SIGKILL. No secrets here —\n// the token reaches the addon via the inherited environment.\nfunction buildRunner(sessionDir: string): string {\n return `#!/bin/bash\nset -e\nSESSION_DIR=${JSON.stringify(sessionDir)}\nPROXY_PORT=8080\nMITM_CA_DIR=\"$SESSION_DIR/ca\"\nTAP_SCRIPT=\"$SESSION_DIR/tap.py\"\nCA_FINGERPRINT=\"\"\nMITM_PID=\"\"\nSUDO_KEEPALIVE_PID=\"\"\nMCP_PROXY=\"$SESSION_DIR/mcp_proxy.py\"\nMCP_PATCH=\"$SESSION_DIR/mcp_patch.py\"\nCLAUDE_CONFIG=\"$HOME/Library/Application Support/Claude/claude_desktop_config.json\"\nCLAUDE_CONFIG_BACKUP=\"$SESSION_DIR/claude_desktop_config.backup.json\"\nMCP_PATCHED=false\n\ncleanup() {\n echo \"\"\n echo \"Stopping Claude Desktop observability...\"\n [ -n \"$MITM_PID\" ] && kill \"$MITM_PID\" 2>/dev/null\n [ -n \"$SUDO_KEEPALIVE_PID\" ] && kill \"$SUDO_KEEPALIVE_PID\" 2>/dev/null\n # Restore the original MCP config (unwrap the proxy) on exit.\n if [ \"$MCP_PATCHED\" = \"true\" ] && [ -f \"$CLAUDE_CONFIG_BACKUP\" ]; then\n cp \"$CLAUDE_CONFIG_BACKUP\" \"$CLAUDE_CONFIG\" && echo \" MCP config restored\"\n fi\n if [ -n \"$CA_FINGERPRINT\" ]; then\n # Non-interactive (-n) so teardown can NEVER hang on a sudo password prompt\n # (which would swallow the user's Ctrl+C). The keepalive below keeps creds\n # warm so this normally succeeds without a prompt.\n if sudo -n security delete-certificate -Z \"$CA_FINGERPRINT\" -t /Library/Keychains/System.keychain 2>/dev/null; then\n echo \" Session CA removed from keychain\"\n else\n echo \" Could not auto-remove the session CA (sudo timed out).\"\n echo \" Remove it manually: sudo security delete-certificate -c mitmproxy /Library/Keychains/System.keychain\"\n fi\n fi\n echo \"Done.\"\n exit 0\n}\ntrap cleanup INT TERM EXIT\n\nif ! command -v mitmdump &>/dev/null; then\n echo \"Installing mitmproxy...\"\n if command -v brew &>/dev/null; then brew install mitmproxy; else echo \"mitmproxy required: brew install mitmproxy\"; exit 1; fi\nfi\n\nmkdir -p \"$MITM_CA_DIR\"\necho \"Generating session CA certificate...\"\nmitmdump --set confdir=\"$MITM_CA_DIR\" --listen-port 0 </dev/null &>/dev/null &\nTMPPID=$!; sleep 2; kill $TMPPID 2>/dev/null || true\n\necho \"Trusting session CA (sudo — removed automatically on exit)...\"\nsudo security add-trusted-cert -d -r trustRoot -k \"/Library/Keychains/System.keychain\" \"$MITM_CA_DIR/mitmproxy-ca-cert.pem\"\nCA_FINGERPRINT=$(openssl x509 -in \"$MITM_CA_DIR/mitmproxy-ca-cert.pem\" -noout -fingerprint -sha1 2>/dev/null | sed \"s/.*=//;s/://g\")\n\n# Keep the sudo timestamp warm for the life of the session so teardown's CA\n# removal never blocks on a password prompt (a prompt would eat Ctrl+C). Exits\n# itself if creds are ever revoked. Killed in cleanup().\n( while true; do sudo -n true 2>/dev/null || exit 0; sleep 30; done ) &\nSUDO_KEEPALIVE_PID=$!\n\nlsof -ti:$PROXY_PORT 2>/dev/null | xargs kill 2>/dev/null || true\nosascript -e 'tell app \"Claude\" to quit' 2>/dev/null || true\nsleep 1\n\n# Patch Claude Desktop's MCP config to wrap each server with the Synkro stdio\n# proxy (restored on exit). Capture URL + token come from the inherited env.\nif [ -f \"$CLAUDE_CONFIG\" ]; then\n cp \"$CLAUDE_CONFIG\" \"$CLAUDE_CONFIG_BACKUP\"\n if python3 \"$MCP_PATCH\" \"$CLAUDE_CONFIG\" \"$MCP_PROXY\" \"$SYNKRO_CAPTURE_URL\" \"$SYNKRO_TAP_TOKEN\" 2>/dev/null; then\n MCP_PATCHED=true\n echo \"MCP servers wrapped with Synkro proxy\"\n else\n echo \"No MCP servers configured — skipping MCP proxy\"\n cp \"$CLAUDE_CONFIG_BACKUP\" \"$CLAUDE_CONFIG\" 2>/dev/null || true\n fi\nfi\n\necho \"Starting proxy on :$PROXY_PORT...\"\n# stdin from /dev/null so mitmdump can't grab the TTY in raw mode — otherwise it\n# captures keypresses (incl. Ctrl+C) instead of letting them become a signal.\nmitmdump --set confdir=\"$MITM_CA_DIR\" --listen-port $PROXY_PORT --mode regular -s \"$TAP_SCRIPT\" </dev/null &>\"$(dirname \"$TAP_SCRIPT\")/mitm.log\" &\nMITM_PID=$!\nsleep 2\n\nCLAUDE_BIN=\"\"\nfor p in \"/Applications/Claude.app/Contents/MacOS/Claude\" \"/Volumes/Claude/Claude.app/Contents/MacOS/Claude\"; do\n [ -f \"$p\" ] && CLAUDE_BIN=\"$p\" && break\ndone\nif [ -z \"$CLAUDE_BIN\" ]; then\n CLAUDE_BIN=\"$(mdfind 'kMDItemFSName == \"Claude.app\"' 2>/dev/null | head -1)/Contents/MacOS/Claude\"\nfi\nif [ ! -f \"$CLAUDE_BIN\" ]; then echo \"Claude Desktop not found.\"; exit 1; fi\n\necho \"Launching Claude Desktop...\"\n\"$CLAUDE_BIN\" --proxy-server=\"http://localhost:$PROXY_PORT\" &>/dev/null &\n\necho \"\"\necho \" ✓ Claude Desktop is being monitored by Synkro (local).\"\necho \" Your conversations will appear in the dashboard Events page.\"\necho \" Press Ctrl+C to stop (this removes the session CA cleanly).\"\necho \"\"\nwait $MITM_PID\n`;\n}\n\n/**\n * Run a Claude Desktop observability session. Blocks (foreground) until the user\n * presses Ctrl+C, then the bash trap removes the session CA + stops the proxy.\n */\nexport async function runClaudeDesktopTap(opts: { backfill?: boolean } = {}): Promise<void> {\n if (process.platform !== 'darwin') {\n console.log(' Claude Desktop observability is macOS-only — skipped.');\n return;\n }\n let token = '';\n try { token = readFileSync(JWT_PATH, 'utf-8').trim(); } catch {}\n if (!token) {\n console.log(' Could not read local auth token (~/.synkro/.mcp-jwt). Run `synkro install` first.');\n return;\n }\n if (opts.backfill) {\n console.log(' History backfill enabled — importing past conversations once this session.');\n }\n\n // Register the synkro-guardrails MCP server in Claude Desktop so the user can\n // manage Synkro standards (rules/tasks/findings) from Desktop too — same stdio\n // proxy → local synkro-server (:18931) that Claude Code's local mode uses.\n try {\n const mcp = installClaudeDesktopMcpConfig();\n if (mcp.ok) {\n console.log(` ✓ Synkro standards MCP registered in Claude Desktop`);\n console.log(` (${mcp.path}) — restart Claude Desktop to load the synkro-guardrails tools.`);\n } else {\n console.log(` ⚠ Claude Desktop MCP not registered: ${mcp.reason}`);\n }\n } catch (err) {\n console.log(` ⚠ Could not register Claude Desktop MCP: ${(err as Error).message}`);\n }\n\n // Stage the tap's scripts under ~/.synkro (a Synkro-managed dir) rather than\n // /tmp — keeps all session state in one predictable, allowlisted location.\n const cdRoot = join(homedir(), '.synkro', 'cd-sessions');\n mkdirSync(cdRoot, { recursive: true });\n const sessionDir = mkdtempSync(join(cdRoot, 'synkro-cd-'));\n writeFileSync(join(sessionDir, 'tap.py'), ADDON_PY, 'utf-8');\n // MCP stdio proxy + config patcher (run by the bash to wrap configured MCP servers).\n writeFileSync(join(sessionDir, 'mcp_proxy.py'), MCP_PROXY_PY, { mode: 0o755 });\n writeFileSync(join(sessionDir, 'mcp_patch.py'), MCP_PATCH_PY, 'utf-8');\n const runnerPath = join(sessionDir, 'run.sh');\n writeFileSync(runnerPath, buildRunner(sessionDir), { mode: 0o755 });\n\n await new Promise<void>((resolve) => {\n // Inherit stdio so the user sees output, the sudo prompt works, and Ctrl+C\n // reaches bash's trap for a clean teardown. Token + URL are passed via env —\n // mitmdump (and thus the addon) inherits them; they never touch the source.\n const child = spawn('bash', [runnerPath], {\n stdio: 'inherit',\n env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? '1' : '' },\n });\n const forward = (sig: NodeJS.Signals) => { try { child.kill(sig); } catch {} };\n process.on('SIGINT', forward);\n process.on('SIGTERM', forward);\n child.on('exit', () => {\n process.off('SIGINT', forward);\n process.off('SIGTERM', forward);\n resolve();\n });\n });\n}\n","/**\n * macOS Keychain export for the containerised Synkro install.\n *\n * Claude Code on macOS stores its auth blob in the user's login keychain under\n * the service name `Claude Code-credentials`. A Linux container can't reach\n * the keychain, so on `synkro install` we shell out to the `security` CLI,\n * extract the blob, and write it to a host-side file that the container\n * bind-mounts read-write.\n *\n * Refresh policy: claude rotates its token periodically. We install a launchd\n * agent that re-exports every 45 min so the container picks up new tokens\n * without manual intervention. Stale tokens between rotations fail at most a\n * single grade before the next refresh runs.\n *\n * IMPORTANT (per Synkro Plan Review R003): the exported file lives ONLY on the\n * host under ~/.synkro/claude-creds/ and is bind-mounted at container runtime.\n * It must never be COPY'd into a Dockerfile or otherwise baked into image\n * layers — image layers are part of the publishable artefact and would leak\n * the customer's credentials to anyone who pulls the image.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nexport const CLAUDE_CREDS_DIR = join(SYNKRO_DIR, 'claude-creds');\nexport const CLAUDE_CREDS_FILE = join(CLAUDE_CREDS_DIR, '.credentials.json');\n\n// Cursor grading uses @cursor/sdk, which authenticates ONLY with a\n// dashboard-issued Cursor API key — the macOS keychain OAuth token is\n// rejected by the SDK. So there is no keychain bridge for Cursor: the user\n// places their API key (cursor.com → Settings → API Keys) in this file and\n// it is bind-mounted into the container.\nexport const CURSOR_CREDS_DIR = join(SYNKRO_DIR, 'cursor-creds');\nexport const CURSOR_API_KEY_FILE = join(CURSOR_CREDS_DIR, 'api-key');\n\n// Keychain service name claude uses on macOS. Don't change without verifying\n// against the installed Claude Code build first — name drift is silent and\n// shows up later as \"all my grades say unavailable.\"\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\n// launchd label for the periodic refresh agent.\nconst LAUNCHD_LABEL = 'com.synkro.cli.claude-creds-refresh';\nconst LAUNCHD_PLIST = join(homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);\n// The resident refresh loop lives in its own script file (not inlined in the\n// plist) so we never have to XML-escape shell operators (<, >, &&, |, 2>&1).\nconst REFRESH_SCRIPT = join(SYNKRO_DIR, 'claude-creds-refresh-loop.sh');\nconst REFRESH_LOG = join(SYNKRO_DIR, 'claude-creds-refresh.log');\n\n// Expiry-aware refresh cadence. The OLD design used a flat 45-min StartInterval\n// on the assumption that Claude OAuth tokens live ~1h — they don't. Observed\n// access-token lifetimes are as short as ~12 min, and the Keychain rotates the\n// token underneath us at unpredictable times. A flat 45-min copy therefore left\n// the container running an expired token for the majority of every cycle (all\n// graders SICK, graded=0). The agent is now a resident loop that re-exports and\n// sleeps until just before the *exported* token's expiry, so the container is\n// never more than a couple minutes from current. We refresh CLAUDE creds here\n// only (the host stays the sole owner of the Claude refresh-token chain — the\n// container must NOT refresh it or it would rotate and invalidate the host\n// Keychain's token, breaking host `claude` auth).\nconst REFRESH_EXPIRY_BUFFER_SECONDS = 120; // wake this long before expiry\nconst MIN_REFRESH_INTERVAL_SECONDS = 30; // floor: don't hammer the Keychain\nconst MAX_REFRESH_INTERVAL_SECONDS = 5 * 60; // ceiling: re-poll to catch rotations even on long-lived tokens\n\nexport class KeychainExportError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'KeychainExportError';\n }\n}\n\n/**\n * True when the platform actually needs the keychain bridge. Linux stores\n * creds in a file already; only darwin uses the keychain.\n */\nexport function needsKeychainBridge(): boolean {\n return platform() === 'darwin';\n}\n\n/**\n * Read the Claude Code credential blob from the macOS keychain. Returns null\n * if the entry doesn't exist (claude not signed in) or if the security CLI\n * fails for any reason. The caller should treat null as \"auth not configured\n * yet\" rather than a hard error — the user may not have launched claude yet.\n */\nexport function readKeychainCreds(): string | null {\n if (platform() !== 'darwin') return null;\n const r = spawnSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) return null;\n const blob = (r.stdout || '').trim();\n return blob || null;\n}\n\n/**\n * One-shot export: pull creds from the keychain and write them to the\n * host-side file the container bind-mounts. Returns the absolute file path,\n * or null if there was nothing to export.\n *\n * The directory and file are chmodded to 700/600 so other users on the host\n * can't read the credentials. The container's bind mount preserves these\n * perms inside the namespace.\n */\nexport function exportKeychainCreds(): string | null {\n const blob = readKeychainCreds();\n if (!blob) return null;\n mkdirSync(CLAUDE_CREDS_DIR, { recursive: true });\n chmodSync(CLAUDE_CREDS_DIR, 0o700);\n // Write only when the blob actually changed. The container's cred-watchdog\n // keys its \"host updated credentials file\" → worker-restart on this file's\n // mtime, so rewriting an identical token every poll would churn the pool\n // needlessly. chmod is mtime-neutral, so re-asserting perms is harmless.\n let changed = true;\n try {\n if (existsSync(CLAUDE_CREDS_FILE)) {\n changed = readFileSync(CLAUDE_CREDS_FILE, 'utf-8') !== blob;\n }\n } catch { /* treat unreadable as changed */ }\n if (changed) writeFileSync(CLAUDE_CREDS_FILE, blob, 'utf-8');\n chmodSync(CLAUDE_CREDS_FILE, 0o600);\n return CLAUDE_CREDS_FILE;\n}\n\n/**\n * True when the user has placed a Cursor API key at CURSOR_API_KEY_FILE.\n * Used by the installer to decide whether the Cursor worker pool can run.\n * We never read the key value here — only confirm the file is present and\n * non-empty so the worker can pick it up via the bind mount.\n */\nexport function cursorApiKeyConfigured(): boolean {\n try {\n return existsSync(CURSOR_API_KEY_FILE)\n && readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Persist a Cursor API key to CURSOR_API_KEY_FILE (0600, dir 0700). The\n * containerised worker bind-mounts the file and reads the key at grade time.\n * Same R003 rule as the Claude creds: host-only file, never baked into an\n * image layer.\n */\nexport function writeCursorApiKey(key: string): void {\n const trimmed = key.trim();\n if (!trimmed) return;\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n chmodSync(CURSOR_CREDS_DIR, 0o700);\n writeFileSync(CURSOR_API_KEY_FILE, trimmed, 'utf-8');\n chmodSync(CURSOR_API_KEY_FILE, 0o600);\n}\n\n/**\n * Check whether the stored Cursor API key is actually accepted by Cursor.\n * Hits the Cursor API's `GET /v1/me` (Basic auth — the key is the username).\n *\n * Returns true (accepted), false (definitively rejected — 401/403), or null\n * (could not determine: no key, network error, or an ambiguous status). A null\n * result must NOT be treated as invalid — a transient network blip should\n * never nag the user to re-enter a working key.\n */\nexport async function validateCursorApiKey(): Promise<boolean | null> {\n let key: string;\n try {\n key = readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n if (!key) return null;\n try {\n const auth = Buffer.from(`${key}:`).toString('base64');\n const r = await fetch('https://api.cursor.com/v1/me', {\n headers: { Authorization: `Basic ${auth}` },\n signal: AbortSignal.timeout(8_000),\n });\n if (r.status === 401 || r.status === 403) return false;\n if (r.ok) return true;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether the exported Claude token is expired (or within the refresh buffer of\n * expiring). Expiry-based, not file-age based: a freshly *written* file can still\n * hold a near-dead token, and a long-lived token is fine no matter how old the\n * file is. Used by `synkro local-cc status` and the refresh loop.\n */\nexport function credsAreStale(): boolean {\n if (!existsSync(CLAUDE_CREDS_FILE)) return true;\n try {\n const raw = readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0; // epoch ms\n if (!exp) return true;\n return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1000;\n } catch {\n return true;\n }\n}\n\n/** XML-escape a value destined for a plist <string> (paths are data, not code). */\nfunction xmlEscape(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Install the launchd agent that keeps the container's Claude creds fresh.\n *\n * This is a KeepAlive *resident loop* (not a flat StartInterval one-shot): it\n * re-exports the Keychain creds, then sleeps until just before the exported\n * token's expiry (clamped to [MIN, MAX]), so the bind-mounted copy is never\n * stale regardless of how short Claude's access tokens are or when the Keychain\n * rotates. It reuses the existing one-shot `synkro local-cc refresh-creds` for\n * the actual export, so it works with any installed CLI build. Idempotent —\n * overwrites the script + plist. Caller does the launchctl bootout/bootstrap.\n *\n * Security: the caller-supplied bin path and the creds path are passed to the\n * loop via launchd EnvironmentVariables (XML data, never shell-evaluated) and\n * read as \"$SYNKRO_BIN\" / \"$SYNKRO_CF\" — no caller string is interpolated into\n * the script body, so there is no shell-injection surface (CWE-78).\n *\n * Returns the absolute path to the written plist for the caller to load.\n */\nexport function writeRefreshAgent(synkroBinPath: string): string {\n if (platform() !== 'darwin') {\n throw new KeychainExportError('writeRefreshAgent is darwin-only');\n }\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n mkdirSync(SYNKRO_DIR, { recursive: true });\n\n // TS template literal: only `${...}` is interpolated. Here that is ONLY the\n // numeric tuning constants — never any path/string. `$VAR`, `$(...)`, and\n // `\\${...}` survive verbatim into bash. expiresAt (epoch ms) is parsed with\n // sed (no jq dependency). We log only when the token actually changes.\n const script = `#!/bin/bash\n# Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.\n# SYNKRO_BIN / SYNKRO_CF are injected by launchd as environment data — never\n# interpolated into this script — so there is no shell-injection surface.\nexport PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\"\nprev=\"\"\nwhile true; do\n { \"$SYNKRO_BIN\" local-cc refresh-creds || synkro local-cc refresh-creds; } >/dev/null 2>&1\n exp=$(sed -n 's/.*\"expiresAt\"[: ]*\\\\([0-9]\\\\{10,\\\\}\\\\).*/\\\\1/p' \"$SYNKRO_CF\" 2>/dev/null | head -1)\n now=$(date +%s)\n if [ -n \"$exp\" ]; then\n delay=$(( exp/1000 - now - ${REFRESH_EXPIRY_BUFFER_SECONDS} ))\n else\n delay=${MIN_REFRESH_INTERVAL_SECONDS}\n fi\n [ \"$delay\" -lt ${MIN_REFRESH_INTERVAL_SECONDS} ] && delay=${MIN_REFRESH_INTERVAL_SECONDS}\n [ \"$delay\" -gt ${MAX_REFRESH_INTERVAL_SECONDS} ] && delay=${MAX_REFRESH_INTERVAL_SECONDS}\n if [ \"$exp\" != \"$prev\" ]; then\n echo \"[$(date '+%Y-%m-%dT%H:%M:%S')] claude creds refreshed (expiresAt=\\${exp:-none}); next check in \\${delay}s\"\n prev=\"$exp\"\n fi\n sleep \"$delay\"\ndone\n`;\n writeFileSync(REFRESH_SCRIPT, script, 'utf-8');\n chmodSync(REFRESH_SCRIPT, 0o700);\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/bash</string>\n <string>${xmlEscape(REFRESH_SCRIPT)}</string>\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n <key>SYNKRO_BIN</key>\n <string>${xmlEscape(synkroBinPath)}</string>\n <key>SYNKRO_CF</key>\n <string>${xmlEscape(CLAUDE_CREDS_FILE)}</string>\n </dict>\n <key>KeepAlive</key>\n <true/>\n <key>RunAtLoad</key>\n <true/>\n <key>ThrottleInterval</key>\n <integer>${MIN_REFRESH_INTERVAL_SECONDS}</integer>\n <key>StandardErrorPath</key>\n <string>${xmlEscape(REFRESH_LOG)}</string>\n <key>StandardOutPath</key>\n <string>${xmlEscape(REFRESH_LOG)}</string>\n</dict>\n</plist>\n`;\n writeFileSync(LAUNCHD_PLIST, plist, 'utf-8');\n return LAUNCHD_PLIST;\n}\n\n/**\n * Load (or reload) the launchd refresh agent. Safe to call multiple times.\n */\nexport function loadRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n // bootout returns non-zero if it isn't loaded; that's fine, we just want to\n // ensure a clean state before bootstrap.\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const r = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) {\n throw new KeychainExportError(\n `launchctl bootstrap failed: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n}\n\n/**\n * Stop the launchd refresh agent and remove the plist. Used by `synkro\n * disconnect` / `synkro uninstall` so we don't leave orphan agents behind.\n */\nexport function uninstallRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n try {\n const fs = require('node:fs');\n if (existsSync(LAUNCHD_PLIST)) fs.unlinkSync(LAUNCHD_PLIST);\n if (existsSync(REFRESH_SCRIPT)) fs.unlinkSync(REFRESH_SCRIPT);\n } catch { /* best-effort */ }\n}\n\n/**\n * Convenience wrapper for `synkro local-cc refresh-creds`. Re-exports the\n * Claude keychain creds. Cursor needs no refresh agent — its API key is a\n * static dashboard key the user supplies once; it doesn't rotate like the\n * Claude OAuth token.\n */\nexport function refreshCreds(): boolean {\n const path = exportKeychainCreds();\n return path !== null;\n}\n\n/**\n * Read the current exported creds (if any). Used by status commands.\n */\nexport function readExportedCreds(): string | null {\n try {\n return readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n } catch {\n return null;\n }\n}\n","/**\n * Containerised Synkro install path.\n *\n * Replaces the host-side pueue + tmux + cc_sessions stack with a single\n * Docker container that bundles MCP server, pglite-db, grader dispatcher,\n * and N tmux-managed claude grader workers.\n *\n * Activated when SYNKRO_DEPLOYMENT_MODE=docker is set. The bare-host path\n * remains the default until two stable release cycles have validated this\n * path under real load (see Phase 5 in the architecture plan).\n *\n * Host-side files this module touches:\n * ~/.synkro/ (bind-mounted RO as /data/synkro-host;\n * the container reads .mcp-jwt and\n * credentials.json from inside it)\n * ~/.synkro/pgdata/ (bind-mounted RW, persistent telemetry)\n * ~/.synkro/claude-creds/ (bind-mounted RW on macOS; symlinked\n * from ~/.claude on Linux)\n *\n * Never bakes credentials into the image — every secret is a runtime bind\n * mount of a host file. See macKeychain.ts for the macOS bridge.\n */\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport {\n CLAUDE_CREDS_DIR,\n CURSOR_CREDS_DIR,\n exportKeychainCreds,\n cursorApiKeyConfigured,\n needsKeychainBridge,\n writeRefreshAgent,\n loadRefreshAgent,\n} from './macKeychain.js';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nconst MCP_JWT_PATH = join(SYNKRO_DIR, '.mcp-jwt');\nconst PGDATA_PATH = join(SYNKRO_DIR, 'pgdata');\n// Docker Desktop on macOS can't reliably bind-mount individual files (the mount\n// shows up as -????? inside the container). Stage the host's ~/.claude.json\n// inside a sibling directory and bind-mount the directory instead.\nconst CLAUDE_HOST_STATE_DIR = join(SYNKRO_DIR, 'claude-host-state');\nconst CLAUDE_HOST_STATE_FILE = join(CLAUDE_HOST_STATE_DIR, '.claude.json');\n\n// Host-side ports the installer maps from the container's internal ports.\n// Bumped by 10000 to avoid colliding with the bare-host install's defaults\n// when both are sitting on the same machine during cutover (Phase 5).\nconst HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || '18931', 10);\nconst HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10);\nconst HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || '18930', 10);\n// PGLite TCP socket published from the container's internal 5433 so host tools\n// (psql/DBeaver/TablePlus) can inspect the telemetry DB. The container's\n// pglite-bootstrap multiplexer enforces access; the 127.0.0.1 bind is the\n// network-layer allowlist.\nconst HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || '15433', 10);\n\nconst CONTAINER_NAME = 'synkro-server';\n// Canonical GHCR coordinates published by .github/workflows/docker-publish.yml.\n// Override at install time via SYNKRO_IMAGE_TAG (or mirror to a private\n// registry and set SYNKRO_IMAGE_REGISTRY for the hedge-fund deployment).\nconst DEFAULT_IMAGE = 'ghcr.io/synkro-sh/synkro-server:latest';\n\nexport class DockerInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'DockerInstallError';\n }\n}\n\nexport type WorkerProvider = 'claude_code' | 'cursor';\n\n/**\n * Split a total worker count across the selected providers.\n * - both providers → even split (an odd worker goes to Claude)\n * - single provider → the whole pool\n * - no providers → treated as Claude-only (legacy default)\n * Used by `synkro install` and `synkro local-cc start|restart` so the split\n * rule lives in exactly one place.\n */\nexport function splitWorkers(total: number, providers: WorkerProvider[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n} {\n const t = Math.max(0, Math.floor(total));\n const hasClaude = providers.includes('claude_code');\n const hasCursor = providers.includes('cursor');\n if (hasClaude && hasCursor) {\n const cursorWorkers = Math.floor(t / 2);\n return { claudeWorkers: t - cursorWorkers, cursorWorkers };\n }\n if (hasCursor) return { claudeWorkers: 0, cursorWorkers: t };\n return { claudeWorkers: t, cursorWorkers: 0 };\n}\n\n/** Normalize a provider token. Accepts claude / claude-code / cc / cursor. */\nexport function normalizeProvider(p: string): WorkerProvider | null {\n const v = p.trim().toLowerCase();\n if (v === 'claude' || v === 'claude-code' || v === 'claude_code' || v === 'cc') return 'claude_code';\n if (v === 'cursor') return 'cursor';\n return null;\n}\n\nexport type PoolResolution = {\n /** Explicit per-kind counts, when the file specifies any. */\n claudeWorkers?: number;\n cursorWorkers?: number;\n /** Shorthand mode when no explicit counts are given. */\n pool: 'auto' | 'claude_code' | 'cursor';\n /** Human-readable problems (unrecognized tokens) — callers should surface these. */\n warnings: string[];\n};\n\n/**\n * Resolve the grader pool from a parsed `.synkro` object into a single shape\n * that every caller (`install`, `local-cc start/restart`) agrees on. This is\n * the ONE place the pool/worker config schema is interpreted.\n *\n * Sources, in priority order:\n * 1. Numeric provider counts directly under `grader:` (the unified form) —\n * grader: { claude-code: 5, cursor: 3 } // mixed pool, any count\n * Aliases (claude / claude-code / cc / cursor) all normalize.\n * 2. Legacy top-level `workers:` block — workers: { claude: 5, cursor: 3 }\n * 3. `grader.pool` string shorthand: auto | claude-code | cursor (+ aliases)\n *\n * Unrecognized tokens produce a warning instead of a silent fallback so a\n * typo'd provider name can't quietly halve the pool.\n */\nexport function resolveGraderPool(parsed: Record<string, any>): PoolResolution {\n const warnings: string[] = [];\n const grader = parsed?.grader && typeof parsed.grader === 'object' ? parsed.grader : {};\n\n // 1+2. Collect explicit numeric counts from grader (unified) then the legacy\n // workers block. Unknown numeric keys under grader warn (likely a typo).\n const counts: { claude_code?: number; cursor?: number } = {};\n const collect = (obj: any, where: string, flagUnknown: boolean) => {\n if (!obj || typeof obj !== 'object') return;\n for (const [k, v] of Object.entries(obj)) {\n if (typeof v !== 'number') continue;\n const np = normalizeProvider(k);\n if (np === 'claude_code') counts.claude_code = Math.max(0, Math.floor(v));\n else if (np === 'cursor') counts.cursor = Math.max(0, Math.floor(v));\n else if (flagUnknown) {\n warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code or cursor) — ignored`);\n }\n }\n };\n collect(grader, 'grader', true);\n collect(parsed?.workers, 'workers', false);\n\n // 3. pool string shorthand.\n let pool: 'auto' | 'claude_code' | 'cursor' = 'auto';\n const rawPool = grader.pool;\n if (rawPool != null && rawPool !== 'auto') {\n const np = normalizeProvider(String(rawPool));\n if (np) pool = np;\n else warnings.push(`synkro.toml: grader.pool=\"${rawPool}\" is not recognized (use auto | claude-code | cursor) — falling back to auto`);\n }\n\n if (counts.claude_code != null || counts.cursor != null) {\n return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };\n }\n return { pool, warnings };\n}\n\n/** Friendly label for display/logging from the internal pool form. */\nexport function poolLabel(pool: 'auto' | 'claude_code' | 'cursor'): string {\n return pool === 'claude_code' ? 'claude-code' : pool;\n}\n\n// Coerce a single TOML value token to JS.\nfunction parseTomlValue(raw: string): any {\n const v = raw.trim();\n if (v.startsWith('[') && v.endsWith(']')) {\n const inner = v.slice(1, -1).trim();\n if (!inner) return [];\n return inner.split(',').map(s => parseTomlValue(s)).filter(s => s !== '');\n }\n if ((v.startsWith('\"') && v.endsWith('\"')) || (v.startsWith(\"'\") && v.endsWith(\"'\"))) return v.slice(1, -1);\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (/^-?\\d+$/.test(v)) return parseInt(v, 10);\n return v;\n}\n\n// Minimal TOML reader for synkro.toml's fixed schema (top-level scalars/arrays\n// + [grader]/[scanning] tables). Returns the same nested-object shape the old\n// YAML parser did, so resolveGraderPool() is unchanged.\nfunction parseSynkroToml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n let section: string | null = null;\n for (const rawLine of raw.split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n const sec = line.match(/^\\[([^\\]]+)\\]$/);\n if (sec) {\n section = sec[1].trim();\n if (!result[section]) result[section] = {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim().replace(/^[\"']|[\"']$/g, '');\n let valRaw = line.slice(eq + 1).trim();\n if (!valRaw.startsWith('\"') && !valRaw.startsWith(\"'\") && !valRaw.startsWith('[')) {\n const h = valRaw.indexOf('#');\n if (h !== -1) valRaw = valRaw.slice(0, h).trim();\n }\n const value = parseTomlValue(valRaw);\n if (section) (result[section] as Record<string, any>)[key] = value;\n else result[key] = value;\n }\n return result;\n}\n\nfunction readSynkroFileConfig(): PoolResolution {\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return { pool: 'auto', warnings: [] };\n const fp = join(root, 'synkro.toml');\n if (!existsSync(fp)) return { pool: 'auto', warnings: [] };\n const parsed = parseSynkroToml(readFileSync(fp, 'utf-8'));\n return resolveGraderPool(parsed);\n } catch {}\n return { pool: 'auto', warnings: [] };\n}\n\n/**\n * Parse `--workers N|--workers=N` and `--provider(s) a,b` flags from a\n * start/restart invocation and compute the per-kind worker split.\n * - `explicit` is true when any worker/provider flag was supplied — callers\n * use it to decide between a config-changing recreate vs a plain start.\n * - Providers default to the coding agents detected on the machine.\n * - Total defaults to 8, capped at 64.\n */\nexport function resolveWorkerConfig(rest: string[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n explicit: boolean;\n} {\n let workers = 8;\n let explicit = false;\n const providers: WorkerProvider[] = [];\n const addProviders = (csv: string) => {\n for (const p of csv.split(',')) {\n const np = normalizeProvider(p);\n if (np && !providers.includes(np)) providers.push(np);\n }\n };\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n if (a === '--workers' || a === '-w') { workers = parseInt(rest[++i] || '8', 10); explicit = true; }\n else if (a.startsWith('--workers=')) { workers = parseInt(a.slice('--workers='.length), 10); explicit = true; }\n else if (a === '--provider' || a === '--providers') { addProviders(rest[++i] || ''); explicit = true; }\n else if (a.startsWith('--provider=')) { addProviders(a.slice('--provider='.length)); explicit = true; }\n else if (a.startsWith('--providers=')) { addProviders(a.slice('--providers='.length)); explicit = true; }\n }\n if (!Number.isFinite(workers) || workers < 1) workers = 8;\n workers = Math.min(workers, 64);\n let provs = providers;\n if (provs.length === 0) {\n const sc = readSynkroFileConfig();\n for (const w of sc.warnings) console.warn(` ⚠ ${w}`);\n if (sc.claudeWorkers != null || sc.cursorWorkers != null) {\n const cw = sc.claudeWorkers || 0;\n const curw = sc.cursorWorkers || 0;\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };\n }\n if (sc.pool === 'cursor') {\n provs = ['cursor'];\n } else if (sc.pool === 'claude_code') {\n provs = ['claude_code'];\n } else {\n provs = detectAgents().map(a => a.kind) as WorkerProvider[];\n if (provs.length === 0) provs = ['claude_code'];\n }\n }\n return { ...splitWorkers(workers, provs), explicit };\n}\n\n/** Resolve which image tag this install should pull. */\nexport function imageTag(): string {\n const registry = process.env.SYNKRO_IMAGE_REGISTRY || ''; // e.g. registry.hedgefund.internal\n const tag = process.env.SYNKRO_IMAGE_TAG || DEFAULT_IMAGE;\n return registry ? `${registry.replace(/\\/+$/, '')}/${tag.replace(/^.*\\//, '')}` : tag;\n}\n\n/** True iff docker is on PATH and the daemon is reachable. */\nexport function assertDockerAvailable(): void {\n const v = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (v.status !== 0) {\n throw new DockerInstallError(\n 'Docker CLI is not installed or the daemon is not reachable.\\n' +\n 'Install Docker Desktop (macOS) or docker-engine (Linux), start the daemon, then re-run.',\n );\n }\n}\n\n/**\n * Compute the bind-mount source for the claude credentials dir. macOS uses\n * the exported keychain shadow; Linux uses ~/.claude directly.\n */\nfunction claudeCredsHostDir(): string {\n if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;\n return join(homedir(), '.claude');\n}\n\n/**\n * Idempotent docker pull + run. If a container with the same name is already\n * up, this stops it first so the new image starts cleanly (matches\n * `synkro update` semantics).\n *\n * Returns the resolved image tag for logging.\n */\n/**\n * Resolve the absolute path to the `synkro` binary for the launchd refresh\n * agent. The location varies by install method (Homebrew's /opt/homebrew/bin,\n * /usr/local/bin, an npm prefix) — never hardcode it, or the refresh job\n * silently fails and the container's Claude creds expire.\n */\nfunction resolveSynkroBin(): string {\n const which = spawnSync('which', ['synkro'], { encoding: 'utf-8', timeout: 5_000 });\n const resolved = (which.stdout || '').split('\\n')[0].trim();\n return resolved || 'synkro';\n}\n\n// Host-side PGLite orphans from pre-Docker installs (or earlier crashed\n// container starts that re-spawned the host bin) squat on the published\n// PGLite port, hold the only client slot under the old maxConnections=1\n// default, and write into a separate pgdata from the container's mount.\n// The migration commit that moved PGLite into the container deleted the\n// source file but never killed running processes — they daemonize to\n// launchd (PPID=1) and outlive the install that spawned them. Sweep them\n// before launching the new container so the publish maps to the right\n// PGLite. Container bun processes are isolated inside their PID namespace\n// and never appear in the host's ps output, so this is safe.\nfunction sweepHostPglite(): void {\n const ps = spawnSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5_000 });\n if (ps.status !== 0) return;\n const targets: number[] = [];\n for (const line of (ps.stdout || '').split('\\n')) {\n if (!/bun\\b/.test(line)) continue;\n if (!/pglite-(db|bootstrap)\\.ts\\b/.test(line)) continue;\n const m = line.trim().match(/^(\\d+)\\s/);\n if (!m) continue;\n const pid = parseInt(m[1], 10);\n if (pid > 0 && pid !== process.pid) targets.push(pid);\n }\n if (targets.length === 0) return;\n console.log(` Sweeping ${targets.length} stale host PGLite process(es): ${targets.join(', ')}`);\n for (const pid of targets) {\n try { process.kill(pid, 'SIGTERM'); } catch {}\n }\n const deadline = Date.now() + 3000;\n while (Date.now() < deadline) {\n const alive = targets.filter(pid => {\n try { process.kill(pid, 0); return true; } catch { return false; }\n });\n if (alive.length === 0) return;\n spawnSync('sleep', ['0.2'], { timeout: 1_000 });\n }\n for (const pid of targets) {\n try { process.kill(pid, 'SIGKILL'); } catch {}\n }\n}\n\nexport async function dockerInstall(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n hostPglitePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n // Per-kind worker counts. claudeWorkers falls back to the legacy\n // workersPerPool; cursorWorkers defaults to 0 (Claude-only install).\n const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;\n const cursorWorkers = opts.cursorWorkers ?? 0;\n const totalWorkers = claudeWorkers + cursorWorkers;\n\n // Ensure persistent host dirs exist before docker tries to bind-mount them.\n // Docker creates missing bind sources as root-owned dirs which then break\n // permissions inside the container — pre-create them as the host user.\n mkdirSync(PGDATA_PATH, { recursive: true });\n mkdirSync(BACKUP_DIR, { recursive: true });\n\n // Stage the host's ~/.claude.json into a dir we can bind-mount cleanly on\n // macOS. Without this the container's workers see -????? at the mount path\n // and Claude Code prompts for browser OAuth on every launch.\n mkdirSync(CLAUDE_HOST_STATE_DIR, { recursive: true });\n const hostClaudeJson = join(homedir(), '.claude.json');\n if (existsSync(hostClaudeJson)) {\n copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);\n }\n if (!existsSync(MCP_JWT_PATH)) {\n throw new DockerInstallError(\n `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`,\n );\n }\n\n // Pre-create the Cursor creds dir so the bind mount target is owned by the\n // host user (Docker would otherwise create it root-owned).\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n\n // On macOS, export keychain creds + install the launchd refresh agent\n // before starting the container so the workers have something to auth with.\n if (needsKeychainBridge()) {\n // Export the Claude Code keychain creds so the container holds a subscription\n // token. Two consumers need it: Claude grader workers AND the out-of-band usage\n // tap (which pings Anthropic to read account-wide utilization). The tap runs even\n // when grading is pure BYOK or pure Cursor, so we export whenever the keychain\n // entry exists — NOT only when there are Claude workers. Hard-fail only when\n // Claude grading actually requires it; otherwise degrade to \"telemetry off\".\n const claudeCredsPath = exportKeychainCreds();\n if (!claudeCredsPath) {\n if (claudeWorkers > 0) {\n throw new DockerInstallError(\n 'Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container.',\n );\n }\n console.warn(' ⚠ Claude Code keychain entry not found — live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).');\n }\n // Cursor uses a dashboard API key (the @cursor/sdk rejects the keychain\n // OAuth token). Warn — don't hard-fail — so a \"both agents\" install still\n // proceeds; the Cursor pool stays idle until the key file is present.\n if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {\n console.warn(' ⚠ No Cursor API key found — Cursor grader workers will be idle.');\n console.warn(' Generate a key at cursor.com → Settings → API Keys, then:');\n console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);\n }\n // launchd refresh agent — Claude only (Cursor's API key is static).\n const plist = writeRefreshAgent(resolveSynkroBin());\n try { loadRefreshAgent(); } catch (err) {\n console.warn(` ⚠ launchd refresh agent not loaded: ${(err as Error).message}`);\n console.warn(` Plist written to ${plist} — load manually with launchctl bootstrap when ready.`);\n }\n } else {\n // Linux — make sure ~/.claude exists so the bind mount target is real.\n mkdirSync(join(homedir(), '.claude'), { recursive: true });\n }\n\n // Pull (or update) the image — unless we're using a locally-built image.\n // A local/air-gapped image won't exist in any registry, so a forced pull\n // would fail. Skip the pull when SYNKRO_SKIP_PULL is set, and fall back to an\n // existing local image if the pull fails for any reason (offline, private\n // registry blip, locally-built dev image). Only hard-fail when there is no\n // local image to fall back to.\n const imageExistsLocally = (): boolean =>\n spawnSync('docker', ['image', 'inspect', image], { stdio: 'ignore', timeout: 30_000 }).status === 0;\n const skipPull = process.env.SYNKRO_SKIP_PULL === '1' || process.env.SYNKRO_SKIP_PULL === 'true';\n\n if (skipPull && imageExistsLocally()) {\n console.log(` Using local image ${image} (SYNKRO_SKIP_PULL set — pull skipped).`);\n } else {\n console.log(` Pulling ${image}...`);\n const pull = spawnSync('docker', ['pull', image], { encoding: 'utf-8', stdio: 'inherit', timeout: 600_000 });\n if (pull.status !== 0) {\n if (imageExistsLocally()) {\n console.warn(` ⚠ docker pull ${image} failed — falling back to the existing local image.`);\n } else {\n throw new DockerInstallError(`docker pull ${image} failed`);\n }\n }\n }\n\n // Gracefully stop existing container if running (snapshot + checkpoint).\n const existing = dockerStatus();\n if (existing.running) {\n console.log(' Stopping existing container gracefully...');\n await dockerSafeStop();\n }\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Kill any leftover host-side PGLite from pre-Docker installs so the new\n // container's published port 5433 isn't shadowed by a stale daemon.\n sweepHostPglite();\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\n //\n // SECURITY: every -p below MUST keep the `127.0.0.1:` host-bind prefix. The\n // container's /api/local/* REST endpoints (snapshot, telemetry, hook-config)\n // are unauthenticated — the localhost-only bind IS the access boundary.\n // Never drop the prefix or switch to 0.0.0.0 / --network host: that would\n // expose an unauthenticated control surface to the LAN.\n const credsDir = claudeCredsHostDir();\n const args = [\n 'run',\n '--detach',\n '--name', CONTAINER_NAME,\n '--restart', 'unless-stopped',\n '-p', `127.0.0.1:${HOST_MCP_PORT}:8931`,\n '-p', `127.0.0.1:${HOST_GRADER_PORT}:8929`,\n '-p', `127.0.0.1:${HOST_CWE_PORT}:8930`,\n '-p', `127.0.0.1:${HOST_PGLITE_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${BACKUP_DIR}:/data/backups`,\n // The whole host ~/.synkro directory, read-only. The container copies\n // .mcp-jwt and credentials.json out of it at boot. A directory mount\n // sidesteps Docker Desktop for macOS's unreliable single-file bind mounts\n // (which previously left a dangling symlink that blocked container start).\n '-v', `${SYNKRO_DIR}:/data/synkro-host:ro`,\n '-v', `${credsDir}:/home/synkro/.claude:rw`,\n '-v', `${join(homedir(), '.claude')}:/data/claude-host:ro`,\n '-v', `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,\n // Cursor creds — mounted RW so the in-container refresher can rotate the\n // access token in place. Only mounted when the install includes Cursor.\n ...(cursorWorkers > 0\n ? ['-v', `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`]\n : []),\n '-e', `WORKERS_PER_POOL=${totalWorkers}`,\n '-e', `CLAUDE_WORKERS=${claudeWorkers}`,\n '-e', `CURSOR_WORKERS=${cursorWorkers}`,\n // Pass through the batch-size lever if the operator set it. Defaults\n // inside the container to 5; clamped to [1, 20] by synkro-server.ts.\n ...(process.env.SYNKRO_MAX_BATCH_SIZE\n ? ['-e', `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`]\n : []),\n // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.\n ...(process.env.SYNKRO_CURSOR_MODEL\n ? ['-e', `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`]\n : []),\n // Fix-poll kill switch. Default ON in the image; a benchmark/headless run\n // (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the\n // interactive AskUserQuestion poll and fall through to generate-the-fix. Only\n // passed when the operator set it; otherwise the image default (1) applies.\n ...(process.env.SYNKRO_FIX_POLL\n ? ['-e', `SYNKRO_FIX_POLL=${process.env.SYNKRO_FIX_POLL}`]\n : []),\n // Connected repo — the server seeds the local app + ruleset named after it.\n ...(opts.connectedRepo\n ? ['-e', `SYNKRO_CONNECTED_REPO=${opts.connectedRepo}`]\n : []),\n // Real account identity (from config.env via process.env) — telemetry is\n // stamped with these instead of a `local-user` placeholder, so the data is\n // correctly attributed from the first graded command.\n ...(process.env.SYNKRO_ORG_ID\n ? ['-e', `SYNKRO_ORG_ID=${process.env.SYNKRO_ORG_ID}`]\n : []),\n ...(process.env.SYNKRO_USER_ID\n ? ['-e', `SYNKRO_USER_ID=${process.env.SYNKRO_USER_ID}`]\n : []),\n ...(process.env.SYNKRO_EMAIL\n ? ['-e', `SYNKRO_EMAIL=${process.env.SYNKRO_EMAIL}`]\n : []),\n // Container-owned cloud telemetry flush (docker/telemetryCloudFlush.ts). The\n // server drains the READ-ONLY host queue (already mounted at\n // /data/synkro-host via the RO mount above — NO new mount) into its pglite\n // and pushes unflushed rows to the D1 telemetry worker. The loop is gated on\n // SYNKRO_TELEMETRY_URL and is harmless when there's nothing to flush, so we\n // always set it (defaulting to the D1 worker). Override via env to redirect.\n '-e', `SYNKRO_TELEMETRY_URL=${process.env.SYNKRO_TELEMETRY_URL || 'https://admin.synkro.sh'}`,\n '-e', 'SYNKRO_TELEMETRY_QUEUE=/data/synkro-host/telemetry-pending.jsonl',\n image,\n ];\n const run = spawnSync('docker', args, { encoding: 'utf-8', stdio: 'inherit', timeout: 60_000 });\n if (run.status !== 0) {\n throw new DockerInstallError(`docker run failed (image ${image})`);\n }\n\n return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };\n}\n\n/**\n * Poll the container's MCP /healthz until it returns 200, or fail after the\n * timeout. Used by install to give a single clean ready signal instead of the\n * multi-port wait the bare-host install has to do.\n */\nexport async function waitForContainerReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_MCP_PORT}/health`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) return true;\n } catch { /* keep retrying */ }\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\nexport async function waitForWorkersReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_GRADER_PORT}/healthz`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) {\n const data = await r.json() as { workers?: number; healthy?: number };\n if ((data.healthy || 0) > 0) return true;\n }\n } catch {}\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\n/**\n * Remove the container (after it's already stopped). Does NOT stop gracefully —\n * callers must use dockerSafeStop() first if the container is running.\n */\nexport function dockerRemove(): void {\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/** @deprecated Use dockerSafeStop() + dockerRemove() instead. */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], { encoding: 'utf-8', timeout: 45_000 });\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/**\n * `synkro update` for the containerised install — pull + restart, preserve\n * all bind-mounted state.\n */\nexport async function dockerUpdate(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<void> {\n if (dockerStatus().running) {\n await dockerSafeStop();\n }\n dockerRemove();\n await dockerInstall(opts);\n}\n\n/** Show host-mapped ports + container status. */\nexport function dockerStatus(): { running: boolean; image?: string; healthz?: string } {\n const r = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const status = (r.stdout || '').trim();\n if (status !== 'running') return { running: false };\n return {\n running: true,\n image: imageTag(),\n healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`,\n };\n}\n\n/**\n * Read the worker-pool config baked into the installed container's env, so\n * `synkro update` can rebuild on a fresh image without resetting the pool.\n * Works on stopped containers too. Returns null when no container exists.\n */\nexport function readContainerConfig(): {\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} | null {\n const r = spawnSync('docker', ['inspect', '--format', '{{json .Config.Env}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0 || !r.stdout) return null;\n let env: unknown;\n try { env = JSON.parse(r.stdout.trim()); } catch { return null; }\n if (!Array.isArray(env)) return null;\n const get = (k: string): string | undefined => {\n const hit = env.find((e): e is string => typeof e === 'string' && e.startsWith(k + '='));\n return hit ? hit.slice(k.length + 1) : undefined;\n };\n const num = (s: string | undefined): number | undefined => {\n if (s === undefined) return undefined;\n const n = parseInt(s, 10);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n claudeWorkers: num(get('CLAUDE_WORKERS')),\n cursorWorkers: num(get('CURSOR_WORKERS')),\n connectedRepo: get('SYNKRO_CONNECTED_REPO') || undefined,\n };\n}\n\n// ─── Safe lifecycle commands ────────────────────────────────────────────────\n\nconst BACKUP_DIR = join(SYNKRO_DIR, 'pgdata-backups');\n\n/**\n * Graceful stop: snapshot → docker stop (30s for CHECKPOINT+flush) → verify.\n * Never force-kills. Returns details about what happened.\n */\nexport async function dockerSafeStop(): Promise<{\n ok: boolean;\n snapshot?: { ok: boolean; error?: string };\n exitCode?: number;\n pgdataCheck?: { healthy: boolean; details: string };\n}> {\n const status = dockerStatus();\n if (!status.running) {\n console.log(' Container is not running.');\n return { ok: true, pgdataCheck: checkPgdata() };\n }\n\n // 1. Request a pre-shutdown snapshot via the REST endpoint\n console.log(' Requesting data snapshot before shutdown...');\n let snapshot: { ok: boolean; error?: string } = { ok: false, error: 'not attempted' };\n try {\n const resp = await fetch(`http://127.0.0.1:${HOST_MCP_PORT}/api/local/snapshot`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reason: 'cli-stop' }),\n signal: AbortSignal.timeout(20_000),\n });\n if (resp.ok) {\n snapshot = { ok: true };\n console.log(' ✓ Snapshot saved.');\n } else {\n snapshot = { ok: false, error: `HTTP ${resp.status}` };\n console.warn(` ⚠ Snapshot request failed (HTTP ${resp.status}). Proceeding with stop.`);\n }\n } catch (e) {\n snapshot = { ok: false, error: String(e).slice(0, 100) };\n console.warn(` ⚠ Snapshot request failed: ${snapshot.error}. Proceeding with stop.`);\n }\n\n // 2. docker stop with 30s grace period (entrypoint trap does CHECKPOINT + final snapshot)\n console.log(' Stopping container (30s grace for CHECKPOINT + WAL flush)...');\n const stop = spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 45_000,\n });\n\n // 3. Verify clean exit\n const inspect = spawnSync('docker', ['inspect', '--format', '{{.State.ExitCode}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const exitCode = parseInt((inspect.stdout || '').trim(), 10);\n if (exitCode === 0) {\n console.log(' ✓ Container stopped cleanly (exit 0).');\n } else {\n console.warn(` ⚠ Container exited with code ${exitCode}.`);\n }\n\n // 4. Check pgdata health on host\n const pgCheck = checkPgdata();\n if (pgCheck.healthy) {\n console.log(` ✓ pgdata looks healthy: ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata check: ${pgCheck.details}`);\n }\n\n return { ok: stop.status === 0, snapshot, exitCode, pgdataCheck: pgCheck };\n}\n\n/**\n * Start the container (must already exist from a prior install).\n * Checks pgdata integrity, starts container, waits for healthcheck.\n */\nexport async function dockerSafeStart(): Promise<{\n ok: boolean;\n pgdataState: string;\n error?: string;\n}> {\n // Check if already running\n const status = dockerStatus();\n if (status.running) {\n console.log(' Container is already running.');\n return { ok: true, pgdataState: 'running' };\n }\n\n // Check if container exists (stopped)\n const exists = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (exists.status !== 0) {\n return { ok: false, pgdataState: 'no_container', error: 'No synkro-server container found. Run `synkro install` first.' };\n }\n\n // Check pgdata state\n const pgCheck = checkPgdata();\n if (existsSync(PGDATA_PATH) && readdirSync(PGDATA_PATH).length > 0) {\n if (pgCheck.healthy) {\n console.log(` pgdata: existing data found — ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata: ${pgCheck.details}`);\n console.log(' Starting anyway — entrypoint will attempt recovery from snapshots if needed.');\n }\n } else {\n console.log(' pgdata: no existing data — fresh start.');\n mkdirSync(PGDATA_PATH, { recursive: true });\n }\n\n // Start the container\n console.log(' Starting container...');\n const start = spawnSync('docker', ['start', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 30_000,\n });\n if (start.status !== 0) {\n return { ok: false, pgdataState: 'start_failed', error: `docker start failed: ${(start.stderr || '').slice(0, 200)}` };\n }\n\n // Wait for healthcheck\n console.log(' Waiting for server to become healthy...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ Server is healthy and ready.');\n return { ok: true, pgdataState: pgCheck.healthy ? 'existing' : 'recovered' };\n } else {\n return { ok: false, pgdataState: 'unhealthy', error: 'Server did not become healthy within 60s. Check: docker logs synkro-server' };\n }\n}\n\n/**\n * Restart: full safe stop, then safe start. Sequential — never overlapping.\n */\nexport async function dockerSafeRestart(): Promise<{ ok: boolean; stop: Awaited<ReturnType<typeof dockerSafeStop>>; start: Awaited<ReturnType<typeof dockerSafeStart>> }> {\n console.log(' === Stop ===');\n const stopResult = await dockerSafeStop();\n if (!stopResult.ok) {\n console.error(' Stop failed. Aborting restart.');\n return { ok: false, stop: stopResult, start: { ok: false, pgdataState: 'not_started', error: 'stop failed' } };\n }\n console.log('\\n === Start ===');\n const startResult = await dockerSafeStart();\n return { ok: startResult.ok, stop: stopResult, start: startResult };\n}\n\nfunction checkPgdata(): { healthy: boolean; details: string } {\n if (!existsSync(PGDATA_PATH)) return { healthy: false, details: 'pgdata directory does not exist' };\n const entries = readdirSync(PGDATA_PATH);\n if (entries.length === 0) return { healthy: true, details: 'empty (fresh start)' };\n const hasPidFile = entries.includes('postmaster.pid');\n const hasWalDir = entries.includes('pg_wal');\n const hasPgControl = entries.includes('global') || entries.includes('pg_control');\n if (hasPidFile) return { healthy: false, details: 'stale postmaster.pid present (unclean shutdown)' };\n if (!hasWalDir) return { healthy: false, details: 'pg_wal directory missing' };\n if (!hasPgControl) return { healthy: false, details: 'pg_control/global directory missing' };\n return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };\n}\n","/**\n * Capture a `claude setup-token` OAuth token (sk-ant-oat01-…) non-interactively.\n *\n * Shared by the GitHub Actions setup (setupGithub.ts) and the CLOUD installer\n * path (install.ts). It is CLOUD-ONLY for `synkro install`: local installs use\n * the macOS keychain bridge (macKeychain.ts) and never call this.\n *\n * `claude setup-token` renders its output as a TUI, so we run it under `script`\n * to capture the pty output, then scrape the token (Claude renders it in the\n * RGB 255,193,7 yellow). The user approves the OAuth in a browser; we never log\n * the token and delete the capture file immediately.\n */\nimport { spawn as nodeSpawn } from 'node:child_process';\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nexport function captureClaudeSetupToken(): Promise<string> {\n const tmpFile = join(SYNKRO_DIR, `token-capture-${Date.now()}.raw`);\n // `script`'s arg order differs by platform:\n // BSD/macOS: script -q <file> <cmd> [args...]\n // util-linux: script -qec '<cmd>' <file>\n const isMac = platform() === 'darwin';\n const bin = 'script';\n const args = isMac\n ? ['-q', tmpFile, 'claude', 'setup-token']\n : ['-qec', 'claude setup-token', tmpFile];\n // When claude.ai rejects the browser approval, `claude setup-token` ends\n // without ever printing a token (the user has no code to paste back, so they\n // Ctrl-C and we exit non-zero, or it exits 0 with nothing). The most common\n // cause is NOT a Synkro bug: a request-rewriting browser extension (Grammarly,\n // ad/script blockers, AI-assistant toolbars) strips the OAuth form's\n // Content-Type — claude.ai then shows \"Authorization failed — Unsupported\n // media type\" — or the user approved on the wrong Claude account. Surface that\n // instead of a bare exit code / empty-capture error so the customer can self-fix.\n const OAUTH_HINT =\n 'The browser approval did not return a token. This usually means claude.ai rejected ' +\n 'the OAuth request (its \"Authorization failed — Unsupported media type\" page), most often ' +\n 'because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped ' +\n 'the request headers, or you approved on the wrong Claude account. Fix: retry in a clean ' +\n 'incognito window with extensions disabled, and approve on the Claude account you want the ' +\n 'cloud workers to use.';\n return new Promise((resolve, reject) => {\n // Force deterministic 24-bit color so the token's yellow is ALWAYS emitted as\n // 38;2;255;193;7 — not a 256-color/basic approximation that varies by the\n // user's terminal. The extractor keys on that exact foreground color.\n const proc = nodeSpawn(bin, args, {\n stdio: 'inherit',\n env: { ...process.env, FORCE_COLOR: '3', COLORTERM: 'truecolor', TERM: 'xterm-256color' },\n });\n proc.on('error', (err) => reject(new Error(`Failed to spawn claude setup-token: ${err.message}`)));\n proc.on('close', (code) => {\n let raw = '';\n try { raw = readFileSync(tmpFile, 'utf-8'); } catch (e) {\n reject(new Error(`Could not read script output file: ${(e as Error).message}`));\n return;\n }\n try { unlinkSync(tmpFile); } catch {}\n if (code !== 0) { reject(new Error(`claude setup-token exited with code ${code}. ${OAUTH_HINT}`)); return; }\n // The token — and ONLY the token — is rendered in yellow (24-bit fg\n // 255,193,7); every other character on screen is a different color. So we do\n // NOT parse the token's shape at all: no `sk-ant-oat01` match, no\n // `[A-Za-z0-9_-]` class, no length check. Every such regex matches forward\n // until the first byte it doesn't expect and then STOPS — and the terminal\n // injects stray control bytes mid-token at each line-wrap, so any shape regex\n // truncates there (that bug stored 90 of 108 chars and 401'd every grade).\n //\n // Instead: a tiny ANSI SGR foreground-color state machine. Walk the output,\n // track the current fg color, and collect EVERY printable character emitted\n // while fg is the token-yellow — across as many wraps/spans as it takes.\n // Whitespace/control bytes the wrap injects are simply not collected; ANSI\n // escapes are consumed to update color, never emitted. What remains is the\n // entire yellow run = the whole token, however long it is.\n const TOKEN_YELLOW = '2;255;193;7';\n let fg = 'default';\n let token = '';\n let i = 0;\n while (i < raw.length) {\n if (raw[i] === '\\x1B') {\n // SGR (\"…m\"): update the foreground-color state.\n const sgr = /^\\x1B\\[([0-9;?]*)m/.exec(raw.slice(i));\n if (sgr) {\n const codes = sgr[1].split(';').map((n) => Number(n) || 0);\n for (let k = 0; k < codes.length; k++) {\n const c = codes[k];\n if (c === 0 || c === 39) fg = 'default';\n else if (c === 38 && codes[k + 1] === 2) { fg = `2;${codes[k + 2]};${codes[k + 3]};${codes[k + 4]}`; k += 4; }\n else if (c === 38 && codes[k + 1] === 5) { fg = `5;${codes[k + 2]}`; k += 2; }\n else if ((c >= 30 && c <= 37) || (c >= 90 && c <= 97)) fg = `basic;${c}`;\n // other codes (bold, bg, underline, …) don't change the fg color\n }\n i += sgr[0].length;\n continue;\n }\n // Any other CSI or OSC escape: consume it without touching color.\n const csi = /^\\x1B\\[[0-9;?]*[ -/]*[@-~]/.exec(raw.slice(i));\n const osc = /^\\x1B\\][^\\x07]*(?:\\x07|\\x1B\\\\)/.exec(raw.slice(i));\n if (csi) { i += csi[0].length; continue; }\n if (osc) { i += osc[0].length; continue; }\n i += 1;\n continue;\n }\n // A printable byte: collect it iff fg is the token-yellow and it isn't a\n // whitespace/control byte (the wrap injects spaces/newlines into the run).\n const ch = raw[i];\n if (fg === TOKEN_YELLOW && !/[\\s\\x00-\\x1F\\x7F]/.test(ch)) token += ch;\n i += 1;\n }\n // The ONLY guard — not a length/shape check, just \"did we see any yellow at\n // all\". An empty capture means color wasn't emitted, not a short token.\n if (!token) {\n // Two distinct causes share this branch. A substantial capture with no\n // yellow run = the terminal didn't emit color. A near-empty capture =\n // setup-token finished without ever rendering a token, i.e. the OAuth\n // approval never came back (same root cause as the non-zero-exit path).\n const reason = raw.length < 200\n ? OAUTH_HINT\n : `is the terminal emitting color? (captured ${raw.length}b but no token-yellow run)`;\n reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));\n return;\n }\n resolve(token);\n });\n });\n}\n","/**\n * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Ensure user is logged in (has JWT + user_id + org_id in ~/.synkro/credentials.json).\n * 2. Check if GitHub is connected via WorkOS Pipes.\n * 3. If not, get Pipes OAuth URL and open browser for the user to authorize.\n * 4. Poll until GitHub connection is established.\n * 5. Run `claude setup-token` to get Claude Code OAuth token.\n * 6. List accessible repos via GitHub API (using Pipes token).\n * 7. Interactive multi-select.\n * 8. Push secrets (SYNKRO_API_KEY + CLAUDE_CODE_OAUTH_TOKEN) to each repo.\n * 9. Write .github/workflows/synkro.yml to the local repo if cwd is one.\n */\nimport { createInterface } from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken, getUserInfo } from '../auth/stub.js';\nimport { captureClaudeSetupToken } from '../local-cc/setupToken.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfig(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nasync function prompt(rl: ReturnType<typeof createInterface>, q: string, opts: { silent?: boolean } = {}): Promise<string> {\n if (opts.silent) {\n process.stdout.write(q);\n const wasRaw = (process.stdin as any).isRaw;\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(true);\n return await new Promise<string>((resolve) => {\n let chunk = '';\n const onData = (data: Buffer) => {\n const s = data.toString('utf-8');\n if (s === '\\r' || s === '\\n' || s === '\\r\\n') {\n process.stdin.removeListener('data', onData);\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(wasRaw ?? false);\n process.stdout.write('\\n');\n resolve(chunk);\n return;\n }\n if (s === '\u0003') process.exit(130);\n if (s === '' || s === '\\b') { chunk = chunk.slice(0, -1); return; }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n switch (os) {\n case 'darwin': bin = 'open'; args = [url]; break;\n case 'win32': bin = 'cmd'; args = ['/c', 'start', '', url]; break;\n default: bin = 'xdg-open'; args = [url]; break;\n }\n execFile(bin, args, () => {});\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nasync function apiCall<T = any>(gatewayUrl: string, jwt: string, path: string, opts: RequestInit = {}): Promise<T> {\n const resp = await fetch(`${gatewayUrl}${path}`, {\n ...opts,\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n ...(opts.headers || {}),\n },\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`API ${resp.status}: ${text.slice(0, 200)}`);\n }\n return resp.json() as Promise<T>;\n}\n\n/**\n * Connect GitHub via WorkOS Pipes OAuth. Returns the token if connected, null if user\n * declines or times out. Exported so `install` can embed this in its flow.\n */\nexport async function connectGitHub(gatewayUrl: string, jwt: string, opts: { silent?: boolean } = {}): Promise<string | null> {\n // Check if already connected\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log(' ✓ GitHub already connected via Synkro.');\n return result.token;\n }\n } catch {}\n\n // Get Pipes OAuth URL and open browser\n if (!opts.silent) console.log(' Opening browser to authorize GitHub...');\n try {\n const authResp = await apiCall<{ url: string }>(\n gatewayUrl, jwt, '/api/pipes-widget/authorize/github', { method: 'POST', body: '{}' },\n );\n openBrowser(authResp.url);\n if (!opts.silent) console.log(' Waiting for authorization...');\n } catch (err) {\n if (!opts.silent) console.error(` Failed to start GitHub authorization: ${(err as Error).message}`);\n return null;\n }\n\n // Poll until connected (max 2 minutes)\n const deadline = Date.now() + 120_000;\n while (Date.now() < deadline) {\n await sleep(2000);\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log('\\n ✓ GitHub connected!');\n return result.token;\n }\n } catch {}\n if (!opts.silent) process.stdout.write('.');\n }\n if (!opts.silent) console.error('\\n Timed out waiting for GitHub authorization.');\n return null;\n}\n\nexport interface SetupGithubOptions {\n nonInteractive?: boolean;\n githubToken?: string;\n skipClaudeToken?: boolean;\n}\n\nexport async function setupGithubCommand(opts: SetupGithubOptions = {}): Promise<void> {\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro-cli login` first.');\n process.exit(1);\n }\n const config = readConfig();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const jwt = getAccessToken();\n if (!jwt) {\n console.error('Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.');\n process.exit(1);\n }\n\n // ── 1. Mint CI API key ──────────────────────────────────────────────\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const minted = await apiCall<{ api_key: string; expires_at: string }>(\n gatewayUrl, jwt, '/api/v1/cli/ci-api-key', { method: 'POST', body: '{}' },\n );\n synkroCiApiKey = minted.api_key;\n console.log(` ✓ Issued CI key (${synkroCiApiKey.slice(0, 18)}…), expires ${minted.expires_at.slice(0, 10)}`);\n } catch (err) {\n console.error(`Failed to mint CI API key: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // ── 2. Get GitHub token via WorkOS Pipes ────────────────────────────\n let ghToken: string;\n\n if (opts.githubToken) {\n ghToken = opts.githubToken;\n } else if (opts.nonInteractive) {\n // In non-interactive mode (CI), try Pipes first, fall back to gh CLI\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n ghToken = result.token;\n } else {\n throw new Error('not connected');\n }\n } catch {\n try {\n ghToken = execSync('gh auth token', { encoding: 'utf-8', timeout: 5000 }).trim();\n } catch {\n console.error('GitHub not connected. Run `synkro-cli setup-github` interactively to connect.');\n return;\n }\n }\n } else {\n // Interactive mode — use Pipes OAuth\n console.log('\\nConnecting to GitHub...');\n const token = await connectGitHub(gatewayUrl, jwt);\n if (!token) {\n console.error('GitHub connection failed. Try again.');\n process.exit(1);\n }\n ghToken = token;\n console.log();\n }\n\n // ── 3. Claude Code OAuth token ──────────────────────────────────────\n let claudeToken: string | undefined;\n if (!opts.skipClaudeToken) {\n console.log('Generating Claude Code OAuth token...');\n console.log(' A browser window will open — authorize with your Claude account.\\n');\n try {\n claudeToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(`Failed to get Claude token: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token received from `claude setup-token`. Expected sk-ant-oat01-...');\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n console.log(' Validating token...');\n try {\n const validateResult = execSync(\n 'claude --print --output-format json \"say ok\"',\n { env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken }, encoding: 'utf-8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] },\n );\n const result = JSON.parse(validateResult);\n if (result.is_error) throw new Error(result.result || 'auth failed');\n console.log(' ✓ Token validated.\\n');\n } catch (err) {\n console.error(`Token validation failed: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n }\n\n // ── 4. Select repos ─────────────────────────────────────────────────\n let selected: Array<{ owner: string; repo: string; full_name: string }>;\n if (opts.nonInteractive) {\n let currentFullName: string | null = null;\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const m = remoteUrl.match(/(?:github\\.com)[:/](.+?)(?:\\.git)?$/);\n if (m) currentFullName = m[1];\n } catch {}\n if (!currentFullName) {\n console.warn(' ⚠ Not in a GitHub repo. Skipping PR scan setup.');\n return;\n }\n const [owner, repo] = currentFullName.split('/');\n selected = [{ owner, repo, full_name: currentFullName }];\n console.log(` Auto-selected repo: ${currentFullName}`);\n } else {\n console.log('Fetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken! });\n if (repos.length === 0) {\n console.error('No accessible repos found. Check your GitHub permissions.');\n process.exit(1);\n }\n console.log(`\\nFound ${repos.length} accessible repo(s):\\n`);\n repos.slice(0, 100).forEach((r, i) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n const rl2 = createInterface({ input, output });\n const selectionRaw = await prompt(rl2, 'Select repos to enable (comma-separated numbers, e.g. 1,3,5): ');\n const selectedIdx = selectionRaw\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl2.close();\n process.exit(1);\n }\n selected = selectedIdx.map((i) => repos[i]);\n console.log(`\\nWill push secrets to ${selected.length} repo(s):`);\n for (const r of selected) console.log(` • ${r.full_name}`);\n console.log();\n const confirm = (await prompt(rl2, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl2.close();\n process.exit(0);\n }\n rl2.close();\n }\n\n // ── 5. Push secrets ─────────────────────────────────────────────────\n console.log();\n for (const r of selected) {\n process.stdout.write(`Pushing secrets to ${r.full_name}... `);\n try {\n await pushSecretsToRepo(\n { token: ghToken! },\n r.owner,\n r.repo,\n {\n claudeCodeOauthToken: claudeToken,\n synkroApiKey: synkroCiApiKey,\n },\n );\n console.log('✓');\n } catch (err) {\n console.log(`✗ (${(err as Error).message})`);\n }\n }\n\n // ── 6. Write workflow file ──────────────────────────────────────────\n console.log();\n const gitRoot = findGitRoot(process.cwd());\n if (gitRoot) {\n const written = writeWorkflowFile(gitRoot);\n if (written) {\n console.log(`Wrote workflow: ${written}`);\n console.log('Commit and push it to enable PR scanning.');\n }\n } else {\n console.log('Not in a git repo. To enable scanning, add this file to your repo:');\n console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);\n console.log(` Content: run \\`synkro-cli setup-github\\` from inside a repo to write it automatically`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (Claude Code, Cursor), drops hook scripts in\n * ~/.synkro/hooks/, writes settings.json hook entries, fetches the latest\n * Edit/Write prompt from the gateway and inlines it into settings, writes\n * config.env. Triggers `synkro login` if not already authed.\n */\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, readdirSync, unlinkSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { createInterface } from 'node:readline';\nimport { createHash } from 'node:crypto';\nimport { detectAgents, type DetectedAgent } from '../installer/agentDetect.js';\nimport { installCCHooks, uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks, uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig, uninstallMcpConfig, uninstallCursorMcpConfig, uninstallClaudeDesktopMcpConfig } from '../installer/mcpConfig.js';\nimport { resolveSkillPaths } from '../installer/skillParser.js';\nimport { emit, setTelemetryState } from '../telemetry/index.js';\nimport { STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_BASH_FOLLOWUP_TS, STUB_AGENT_JUDGE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_INSTALL_SCAN_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_MCP_GATE_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS } from '../installer/hookScriptsTs.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo, ensureValidToken, refreshToken } from '../auth/stub.js';\nimport { promptRepoConnection } from './repoConnect.js';\nimport { setApiBaseUrl, listProjects } from '../api/projects.js';\n// connectGitHub import removed — GitHub PR scanning is hidden; the install no\n// longer connects GitHub. setupGithub.ts is kept for when it's re-enabled.\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { claudeDesktopInstalled } from '../installer/claudeDesktopTap.js';\nimport { dockerInstall, waitForContainerReady, waitForWorkersReady, splitWorkers, resolveGraderPool, poolLabel, dockerStatus, dockerSafeStop, dockerRemove, type WorkerProvider } from '../local-cc/dockerInstall.js';\nimport { captureClaudeSetupToken } from '../local-cc/setupToken.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: string;\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst HOOKS_DIR = join(SYNKRO_DIR, 'hooks');\nconst BIN_DIR = join(SYNKRO_DIR, 'bin');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\n// Hooks are ALWAYS thin stubs now — the legacy self-contained \"full\" hooks were\n// removed once cloud grading moved server-side (stub → gateway /grade/submit →\n// scanRouter). Kept as a function so callers/config stay stable.\nfunction resolvePersistedHookMode(): 'stub' {\n return 'stub';\n}\n\nconst MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline';\n\nconst HOME = homedir();\nconst TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');\nconst PORT = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\nconst URL = \\`http://127.0.0.1:\\${PORT}\\`;\n\n// Re-read the JWT on EVERY request, not once at spawn: a \\`synkro restart\\` rotates\n// the token, and a proxy that cached the old one would 401 every call until it\n// was respawned (a manual /mcp).\nfunction loadToken() {\n try { return readFileSync(TOKEN_PATH, 'utf-8').trim(); } catch { return ''; }\n}\nfunction write(obj) { try { process.stdout.write(JSON.stringify(obj) + '\\\\n'); } catch {} }\n\n// Answer \\`initialize\\` LOCALLY without touching the backend. Claude Code latches an\n// MCP server as failed — forcing a manual /mcp — only when the child process exits\n// or the initialize handshake fails. A \\`synkro restart\\` takes the backend down for\n// ~30-60s, so if CC (re)connects in that window a forwarded initialize would fail\n// and the server would be dead until /mcp. Replying here makes the handshake\n// succeed unconditionally; tools/list and tools/call still hit the backend.\nfunction localInitialize(msg) {\n return {\n jsonrpc: '2.0',\n id: msg.id,\n result: {\n protocolVersion: (msg && msg.params && msg.params.protocolVersion) || '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'synkro-guardrails-local', version: '1.0.0' },\n },\n };\n}\n\n// Cap a single JSON-RPC line. This proxy only carries small tool-call envelopes;\n// refusing oversized input keeps a malicious or runaway client from buffering\n// unbounded memory (CWE-400).\nconst MAX_LINE_BYTES = 4 * 1024 * 1024;\n\nconst rl = createInterface({ input: process.stdin, terminal: false });\n\nrl.on('line', async (line) => {\n if (!line.trim()) return;\n if (line.length > MAX_LINE_BYTES) return;\n let msg;\n try { msg = JSON.parse(line); } catch { return; }\n // Notifications carry no id and need no reply.\n if (msg.id == null && typeof msg.method === 'string' && msg.method.startsWith('notifications/')) return;\n\n // initialize: try the backend briefly for the real instructions, but ALWAYS\n // fall back to a local reply so the handshake can never block or fail.\n if (msg.method === 'initialize') {\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': \\`Bearer \\${loadToken()}\\` },\n body: line,\n signal: AbortSignal.timeout(2500),\n });\n if (resp.ok) { process.stdout.write((await resp.text()) + '\\\\n'); return; }\n } catch {}\n write(localInitialize(msg));\n return;\n }\n\n // tool calls: forward, allowing a generous per-attempt window for slow calls\n // (create + decompose). If the backend is UNREACHABLE (mid-restart), retry only\n // briefly, then return a CLEAN JSON-RPC error and move on. Never hold the request\n // open for tens of seconds — a stdio request held that long wedges CC's MCP\n // client and every later call queues behind it. A returned error keeps the\n // client responsive and the server connected; the agent just retries.\n const REFUSED_BUDGET_MS = 4000;\n const deadline = Date.now() + REFUSED_BUDGET_MS;\n let attempt = 0;\n while (true) {\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': \\`Bearer \\${loadToken()}\\` },\n body: line,\n signal: AbortSignal.timeout(30000),\n });\n if (resp.status === 204) return;\n process.stdout.write((await resp.text()) + '\\\\n');\n return;\n } catch (err) {\n // A 30s timeout means the backend was UP but slow — don't spin. ECONNREFUSED /\n // fetch failed means it's unreachable → retry briefly within the budget.\n const name = err && err.name;\n const isTimeout = name === 'TimeoutError' || name === 'AbortError';\n if (!isTimeout && Date.now() < deadline) {\n attempt++;\n await new Promise((r) => setTimeout(r, Math.min(500, 150 * attempt)));\n continue;\n }\n if (msg.id != null) {\n write({\n jsonrpc: '2.0',\n id: msg.id,\n error: { code: -32603, message: 'Synkro MCP backend unavailable (container may be restarting) — retry in a moment' },\n });\n }\n return;\n }\n }\n});\n`;\n\ninterface InstallOptions {\n gatewayUrl?: string; // override default\n apiKey?: string; // skip prompting if provided (for tests/CI)\n skipAuth?: boolean;\n noMcp?: boolean; // skip registering the guardrails MCP server\n force?: boolean; // bypass the \"already installed\" short-circuit\n linkRepo?: boolean; // auto-link current repo (non-interactive)\n cursorApiKey?: string; // Cursor API key for the cursor grader pool\n hookMode?: 'full' | 'stub'; // 'stub' = thin pipe hooks; logic in the container\n byokGateway?: boolean; // legacy: no-container BYOK grading via the gateway\n}\n\n// Accept a gateway URL only if it's a plain http(s) URL. The published CLI is\n// invoked from arbitrary cwds where a developer's monorepo .env / op://\n// reference / direnv export may have poisoned SYNKRO_GATEWAY_URL with a value\n// the CLI can't actually call (e.g. \"op://dev/synkro/gateway-url\"). Silently\n// ignoring those falls through to the prod default so customers never have to\n// wrestle with shell hygiene before `synkro install` works.\nfunction sanitizeGatewayCandidate(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n return /^https?:\\/\\//.test(raw) ? raw : undefined;\n}\n\nexport function parseArgs(argv: string[]): InstallOptions {\n const opts: InstallOptions = {};\n for (const a of argv) {\n if (a.startsWith('--api-key=')) opts.apiKey = a.slice('--api-key='.length);\n else if (a.startsWith('--gateway=')) opts.gatewayUrl = a.slice('--gateway='.length);\n else if (a.startsWith('--cursor-api-key=')) opts.cursorApiKey = a.slice('--cursor-api-key='.length);\n else if (a === '--skip-auth') opts.skipAuth = true;\n else if (a === '--no-mcp') opts.noMcp = true;\n else if (a === '--force' || a === '-f') opts.force = true;\n else if (a === '--link-repo') opts.linkRepo = true;\n else if (a === '--byok-gateway') opts.byokGateway = true;\n }\n // Hooks are always thin stubs now (the legacy \"full\" hooks were removed); the\n // --stub/--mode/--legacy flags are accepted-and-ignored for backward compat.\n if (!opts.gatewayUrl) {\n const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);\n if (fromEnv) opts.gatewayUrl = fromEnv;\n }\n // NOTE: we deliberately do NOT pick up SYNKRO_API_KEY from process.env. The\n // root .env / global env may contain a stale or unrelated SYNKRO_API_KEY.\n // The legitimate sources are: --api-key flag, or fresh OAuth via authenticate().\n return opts;\n}\n\n// Ask the user which detected agents they want guardrails installed for.\n// If only one is detected, no prompt — return as-is. If multiple, show a\n// picker that accepts: \"1\" (first), \"2\" (second), \"3\"/\"both\"/\"all\"/empty\n// (everything). Anything else re-prompts.\nasync function promptAgentSelection<T extends { kind: string; name: string }>(\n detected: T[],\n): Promise<T[]> {\n if (detected.length <= 1) return detected;\n\n console.log('Multiple coding agents detected. Which ones do you want Synkro guardrails installed for?');\n detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));\n console.log(` ${detected.length + 1}. Both / all (default)`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (): Promise<T[]> => new Promise((resolve) => {\n rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {\n const t = answer.trim().toLowerCase();\n if (t === '' || t === String(detected.length + 1) || t === 'both' || t === 'all') {\n rl.close();\n return resolve(detected);\n }\n const n = parseInt(t, 10);\n if (Number.isInteger(n) && n >= 1 && n <= detected.length) {\n rl.close();\n return resolve([detected[n - 1]]);\n }\n console.log('Invalid choice. Try again.');\n resolve(ask());\n });\n });\n return ask();\n}\n\n// Collect the Cursor API key when the install includes a Cursor worker pool.\n// Sources, in order: --cursor-api-key flag, SYNKRO_CURSOR_API_KEY env, then\n// an interactive paste prompt. An already-configured key is kept only if\n// Cursor still accepts it.\nasync function promptCursorApiKey(opts: InstallOptions): Promise<void> {\n const { cursorApiKeyConfigured, writeCursorApiKey, validateCursorApiKey } = await import('../local-cc/macKeychain.js');\n if (cursorApiKeyConfigured()) {\n // A key file exists — confirm Cursor still accepts it before trusting it.\n // Only a definitive rejection (false) re-prompts; a network blip (null)\n // leaves the working key alone.\n const valid = await validateCursorApiKey();\n if (valid !== false) {\n console.log(' ✓ Cursor API key validated.');\n return;\n }\n console.warn(' ⚠ The saved Cursor API key was rejected by Cursor (revoked or expired).');\n console.warn(' Paste a fresh key below, or press Enter to skip (Cursor workers stay idle).');\n // fall through to re-source / re-prompt the key\n }\n\n const provided = (opts.cursorApiKey || process.env.SYNKRO_CURSOR_API_KEY || '').trim();\n if (provided) {\n writeCursorApiKey(provided);\n console.log(' ✓ Cursor API key saved to ~/.synkro/cursor-creds/api-key');\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const key = await new Promise<string>((resolve) => {\n rl.question(\n 'Cursor grading needs a Cursor API key (cursor.com → Settings → API Keys).\\n' +\n 'Paste it now, or press Enter to skip (Cursor workers stay idle until set): ',\n (answer) => { rl.close(); resolve(answer.trim()); },\n );\n });\n if (key) {\n writeCursorApiKey(key);\n console.log(' ✓ Cursor API key saved.');\n } else {\n console.log(' ⚠ Skipped — Cursor workers will be idle. Re-run install or pass --cursor-api-key=… later.');\n }\n}\n\n// ONE deployment choice: WHERE the grading worker container runs.\n// local — a container on THIS machine (Docker); storage = local PGLite.\n// cloud — a private PER-ORG container hosted by Synkro (Cloudflare Containers);\n// storage = Synkro Timescale; authed by the org's own setup-token.\n// BYOK applies to both (local = your keychain Claude; cloud = your setup-token).\n// Default — and the non-TTY answer — is local. The legacy no-container BYOK-\n// gateway grading path is still reachable via `--byok-gateway` or an existing\n// .synkro with `mode = \"byok\"`.\n// Ask where grading runs — local (Docker container on this machine) vs cloud (CF container).\n// `current` is the default (from synkro.toml / config.env): a bare Enter keeps it, and a\n// non-interactive shell keeps it without prompting. Asked on EVERY install/restart — the deploy\n// target is an explicit choice, not something silently inferred from the toml (which confused\n// local-vs-cloud). The toml still holds local config details; this just picks the target.\nasync function promptDeployLocation(current: 'local' | 'cloud' = 'local'): Promise<'local' | 'cloud'> {\n if (!process.stdin.isTTY) return current;\n const other = current === 'cloud' ? 'local' : 'cloud';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should Synkro run?\\n' +\n ' local — a grading container on this machine (Docker)\\n' +\n ' cloud — a private container hosted by Synkro\\n' +\n `Both use your own Claude key. Choose [${current}] / ${other}: `,\n (answer) => {\n rl.close();\n const a = answer.trim().toLowerCase();\n resolve(a === 'cloud' ? 'cloud' : a === 'local' ? 'local' : current);\n },\n );\n });\n}\n\n// Harness-aware transcript-import consent. Offers to import the history of the\n// agent(s) the user actually selected — not always Claude Code. With both\n// selected, the user picks which source(s) to pull.\nasync function promptTranscriptSources(wantCC: boolean, wantCursor: boolean): Promise<{ cc: boolean; cursor: boolean }> {\n if (!wantCC && !wantCursor) return { cc: false, cursor: false };\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));\n try {\n if (wantCC && wantCursor) {\n console.log('Import and embed past session history? This indexes sessions so Ask Synkro');\n console.log('can answer questions about your patterns and the dashboard shows full history.');\n console.log(' 1. Cursor only');\n console.log(' 2. Claude Code only');\n console.log(' 3. Both');\n console.log(' n. Skip');\n const a = await ask('Choose [1/2/3/n] (default: 3): ');\n if (a === 'n' || a === 'no') return { cc: false, cursor: false };\n if (a === '1') return { cc: false, cursor: true };\n if (a === '2') return { cc: true, cursor: false };\n return { cc: true, cursor: true }; // '', '3', or anything else → both\n }\n const which = wantCursor ? 'Cursor' : 'Claude Code';\n const a = await ask(\n `Import and embed your ${which} session history?\\n` +\n 'This indexes past sessions so Ask Synkro can answer questions about your\\n' +\n 'coding patterns and the dashboard shows full history. (Y/n) ',\n );\n const yes = a === '' || a === 'y' || a === 'yes';\n return { cc: wantCC && yes, cursor: wantCursor && yes };\n } finally {\n rl.close();\n }\n}\n\n// Opt-in: connect a Codex (ChatGPT subscription) session for cloud grading. The\n// browser approval is the user's consent; the session is stored + refreshed\n// internally by Synkro (never a public endpoint). Non-TTY defaults to skip.\nasync function promptCodexCloudSetup(): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Connect a Codex (ChatGPT subscription) session for cloud grading? This opens a\\n' +\n 'browser to authorize; your subscription is stored securely and refreshed for you. (y/N) ',\n (answer) => {\n rl.close();\n const a = answer.trim().toLowerCase();\n resolve(a === 'y' || a === 'yes');\n },\n );\n });\n}\n\nconst OFFSETS_DIR = join(SYNKRO_DIR, '.transcript-offsets');\n\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n mkdirSync(OFFSETS_DIR, { recursive: true });\n mkdirSync(join(SYNKRO_DIR, 'sessions'), { recursive: true });\n}\n\nexport function writeHookScripts(): {\n bashScript: string;\n skillJudgeScript: string;\n cursorSkillJudgeScript: string;\n bashFollowupScript: string;\n editPrecheckScript: string;\n cwePrecheckScript: string;\n cvePrecheckScript: string;\n planJudgeScript: string;\n agentJudgeScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n transcriptSyncScript: string;\n userPromptSubmitScript: string;\n installScanScript: string;\n cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\n cursorAgentCaptureScript: string;\n taskActivateIntentScript: string;\n mcpGateScript: string;\n} {\n // Source is inlined at build time via tsup's `define` (see tsup.config.ts).\n // Reading it from disk doesn't work after npm publish because packages/api\n // isn't shipped with the CLI tarball.\n const installExtractCorePath = join(HOOKS_DIR, 'installExtractCore.ts');\n\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.ts');\n const skillJudgeScriptPath = join(HOOKS_DIR, 'cc-skill-judge.ts');\n const cursorSkillJudgePath = join(HOOKS_DIR, 'cursor-skill-judge.ts');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.ts');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.ts');\n const cwePrecheckScriptPath = join(HOOKS_DIR, 'cc-cwe-precheck.ts');\n const cvePrecheckScriptPath = join(HOOKS_DIR, 'cc-cve-precheck.ts');\n const planJudgeScriptPath = join(HOOKS_DIR, 'cc-plan-judge.ts');\n const agentJudgeScriptPath = join(HOOKS_DIR, 'cc-agent-judge.ts');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.ts');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.ts');\n const transcriptSyncScriptPath = join(HOOKS_DIR, 'cc-transcript-sync.ts');\n const userPromptSubmitScriptPath = join(HOOKS_DIR, 'cc-user-prompt-submit.ts');\n const commonScriptPath = join(HOOKS_DIR, '_synkro-common.ts');\n const commonBashScriptPath = join(HOOKS_DIR, '_synkro-common.sh');\n const installScanScriptPath = join(HOOKS_DIR, 'cc-install-scan.ts');\n const cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-capture.ts');\n const cursorAgentCapturePath = join(HOOKS_DIR, 'cursor-agent-capture.ts');\n const mcpStdioProxyPath = join(HOOKS_DIR, 'mcp-stdio-proxy.ts');\n const taskActivateIntentScriptPath = join(HOOKS_DIR, 'cc-task-activate-intent.ts');\n // MCP server governance gate. Always a thin forwarder (the gate logic lives in\n // the server's scanRouter), so it's the stub in both full and stub modes.\n const mcpGateScriptPath = join(HOOKS_DIR, 'cc-mcp-gate.ts');\n\n // ─── Stub mode: write ~3-line delegating stubs + the plumbing common ───\n // Same filenames as full mode (so the CC/Cursor hook registration is\n // unchanged), but every hook just pipes a fixed envelope to the local\n // server's /api/scan/{surface}. No scanning logic lands on disk — all the\n // brains live in the container, updatable via a GHCR push alone.\n const stubCommonPath = join(HOOKS_DIR, '_synkro-stub-common.ts');\n const stubFiles: Array<[string, string]> = [\n [stubCommonPath, STUB_COMMON_TS],\n [bashScriptPath, STUB_BASH_JUDGE_TS],\n [skillJudgeScriptPath, STUB_SKILL_JUDGE_TS],\n [cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],\n [bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],\n [editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],\n [cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],\n [cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],\n [planJudgeScriptPath, STUB_PLAN_JUDGE_TS],\n [agentJudgeScriptPath, STUB_AGENT_JUDGE_TS],\n [stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],\n [sessionStartScriptPath, STUB_SESSION_START_TS],\n [transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],\n [userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],\n [installScanScriptPath, STUB_INSTALL_SCAN_TS],\n [taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS],\n [mcpGateScriptPath, STUB_MCP_GATE_TS],\n [cursorBashJudgePath, STUB_CURSOR_BASH_JUDGE_TS],\n [cursorEditCapturePath, STUB_CURSOR_EDIT_CAPTURE_TS],\n [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS],\n ];\n for (const [p, content] of stubFiles) {\n writeFileSync(p, content, 'utf-8');\n chmodSync(p, 0o755);\n }\n // The MCP stdio proxy is the guardrails MCP server bridge, not hook logic —\n // needed in both modes for the `synkro-guardrails` tools.\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, 'utf-8');\n chmodSync(mcpStdioProxyPath, 0o755);\n // Remove full-mode-only artifacts so a stub install leaves a genuinely\n // minimal hooks dir — no dead 2,500-line common, no install-extract core.\n // (Stubs import only _synkro-stub-common.ts.)\n for (const stale of ['_synkro-common.ts', '_synkro-common.sh', 'installExtractCore.ts']) {\n try { unlinkSync(join(HOOKS_DIR, stale)); } catch {}\n }\n return {\n bashScript: bashScriptPath,\n skillJudgeScript: skillJudgeScriptPath,\n cursorSkillJudgeScript: cursorSkillJudgePath,\n bashFollowupScript: bashFollowupScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n cwePrecheckScript: cwePrecheckScriptPath,\n cvePrecheckScript: cvePrecheckScriptPath,\n planJudgeScript: planJudgeScriptPath,\n agentJudgeScript: agentJudgeScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\n transcriptSyncScript: transcriptSyncScriptPath,\n userPromptSubmitScript: userPromptSubmitScriptPath,\n installScanScript: installScanScriptPath,\n cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\n cursorAgentCaptureScript: cursorAgentCapturePath,\n taskActivateIntentScript: taskActivateIntentScriptPath,\n mcpGateScript: mcpGateScriptPath,\n };\n}\n\n// Sanitize values before writing into config.env — the file is sourced as a\n// shell script, so any unquoted shell metacharacter in a value is dangerous\n// (newlines smuggle new assignments; (){}#`$ etc. let an attacker run code\n// on `source config.env`). Two defenses:\n// 1. Strip non-printable ASCII (drops newlines, tabs, control chars)\n// 2. Wrap the result in single-quotes — single-quoted shell strings are\n// entirely literal, so anything inside is safe regardless of contents.\n// Internal single-quotes are escaped using the standard '\\'' trick.\nfunction sanitizeConfigValue(raw: string | undefined, maxLen = 256): string {\n if (!raw) return '';\n return raw\n .replace(/[^\\x20-\\x7E]/g, '') // drop non-printable (newlines/tabs/control)\n .slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n // Escape any single quote in `value` for inclusion inside a single-quoted\n // shell string: 'foo'\"'\"'bar' renders as foo'bar.\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Resolve the absolute path to the synkro bundle (dist/bootstrap.js) so hook\n// scripts can invoke `node \"$SYNKRO_CLI_BIN\" grade ...` without relying on\n// the user's shell PATH. Returning a single path (vs a command string) keeps\n// the hook invocation a single quoted arg — no shell-injection surface.\n//\n// process.argv[1] is the script the user executed: with the pnpm/npm shim\n// it's the absolute path to dist/bootstrap.js. With `just synkro install`\n// it's also the absolute bundle path (the just recipe runs `node bootstrap.js`).\nfunction resolveSynkroBundle(): string | null {\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) return scriptPath;\n return null;\n}\n\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string; inference?: string; synkroBin?: string | null; transcriptConsent?: boolean; localInference?: boolean; deploymentMode?: 'bare-host' | 'docker'; gradingMode?: 'local' | 'byok'; storageMode?: 'local' | 'cloud'; deployLocation?: 'local' | 'cloud'; hookMode?: 'full' | 'stub' }): void {\n const credsPath = join(SYNKRO_DIR, 'credentials.json');\n const safeGateway = sanitizeConfigValue(opts.gatewayUrl);\n const safeUserId = sanitizeConfigValue(opts.userId);\n const safeOrgId = sanitizeConfigValue(opts.orgId);\n const safeEmail = sanitizeConfigValue(opts.email);\n const safeTier = sanitizeConfigValue(opts.tier ?? 'pro', 32);\n const safeInference = sanitizeConfigValue(opts.inference ?? 'fast', 16);\n // Allow up to 1KB for the bin command since it may be `node /long/path/to/bootstrap.js`.\n const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? '', 1024);\n const lines = [\n '# Synkro CLI config (managed by synkro install)',\n '# JWT auth — the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime',\n '# and send Authorization: Bearer <access_token> on every gateway call.',\n `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,\n `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,\n `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,\n `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,\n `SYNKRO_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\n if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);\n if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);\n if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);\n if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);\n if (opts.transcriptConsent !== undefined) {\n lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? 'yes' : 'no')}`);\n }\n lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? 'yes' : 'no')}`);\n // Persisted so hooks see the same value without shell-sourced env.\n const safeMode = sanitizeConfigValue(opts.deploymentMode ?? 'docker', 16);\n lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);\n // User-owned data axes — where grading runs and where telemetry is stored.\n lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? 'local', 16))}`);\n lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? 'local', 16))}`);\n // Canonical deployment location — where the grading container runs. The two\n // legacy mode fields above are derived from this for back-compat readers.\n lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle(sanitizeConfigValue(opts.deployLocation ?? 'local', 16))}`);\n // Hook delivery mode — always 'stub' now: thin pipe hooks whose scanning logic\n // lives in the container. Emitted for back-compat readers of the config file.\n lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.hookMode ?? 'stub', 8))}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Cloud install: capture the org's Claude setup-token (CLOUD ONLY) and POST it\n * to the gateway, which stores it as the org secret and provisions the per-org\n * Cloudflare Containers grader. The token lives only in the request body —\n * never logged, never written to config/.synkro. Local installs never call this.\n */\nconst CLOUD_JWT_PATH = join(SYNKRO_DIR, '.cloud-jwt');\n\n/** True if a JWT is expired (or unparseable), with a 5-minute safety buffer. */\nfunction jwtExpired(token: string): boolean {\n try {\n const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf-8')) as { exp?: number };\n if (!p?.exp) return true;\n return Date.now() > p.exp * 1000 - 5 * 60 * 1000;\n } catch { return true; }\n}\n\n/**\n * Long-lived (1-year) cloud token for cloud CLI→gateway calls (provision /\n * local↔cloud switch). On first use it mints one via /api/v1/cli/cloud-token\n * (which needs a fresh WorkOS JWT) and stores it in ~/.synkro/.cloud-jwt; on\n * every later call it reuses the stored token while still valid and only\n * re-mints when expired. This is why switching deploy location no longer 401s on\n * the 5-minute WorkOS access token — exactly the failure that bit us re-provisioning.\n */\nexport async function getOrMintCloudToken(gatewayUrl: string): Promise<string> {\n // CWE-918: never fetch a caller-supplied gateway URL without the allowlist check.\n assertGatewayAllowed(gatewayUrl);\n\n let stored = '';\n try { stored = readFileSync(CLOUD_JWT_PATH, 'utf-8').trim(); } catch { /* none yet */ }\n if (stored && !jwtExpired(stored)) return stored;\n\n // Expired/missing → mint a fresh one. Minting requires a real user, so\n // force-refresh the WorkOS token first (the CLI's own expiry check has\n // disagreed with the gateway before, so don't trust isTokenExpired here).\n await refreshToken().catch(() => false);\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated — run `synkro login`');\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/cloud-token`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n });\n if (!resp.ok) {\n const t = await resp.text().catch(() => '');\n throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);\n }\n const { token } = await resp.json() as { token: string };\n writeFileSync(CLOUD_JWT_PATH, token + '\\n', { mode: 0o600 });\n return token;\n}\n\nasync function provisionCloudContainer(opts: {\n gatewayUrl: string; jwt: string;\n orgId?: string; userId?: string; email?: string;\n hasClaudeCode: boolean; hasCursor: boolean;\n}): Promise<void> {\n let setupToken: string;\n try {\n console.log(' Authorize your Claude key for the hosted Synkro worker —');\n console.log(' a browser window will open for approval...\\n');\n setupToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(` ✗ Could not capture Claude setup-token: ${(err as Error).message}`);\n console.error(' Cloud needs a Claude setup-token. Run `claude setup-token` manually, then re-run install.');\n process.exit(1);\n }\n\n // Validate the captured token by ACTUALLY USING IT — the same guard the GitHub\n // path runs (setupGithub.ts). A truncated/wrong-account/no-credits token sails\n // through capture but 401s the moment Claude calls the API; round-tripping it\n // through a real `claude --print` here catches that at the user's machine,\n // BEFORE it's stored in Vault and only fails (silently) inside the container.\n // The container authenticates exactly this way (CLAUDE_CODE_OAUTH_TOKEN), so a\n // pass here means a pass there.\n console.log(' Validating Claude token...');\n // `claude --print` is slow (~10s) and OCCASIONALLY spikes past the deadline — a timeout is\n // flakiness, NOT a bad token (the same install passed once and timed out once). So: retry with\n // a generous deadline, HARD-FAIL only on a REAL auth rejection (is_error), and on a persistent\n // timeout DON'T abort — the cloud smoke-grade below runs THIS token in the actual container\n // (no local login to fall back on) and is the authoritative end-to-end check.\n let validated = false;\n let authRejected: string | null = null;\n let lastErr = '';\n for (let attempt = 1; attempt <= 2 && !validated; attempt++) {\n try {\n const out = execSync('claude --print --output-format json \"say ok\"', {\n env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: setupToken },\n encoding: 'utf-8',\n timeout: 60_000,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const result = JSON.parse(out) as { is_error?: boolean; result?: string };\n if (result.is_error) { authRejected = result.result || 'auth failed'; break; }\n validated = true;\n } catch (err) {\n lastErr = err instanceof Error ? err.message : String(err);\n if (attempt < 2) console.log(` ⏳ validation slow (${lastErr.slice(0, 30)}…) — retrying once...`);\n }\n }\n if (authRejected) {\n console.error(` ✗ Claude token rejected: ${authRejected}`);\n console.error(' Re-run install to re-authorize (wrong account or no API credits) — nothing was stored.');\n process.exit(1);\n }\n if (validated) {\n console.log(' ✓ Claude token validated.\\n');\n } else {\n console.warn(` ⚠ Couldn't confirm the token locally in time (${lastErr.slice(0, 50)}).`);\n console.warn(' Proceeding — the cloud smoke-grade below verifies it end-to-end in the container.\\n');\n }\n\n const repo = detectGitRepo() || undefined;\n const sf = readFullSynkroFile();\n const harness: string[] = [];\n if (opts.hasClaudeCode) harness.push('claude-code');\n if (opts.hasCursor) harness.push('cursor');\n // Honor the worker count from synkro.toml ([grader] claude-code = N); fall back\n // to the env override, then a sane cloud default. This is what the org's\n // container boots — clamped server-side to whatever the instance can hold.\n const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '', 10);\n const total = (sf?.workers?.claude && sf.workers.claude > 0)\n ? sf.workers.claude\n : (Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4);\n // Cursor pool size from synkro.toml ([grader] cursor = N); 0 if not configured.\n const cursorTotal = (sf?.workers?.cursor && sf.workers.cursor > 0) ? sf.workers.cursor : 0;\n // When a cursor pool is configured, ship the org's Cursor API key (written to\n // ~/.synkro/cursor-creds/api-key by promptCursorApiKey) so cloud cursor workers\n // can grade. Only in the body — never logged or written to .synkro config.\n let cursorApiKey = '';\n if (cursorTotal > 0) {\n try { cursorApiKey = readFileSync(join(SYNKRO_DIR, 'cursor-creds', 'api-key'), 'utf-8').trim(); } catch {}\n }\n // Use the long-lived cloud token so this — and especially a later local↔cloud\n // switch — never 401s on an expired 5-min WorkOS token. Falls back to the\n // session JWT if the cloud token can't be obtained.\n let cloudJwt = opts.jwt;\n try { cloudJwt = await getOrMintCloudToken(opts.gatewayUrl); }\n catch (e) { console.warn(` (cloud token unavailable, using session token: ${(e as Error).message})`); }\n\n try {\n const resp = await fetch(`${opts.gatewayUrl}/api/v1/cli/cloud-provision`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${cloudJwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n org_id: opts.orgId,\n user_id: opts.userId,\n email: opts.email,\n harness,\n claude_workers: total,\n cursor_workers: cursorTotal,\n cursor_api_key: cursorApiKey, // never logged; gateway stores it as the org secret\n connected_repo: repo,\n ruleset: readFullSynkroFile()?.ruleset || 'default',\n setup_token: setupToken, // never logged; gateway stores it as the org secret\n }),\n });\n if (!resp.ok) {\n const t = await resp.text().catch(() => '');\n throw new Error(`cloud-provision failed (${resp.status}): ${t.slice(0, 200)}`);\n }\n const out = await resp.json().catch(() => ({})) as { status?: string; container_id?: string; expires_at?: string };\n console.log(` ✓ cloud container provisioned${out.container_id ? ` (${out.container_id})` : ''}`);\n console.log();\n } catch (err) {\n console.error(` ✗ Cloud provisioning failed: ${(err as Error).message}`);\n console.error(' Hooks + MCP are installed; re-run `synkro install` → cloud to retry.');\n console.log();\n } finally {\n setupToken = ''; // clear from memory\n }\n}\n\n/**\n * On a failed smoke grade, fetch the container's per-worker log tails (the actual\n * claude error — auth, crash, etc.) and print them. This is the only way to see\n * the container-internal failure (CF doesn't surface container stdout).\n */\nasync function printWorkerDebug(base: string, jwt: string): Promise<void> {\n try {\n console.warn(' ── container worker logs (the actual claude error) ──');\n const r = await fetch(`${base}/debug`, { headers: { Authorization: `Bearer ${jwt}` }, signal: AbortSignal.timeout(90_000) });\n const d = (await r.json().catch(() => null)) as\n | { grader_error_code?: string | null; grader_error_detail?: string | null; workers?: Array<{ worker: string; logTail: string; channelLog?: string; pane?: string; errorCode?: string }>; sick_pool?: Array<{ kind: string; sickReason: string }> }\n | null;\n if (!d || !Array.isArray(d.workers)) { console.warn(` (worker debug unavailable: HTTP ${r.status})\\n`); return; }\n // Lead with the classified cause if the container flagged one — no need to\n // read the raw screen below.\n if (d.grader_error_code) {\n console.warn(` ▸ DIAGNOSIS [${d.grader_error_code}]: ${d.grader_error_detail || ''}`);\n }\n // Show one worker in full (channel flow is the same across them).\n for (const w of d.workers.slice(0, 2)) {\n console.warn(` [${w.worker}] launcher log:`);\n for (const l of String(w.logTail || '').split('\\n').filter((l) => l.trim()).slice(-6)) console.warn(' ' + l.slice(0, 240));\n if (w.channelLog && !w.channelLog.startsWith('(no channel')) {\n console.warn(` [${w.worker}] channel flow (token + grade delivery):`);\n for (const l of String(w.channelLog).split('\\n').filter((l) => l.trim()).slice(-12)) console.warn(' ' + l.slice(0, 240));\n } else {\n console.warn(` [${w.worker}] channel: ${w.channelLog}`);\n }\n if (w.pane && !w.pane.startsWith('(') ) {\n console.warn(` [${w.worker}] claude screen:`);\n for (const l of String(w.pane).split('\\n').filter((l) => l.trim()).slice(-12)) console.warn(' ' + l.slice(0, 240));\n }\n }\n if (Array.isArray(d.sick_pool) && d.sick_pool.length) {\n console.warn(' sick workers:');\n for (const s of d.sick_pool) console.warn(` ${s.kind}: ${s.sickReason || '(no reason)'}`);\n }\n console.warn('');\n } catch { console.warn(' (could not fetch worker debug)\\n'); }\n}\n\n/**\n * Warm + verify the cloud grader after provisioning: boot the org's container,\n * wait for a healthy Claude worker, then run ONE smoke grade and assert a real\n * <synkro-verdict>. Surfaces a broken grader at install time instead of silently\n * declaring success. Returns true only when grading actually works end-to-end.\n */\nasync function verifyCloudGrader(jwt: string): Promise<boolean> {\n const base = (process.env.SYNKRO_CONTAINERS_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n console.log(' Warming the cloud grader (booting container + checking workers)...');\n\n // 1. Boot + wait for a healthy worker via /diag (the container cold-starts here).\n const deadline = Date.now() + 180_000;\n let healthy = 0;\n while (Date.now() < deadline) {\n try {\n const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt}` }, signal: AbortSignal.timeout(60_000) });\n if (r.ok) {\n const j = (await r.json()) as { claude_code?: { healthy?: number } };\n healthy = j.claude_code?.healthy ?? 0;\n if (healthy >= 1) break;\n }\n } catch { /* still cold-booting */ }\n await new Promise((res) => setTimeout(res, 5000));\n }\n if (healthy < 1) {\n console.warn(' ⚠ cloud grader did not report a healthy worker within ~3m.');\n console.warn(' Grading is NOT verified — check the container or re-run `synkro install`.\\n');\n return false;\n }\n console.log(` ✓ ${healthy} Claude worker(s) healthy`);\n\n // 2. Smoke grade — a known-risky edit must come back as a real verdict.\n // The grade can take a few seconds (and up to 120s on a cold worker), so tick\n // an elapsed-time line while we wait — otherwise it just looks frozen.\n console.log(' Running smoke grade (a worker scores a known-risky edit; up to 120s)...');\n const started = Date.now();\n const ticker = setInterval(() => {\n const secs = Math.round((Date.now() - started) / 1000);\n process.stdout.write(`\\r …grading (${secs}s elapsed) `);\n }, 1000);\n const stopTicker = () => { clearInterval(ticker); process.stdout.write('\\r\\x1b[2K'); };\n try {\n const probe = 'Grade this edit. File: t.py. Change: os.system(request.args[\"cmd\"])';\n const r = await fetch(`${base}/grade/submit`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ role: 'grade-edit', payload: probe, content: probe, agent_kind: 'claude_code' }),\n signal: AbortSignal.timeout(120_000),\n });\n stopTicker();\n const raw = await r.text();\n let body: { result?: string; error?: string; error_code?: string; hint?: string } = {};\n try { body = JSON.parse(raw); } catch { /* non-JSON error page */ }\n if (r.ok && typeof body.result === 'string' && body.result.includes('<synkro-verdict>')) {\n console.log(' ✓ smoke grade passed — cloud grading is live\\n');\n return true;\n }\n // Surface the ACTUAL container error instead of guessing — these distinguish\n // the failure modes (token vs no-worker vs container drop).\n const detail = (body.error || raw || '').trim().slice(0, 300) || '(empty response)';\n console.warn(` ✗ smoke grade FAILED (HTTP ${r.status}): ${detail}`);\n if (body.error_code) {\n // The container classified the failure — show the code + actionable hint and\n // skip the heuristic guessing below.\n console.warn(` ▸ ${body.error_code}: ${body.hint || ''}`);\n } else if (/no grader worker ready/i.test(detail)) {\n console.warn(' → the worker pool never registered (cold-start, or the pool failed to boot).');\n } else if (/connection lost|network|502/i.test(detail)) {\n console.warn(' → the container dropped the connection mid-grade — the claude worker crashed or hung (most often a bad/expired setup-token).');\n } else {\n console.warn(' → the worker returned an error; most often a bad/expired Claude setup-token.');\n }\n if (!body.error_code) console.warn(' Re-run `synkro install` → cloud to re-authorize the hosted worker.\\n');\n await printWorkerDebug(base, jwt);\n return false;\n } catch (e) {\n stopTicker();\n const reason = (e as Error)?.name === 'TimeoutError'\n ? 'the grade never returned within 120s (worker hung — usually claude auth)'\n : `the request failed (${(e as Error)?.message || 'network'})`;\n console.warn(` ✗ smoke grade FAILED: ${reason}.`);\n console.warn(' The container is provisioned but a grade does not complete; re-run `synkro install` → cloud.\\n');\n await printWorkerDebug(base, jwt);\n return false;\n }\n}\n\n/**\n * Read the deploy location the hooks currently route on, from config.env.\n * Defaults to 'local' when unset (the field is only ever written by\n * install/restart).\n */\nfunction readPersistedDeployLocation(): 'local' | 'cloud' {\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);\n if (m?.[1] === 'cloud') return 'cloud';\n }\n } catch { /* default local */ }\n return 'local';\n}\n\n/**\n * Surgically repoint the location keys in config.env, preserving every other\n * line. The hooks read SYNKRO_DEPLOY_LOCATION at call time, so this is what\n * actually reroutes grading between the local container and the CF container.\n */\nfunction updateConfigEnvLocation(location: 'local' | 'cloud'): void {\n if (!existsSync(CONFIG_PATH)) return;\n let env = readFileSync(CONFIG_PATH, 'utf-8');\n const set = (k: string, v: string) => {\n const re = new RegExp(`^${k}=.*$`, 'm');\n const line = `${k}='${v}'`;\n env = re.test(env) ? env.replace(re, line) : `${env.replace(/\\n?$/, '\\n')}${line}`;\n };\n set('SYNKRO_DEPLOY_LOCATION', location);\n set('SYNKRO_STORAGE_MODE', location === 'cloud' ? 'cloud' : 'local');\n writeFileSync(CONFIG_PATH, env, 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Reconcile the deploy location declared in synkro.toml ([grader] location)\n * against what's persisted in config.env, switching local<->cloud when it\n * changed. `synkro restart` calls this so a user can flip the toml and have it\n * picked up without a full reinstall.\n *\n * Returns the resolved location. When it returns 'cloud' the switch is COMPLETE\n * (local container torn down, CF container provisioned, config.env repointed) and\n * the caller must SKIP its local docker steps. When 'local', config.env is local\n * and the caller runs its normal docker (re)start.\n */\n/**\n * (Re)register the guardrails MCP for a deploy location: local → stdio proxy to\n * the local container; cloud → the hosted HTTP endpoint\n * (${gatewayUrl}/api/v1/mcp/guardrails, served by the api worker off Timescale),\n * so the rules/standards server follows the grader on a local<->cloud switch.\n *\n * The MCP JWT is the same long-lived org-scoped mcp:guardrails token for both\n * locations, so we REUSE the one minted at install (read-only — ~/.synkro is\n * never written here). If it's absent we mint one in-memory for the editor\n * config; persisting it is `synkro install`'s job, not this switch's.\n */\nasync function applyMcpConfig(opts: {\n gatewayUrl: string; jwt: string; hasClaudeCode: boolean; hasCursor: boolean; local: boolean;\n}): Promise<void> {\n if (!opts.hasClaudeCode && !opts.hasCursor) return;\n let mcpJwt = '';\n try { mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch { /* mint below */ }\n if (!mcpJwt) {\n // Never log the token or raw fetch errors next to it (CWE-200).\n try {\n const r = await fetch(`${opts.gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${opts.jwt}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (r.ok) mcpJwt = ((await r.json()) as { token: string }).token;\n else console.warn(` ⚠ MCP token mint failed (${r.status}) — run \\`synkro install\\` to set MCP up.`);\n } catch { console.warn(' ⚠ MCP token mint failed (network) — run `synkro install` to set MCP up.'); }\n }\n if (opts.hasClaudeCode) {\n try {\n const mcp = installMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: mcpJwt, local: opts.local });\n console.log(` ✓ Claude Code MCP → ${mcp.url}`);\n } catch (err) { console.warn(` ⚠ Claude Code MCP config failed: ${(err as Error).message}`); }\n }\n if (opts.hasCursor) {\n try {\n const mcp = installCursorMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: opts.local ? '' : mcpJwt, local: opts.local });\n console.log(` ✓ Cursor MCP → ${mcp.url}`);\n } catch (err) { console.warn(` ⚠ Cursor MCP config failed: ${(err as Error).message}`); }\n }\n}\n\nexport async function reconcileDeployLocation(): Promise<{ location: 'local' | 'cloud'; changed: boolean }> {\n const sf = readFullSynkroFile();\n const tomlLoc: 'local' | 'cloud' = sf?.grader.location === 'cloud' ? 'cloud' : 'local';\n const current = readPersistedDeployLocation();\n // Ask on restart too — default to what's actually running (config.env), else the toml. A bare\n // Enter or non-interactive shell keeps the current target (no surprise switches in CI). The\n // choice is authoritative and synced back into the toml so the two never drift.\n const desired = await promptDeployLocation(current || tomlLoc);\n if (desired !== tomlLoc) updateSynkroTomlLocation(desired);\n if (desired === current) return { location: desired, changed: false };\n const hasClaudeCode = sf ? sf.harness.includes('claude-code') : true;\n const hasCursor = sf ? sf.harness.includes('cursor') : false;\n\n console.log(`\\nDeploy location: ${current || '(none)'} → ${desired}\\n`);\n\n // Both directions repoint the MCP (rules/standards) server → resolve auth +\n // gateway once, up front.\n if (!isAuthenticated()) {\n console.log(' Opening browser for Synkro auth...');\n const ok = await authenticate(() => {});\n if (!ok) { console.error(' ✗ authentication failed'); process.exit(1); }\n }\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.error(' ✗ no access token — run `synkro login`'); process.exit(1); }\n const gatewayUrl = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || 'https://api.synkro.sh';\n // CWE-918: reject a hostile SYNKRO_GATEWAY_URL override before we send the JWT\n // (+ setup-token) to it — same allowlist the install path enforces.\n try { assertGatewayAllowed(gatewayUrl); }\n catch (err) { console.error((err as Error).message); process.exit(1); }\n\n if (desired === 'cloud') {\n // local → cloud: gracefully tear down the local grader, provision the CF\n // container, then repoint grading + rules/MCP at the cloud.\n if (dockerStatus().running) {\n console.log(' Stopping local grader container (snapshotting state first)...');\n await dockerSafeStop();\n }\n dockerRemove(); // harmless if already gone\n console.log(' ✓ local container stopped + removed');\n\n let orgId: string | undefined, userId: string | undefined, email: string | undefined;\n try { const info = getUserInfo(); userId = info.id; orgId = info.org_id; email = info.email; } catch { /* unreachable post-auth */ }\n await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });\n updateConfigEnvLocation('cloud');\n await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: false });\n await verifyCloudGrader(token);\n console.log(' ✓ grading + rules/MCP now served from Synkro Cloud\\n');\n } else {\n // cloud → local: repoint grading + MCP to local now; the caller's docker\n // (re)start brings the local grader container (which serves the MCP) up.\n updateConfigEnvLocation('local');\n await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: true });\n console.log(' ✓ switched to local — bringing the local grader + MCP up\\n');\n }\n return { location: desired, changed: true };\n}\n\n/**\n * Recycle the org's cloud grader container (destroy → fresh boot), then warm +\n * verify it. Called by `synkro restart` when already on cloud, so a restart spins\n * a NET-NEW container (picks up a rotated token / new image, clears wedged state).\n */\nexport async function recycleCloudContainer(): Promise<void> {\n if (!isAuthenticated()) { console.error('Not authenticated — run `synkro login`.'); return; }\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.error('No access token — run `synkro login`.'); return; }\n const base = (process.env.SYNKRO_CONTAINERS_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n console.log('Synkro: recycling the cloud grader container (destroy → fresh boot)…');\n try {\n const r = await fetch(`${base}/recycle`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(30_000) });\n if (r.ok) console.log(' ✓ old container destroyed');\n else console.warn(` ⚠ recycle returned HTTP ${r.status} — continuing to warm anyway.`);\n } catch { console.warn(' ⚠ recycle request failed (network) — continuing to warm anyway.'); }\n // Boot the fresh container + confirm it actually grades.\n await verifyCloudGrader(token);\n}\n\n/**\n * Resolve the deployment mode. Live env var wins (lets ops experiment without\n * mutating the persisted config), then the persisted SYNKRO_DEPLOYMENT_MODE\n * from ~/.synkro/config.env, then defaults to docker.\n *\n * Returns the lowercased string for direct comparison.\n */\nfunction resolveDeploymentMode(): 'docker' | 'bare-host' {\n const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();\n if (envOverride === 'bare-host' || envOverride === 'docker') return envOverride;\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n const val = m?.[1]?.toLowerCase();\n if (val === 'bare-host' || val === 'docker') return val;\n }\n } catch { /* fall through to default */ }\n return 'docker';\n}\n\nfunction collectLocalMetadata(includeClaudeCode = true): Record<string, unknown> {\n const meta: Record<string, unknown> = { platform: process.platform };\n try {\n meta.display_name = execSync('git config user.name', { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {}\n try {\n const remote = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remote.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const m = sshMatch || httpMatch;\n if (m) meta.active_repo = m[1];\n } catch {}\n\n if (!includeClaudeCode) return meta;\n\n try {\n meta.cc_version = execSync('claude --version', { encoding: 'utf-8', timeout: 5000 }).trim().split('\\n')[0];\n } catch {}\n\n const claudeDir = join(homedir(), '.claude');\n\n try {\n const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));\n const plugins = Object.keys(settings.enabledPlugins ?? {}).filter(k => settings.enabledPlugins[k]);\n if (plugins.length) meta.enabled_plugins = plugins;\n if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;\n } catch {}\n\n try {\n const mcpCache = JSON.parse(readFileSync(join(claudeDir, 'mcp-needs-auth-cache.json'), 'utf-8'));\n const mcpNames = Object.keys(mcpCache);\n if (mcpNames.length) meta.mcp_servers = mcpNames;\n } catch {}\n\n try {\n const mcpList = execSync('claude mcp list 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });\n const connected = mcpList.split('\\n')\n .filter(l => l.includes('Connected'))\n .map(l => l.split(':')[0].trim())\n .filter(Boolean);\n if (connected.length) meta.mcp_servers_connected = connected;\n } catch {}\n\n try {\n const sessionsDir = join(claudeDir, 'sessions');\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json')).slice(-5);\n for (const f of files) {\n const s = JSON.parse(readFileSync(join(sessionsDir, f), 'utf-8'));\n if (s.version) { meta.cc_version = meta.cc_version || s.version; break; }\n }\n } catch {}\n\n return meta;\n}\n\nasync function fetchUserProfile(gatewayUrl: string, token: string, hasClaudeCode = true): Promise<{ tier: string; inference: string; localInference: boolean; captureDepth: string }> {\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n headers: { 'Authorization': `Bearer ${token}` },\n });\n if (!resp.ok) return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n const data = await resp.json() as { fast_inference?: boolean; plan_tier?: string; local_inference?: boolean; capture_depth?: string };\n\n const meta = collectLocalMetadata(hasClaudeCode);\n fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(meta),\n }).catch(() => {});\n\n return {\n tier: data.plan_tier ?? 'pro',\n inference: data.fast_inference ? 'fast' : 'standard',\n localInference: !!data.local_inference,\n captureDepth: data.capture_depth ?? 'full',\n };\n } catch {\n return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n }\n}\n\n// JWT-only auth — no API key minting. The hook scripts read the JWT from\n// ~/.synkro/credentials.json and send it as Authorization: Bearer <jwt>.\n// Auth middleware on the gateway resolves user/org from JWT claims via JWKS.\n\n// The CLI ships the user's WorkOS Bearer JWT to the gateway URL. If a\n// hostile actor sets --gateway=http://evil.example.com on a user's machine,\n// the JWT lands at the attacker's server. Allow only:\n// - https://*.synkro.sh and synkro.sh apex\n// - http(s)://localhost or 127.0.0.1 (dev)\n// Anything else is rejected before any fetch is attempted.\nfunction assertGatewayAllowed(gatewayUrl: string): void {\n let parsed: URL;\n try { parsed = new URL(gatewayUrl); }\n catch { throw new Error(`Invalid gateway URL: ${gatewayUrl}`); }\n const proto = parsed.protocol;\n const host = parsed.hostname;\n if (proto !== 'http:' && proto !== 'https:') {\n throw new Error(`Gateway URL must be http(s); got ${proto}`);\n }\n const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';\n const isSynkro = host === 'synkro.sh' || host.endsWith('.synkro.sh');\n if (proto === 'http:' && !isLocalhost) {\n throw new Error(`Gateway URL must be HTTPS for non-localhost hosts; got ${gatewayUrl}`);\n }\n if (!isLocalhost && !isSynkro) {\n throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);\n }\n}\n\n// Detect a complete prior install: every hook script present, config.env\n// written, and CC settings.json carries our `__synkro_managed__` markers.\n// MCP registration is opt-out (--no-mcp) so we don't gate on it. Returns\n// true only when every required surface is present so we never short-\n// circuit a partial / broken install.\nfunction isAlreadyInstalled(): boolean {\n const requiredScripts = [\n join(HOOKS_DIR, 'cc-bash-judge.ts'),\n join(HOOKS_DIR, 'cc-bash-followup.ts'),\n join(HOOKS_DIR, 'cc-edit-precheck.ts'),\n join(HOOKS_DIR, 'cc-cve-precheck.ts'),\n join(HOOKS_DIR, 'cc-plan-judge.ts'),\n join(HOOKS_DIR, 'cc-stop-summary.ts'),\n join(HOOKS_DIR, 'cc-session-start.ts'),\n ];\n if (!requiredScripts.every((p) => existsSync(p))) return false;\n if (!existsSync(CONFIG_PATH)) return false;\n\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hooks = settings?.hooks;\n if (!hooks || typeof hooks !== 'object') return false;\n const hasManaged = (kind: string) =>\n Array.isArray(hooks[kind]) &&\n hooks[kind].some((entry: Record<string, unknown>) => entry?.__synkro_managed__ === true);\n if (!hasManaged('PreToolUse')) return false;\n if (!hasManaged('PostToolUse')) return false;\n if (!hasManaged('SessionEnd')) return false;\n if (!hasManaged('SessionStart')) return false;\n } catch {\n return false;\n }\n return true;\n}\n\n\n// Opt-in for shipping local product analytics (events captured locally to\n// ~/.synkro/telemetry.db) up to the gateway. Local capture stays on either\n// way — this only controls remote flush. Non-interactive defaults to YES, in\n// line with the documented behavior in DESIGN.md §9.\nasync function promptTelemetryConsent(): Promise<boolean> {\n if (!process.stdin.isTTY) return true;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const a = await new Promise<string>((res) =>\n rl.question(\n 'Allow Synkro to send local product analytics (events, timings, rule activity)\\n' +\n 'to our gateway? You can inspect data locally with `synkro telemetry stats`. (Y/n) ',\n (ans) => res(ans.trim().toLowerCase()),\n ),\n );\n return a === '' || a === 'y' || a === 'yes';\n } finally {\n rl.close();\n }\n}\n\nexport async function installCommand(opts: InstallOptions = {}): Promise<void> {\n // Synkro anchors all state — projects, telemetry, rulesets — to a repo\n // identity. Without a git repo there is nothing to key that state on, so a\n // repo is mandatory. This is a purely local check (`git remote get-url\n // origin`) — no GitHub login involved.\n if (!detectGitRepo()) {\n console.error('Synkro must be installed inside a git repository.');\n console.error(' `cd` into your project and re-run `synkro install`.');\n process.exit(1);\n }\n\n const gatewayUrl = opts.gatewayUrl\n || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL)\n || 'https://api.synkro.sh';\n\n // Reject hostile gateway overrides before we leak the JWT.\n try {\n assertGatewayAllowed(gatewayUrl);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n // No short-circuit. Plain `synkro install` is always a full refresh —\n // re-pulls the latest container image, re-writes hooks, re-registers the\n // MCP server. Each individual step is idempotent on its own; auth reuses\n // existing valid credentials so the user isn't bounced to the browser\n // unnecessarily.\n console.log('Synkro install starting...\\n');\n\n // 1. Auth via browser OAuth (WorkOS). Persists JWT + refresh_token to\n // ~/.synkro/credentials.json. The hook scripts authenticate against\n // the gateway with `Authorization: Bearer <access_token>` — auth\n // middleware resolves user/org from the JWT claims directly.\n if (!isAuthenticated()) {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n switch (status.phase) {\n case 'starting': console.log(' Starting local callback server...'); break;\n case 'browser-opened': console.log(` Browser opened: ${status.url}`); break;\n case 'waiting': console.log(' Waiting for browser auth to complete...'); break;\n case 'success': console.log(' ✓ Authenticated'); break;\n case 'error': console.error(` ✗ ${status.message}`); break;\n }\n });\n if (!result) {\n console.error('Authentication failed. If you are running a self-hosted dashboard, set SYNKRO_WEB_AUTH_URL to its origin.');\n process.exit(1);\n }\n }\n const token = getAccessToken();\n if (!token) {\n console.error('No access token available after auth.');\n process.exit(1);\n }\n\n // 1b. GitHub PR scanning is hidden for now — the install no longer prompts\n // for it. The code path (connectGitHub / setupGithubCommand below) is kept\n // intact, just unreachable, for when PR scanning is re-enabled.\n const ghToken: string | null = null;\n\n // 1c. Connect repos (local git or GitHub OAuth)\n setApiBaseUrl(`${gatewayUrl}/api`);\n await promptRepoConnection({ linkRepo: opts.linkRepo });\n\n // 2. Detect installed agents + resolve config.\n // If a valid .synkro file exists, skip all interactive prompts and derive\n // agent selection, grading mode, and storage mode from it.\n const existingSynkro = readFullSynkroFile();\n const detected = detectAgents();\n\n let agents: DetectedAgent[];\n let gradingMode: 'local' | 'byok';\n let storageMode: 'local' | 'cloud';\n // The single user-facing axis: where the grading container runs.\n let deployLocation: 'local' | 'cloud' = 'local';\n // Per-source transcript-import consent. Defaults to NO import — embedding past\n // session history is privacy-sensitive and must be explicitly consented (the\n // prompt below sets these). Never import silently.\n let transcriptCC = false;\n let transcriptCursor = false;\n\n if (existingSynkro) {\n // .synkro exists — skip prompts, use its config\n const wantCC = existingSynkro.harness.includes('claude-code');\n const wantCursor = existingSynkro.harness.includes('cursor');\n agents = detected.filter(a =>\n (a.kind === 'claude_code' && wantCC) || (a.kind === 'cursor' && wantCursor)\n );\n if (agents.length === 0 && detected.length > 0) agents = detected;\n gradingMode = existingSynkro.grader.mode === 'byok' ? 'byok' : 'local';\n // The deploy target (local vs cloud) is ALWAYS an explicit choice — even when a synkro.toml\n // exists — so it's never silently inferred (the source of the local-vs-cloud confusion).\n // Default to the toml's [grader] location; a bare Enter (or non-interactive) keeps it. If the\n // user picks a different target we sync it back into synkro.toml below so the two never drift.\n const tomlLocation: 'local' | 'cloud' = existingSynkro.grader.location === 'cloud' ? 'cloud' : 'local';\n deployLocation = await promptDeployLocation(tomlLocation);\n storageMode = deployLocation === 'cloud' ? 'cloud' : 'local';\n if (deployLocation !== tomlLocation) updateSynkroTomlLocation(deployLocation);\n\n console.log(`Using .synkro config:`);\n for (const w of existingSynkro.warnings) console.warn(` ⚠ ${w}`);\n console.log(` harness: ${existingSynkro.harness.join(', ')}`);\n console.log(` grading: ${gradingMode} pool: ${poolLabel(existingSynkro.grader.pool)}`);\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n } else {\n // No .synkro — interactive prompts\n if (detected.length === 0) {\n console.error('No supported coding agents detected. Install Claude Code or Cursor first.');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of detected) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n agents = await promptAgentSelection(detected);\n if (agents.length < detected.length) {\n console.log(`Installing hooks for: ${agents.map(a => a.name).join(', ')}\\n`);\n }\n\n if (opts.byokGateway) {\n // Legacy escape hatch: no container, grading via the gateway with the\n // user's own provider key. Never offered in the prompt.\n gradingMode = 'byok'; storageMode = 'cloud'; deployLocation = 'local';\n console.log(' deploy: legacy BYOK-gateway grading (no container)\\n');\n } else {\n deployLocation = await promptDeployLocation();\n gradingMode = 'local';\n storageMode = deployLocation === 'cloud' ? 'cloud' : 'local';\n console.log(` deploy: ${deployLocation}\\n`);\n }\n }\n\n // Resolve the deployment target. local → local Docker (today's path,\n // unchanged). cloud → a per-org Cloudflare container. The legacy no-container\n // BYOK-gateway path is selected only by --byok-gateway or an old .synkro\n // mode='byok' (never the prompt).\n type DeployTarget = 'local-docker' | 'cloud-container' | 'gateway-byok';\n const target: DeployTarget =\n deployLocation === 'cloud' ? 'cloud-container'\n : (opts.byokGateway || gradingMode === 'byok') ? 'gateway-byok'\n : 'local-docker';\n\n // Transcript-import consent — harness-aware, asked in BOTH paths. A .synkro\n // file configures harness/grading but NOT whether to embed past session\n // history, so importing must still be consented. Interactive → always prompt\n // (which sources). Non-interactive → no import unless explicitly opted in via\n // SYNKRO_IMPORT_TRANSCRIPTS=yes (privacy-safe: never process session data\n // silently).\n {\n const wantCC = agents.some(a => a.kind === 'claude_code');\n const wantCursor = agents.some(a => a.kind === 'cursor');\n if ((wantCC || wantCursor) && process.stdin.isTTY) {\n const src = await promptTranscriptSources(wantCC, wantCursor);\n transcriptCC = src.cc;\n transcriptCursor = src.cursor;\n const chosen = [src.cc && 'Claude Code', src.cursor && 'Cursor'].filter(Boolean).join(' + ');\n console.log(chosen ? ` ✓ Session import: ${chosen}\\n` : ' ✗ Session import skipped\\n');\n } else if ((wantCC || wantCursor) && process.env.SYNKRO_IMPORT_TRANSCRIPTS === 'yes') {\n transcriptCC = wantCC;\n transcriptCursor = wantCursor;\n console.log(' ✓ Session import: SYNKRO_IMPORT_TRANSCRIPTS=yes\\n');\n } else if (!process.stdin.isTTY) {\n console.log(' ✗ Session import skipped (non-interactive; set SYNKRO_IMPORT_TRANSCRIPTS=yes to opt in)\\n');\n }\n }\n\n // Combined consent for config + the ongoing-sync gate (either source on).\n const transcriptConsent = transcriptCC || transcriptCursor;\n\n // Telemetry opt-in (DESIGN.md §9). Local capture stays on either way; this\n // prompt only controls remote flush. setTelemetryState writes both the meta\n // table and config.env so hooks see the same value without a re-source.\n const telemetryConsent = await promptTelemetryConsent();\n await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });\n emit('install', {\n phase: 'started',\n cli_version_to: __SYNKRO_CLI_VERSION__,\n agents_detected: agents.map((a) => a.kind),\n with_github: false,\n with_local_cc: false,\n with_transcript_consent: transcriptConsent,\n duration_ms: 0,\n });\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n // Stub hooks are thin pipes to a LOCAL container on 18931 (all scanning brains\n // — route(), systemMessage shaping, rule fetch — live server-side there). Cloud\n // mode runs NO local container: the per-org grader is remote and the DO exposes\n // only the dispatcher (:8929/submit), not /api/scan/ (:8931). So stub hooks in\n // cloud fail open to `{}` — no systemMessage, no blocking. Full hooks carry the\n // route()+hostedGrade() logic inline (grade → containers.synkro.sh) and fall\n // back to the gateway for config/rules, so they're the ONLY mode that works in\n // cloud. Force it (an explicit --stub can't function against a remote grader).\n // Cloud grading now runs server-side through the org container's scanRouter: the\n // stub posts each surface's ScanEnvelope to the gateway's /grade/submit as role\n // 'scan:<surface>' and gets the shaped decision back (see runStub + the dispatcher's\n // scan:<surface> handler). So stub mode works in cloud too, using the ONE container\n // implementation — cloud is no longer forced onto the legacy self-contained hooks.\n const hookMode = opts.hookMode || resolvePersistedHookMode();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/\\n');\n\n // Kill any stale legacy Python grader daemons so they don't keep handling\n // requests after we've switched to the local-CC channel path.\n for (const mode of ['edit', 'bash']) {\n const pidFile = join(SYNKRO_DIR, 'daemon', mode, 'daemon.pid');\n try {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n if (pid > 0) {\n process.kill(pid, 'SIGTERM');\n console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);\n }\n } catch {}\n }\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n let hasCursor = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n hasClaudeCode = true;\n installCCHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.bashScript,\n skillJudgeScriptPath: scripts.skillJudgeScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n taskActivateIntentScriptPath: scripts.taskActivateIntentScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n skipTranscriptSync: !transcriptConsent,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n } else if (agent.kind === 'cursor') {\n hasCursor = true;\n installCursorHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n skillJudgeScriptPath: scripts.cursorSkillJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Fetch user profile early — we need captureDepth to decide local vs cloud MCP.\n let userId: string | undefined;\n let orgId: string | undefined;\n let email: string | undefined;\n try {\n const info = getUserInfo();\n userId = info.id;\n orgId = info.org_id;\n email = info.email;\n } catch {\n // unreachable — we just authenticated above\n }\n const profile = await fetchUserProfile(gatewayUrl, token, hasClaudeCode);\n // Cloud-only when both axes are cloud: BYOK grading + cloud storage bypass\n // the container's worker pool AND its PGLite, so skip Docker entirely and\n // register the MCP against the cloud endpoint. Any other combination still\n // needs the local container (for grading and/or local storage).\n // Local Docker is the ONLY target that runs a container on this machine.\n // cloud-container provisions a remote per-org CF container; gateway-byok runs\n // no container. Both register the cloud MCP and skip local Docker.\n const useLocalMcp = target === 'local-docker';\n if (target === 'cloud-container') {\n console.log('Cloud setup — Synkro will provision a private hosted container for your org.\\n');\n } else if (target === 'gateway-byok') {\n console.log('Cloud-only setup (BYOK grading via gateway) — skipping the local container.\\n');\n }\n\n // 5c. Register the Synkro Guardrails MCP server in ~/.claude.json.\n // Local mode: point to localhost:8931, start local server, backfill rules.\n // Cloud mode: mint long-lived JWT, point to cloud endpoint.\n if (hasClaudeCode && !opts.noMcp) {\n if (useLocalMcp) {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n let mcpJwt = '';\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n mcpJwt = minted.token;\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), mcpJwt + '\\n', { mode: 0o600 });\n } else {\n console.warn(' ⚠ Could not mint MCP token — local server will reject requests until re-installed.');\n }\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: mcpJwt, local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log();\n } catch (err) {\n console.warn(` ⚠ Local MCP setup failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry.');\n console.log();\n }\n } else {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log(` expires: ${minted.expires_at} (~1 year)`);\n console.log(' (restart any running Claude Code session for it to load)');\n console.log();\n } catch (err) {\n console.warn(` ⚠ MCP registration failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry MCP setup.');\n console.log();\n }\n }\n }\n\n // 5d. Register the Synkro Guardrails MCP server in ~/.cursor/mcp.json.\n if (hasCursor && !opts.noMcp) {\n try {\n if (useLocalMcp) {\n // Ensure JWT exists (may already be minted in 5c for Claude Code)\n const jwtPath = join(SYNKRO_DIR, '.mcp-jwt');\n if (!existsSync(jwtPath)) {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(jwtPath, minted.token + '\\n', { mode: 0o600 });\n }\n }\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: '', local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n } else {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n }\n console.log();\n } catch (err) {\n console.warn(` ⚠ Cursor MCP registration failed: ${(err as Error).message}`);\n console.log();\n }\n }\n\n // 6. Write config.env — hook scripts read SYNKRO_GATEWAY_URL and the\n // credentials path. They auth via Bearer JWT (resolved from the\n // credentials file at runtime so refreshes Just Work).\n const synkroBundle = resolveSynkroBundle();\n // Persist the deployment mode so subsequent installs / hooks see the same\n // value without depending on shell-sourced env.\n const persistedMode = resolveDeploymentMode();\n writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode, gradingMode, storageMode, deployLocation, hookMode });\n console.log(`Wrote config to ${CONFIG_PATH}`);\n console.log(` inference: ${profile.inference} (server-side grading)`);\n if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);\n if (synkroBundle) console.log(` SYNKRO_CLI_BIN=${synkroBundle}`);\n else console.warn(' ⚠ Could not resolve synkro bundle path; hooks will fall back to PATH lookup of `synkro`.');\n\n try {\n const prompts = await fetchJudgePrompts({ gatewayUrl, jwt: token });\n console.log(` prompts: ${prompts.version} (live)`);\n } catch (err) {\n console.warn(` ⚠ Could not cache judge prompts: ${(err as Error).message}`);\n }\n\n // Write synkro.toml to repo root if one doesn't exist yet — LOCAL only. In\n // cloud mode application is fully server-side (org standards + pool live in the\n // dashboard), so there's no repo config to write; the harness enforces by org\n // wherever Synkro is installed.\n if (deployLocation !== 'cloud') {\n writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode, deployLocation });\n } else {\n console.log(' Cloud mode: standards + grader pool are configured in the dashboard');\n console.log(' (Settings → Standards / Pool) — no synkro.toml needed.');\n }\n console.log();\n\n // Tracks whether the cloud grader verified end-to-end (pueue+tmux smoke grade).\n // null = not a cloud install / not checked; false = verified and FAILED.\n let cloudGradeOk: boolean | null = null;\n\n // A Cursor API key is only needed when the grader pool actually runs Cursor\n // WORKERS. Cursor being in `harness` just means Cursor's edits get graded —\n // by whatever pool is configured. With a claude-code pool, Claude Code grades\n // Cursor's edits and no Cursor key is required. Mirror the exact worker\n // resolution used below (explicit counts > pool shorthand > harness) so the\n // prompt fires iff a Cursor worker will exist.\n const graderUsesCursor = (() => {\n if (!hasCursor) return false;\n const sfp = readFullSynkroFile();\n if (sfp && (sfp.workers.claude != null || sfp.workers.cursor != null)) {\n return Math.max(0, Math.floor(sfp.workers.cursor || 0)) > 0;\n }\n const pool = sfp?.grader?.pool || 'auto';\n if (pool === 'cursor') return true;\n if (pool === 'claude_code') return false;\n return hasCursor; // 'auto' → cursor workers only if Cursor is a harness\n })();\n\n // The container provides the grading worker pool and local PGLite — needed\n // for any setup except cloud-only (BYOK grading + cloud storage).\n if (useLocalMcp) {\n const { assertDockerAvailable } = await import('../local-cc/dockerInstall.js');\n try {\n assertDockerAvailable();\n } catch (err) {\n console.error(`\\n✗ ${(err as Error).message}`);\n process.exit(1);\n }\n\n // A Cursor grader worker needs a Cursor API key — collect it before starting\n // the container so the workers come up authenticated. Skipped when the pool\n // is claude-code-only (Claude Code grades Cursor's edits, no key needed).\n if (graderUsesCursor) {\n await promptCursorApiKey(opts);\n }\n\n console.log('Installing Synkro server container...');\n // Read .synkro for worker config: explicit counts > pool > agent detection.\n const sf = readFullSynkroFile();\n for (const w of sf?.warnings || []) console.warn(` ⚠ ${w}`);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {\n claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));\n cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (claudeWorkers + cursorWorkers === 0) { claudeWorkers = 0; cursorWorkers = 8; }\n console.log(` synkro.toml: explicit workers — ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n } else {\n const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const synkroFilePool = sf?.grader?.pool || 'auto';\n let providers: WorkerProvider[] = [];\n if (synkroFilePool === 'cursor') {\n providers = ['cursor'];\n } else if (synkroFilePool === 'claude_code') {\n providers = ['claude_code'];\n } else {\n if (hasClaudeCode) providers.push('claude_code');\n if (hasCursor) providers.push('cursor');\n }\n ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));\n if (synkroFilePool !== 'auto') console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);\n }\n console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n // Connected repo — the container names the seeded app + ruleset after it.\n const connectedRepo = detectGitRepo() || undefined;\n const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } =\n await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);\n console.log(' waiting for container to be ready...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ container ready');\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n try {\n const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpJwt}` },\n body: JSON.stringify({ capture_type: 'healthcheck', event_id: `healthcheck_${Date.now()}` }),\n signal: AbortSignal.timeout(5000),\n });\n if (ingestResp.ok) {\n console.log(' ✓ ingest endpoint verified');\n } else {\n console.warn(` ⚠ ingest endpoint returned ${ingestResp.status} — telemetry spool may not drain.`);\n console.warn(' The .mcp-jwt token may not match the container. Try: synkro uninstall && synkro install');\n }\n } catch {\n console.warn(' ⚠ ingest endpoint unreachable — telemetry spool may not drain.');\n }\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log(' ✓ workers ready');\n await syncSkillFiles();\n // Auto-discover skills the user already has (Claude Code / skills.sh folders)\n // and offer to ingest them — each is security-vetted before any rule is added.\n await discoverAndIngestSkills();\n } else {\n console.warn(' ⚠ workers did not register within 30s — skill sync skipped');\n }\n } else {\n console.error(' ✗ container did not become healthy within 60s');\n console.error(' Run `docker logs synkro-server` to debug.');\n process.exit(1);\n }\n console.log();\n } else if (target === 'cloud-container') {\n // Cloud: capture the org's Claude setup-token (CLOUD ONLY) and hand it to\n // the gateway, which stores it as the org secret and provisions the per-org\n // Cloudflare container. No local container, no keychain, no mounts.\n // Cursor pool? Collect the Cursor API key first so it's bundled into the\n // provision (cloud cursor workers grade with it). Only when a Cursor worker\n // actually exists — a claude-code pool grades Cursor's edits without a key.\n if (graderUsesCursor) await promptCursorApiKey(opts);\n await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });\n // Warm + smoke-test the hosted grader (pueue+tmux channel grade) so a broken\n // container/token surfaces HERE, not silently on the user's first edit. The\n // result gates the final success message below — a failed grade is a failed\n // install, not a clean one.\n cloudGradeOk = await verifyCloudGrader(token);\n // DISABLED: Codex (ChatGPT subscription) cloud grading is not supported yet, so we don't\n // prompt for it during install (it confused users into authorizing a grader we can't use).\n // Re-enable this block once Codex cloud grading ships. The browser approval is the user's\n // consent; the session is stored + refreshed by the internal credential service (never a\n // public endpoint), one per (org, user).\n // if (await promptCodexCloudSetup()) {\n // const { setupCodexCloud } = await import('../local-cc/codexCloudSetup.js');\n // const res = await setupCodexCloud(gatewayUrl, token, (m) => console.log(` ${m}`));\n // if (!res.ok) console.warn(` ⚠ Codex cloud setup skipped: ${res.error}`);\n // }\n }\n\n // 7. Import + embed session transcripts for the consented source(s).\n if (transcriptConsent) {\n const repo = detectGitRepo();\n if (repo) {\n if (storageMode === 'local') {\n // Local: sync into PGLite — the container's embedding model vectors each message.\n try {\n let mcpToken = '';\n try { mcpToken = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (mcpToken) {\n const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n let imported = 0;\n if (transcriptCC && hasClaudeCode) {\n const r = await syncTranscriptsLocal(mcpPort, mcpToken, repo);\n if (r.messages > 0) {\n console.log(` ✓ Imported ${r.sessions} Claude Code sessions (${r.messages} messages) into local store.`);\n imported += r.messages;\n }\n }\n if (transcriptCursor && hasCursor) {\n const r = await syncCursorTranscriptsLocal(mcpPort, mcpToken, repo);\n if (r.messages > 0) {\n console.log(` ✓ Imported ${r.sessions} Cursor sessions (${r.messages} messages) into local store.`);\n imported += r.messages;\n }\n }\n if (imported > 0) {\n console.log(' Embeddings generated. Analyzing for rule suggestions...');\n try {\n const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n signal: AbortSignal.timeout(90000),\n });\n if (suggestResp.ok) {\n const suggestResult = await suggestResp.json() as { suggested?: number; skippedDuplicates?: number };\n if (suggestResult.suggested && suggestResult.suggested > 0) {\n console.log(` ✓ Generated ${suggestResult.suggested} rule suggestions from your session history.`);\n console.log(' Ask Synkro: \"show me suggested rules\" to review them.\\n');\n } else {\n console.log(' No rule suggestions generated yet — more session data needed.\\n');\n }\n }\n } catch {\n console.log(' Rule analysis will run automatically as more sessions are recorded.\\n');\n }\n }\n } else {\n console.warn(' ⚠ Session import skipped — container auth token not found.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Local session import failed: ${(err as Error).message}\\n`);\n }\n } else {\n // Cloud (Timescale via gateway): Claude Code transcripts. Cursor cloud\n // backfill isn't wired yet — Cursor import currently requires local storage.\n if (transcriptCC && hasClaudeCode) {\n try {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(` ✓ Indexed ${ingested} session insights from Claude Code history.`);\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n try {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(` ✓ Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);\n console.log(' Embeddings use your configured inference provider.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\n`);\n }\n }\n if (transcriptCursor && hasCursor) {\n console.log(' ℹ Cursor transcript import requires local storage mode — skipped under cloud storage.\\n');\n }\n }\n }\n }\n\n // 8. PR scan setup (secrets + workflow) — only if GitHub was connected in step 1b\n if (ghToken) {\n const { setupGithubCommand } = await import('./setupGithub.js');\n await setupGithubCommand({ nonInteractive: true, githubToken: ghToken });\n }\n\n // 9. Done — but a cloud install whose grader didn't verify is NOT a success.\n if (cloudGradeOk === false) {\n console.error('');\n console.error('✗ Synkro installed, but the cloud grader is NOT working — grading will fail on your edits.');\n console.error(' The smoke grade above shows why (most often a bad/incomplete Claude setup-token → 401).');\n console.error(' Re-run `synkro install` → cloud to re-authorize with a fresh token.');\n process.exitCode = 1;\n } else {\n console.log('✓ Synkro installed.');\n }\n\n // 10. Claude Desktop observability (macOS) is a deliberate, SEPARATE action —\n // install must return cleanly, not hand off into a long-running foreground MITM\n // tap. So we don't start it here; we just point the user at the command. The tap\n // (session-scoped local proxy → conversations in the dashboard) runs only via\n // `synkro claude-desktop`, where it owns the foreground until Ctrl+C.\n if (process.stdin.isTTY && claudeDesktopInstalled()) {\n console.log('\\n Claude Desktop detected — to monitor its conversations in your dashboard, run: synkro claude-desktop');\n }\n}\n\n/**\n * Identify the git repo `synkro install` is running in. `git` itself walks up\n * from the cwd, so this resolves correctly from any subdirectory or worktree.\n * Tries the `origin` remote (→ owner/repo), then falls back to the repo's\n * top-level directory name. Returns null only when the cwd is not a git repo\n * at all — mirrors the hook's detectRepo() so the seeded project id and the id\n * telemetry lands under always agree.\n */\n// Coerce a single TOML value token (\"local\", 8, true, [\"a\",\"b\"]) to JS.\nfunction parseTomlValue(raw: string): any {\n const v = raw.trim();\n if (v.startsWith('[') && v.endsWith(']')) {\n const inner = v.slice(1, -1).trim();\n if (!inner) return [];\n return inner.split(',').map(s => parseTomlValue(s)).filter(s => s !== '');\n }\n if ((v.startsWith('\"') && v.endsWith('\"')) || (v.startsWith(\"'\") && v.endsWith(\"'\"))) return v.slice(1, -1);\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (/^-?\\d+$/.test(v)) return parseInt(v, 10);\n return v;\n}\n\n// Minimal TOML reader for synkro.toml's fixed, flat schema: top-level\n// scalars/arrays plus the [grader] and [scanning] tables. Not a general TOML\n// parser — we own the writer, so input shape is bounded. Returns the same\n// nested-object shape the old YAML parser did, so downstream consumers are\n// unchanged.\nfunction parseSynkroToml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n let section: string | null = null;\n for (const rawLine of raw.split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n const sec = line.match(/^\\[([^\\]]+)\\]$/);\n if (sec) {\n section = sec[1].trim();\n if (!result[section]) result[section] = {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim().replace(/^[\"']|[\"']$/g, '');\n let valRaw = line.slice(eq + 1).trim();\n // Strip an inline comment only off bare scalars — never inside a quoted\n // string or an inline array (where a '#' would be data).\n if (!valRaw.startsWith('\"') && !valRaw.startsWith(\"'\") && !valRaw.startsWith('[')) {\n const h = valRaw.indexOf('#');\n if (h !== -1) valRaw = valRaw.slice(0, h).trim();\n }\n const value = parseTomlValue(valRaw);\n if (section) (result[section] as Record<string, any>)[key] = value;\n else result[key] = value;\n }\n return result;\n}\n\nfunction writeSynkroFileIfMissing(opts: { hasClaudeCode: boolean; hasCursor: boolean; gradingMode: string; deployLocation?: 'local' | 'cloud' }): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return;\n // Never treat $HOME as a repo to onboard: ~/.synkro is Synkro's own config\n // DIRECTORY, so writing config there would collide with it and an existence\n // check would always \"find\" it. (git rev-parse can resolve to $HOME when\n // dotfiles are git-tracked or cwd is a non-git dir under home.)\n if (root === homedir()) return;\n const fp = join(root, 'synkro.toml');\n // Onboarded only if a real synkro.toml FILE exists — statSync().isFile() so\n // a directory (or any non-file) never counts as \"already configured\".\n let hasFile = false;\n try { hasFile = statSync(fp).isFile(); } catch {}\n if (hasFile) {\n console.log(` synkro.toml: ${fp} (existing, respected)`);\n return;\n }\n let pool: string = 'auto';\n const harness: string[] = [];\n if (opts.hasClaudeCode) { harness.push('claude-code'); if (!opts.hasCursor) pool = 'claude'; }\n if (opts.hasCursor) { harness.push('cursor'); if (!opts.hasClaudeCode) pool = 'cursor'; }\n const mode = opts.gradingMode === 'byok' ? 'byok' : 'local';\n // TOML scope rule: all top-level keys MUST precede any [table] header, or\n // they'd bind to the open table. Emit version/harness first, then the [grader]\n // and [scanning] tables. (Rulesets + skills are managed in the registry/dashboard\n // now, not via synkro.toml — so they're no longer written here.)\n const toml = [\n 'version = 1',\n '',\n `harness = [${harness.map(h => `\"${h}\"`).join(', ')}]`,\n '',\n '[grader]',\n `location = \"${opts.deployLocation || 'local'}\"`,\n `pool = \"${pool}\"`,\n `mode = \"${mode}\"`,\n '',\n '[scanning]',\n 'cwe = true',\n 'cve = true',\n '',\n ].join('\\n');\n writeFileSync(fp, toml, 'utf-8');\n console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);\n } catch {}\n}\n\n// Sync [grader] location in an EXISTING repo synkro.toml to the chosen deploy target, so the\n// visible toml never drifts from config.env after the local/cloud prompt. No-op if there's no\n// toml or no location line (config.env stays authoritative at runtime regardless).\nfunction updateSynkroTomlLocation(location: 'local' | 'cloud'): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root || root === homedir()) return;\n const fp = join(root, 'synkro.toml');\n let txt = '';\n try { if (!statSync(fp).isFile()) return; txt = readFileSync(fp, 'utf-8'); } catch { return; }\n const re = /^(\\s*location\\s*=\\s*).*$/m;\n if (!re.test(txt)) return;\n const next = txt.replace(re, `$1\"${location}\"`);\n if (next !== txt) { writeFileSync(fp, next, 'utf-8'); console.log(` synkro.toml: [grader] location = \"${location}\"`); }\n } catch {}\n}\n\ninterface SynkroFile {\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude_code' | 'cursor'; mode: string; location?: 'local' | 'cloud' };\n workers: { claude?: number; cursor?: number };\n /** Config-parse warnings (unrecognized provider tokens) to surface to the user. */\n warnings: string[];\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n /** Registry subscriptions: \"namespace/name\" -> version (e.g. \"v3\" | \"latest\"). */\n standards: Record<string, string>;\n _repoRoot: string;\n}\n\nexport function readFullSynkroFile(): SynkroFile | null {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return null;\n const fp = join(root, 'synkro.toml');\n if (!existsSync(fp)) return null;\n const parsed = parseSynkroToml(readFileSync(fp, 'utf-8'));\n const valid = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => valid.includes(h as any))\n : ['claude-code', 'cursor'];\n const resolved = resolveGraderPool(parsed);\n return {\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: resolved.pool,\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : 'local',\n location: parsed.grader?.location === 'cloud' ? 'cloud' : 'local',\n },\n workers: {\n ...(resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {}),\n ...(resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}),\n },\n warnings: resolved.warnings,\n ruleset: parsed.ruleset || 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string' && s.endsWith('.md')) : [],\n scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },\n standards: (parsed.standards && typeof parsed.standards === 'object')\n ? Object.fromEntries(Object.entries(parsed.standards as Record<string, unknown>)\n .filter(([k, v]) => typeof v === 'string' && k.includes('/'))\n .map(([k, v]) => [k, String(v)]))\n : {},\n _repoRoot: root,\n };\n } catch { return null; }\n}\n\nexport function reconcileHarness(): { claudeWorkers: number; cursorWorkers: number } | null {\n const sf = readFullSynkroFile();\n if (!sf) {\n console.log('No .synkro file found in repo root — skipping harness reconciliation.');\n return null;\n }\n\n const wantCC = sf.harness.includes('claude-code');\n const wantCursor = sf.harness.includes('cursor');\n for (const w of sf.warnings) console.warn(` ⚠ ${w}`);\n console.log(`synkro.toml: harness=[${sf.harness.join(', ')}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);\n\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/');\n\n const ccSettings = join(homedir(), '.claude', 'settings.json');\n if (wantCC) {\n installCCHooks(ccSettings, {\n bashJudgeScriptPath: scripts.bashScript,\n skillJudgeScriptPath: scripts.skillJudgeScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n taskActivateIntentScriptPath: scripts.taskActivateIntentScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(' ✓ Claude Code hooks registered');\n try {\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcpJwt) {\n installMcpConfig({ gatewayUrl: '', bearerToken: mcpJwt, local: true });\n console.log(' ✓ Claude Code MCP registered');\n }\n } catch {}\n } else {\n if (uninstallCCHooks(ccSettings)) console.log(' ✗ Claude Code hooks removed');\n if (uninstallMcpConfig()) console.log(' ✗ Claude Code MCP removed');\n if (uninstallClaudeDesktopMcpConfig()) console.log(' ✗ Claude Desktop MCP removed');\n }\n\n const cursorHooks = join(homedir(), '.cursor', 'hooks.json');\n if (wantCursor) {\n installCursorHooks(cursorHooks, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n skillJudgeScriptPath: scripts.cursorSkillJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(' ✓ Cursor hooks registered');\n try {\n installCursorMcpConfig({ gatewayUrl: '', bearerToken: '', local: true });\n console.log(' ✓ Cursor MCP registered');\n } catch {}\n } else {\n if (uninstallCursorHooks(cursorHooks)) console.log(' ✗ Cursor hooks removed');\n if (uninstallCursorMcpConfig()) console.log(' ✗ Cursor MCP removed');\n }\n\n // Explicit worker counts from .synkro take priority\n if (sf.workers.claude != null || sf.workers.cursor != null) {\n const cw = Math.max(0, Math.floor(sf.workers.claude || 0));\n const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };\n }\n\n const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const providers: WorkerProvider[] = [];\n if (sf.grader.pool === 'cursor') {\n providers.push('cursor');\n } else if (sf.grader.pool === 'claude_code') {\n providers.push('claude_code');\n } else {\n if (wantCC) providers.push('claude_code');\n if (wantCursor) providers.push('cursor');\n }\n if (providers.length === 0) providers.push('claude_code');\n return splitWorkers(total, providers);\n}\n\n// Ingest a list of {source, content} skills through the container, SEQUENTIALLY: each\n// file must dedup against rules the previous files just added, else two md files with the\n// same rule would each add it (cross-file dupe). Surfaces vet-BLOCKED skills distinctly\n// (created=0 with a pass ✓ would misrepresent a rejection). Returns a summary.\nasync function ingestSkillTasks(\n tasks: { source: string; content: string }[],\n mcpPort: string,\n): Promise<{ ingested: number; rules: number; rejected: { source: string; reason: string }[] }> {\n const summary = { ingested: 0, rules: 0, rejected: [] as { source: string; reason: string }[] };\n for (const { source, content } of tasks) {\n try {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source, content }),\n signal: AbortSignal.timeout(65000),\n });\n if (!resp.ok) throw new Error(`${resp.status}`);\n const r = await resp.json() as { status?: string; created?: number; message?: string; reasons?: string[] };\n if (r.status === 'rejected') {\n const reason = (r.reasons || []).slice(0, 2).join('; ') || r.message || 'blocked by security vetting';\n summary.rejected.push({ source, reason });\n console.log(` ⛔ skill ${source}: BLOCKED — ${reason}`);\n } else {\n const created = r.created || 0;\n summary.ingested++;\n summary.rules += created;\n console.log(created > 0\n ? ` ✓ skill ${source}: ${created} rule${created === 1 ? '' : 's'} → its own pack`\n : ` ✓ skill ${source}: ${r.message || 'up to date'}`);\n }\n } catch (e) {\n console.warn(` ⚠ skill ${source}: sync failed (${e instanceof Error ? e.message : String(e)})`);\n }\n }\n return summary;\n}\n\nexport async function syncSkillFiles(): Promise<void> {\n const sf = readFullSynkroFile();\n if (!sf || sf.skills.length === 0) return;\n\n const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);\n if (resolved.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const tasks = resolved.map(fp => {\n const content = readFileSync(fp, 'utf-8');\n const source = `skill:${fp.split('/').pop()}`;\n if (!content.trim()) {\n console.log(` ⊘ skill ${source}: empty file, skipped`);\n return null;\n }\n const lines = content.split('\\n').length;\n console.log(` → read ${source} (${lines} lines, ${(content.length / 1024).toFixed(1)} KB)`);\n return { source, content };\n }).filter(Boolean) as { source: string; content: string }[];\n\n if (tasks.length === 0) return;\n\n console.log(` indexing ${tasks.length} skill file${tasks.length === 1 ? '' : 's'} — comparing against existing ruleset (this may take a few seconds)...`);\n await ingestSkillTasks(tasks, mcpPort);\n}\n\n// ─── Skill auto-discovery (install-time onboarding) ─────────────────────────────\n// Common locations Claude Code / skills.sh / agent tools drop skills. Home AND repo,\n// since skills can be user-global or project-scoped. skills.sh symlinks .agents→.claude,\n// so we dedup by CONTENT HASH (not path) to avoid double-counting a symlinked skill.\n// Bounded scan. Handles both the skills.sh layout (<dir>/<name>/SKILL.md) and flat *.md.\ntype DiscoveredSkill = { source: string; content: string; name: string; hash: string; ingested: boolean };\n// Normalize a skill name for identity matching: lowercase, drop a trailing .md/.mdx\n// (some rows store the name WITH the extension, others without — normalize both sides).\nfunction normSkillName(name: string): string { return name.toLowerCase().replace(/\\.mdx?$/, ''); }\nfunction discoverSkillFiles(\n repoRoot: string | null,\n excludeHashes: Set<string>,\n ingestedHashes: Set<string>,\n ingestedNames: Set<string>,\n): DiscoveredSkill[] {\n const roots: string[] = [];\n const add = (p: string) => { try { if (p && existsSync(p) && statSync(p).isDirectory()) roots.push(p); } catch { /* ignore */ } };\n add(join(homedir(), '.claude', 'skills'));\n add(join(homedir(), '.agents', 'skills'));\n if (repoRoot) { add(join(repoRoot, '.claude', 'skills')); add(join(repoRoot, '.agents', 'skills')); }\n\n const out: DiscoveredSkill[] = [];\n const seen = new Set<string>();\n const MAX = 200;\n const consider = (file: string, name: string) => {\n if (out.length >= MAX) return;\n let content = '';\n try { const st = statSync(file); if (!st.isFile() || st.size > 200_000) return; content = readFileSync(file, 'utf-8'); } catch { return; }\n if (!content.trim()) return;\n const hash = createHash('sha256').update(content).digest('hex');\n // Hard-skip symlink dupes (seen) and skills managed via synkro.toml (auto-synced there).\n if (seen.has(hash) || excludeHashes.has(hash)) return;\n seen.add(hash);\n // Already ingested into the store? Still SHOW it, but tag it so the prompt makes it\n // unselectable. Exact content_hash match (unchanged) OR same normalized name (ingested\n // but since edited) both count as \"already ingested\".\n const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));\n out.push({ source: `skill:${name}`, content, name, hash, ingested });\n };\n for (const root of roots) {\n let entries: string[] = [];\n try { entries = readdirSync(root); } catch { continue; }\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n const full = join(root, entry);\n let st; try { st = statSync(full); } catch { continue; } // follows symlinks\n if (st.isDirectory()) {\n const skillMd = join(full, 'SKILL.md');\n if (existsSync(skillMd)) consider(skillMd, entry); // skills.sh: <name>/SKILL.md\n } else if (/\\.mdx?$/i.test(entry) && entry.toUpperCase() !== 'README.MD') {\n consider(full, entry.replace(/\\.mdx?$/i, ''));\n }\n }\n }\n return out;\n}\n\n// A stable fingerprint of the discovered set so a plain `synkro install` re-run only\n// re-prompts when the user has ADDED skills since last time (no nagging on the same set,\n// whether they accepted or declined).\nconst SKILLS_DISCOVERED_PATH = join(SYNKRO_DIR, '.skills-discovered');\nfunction discoverySetHash(found: DiscoveredSkill[]): string {\n return createHash('sha256').update(found.map(f => f.hash).sort().join(',')).digest('hex');\n}\n\nasync function promptSkillDiscovery(found: DiscoveredSkill[]): Promise<number[]> {\n if (!process.stdin.isTTY || found.length === 0) return [];\n const selectableIdx = found.map((s, i) => (s.ingested ? -1 : i)).filter(i => i >= 0);\n if (selectableIdx.length === 0) return []; // everything already ingested — nothing to pick\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> => new Promise(res => rl.question(q, a => res(a.trim())));\n try {\n console.log(`\\nFound ${found.length} skill${found.length === 1 ? '' : 's'} in your Claude Code / skills.sh folders:`);\n // Already-ingested skills are shown for context but NOT selectable — only uningested ones can be picked.\n found.forEach((s, i) => console.log(` ${i + 1}. ${s.name}${s.ingested ? ' — already ingested (not selectable)' : ''}`));\n console.log('Ingest the uningested skills as enforceable rules? Each is security-vetted first — anything');\n console.log('malicious (prompt injection, guardrail-weakening, secret exfil) is blocked and reported.');\n const a = (await ask(`Ingest [all] (${selectableIdx.length} uningested) / type numbers (e.g. 1,3) / [skip]: `)).toLowerCase();\n if (a === 'skip' || a === 'n' || a === 'no' || a === 's') return [];\n if (a === '' || a === 'all' || a === 'y' || a === 'yes' || a === 'a') return selectableIdx;\n const picks = a.split(/[\\s,]+/).map(x => parseInt(x, 10) - 1).filter(i => Number.isInteger(i) && i >= 0 && i < found.length);\n const valid: number[] = [];\n for (const i of [...new Set(picks)]) {\n if (found[i].ingested) { console.log(` · ${found[i].name} is already ingested — skipped`); continue; }\n valid.push(i);\n }\n return valid;\n } finally { rl.close(); }\n}\n\n// Install-time: scan common skill folders, offer to ingest what's not already tracked.\n// Local-mode only (posts to the just-started local container); the container security-\n// vets each skill before any rule is added. Called right after syncSkillFiles().\nexport async function discoverAndIngestSkills(): Promise<void> {\n try {\n const sf = readFullSynkroFile();\n const repoRoot = sf?._repoRoot || detectGitRepo();\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n // Exclude skills already tracked in synkro.toml (by content hash).\n const excludeHashes = new Set<string>();\n if (sf?.skills?.length) {\n for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {\n try { excludeHashes.add(createHash('sha256').update(readFileSync(fp, 'utf-8')).digest('hex')); } catch { /* ignore */ }\n }\n }\n // Identify skills ALREADY INGESTED into the store (PGLite local / Timescale cloud)\n // so the prompt can show them as unselectable. content_hash = exact match (unchanged);\n // normalized name = ingested-but-since-edited. Both mark the skill \"already ingested\".\n const ingestedHashes = new Set<string>();\n const ingestedNames = new Set<string>();\n try {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills?status=active`, { signal: AbortSignal.timeout(5000) });\n if (resp.ok) {\n const { skills } = await resp.json() as { skills?: Array<{ name?: string; content_hash?: string }> };\n for (const s of skills || []) {\n if (s.content_hash) ingestedHashes.add(s.content_hash);\n if (s.name) ingestedNames.add(normSkillName(String(s.name)));\n }\n }\n } catch { /* container unreachable — treat nothing as ingested (worst case: re-offers) */ }\n\n const found = discoverSkillFiles(repoRoot, excludeHashes, ingestedHashes, ingestedNames);\n if (found.length === 0) return;\n\n const selectable = found.filter(f => !f.ingested);\n if (selectable.length === 0) {\n // Everything discovered is already ingested — no selection needed, just acknowledge\n // and move on to the next install step. (No numbered list: nothing to pick.)\n const names = found.map(f => f.name);\n const shown = names.slice(0, 5).join(', ') + (names.length > 5 ? `, +${names.length - 5} more` : '');\n console.log(`\\n✓ ${found.length} skill${found.length === 1 ? '' : 's'} already ingested (${shown}) — nothing new to add.`);\n return;\n }\n\n // Don't nag: skip if this exact set of UNINGESTED skills was already offered.\n const setHash = discoverySetHash(selectable);\n let prev = '';\n try { prev = readFileSync(SKILLS_DISCOVERED_PATH, 'utf-8').trim(); } catch { /* first run */ }\n if (prev === setHash) return;\n\n const picks = await promptSkillDiscovery(found);\n try { writeFileSync(SKILLS_DISCOVERED_PATH, setHash); } catch { /* best-effort */ }\n if (picks.length === 0) {\n console.log(' Skipped — you can ingest skills anytime from the Synkro dashboard or via the MCP tool.');\n return;\n }\n\n console.log(` Ingesting ${picks.length} skill${picks.length === 1 ? '' : 's'} (security-vetting each first)…`);\n const summary = await ingestSkillTasks(picks.map(i => ({ source: found[i].source, content: found[i].content })), mcpPort);\n const parts = [`${summary.ingested} ingested (${summary.rules} rule${summary.rules === 1 ? '' : 's'})`];\n if (summary.rejected.length) parts.push(`${summary.rejected.length} blocked`);\n console.log(` Skills: ${parts.join(', ')} — each in its own toggleable pack.`);\n } catch (e) {\n console.warn(` ⚠ skill discovery skipped (${e instanceof Error ? e.message : String(e)})`);\n }\n}\n\nexport function detectGitRepo(): string | null {\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return '';\n }\n };\n const remoteUrl = run('git remote get-url origin');\n if (remoteUrl) {\n return remoteUrl\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\/\\/[^/]+\\//, '')\n .replace(/\\.git$/, '');\n }\n // No origin remote — fall back to the repo's top-level directory name.\n const root = run('git rev-parse --show-toplevel');\n return root ? (root.split('/').pop() || null) : null;\n}\n\nfunction getClaudeProjectsFolder(): string | null {\n const cwd = process.cwd();\n // CC stores transcripts in ~/.claude/projects/{sanitized-path}/\n // where sanitized-path replaces / with -\n const sanitized = '-' + cwd.replace(/\\//g, '-');\n const projectsDir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(projectsDir) ? projectsDir : null;\n}\n\ninterface SessionInsight {\n session_id: string;\n insight_type: 'summary' | 'user_message';\n content: string;\n metadata?: Record<string, unknown>;\n}\n\nfunction extractSessionInsights(projectsDir: string): SessionInsight[] {\n const insights: SessionInsight[] = [];\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Extract compaction summaries\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user' &&\n typeof entry.message?.content === 'string' &&\n entry.message.content.startsWith('This session is being continued')) {\n insights.push({\n session_id: sessionId,\n insight_type: 'summary',\n content: entry.message.content.slice(0, 4000),\n metadata: { source: 'compaction_summary' },\n });\n }\n } catch {}\n }\n\n // Extract user messages (last 20 per session — recent preferences)\n const userMessages: string[] = [];\n for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user') {\n const text = typeof entry.message?.content === 'string'\n ? entry.message.content\n : Array.isArray(entry.message?.content)\n ? entry.message.content.map((b: any) => b.text ?? b).filter((t: any) => typeof t === 'string').join(' ')\n : null;\n if (text && text.length > 10 && text.length < 2000 && !text.startsWith('This session is being continued')) {\n userMessages.push(text);\n }\n }\n } catch {}\n }\n for (const msg of userMessages.reverse()) {\n insights.push({\n session_id: sessionId,\n insight_type: 'user_message',\n content: msg.slice(0, 2000),\n });\n }\n } catch {}\n }\n\n return insights;\n}\n\nasync function ingestSessionTranscripts(gatewayUrl: string, token: string, repo: string): Promise<number> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return 0;\n\n const insights = extractSessionInsights(projectsDir);\n if (insights.length === 0) return 0;\n\n console.log(`Found ${insights.length} session insights from Claude Code history...`);\n\n // Send in batches of 100\n let total = 0;\n for (let i = 0; i < insights.length; i += 100) {\n const batch = insights.slice(i, i + 100);\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ingest-sessions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions: batch }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number };\n total += result.accepted;\n }\n } catch {}\n }\n\n return total;\n}\n\ninterface TranscriptMessage {\n message_index: number;\n type: 'user' | 'assistant';\n content: string;\n tool_calls?: Array<{ name: string; input: string; id: string }>;\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n}\n\nfunction extractTextContent(content: any): string {\n if (typeof content === 'string') return content.slice(0, 8000);\n if (Array.isArray(content)) {\n return content\n .filter((b: any) => typeof b === 'string' || (b?.type === 'text'))\n .map((b: any) => typeof b === 'string' ? b : (b?.text || ''))\n .join(' ')\n .slice(0, 8000);\n }\n return '';\n}\n\n// ─── Cursor transcript import ────────────────────────────────────────────────\n// Cursor stores transcripts at\n// ~/.cursor/projects/{slug}/agent-transcripts/{convId}/{convId}.jsonl\n// where each line is {role:'user'|'assistant', message:{content:[{type:'text',\n// text}]}} — note `role` is top-level (vs CC's {type, message:{role,...}}).\n// The slug is the workspace path with non-alphanumerics collapsed to '-'.\nfunction cursorProjectSlug(workspaceRoot: string): string {\n return workspaceRoot.replace(/^[/]+/, '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n}\n\nfunction getCursorTranscriptsDir(): string | null {\n const dir = join(homedir(), '.cursor', 'projects', cursorProjectSlug(process.cwd()), 'agent-transcripts');\n return existsSync(dir) ? dir : null;\n}\n\n// Conversation ids are UUIDs. CWE-22: validate against a strict allowlist before\n// using a dir name in any path join, so a crafted entry (.., /) can't traverse.\nfunction isSafeConvId(id: string): boolean {\n return /^[A-Za-z0-9_-]+$/.test(id);\n}\n\nfunction parseCursorTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n const role = entry.role || entry.message?.role;\n if (role !== 'user' && role !== 'assistant') continue;\n const text = extractTextContent(entry.message?.content ?? entry.content);\n if (!text) continue;\n messages.push({ message_index: i, type: role, content: text });\n } catch {}\n }\n return messages;\n}\n\nasync function syncCursorTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const dir = getCursorTranscriptsDir();\n if (!dir) return { sessions: 0, messages: 0 };\n // Each conversation is a subdir {convId}/ holding {convId}.jsonl.\n let convDirs: string[] = [];\n try {\n convDirs = readdirSync(dir, { withFileTypes: true })\n .filter(d => d.isDirectory() && isSafeConvId(d.name))\n .map(d => d.name);\n } catch { return { sessions: 0, messages: 0 }; }\n if (convDirs.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${convDirs.length} Cursor session transcripts, importing + embedding...`);\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < convDirs.length; i++) {\n const convId = convDirs[i];\n if (!isSafeConvId(convId)) continue; // defense-in-depth before any path join\n const filePath = join(dir, convId, `${convId}.jsonl`);\n if (!existsSync(filePath)) continue;\n try {\n const all = parseCursorTranscriptFile(filePath);\n const messages = all.length > 500 ? all.slice(-500) : all;\n if (messages.length === 0) continue;\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: convId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === convDirs.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);\n }\n // Offset so the Stop hook doesn't re-send already-imported lines.\n try {\n const lc = readFileSync(filePath, 'utf-8').split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, convId), String(lc), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nfunction parseTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n\n const msg: TranscriptMessage = {\n message_index: i,\n type: entry.type,\n content: extractTextContent(entry.message?.content),\n };\n\n if (entry.type === 'assistant') {\n if (Array.isArray(entry.message?.content)) {\n const toolCalls = entry.message.content\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({\n name: b.name || '',\n input: JSON.stringify(b.input || {}).slice(0, 500),\n id: b.id || '',\n }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n }\n if (entry.message?.model) msg.model = entry.message.model;\n if (entry.message?.usage) {\n msg.usage = {\n input_tokens: entry.message.usage.input_tokens,\n output_tokens: entry.message.usage.output_tokens,\n cache_creation_input_tokens: entry.message.usage.cache_creation_input_tokens,\n cache_read_input_tokens: entry.message.usage.cache_read_input_tokens,\n };\n }\n }\n\n if (msg.content.length > 0) messages.push(msg);\n } catch {}\n }\n\n return messages;\n}\n\nasync function syncTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${files.length} CC session transcripts, importing + embedding...`);\n\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;\n if (messages.length === 0) continue;\n\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: sessionId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === files.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);\n }\n\n // Write offset so the Stop hook doesn't re-send\n try {\n const content = readFileSync(join(projectsDir, file), 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nasync function syncTranscriptsBulk(gatewayUrl: string, token: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(`Found ${files.length} CC session transcripts, syncing...`);\n\n const maxMessagesPerSession = 500;\n let totalSessions = 0;\n let totalMessages = 0;\n\n // Batch sessions into groups of 5\n for (let i = 0; i < files.length; i += 5) {\n const batch = files.slice(i, i + 5);\n const sessions: Array<{ cc_session_id: string; messages: TranscriptMessage[] }> = [];\n\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > maxMessagesPerSession\n ? allMessages.slice(-maxMessagesPerSession)\n : allMessages;\n\n if (messages.length > 0) {\n sessions.push({ cc_session_id: sessionId, messages });\n }\n } catch {}\n }\n\n if (sessions.length === 0) continue;\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number; sessions: number };\n totalMessages += result.accepted;\n totalSessions += result.sessions;\n }\n } catch {}\n\n // Write offset files so the Stop hook doesn't re-send\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n }\n\n return { sessions: totalSessions, messages: totalMessages };\n}\n","/**\n * Filesystem setup for the local-CC channel plugin.\n *\n * Idempotent: safe to call multiple times. Writes:\n * ~/.synkro/cc_sessions/synkro-channel.ts (the Bun MCP plugin)\n * ~/.synkro/cc_sessions/package.json (deps for the plugin)\n * ~/.synkro/cc_sessions/.claude/settings.json (fastMode:true scoped to this cwd)\n *\n * Then patches ~/.claude.json to register the plugin under mcpServers, and runs\n * `bun install` in the session dir so @modelcontextprotocol/sdk is on disk.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, copyFileSync, renameSync, unlinkSync, openSync, fsyncSync, closeSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { spawnSync } from 'node:child_process';\n\nexport const CLAUDE_JSON_BACKUP_PATH = join(homedir(), '.claude.json.synkro-bak');\n\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\nexport const PLUGIN_PATH = join(SESSION_DIR, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH = join(SESSION_DIR, 'package.json');\nexport const PLUGIN_SETTINGS_DIR = join(SESSION_DIR, '.claude');\nexport const PLUGIN_SETTINGS_PATH = join(PLUGIN_SETTINGS_DIR, 'settings.json');\nexport const PROJECT_MCP_PATH = join(SESSION_DIR, '.mcp.json');\nexport const CLAUDE_JSON_PATH = join(homedir(), '.claude.json');\nexport const RUN_SCRIPT_PATH = join(SESSION_DIR, 'run-claude.sh');\nexport const TMUX_SESSION_NAME = 'synkro-local-cc';\n\n// Channel 1 (general A) — pueue task → claude listening on this internal port.\n// The MCP server's dispatcher (8929) round-robins between Channel 1 and 3.\nexport const CHANNEL_1_PORT = 8941;\n\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\nexport const PLUGIN_PATH_2 = join(SESSION_DIR_2, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_2 = join(SESSION_DIR_2, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_2 = join(SESSION_DIR_2, '.claude');\nexport const PLUGIN_SETTINGS_PATH_2 = join(PLUGIN_SETTINGS_DIR_2, 'settings.json');\nexport const PROJECT_MCP_PATH_2 = join(SESSION_DIR_2, '.mcp.json');\nexport const RUN_SCRIPT_PATH_2 = join(SESSION_DIR_2, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_2 = 'synkro-local-cc-2';\n// CWE worker A. Dispatcher on 8930 round-robins Channel 2 and 4.\nexport const CHANNEL_2_PORT = 8951;\n\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\nexport const PLUGIN_PATH_3 = join(SESSION_DIR_3, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_3 = join(SESSION_DIR_3, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_3 = join(SESSION_DIR_3, '.claude');\nexport const PLUGIN_SETTINGS_PATH_3 = join(PLUGIN_SETTINGS_DIR_3, 'settings.json');\nexport const PROJECT_MCP_PATH_3 = join(SESSION_DIR_3, '.mcp.json');\nexport const RUN_SCRIPT_PATH_3 = join(SESSION_DIR_3, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_3 = 'synkro-local-cc-3';\nexport const CHANNEL_3_PORT = 8942; // general worker B\n\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\nexport const PLUGIN_PATH_4 = join(SESSION_DIR_4, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_4 = join(SESSION_DIR_4, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_4 = join(SESSION_DIR_4, '.claude');\nexport const PLUGIN_SETTINGS_PATH_4 = join(PLUGIN_SETTINGS_DIR_4, 'settings.json');\nexport const PROJECT_MCP_PATH_4 = join(SESSION_DIR_4, '.mcp.json');\nexport const RUN_SCRIPT_PATH_4 = join(SESSION_DIR_4, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_4 = 'synkro-local-cc-4';\nexport const CHANNEL_4_PORT = 8952; // CWE worker B\n\n/**\n * Bash wrapper that owns the tmux session hosting `claude`. Pueue runs this\n * script as its task. The script:\n * 1. Kills any prior tmux session by the same name (idempotent restart).\n * 2. Starts a detached tmux session running claude. tmux gives claude a\n * real pty, so it stays in interactive mode (vs. dropping into --print).\n * 3. Blocks on tmux's lifetime so pueue's task duration mirrors claude's.\n * `synkro local-cc stop` kills the tmux session, which unblocks the\n * poll and lets pueue mark the task done.\n *\n * Dismissal of startup dialogs (workspace trust / dev-channels /\n * MCP consent) is owned by the TS-side waitForChannelReady, which polls\n * the channel TCP port and injects \\`tmux send-keys ... Enter\\` each tick\n * the probe fails. That's adaptive (no fixed window) and self-terminating\n * (stops the moment the port binds).\n */\nconst RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Do not edit.\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME}\nLOG=\"$HOME/.synkro/cc_sessions/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\n# Pre-flight checks\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH. Install Claude Code first.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\n# Check claude is authenticated\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed. Is Claude Code installed correctly?\"\n exit 1\nfi\n\nlog \"Starting local-CC session...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\n# Kill any previous session so restarts come up clean.\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\n# Start claude inside a detached tmux session so it has a real pty.\n# Redirect stderr to the log so we can see why it dies.\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\n# Claude's --dangerously-load-development-channels shows a confirmation\n# prompt: option 1 = \"I am using this for local development\" (accept),\n# option 2 = \"Exit\". Auto-accept by sending '1' + Enter.\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n # Additional Enter for any follow-up prompts (workspace trust, MCP consent)\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to claude session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n log \"Try running claude manually to verify auth: claude --print 'say ok'\"\n exit 1\nfi\n\nlog \"tmux session started successfully.\"\n\n# Block on the tmux session so pueue's task lifetime tracks claude's.\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst RUN_SCRIPT_SOURCE_2 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Channel 2 (CWE scan, port ${CHANNEL_2_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_2}\nLOG=\"$HOME/.synkro/cc_sessions_2/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC channel 2 (port ${CHANNEL_2_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to channel 2 session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_2_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// General worker B (port CHANNEL_3_PORT) — dispatcher pairs this with cc_sessions on 8929.\nconst RUN_SCRIPT_SOURCE_3 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. General worker B (port ${CHANNEL_3_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_3}\nLOG=\"$HOME/.synkro/cc_sessions_3/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC general worker B (port ${CHANNEL_3_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to general worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_3_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// CWE worker B (port CHANNEL_4_PORT) — dispatcher pairs this with cc_sessions_2 on 8930.\nconst RUN_SCRIPT_SOURCE_4 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. CWE worker B (port ${CHANNEL_4_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_4}\nLOG=\"$HOME/.synkro/cc_sessions_4/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC CWE worker B (port ${CHANNEL_4_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to CWE worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_4_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst MCP_SERVER_NAME = 'synkro-local';\n\nconst PLUGIN_PACKAGE_JSON = JSON.stringify(\n {\n name: 'synkro-local-channel',\n private: true,\n version: '0.1.0',\n type: 'module',\n dependencies: {\n '@modelcontextprotocol/sdk': '^1.0.0',\n },\n },\n null,\n 2,\n) + '\\n';\n\nexport class LocalCCInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCInstallError';\n }\n}\n\ninterface ChannelInstallSlot {\n sessionDir: string;\n pluginPath: string;\n pluginPkgPath: string;\n pluginSettingsDir: string;\n pluginSettingsPath: string;\n projectMcpPath: string;\n runScriptPath: string;\n runScriptSource: string;\n}\n\nconst CHANNELS: readonly ChannelInstallSlot[] = [\n { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH, pluginPkgPath: PLUGIN_PKG_PATH, pluginSettingsDir: PLUGIN_SETTINGS_DIR, pluginSettingsPath: PLUGIN_SETTINGS_PATH, projectMcpPath: PROJECT_MCP_PATH, runScriptPath: RUN_SCRIPT_PATH, runScriptSource: RUN_SCRIPT_SOURCE },\n { sessionDir: SESSION_DIR_2, pluginPath: PLUGIN_PATH_2, pluginPkgPath: PLUGIN_PKG_PATH_2, pluginSettingsDir: PLUGIN_SETTINGS_DIR_2, pluginSettingsPath: PLUGIN_SETTINGS_PATH_2, projectMcpPath: PROJECT_MCP_PATH_2, runScriptPath: RUN_SCRIPT_PATH_2, runScriptSource: RUN_SCRIPT_SOURCE_2 },\n { sessionDir: SESSION_DIR_3, pluginPath: PLUGIN_PATH_3, pluginPkgPath: PLUGIN_PKG_PATH_3, pluginSettingsDir: PLUGIN_SETTINGS_DIR_3, pluginSettingsPath: PLUGIN_SETTINGS_PATH_3, projectMcpPath: PROJECT_MCP_PATH_3, runScriptPath: RUN_SCRIPT_PATH_3, runScriptSource: RUN_SCRIPT_SOURCE_3 },\n { sessionDir: SESSION_DIR_4, pluginPath: PLUGIN_PATH_4, pluginPkgPath: PLUGIN_PKG_PATH_4, pluginSettingsDir: PLUGIN_SETTINGS_DIR_4, pluginSettingsPath: PLUGIN_SETTINGS_PATH_4, projectMcpPath: PROJECT_MCP_PATH_4, runScriptPath: RUN_SCRIPT_PATH_4, runScriptSource: RUN_SCRIPT_SOURCE_4 },\n];\n\nfunction writePluginFiles(): void {\n for (const c of CHANNELS) {\n mkdirSync(c.sessionDir, { recursive: true });\n mkdirSync(c.pluginSettingsDir, { recursive: true });\n // Channel plugin source lives in the Docker image only (IP protection).\n // Workers inside the container use /app/channel-plugin.ts directly.\n writeFileSync(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, 'utf-8');\n writeFileSync(\n c.pluginSettingsPath,\n JSON.stringify({\n fastMode: true,\n enabledMcpjsonServers: ['synkro-local'],\n // Extended thinking is pure latency tax on a structured verdict grade and\n // emits reasoning the verdict parser isn't tuned for. effortLevel only\n // SOFTENS it; CLAUDE_CODE_DISABLE_THINKING=1 is the authoritative off\n // switch. Mirror the cloud pool (pool-config.sh) so local + cloud graders\n // behave identically.\n effortLevel: 'low',\n env: { CLAUDE_CODE_DISABLE_THINKING: '1' },\n }, null, 2) + '\\n',\n 'utf-8',\n );\n writeFileSync(c.runScriptPath, c.runScriptSource, 'utf-8');\n chmodSync(c.runScriptPath, 0o755);\n }\n}\n\nfunction runBunInstall(): void {\n for (const c of CHANNELS) {\n const r = spawnSync('bun', ['install', '--silent'], {\n cwd: c.sessionDir,\n encoding: 'utf-8',\n timeout: 120_000,\n });\n if (r.status !== 0) {\n throw new LocalCCInstallError(\n `bun install failed in ${c.sessionDir}: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n }\n}\n\ninterface ClaudeJson {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n projects?: Record<string, Record<string, unknown>>;\n [k: string]: unknown;\n}\n\n/**\n * Safely apply `mutator` to ~/.claude.json with five layers of protection:\n * 1. Refuse to operate on malformed input — never try to \"fix\" a corrupted file.\n * 2. Take a rolling backup at ~/.claude.json.synkro-bak before any write.\n * 3. Validate the post-mutation result is still well-formed JSON and didn't\n * lose any top-level keys that existed in the original.\n * 4. Atomic write via tmp + fsync + rename(2) (POSIX-atomic).\n * 5. Restore from backup on any error in the write phase.\n *\n * If the file doesn't exist (no claude install on this machine), the mutation\n * is silently skipped — we don't create the file, since claude owns it.\n */\nfunction safelyMutateClaudeJson(mutator: (json: ClaudeJson) => boolean): void {\n if (!existsSync(CLAUDE_JSON_PATH)) {\n // claude.json doesn't exist; nothing to mutate. We don't create it.\n return;\n }\n\n // (1) Read + parse, refuse to proceed on malformed input.\n const originalText = readFileSync(CLAUDE_JSON_PATH, 'utf-8');\n let parsed: ClaudeJson;\n try {\n parsed = JSON.parse(originalText) as ClaudeJson;\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to modify malformed ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Please fix the JSON manually before retrying.`,\n err,\n );\n }\n\n const originalKeys = new Set(Object.keys(parsed));\n\n // (Mutator returns true if it changed anything; false means no-op.)\n const dirty = mutator(parsed);\n if (!dirty) return;\n\n // (3a) Validate the in-memory mutation didn't drop a top-level key.\n for (const k of originalKeys) {\n if (!(k in parsed)) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: mutator dropped top-level key \"${k}\". ` +\n `This is a bug — please report.`,\n );\n }\n }\n\n const newText = JSON.stringify(parsed, null, 2) + '\\n';\n\n // (3b) Round-trip parse to guarantee well-formed output before any write.\n try {\n JSON.parse(newText);\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: serialized result is not valid JSON. ` +\n `This is a bug — please report.`,\n err,\n );\n }\n\n // (2) Take a rolling backup of the *original* (pre-mutation) bytes.\n copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);\n\n // (4) Atomic write: tmp + fsync + rename. This guarantees a reader\n // never sees a half-written file — they see either the old or new.\n const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;\n try {\n writeFileSync(tmpPath, newText, 'utf-8');\n // fsync the tmp file so the bytes are durably on disk before rename.\n const fd = openSync(tmpPath, 'r');\n try { fsyncSync(fd); } finally { closeSync(fd); }\n renameSync(tmpPath, CLAUDE_JSON_PATH);\n } catch (err) {\n // (5) Best-effort recovery: restore the original from backup.\n try { unlinkSync(tmpPath); } catch { /* ignore */ }\n try { copyFileSync(CLAUDE_JSON_BACKUP_PATH, CLAUDE_JSON_PATH); } catch { /* ignore */ }\n throw new LocalCCInstallError(\n `failed to write ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Backup at ${CLAUDE_JSON_BACKUP_PATH} preserves the prior state.`,\n err,\n );\n }\n}\n\n/**\n * Register the synkro-local MCP server at PROJECT scope (.mcp.json inside\n * ~/.synkro/cc_sessions) — NOT at user scope in ~/.claude.json. User scope\n * would make every claude session on the machine spawn the plugin and race\n * to bind the UDS, which corrupts request routing.\n */\nfunction writeProjectMcpJson(): void {\n for (const c of CHANNELS) {\n const mcp = {\n mcpServers: {\n [MCP_SERVER_NAME]: {\n command: 'bun',\n args: [c.pluginPath],\n },\n },\n };\n writeFileSync(c.projectMcpPath, JSON.stringify(mcp, null, 2) + '\\n', 'utf-8');\n }\n}\n\n/**\n * Pre-accept the workspace trust dialog and approve the project-scoped MCP\n * server in ~/.claude.json so claude doesn't block on those prompts under\n * tmux. Also strips any stale top-level mcpServers[\"synkro-local\"] left over\n * from previous (incorrect) installs.\n *\n * All mutations go through safelyMutateClaudeJson — see that function for\n * the integrity guarantees.\n */\nfunction patchClaudeJson(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n\n // Remove any stale user-scope registration from previous installs.\n if (parsed.mcpServers && typeof parsed.mcpServers === 'object' && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n\n if (!parsed.projects || typeof parsed.projects !== 'object') {\n parsed.projects = {};\n }\n const projects = parsed.projects as Record<string, Record<string, unknown>>;\n\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n const existing = projects[dir] && typeof projects[dir] === 'object'\n ? projects[dir]\n : {};\n const wantEnabled = Array.from(new Set([\n ...((existing.enabledMcpjsonServers as string[] | undefined) ?? []),\n MCP_SERVER_NAME,\n ]));\n const next = {\n ...existing,\n hasTrustDialogAccepted: true,\n hasCompletedProjectOnboarding: true,\n enabledMcpjsonServers: wantEnabled,\n };\n if (\n existing.hasTrustDialogAccepted !== true ||\n existing.hasCompletedProjectOnboarding !== true ||\n JSON.stringify(existing.enabledMcpjsonServers ?? []) !== JSON.stringify(wantEnabled)\n ) {\n projects[dir] = next;\n dirty = true;\n }\n }\n return dirty;\n });\n}\n\n/**\n * Run the full local-CC install. Throws LocalCCInstallError on any failure\n * the caller is expected to surface. Does NOT start the pueue task — call\n * `pueue.ensureRunning()` after for that.\n */\nexport function installLocalCC(): { sessionDir: string; pluginPath: string } {\n // bun is required for the plugin runtime — auto-install if missing.\n let bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n if (process.platform === 'darwin') {\n console.log(' Installing bun via brew...');\n const brewR = spawnSync('brew', ['install', 'oven-sh/bun/bun'], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n if (brewR.status !== 0) {\n throw new LocalCCInstallError('bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash');\n }\n bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n throw new LocalCCInstallError('bun installed but not found on PATH. Restart your terminal and re-run install.');\n }\n } else {\n throw new LocalCCInstallError('bun is required. Install it: curl -fsSL https://bun.sh/install | bash');\n }\n }\n\n writePluginFiles();\n runBunInstall();\n writeProjectMcpJson();\n patchClaudeJson();\n\n return { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH };\n}\n\n/**\n * Strip every trace of local-cc from ~/.claude.json:\n * - user-scope mcpServers[\"synkro-local\"] (legacy from older installs)\n * - projects[SESSION_DIR] entry (workspace trust + enabledMcpjsonServers we added)\n *\n * Goes through safelyMutateClaudeJson for the same integrity guarantees as\n * patchClaudeJson — atomic write, backup, post-write validation. Errors are\n * surfaced so the caller sees them, not silently swallowed.\n *\n * Plugin files in ~/.synkro/cc_sessions/ are removed by the caller via\n * `rm -rf ~/.synkro` when uninstalling with --purge.\n */\nexport function uninstallLocalCC(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n if (parsed.mcpServers && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n if (parsed.projects && typeof parsed.projects === 'object' && parsed.projects[dir]) {\n delete parsed.projects[dir];\n dirty = true;\n }\n }\n return dirty;\n });\n}\n","// :)\n/**\n * synkro disconnect — remove all Synkro hook entries from agent settings.\n *\n * Preserves any other hooks the user has (Corridor, Noma, custom).\n *\n * Normal uninstall removes everything — hooks, configs, container, image, and\n * ~/.synkro — EXCEPT the user's scan data (~/.synkro/pgdata and pgdata-backups).\n * `--purge` wipes that data too, after an explicit typed confirmation, leaving\n * the machine as if Synkro was never installed.\n *\n * Order matters: tear down LIVE runtime first (pueue task + tmux session +\n * spawned bun plugin), THEN strip config, THEN remove files. Reversing the\n * order leaves orphaned processes holding the TCP port and a tmux session\n * with claude running from a deleted cwd.\n */\nimport { existsSync, rmSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { uninstallMcpConfig, uninstallCursorMcpConfig, uninstallClaudeDesktopMcpConfig } from '../installer/mcpConfig.js';\nimport { uninstallLocalCC } from '../local-cc/install.js';\nimport { dockerRemove, dockerStatus, dockerSafeStop, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\nimport { emit, flush } from '../telemetry/index.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nasync function tearDownLocalCC(): Promise<void> {\n const docker = dockerStatus();\n if (docker.running) {\n await dockerSafeStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n dockerRemove();\n console.log('✓ removed synkro-server container');\n\n // Remove the Docker image too — a later `synkro install` just re-pulls it.\n try {\n const image = imageTag();\n const r = spawnSync('docker', ['rmi', '-f', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(r.status === 0 ? `✓ removed Docker image ${image}` : '· no Docker image to remove');\n } catch { /* docker may not be installed */ }\n\n if (needsKeychainBridge()) {\n try { uninstallRefreshAgent(); console.log('✓ launchd refresh agent removed'); } catch { /* best-effort */ }\n }\n\n uninstallLocalCC();\n console.log('✓ cleaned ~/.claude.json entries');\n}\n\nfunction confirmPurge(): Promise<boolean> {\n console.log('⚠ WARNING — synkro uninstall --purge');\n console.log(' This permanently deletes ALL Synkro data on this machine,');\n console.log(' including every scan finding, telemetry record, and backup in');\n console.log(' ~/.synkro/pgdata and ~/.synkro/pgdata-backups. It cannot be undone.\\n');\n if (!process.stdin.isTTY) {\n console.log(' Non-interactive shell — re-run in a terminal to confirm.');\n return Promise.resolve(false);\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\" Type 'yes' to wipe everything (anything else cancels): \", (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'yes');\n });\n });\n}\n\nexport async function disconnectCommand(\n args: string[] = [],\n opts: { flavor?: 'disconnect' | 'uninstall' | 'reinstall' } = {},\n): Promise<void> {\n const purge = args.includes('--purge');\n // bootstrap.js routes both `synkro uninstall` and `synkro disconnect` here\n // directly, so an explicit flavor from a wrapper wins; otherwise sniff argv\n // to attribute the event correctly. Reinstall always passes flavor itself.\n const flavor: 'disconnect' | 'uninstall' | 'reinstall' =\n opts.flavor ?? (process.argv[2] === 'uninstall' ? 'uninstall' : 'disconnect');\n const startedAt = Date.now();\n\n // Track which surfaces actually came off so the uninstall event records\n // reality (vs a blanket \"all true\"). DESIGN.md §5.\n const removed = { hooks: false, mcp: false, config: false, telemetry_db: false };\n\n if (purge && !(await confirmPurge())) {\n console.log('\\nAborted — nothing was removed.');\n return;\n }\n\n console.log('\\nSynkro uninstall starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n await tearDownLocalCC();\n\n const agents = detectAgents();\n let sawClaudeCode = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n sawClaudeCode = true;\n const r = uninstallCCHooks(agent.settingsPath);\n if (r) removed.hooks = true;\n console.log(`${r ? '✓' : '·'} ${agent.name}: ${r ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n } else if (agent.kind === 'cursor') {\n const r = uninstallCursorHooks(agent.settingsPath);\n if (r) removed.hooks = true;\n console.log(`${r ? '✓' : '·'} ${agent.name}: ${r ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n }\n }\n\n // Also remove the Synkro guardrails MCP server entries from config files\n // (this is the OTHER mcp server — the rule-suggestions one — distinct from\n // the local-cc channel server cleaned up above).\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n if (mcpRemoved) removed.mcp = true;\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails (CC): ${mcpRemoved ? 'removed from ~/.claude.json' : 'no entry found'}`);\n }\n {\n const cursorMcpRemoved = uninstallCursorMcpConfig();\n if (cursorMcpRemoved) removed.mcp = true;\n console.log(`${cursorMcpRemoved ? '✓' : '·'} MCP guardrails (Cursor): ${cursorMcpRemoved ? 'removed from ~/.cursor/mcp.json' : 'no entry found'}`);\n }\n {\n const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();\n if (desktopMcpRemoved) removed.mcp = true;\n console.log(`${desktopMcpRemoved ? '✓' : '·'} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? 'removed from claude_desktop_config.json' : 'no entry found'}`);\n }\n\n // Filesystem cleanup. Normal uninstall removes everything except the user's\n // scan data (pgdata + pgdata-backups) and the transcript import cursor\n // (.transcript-offsets) — keeping the cursor means a reinstall resumes\n // incremental transcript sync instead of re-sending (and re-embedding)\n // already-imported lines. --purge removes all of it.\n // NOTE: today even --purge wipes the whole ~/.synkro tree, so telemetry.db\n // goes with it. We still leave removed.telemetry_db = false until purge\n // actually targets it surgically — that's a follow-up (DESIGN.md §12).\n if (existsSync(SYNKRO_DIR)) {\n if (purge) {\n // Emit the uninstall event BEFORE the SYNKRO_DIR is wiped — once the dir\n // is gone, the telemetry DB it lives in is gone too, so a later emit\n // would silently no-op.\n emit('uninstall', {\n flavor,\n purge,\n removed: { ...removed, config: true },\n duration_ms: Date.now() - startedAt,\n });\n // Ship the uninstall event (and anything else pending) NOW — rmSync below\n // wipes the JSONL queue, so a later detached flush would find nothing.\n // force bypasses the throttle; best-effort — the container is already down\n // (torn down above), so this only reaches remote, and no-ops if that's off.\n await flush({ force: true });\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n removed.config = true;\n console.log(`✓ wiped ${SYNKRO_DIR} entirely — including all scan data and backups`);\n } else {\n const keep = new Set([\n join(SYNKRO_DIR, 'pgdata'),\n join(SYNKRO_DIR, 'pgdata-backups'),\n join(SYNKRO_DIR, '.transcript-offsets'),\n ]);\n const preserved: string[] = [];\n for (const entry of readdirSync(SYNKRO_DIR)) {\n const full = join(SYNKRO_DIR, entry);\n if (keep.has(full)) { preserved.push(entry); continue; }\n rmSync(full, { recursive: true, force: true });\n }\n removed.config = true;\n if (preserved.length > 0) {\n console.log(`✓ removed Synkro config from ${SYNKRO_DIR} (kept your scan data: ${preserved.join(', ')})`);\n console.log(' run `synkro uninstall --purge` to delete that too');\n } else {\n console.log(`✓ removed ${SYNKRO_DIR}`);\n }\n }\n } else {\n console.log(`· ${SYNKRO_DIR} already gone`);\n }\n\n // Non-purge teardown: emit after the FS cleanup so removed.config is honest.\n // (Purge already emitted above, before the DB was deleted.)\n if (!purge) {\n emit('uninstall', { flavor, purge, removed, duration_ms: Date.now() - startedAt });\n }\n\n console.log(purge\n ? '\\nSynkro fully removed — this machine is clean, as if it was never installed.'\n : '\\nSynkro uninstalled. Your scan data is preserved.');\n}\n","/**\n * JSONL log of every channel turn (grade / classify) — one entry per line.\n *\n * Written by `submitToChannel` after each request completes (success or fail).\n * Read by `synkro local-cc logs` to show recent activity.\n *\n * Best-effort: any I/O error here is swallowed so logging never breaks the\n * actual grading path.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, closeSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { LocalCCRole } from './prompts.js';\n\nexport const TURN_LOG_PATH = join(homedir(), '.synkro', 'cc_sessions', 'turns.log');\n\nexport type TurnStatus = 'ok' | 'timeout' | 'error';\n\nexport interface TurnEntry {\n /** ISO timestamp of when the request started. */\n ts: string;\n role: LocalCCRole;\n /** Wall-clock ms from submit to result/error. */\n duration_ms: number;\n status: TurnStatus;\n /** Truncated request payload (first ~400 chars of caller-supplied text). */\n request_preview: string;\n /** Truncated response result (first ~400 chars). Empty on non-ok. */\n response_preview: string;\n /** Parsed severity if we can extract one from the verdict. Optional. */\n severity?: string;\n /** Error message if status is timeout/error. */\n error?: string;\n}\n\nconst PREVIEW_MAX = 400;\n\nfunction truncate(s: string, max = PREVIEW_MAX): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + '… [+' + (s.length - max) + ' chars]';\n}\n\n/** Best-effort extraction of severity from the result text. */\nfunction extractSeverity(result: string): string | undefined {\n // <synkro-verdict>{\"severity\":\"audit\",…}</synkro-verdict>\n const m = result.match(/<synkro-(?:verdict|intent)>([\\s\\S]*?)<\\/synkro-(?:verdict|intent)>/);\n if (!m) return undefined;\n try {\n const obj = JSON.parse(m[1]) as { severity?: string; verdict?: string; type?: string; ok?: boolean };\n if (obj.severity) return String(obj.severity);\n if (typeof obj.ok === 'boolean') return obj.ok ? 'ok' : 'violations';\n if (obj.type) return String(obj.type);\n if (obj.verdict) return String(obj.verdict);\n } catch {\n // not parseable JSON inside the tag — ignore\n }\n return undefined;\n}\n\n/** Append one turn to the JSONL log. Best-effort — never throws. */\nexport function appendTurn(args: {\n startedAt: number;\n role: LocalCCRole;\n request: string;\n result?: string;\n status: TurnStatus;\n error?: string;\n}): void {\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n const entry: TurnEntry = {\n ts: new Date(args.startedAt).toISOString(),\n role: args.role,\n duration_ms: Date.now() - args.startedAt,\n status: args.status,\n request_preview: truncate(args.request),\n response_preview: args.result ? truncate(args.result) : '',\n severity: args.result ? extractSeverity(args.result) : undefined,\n error: args.error,\n };\n appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // never let logging fail the call site\n }\n}\n\n/**\n * Read the most recent N turn entries (newest first). Returns an empty array\n * on any error.\n */\nexport function readRecentTurns(n = 20): TurnEntry[] {\n if (!existsSync(TURN_LOG_PATH)) return [];\n try {\n // For typical sizes this is fine; if the file grows huge we'd switch to\n // tail-style reverse reading. Cap by stat size for safety.\n const size = statSync(TURN_LOG_PATH).size;\n if (size === 0) return [];\n const text = readFileSync(TURN_LOG_PATH, 'utf-8');\n const lines = text.split('\\n').filter(Boolean);\n const lastN = lines.slice(-n).reverse();\n return lastN\n .map(line => {\n try { return JSON.parse(line) as TurnEntry; } catch { return null; }\n })\n .filter((x): x is TurnEntry => x !== null);\n } catch {\n return [];\n }\n}\n\n/**\n * Tail the turn log: invoke `onEntry` for each new entry appended after the\n * call. Handles file truncation/rotation by re-syncing offset to 0 when size\n * shrinks. Returns a `stop()` to detach the watcher.\n */\nexport function followTurns(onEntry: (t: TurnEntry) => void): () => void {\n // Make sure the file exists so watchFile has something to poll. Creating\n // an empty file is harmless if it already exists.\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n if (!existsSync(TURN_LOG_PATH)) {\n appendFileSync(TURN_LOG_PATH, '', 'utf-8');\n }\n } catch {\n // best-effort\n }\n\n let lastSize = (() => {\n try { return statSync(TURN_LOG_PATH).size; } catch { return 0; }\n })();\n let pendingPartial = ''; // bytes after the last \\n if a write split on a line boundary\n\n const drainNewBytes = (from: number, to: number): void => {\n if (to <= from) return;\n let fd: number | null = null;\n try {\n fd = openSync(TURN_LOG_PATH, 'r');\n const len = to - from;\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, from);\n const text = pendingPartial + buf.toString('utf-8');\n const lastNewline = text.lastIndexOf('\\n');\n if (lastNewline === -1) {\n pendingPartial = text;\n return;\n }\n const complete = text.slice(0, lastNewline);\n pendingPartial = text.slice(lastNewline + 1);\n for (const line of complete.split('\\n')) {\n if (!line) continue;\n try {\n onEntry(JSON.parse(line) as TurnEntry);\n } catch {\n // skip malformed lines\n }\n }\n } catch {\n // best-effort\n } finally {\n if (fd !== null) {\n try { closeSync(fd); } catch { /* noop */ }\n }\n }\n };\n\n // Poll-based watcher (cross-platform reliable). 250ms is responsive\n // enough for tail-f UX without burning CPU.\n watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {\n if (curr.size < lastSize) {\n // file truncated/rotated — reset.\n lastSize = 0;\n pendingPartial = '';\n }\n if (curr.size > lastSize) {\n drainNewBytes(lastSize, curr.size);\n lastSize = curr.size;\n }\n });\n\n return () => unwatchFile(TURN_LOG_PATH);\n}\n","/**\n * Client for the local-CC channel plugin.\n *\n * POSTs to a TCP port on 127.0.0.1 exposed by the channel plugin running\n * inside the pueue-managed `claude` process. Returns the raw text Claude\n * wrote into the `reply` tool's `result` argument.\n */\n\nimport { request as httpRequest } from 'node:http';\nimport { connect } from 'node:net';\nimport type { LocalCCRole } from './prompts.js';\nimport { appendTurn } from './turnLog.js';\n\nexport const CHANNEL_HOST = '127.0.0.1';\nexport const CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || '8929', 10);\n\nconst DEFAULT_TIMEOUT_MS = 90_000;\n\nexport class LocalCCError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCError';\n }\n}\n\nexport interface SubmitOptions {\n /** Timeout for the round-trip in ms (default 90s). */\n timeoutMs?: number;\n /** Override the channel port (default: CHANNEL_PORT / 8929). */\n port?: number;\n}\n\n/**\n * Send a request through the channel and wait for Claude's `reply` tool output.\n * Throws LocalCCError if the channel isn't reachable, the channel times out, or\n * the plugin returns a non-2xx status.\n */\nexport async function submitToChannel(\n role: LocalCCRole,\n payload: string,\n opts: SubmitOptions = {},\n): Promise<string> {\n const body = JSON.stringify({ role, payload, content: payload });\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const port = opts.port ?? CHANNEL_PORT;\n const startedAt = Date.now();\n\n try {\n const result = await new Promise<string>((resolve, reject) => {\n const req = httpRequest({\n host: CHANNEL_HOST,\n port,\n method: 'POST',\n path: '/submit',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n timeout: timeoutMs,\n }, res => {\n const chunks: Buffer[] = [];\n res.on('data', c => chunks.push(c));\n res.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (res.statusCode !== 200) {\n reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));\n return;\n }\n try {\n const parsed = JSON.parse(text) as { result?: string; error?: string };\n if (parsed.error) {\n reject(new LocalCCError(parsed.error));\n return;\n }\n resolve(String(parsed.result ?? ''));\n } catch (err) {\n reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));\n }\n });\n });\n req.on('timeout', () => {\n req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));\n });\n req.on('error', err => {\n const msg = (err as NodeJS.ErrnoException).code === 'ECONNREFUSED'\n ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)`\n : `channel request failed: ${(err as Error).message}`;\n reject(new LocalCCError(msg, err));\n });\n req.write(body);\n req.end();\n });\n appendTurn({ startedAt, role, request: payload, result, status: 'ok' });\n return result;\n } catch (err) {\n const message = (err as Error).message ?? String(err);\n const status = /timed out/i.test(message) ? 'timeout' : 'error';\n appendTurn({ startedAt, role, request: payload, status, error: message });\n throw err;\n }\n}\n\n/** Quick TCP probe — true if anything is listening on the given port (default: CHANNEL_PORT). */\nexport function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, CHANNEL_HOST);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n","/**\n * `synkro grade <edit|bash>` — replacement for the legacy\n * ~/.synkro/bin/grader_daemon.py. Reads a grading payload from stdin,\n * routes it through the local-CC channel, and writes the raw verdict\n * (including the <synkro-verdict>...</synkro-verdict> wrapper that the\n * shell hook scripts grep for) to stdout.\n *\n * Failures print to stderr and exit non-zero so the hook scripts fall\n * through to the cloud fast-tier path cleanly.\n */\n\nimport { submitToChannel, LocalCCError } from '../local-cc/client.js';\nimport type { LocalCCRole } from '../local-cc/prompts.js';\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', c => chunks.push(c));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nexport async function gradeCommand(args: string[]): Promise<void> {\n const mode = args[0] ?? '';\n let role: LocalCCRole;\n if (mode === 'edit') role = 'grade-edit';\n else if (mode === 'bash') role = 'grade-bash';\n else if (mode === 'plan') role = 'grade-plan';\n else if (mode === 'cwe') role = 'grade-cwe';\n else {\n console.error('Usage: synkro grade <edit|bash|plan|cwe>');\n process.exit(2);\n }\n\n const payload = await readStdin();\n if (!payload.trim()) {\n console.error('synkro grade: empty stdin');\n process.exit(2);\n }\n\n try {\n const result = await submitToChannel(role, payload, { timeoutMs: 60_000 });\n process.stdout.write(result);\n if (!result.endsWith('\\n')) process.stdout.write('\\n');\n } catch (err) {\n if (err instanceof LocalCCError) {\n console.error(`synkro grade: ${err.message}`);\n } else {\n console.error(`synkro grade: ${(err as Error).message}`);\n }\n process.exit(3);\n }\n}\n","/**\n * Pueue lifecycle for the local-CC `claude` process.\n *\n * One labelled pueue task per user, started at install (or on demand) and\n * managed via the `pueue` CLI. We assume `pueue` and `pueued` are installed\n * and the daemon is running — surface a friendly error otherwise.\n */\n\nimport { execFileSync, spawnSync, spawn } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { connect } from 'node:net';\n\nexport const TASK_LABEL = 'synkro-local-cc';\nexport const TMUX_SESSION = 'synkro-local-cc';\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\n\nexport const TASK_LABEL_2 = 'synkro-local-cc-2';\nexport const TMUX_SESSION_2 = 'synkro-local-cc-2';\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\n\nexport const TASK_LABEL_3 = 'synkro-local-cc-3';\nexport const TMUX_SESSION_3 = 'synkro-local-cc-3';\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\n\nexport const TASK_LABEL_4 = 'synkro-local-cc-4';\nexport const TMUX_SESSION_4 = 'synkro-local-cc-4';\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\n\nexport class PueueError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'PueueError';\n }\n}\n\ninterface PueueStatusEntry {\n id: number;\n label?: string;\n status: string | { Running?: unknown; Done?: { result?: string } };\n command: string;\n path: string;\n}\n\ninterface PueueStatus {\n tasks: Record<string, PueueStatusEntry>;\n}\n\nfunction pueueAvailable(): void {\n const r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.');\n }\n}\n\nfunction statusJson(): PueueStatus {\n pueueAvailable();\n const r = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || 'unknown error'} — is pueued running?`);\n }\n try {\n return JSON.parse(r.stdout) as PueueStatus;\n } catch (err) {\n throw new PueueError(`pueue status returned non-JSON output: ${r.stdout.slice(0, 200)}`, err);\n }\n}\n\nexport interface TaskInfo {\n id: number;\n label: string;\n status: string;\n command: string;\n cwd: string;\n}\n\nfunction statusName(s: PueueStatusEntry['status']): string {\n if (typeof s === 'string') return s;\n if (s && typeof s === 'object') {\n if ('Running' in s) return 'Running';\n if ('Done' in s) {\n const result = (s as { Done?: { result?: string | object } }).Done?.result;\n if (typeof result === 'string') return `Done (${result})`;\n if (result && typeof result === 'object') return `Done (${Object.keys(result)[0] ?? 'unknown'})`;\n return 'Done';\n }\n return Object.keys(s)[0] ?? 'unknown';\n }\n return 'unknown';\n}\n\nexport interface ChannelIdentity {\n taskLabel: string;\n tmuxSession: string;\n sessionDir: string;\n}\n\nexport const CHANNEL_PRIMARY: ChannelIdentity = { taskLabel: TASK_LABEL, tmuxSession: TMUX_SESSION, sessionDir: SESSION_DIR };\nexport const CHANNEL_SECONDARY: ChannelIdentity = { taskLabel: TASK_LABEL_2, tmuxSession: TMUX_SESSION_2, sessionDir: SESSION_DIR_2 };\nexport const CHANNEL_TERTIARY: ChannelIdentity = { taskLabel: TASK_LABEL_3, tmuxSession: TMUX_SESSION_3, sessionDir: SESSION_DIR_3 };\nexport const CHANNEL_QUATERNARY: ChannelIdentity = { taskLabel: TASK_LABEL_4, tmuxSession: TMUX_SESSION_4, sessionDir: SESSION_DIR_4 };\nexport const ALL_CHANNELS: readonly ChannelIdentity[] = [CHANNEL_PRIMARY, CHANNEL_SECONDARY, CHANNEL_TERTIARY, CHANNEL_QUATERNARY];\n\n/** Find a synkro local-cc task by label. Returns null if absent. */\nexport function findTask(channel: ChannelIdentity = CHANNEL_PRIMARY): TaskInfo | null {\n const data = statusJson();\n for (const [id, t] of Object.entries(data.tasks)) {\n if (t.label === channel.taskLabel) {\n return {\n id: Number(id),\n label: t.label,\n status: statusName(t.status),\n command: t.command,\n cwd: t.path,\n };\n }\n }\n return null;\n}\n\n/** True if a synkro local-cc task is currently Running. */\nexport function isRunning(channel: ChannelIdentity = CHANNEL_PRIMARY): boolean {\n const t = findTask(channel);\n return t?.status === 'Running';\n}\n\nexport interface StartOptions {\n /** Override the channel plugin script path. */\n pluginPath?: string;\n /** Working directory for the claude process. */\n cwd?: string;\n /** Which channel to start (default: primary). */\n channel?: ChannelIdentity;\n}\n\n/**\n * Add a fresh pueue task for the local-cc claude session. Removes any prior\n * task with the same label first (Done or otherwise) so subsequent calls\n * always replace cleanly.\n */\nexport function startTask(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const cwd = opts.cwd ?? ch.sessionDir;\n // Remove ALL tasks with this label (not just the first) to clear stale duplicates\n let existing = findTask(ch);\n while (existing) {\n if (existing.status === 'Running' || existing.status === 'Queued') {\n spawnSync('tmux', ['kill-session', '-t', `=${ch.tmuxSession}`], { encoding: 'utf-8' });\n spawnSync('pueue', ['kill', String(existing.id)], { encoding: 'utf-8' });\n for (let i = 0; i < 10; i++) {\n const check = findTask(ch);\n if (!check || check.id !== existing.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(existing.id)], { encoding: 'utf-8' });\n existing = findTask(ch);\n }\n\n const runScript = join(cwd, 'run-claude.sh');\n const args = [\n 'add',\n '--label', ch.taskLabel,\n '--working-directory', cwd,\n '--',\n 'bash', runScript,\n ];\n\n const r = spawnSync('pueue', args, { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);\n }\n const created = findTask(ch);\n if (!created) {\n throw new PueueError(`pueue add succeeded but no task with label ${ch.taskLabel} found`);\n }\n return created;\n}\n\n/** Stop and remove the synkro local-cc task. Also kills the tmux session\n * that owns the underlying claude process. No-op if neither is present. */\nexport function stopTask(channel: ChannelIdentity = CHANNEL_PRIMARY): void {\n spawnSync('tmux', ['kill-session', '-t', `=${channel.tmuxSession}`], { encoding: 'utf-8' });\n // Remove ALL tasks with this label, waiting for each kill to land\n let t = findTask(channel);\n while (t) {\n if (t.status === 'Running' || t.status === 'Queued') {\n spawnSync('pueue', ['kill', String(t.id)], { encoding: 'utf-8' });\n // Wait for the task to leave Running state before removing\n for (let i = 0; i < 10; i++) {\n const check = findTask(channel);\n if (!check || check.id !== t.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(t.id)], { encoding: 'utf-8' });\n t = findTask(channel);\n }\n}\n\n/** Print the last N log lines for the task. Returns the raw text. */\nexport function tailLogs(lines = 80, channel: ChannelIdentity = CHANNEL_PRIMARY): string {\n const t = findTask(channel);\n if (!t) return `(no ${channel.taskLabel} task)`;\n const r = spawnSync('pueue', ['log', '--lines', String(lines), String(t.id)], { encoding: 'utf-8' });\n return r.stdout || r.stderr || '(no output)';\n}\n\n/** Ensure the task is running; start it if not. Returns the task info. */\nexport function ensureRunning(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const t = findTask(ch);\n if (t && t.status === 'Running') return t;\n return startTask(opts);\n}\n\n/** Probe a TCP port — true if something accepts a connection within timeoutMs. */\nfunction probePort(host: string, port: number, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, host);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n\n/** Dismiss startup prompts in the tmux session. Best-effort.\n * Sends '1' (dev-channels accept) then Enter (other prompts). */\nfunction tmuxDismissPrompts(tmuxSession: string = TMUX_SESSION): void {\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, '1'], { encoding: 'utf-8' });\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, 'Enter'], { encoding: 'utf-8' });\n}\n\n/**\n * Wait up to `timeoutMs` for the channel TCP listener to come up. Each tick\n * the probe fails, send an Enter into the tmux session — that walks claude\n * past any startup confirmation dialogs (workspace trust, dev-channels,\n * MCP consent). The loop self-terminates the moment the port binds, so\n * we don't keep spamming Enters once claude is at the live `❯` prompt.\n */\nexport async function waitForChannelReady(\n port: number,\n timeoutMs = 60_000,\n host = '127.0.0.1',\n tmuxSession: string = TMUX_SESSION,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await probePort(host, port)) return true;\n tmuxDismissPrompts(tmuxSession);\n await new Promise(r => setTimeout(r, 1000));\n }\n return probePort(host, port);\n}\n\nfunction brewInstall(pkg: string): boolean {\n const brew = spawnSync('brew', ['--version'], { encoding: 'utf-8' });\n if (brew.status !== 0) return false;\n console.log(` Installing ${pkg} via brew...`);\n const r = spawnSync('brew', ['install', pkg], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n return r.status === 0;\n}\n\n/** Ensure pueue is installed and pueued daemon is running. Auto-installs on macOS. */\nexport function assertPueueInstalled(): void {\n let r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('pueue')) {\n r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('pueue install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue');\n }\n }\n // Ensure pueued daemon is running\n const status = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (status.status !== 0) {\n console.log(' Starting pueued daemon...');\n const child = spawn('pueued', ['-d'], { stdio: 'ignore', detached: true });\n child.unref();\n // Give the daemon a moment to bind its socket\n spawnSync('sleep', ['1']);\n const retry = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (retry.status !== 0) {\n throw new PueueError('pueue daemon not reachable after starting pueued. Check `pueued` manually.');\n }\n }\n spawnSync('pueue', ['parallel', '2'], { encoding: 'utf-8' });\n}\n\n/** Ensure claude CLI is installed. */\nexport function assertClaudeInstalled(): void {\n const r = spawnSync('claude', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code');\n }\n}\n\n/** Ensure tmux is installed. Auto-installs on macOS. */\nexport function assertTmuxInstalled(): void {\n let r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('tmux')) {\n r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('tmux install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)');\n }\n }\n}\n\n/** No-throw helper used by the install command for status messages. */\nexport function readVersion(bin: string): string | null {\n try {\n return execFileSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {\n return null;\n }\n}\n","/**\n * Inference provider detection.\n *\n * The source of truth is the user's inference_settings in TimescaleDB.\n * At install time, `/api/v1/cli/me` returns `local_inference: true` when\n * `gradingProvider = 'claude-code'`. The install flow writes\n * `SYNKRO_LOCAL_INFERENCE='yes'` into ~/.synkro/config.env.\n *\n * At runtime the hook scripts use a TCP probe (synkro_channel_up) to decide\n * routing — this module is only used by CLI commands and intent routers.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nexport function isLocalCCEnabled(): boolean {\n if (!existsSync(CONFIG_PATH)) return false;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);\n return match?.[1] === 'yes';\n } catch {\n return false;\n }\n}\n","/**\n * `synkro local-cc <subcommand>` — manage the local pueue-backed Claude Code\n * session that powers grading and intent classification when the inference\n * provider toggle is set to local-cc.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { installLocalCC, SESSION_DIR, PLUGIN_PATH, RUN_SCRIPT_PATH, PLUGIN_SETTINGS_PATH, CLAUDE_JSON_PATH, TMUX_SESSION_NAME, CHANNEL_2_PORT, TMUX_SESSION_NAME_2 } from '../local-cc/install.js';\nimport { readRecentTurns, followTurns, TURN_LOG_PATH, type TurnEntry } from '../local-cc/turnLog.js';\nimport {\n assertClaudeInstalled,\n assertPueueInstalled,\n assertTmuxInstalled,\n ensureRunning,\n findTask,\n startTask,\n stopTask,\n tailLogs,\n waitForChannelReady,\n CHANNEL_PRIMARY,\n CHANNEL_SECONDARY,\n} from '../local-cc/pueue.js';\nimport { isLocalCCEnabled } from '../local-cc/settings.js';\nimport { refreshCreds, needsKeychainBridge, credsAreStale, CLAUDE_CREDS_FILE } from '../local-cc/macKeychain.js';\nimport { dockerStatus, dockerStop, dockerUpdate, waitForContainerReady, waitForWorkersReady, resolveWorkerConfig } from '../local-cc/dockerInstall.js';\nimport { reconcileHarness, syncSkillFiles } from './install.js';\nimport { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from 'node:fs';\n\nconst SYNKRO_CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\n/**\n * Resolution order matches commands/install.ts: live env var first, then the\n * persisted config.env value, then 'bare-host'. Kept inline rather than\n * imported from install.ts so this module stays loadable when the install\n * command tree is pruned in future cleanups.\n */\nfunction deploymentMode(): 'bare-host' | 'docker' {\n const env = (process.env.SYNKRO_DEPLOYMENT_MODE || '').toLowerCase();\n if (env === 'docker') return 'docker';\n if (env === 'bare-host') return 'bare-host';\n try {\n if (fsExistsSync(SYNKRO_CONFIG_PATH)) {\n const m = fsReadFileSync(SYNKRO_CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n if (m && m[1] === 'docker') return 'docker';\n }\n } catch { /* fall through */ }\n return 'bare-host';\n}\n\nfunction inDockerMode(): boolean { return deploymentMode() === 'docker'; }\nimport { submitToChannel, isChannelAvailable, CHANNEL_HOST, CHANNEL_PORT } from '../local-cc/client.js';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\n\nfunction printHelp(): void {\n console.log(`synkro local-cc — manage the local Claude Code inference session\n\nOVERVIEW\n Routes Synkro's grading and intent-classification work through a long-running\n Claude Code session on this machine instead of remote Inngest+Gemini.\n\n When enabled, three call sites switch over:\n • security grading on edit/bash hooks\n • intent classification (agent input → structured intent)\n • remediate intent classification\n\n The session is hosted in a detached tmux session managed by a pueue task.\n The CLI talks to it via Claude Code's channels API: a Bun MCP plugin that\n pushes events into the session and receives Claude's responses through a\n \\`reply\\` MCP tool.\n\nUSAGE\n synkro local-cc <subcommand> [args]\n\nSUBCOMMANDS\n enable Install plugin, start pueue task, flip toggle to local-cc\n disable Flip toggle back to inngest (pueue task left running)\n status Show provider toggle, pueue task state, channel reachability\n start Idempotently bring up the pueue task + wait for the channel\n stop Kill the tmux session and remove the pueue task\n restart stop, then start\n install Regenerate ~/.synkro/cc_sessions/ files (plugin, runner, settings)\n logs [N] [--raw] [--live]\n Show the last N (default 20) channel turns: when, role,\n duration, severity, request preview.\n --raw / -r include full request/response payloads\n --live / -f tail the log; print new turns as they arrive\n (Ctrl-C to exit)\n --tmux escape hatch — print the raw pueue/tmux\n pane log instead\n attach [--readonly] Attach to the tmux session hosting claude (Ctrl-B D to detach;\n --readonly / -r to attach view-only)\n test Send a smoke-test classification through the channel\n help Show this message\n\nCONFIGURATION\n Provider toggle:\n Stored server-side in your inference settings (gradingProvider).\n Toggle via: synkro local-cc enable / disable\n Or via dashboard inference settings (set grading provider to claude-code).\n\n Claude Code session settings (scoped to ~/.synkro/cc_sessions only):\n ${PLUGIN_SETTINGS_PATH}\n Currently sets {\"fastMode\": true}. Edit to add other CC settings for this\n session without affecting your other CC projects.\n\n MCP server registration (for the channel plugin):\n ${CLAUDE_JSON_PATH}\n A 'synkro-local' entry under mcpServers, pointing at:\n bun ${PLUGIN_PATH}\n Workspace trust is also pre-accepted under projects[\\`${SESSION_DIR}\\`].\n\n Channel runtime files:\n ${RUN_SCRIPT_PATH}\n bash wrapper that pueue invokes; owns the tmux session lifecycle.\n ${PLUGIN_PATH}\n Bun MCP channel plugin (auto-generated, do not edit).\n 127.0.0.1:${CHANNEL_PORT}\n Loopback TCP endpoint the CLI POSTs to in order to submit a request.\n\nENVIRONMENT VARIABLES\n SYNKRO_CHANNEL_PORT Override the TCP port used by both the plugin and\n the CLI client (loopback only). Default: 8929\n SYNKRO_CHANNEL_TIMEOUT_MS Per-request timeout inside the channel plugin\n (default: 120000)\n\nREQUIRED TOOLS\n claude Claude Code CLI, authenticated to your subscription\n pueue pueue + pueued (https://github.com/Nukesor/pueue) running\n tmux For detached pty around claude\n bun Runtime for the MCP channel plugin\n\nTROUBLESHOOTING\n • Channel unreachable after \\`enable\\`?\n synkro local-cc logs # check pueue task output\n tmux attach -t ${TMUX_SESSION_NAME} # see what claude is showing\n • Stale state after a crash:\n synkro local-cc stop && synkro local-cc start\n • Want to inspect or interact with the live session:\n synkro local-cc attach\n`);\n}\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readGatewayUrl(): string {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);\n if (m) return m[1];\n }\n return 'https://api.synkro.sh';\n}\n\nfunction updateLocalInferenceFlag(enabled: boolean): void {\n if (!existsSync(CONFIG_PATH)) return;\n let content = readFileSync(CONFIG_PATH, 'utf-8');\n const flag = enabled ? 'yes' : 'no';\n if (content.includes('SYNKRO_LOCAL_INFERENCE=')) {\n content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);\n } else {\n content = content.trimEnd() + `\\nSYNKRO_LOCAL_INFERENCE='${flag}'\\n`;\n }\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\nasync function setServerGradingProvider(provider: 'claude-code' | null): Promise<void> {\n await ensureValidToken();\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated. Run `synkro install` first.');\n const gatewayUrl = readGatewayUrl();\n const body = provider\n ? { roles: { grading: { provider, model: 'default' } } }\n : { roles: { grading: { provider: null, model: null } } };\n const resp = await fetch(`${gatewayUrl}/api/settings/inference?scope=user`, {\n method: 'PUT',\n headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`Failed to update inference settings: ${resp.status} ${text.slice(0, 200)}`);\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n console.log(`Local inference: ${isLocalCCEnabled() ? 'enabled' : 'disabled'}`);\n console.log(`Deployment mode: ${deploymentMode()}`);\n\n if (inDockerMode()) {\n const status = dockerStatus();\n if (!status.running) {\n console.log('synkro-server container: not running');\n console.log('Run `synkro install` (with SYNKRO_DEPLOYMENT_MODE=docker) to provision.');\n } else {\n console.log(`synkro-server container: running (${status.image})`);\n try {\n const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3_000) });\n console.log(`Health probe: ${r.ok ? 'ok' : `HTTP ${r.status}`}`);\n } catch (err) {\n console.log(`Health probe: ${(err as Error).message}`);\n }\n }\n if (needsKeychainBridge()) {\n console.log(`Keychain creds: ${credsAreStale() ? 'STALE — run `synkro local-cc refresh-creds`' : 'fresh'}`);\n }\n return;\n }\n\n try {\n assertPueueInstalled();\n } catch (err) {\n console.log(`Pueue: NOT AVAILABLE (${(err as Error).message})`);\n return;\n }\n\n // Channel 1 (judge: bash + edit, port 8929)\n const t = findTask(CHANNEL_PRIMARY);\n if (!t) {\n console.log('Channel 1 (judge) pueue task: not present');\n } else {\n console.log(`Channel 1 (judge) pueue task: id=${t.id} status=${t.status}`);\n }\n const ch1Up = await isChannelAvailable();\n console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? 'reachable' : 'unreachable'}`);\n const tmux1 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? 'live' : 'absent'}`);\n\n // Channel 2 (CWE, port 8930)\n const t2 = findTask(CHANNEL_SECONDARY);\n if (!t2) {\n console.log('Channel 2 (CWE) pueue task: not present');\n } else {\n console.log(`Channel 2 (CWE) pueue task: id=${t2.id} status=${t2.status}`);\n }\n const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);\n console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? 'reachable' : 'unreachable'}`);\n const tmux2 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME_2}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? 'live' : 'absent'}`);\n}\n\nasync function cmdEnable(): Promise<void> {\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n console.log('Installing local-CC channel plugin...');\n const r = installLocalCC();\n console.log(` plugin: ${r.pluginPath}`);\n console.log(` cwd: ${r.sessionDir}`);\n console.log('Starting channel 1 (judge)...');\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(` task: id=${t1.id} status=${t1.status}`);\n console.log('Starting channel 2 (CWE)...');\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(` task: id=${t2.id} status=${t2.status}`);\n console.log('Waiting for channels (auto-confirming any CC prompts)...');\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n if (ready1) console.log(` channel 1 ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);\n else console.warn(` ⚠ channel 1 did not come up within 60s — check \\`synkro local-cc logs\\``);\n if (ready2) console.log(` channel 2 ready at ${CHANNEL_HOST}:${CHANNEL_2_PORT}`);\n else console.warn(` ⚠ channel 2 (CWE) did not come up within 60s`);\n console.log('Updating inference settings...');\n await setServerGradingProvider('claude-code');\n updateLocalInferenceFlag(true);\n console.log('Grading provider set to claude-code (local inference enabled).');\n}\n\nasync function cmdDisable(): Promise<void> {\n console.log('Updating inference settings...');\n await setServerGradingProvider(null);\n updateLocalInferenceFlag(false);\n console.log('Grading provider cleared (remote inference restored). Pueue task left running — use `synkro local-cc stop` to terminate.');\n}\n\nasync function warmChannels(ready1: boolean, ready2: boolean): Promise<void> {\n const warmups: Promise<void>[] = [];\n if (ready1) {\n warmups.push(\n submitToChannel('grade-bash', 'Proposed command: echo hello\\nUser intent: warmup\\nRecent user messages: []\\nRecent actions: []\\nOrg rules: []\\n', { timeoutMs: 30_000 })\n .then(() => console.log(' channel 1 warm.'))\n .catch(() => console.log(' channel 1 warmup skipped (non-fatal).'))\n );\n }\n if (ready2) {\n warmups.push(\n submitToChannel('grade-cwe', 'File: /tmp/warmup.ts\\nContent (first 4000 chars):\\nconsole.log(\"hello\");\\n\\nCWE rules to check against:\\n[]\\n', { timeoutMs: 30_000, port: CHANNEL_2_PORT })\n .then(() => console.log(' channel 2 warm.'))\n .catch(() => console.log(' channel 2 warmup skipped (non-fatal).'))\n );\n }\n if (warmups.length) {\n console.log('Warming up inference...');\n await Promise.all(warmups);\n }\n}\n\nasync function cmdStart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n // With worker flags, `start` (re)creates the container with the requested\n // pool config. Without flags it just ensures the container is running.\n if (rest.length > 0) {\n const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);\n console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n return;\n }\n // Container is normally managed by docker's `--restart unless-stopped`,\n // so \"start\" really just means: ensure it's running and healthy. If it's\n // not even installed, point the user at `synkro install`.\n const status = dockerStatus();\n if (!status.running) {\n console.warn('synkro-server container is not running. Run `synkro install` to provision it.');\n process.exitCode = 1;\n return;\n }\n console.log('synkro-server container already running — waiting for /healthz...');\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? `✓ container ready (${status.healthz})` : '⚠ container did not pass /healthz within 60s');\n return;\n }\n\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(`Channel 1 (judge): id=${t1.id} status=${t1.status}`);\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 2 (CWE): id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction cmdStop(): void {\n if (inDockerMode()) {\n dockerStop();\n console.log('synkro-server container stopped and removed.');\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n console.log('Both channels stopped.');\n}\n\nasync function cmdRestart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n const { explicit } = resolveWorkerConfig(rest);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (explicit) {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n } else {\n const reconciled = reconcileHarness();\n if (reconciled) {\n ({ claudeWorkers, cursorWorkers } = reconciled);\n } else {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n }\n }\n console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n if (ready) {\n const workersUp = await waitForWorkersReady(30_000);\n console.log(workersUp ? '✓ workers ready' : '⚠ workers did not register within 30s');\n if (workersUp) await syncSkillFiles();\n }\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n const t1 = startTask({ channel: CHANNEL_PRIMARY });\n const t2 = startTask({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 1 restarted: id=${t1.id} status=${t1.status}`);\n console.log(`Channel 2 restarted: id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction relativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (!Number.isFinite(ts)) return iso;\n const sec = Math.max(0, Math.round((Date.now() - ts) / 1000));\n if (sec < 60) return `${sec}s ago`;\n if (sec < 3600) return `${Math.round(sec / 60)}m ago`;\n if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;\n return `${Math.round(sec / 86400)}d ago`;\n}\n\nfunction colorize(s: string, code: number): string {\n if (!process.stdout.isTTY) return s;\n return `\u001b[${code}m${s}\u001b[0m`;\n}\n\nfunction statusGlyph(t: TurnEntry): string {\n if (t.status === 'ok') return colorize('✓', 32); // green check\n if (t.status === 'timeout') return colorize('⏱', 33); // yellow clock\n return colorize('✗', 31); // red X\n}\n\nfunction severityCell(t: TurnEntry): string {\n if (t.severity) {\n const sev = t.severity;\n if (sev === 'block' || sev === 'violations' || sev === 'unclear') return colorize(sev, 33);\n return colorize(sev, 36); // cyan for everything else\n }\n if (t.error) return colorize(t.error.slice(0, 40), 31);\n return '—';\n}\n\nfunction firstLine(s: string): string {\n return s.split('\\n').find(l => l.trim().length > 0)?.trim() ?? '';\n}\n\nfunction formatTurn(t: TurnEntry, raw: boolean): string {\n const when = relativeTime(t.ts).padEnd(8);\n const dur = (t.duration_ms < 1000\n ? `${t.duration_ms}ms`\n : `${(t.duration_ms / 1000).toFixed(1)}s`).padStart(7);\n const role = t.role.padEnd(24);\n const sev = severityCell(t).padEnd(20);\n const preview = (() => {\n const req = firstLine(t.request_preview).slice(0, 60);\n return colorize(req, 90);\n })();\n const head = `${statusGlyph(t)} ${when} ${dur} ${role} ${sev} ${preview}`;\n if (!raw) return head;\n // raw mode: include full request + response previews on subsequent lines\n const blocks: string[] = [head];\n blocks.push(colorize(' request:', 90));\n blocks.push(' ' + t.request_preview.replace(/\\n/g, '\\n '));\n if (t.response_preview) {\n blocks.push(colorize(' response:', 90));\n blocks.push(' ' + t.response_preview.replace(/\\n/g, '\\n '));\n }\n if (t.error) {\n blocks.push(colorize(' error:', 31) + ' ' + t.error);\n }\n return blocks.join('\\n');\n}\n\nfunction cmdLogs(rest: string[]): void | Promise<void> {\n let n = 20;\n let raw = false;\n let live = false;\n\n if (inDockerMode()) {\n // In docker mode the meaningful logs come from the container itself: MCP\n // boot, dispatcher routing, worker tmux output. Forward to `docker logs`\n // and let docker handle the --follow / --tail semantics.\n const followFlag = rest.includes('--live') || rest.includes('-f') ? ['--follow'] : [];\n const tailArg = (() => {\n for (const arg of rest) {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) return String(parsed);\n }\n return '200';\n })();\n spawnSync('docker', ['logs', '--tail', tailArg, ...followFlag, 'synkro-server'], { stdio: 'inherit' });\n return;\n }\n\n for (const arg of rest) {\n if (arg === '--raw' || arg === '-r') raw = true;\n else if (arg === '--live' || arg === '-f') live = true;\n else if (arg === '--tmux') {\n // Escape hatch: show the original raw pueue/tmux output for the task.\n console.log(tailLogs(80));\n return;\n } else {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) n = parsed;\n }\n }\n\n const header = ' ' + colorize('status when dur role severity request', 90);\n\n // Print backlog (newest first) — same as non-live mode.\n const turns = readRecentTurns(n);\n if (turns.length === 0) {\n if (!live) {\n console.log(`No turns logged yet at ${TURN_LOG_PATH}.`);\n console.log('Run a few requests through the channel (synkro local-cc test) and try again.');\n return;\n }\n console.log(`No turns logged yet at ${TURN_LOG_PATH} — waiting for new entries… (Ctrl-C to exit)`);\n } else {\n console.log(`Last ${turns.length} channel turn(s) (newest first):`);\n console.log(header);\n for (const t of turns) console.log(' ' + formatTurn(t, raw));\n }\n\n if (!live) {\n if (!raw) console.log(' ' + colorize('(use --raw / -r to see full payloads, --live / -f to follow)', 90));\n return;\n }\n\n // Live tail. Follow the JSONL log; print each new entry as it arrives.\n // Returning a Promise keeps the process alive until SIGINT.\n return new Promise<void>(resolve => {\n console.log(' ' + colorize('— following new turns (Ctrl-C to exit) —', 90));\n const stop = followTurns(t => {\n console.log(' ' + formatTurn(t, raw));\n });\n const onSigint = () => {\n stop();\n process.removeListener('SIGINT', onSigint);\n resolve();\n };\n process.on('SIGINT', onSigint);\n });\n}\n\nfunction cmdAttach(rest: string[]): void {\n assertTmuxInstalled();\n const readonly = rest.some(a => a === '--readonly' || a === '-r');\n\n // Verify the session exists before tmux drops the user into a confusing state.\n const has = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n if (has.status !== 0) {\n console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);\n process.exit(1);\n }\n\n if (!process.stdout.isTTY) {\n console.error('attach requires a TTY. Run this command directly in your terminal, not piped.');\n process.exit(1);\n }\n\n console.log(`Attaching to tmux session '${TMUX_SESSION_NAME}'${readonly ? ' (read-only)' : ''}.`);\n console.log('Detach with Ctrl-B then D. (Do not press Ctrl-C — that would interrupt claude.)');\n console.log();\n\n const args = readonly\n ? ['attach-session', '-r', '-t', TMUX_SESSION_NAME]\n : ['attach-session', '-t', TMUX_SESSION_NAME];\n const r = spawnSync('tmux', args, { stdio: 'inherit' });\n process.exit(r.status ?? 0);\n}\n\nasync function cmdTest(): Promise<void> {\n // In docker mode the channel is published on the HOST at SYNKRO_HOST_GRADER_PORT\n // (default 18929 → container 8929). CHANNEL_PORT (8929) is the IN-container port and\n // isn't bound on the host, so the default would always \"connection refused\" even when\n // grading is healthy. Use the docker-mapped host port when in docker mode.\n const port = inDockerMode()\n ? parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10)\n : CHANNEL_PORT;\n console.log(`Sending smoke-test grading request through the channel (127.0.0.1:${port})...`);\n const result = await submitToChannel(\n 'grade-bash',\n 'Command: echo \"hello world\"\\nIntent: testing channel connectivity',\n { port },\n );\n console.log('Raw reply:');\n console.log(result);\n}\n\nfunction cmdInstall(): void {\n const r = installLocalCC();\n console.log(`Reinstalled plugin at ${r.pluginPath}`);\n}\n\n/**\n * Re-export Claude Code credentials from the macOS keychain into the\n * host-side file the container bind-mounts. Called by the launchd refresh\n * agent every 6 hours, and exposed as `synkro local-cc refresh-creds` for\n * manual invocation if the user just re-authed.\n *\n * On Linux this is a no-op — creds already live in a file the container can\n * read directly via bind mount.\n */\nfunction cmdRefreshCreds(): void {\n if (!needsKeychainBridge()) {\n console.log('No-op on this platform — credentials are already file-based.');\n return;\n }\n const refreshed = refreshCreds();\n if (refreshed) {\n console.log(`Exported claude credentials to ${CLAUDE_CREDS_FILE}`);\n } else {\n console.warn('No Claude Code credentials found in the keychain.');\n console.warn('Run `claude login` first, then re-run this command.');\n process.exitCode = 1;\n }\n if (credsAreStale()) {\n console.warn('⚠ Exported credentials are older than the refresh interval.');\n }\n}\n\nexport async function localCcCommand(args: string[]): Promise<void> {\n const sub = args[0] ?? '';\n try {\n switch (sub) {\n case 'enable': await cmdEnable(); break;\n case 'disable': cmdDisable(); break;\n case 'status': await cmdStatus(); break;\n case 'start': await cmdStart(args.slice(1)); break;\n case 'stop': cmdStop(); break;\n case 'restart': await cmdRestart(args.slice(1)); break;\n case 'install': cmdInstall(); break;\n case 'logs': await cmdLogs(args.slice(1)); break;\n case 'attach': cmdAttach(args.slice(1)); break;\n case 'test': await cmdTest(); break;\n case 'refresh-creds': cmdRefreshCreds(); break;\n case '':\n case 'help':\n case '--help':\n case '-h':\n printHelp(); break;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n printHelp();\n process.exit(1);\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n","/**\n * synkro import — backfill historical Claude Code sessions for THIS repo.\n *\n * Walks ~/.claude/projects/<sanitized-cwd>/*.jsonl (every past session in the\n * repo), parses each turn (user/assistant content + per-turn usage + tool calls),\n * and ships them to storage so the dashboard can show every historical session\n * line-by-line.\n *\n * • CLOUD mode → ships to the API /api/v1/cli/sync-transcripts (automatic).\n * • LOCAL mode → on-device only, and CONSENT-GATED: we prompt before importing\n * since it ingests the repo's full transcript history.\n *\n * Idempotent: message ids are deterministic (session:seq), so re-running is safe.\n */\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n const out: Record<string, string> = {};\n try {\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n } catch {}\n return out;\n}\n\nfunction projectsFolder(): string | null {\n // CC stores transcripts in ~/.claude/projects/<cwd with '/' -> '-'>. The leading\n // slash already becomes the leading dash — do NOT prepend another '-'.\n const sanitized = process.cwd().replace(/\\//g, '-');\n const dir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(dir) ? dir : null;\n}\n\nfunction repoName(): string {\n // Use the git remote \"owner/repo\" so it matches the project the hook already\n // created (project_repos.full_name). A bare basename would create a duplicate.\n try {\n const url = execSync('git config --get remote.origin.url', { encoding: 'utf-8' }).trim();\n const m = url.match(/[:/]([^/:]+\\/[^/]+?)(?:\\.git)?$/);\n if (m) return m[1];\n } catch {}\n try { return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim().split('/').pop() || 'repo'; }\n catch { return process.cwd().split('/').pop() || 'repo'; }\n}\n\nfunction extractText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .map((b: any) => (typeof b === 'string' ? b : b?.type === 'text' ? (b.text || '') : ''))\n .filter(Boolean)\n .join('\\n');\n }\n return '';\n}\n\ninterface ParsedSession {\n cc_session_id: string;\n messages: Array<Record<string, unknown>>;\n actions: Array<Record<string, unknown>>;\n}\n\nfunction parseSession(filePath: string, sessionId: string): ParsedSession {\n const lines = readFileSync(filePath, 'utf-8').split('\\n').filter(Boolean);\n const messages: Array<Record<string, unknown>> = [];\n const actions: Array<Record<string, unknown>> = [];\n let step = 0;\n for (let i = 0; i < lines.length; i++) {\n let e: any;\n try { e = JSON.parse(lines[i]); } catch { continue; }\n const kind = e.type === 'assistant' || e.message?.role === 'assistant' ? 'assistant'\n : e.type === 'user' || e.message?.role === 'user' ? 'user' : '';\n if (!kind) continue;\n const msgObj = e.message;\n // Tool RESULTS are role:user entries carrying a `toolUseResult` and/or `tool_result`/`tool_use_id`\n // content block. Classify them STRUCTURALLY here (→ 'tool') so the server trusts the CLI's typing\n // instead of guessing positionally — a purely positional \"user-turn-after-a-tool-call = tool\" rule\n // false-positives real user interjections like \"keep going\" typed mid-tool-sequence.\n const _cr = msgObj?.content ?? e.content;\n const isToolResult = kind === 'user' && (\n !!e.toolUseResult ||\n (Array.isArray(_cr) && _cr.some((b: any) => b?.type === 'tool_result' || b?.tool_use_id != null))\n );\n const finalKind = isToolResult ? 'tool' : kind;\n const text = extractText(msgObj?.content ?? e.content);\n // Tool calls FIRST — a tool-only assistant turn (Edit/Bash/Read, no text) must\n // still be captured (it's most of a coding session); skipping on empty text\n // dropped both the turn and its tool calls.\n const blocks = kind === 'assistant' && Array.isArray(msgObj?.content) ? msgObj.content : [];\n const toolCalls = blocks\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({ name: b.name, input: JSON.stringify(b.input || {}).slice(0, 20000), id: b.id }));\n if (!text.trim() && toolCalls.length === 0) continue;\n const msg: Record<string, unknown> = {\n message_index: i, type: finalKind,\n // CC's per-entry uuid is STABLE across transcript rewrites/compaction; message_index (line\n // number) is not. The server keys conversation_messages on this uuid so re-imports update in\n // place instead of orphaning/duplicating rows. Falls back to message_index server-side if absent.\n uuid: typeof e.uuid === 'string' ? e.uuid : undefined,\n content: (text.trim() ? text.slice(0, 8000) : '↳ ' + toolCalls.map((t: any) => t.name).join(', ')),\n timestamp: e.timestamp || null,\n };\n if (kind === 'assistant') {\n if (typeof msgObj?.model === 'string') msg.model = msgObj.model;\n const u = msgObj?.usage;\n if (u && typeof u === 'object') {\n msg.usage = {\n input_tokens: Number(u.input_tokens) || 0,\n output_tokens: Number(u.output_tokens) || 0,\n cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,\n cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0,\n };\n }\n if (toolCalls.length) {\n msg.tool_calls = toolCalls;\n for (const t of toolCalls) actions.push({ step: ++step, tool: t.name, summary: '', file: null, outcome: null });\n }\n }\n messages.push(msg);\n }\n return { cc_session_id: sessionId, messages, actions };\n}\n\nfunction ask(q: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => rl.question(q, (a) => { rl.close(); resolve(/^y(es)?$/i.test(a.trim())); }));\n}\n\nexport async function importCommand(): Promise<void> {\n const config = readConfigEnv();\n const isCloud = config.SYNKRO_DEPLOY_LOCATION === 'cloud' || config.SYNKRO_STORAGE_MODE === 'cloud';\n const repo = repoName();\n\n const dir = projectsFolder();\n if (!dir) { console.log('No Claude Code transcripts found for this repo (~/.claude/projects).'); return; }\n const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));\n if (!files.length) { console.log('No sessions to import.'); return; }\n\n console.log(`Found ${files.length} session(s) for \"${repo}\" (${isCloud ? 'cloud' : 'local'} mode).`);\n\n // LOCAL: ingesting the full transcript history is consent-gated.\n if (!isCloud) {\n const ok = await ask(`Import all ${files.length} historical session(s) into your on-device store? [y/N] `);\n if (!ok) { console.log('Aborted — nothing imported.'); return; }\n }\n\n const sessions = files.map((f) => parseSession(join(dir, f), f.replace('.jsonl', '')))\n .filter((s) => s.messages.length > 0);\n const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);\n\n let ok = 0, fail = 0;\n if (isCloud) {\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.log('Not authenticated — run `synkro login` first.'); return; }\n const gateway = config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh';\n // One session per call so large sessions (up to the endpoint's per-session cap)\n // land fully without a giant multi-session payload.\n for (const s of sessions) {\n try {\n const r = await fetch(`${gateway}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({ repo, sessions: [s] }),\n signal: AbortSignal.timeout(60000),\n });\n if (r.ok) { ok++; process.stdout.write('.'); } else { fail++; console.warn(`\\n ${String(s.cc_session_id).slice(0, 8)} failed: HTTP ${r.status}`); }\n } catch (e) { fail++; console.warn(`\\n ${String(s.cc_session_id).slice(0, 8)} error: ${(e as Error).message}`); }\n }\n process.stdout.write('\\n');\n } else {\n const port = config.SYNKRO_MCP_PORT || '18931';\n for (const s of sessions) {\n try {\n const r = await fetch(`http://127.0.0.1:${port}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ capture_type: 'transcript_sync', session_id: s.cc_session_id, repo, messages: s.messages, actions: s.actions }),\n signal: AbortSignal.timeout(15000),\n });\n if (r.ok) ok++; else fail++;\n } catch { fail++; }\n }\n if (fail && ok === 0) console.log(' (is the local container running? `synkro status`)');\n }\n\n console.log(`\\n✓ Imported ${ok} session(s) (${totalMsgs} messages)${fail ? `, ${fail} failed` : ''}.`);\n}\n","// :)\n/**\n * Client-side pack verification — the trust gate for `synkro sync`.\n *\n * Mirrors packages/api/src/lib/packSigning.ts BYTE-FOR-BYTE: the canonical form\n * (sorted-key JSON over {rules, docs, manifest}) must reproduce exactly the\n * bytes the publisher hashed + signed, or the digest check fails. Nothing binds\n * unless verifyPack() returns true — recompute the digest from what we fetched,\n * confirm it matches the claimed digest, then verify the Ed25519 signature\n * against the distributed public key. Fail closed.\n */\n\nimport crypto from 'node:crypto';\n\nexport interface PackContent {\n rules: unknown[];\n docs?: Array<{ path: string; content: string }>;\n manifest?: Record<string, unknown>;\n}\n\n/** Deterministic JSON: object keys sorted recursively, arrays order-preserved. */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';\n}\n\n// CONTENT address — rules + docs only, NOT the manifest. Must stay byte-identical\n// to the API's packSigning.canonicalize so a client-recomputed digest matches.\nexport function canonicalize(pack: PackContent): string {\n return stableStringify({\n rules: pack.rules ?? [],\n docs: pack.docs ?? [],\n });\n}\n\nexport function computeDigest(canonical: string): string {\n return 'sha256:' + crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');\n}\n\nexport function verifySignature(digest: string, signatureB64: string, publicKeyPem: string): boolean {\n try {\n const key = crypto.createPublicKey(publicKeyPem);\n return crypto.verify(null, Buffer.from(digest, 'utf8'), key, Buffer.from(signatureB64, 'base64'));\n } catch {\n return false;\n }\n}\n\n/**\n * Recompute the digest from the pack we fetched, confirm it equals the claimed\n * digest (catches a tampered blob/row), then verify the signature. Fail closed.\n */\nexport function verifyPack(\n pack: PackContent,\n claimedDigest: string,\n signatureB64: string,\n publicKeyPem: string,\n): boolean {\n const recomputed = computeDigest(canonicalize(pack));\n if (recomputed !== claimedDigest) return false;\n return verifySignature(claimedDigest, signatureB64, publicKeyPem);\n}\n","// :)\n/**\n * synkro.lock — the trust anchor committed alongside synkro.toml.\n *\n * For every subscribed pack it pins the exact attested (version, digest,\n * signature, signing key). `synkro sync` resolves floating refs to a concrete\n * digest, verifies it, and records it here so the same bytes are reproducible\n * and a tampered re-resolve is detectable. Hand-rolled TOML writer/reader to\n * match the repo's dependency-free synkro.toml handling.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface LockEntry {\n ref: string; // \"namespace/name\"\n version: string;\n digest: string; // sha256:<hex>\n signature: string; // Ed25519, base64\n signingKeyId: string;\n}\n\nconst LOCK_FILE = 'synkro.lock';\n\nexport function lockPath(repoRoot: string): string {\n return join(repoRoot, LOCK_FILE);\n}\n\n/** Parse the [[pack]] array-of-tables in synkro.lock. Tolerant of absence. */\nexport function readLockfile(repoRoot: string): LockEntry[] {\n const fp = lockPath(repoRoot);\n if (!existsSync(fp)) return [];\n const out: LockEntry[] = [];\n let cur: Partial<LockEntry> | null = null;\n const flush = () => {\n if (cur && cur.ref && cur.version && cur.digest) {\n out.push({\n ref: cur.ref, version: cur.version, digest: cur.digest,\n signature: cur.signature ?? '', signingKeyId: cur.signingKeyId ?? '',\n });\n }\n cur = null;\n };\n for (const rawLine of readFileSync(fp, 'utf-8').split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n if (line === '[[pack]]') { flush(); cur = {}; continue; }\n if (!cur) continue;\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n const val = line.slice(eq + 1).trim().replace(/^[\"']|[\"']$/g, '');\n if (key === 'ref') cur.ref = val;\n else if (key === 'version') cur.version = val;\n else if (key === 'digest') cur.digest = val;\n else if (key === 'signature') cur.signature = val;\n else if (key === 'signing_key_id') cur.signingKeyId = val;\n }\n flush();\n return out;\n}\n\nexport function writeLockfile(repoRoot: string, entries: LockEntry[]): void {\n const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));\n const body = [\n '# synkro.lock — generated by `synkro sync`. Commit this file.',\n '# Pins each subscribed pack to its verified, attested digest.',\n '',\n ...sorted.flatMap((e) => ([\n '[[pack]]',\n `ref = \"${e.ref}\"`,\n `version = \"${e.version}\"`,\n `digest = \"${e.digest}\"`,\n `signature = \"${e.signature}\"`,\n `signing_key_id = \"${e.signingKeyId}\"`,\n '',\n ])),\n ].join('\\n');\n writeFileSync(lockPath(repoRoot), body, 'utf-8');\n}\n","// :)\n/**\n * `synkro sync` — pull the registry packs a repo subscribes to, verify them,\n * and apply.\n *\n * Reads the [standards] table from synkro.toml, resolves each ref against the\n * gateway, VERIFIES the Ed25519 signature + content digest locally (nothing\n * binds unless it passes), pins the result in synkro.lock, and materializes:\n * • local → verified pack JSON in ~/.synkro/cache/packs/ (read by the\n * container's readRules; the repo tree stays clean — toml + lock only).\n * • cloud → lockfile only; mandatory/subscribed packs are injected\n * server-side at grade time (Timescale). local → pglite / cloud → timescale.\n */\n\nimport { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { readFullSynkroFile } from './install.js';\nimport { ensureValidToken, getAccessToken } from '../auth/stub.js';\nimport { verifyPack, type PackContent } from '../installer/packVerify.js';\nimport { writeLockfile, type LockEntry } from '../installer/lockfile.js';\n\ninterface ResolveResponse extends PackContent {\n ref: string;\n version: string;\n digest: string;\n signature: string;\n signingKeyId: string;\n publicKey: string;\n}\n\nfunction cacheKey(ref: string, version: string): string {\n return ref.replace(/\\//g, '__').replace(/[^\\w.@-]/g, '_') + '@' + version + '.json';\n}\n\nexport async function syncCommand(_args: string[] = []): Promise<void> {\n // Cloud mode applies standards 100% server-side: org-mandatory packs (all\n // repos or selected) are enforced at the harness by org, with no synkro.toml.\n // `synkro sync` is a local-only concept — there's nothing to pull on cloud.\n if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {\n console.log('Cloud mode: standards are applied org-wide from the dashboard');\n console.log('(Settings → Standards). No synkro.toml or `synkro sync` needed —');\n console.log('packs enforce automatically wherever Synkro is installed.');\n return;\n }\n\n const sf = readFullSynkroFile();\n if (!sf) {\n console.error('No synkro.toml found in the repo root. Run `synkro install` first.');\n process.exitCode = 1;\n return;\n }\n\n const refs = Object.entries(sf.standards);\n if (refs.length === 0) {\n console.log('No [standards] subscriptions in synkro.toml — nothing to sync.');\n return;\n }\n\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) {\n console.error('Not logged in. Run `synkro login` and try again.');\n process.exitCode = 1;\n return;\n }\n\n const gateway = (process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const cloud = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud';\n const cacheDir = join(homedir(), '.synkro', 'cache', 'packs');\n if (!cloud) mkdirSync(cacheDir, { recursive: true });\n\n console.log(`Syncing ${refs.length} standard(s) from the registry…`);\n const lock: LockEntry[] = [];\n const keptCacheFiles = new Set<string>();\n\n for (const [ref, version] of refs) {\n let url = `${gateway}/api/v1/registry/resolve?ref=${encodeURIComponent(ref)}`;\n if (version && version !== 'latest') url += `&version=${encodeURIComponent(version)}`;\n let data: ResolveResponse;\n try {\n const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });\n if (!resp.ok) {\n console.error(` ✗ ${ref}: resolve failed (${resp.status} ${resp.statusText})`);\n continue;\n }\n data = await resp.json() as ResolveResponse;\n } catch (err) {\n console.error(` ✗ ${ref}: ${(err as Error).message}`);\n continue;\n }\n\n const pack: PackContent = { rules: data.rules ?? [], docs: data.docs ?? [], manifest: data.manifest ?? {} };\n if (!verifyPack(pack, data.digest, data.signature, data.publicKey)) {\n console.error(` ✗ ${ref}: signature/digest verification FAILED — refusing to apply.`);\n continue;\n }\n\n lock.push({ ref, version: data.version, digest: data.digest, signature: data.signature, signingKeyId: data.signingKeyId });\n\n if (!cloud) {\n const fname = cacheKey(ref, data.version);\n keptCacheFiles.add(fname);\n writeFileSync(join(cacheDir, fname), JSON.stringify({\n ref, version: data.version, digest: data.digest,\n rules: pack.rules, docs: pack.docs, manifest: pack.manifest,\n }), 'utf-8');\n }\n\n const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;\n console.log(` ✓ ${ref}:${data.version} — verified (${ruleCount} rule${ruleCount === 1 ? '' : 's'})`);\n }\n\n // Local: drop cache packs no longer subscribed so a removed [standards] line\n // actually stops enforcing. (Synced packs are read-only here — edit upstream\n // and re-publish, never hand-edit the cache.)\n if (!cloud && existsSync(cacheDir)) {\n for (const f of readdirSync(cacheDir)) {\n if (f.endsWith('.json') && !keptCacheFiles.has(f)) {\n try { rmSync(join(cacheDir, f)); } catch {}\n }\n }\n }\n\n if (lock.length > 0) {\n writeLockfile(sf._repoRoot, lock);\n console.log(`Wrote synkro.lock (${lock.length} pack${lock.length === 1 ? '' : 's'} pinned).`);\n } else {\n console.error('No packs synced successfully.');\n process.exitCode = 1;\n }\n}\n","/**\n * synkro stop / start / restart — safe container lifecycle commands.\n *\n * These ensure PGLite data is always persisted to the host before shutdown,\n * and verify integrity on start. Agents should use these instead of raw\n * docker commands to avoid data corruption.\n */\nimport {\n dockerSafeStop, dockerSafeStart, dockerSafeRestart, assertDockerAvailable,\n resolveWorkerConfig, dockerUpdate, waitForContainerReady, waitForWorkersReady, readContainerConfig,\n} from '../local-cc/dockerInstall.js';\nimport { detectGitRepo, reconcileHarness, syncSkillFiles, reconcileDeployLocation, recycleCloudContainer } from './install.js';\n\n/**\n * The connected repo for a container recreate. start/restart/update all run\n * from inside the repo, so detect it live; fall back to whatever the existing\n * container recorded. Without this, recreating the container drops\n * SYNKRO_CONNECTED_REPO and the dashboard loses its project anchor.\n */\nfunction resolveConnectedRepo(): string | undefined {\n return detectGitRepo() ?? readContainerConfig()?.connectedRepo ?? undefined;\n}\n\nexport async function stopCommand(): Promise<void> {\n assertDockerAvailable();\n console.log('Synkro: stopping server\\n');\n const result = await dockerSafeStop();\n if (!result.ok) {\n console.error('\\nStop failed. Check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nServer stopped.');\n}\n\nexport async function startCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n // With --workers / --provider(s) flags, recreate the container with the\n // requested pool config (container env is fixed at `docker run` time, so a\n // plain `docker start` can't change it). Without flags, plain safe-start.\n const cfg = resolveWorkerConfig(rest);\n if (cfg.explicit) {\n console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer is running.');\n return;\n }\n console.log('Synkro: starting server\\n');\n const result = await dockerSafeStart();\n if (!result.ok) {\n console.error(`\\nStart failed: ${result.error}`);\n process.exit(1);\n }\n console.log('\\nServer is running.');\n}\n\nexport async function updateCommand(): Promise<void> {\n assertDockerAvailable();\n // Read the installed container's pool config so the rebuilt container keeps\n // the same worker split instead of resetting to defaults.\n const cfg = readContainerConfig();\n if (!cfg) {\n console.error('No synkro-server container found. Run `synkro install` first.');\n process.exit(1);\n }\n const claudeWorkers = cfg.claudeWorkers ?? 8;\n const cursorWorkers = cfg.cursorWorkers ?? 0;\n console.log('Synkro: updating to the latest container image');\n console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)\\n`);\n // dockerUpdate: graceful stop (snapshot + checkpoint) → remove → pull latest → recreate.\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(90_000);\n if (!ready) {\n console.error('\\n⚠ container did not pass its health check within 90s — check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nSynkro updated — now running the latest version.');\n}\n\nexport async function restartCommand(rest: string[] = []): Promise<void> {\n // First reconcile the deploy location from synkro.toml. If the user flipped\n // [grader] location, this switches local<->cloud: local→cloud safely stops +\n // removes the local container and provisions the CF container; cloud→local\n // repoints config so the docker (re)start below brings the local grader back.\n const { location, changed } = await reconcileDeployLocation();\n if (location === 'cloud') {\n // Cloud grader: a restart spins a NET-NEW container. If we just switched\n // local→cloud, reconcile already provisioned + verified a fresh one — don't\n // double-boot. Otherwise (already cloud), destroy the running container and\n // boot a fresh one so the user gets a clean, single, net-new instance.\n if (!changed) await recycleCloudContainer();\n return;\n }\n\n assertDockerAvailable();\n const cfg = resolveWorkerConfig(rest);\n\n // Reconcile harness from .synkro (hooks, MCP, worker allocation)\n let claudeWorkers = cfg.claudeWorkers;\n let cursorWorkers = cfg.cursorWorkers;\n if (!cfg.explicit) {\n const reconciled = reconcileHarness();\n if (reconciled) {\n claudeWorkers = reconciled.claudeWorkers;\n cursorWorkers = reconciled.cursorWorkers;\n }\n }\n\n console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer restarted successfully.');\n\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log('✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn('⚠ workers did not register within 30s — skill sync skipped');\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isAuthenticated, getAccessToken } from '../auth/stub.js';\nimport { getTelemetryState, setTelemetryState } from '../telemetry/optout.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nfunction updateConfigValue(key: string, value: string): void {\n if (!existsSync(CONFIG_PATH)) {\n console.error('No config found. Run `synkro install` first.');\n process.exit(1);\n }\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\n');\n const pattern = new RegExp(`^${key}=`);\n let found = false;\n const updated = lines.map((line) => {\n if (pattern.test(line.trim())) {\n found = true;\n return `${key}='${value}'`;\n }\n return line;\n });\n if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);\n writeFileSync(CONFIG_PATH, updated.join('\\n'), 'utf-8');\n}\n\n// The canonical 3-state grading location, derived from the two routing vars the\n// hooks actually read — keep this in lockstep with route()/selectGrader() in\n// hookScriptsTs.ts. 'byok' (own API key) wins; else cloud worker pool; else\n// on-device workers. Single source of truth, so the printout can't drift.\nfunction resolveInferenceMode(cfg: Record<string, string>): 'local' | 'cloud' | 'byok' {\n if ((cfg.SYNKRO_GRADING_MODE || 'local') === 'byok') return 'byok';\n if (cfg.SYNKRO_DEPLOY_LOCATION === 'cloud') return 'cloud';\n return 'local';\n}\n\n// After a grading/storage change, bring the local container in line with the\n// new modes — this is what makes switching seamless (no separate install step).\n// Cloud-only (BYOK grading + cloud storage) needs no container; every other\n// combo needs it. Only acts when crossing that boundary; wrapped so a Docker\n// hiccup never fails the config write itself.\nasync function reconcileContainer(): Promise<void> {\n const cfg = readConfigEnv();\n const cloudOnly =\n (cfg.SYNKRO_GRADING_MODE || 'local') === 'byok' &&\n (cfg.SYNKRO_STORAGE_MODE || 'local') === 'cloud';\n\n try {\n const { dockerInstall, dockerSafeStop, readContainerConfig, assertDockerAvailable, waitForContainerReady } =\n await import('../local-cc/dockerInstall.js');\n const existing = readContainerConfig();\n\n if (cloudOnly && existing) {\n console.log('Cloud-only mode — the local container is no longer needed; stopping it...');\n await dockerSafeStop();\n console.log(' ✓ container stopped (grading → BYOK, telemetry → cloud).');\n } else if (!cloudOnly && !existing) {\n assertDockerAvailable();\n console.log('This mode needs the Synkro container — provisioning...');\n const { detectGitRepo } = await import('./install.js');\n await dockerInstall({ claudeWorkers: 8, cursorWorkers: 0, connectedRepo: detectGitRepo() ?? undefined });\n const ready = await waitForContainerReady(60_000);\n console.log(ready\n ? ' ✓ container running.'\n : ' ⚠ container did not pass its health check — see `docker logs synkro-server`.');\n }\n // else: the container's presence already matches the mode — nothing to do.\n } catch (err) {\n console.warn(` ⚠ Container reconcile skipped: ${(err as Error).message}`);\n console.warn(' Run `synkro install` to (re)provision the container if needed.');\n }\n}\n\nexport async function configCommand(args: string[]): Promise<void> {\n if (args.length === 0) {\n const config = readConfigEnv();\n console.log('Synkro config:\\n');\n console.log(` inference: ${resolveInferenceMode(config)}`);\n console.log(` storage: ${config.SYNKRO_STORAGE_MODE || 'local'}`);\n console.log(` tier: ${config.SYNKRO_TIER || 'pro'}`);\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh'}`);\n console.log(` version: ${config.SYNKRO_VERSION || '?'}`);\n console.log(`\\nTo change:`);\n console.log(` synkro config --inference <local|cloud|byok> — where grading runs`);\n console.log(` local — Claude/Cursor workers on this machine (your subscription)`);\n console.log(` cloud — Claude/Cursor workers in the Synkro cloud pool`);\n console.log(` byok — graded via your own provider API key`);\n console.log(` synkro config storage <local|cloud> — where telemetry is stored`);\n return;\n }\n\n // User-owned data axes — written to config.env; the hooks load config.env at\n // process start, so the change takes effect on the next graded tool call.\n if (args[0] === 'grading') {\n const value = args[1];\n if (value !== 'local' && value !== 'byok') {\n console.error('Usage: synkro config grading <local|byok>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_GRADING_MODE', value);\n console.log(`✓ Grading mode set to '${value}'.`);\n if (value === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n }\n await reconcileContainer();\n return;\n }\n // Telemetry opt-out toggles (DESIGN.md §9). State lives in\n // meta + config.env; setTelemetryState emits the config_change event itself.\n if (args[0] === 'telemetry') {\n const rest = args.slice(1);\n if (rest.length === 0) {\n const s = await getTelemetryState();\n const label = !s.enabled ? 'OFF' : (s.remoteFlushEnabled ? 'ON' : 'local-only');\n console.log(`Telemetry: ${label}`);\n return;\n }\n if (rest.length !== 1 || !['--on', '--off', '--local-only'].includes(rest[0])) {\n console.error('Usage: synkro config telemetry [--on|--off|--local-only]');\n process.exit(1);\n }\n const flag = rest[0];\n if (flag === '--on') {\n await setTelemetryState({ enabled: true, remoteFlushEnabled: true });\n console.log('Telemetry: ON');\n } else if (flag === '--off') {\n await setTelemetryState({ enabled: false, remoteFlushEnabled: false });\n console.log('Telemetry: OFF');\n } else {\n await setTelemetryState({ enabled: true, remoteFlushEnabled: false });\n console.log('Telemetry: local-only');\n }\n return;\n }\n if (args[0] === 'storage') {\n const value = args[1];\n if (value !== 'local' && value !== 'cloud') {\n console.error('Usage: synkro config storage <local|cloud>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_STORAGE_MODE', value);\n console.log(`✓ Storage mode set to '${value}'.`);\n await reconcileContainer();\n return;\n }\n\n let inferenceValue: string | undefined;\n for (const a of args) {\n if (a.startsWith('--inference=')) inferenceValue = a.slice('--inference='.length);\n else if (a === '--inference' && args.indexOf(a) + 1 < args.length) inferenceValue = args[args.indexOf(a) + 1];\n }\n\n if (!inferenceValue || !['local', 'cloud', 'byok'].includes(inferenceValue)) {\n console.error('Usage: synkro config --inference <local|cloud|byok>');\n process.exit(1);\n }\n\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro login` first.');\n process.exit(1);\n }\n\n const token = getAccessToken();\n const config = readConfigEnv();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n\n // Persist the canonical mode to the DB so the dashboard reflects it.\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ inference_mode: inferenceValue }),\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to reach server: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Mirror the mode into the two routing vars the hooks read, so the next graded\n // tool call actually runs where the dashboard now says it does.\n if (inferenceValue === 'byok') {\n updateConfigValue('SYNKRO_GRADING_MODE', 'byok');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'local');\n } else if (inferenceValue === 'cloud') {\n updateConfigValue('SYNKRO_GRADING_MODE', 'local');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'cloud');\n } else {\n updateConfigValue('SYNKRO_GRADING_MODE', 'local');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'local');\n }\n\n console.log(`✓ Inference set to '${inferenceValue}'.`);\n if (inferenceValue === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n } else if (inferenceValue === 'cloud') {\n console.log(' Grading runs on the Synkro cloud worker pool.');\n }\n\n // local/byok grade through the on-device container; cloud uses the hosted\n // pool, so it doesn't need (and shouldn't provision) a local container.\n if (inferenceValue !== 'cloud') await reconcileContainer();\n}\n","/**\n * `synkro telemetry <sub>` — inspect, flush, or wipe the local event log.\n *\n * Every subcommand drains the JSONL queue into pglite before reading, so\n * stats/tail/export reflect any in-flight emits. When the container is down,\n * reads return whatever's already in pglite (possibly empty) plus a \"pending\n * locally\" count surfaced by stats.\n */\n\nimport { createInterface } from 'node:readline';\n\nimport { flush } from '../telemetry/flush.js';\nimport { purge } from '../telemetry/purge.js';\nimport { exportEvents, getStats, tail, type TailOptions } from '../telemetry/stats.js';\nimport { setTelemetryState } from '../telemetry/optout.js';\nimport type { EventRow } from '../telemetry/types.js';\n\nconst HELP = `synkro telemetry — inspect or flush local telemetry events\n\nUsage:\n synkro telemetry stats show counts, queue depth, last flush\n synkro telemetry tail [opts] print recent events (most recent first)\n --limit=N max rows (default 20)\n --type=X filter by event_type\n --session=Y filter by cc_session_id\n synkro telemetry flush [--force] ship unflushed events now (bypass throttle)\n synkro telemetry export <path> dump every event as NDJSON to <path>\n synkro telemetry purge [--keep-install-id] [--yes]\n wipe local events; --keep-install-id keeps your install_id\n synkro telemetry on enable collection AND remote flush\n synkro telemetry off disable collection entirely\n synkro telemetry local-only collect locally, never flush remotely\n`;\n\nfunction parseFlag(args: string[], name: string): string | undefined {\n const prefix = `--${name}=`;\n for (const a of args) if (a.startsWith(prefix)) return a.slice(prefix.length);\n const idx = args.indexOf(`--${name}`);\n if (idx >= 0 && idx + 1 < args.length && !args[idx + 1].startsWith('--')) {\n return args[idx + 1];\n }\n return undefined;\n}\n\nfunction hasFlag(args: string[], name: string): boolean {\n return args.includes(`--${name}`);\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + `… (+${s.length - max} chars)`;\n}\n\nasync function printStats(): Promise<void> {\n const s = await getStats();\n console.log('Telemetry:');\n console.log(` install_id: ${s.install_id}`);\n console.log(` enabled: ${s.enabled ? 'yes' : 'no'}`);\n console.log(` remote_flush: ${s.remote_flush_enabled ? 'yes' : 'no'}`);\n console.log(` pglite (local): ${s.pglite_reachable ? 'reachable' : 'none (cloud mode — query central for totals)'}`);\n console.log(` local mirror events: ${s.total_events}`);\n console.log(` pending (queue): ${s.pending_local}`);\n if (s.newest) console.log(` newest event: ${s.newest}`);\n if (s.last_drained_at) console.log(` last local drain: ${s.last_drained_at}`);\n if (s.last_flush_at) console.log(` last flush attempt: ${s.last_flush_at}`);\n if (s.last_flush_ok_at) console.log(` last flush success: ${s.last_flush_ok_at}`);\n if (s.last_flush_error) console.log(` last flush error: ${s.last_flush_error}`);\n const types = Object.entries(s.by_type);\n if (types.length > 0) {\n console.log(` by type:`);\n for (const [name, count] of types) {\n console.log(` ${name.padEnd(22)} ${count}`);\n }\n }\n}\n\nasync function printTail(args: string[]): Promise<void> {\n const opts: TailOptions = {};\n const limitRaw = parseFlag(args, 'limit');\n if (limitRaw) {\n const n = Number.parseInt(limitRaw, 10);\n if (Number.isFinite(n) && n > 0) opts.limit = n;\n }\n const typeRaw = parseFlag(args, 'type');\n if (typeRaw) opts.eventType = typeRaw;\n const sessionRaw = parseFlag(args, 'session');\n if (sessionRaw) opts.sessionId = sessionRaw;\n\n const rows = await tail(opts);\n if (rows.length === 0) {\n console.log('(no events — run `synkro start` if the container is down so JSONL pending events can drain)');\n return;\n }\n for (const row of rows as EventRow[]) {\n const session = row.cc_session_id ? ` session=${String(row.cc_session_id).slice(0, 8)}` : '';\n const ts = typeof row.occurred_at === 'string' ? row.occurred_at : new Date(row.occurred_at as unknown as string | number | Date).toISOString();\n console.log(` ${ts} ${row.event_type.padEnd(20)} ${row.emitter}${session}`);\n const contextStr = typeof row.context === 'string' ? row.context : JSON.stringify(row.context);\n console.log(` context: ${truncate(contextStr, 200)}`);\n }\n}\n\nasync function runFlush(args: string[]): Promise<void> {\n const force = hasFlag(args, 'force');\n const r = await flush({ force });\n const parts = [`remote sent: ${r.sent}`, `queued (kept): ${r.failed}`, `local mirror: ${r.mirrored}`];\n if (r.skipped_reason) parts.push(`remote skipped (${r.skipped_reason})`);\n console.log(parts.join(', '));\n}\n\nasync function runExport(args: string[]): Promise<void> {\n const path = args[0];\n if (!path) {\n console.error('usage: synkro telemetry export <path>');\n process.exit(1);\n }\n await exportEvents(path);\n console.log(`wrote ${path}`);\n}\n\nfunction confirmYesNo(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return Promise.resolve(false);\n return new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`${question} (y/N): `, (answer) => {\n rl.close();\n const t = answer.trim().toLowerCase();\n resolve(t === 'y' || t === 'yes');\n });\n });\n}\n\nasync function runPurge(args: string[]): Promise<void> {\n const keepInstallId = hasFlag(args, 'keep-install-id');\n const skipPrompt = hasFlag(args, 'yes') || hasFlag(args, 'y');\n\n if (!skipPrompt) {\n const msg = keepInstallId\n ? 'Wipe all local telemetry events (keeping your install_id)?'\n : 'Wipe all local telemetry events AND reset install_id?';\n const ok = await confirmYesNo(msg);\n if (!ok) {\n console.log('aborted');\n return;\n }\n }\n\n const result = await purge({ keepInstallId });\n if (!result.pglite_reachable) {\n console.log(`removed ${result.removed_pending} pending event(s) from local queue (container down — pglite events untouched)`);\n return;\n }\n console.log(`removed ${result.removed_events} pglite event(s) + ${result.removed_pending} pending; meta ${result.removed_meta ? 'reset' : 'unchanged'}`);\n}\n\nexport async function telemetryCommand(args: string[]): Promise<void> {\n const sub = args[0];\n const rest = args.slice(1);\n\n switch (sub) {\n case 'stats':\n await printStats();\n return;\n case 'tail':\n await printTail(rest);\n return;\n case 'flush':\n await runFlush(rest);\n return;\n case 'export':\n await runExport(rest);\n return;\n case 'purge':\n await runPurge(rest);\n return;\n case 'on':\n await setTelemetryState({ enabled: true, remoteFlushEnabled: true });\n console.log('telemetry: on (collection + remote flush enabled)');\n return;\n case 'off':\n await setTelemetryState({ enabled: false, remoteFlushEnabled: false });\n console.log('telemetry: off (no collection, no remote flush)');\n return;\n case 'local-only':\n await setTelemetryState({ enabled: true, remoteFlushEnabled: false });\n console.log('telemetry: local-only (collection on, remote flush off)');\n return;\n case undefined:\n case '':\n case 'help':\n case '--help':\n case '-h':\n console.log(HELP);\n return;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n console.log(HELP);\n process.exit(1);\n }\n}\n","#!/usr/bin/env node\n/**\n * Synkro CLI bootstrap.\n *\n * Loads .env, then dispatches to the right subcommand.\n */\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst envCandidates = [\n resolve(process.env.HOME ?? '', '.synkro', 'config.env'),\n];\nfor (const envPath of envCandidates) {\n if (!existsSync(envPath)) continue;\n const envContent = readFileSync(envPath, 'utf-8');\n for (const line of envContent.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex <= 0) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n const value = trimmed.slice(eqIndex + 1).trim().replace(/^['\"]|['\"]$/g, '');\n if (!process.env[key] && !value.startsWith('op://')) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\n\n// The detached flush child re-execs this bootstrap (`telemetry flush --detached`)\n// only to drain the queue. It must NOT emit its own `cli_invocation`; flushDetached\n// already sets this env, and we honor it here too.\nconst isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === '1';\n\n// Subcommands too hot or too trivial to warrant spawning a detached flush child:\n// `grade` fires on a per-hook hot loop; version/help do no telemetry-worthy work.\n// Any events they do emit ride along on the next real invocation's flush.\nconst FLUSH_SKIP = new Set(['grade', 'version', '--version', '-v', 'help', '--help', '-h', '']);\n\nfunction printVersion() {\n console.log(__SYNKRO_CLI_VERSION__);\n}\n\nfunction printHelp() {\n console.log(`Synkro CLI — runtime safety for AI coding agents\n\nUsage:\n synkro <command> [options]\n\nCommands:\n install [--force] Install or update Synkro\n uninstall [--purge] Remove Synkro (keeps scan data; --purge wipes that too)\n stop Gracefully stop the server (snapshot + checkpoint)\n start [opts] Start the server (with pgdata integrity check)\n restart [opts] Safe restart (stop → start, data preserved)\n update Pull the latest container image and safely restart\n config Show or change grading + storage modes\n claude-desktop Monitor Claude Desktop conversations (local, macOS)\n telemetry <sub> Inspect or flush local telemetry events\n version Show version\n\n config:\n synkro config show current settings\n synkro config grading <local|byok> where grading runs\n synkro config storage <local|cloud> where telemetry is stored\n\n start/restart opts (recreate the worker pool):\n --workers N total grader workers (default 8, even-split)\n --providers a,b grading agents: claude, cursor (or both)\n e.g. synkro restart --workers 16 --providers claude,cursor\n\nQuick start:\n $ synkro install # one-time setup\n $ claude # use Claude Code normally; Synkro judges in real time\n`);\n}\n\nasync function main() {\n // Telemetry import is lazy but happens early — almost every command benefits\n // from `cli_invocation` and the post-dispatch flush. index.ts handles its own\n // errors, so a wedged telemetry path never propagates into dispatch.\n /** @type {null | typeof import('./telemetry/index.js').emit} */\n let emit = null;\n try {\n ({ emit } = await import('./telemetry/index.js'));\n } catch { /* telemetry disabled or broken — proceed without it */ }\n\n // Every invocation gets a cli_invocation event, except `grade` (fires on a hot\n // loop and would drown the table) and the detached flush child.\n if (emit && !isDetachedChild && cmd !== 'grade') {\n try {\n emit('cli_invocation', {\n argv: process.argv.slice(2),\n subcommand: cmd || 'help',\n cwd: process.cwd(),\n });\n } catch { /* never block dispatch */ }\n }\n\n switch (cmd) {\n case 'install': {\n const { installCommand, parseArgs } = await import('./commands/install.js');\n await installCommand(parseArgs(subArgs));\n break;\n }\n case 'uninstall':\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n // MUST await — main()'s .then(shutdown) calls process.exit() as soon as main\n // returns, so a fire-and-forget uninstall gets killed mid-teardown (the process\n // died during the first await — the pre-shutdown snapshot fetch — leaving the\n // container running and hooks in place).\n await disconnectCommand(subArgs);\n break;\n }\n case 'login': {\n const { authenticate, isAuthenticated } = await import('./auth/stub.js');\n if (isAuthenticated()) {\n console.log('Already authenticated.');\n } else {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n if (status.phase === 'success') console.log(' ✓ Authenticated');\n else if (status.phase === 'error') console.error(' ✗ ' + status.message);\n });\n if (!result) { console.error('Authentication failed.'); process.exit(1); }\n }\n break;\n }\n case 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(subArgs);\n break;\n }\n case 'local-cc': {\n const { localCcCommand } = await import('./commands/localCc.js');\n await localCcCommand(subArgs);\n break;\n }\n case 'import': {\n const { importCommand } = await import('./commands/import.js');\n await importCommand();\n break;\n }\n case 'sync': {\n const { syncCommand } = await import('./commands/sync.js');\n await syncCommand(subArgs);\n break;\n }\n case 'version':\n case '--version':\n case '-v': {\n printVersion();\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n case '': {\n printHelp();\n break;\n }\n case 'stop': {\n const { stopCommand } = await import('./commands/lifecycle.js');\n await stopCommand();\n break;\n }\n case 'start': {\n const { startCommand } = await import('./commands/lifecycle.js');\n await startCommand(args.slice(1));\n break;\n }\n case 'restart': {\n const { restartCommand } = await import('./commands/lifecycle.js');\n await restartCommand(args.slice(1));\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/lifecycle.js');\n await updateCommand();\n break;\n }\n case 'config': {\n const { configCommand } = await import('./commands/config.js');\n await configCommand(args.slice(1));\n break;\n }\n case 'claude-desktop': {\n // Start a Claude Desktop observability session against the already-running\n // container — no reinstall, no image pull. (The same tap is also offered\n // during `synkro install`.) Runs until Ctrl+C.\n const { runClaudeDesktopTap } = await import('./installer/claudeDesktopTap.js');\n // History backfill is opt-in: `synkro claude-desktop --backfill` imports past\n // conversations once. Without it, only live turns are captured.\n await runClaudeDesktopTap({ backfill: subArgs.includes('--backfill') });\n break;\n }\n case 'telemetry': {\n const { telemetryCommand } = await import('./commands/telemetry.js');\n await telemetryCommand(subArgs);\n break;\n }\n default: {\n console.error(`Unknown command: ${cmd}`);\n printHelp();\n process.exit(1);\n }\n }\n}\n\n// Post-dispatch flush — detached so it can outlive this process and so any\n// network/sqlite hiccup never delays CLI exit. Wrapped in its own try/catch\n// because a flush failure must NEVER prevent the CLI from exiting.\nasync function postDispatchFlush() {\n if (FLUSH_SKIP.has(cmd)) return;\n try {\n const { flushDetached } = await import('./telemetry/flush.js');\n flushDetached();\n } catch { /* flush is best-effort; ignore */ }\n}\n\n// Drain the cached pglite connection so the event loop can empty and Node exits\n// on its own — an open postgres.js socket otherwise keeps the CLI alive until\n// idle_timeout (~30s). The detached flush child is a separate unref'd process in\n// its own group, so nothing here touches it. If some stray handle still holds\n// the loop open, force the exit as a fallback — but only once stdout/stderr have\n// drained, so piped output is never truncated.\n/** @param {number} code */\nasync function shutdown(code) {\n try {\n const { closeDb } = await import('./telemetry/db.js');\n await closeDb();\n } catch { /* cleanup must never block exit */ }\n process.exitCode = code;\n const force = setTimeout(() => {\n let pending = 0;\n const done = () => { if (--pending === 0) process.exit(code); };\n for (const stream of [process.stdout, process.stderr]) {\n if (stream.writableLength > 0) { pending++; stream.once('drain', done); }\n }\n if (pending === 0) process.exit(code);\n }, 200);\n force.unref();\n}\n\nmain()\n .then(() => postDispatchFlush())\n .then(() => shutdown(0))\n .catch(async (err) => {\n // Best-effort: emit an `error` event before exiting. Wrapped in its own\n // try/catch so a wedged telemetry path can't shadow the original error.\n try {\n const { emit } = await import('./telemetry/index.js');\n emit('error', {\n category: 'unknown',\n code: (err && err.code) || 'UNKNOWN',\n message: String(err && err.message ? err.message : err),\n stack: (err && err.stack) || '',\n emitter_context: 'bootstrap:' + (cmd || 'unknown'),\n });\n } catch { /* swallow */ }\n await postDispatchFlush();\n console.error(err);\n await shutdown(1);\n });\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4lB9B,IAAM,wBAAwB,SAAS,iBAAiB,4CAA4C;AACpG,IAAM,uBAAuB,SAAS,gBAAgB,4CAA4C;AAClG,IAAM,uBAAuB,SAAS,gBAAgB,4CAA4C;AAClG,IAAM,qBAAqB,SAAS,cAAc,2BAA2B;AAK7E,IAAM,sBAAsB,SAAS,eAAe,4CAA4C;AAChG,IAAM,uBAAuB,SAAS,gBAAgB,2BAA2B;AACjF,IAAM,sBAAsB,SAAS,eAAe,2BAA2B;AAG/E,IAAM,mBAAmB,SAAS,YAAY,2BAA2B;AACzE,IAAM,qBAAqB,SAAS,cAAc,qBAAqB;AAKvE,IAAM,uBAAuB,SAAS,gBAAgB,iDAAiD;AACvG,IAAM,wBAAwB,SAAS,iBAAiB,qBAAqB;AAC7E,IAAM,0BAA0B,SAAS,mBAAmB,kEAAkE;AAC9H,IAAM,6BAA6B,SAAS,iBAAiB,qBAAqB;AAClF,IAAM,wBAAwB,SAAS,iBAAiB,qBAAqB;AAO7E,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBrC,IAAM,4BAA4B,SAAS,cAAc,8CAA8C;AACvG,IAAM,6BAA6B,SAAS,eAAe,+DAA+D;AAC1H,IAAM,8BAA8B,SAAS,iBAAiB,+DAA+D;AAC7H,IAAM,+BAA+B,SAAS,eAAe,8CAA8C;AAAA;AAAA;;;ACxqBlH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,SAAS,oBAAqD;AAC9D,SAAS,iBAAAE,gBAAe,gBAAAC,gBAAc,cAAAC,cAAY,aAAAC,YAAW,cAAAC,mBAAkB;AAC/E,SAAS,WAAAC,WAAS,YAAAC,iBAAgB;AAClC,SAAS,QAAAC,QAAM,WAAAC,gBAAe;AAC9B,SAAS,gBAAgB;AACzB,OAAO,SAAS;AAmKhB,SAAS,YAAY,KAAmB;AACtC,QAAM,KAAKF,UAAS;AACpB,MAAI;AACJ,MAAIG;AAEJ,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,YAAM;AACN,MAAAA,QAAO,CAAC,GAAG;AACX;AAAA,IACF,KAAK;AAIH,YAAM;AACN,MAAAA,QAAO,CAAC,MAAM,SAAS,IAAI,GAAG;AAC9B;AAAA,IACF;AACE,YAAM;AACN,MAAAA,QAAO,CAAC,GAAG;AAAA,EACf;AAKA,WAAS,KAAKA,OAAM,CAAC,UAAU;AAC7B,QAAI,OAAO;AACT,cAAQ,MAAM,uCAAuC;AACrD,cAAQ,IAAI,kCAAkC,GAAG,EAAE;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAKO,SAAS,gBAAgB,MAA6B;AAC3D,QAAM,MAAMD,SAAQ,SAAS;AAE7B,MAAI,CAACN,aAAW,GAAG,GAAG;AACpB,IAAAC,WAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EACjD;AAEA,EAAAH,eAAc,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACzE;AAKO,SAAS,kBAA0C;AACxD,MAAI,CAACE,aAAW,SAAS,GAAG;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAUD,eAAa,WAAW,MAAM;AAC9C,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,SAAS,OAAO;AACd,WAAO;AAAA,EACT;AACF;AAWA,SAAS,uBAAiD;AAMxD,QAAM,eAAe;AAAA,IACnB,+BAA+B;AAAA,IAC/B,gCAAgC;AAAA,IAChC,gCAAgC;AAAA,IAChC,QAAQ;AAAA,EACV;AAEA,SAAO,IAAI,QAAQ,CAACS,UAAS,WAAW;AACtC,UAAM,SAAS,aAAa,CAAC,KAAsB,QAAwB;AAGzE,UAAI,IAAI,WAAW,WAAW;AAC5B,cAAM,SAAS,IAAI,QAAQ;AAC3B,YAAI,WAAW,qBAAqB;AAClC,cAAI,UAAU,KAAK,YAAY;AAAA,QACjC,OAAO;AACL,cAAI,UAAU,KAAK;AAAA,YACjB,gCAAgC;AAAA,YAChC,gCAAgC;AAAA,YAChC,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AACA,YAAI,IAAI;AACR;AAAA,MACF;AAKA,YAAM,YAAY,IAAI,QAAQ;AAC9B,UAAI,aAAa,cAAc,qBAAqB;AAClD,YAAI,UAAU,KAAK,EAAE,QAAQ,SAAS,CAAC;AACvC,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,CAAC,IAAI,KAAK;AACZ,YAAI,UAAU,KAAK,YAAY;AAC/B,YAAI,IAAI;AACR;AAAA,MACF;AAEA,YAAM,MAAM,IAAI,IAAI,IAAI,KAAK,oBAAoB,IAAI,EAAE;AAEvD,UAAI,IAAI,aAAa,SAAS;AAC5B,YAAI,UAAU,KAAK,YAAY;AAC/B,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,IAAI,WAAW,QAAQ;AAIzB,YAAI,UAAU,KAAK,EAAE,GAAG,cAAc,SAAS,iBAAiB,gBAAgB,YAAY,CAAC;AAC7F,YAAI,IAAI,UAAU;AAClB;AAAA,MACF;AAGA,YAAM,WAAW,KAAK;AACtB,YAAM,SAAmB,CAAC;AAC1B,UAAI,QAAQ;AACZ,UAAI,UAAU;AACd,UAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,YAAI,QAAS;AACb,iBAAS,MAAM;AACf,YAAI,QAAQ,UAAU;AACpB,oBAAU;AACV,cAAI,UAAU,KAAK,YAAY;AAC/B,cAAI,IAAI;AACR;AAAA,QACF;AACA,eAAO,KAAK,KAAK;AAAA,MACnB,CAAC;AACD,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI,QAAS;AACb,YAAI;AACJ,YAAI;AACF,mBAAS,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,QAC5D,QAAQ;AACN,cAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACjD,qBAAW,MAAM;AACf,mBAAO,MAAM;AACb,mBAAO,IAAI,MAAM,0CAA0C,CAAC;AAAA,UAC9D,GAAG,GAAG;AACN;AAAA,QACF;AAEA,cAAM,QAA4B,QAAQ;AAC1C,YAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,cAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,gBAAgB,CAAC,CAAC;AAClD,qBAAW,MAAM;AACf,mBAAO,MAAM;AACb,mBAAO,IAAI,MAAM,sCAAsC,CAAC;AAAA,UAC1D,GAAG,GAAG;AACN;AAAA,QACF;AAEA,cAAM,WAA4B;AAAA,UAChC,cAAc;AAAA,UACd,eAAe,OAAO,OAAO,kBAAkB,WAAW,OAAO,gBAAgB;AAAA,UACjF,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,UAC/D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,UACzD,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,UAC5D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAAA,QAC3D;AAEA,YAAI,UAAU,KAAK,EAAE,GAAG,cAAc,gBAAgB,mBAAmB,CAAC;AAC1E,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AAEpC,mBAAW,MAAM;AACf,iBAAO,MAAM;AACb,UAAAA,SAAQ,QAAQ;AAAA,QAClB,GAAG,GAAG;AAAA,MACR,CAAC;AACD,UAAI,GAAG,SAAS,CAAC,MAAM;AACrB,YAAI,QAAS;AACb,kBAAU;AACV,YAAI;AAAE,cAAI,UAAU,KAAK,YAAY;AAAG,cAAI,IAAI;AAAA,QAAG,QAAQ;AAAA,QAAC;AAC5D,mBAAW,MAAM;AACf,iBAAO,MAAM;AACb,iBAAO,CAAC;AAAA,QACV,GAAG,GAAG;AAAA,MACR,CAAC;AAAA,IACH,CAAC;AAED,WAAO,OAAO,IAAI;AAElB,WAAO,GAAG,SAAS,CAAC,UAAiC;AACnD,UAAI,MAAM,SAAS,cAAc;AAC/B;AAAA,UACE,IAAI;AAAA,YACF,QAAQ,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF,OAAO;AACL,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAYA,eAAsB,aACpB,UACiC;AACjC,QAAMC,QAAO,aAAa,MAAM;AAAA,EAAC;AAEjC,MAAI;AACF,IAAAA,MAAK,EAAE,OAAO,WAAW,CAAC;AAG1B,UAAM,gBAAgB,qBAAqB;AAG3C,UAAM,UAAU,GAAG,mBAAmB,kBAAkB,IAAI;AAC5D,gBAAY,OAAO;AAEnB,IAAAA,MAAK,EAAE,OAAO,kBAAkB,KAAK,QAAQ,CAAC;AAC9C,IAAAA,MAAK,EAAE,OAAO,UAAU,CAAC;AAGzB,UAAM,OAAO,MAAM;AAEnB,IAAAA,MAAK,EAAE,OAAO,UAAU,CAAC;AACzB,oBAAgB,IAAI;AACpB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,IAAAA,MAAK,EAAE,OAAO,SAAS,QAAQ,CAAC;AAChC,WAAO;AAAA,EACT;AACF;AAKO,SAAS,kBAA2B;AACzC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,MAAO,QAAO;AAGnB,MAAI;AACF,UAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,QAAI,CAAC,SAAS,IAAK,QAAO;AAG1B,WAAO,KAAK,IAAI,IAAI,QAAQ,MAAM;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,mBAA2B;AACzC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAEA,QAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,MAAI,CAAC,SAAS,KAAK;AACjB,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,SAAO,QAAQ;AACjB;AAKO,SAAS,cAAwB;AACtC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAKA,MAAI,MAAM,SAAS;AACjB,WAAO;AAAA,MACL,IAAI,MAAM;AAAA,MACV,OAAO,MAAM,SAAS;AAAA,MACtB,QAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,eAAe;AAAA,EACjC;AAEA,SAAO;AAAA,IACL,IAAI,QAAQ;AAAA,IACZ,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ;AAAA,EAClB;AACF;AAKO,SAAS,iBAAgC;AAC9C,QAAM,QAAQ,gBAAgB;AAC9B,SAAO,OAAO,gBAAgB;AAChC;AAKO,SAAS,iBAA0B;AACxC,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI;AACF,UAAM,UAAU,IAAI,OAAO,MAAM,YAAY;AAC7C,QAAI,CAAC,SAAS,IAAK,QAAO;AAG1B,UAAM,YAAY,QAAQ,MAAM;AAChC,UAAM,SAAS,IAAI,KAAK;AACxB,WAAO,KAAK,IAAI,IAAI,YAAY;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,eAAiC;AACrD,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO,cAAe,QAAO;AAElC,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG,cAAc,qBAAqB;AAAA,MACjE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,eAAe,MAAM,cAAc,CAAC;AAAA,IAC7D,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,KAAK,cAAc;AACrB,sBAAgB;AAAA,QACd,GAAG;AAAA,QACH,cAAc,KAAK;AAAA,QACnB,eAAe,KAAK,iBAAiB,MAAM;AAAA,MAC7C,CAAC;AACD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,mBAAqC;AAKzD,QAAM,QAAQ,gBAAgB;AAC9B,MAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,cAAe,QAAO;AAE1D,MAAI,eAAe,GAAG;AACpB,QAAI,CAAC,MAAM,cAAe,QAAO;AACjC,QAAI,CAAC,gBAAgB;AACnB,uBAAiB,aAAa,EAAE,QAAQ,MAAM;AAAE,yBAAiB;AAAA,MAAM,CAAC;AAAA,IAC1E;AACA,UAAM,YAAY,MAAM;AACxB,QAAI,CAAC,WAAW;AAKd,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAQA,eAAsB,YAAY,KAAa,OAAoB,CAAC,GAAsB;AACxF,QAAM,YAAY,OAAoB;AAAA,IACpC,GAAG;AAAA,IACH,SAAS,EAAE,GAAI,KAAK,SAAgD,eAAe,UAAU,eAAe,KAAK,EAAE,GAAG;AAAA,EACxH;AACA,QAAM,iBAAiB;AACvB,MAAI,MAAM,MAAM,MAAM,KAAK,UAAU,CAAC;AACtC,MAAI,IAAI,WAAW,OAAO,MAAM,aAAa,GAAG;AAC9C,UAAM,MAAM,MAAM,KAAK,UAAU,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAKO,SAAS,mBAAyB;AACvC,MAAIT,aAAW,SAAS,GAAG;AACzB,IAAAE,YAAW,SAAS;AAAA,EACtB;AACF;AAWA,eAAsB,WACpB,QACA,eACiC;AAGjC,SAAO;AAAA,IACL,mBAAmB,QAAQ,IAAI,gBAAgB;AAAA,IAC/C,uBAAuB,QAAQ,IAAI,mBAAmB;AAAA,IACtD,YAAY,QAAQ,IAAI,mBAAmB;AAAA,IAC3C,UAAU,QAAQ,IAAI,iBAAiB;AAAA,IACvC,mBAAmB,QAAQ,IAAI,sBAAsB;AAAA,EACvD;AACF;AAvoBA,IA8BM,MAKA,kBACA,qBAGA,WACA,aACA,gBA0FA,YAmbF;AAtjBJ;AAAA;AAAA;AA8BA,IAAM,OAAO;AAKb,IAAM,mBAAmB,QAAQ,IAAI;AACrC,IAAM,sBAAuB,oBAAoB,eAAe,KAAK,gBAAgB,IACjF,mBACA;AACJ,IAAM,YAAY,QAAQ,IAAI,oBAAoBG,OAAKF,UAAQ,GAAG,WAAW,kBAAkB;AAC/F,IAAM,cAAc,QAAQ,IAAI,mBAAmB,QAAQ,IAAI;AAC/D,IAAM,iBAAkB,eAAe,eAAe,KAAK,WAAW,IAClE,cACA;AAwFJ,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmbnB,IAAI,iBAA0C;AAAA;AAAA;;;ACtjB9C;AAAA;AAAA;AAMA;AAAA;AAAA;;;ACMO,SAAS,cAAc,KAAmB;AAC/C,YAAU;AACZ;AAkDA,eAAe,QACb,QACA,UACA,MACY;AACZ,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,QAAM,MAAM,GAAG,OAAO,GAAG,QAAQ;AACjC,QAAM,iBAAiB;AACvB,QAAM,cAAc,eAAe;AAEnC,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAEA,MAAI,aAAa;AACf,YAAQ,eAAe,IAAI,UAAU,WAAW;AAAA,EAClD;AAEA,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA;AAAA,IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EACtC,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,QAAQ,SAAS,WAAW,EAAE;AACjF,UAAM,IAAI,MAAM,MAAM,UAAU,cAAc,SAAS,MAAM,EAAE;AAAA,EACjE;AAEA,SAAO,SAAS,KAAK;AACvB;AAyBA,eAAsB,cACpB,MACA,OACgC;AAChC,QAAM,OAAgC,EAAE,KAAK;AAC7C,MAAI,SAAS,MAAM,SAAS,EAAG,MAAK,QAAQ;AAC5C,SAAO,QAA+B,QAAQ,aAAa,IAAI;AACjE;AAkBA,eAAsB,eAAmC;AACvD,SAAO,QAAmB,OAAO,WAAW;AAC9C;AArJA,IAUI;AAVJ;AAAA;AAAA;AAQA;AAEA,IAAI,UAAU;AAAA;AAAA;;;ACVd,IAaa,sBAoDA;AAjEb;AAAA;AAAA;AAaO,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoD7B,IAAM,gBAAgB;AAAA;AAAA;;;AC1D7B,SAAS,cAAAO,cAAY,aAAAC,YAAW,iBAAAC,uBAAqB;AACrD,SAAS,YAAAC,iBAAgB;AACzB,SAAS,QAAAC,cAAY;AAOrB,SAAS,YAAY,OAAe,OAAe,MAAc,MAAc,OAAqB;AAClG,EAAAD,UAAS,iBAAiB,IAAI,WAAW,KAAK,IAAI,IAAI,aAAa;AAAA,IACjE,OAAO;AAAA,IACP,KAAK,EAAE,GAAG,QAAQ,KAAK,UAAU,MAAM;AAAA,IACvC,OAAO,CAAC,QAAQ,UAAU,MAAM;AAAA,IAChC,SAAS;AAAA,EACX,CAAC;AACH;AAKA,eAAsB,oBAAoB,MAA6F;AACrI,QAAM,QAAmE,CAAC;AAC1E,MAAI,OAAO;AACX,SAAO,QAAQ,GAAG;AAChB,UAAM,MAAM,uDAAuD,IAAI;AACvE,UAAM,OAAO,MAAM,MAAM,KAAK;AAAA,MAC5B,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,KAAK;AAAA,QACnC,QAAQ;AAAA,QACR,wBAAwB;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,cAAc,KAAK,MAAM,gBAAgB;AAAA,IAC3D;AACA,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,WAAW,EAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,MAAM,EAAE,MAAM,WAAW,EAAE,UAAU,CAAC;AAAA,IAC3E;AACA,QAAI,KAAK,SAAS,IAAK;AACvB;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,kBACpB,MACA,OACA,MACA,SACe;AACf,MAAI;AAAE,IAAAA,UAAS,gBAAgB,EAAE,OAAO,UAAU,SAAS,IAAK,CAAC;AAAA,EAAG,QAAQ;AAC1E,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,QAAQ,sBAAsB;AAChC,gBAAY,KAAK,OAAO,OAAO,MAAM,2BAA2B,QAAQ,oBAAoB;AAAA,EAC9F;AACA,cAAY,KAAK,OAAO,OAAO,MAAM,kBAAkB,QAAQ,YAAY;AAC7E;AAMO,SAAS,kBAAkB,cAAqC;AACrE,QAAM,cAAcC,OAAK,cAAc,WAAW,WAAW;AAC7D,EAAAH,WAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,eAAeG,OAAK,aAAa,YAAY;AACnD,EAAAF,gBAAc,cAAc,sBAAsB,OAAO;AACzD,SAAO;AACT;AAKO,SAAS,YAAY,UAAiC;AAC3D,MAAI,MAAM;AACV,SAAO,OAAO,QAAQ,KAAK;AACzB,QAAIF,aAAWI,OAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAC1C,UAAM,SAASA,OAAK,KAAK,IAAI;AAC7B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AA7FA,IA+Fa,cAKA;AApGb;AAAA;AAAA;AAUA;AAqFO,IAAM,eAAe;AAAA,MAC1B,cAAc;AAAA,MACd,gBAAgB;AAAA,IAClB;AAEO,IAAM,yBAAyB;AAAA;AAAA;;;AC5FtC,SAAS,YAAAC,WAAU,gBAAAC,qBAAoB;AACvC,SAAS,mBAAmB;AAC5B,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,uBAAuB;AAUhC,SAAS,gBAAgE;AACvE,MAAI;AACF,UAAM,YAAYF,UAAS,6BAA6B,EAAE,UAAU,SAAS,SAAS,KAAM,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAE,CAAC,EAAE,KAAK;AAEpI,UAAM,WAAW,UAAU,MAAM,6BAA6B;AAC9D,UAAM,YAAY,UAAU,MAAM,qCAAqC;AACvE,UAAM,QAAQ,YAAY;AAC1B,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,WAAW,MAAM,CAAC;AACxB,WAAO,EAAE,UAAU,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK,SAAS;AAAA,EACtE,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,SAAS,oBAAoE;AAC3E,MAAI;AACF,UAAM,UAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AACxD,UAAM,QAAwD,CAAC;AAC/D,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,KAAK,MAAM,KAAK,WAAW,GAAG,EAAG;AACxD,UAAI;AACF,cAAM,YAAYC,cAAa,OAAO,CAAC,MAAM,MAAM,MAAM,UAAU,WAAW,QAAQ,GAAG;AAAA,UACvF,UAAU;AAAA,UACV,SAAS;AAAA,UACT,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,QAChC,CAAC,EAAE,KAAK;AACR,cAAM,WAAW,UAAU,MAAM,6BAA6B;AAC9D,cAAM,YAAY,UAAU,MAAM,qCAAqC;AACvE,cAAM,QAAQ,YAAY;AAC1B,YAAI,OAAO;AACT,gBAAM,WAAW,MAAM,CAAC;AACxB,gBAAM,KAAK,EAAE,UAAU,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC;AAAA,QAC3E;AAAA,MACF,QAAQ;AAAA,MAAoC;AAAA,IAC9C;AACA,WAAO;AAAA,EACT,QAAQ;AAAE,WAAO,CAAC;AAAA,EAAG;AACvB;AAEA,SAAS,IAAI,IAAwC,UAAmC;AACtF,SAAO,IAAI,QAAQ,CAACE,aAAY,GAAG,SAAS,UAAUA,QAAO,CAAC;AAChE;AAEA,SAAS,qBAAsC;AAC7C,SAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,UAAM,SAASD,cAAa,CAAC,KAAK,QAAQ;AACxC,UAAI,IAAI,WAAW,WAAW;AAC5B,YAAI,UAAU,KAAK;AAAA,UACjB,+BAA+BE;AAAA,UAC/B,gCAAgC;AAAA,UAChC,gCAAgC;AAAA,QAClC,CAAC;AACD,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,IAAI,QAAQ,WAAW,IAAI,WAAW,QAAQ;AAChD,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI;AACR;AAAA,MACF;AAEA,UAAI,OAAO;AACX,UAAI,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAQ;AAAA,MAAO,CAAC;AAC5C,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,cAAI,CAAC,OAAO,cAAc;AACxB,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,+BAA+BA;AAAA,YACjC,CAAC;AACD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC,CAAC;AACzD;AAAA,UACF;AACA,cAAI,UAAU,KAAK;AAAA,YACjB,gBAAgB;AAAA,YAChB,+BAA+BA;AAAA,UACjC,CAAC;AACD,cAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AACpC,qBAAW,MAAM,OAAO,MAAM,GAAG,GAAG;AACpC,UAAAD,SAAQ,OAAO,YAAY;AAAA,QAC7B,QAAQ;AACN,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AAAA,QACnD;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAA+B;AACjD,UAAI,IAAI,SAAS,cAAc;AAC7B,eAAO,IAAI,MAAM,QAAQ,WAAW,kDAAkD,CAAC;AAAA,MACzF,OAAO;AACL,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AAED,WAAO,OAAO,WAAW;AAAA,EAC3B,CAAC;AACH;AAEA,SAASE,aAAY,KAAmB;AACtC,QAAM,EAAE,UAAAC,UAAS,IAAI,UAAQ,eAAoB;AACjD,QAAM,OAAO,QAAQ;AACrB,QAAM,KAAK,CAAC,QAAsB;AAChC,QAAI,IAAK,SAAQ,IAAI,6BAA6B,GAAG,EAAE;AAAA,EACzD;AACA,MAAI,SAAS,SAAU,CAAAA,UAAS,QAAQ,CAAC,GAAG,GAAG,EAAE;AAAA,WACxC,SAAS,QAAS,CAAAA,UAAS,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,GAAG,EAAE;AAAA,MAClE,CAAAA,UAAS,YAAY,CAAC,GAAG,GAAG,EAAE;AACrC;AAEA,eAAe,8BAAqE;AAClF,QAAM,MAAM,GAAGF,oBAAmB,oBAAoB,WAAW;AACjE,UAAQ,IAAI,+CAA+C;AAC3D,EAAAC,aAAY,GAAG;AAEf,UAAQ,IAAI,uCAAuC;AACnD,QAAM,UAAU,MAAM,mBAAmB;AACzC,UAAQ,IAAI,6BAAwB;AAEpC,QAAM,QAAQ,MAAM,oBAAoB,EAAE,OAAO,QAAQ,CAAC;AAC1D,MAAI,MAAM,WAAW,GAAG;AACtB,YAAQ,IAAI,wCAAwC;AACpD,WAAO,CAAC;AAAA,EACV;AAEA,UAAQ,IAAI,WAAW,MAAM,MAAM;AAAA,CAAW;AAC9C,QAAM,QAAQ,CAAC,GAAQ,MAAc;AACnC,YAAQ,IAAI,OAAO,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE;AAAA,EAChE,CAAC;AACD,UAAQ,IAAI;AAEZ,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,MAAI;AACF,UAAM,YAAY,MAAM,IAAI,IAAI,wDAAwD;AACxF,UAAM,UAAU,UACb,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,SAAS,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,EACrC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,IAAI,MAAM,MAAM;AAExD,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,sBAAsB;AAClC,aAAO,CAAC;AAAA,IACV;AAEA,WAAO,QAAQ,IAAI,CAAC,OAAO,EAAE,WAAW,MAAM,CAAC,EAAE,UAAU,EAAE;AAAA,EAC/D,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAEA,eAAsB,qBAAqB,MAA8C;AACvF,QAAM,YAAY,cAAc;AAChC,QAAM,cAAc,CAAC,YAAY,kBAAkB,IAAI,CAAC;AAExD,MAAI,MAAM,YAAY,WAAW;AAC/B,YAAQ,IAAI,4BAA4B;AACxC,QAAI;AACF,YAAM,WAAW,MAAM,aAAa;AACpC,YAAM,gBAAgB,SAAS;AAAA,QAAK,CAAC,MACnC,EAAE,OAAO,KAAK,CAAC,MAAW,EAAE,cAAc,UAAU,QAAQ;AAAA,MAC9D;AACA,UAAI,CAAC,eAAe;AAClB,cAAM,cAAc,UAAU,WAAW,CAAC,EAAE,WAAW,UAAU,SAAS,CAAC,CAAC;AAC5E,gBAAQ,IAAI,6BAAwB,UAAU,SAAS,eAAe,UAAU,QAAQ,EAAE;AAAA,MAC5F,OAAO;AACL,gBAAQ,IAAI,YAAO,UAAU,QAAQ,yCAAyC;AAAA,MAChF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,iCAA6B,IAAc,OAAO,EAAE;AAAA,IACnE;AACA,YAAQ,IAAI;AACZ;AAAA,EACF;AAEA,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,MAAI;AACF,YAAQ,IAAI,4BAA4B;AAExC,QAAI,oBAAoB,oBAAI,IAAY;AACxC,QAAI,YAAY,SAAS,GAAG;AAC1B,UAAI;AACF,cAAM,WAAW,MAAM,aAAa;AACpC,4BAAoB,IAAI;AAAA,UACtB,SAAS,QAAQ,CAAC,OAAY,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAW,EAAE,SAAS,CAAC;AAAA,QAC3E;AAAA,MACF,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,QAAI,YAAY,SAAS,GAAG;AAC1B,cAAQ,IAAI,WAAW,YAAY,MAAM;AAAA,CAA+B;AACxE,kBAAY,QAAQ,CAAC,GAAG,MAAM;AAC5B,cAAM,SAAS,kBAAkB,IAAI,EAAE,QAAQ;AAC/C,gBAAQ,IAAI,OAAO,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,oBAAe,EAAE,EAAE;AAAA,MAC5F,CAAC;AACD,YAAM,QAAQ,YAAY,SAAS;AACnC,YAAM,UAAU,YAAY,SAAS;AACrC,cAAQ,IAAI,OAAO,OAAO,KAAK,EAAE,SAAS,CAAC,CAAC,kCAAkC;AAC9E,cAAQ,IAAI,OAAO,OAAO,OAAO,EAAE,SAAS,CAAC,CAAC,gBAAgB;AAC9D,cAAQ,IAAI;AAEZ,YAAM,YAAY,MAAM,IAAI,IAAI,8DAA8D;AAC9F,YAAM,OAAO,UAAU,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,EAAE,KAAK,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC5F,cAAQ,IAAI;AACZ,SAAG,MAAM;AAET,UAAI,KAAK,SAAS,KAAK,GAAG;AACxB,cAAM,gBAAgB,MAAM,4BAA4B;AACxD,YAAI,cAAc,SAAS,GAAG;AAC5B,gBAAM,WAAW,cAAc,OAAO,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,SAAS,CAAC;AAChF,cAAI,SAAS,WAAW,GAAG;AACzB,oBAAQ,IAAI,iDAA4C;AAAA,UAC1D,OAAO;AACL,kBAAM,cAAc,SAAS,WAAW,IACpC,SAAS,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK,YAC1C;AACJ,kBAAM,cAAc,aAAa,QAAQ;AACzC,oBAAQ,IAAI,mBAAc,SAAS,MAAM,wBAAwB,WAAW,GAAG;AAAA,UACjF;AAAA,QACF;AAAA,MACF,WAAW,KAAK,SAAS,OAAO,KAAK,KAAK,WAAW,GAAG;AACtD,gBAAQ,IAAI,sDAAsD;AAAA,MACpE,OAAO;AACL,cAAM,cAAc,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,YAAY,MAAM;AACzF,cAAM,SAAS,YAAY,IAAI,CAAC,MAAM,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,QAAQ,CAAC;AACtG,YAAI,OAAO,WAAW,GAAG;AACvB,kBAAQ,IAAI,iDAA4C;AAAA,QAC1D,OAAO;AACL,qBAAW,QAAQ,QAAQ;AACzB,gBAAI;AACF,oBAAM,cAAc,KAAK,WAAW,CAAC,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC;AAClE,sBAAQ,IAAI,6BAAwB,KAAK,SAAS,eAAe,KAAK,QAAQ,EAAE;AAAA,YAClF,SAAS,KAAK;AACZ,sBAAQ,KAAK,2BAAsB,KAAK,QAAQ,KAAM,IAAc,OAAO,EAAE;AAAA,YAC/E;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,UAAoB,CAAC;AAC3B,UAAI,WAAW;AACb,gBAAQ,KAAK,mBAAmB,UAAU,QAAQ,GAAG;AAAA,MACvD;AACA,cAAQ,KAAK,gCAAgC;AAC7C,cAAQ,KAAK,cAAc;AAE3B,cAAQ,QAAQ,CAAC,KAAK,MAAM;AAC1B,gBAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,GAAG,EAAE;AAAA,MAClC,CAAC;AACD,cAAQ,IAAI;AAEZ,YAAM,SAAS,MAAM,IAAI,IAAI,qBAAqB;AAClD,YAAM,YAAY,SAAS,OAAO,KAAK,GAAG,EAAE;AAC5C,cAAQ,IAAI;AACZ,SAAG,MAAM;AAET,YAAM,WAAW,YAAY,IAAI;AACjC,YAAM,YAAY,YAAY,IAAI;AAClC,YAAM,UAAU,YAAY,IAAI;AAEhC,UAAI,cAAc,YAAY,WAAW;AACvC,YAAI;AACF,gBAAM,WAAW,MAAM,aAAa;AACpC,gBAAM,gBAAgB,SAAS;AAAA,YAAK,CAAC,MACnC,EAAE,OAAO,KAAK,CAAC,MAAW,EAAE,cAAc,UAAU,QAAQ;AAAA,UAC9D;AACA,cAAI,CAAC,eAAe;AAClB,kBAAM,cAAc,UAAU,WAAW,CAAC,EAAE,WAAW,UAAU,SAAS,CAAC,CAAC;AAC5E,oBAAQ,IAAI,6BAAwB,UAAU,SAAS,eAAe,UAAU,QAAQ,EAAE;AAAA,UAC5F,OAAO;AACL,oBAAQ,IAAI,YAAO,UAAU,QAAQ,yCAAyC;AAAA,UAChF;AAAA,QACF,SAAS,KAAK;AACZ,kBAAQ,KAAK,iCAA6B,IAAc,OAAO,EAAE;AAAA,QACnE;AAAA,MACF,WAAW,cAAc,WAAW;AAClC,cAAM,gBAAgB,MAAM,4BAA4B;AACxD,YAAI,cAAc,SAAS,GAAG;AAC5B,cAAI;AACF,kBAAM,WAAW,MAAM,aAAa;AACpC,kBAAM,cAAc,IAAI;AAAA,cACtB,SAAS,QAAQ,CAAC,OAAY,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAW,EAAE,SAAS,CAAC;AAAA,YAC3E;AACA,kBAAM,WAAW,cAAc,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,SAAS,CAAC;AAE1E,gBAAI,SAAS,WAAW,GAAG;AACzB,sBAAQ,IAAI,iDAA4C;AAAA,YAC1D,OAAO;AACL,oBAAM,cAAc,SAAS,WAAW,IACpC,SAAS,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK,YAC1C;AACJ,oBAAM,cAAc,aAAa,QAAQ;AACzC,sBAAQ,IAAI,mBAAc,SAAS,MAAM,wBAAwB,WAAW,GAAG;AAAA,YACjF;AAAA,UACF,SAAS,KAAK;AACZ,oBAAQ,KAAK,kCAA8B,IAAc,OAAO,EAAE;AAAA,UACpE;AAAA,QACF;AAAA,MACF,WAAW,cAAc,SAAS;AAChC,gBAAQ,IAAI,sDAAsD;AAAA,MACpE,OAAO;AACL,gBAAQ,IAAI,6CAA6C;AAAA,MAC3D;AAAA,IACF;AAAA,EACF,QAAQ;AACN,OAAG,MAAM;AAAA,EACX;AACA,UAAQ,IAAI;AACd;AA1UA,IAeME,mBACAH,sBAGA;AAnBN;AAAA;AAAA;AAYA;AACA;AAEA,IAAMG,oBAAmB,QAAQ,IAAI;AACrC,IAAMH,uBAAuBG,qBAAoB,eAAe,KAAKA,iBAAgB,IACjFA,oBACA;AACJ,IAAM,cAAc;AAAA;AAAA;;;ACTpB,eAAsB,kBAAkB,MAMrC;AACD,QAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,OAAO,EAAE,CAAC;AACjD,QAAM,OAAO,MAAM,MAAM,KAAK;AAAA,IAC5B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,iBAAiB,UAAU,KAAK,GAAG;AAAA,MACnC,cAAc;AAAA,IAChB;AAAA,IACA,QAAQ,YAAY,QAAQ,GAAI;AAAA,EAClC,CAAC;AAED,MAAI,CAAC,KAAK,IAAI;AACZ,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AAEA,QAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,SAAO,EAAE,SAAS,KAAK,SAAS,WAAW,UAAU;AACvD;AAjCA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AA0BA,SAAS,SAAAC,cAAa;AACtB,SAAS,iBAAAC,iBAAe,aAAa,aAAAC,YAAW,gBAAAC,gBAAc,cAAAC,oBAAkB;AAChF,SAAS,QAAAC,cAAY;AACrB,SAAS,WAAAC,iBAAe;AAejB,SAAS,yBAAkC;AAChD,SAAO,QAAQ,aAAa,YACvBF,aAAW,gDAAgD;AAClE;AA4lCA,SAAS,YAAY,YAA4B;AAC/C,SAAO;AAAA;AAAA,cAEK,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqGxC;AAMA,eAAsB,oBAAoB,OAA+B,CAAC,GAAkB;AAC1F,MAAI,QAAQ,aAAa,UAAU;AACjC,YAAQ,IAAI,8DAAyD;AACrE;AAAA,EACF;AACA,MAAI,QAAQ;AACZ,MAAI;AAAE,YAAQD,eAAa,UAAU,OAAO,EAAE,KAAK;AAAA,EAAG,QAAQ;AAAA,EAAC;AAC/D,MAAI,CAAC,OAAO;AACV,YAAQ,IAAI,qFAAqF;AACjG;AAAA,EACF;AACA,MAAI,KAAK,UAAU;AACjB,YAAQ,IAAI,mFAA8E;AAAA,EAC5F;AAKA,MAAI;AACF,UAAM,MAAM,8BAA8B;AAC1C,QAAI,IAAI,IAAI;AACV,cAAQ,IAAI,4DAAuD;AACnE,cAAQ,IAAI,QAAQ,IAAI,IAAI,sEAAiE;AAAA,IAC/F,OAAO;AACL,cAAQ,IAAI,+CAA0C,IAAI,MAAM,EAAE;AAAA,IACpE;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,IAAI,mDAA+C,IAAc,OAAO,EAAE;AAAA,EACpF;AAIA,QAAM,SAASE,OAAKC,UAAQ,GAAG,WAAW,aAAa;AACvD,EAAAJ,WAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,aAAa,YAAYG,OAAK,QAAQ,YAAY,CAAC;AACzD,EAAAJ,gBAAcI,OAAK,YAAY,QAAQ,GAAG,UAAU,OAAO;AAE3D,EAAAJ,gBAAcI,OAAK,YAAY,cAAc,GAAG,cAAc,EAAE,MAAM,IAAM,CAAC;AAC7E,EAAAJ,gBAAcI,OAAK,YAAY,cAAc,GAAG,cAAc,OAAO;AACrE,QAAM,aAAaA,OAAK,YAAY,QAAQ;AAC5C,EAAAJ,gBAAc,YAAY,YAAY,UAAU,GAAG,EAAE,MAAM,IAAM,CAAC;AAElE,QAAM,IAAI,QAAc,CAACM,aAAY;AAInC,UAAM,QAAQP,OAAM,QAAQ,CAAC,UAAU,GAAG;AAAA,MACxC,OAAO;AAAA,MACP,KAAK,EAAE,GAAG,QAAQ,KAAK,oBAAoB,aAAa,iBAAiB,UAAU,sBAAsB,eAAe,uBAAuB,gBAAgB,0BAA0B,mBAAmB,yBAAyB,kBAAkB,sBAAsB,eAAe,kBAAkB,OAAO,uBAAuB,UAAU,oBAAoB,KAAK,WAAW,MAAM,GAAG;AAAA,IACrY,CAAC;AACD,UAAM,UAAU,CAAC,QAAwB;AAAE,UAAI;AAAE,cAAM,KAAK,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAC;AAAA,IAAE;AAC7E,YAAQ,GAAG,UAAU,OAAO;AAC5B,YAAQ,GAAG,WAAW,OAAO;AAC7B,UAAM,GAAG,QAAQ,MAAM;AACrB,cAAQ,IAAI,UAAU,OAAO;AAC7B,cAAQ,IAAI,WAAW,OAAO;AAC9B,MAAAO,SAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AApzCA,IAkCM,aACA,UACA,eACA,gBACA,mBACA,kBACA,eACA,UAYA,UAm7BA,cAqIA;AA7mCN;AAAA;AAAA;AA8BA;AAIA,IAAM,cAAc;AACpB,IAAM,WAAW;AACjB,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,WAAWF,OAAKC,UAAQ,GAAG,WAAW,UAAU;AAYtD,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["existsSync","readFileSync","homedir","join","cached","randomUUID","existsSync","mkdirSync","homedir","join","cached","ensureDir","SYNKRO_DIR","cached","readFileSync","writeFileSync","appendFileSync","mkdirSync","renameSync","existsSync","homedir","join","ensureDir","SYNKRO_DIR","existsSync","readFileSync","writeFileSync","homedir","join","SYNKRO_DIR","existsSync","readFileSync","homedir","join","CONFIG_PATH","SYNKRO_DIR","CREDS_PATH","jwt","existsSync","readFileSync","writeFileSync","existsSync","readFileSync","unlinkSync","randomUUID","randomUUID","writeFileSync","existsSync","homedir","join","execFileSync","cmd","existsSync","homedir","join","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","readFileSync","writeFileSync","renameSync","mkdirSync","dirname","homedir","isSynkroEntry","SYNKRO_MARKER","removeSynkroEntries","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","url","jwt","resolveBunBin","existsSync","resolve","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","platform","join","dirname","args","resolve","emit","existsSync","mkdirSync","writeFileSync","execSync","join","execSync","execFileSync","createServer","resolve","SYNKRO_WEB_AUTH_URL","openBrowser","execFile","RAW_WEB_AUTH_URL","spawn","writeFileSync","mkdirSync","readFileSync","existsSync","join","homedir","resolve","SYNKRO_DIR","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","homedir","platform","join","spawnSync","SYNKRO_DIR","existsSync","mkdirSync","readFileSync","readdirSync","homedir","join","execSync","spawnSync","which","args","readFileSync","unlinkSync","homedir","platform","join","SYNKRO_DIR","args","resolve","createInterface","execSync","existsSync","readFileSync","homedir","platform","join","execFile","CONFIG_PATH","resolve","openBrowser","args","jwt","SYNKRO_DIR","detectGitRepo","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","readdirSync","unlinkSync","statSync","homedir","join","execSync","createInterface","createHash","ask","resolve","cursorApiKeyConfigured","writeCursorApiKey","validateCursorApiKey","a","which","SYNKRO_DIR","shellQuoteSingle","CONFIG_PATH","jwt","l","isLocalhost","assertDockerAvailable","setupGithubCommand","parseTomlValue","parseSynkroToml","cmd","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","copyFileSync","renameSync","unlinkSync","openSync","closeSync","join","homedir","spawnSync","init_install","existsSync","readdirSync","homedir","join","spawnSync","createInterface","resolve","args","purge","SYNKRO_DIR","init_install","appendFileSync","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","init_client","resolve","args","init_client","execFileSync","spawnSync","spawn","homedir","join","connect","args","resolve","SESSION_DIR","SESSION_DIR_2","SESSION_DIR_3","SESSION_DIR_4","existsSync","readFileSync","homedir","join","CONFIG_PATH","spawnSync","homedir","join","existsSync","readFileSync","writeFileSync","CONFIG_PATH","jwt","ready","resolve","args","init_install","init_client","existsSync","readFileSync","readdirSync","homedir","join","execSync","createInterface","readConfigEnv","CONFIG_PATH","ask","resolve","ok","existsSync","readFileSync","writeFileSync","join","existsSync","mkdirSync","readdirSync","rmSync","writeFileSync","homedir","join","detectGitRepo","readFileSync","writeFileSync","existsSync","join","homedir","readConfigEnv","CONFIG_PATH","dockerInstall","dockerSafeStop","readContainerConfig","assertDockerAvailable","waitForContainerReady","detectGitRepo","args","config","SYNKRO_DIR","telemetry_exports","createInterface","args","truncate","resolve","init_telemetry","readFileSync","existsSync","resolve","printHelp","emit","installCommand","parseArgs","disconnectCommand","authenticate","isAuthenticated","gradeCommand","localCcCommand","importCommand","syncCommand","stopCommand","startCommand","restartCommand","updateCommand","configCommand","runClaudeDesktopTap","telemetryCommand","flushDetached","closeDb"]}
|
|
1
|
+
{"version":3,"sources":["../cli/telemetry/metaCache.ts","../cli/telemetry/identity.ts","../cli/telemetry/emit.ts","../cli/telemetry/db.ts","../cli/telemetry/drain.ts","../cli/telemetry/optout.ts","../cli/telemetry/wire.ts","../cli/telemetry/flush.ts","../cli/telemetry/stats.ts","../cli/telemetry/purge.ts","../cli/telemetry/client.ts","../cli/telemetry/export.ts","../cli/telemetry/index.ts","../cli/installer/agentDetect.ts","../cli/installer/platform.ts","../cli/installer/ccHookConfig.ts","../cli/installer/cursorHookConfig.ts","../cli/installer/mcpConfig.ts","../cli/installer/skillParser.ts","../cli/installer/hookScriptsTs.ts","../cli/auth/stub.ts","../cli/auth/index.ts","../cli/api/projects.ts","../cli/installer/workflowTemplate.ts","../cli/installer/githubSetup.ts","../cli/commands/repoConnect.ts","../cli/installer/promptFetcher.ts","../cli/installer/claudeDesktopTap.ts","../cli/local-cc/macKeychain.ts","../cli/local-cc/dockerInstall.ts","../cli/local-cc/setupToken.ts","../cli/commands/setupGithub.ts","../cli/commands/install.ts","../cli/local-cc/install.ts","../cli/commands/disconnect.ts","../cli/local-cc/turnLog.ts","../cli/local-cc/client.ts","../cli/commands/grade.ts","../cli/local-cc/pueue.ts","../cli/local-cc/settings.ts","../cli/commands/localCc.ts","../cli/commands/import.ts","../cli/installer/packVerify.ts","../cli/installer/lockfile.ts","../cli/commands/sync.ts","../cli/commands/lifecycle.ts","../cli/commands/config.ts","../cli/commands/telemetry.ts","../cli/bootstrap.js"],"sourcesContent":["/**\n * Synchronous mirror of telemetry_meta for use on the hot emit() path.\n *\n * Why a separate JSON file:\n * emit() is sync (callable from any code path / runtime, including bun\n * hooks). The authoritative meta store lives in pglite inside the docker\n * container, which is async (postgres.js) and may be unreachable. We can't\n * open it on every emit. So:\n *\n * - setTelemetryState() and ingest from drain.ts write to BOTH this file\n * AND telemetry_meta in pglite.\n * - emit() only reads this file. If the file is missing, defaults apply.\n *\n * Layout: ~/.synkro/telemetry-meta.json — single object, no schema migration\n * needed because the keys are small and forward-compatible (unknown keys\n * ignored).\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nexport interface TelemetryMeta {\n install_id: string;\n enabled: boolean;\n remote_flush_enabled: boolean;\n /** ISO timestamp of last successful drain into local pglite (best-effort). */\n last_drained_at?: string;\n /**\n * Flush bookkeeping. Lives here (not in pglite) so the throttle + flush\n * state work in CLOUD mode, where there is no local pglite at all — the\n * remote flush reads its cadence from this file, independent of any DB.\n */\n last_flush_at?: string;\n last_flush_ok_at?: string;\n last_flush_error?: string;\n}\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst META_PATH = join(SYNKRO_DIR, 'telemetry-meta.json');\n\nconst DEFAULTS: TelemetryMeta = {\n install_id: '',\n enabled: true,\n remote_flush_enabled: true,\n};\n\nlet cached: TelemetryMeta | null = null;\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) {\n mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n }\n}\n\n/**\n * Read the meta cache, generating an install_id on first read.\n * Synchronous and very fast (single small JSON file).\n * Cached for the rest of the process; call invalidateMetaCache() after a write.\n */\nexport function readMetaCache(): TelemetryMeta {\n if (cached) return cached;\n let meta: TelemetryMeta = { ...DEFAULTS };\n if (existsSync(META_PATH)) {\n try {\n const raw = readFileSync(META_PATH, 'utf-8');\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') {\n meta = { ...DEFAULTS, ...(parsed as Partial<TelemetryMeta>) };\n }\n } catch {\n // unparseable — fall through to defaults; we'll rewrite below.\n }\n }\n if (!meta.install_id) {\n meta.install_id = randomUUID();\n writeMetaCache(meta);\n }\n cached = meta;\n return cached;\n}\n\n/**\n * Atomically replace the meta file. Cheap; do not call from emit() hot path.\n */\nexport function writeMetaCache(meta: TelemetryMeta): void {\n try {\n ensureDir();\n const tmp = `${META_PATH}.tmp`;\n writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 0o600 });\n renameSync(tmp, META_PATH);\n cached = { ...meta };\n } catch {\n // best-effort — caller already has the new state in memory if they need it\n }\n}\n\nexport function invalidateMetaCache(): void {\n cached = null;\n}\n\n/** Convenience: update one or more fields atomically. */\nexport function patchMetaCache(patch: Partial<TelemetryMeta>): TelemetryMeta {\n const current = readMetaCache();\n const next: TelemetryMeta = { ...current, ...patch };\n writeMetaCache(next);\n return next;\n}\n","/**\n * Per-process identity context for telemetry — DESIGN.md §3.\n *\n * Resolves install_id (persisted in metaCache, generated once), user/org/email\n * (from the WorkOS JWT in ~/.synkro/credentials.json), and machine info\n * (cli_version, platform, hostname_hash, node_version). Cached once per process.\n *\n * Synchronous — emit() must not await for identity. The JWT is decoded inline\n * to avoid pulling jsonwebtoken/jose just for the claim grab.\n *\n * Never throws — partial identity is acceptable.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir, hostname, platform } from 'node:os';\nimport { join } from 'node:path';\n\nimport { readMetaCache } from './metaCache.js';\n\n// Injected by tsup at build time (see tsup.config.ts).\ndeclare const __SYNKRO_CLI_VERSION__: string;\n\nconst CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH\n || join(homedir(), '.synkro', 'credentials.json');\n\nexport interface IdentityContext {\n install_id: string;\n user_id?: string;\n org_id?: string;\n email?: string;\n cli_version: string;\n platform: string;\n hostname_hash: string;\n node_version: string;\n}\n\nlet cached: IdentityContext | null = null;\n\ninterface WorkOSClaims {\n sub?: string;\n org_id?: string;\n email?: string;\n}\n\ninterface StoredCreds {\n access_token?: string;\n user_id?: string;\n org_id?: string;\n email?: string;\n}\n\nfunction decodeJwtPayload(token: string): WorkOSClaims | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const json = Buffer.from(parts[1], 'base64url').toString('utf-8');\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === 'object') return parsed as WorkOSClaims;\n return null;\n } catch {\n return null;\n }\n}\n\nfunction loadCredentialsIdentity(): { user_id?: string; org_id?: string; email?: string } {\n if (!existsSync(CREDS_PATH)) return {};\n let raw: string;\n try {\n raw = readFileSync(CREDS_PATH, 'utf-8');\n } catch {\n return {};\n }\n\n let creds: StoredCreds;\n try {\n creds = JSON.parse(raw) as StoredCreds;\n } catch {\n return {};\n }\n\n const out: { user_id?: string; org_id?: string; email?: string } = {};\n if (creds.user_id) out.user_id = creds.user_id;\n if (creds.org_id) out.org_id = creds.org_id;\n if (creds.email) out.email = creds.email;\n\n if (creds.access_token && (!out.user_id || !out.org_id || !out.email)) {\n const claims = decodeJwtPayload(creds.access_token);\n if (claims) {\n if (!out.user_id && claims.sub) out.user_id = claims.sub;\n if (!out.org_id && claims.org_id) out.org_id = claims.org_id;\n if (!out.email && claims.email) out.email = claims.email;\n }\n }\n\n return out;\n}\n\nfunction hostnameHash(): string {\n try {\n return createHash('sha256').update(hostname()).digest('hex').slice(0, 16);\n } catch {\n return '';\n }\n}\n\nexport function getIdentity(): IdentityContext {\n if (cached) return cached;\n\n let cliVersion = '0.0.0';\n try {\n cliVersion = __SYNKRO_CLI_VERSION__;\n } catch { /* declare-only at type-check time, defined at build/runtime */ }\n\n const creds = loadCredentialsIdentity();\n const meta = readMetaCache();\n\n cached = {\n install_id: meta.install_id,\n user_id: creds.user_id,\n org_id: creds.org_id,\n email: creds.email,\n cli_version: cliVersion,\n platform: platform(),\n hostname_hash: hostnameHash(),\n node_version: process.version,\n };\n return cached;\n}\n","/**\n * Synchronous telemetry emit.\n *\n * Appends one JSON line per event to ~/.synkro/telemetry-pending.jsonl.\n * Sync, fast, works in any runtime (node, bun) and tolerates a missing\n * pglite — drain.ts later batches these into pglite via postgres.js.\n *\n * appendFileSync with flag:'a' is atomic per call for writes <= PIPE_BUF\n * (≥4KB on POSIX) so concurrent emits from multiple hook processes don't\n * tear lines. Events are kept compact (single-line JSON, no pretty-print)\n * to stay under that threshold.\n *\n * Lock-free BY DESIGN: hooks and the compiled binary depend on emit staying a\n * plain synchronous appendFileSync — no async, no locking. flush() owns ALL\n * queue rotation/locking. When flush rotates the live queue out from under us\n * (renames it to a .processing sibling), O_APPEND makes this safe with zero\n * coordination: an emit that already opened the old inode lands in the rotated\n * file (which flush processes), and any later emit reopens the path fresh,\n * recreating an empty live queue. Either way, no event is lost.\n *\n * Never throws — telemetry must not take the CLI or hooks down.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { execFileSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { getIdentity } from './identity.js';\nimport { readMetaCache } from './metaCache.js';\nimport type { EmitOptions, EventContext, EventType } from './types.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nexport const PENDING_PATH = join(SYNKRO_DIR, 'telemetry-pending.jsonl');\n\ninterface GitInfo {\n branch: string | null;\n sha: string | null;\n}\n\n// One git-exec per (process, cwd) — keeps a tight loop of hook emits fast.\nconst gitCache = new Map<string, GitInfo>();\n\nfunction deriveGit(cwd: string): GitInfo {\n const cached = gitCache.get(cwd);\n if (cached) return cached;\n const info: GitInfo = { branch: null, sha: null };\n try {\n const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 500, encoding: 'utf-8',\n }).trim();\n if (branch) info.branch = branch;\n } catch {}\n try {\n const sha = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 500, encoding: 'utf-8',\n }).trim();\n if (sha) info.sha = sha;\n } catch {}\n gitCache.set(cwd, info);\n return info;\n}\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) {\n mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n }\n}\n\n// PRIVACY BOUNDARY: telemetry NEVER collects the grader's free-text explanations\n// — they can quote the user's rule text, which is IP. As a guard independent of\n// any caller, drop `reasoning`/`raw_response` outright and truncate `reason` to a\n// short reason CODE, so no free-text rule content can reach the queue (and thus\n// the cloud). Emit sites already pass constant reason codes; this makes it\n// impossible to regress.\nconst REASON_CODE_MAX = 200;\nfunction redactContext(context: unknown): unknown {\n if (!context || typeof context !== 'object' || Array.isArray(context)) return context;\n const c = context as Record<string, unknown>;\n if (!('reasoning' in c) && !('raw_response' in c) && typeof c.reason !== 'string') return context;\n const out: Record<string, unknown> = { ...c };\n delete out.reasoning;\n delete out.raw_response;\n if (typeof out.reason === 'string' && out.reason.length > REASON_CODE_MAX) {\n out.reason = out.reason.slice(0, REASON_CODE_MAX);\n }\n return out;\n}\n\nexport function emit<T extends EventType>(\n eventType: T,\n context: EventContext[T],\n opts?: EmitOptions,\n): void {\n try {\n const meta = readMetaCache();\n if (!meta.enabled) return;\n\n const identity = getIdentity();\n const cwd = opts?.cwd ?? process.cwd();\n const git = deriveGit(cwd);\n const emitter = process.env.SYNKRO_TELEMETRY_EMITTER || 'bootstrap';\n\n const row = {\n client_event_id: randomUUID(),\n event_type: eventType,\n occurred_at: new Date().toISOString(),\n\n install_id: identity.install_id,\n user_id: identity.user_id ?? null,\n org_id: identity.org_id ?? null,\n email: identity.email ?? null,\n\n cli_version: identity.cli_version,\n emitter,\n\n agent_kind: opts?.agent_kind ?? null,\n cc_session_id: opts?.cc_session_id ?? null,\n cc_tool_use_id: opts?.cc_tool_use_id ?? null,\n cc_turn_id: opts?.cc_turn_id ?? null,\n cc_model: opts?.cc_model ?? null,\n permission_mode: opts?.permission_mode ?? null,\n\n cwd,\n repo: opts?.repo ?? null,\n git_branch: git.branch,\n git_sha: git.sha,\n\n policy_name: opts?.policy_name ?? null,\n capture_depth: opts?.capture_depth ?? null,\n\n platform: identity.platform,\n hostname_hash: identity.hostname_hash || null,\n node_version: identity.node_version,\n\n context: redactContext(context),\n };\n\n ensureDir();\n appendFileSync(PENDING_PATH, JSON.stringify(row) + '\\n', { mode: 0o600 });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry emit(${eventType}) failed: ${msg}\\n`);\n }\n}\n","/**\n * Telemetry store — pglite via postgres.js.\n *\n * The actual database is the long-running PGlite instance inside the\n * synkro-server docker container (`pglite-bootstrap.ts`). We connect over\n * the container's host-mapped TCP port (default 127.0.0.1:15433) using\n * postgres.js — same wire protocol as a real Postgres.\n *\n * Implication: when `synkro start` hasn't run (or the container is down),\n * connectDb() returns null. Telemetry collection still works — emit()\n * writes JSONL to disk; drain.ts ships those rows once the container is\n * back up.\n *\n * Tables: telemetry_events, telemetry_meta. Live alongside synkro-server's\n * own tables (guard_checks, scans, …) in the same `postgres` database.\n */\n\nimport postgres from 'postgres';\n\nconst PGLITE_HOST = process.env.SYNKRO_PGLITE_HOST || '127.0.0.1';\nconst PGLITE_PORT = parseInt(\n process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || '15433',\n 10,\n);\nconst PGLITE_USER = process.env.SYNKRO_PGLITE_USER || 'postgres';\nconst PGLITE_DB = process.env.SYNKRO_PGLITE_DB || 'postgres';\n\nconst SCHEMA_VERSION = 1;\n\nexport type Sql = ReturnType<typeof postgres>;\n\nlet cached: Sql | null = null;\nlet migrated = false;\n\n// A failed probe latches so we don't hammer a truly-down port on every emit —\n// but only for INIT_RETRY_MS. A long-lived process (REPL, daemon) that first\n// touched pglite while the container was down still reconnects once it's back.\nconst INIT_RETRY_MS = 30_000;\nlet initFailedAt = 0;\n\nconst MIGRATION_V1 = `\n CREATE TABLE IF NOT EXISTS telemetry_events (\n id BIGSERIAL PRIMARY KEY,\n client_event_id TEXT NOT NULL UNIQUE,\n\n event_type TEXT NOT NULL,\n occurred_at TIMESTAMPTZ NOT NULL,\n\n install_id TEXT NOT NULL,\n user_id TEXT,\n org_id TEXT,\n email TEXT,\n\n cli_version TEXT NOT NULL,\n emitter TEXT NOT NULL,\n\n agent_kind TEXT,\n cc_session_id TEXT,\n cc_tool_use_id TEXT,\n cc_turn_id TEXT,\n cc_model TEXT,\n permission_mode TEXT,\n\n cwd TEXT,\n repo TEXT,\n git_branch TEXT,\n git_sha TEXT,\n\n policy_name TEXT,\n capture_depth TEXT,\n\n platform TEXT NOT NULL,\n hostname_hash TEXT,\n node_version TEXT,\n\n context JSONB NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS telemetry_events_by_type_time\n ON telemetry_events (event_type, occurred_at DESC);\n CREATE INDEX IF NOT EXISTS telemetry_events_by_session\n ON telemetry_events (cc_session_id, occurred_at) WHERE cc_session_id IS NOT NULL;\n\n CREATE TABLE IF NOT EXISTS telemetry_meta (\n k TEXT PRIMARY KEY,\n v TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS sessions_command_history (\n id BIGSERIAL PRIMARY KEY,\n command TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n\n CREATE TABLE IF NOT EXISTS sessions_project_keys (\n slug TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n project_name TEXT NOT NULL,\n api_key TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n`;\n\n/**\n * Open / return the postgres.js client pointed at the container's PGlite.\n * Returns null if the container is unreachable; callers (drain, stats, …)\n * surface that condition explicitly. Never throws.\n */\nexport async function connectDb(): Promise<Sql | null> {\n if (cached) return cached;\n if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;\n try {\n const sql = postgres({\n host: PGLITE_HOST,\n port: PGLITE_PORT,\n username: PGLITE_USER,\n database: PGLITE_DB,\n // max:1 mirrors synkro-server — PGlite is single-threaded; multiple\n // sockets just queue at the multiplexer.\n max: 1,\n connect_timeout: 2,\n idle_timeout: 30,\n onnotice: () => {},\n });\n // Probe with a no-op so a dead port fails fast instead of on first query.\n await sql`SELECT 1`;\n cached = sql;\n initFailedAt = 0;\n if (!migrated) await runMigrations(sql);\n return cached;\n } catch (err) {\n initFailedAt = Date.now();\n // Container-down is the common case — silent by default. Surface only\n // when explicitly debugging: SYNKRO_TELEMETRY_DEBUG=1.\n if (process.env.SYNKRO_TELEMETRY_DEBUG === '1') {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry pglite unreachable at ${PGLITE_HOST}:${PGLITE_PORT}: ${msg}\\n`);\n }\n return null;\n }\n}\n\nasync function runMigrations(sql: Sql): Promise<void> {\n try {\n await sql.unsafe(MIGRATION_V1);\n const rows = await sql`SELECT v FROM telemetry_meta WHERE k = 'schema_version'`;\n const current = rows.length ? Number(rows[0].v) : 0;\n if (current !== SCHEMA_VERSION) {\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('schema_version', ${String(SCHEMA_VERSION)})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n }\n migrated = true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry migration failed: ${msg}\\n`);\n }\n}\n\n/** Close the cached connection. Call from process exit handlers. */\nexport async function closeDb(): Promise<void> {\n if (!cached) return;\n try { await cached.end({ timeout: 1 }); } catch {}\n cached = null;\n}\n\n/**\n * Reset module state — for tests / dev. Drops cached connection without\n * closing it (caller's responsibility if needed).\n */\nexport function resetDbForTests(): void {\n cached = null;\n initFailedAt = 0;\n migrated = false;\n}\n","/**\n * The telemetry queue: ~/.synkro/telemetry-pending.jsonl.\n *\n * The JSONL file is the single source of truth for undelivered events. Two\n * independent sinks consume it:\n *\n * 1. REMOTE flush (flush.ts) — POST to /api/v1/telemetry/flush (central\n * Timescale). This is the authoritative store. Works with no local\n * container, so it's what gives cloud mode telemetry parity.\n * 2. LOCAL pglite (this file, drainToPglite) — an OPTIONAL, idempotent,\n * NON-destructive copy into the local container's pglite, purely so the\n * local Deno dashboard has something to read. Only happens when a local\n * container is up (local mode). It never removes lines from the queue —\n * flush owns line removal once an event is delivered to every available\n * destination.\n *\n * So: drainToPglite() = \"mirror the queue into the local DB for the dashboard\",\n * flush() = \"deliver the queue to its real destinations and prune what landed\".\n *\n * Nothing here throws; callers get structured results.\n */\n\nimport { openSync, closeSync, fstatSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, mkdirSync, renameSync, existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { connectDb, type Sql } from './db.js';\nimport { PENDING_PATH } from './emit.js';\nimport { patchMetaCache } from './metaCache.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst QUEUE_LOCK = join(SYNKRO_DIR, '.telemetry-queue.lock');\n\n// Rotate target for flush: the live queue is atomically renamed here, processed,\n// then unlinked. A crash leaves it orphaned; the next flush folds it back in\n// (recoverProcessing). Sibling of PENDING_PATH so it shares the same dir/perms.\nconst PROCESSING_PATH = `${PENDING_PATH}.processing`;\n\n// Insert chunk size — keeps a single multi-row INSERT modest for PGlite's\n// single-threaded executor and well under Postgres' parameter ceiling.\nconst CHUNK = 500;\nconst STALE_LOCK_MS = 60_000;\n// Safety cap on the queue. If no store is reachable for a long stretch the queue\n// would otherwise grow without bound; past this many rows the OLDEST are dropped\n// (once, non-silently) rather than risk an unbounded pending file.\nconst MAX_QUEUE_ROWS = 50_000;\n\nexport interface PendingRow {\n client_event_id: string;\n event_type: string;\n occurred_at: string;\n install_id: string;\n user_id: string | null;\n org_id: string | null;\n email: string | null;\n cli_version: string;\n emitter: string;\n agent_kind: string | null;\n cc_session_id: string | null;\n cc_tool_use_id: string | null;\n cc_turn_id: string | null;\n cc_model: string | null;\n permission_mode: string | null;\n cwd: string | null;\n repo: string | null;\n git_branch: string | null;\n git_sha: string | null;\n policy_name: string | null;\n capture_depth: string | null;\n platform: string;\n hostname_hash: string | null;\n node_version: string;\n context: unknown;\n}\n\nexport interface DrainResult {\n ok: boolean;\n ingested: number;\n pending: number;\n reason?: 'db_unavailable' | 'no_pending' | 'error';\n error?: string;\n}\n\nfunction ensureDir(): void {\n if (!existsSync(SYNKRO_DIR)) mkdirSync(SYNKRO_DIR, { recursive: true, mode: 0o700 });\n}\n\n/**\n * Acquire the queue lock. Only writers that REWRITE the queue (flush) need it;\n * non-destructive readers (drainToPglite) don't.\n *\n * Race-free by construction: we always try the atomic exclusive create FIRST\n * (openSync 'wx'). Only when that fails do we consider stealing a stale lock,\n * and the steal re-attempts the SAME atomic 'wx' create — which is the single\n * arbiter, so at most one caller wins. There is no existsSync/stat → act\n * window (CWE-362): every decision is made from the failure of an atomic op,\n * and staleness is read from the lock's own fd (fstat), not a path re-stat.\n */\nexport function acquireQueueLock(): number | null {\n ensureDir();\n try {\n return openSync(QUEUE_LOCK, 'wx');\n } catch {\n // Lock already exists. Inspect it via its own fd (no path TOCTOU).\n let fd: number;\n try {\n fd = openSync(QUEUE_LOCK, 'r');\n } catch {\n // Vanished between our create-attempt and open — retry the create once.\n try { return openSync(QUEUE_LOCK, 'wx'); } catch { return null; }\n }\n let stale = false;\n try { stale = Date.now() - fstatSync(fd).mtimeMs > STALE_LOCK_MS; } catch {}\n try { closeSync(fd); } catch {}\n if (!stale) return null;\n // Stale holder (crashed >60s ago). Remove and re-create atomically; the\n // 'wx' create is the arbiter, so concurrent stealers can't both win. A rare\n // double-steal is benign here — flush rewrites the queue atomically and the\n // server dedups on client_event_id, so at worst a few events re-send.\n try { unlinkSync(QUEUE_LOCK); } catch {}\n try { return openSync(QUEUE_LOCK, 'wx'); } catch { return null; }\n }\n}\n\nexport function releaseQueueLock(fd: number): void {\n try { closeSync(fd); } catch {}\n try { unlinkSync(QUEUE_LOCK); } catch {}\n}\n\n/** Parse a JSONL queue file into valid rows + a count of malformed lines. */\nfunction parseQueueFile(path: string): { rows: PendingRow[]; malformed: number } {\n if (!existsSync(path)) return { rows: [], malformed: 0 };\n let raw: string;\n try { raw = readFileSync(path, 'utf-8'); } catch { return { rows: [], malformed: 0 }; }\n if (!raw) return { rows: [], malformed: 0 };\n const rows: PendingRow[] = [];\n let malformed = 0;\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const r = JSON.parse(line) as PendingRow;\n if (r && r.client_event_id && r.event_type) rows.push(r);\n else malformed++;\n } catch {\n malformed++;\n }\n }\n return { rows, malformed };\n}\n\n/** Parse the live JSONL queue. Returns valid rows + a count of malformed lines. */\nexport function readQueue(): { rows: PendingRow[]; malformed: number } {\n return parseQueueFile(PENDING_PATH);\n}\n\n/** Atomically rewrite the queue with exactly `rows` (empty → empty file). */\nexport function rewriteQueue(rows: PendingRow[]): void {\n ensureDir();\n const body = rows.length ? rows.map((r) => JSON.stringify(r)).join('\\n') + '\\n' : '';\n const tmp = `${PENDING_PATH}.tmp`;\n try {\n writeFileSync(tmp, body, { mode: 0o600 });\n renameSync(tmp, PENDING_PATH);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry queue rewrite failed: ${msg}\\n`);\n }\n}\n\n// ── Rotate-then-process (flush queue integrity) ──────────────────────────────\n// flush must NOT overwrite events emitted while it works. Rather than reading a\n// snapshot and rewriting the whole file at the end (which destroys concurrent\n// appends), it ROTATES the live queue into PROCESSING_PATH, works only from\n// there, then appends any undelivered survivors back. All three helpers assume\n// the caller holds the queue lock; emit() stays lock-free and just keeps\n// appending to a fresh PENDING_PATH that O_APPEND recreates for it.\n\n/**\n * Crash recovery: if a previous flush rotated the queue into .processing but\n * died before clearing it, fold those rows back onto the live queue so the next\n * cycle reprocesses them. No-op when nothing is orphaned. MUST run BEFORE\n * rotateQueueForProcessing so the orphan isn't clobbered by the rename.\n * Duplicates are harmless (server dedups on client_event_id; pglite ON CONFLICT).\n */\nexport function recoverProcessing(): void {\n if (!existsSync(PROCESSING_PATH)) return;\n ensureDir();\n let body: string;\n try { body = readFileSync(PROCESSING_PATH, 'utf-8'); } catch { return; }\n try {\n if (body.trim()) appendFileSync(PENDING_PATH, body.endsWith('\\n') ? body : body + '\\n', { mode: 0o600 });\n unlinkSync(PROCESSING_PATH);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry queue recovery failed: ${msg}\\n`);\n }\n}\n\n/**\n * Atomically rotate the live queue into .processing and return its rows. A\n * concurrent lock-free emit either already opened the old inode (its line lands\n * in the rotated file we process) or reopens the path afterwards (O_APPEND\n * recreates an empty live queue) — so nothing emitted mid-cycle is lost. Returns\n * an empty set when there is nothing to rotate. Enforces the max-queue cap: past\n * MAX_QUEUE_ROWS the OLDEST rows are dropped with one non-silent warning.\n */\nexport function rotateQueueForProcessing(): { rows: PendingRow[]; malformed: number } {\n ensureDir();\n if (!existsSync(PENDING_PATH)) return { rows: [], malformed: 0 };\n try {\n renameSync(PENDING_PATH, PROCESSING_PATH);\n } catch {\n return { rows: [], malformed: 0 }; // vanished — treat as empty this cycle\n }\n const { rows, malformed } = parseQueueFile(PROCESSING_PATH);\n if (rows.length > MAX_QUEUE_ROWS) {\n const dropped = rows.length - MAX_QUEUE_ROWS;\n process.stderr.write(`[synkro] telemetry queue over ${MAX_QUEUE_ROWS}-row cap: dropping ${dropped} oldest event(s)\\n`);\n return { rows: rows.slice(dropped), malformed };\n }\n return { rows, malformed };\n}\n\n/**\n * Close out a flush cycle: append undelivered survivors back onto the LIVE queue\n * (which may have grown from concurrent emits), then drop the processed file.\n * Order is crash-safe — survivors are persisted BEFORE the unlink, so a crash in\n * between just re-folds (and idempotently re-processes) them next run rather than\n * losing them. Pass [] to simply discard a fully-drained processing file. The\n * append is a single call, matching emit()'s own atomicity contract.\n */\nexport function finalizeProcessing(survivors: PendingRow[]): void {\n if (survivors.length) {\n ensureDir();\n const body = survivors.map((r) => JSON.stringify(r)).join('\\n') + '\\n';\n try {\n appendFileSync(PENDING_PATH, body, { mode: 0o600 });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry survivor re-append failed: ${msg}\\n`);\n return; // keep the processing file if survivors couldn't be saved\n }\n }\n try { unlinkSync(PROCESSING_PATH); } catch {}\n}\n\n/**\n * Bulk-insert rows into telemetry_events, idempotent on client_event_id.\n * Chunked. Returns the number of NEW rows inserted (conflicts skipped).\n *\n * Resilient per chunk: a poison chunk/row is logged to stderr and SKIPPED so it\n * can't fail the whole batch — one bad row must not wedge queue removal for\n * every other row (see flush.ts). When `mirroredIds` is supplied, the id of\n * every row in a chunk that landed is added to it, so a caller for which pglite\n * is the ONLY store (local-only mode) can tell which rows are safe to drop.\n */\nexport async function insertEvents(sql: Sql, rows: PendingRow[], mirroredIds?: Set<string>): Promise<number> {\n let inserted = 0;\n for (let i = 0; i < rows.length; i += CHUNK) {\n const batch = rows.slice(i, i + CHUNK);\n const payload = batch.map((r) => ({\n client_event_id: r.client_event_id,\n event_type: r.event_type,\n occurred_at: r.occurred_at,\n install_id: r.install_id,\n user_id: r.user_id,\n org_id: r.org_id,\n email: r.email,\n cli_version: r.cli_version,\n emitter: r.emitter,\n agent_kind: r.agent_kind,\n cc_session_id: r.cc_session_id,\n cc_tool_use_id: r.cc_tool_use_id,\n cc_turn_id: r.cc_turn_id,\n cc_model: r.cc_model,\n permission_mode: r.permission_mode,\n cwd: r.cwd,\n repo: r.repo,\n git_branch: r.git_branch,\n git_sha: r.git_sha,\n policy_name: r.policy_name,\n capture_depth: r.capture_depth,\n platform: r.platform,\n hostname_hash: r.hostname_hash,\n node_version: r.node_version,\n // Pass the object, NOT a pre-stringified string: postgres.js JSON-encodes\n // jsonb-bound values itself; stringifying would double-encode into a jsonb\n // string scalar (context->>'k' would then return null for every key).\n // Cast narrows `unknown ?? {}` (which TS widens to the universal `{}`) to a\n // concrete JSON object so postgres.js's helper accepts it as a jsonb value.\n context: (r.context ?? {}) as Record<string, unknown>,\n }));\n // postgres.js's multi-row insert helper resolves its row generic to `never`\n // for a heterogeneous nullable shape, so the columns-typed overload won't\n // match our concrete payload. Cast just the rows arg to `any` — the helper\n // still returns a proper Helper for the template tag. Runtime is correct and\n // smoke-tested; only the static overload is bypassed.\n try {\n const res = await sql`\n INSERT INTO telemetry_events ${sql(payload as unknown as Record<string, never>[],\n 'client_event_id', 'event_type', 'occurred_at',\n 'install_id', 'user_id', 'org_id', 'email',\n 'cli_version', 'emitter',\n 'agent_kind', 'cc_session_id', 'cc_tool_use_id', 'cc_turn_id', 'cc_model', 'permission_mode',\n 'cwd', 'repo', 'git_branch', 'git_sha',\n 'policy_name', 'capture_depth',\n 'platform', 'hostname_hash', 'node_version',\n 'context'\n )}\n ON CONFLICT (client_event_id) DO NOTHING\n `;\n inserted += res.count ?? batch.length;\n // Chunk landed: every row is now present in pglite (freshly inserted or\n // already there via ON CONFLICT), so it's safe to mark as mirrored.\n if (mirroredIds) for (const r of batch) mirroredIds.add(r.client_event_id);\n } catch (err) {\n // Poison chunk — skip it and keep going; its rows stay unmarked so flush\n // keeps them queued for a later retry instead of failing the whole batch.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry local mirror chunk failed (skipped ${batch.length}): ${msg}\\n`);\n }\n }\n return inserted;\n}\n\n/**\n * NON-destructive mirror of the queue into local pglite, for the dashboard.\n * No-op (db_unavailable) when there's no local container — that's expected in\n * cloud mode, where the dashboard reads central instead. Never removes queue\n * lines (flush owns that). Safe to call repeatedly (idempotent insert).\n */\nexport async function drainToPglite(): Promise<DrainResult> {\n const { rows } = readQueue();\n if (rows.length === 0) return { ok: true, ingested: 0, pending: 0, reason: 'no_pending' };\n const sql = await connectDb();\n if (!sql) return { ok: false, ingested: 0, pending: rows.length, reason: 'db_unavailable' };\n try {\n const ingested = await insertEvents(sql, rows);\n patchMetaCache({ last_drained_at: new Date().toISOString() });\n return { ok: true, ingested, pending: rows.length };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { ok: false, ingested: 0, pending: rows.length, reason: 'error', error: msg };\n }\n}\n","/**\n * Telemetry opt-out toggles.\n *\n * Two places stay in sync:\n * 1. metaCache.json — sync source for emit() hot path; the authoritative\n * copy for the opt-out flags.\n * 2. config.env — shell-sourced by hooks (informational; hooks still emit\n * via JSONL even if the env flag is set, so the gate is metaCache).\n *\n * Defaults: both flags 'yes'. Each toggle emits a `config_change` event\n * BEFORE applying so disabling telemetry still records the disable event.\n */\n\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { emit } from './emit.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nconst KEY_ENABLED = 'SYNKRO_TELEMETRY_ENABLED';\nconst KEY_REMOTE = 'SYNKRO_TELEMETRY_REMOTE_FLUSH';\n\nexport interface TelemetryState {\n enabled: boolean;\n remoteFlushEnabled: boolean;\n}\n\nfunction sanitize(raw: string, maxLen = 256): string {\n return raw.replace(/[^\\x20-\\x7E]/g, '').slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nfunction writeConfigEnvFlag(key: string, value: string): void {\n const safe = sanitize(value, 8);\n const line = `${key}=${shellQuoteSingle(safe)}`;\n let content = '';\n if (existsSync(CONFIG_PATH)) {\n try { content = readFileSync(CONFIG_PATH, 'utf-8'); } catch { content = ''; }\n }\n const re = new RegExp(`^${key}=.*$`, 'm');\n if (re.test(content)) {\n content = content.replace(re, line);\n } else {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n';\n content += `${line}\\n`;\n }\n try {\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n try { chmodSync(CONFIG_PATH, 0o600); } catch {}\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry config write failed: ${msg}\\n`);\n }\n}\n\n/**\n * Current state — reads metaCache (sync, always available), the authoritative\n * copy for the opt-out flags.\n */\nexport async function getTelemetryState(): Promise<TelemetryState> {\n const meta = readMetaCache();\n return {\n enabled: meta.enabled,\n remoteFlushEnabled: meta.remote_flush_enabled,\n };\n}\n\n/** Sync variant for the emit() path — never awaits pglite. */\nexport function getTelemetryStateSync(): TelemetryState {\n const meta = readMetaCache();\n return {\n enabled: meta.enabled,\n remoteFlushEnabled: meta.remote_flush_enabled,\n };\n}\n\nexport async function setTelemetryState(opts: {\n enabled?: boolean;\n remoteFlushEnabled?: boolean;\n}): Promise<void> {\n const current = await getTelemetryState();\n\n const patch: { enabled?: boolean; remote_flush_enabled?: boolean } = {};\n\n if (opts.enabled !== undefined && opts.enabled !== current.enabled) {\n emit('config_change', {\n setting: 'telemetry_enabled',\n from: current.enabled ? 'yes' : 'no',\n to: opts.enabled ? 'yes' : 'no',\n source: 'cli',\n });\n patch.enabled = opts.enabled;\n writeConfigEnvFlag(KEY_ENABLED, opts.enabled ? 'yes' : 'no');\n }\n\n if (opts.remoteFlushEnabled !== undefined\n && opts.remoteFlushEnabled !== current.remoteFlushEnabled) {\n emit('config_change', {\n setting: 'remote_flush_enabled',\n from: current.remoteFlushEnabled ? 'yes' : 'no',\n to: opts.remoteFlushEnabled ? 'yes' : 'no',\n source: 'cli',\n });\n patch.remote_flush_enabled = opts.remoteFlushEnabled;\n writeConfigEnvFlag(KEY_REMOTE, opts.remoteFlushEnabled ? 'yes' : 'no');\n }\n\n if (Object.keys(patch).length > 0) patchMetaCache(patch);\n}\n","/**\n * The telemetry WIRE CONTRACT — the single source of truth for the event shape,\n * the POST /flush request/response, the batching limits, and the retry policy,\n * shared by every hop of the pipeline:\n *\n * emit() (CLI hooks) → flush() (CLI host) → cloud flush (container) → ingest (Worker)\n *\n * Zero runtime deps and no imports, so the SAME file can be consumed by:\n * - the CLI (bundled by tsup into the binary),\n * - the synkro-server container (bun; Dockerfile-copied + relative-imported),\n * - the Cloudflare Worker (workerd; bundled by wrangler/esbuild).\n *\n * Anything about \"what a telemetry event looks like on the wire\" or \"how a flush\n * is batched/retried\" lives HERE and nowhere else — previously these were\n * re-declared in flush.ts, telemetryCloudFlush.ts, and telemetryWorker.ts, and\n * drifted (the container's batching dropped the byte cap + unrecoverable-drop).\n */\n\n/** Ingest route, relative to the gateway/worker base URL. */\nexport const TELEMETRY_FLUSH_PATH = '/api/v1/telemetry/flush';\n\n// ── client-side batching (producer → POST) ──────────────────────────────────\n/** Max rows per POST batch. */\nexport const BATCH_LIMIT = 200;\n/** Max serialized bytes per POST batch (kept under the server body cap). */\nexport const BATCH_BYTES = 1024 * 1024;\n/** Per-request timeout for a flush POST. */\nexport const REQUEST_TIMEOUT_MS = 15_000;\n\n// ── server-side ingest limits (must stay ≥ the client's batch sizing) ────────\n/** Max events accepted in one /flush request. */\nexport const MAX_EVENTS = 500;\n/** Body byte cap the ingest rejects above (413). */\nexport const TELEMETRY_BODY_MAX = 4 * 1024 * 1024;\n/** Rows per D1 sub-batch (one implicit transaction). */\nexport const D1_BATCH = 50;\n\n// ── retry policy ─────────────────────────────────────────────────────────────\n/**\n * HTTP statuses where retrying can NEVER succeed — the payload is poison, so the\n * producer must DROP the batch (advance past it) rather than wedge the queue on\n * it forever. 400 malformed, 409 content-conflict (an event id already exists\n * with DIFFERENT content — retrying sends the same conflicting bytes), 413\n * too-large, 422 unprocessable.\n */\nexport const UNRECOVERABLE_STATUSES: readonly number[] = [400, 409, 413, 422];\nexport function isUnrecoverable(status: number): boolean {\n return UNRECOVERABLE_STATUSES.includes(status);\n}\n\n// ── the event, on the wire ───────────────────────────────────────────────────\n/**\n * Canonical, order-stable field list. Producers map their rows to these keys;\n * the ingest reads them; the container INSERTs exactly this set. Keeping one\n * list means adding a column is a one-line change here, not four.\n */\nexport const TELEMETRY_WIRE_FIELDS = [\n 'client_event_id', 'install_id', 'user_id', 'org_id', 'email', 'event_type',\n 'occurred_at', 'cli_version', 'emitter', 'agent_kind', 'cc_session_id',\n 'cc_tool_use_id', 'cc_turn_id', 'cc_model', 'permission_mode', 'cwd', 'repo',\n 'git_branch', 'git_sha', 'policy_name', 'capture_depth', 'platform',\n 'hostname_hash', 'node_version', 'context',\n] as const;\nexport type TelemetryWireField = typeof TELEMETRY_WIRE_FIELDS[number];\n\n/**\n * One telemetry event on the wire. Everything is nullable except the idempotency\n * key (`install_id` + `client_event_id`), `event_type`, and `occurred_at`.\n * NOTE: `org_id` here is ADVISORY (client-reported) — the ingest ALWAYS stamps\n * the stored org from the authenticated principal, never from this field.\n */\nexport interface TelemetryEventWire {\n client_event_id: string;\n install_id: string;\n event_type: string;\n occurred_at: string; // ISO-8601; ingest normalizes to UTC\n user_id?: string | null;\n org_id?: string | null; // advisory only — see note above\n email?: string | null;\n cli_version?: string | null;\n emitter?: string | null;\n agent_kind?: string | null;\n cc_session_id?: string | null;\n cc_tool_use_id?: string | null;\n cc_turn_id?: string | null;\n cc_model?: string | null;\n permission_mode?: string | null;\n cwd?: string | null;\n repo?: string | null;\n git_branch?: string | null;\n git_sha?: string | null;\n policy_name?: string | null;\n capture_depth?: string | null;\n platform?: string | null;\n hostname_hash?: string | null;\n node_version?: string | null;\n context?: unknown; // object; the ingest JSON.stringifies it once\n}\n\n/** POST /api/v1/telemetry/flush request body. */\nexport interface FlushRequest {\n install_id?: string;\n cli_version?: string;\n platform?: string;\n events: TelemetryEventWire[];\n}\n\n/** POST /api/v1/telemetry/flush response body. */\nexport interface FlushResponse {\n accepted: number; // events in the request that passed validation\n inserted: number; // newly stored (not previously seen)\n duplicates?: number; // id already present with IDENTICAL content — skipped, idempotent\n conflicts?: string[]; // client_event_ids present with DIFFERENT content (409); NOT stored\n error?: string; // set on a non-2xx (e.g. 'content_conflict')\n}\n\n/**\n * Split rows into POST batches bounded by BOTH row count and serialized bytes.\n * Shared so the host flush and the container flush can't drift again. `sizeOf`\n * returns the serialized byte length of one row's wire form (e.g.\n * `JSON.stringify(rowToWire(r)).length`).\n */\nexport function batchBySizeAndCount<T>(\n rows: readonly T[],\n sizeOf: (row: T) => number,\n opts?: { limit?: number; maxBytes?: number },\n): T[][] {\n const limit = opts?.limit ?? BATCH_LIMIT;\n const maxBytes = opts?.maxBytes ?? BATCH_BYTES;\n const batches: T[][] = [];\n let cur: T[] = [];\n let bytes = 0;\n for (const r of rows) {\n const sz = sizeOf(r);\n if (cur.length > 0 && (cur.length >= limit || bytes + sz > maxBytes)) {\n batches.push(cur);\n cur = [];\n bytes = 0;\n }\n cur.push(r);\n bytes += sz;\n }\n if (cur.length) batches.push(cur);\n return batches;\n}\n","/**\n * Telemetry flush — the queue processor.\n *\n * ROTATE-THEN-PROCESS: under the queue lock, flush atomically rotates the live\n * JSONL queue aside (drain.rotateQueueForProcessing) and works ONLY from the\n * rotated copy, so concurrent lock-free emit()s keep appending to a fresh queue\n * and nothing emitted mid-cycle is lost. Undelivered survivors are appended back\n * onto the live queue at the end (drain.finalizeProcessing). A crash between\n * rotate and finalize is recovered on the next run (drain.recoverProcessing).\n *\n * Destinations for each event:\n * - REMOTE: POST /api/v1/telemetry/flush (central Timescale). The\n * AUTHORITATIVE store. Needs no local container, so this is what gives\n * cloud mode parity with local mode.\n * - LOCAL pglite: best-effort idempotent mirror for the local dashboard. It is\n * a CACHE, not a gate — it NEVER blocks removal of a remote-confirmed row.\n *\n * Survivor rule (a row stays only while it still owes a store it hasn't reached):\n * remote on → survives until the remote 2xx-confirms it (pglite ignored).\n * remote off → survives until pglite (then the only store) has mirrored it.\n * no store → survives (nothing reachable; retry next cycle).\n *\n * Mode matrix:\n * cloud (remote on, no pglite) → POST → remove. dashboard = central\n * local (remote on, pglite up) → POST + mirror → remove. dashboard = local or central\n * local-only (remote off, pglite)→ mirror → remove. dashboard = local\n * offline (remote off, no pglite)→ keep, retry later.\n *\n * Throttle (60s) gates only the REMOTE POST and keys off the last SUCCESSFUL\n * delivery (last_flush_ok_at), so a failed cycle never starves the next retry;\n * the local mirror + removal always run so local-only mode drains promptly.\n * Never throws.\n */\n\nimport { spawn } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { connectDb } from './db.js';\nimport {\n acquireQueueLock, releaseQueueLock, insertEvents,\n recoverProcessing, rotateQueueForProcessing, finalizeProcessing,\n type PendingRow,\n} from './drain.js';\nimport { getIdentity } from './identity.js';\nimport { getTelemetryState } from './optout.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n// The shared wire contract — event shape, batch limits, ingest path, and the\n// unrecoverable-status set — lives in one file so the host flush, the container\n// flush, and the Worker ingest can never drift. See wire.ts.\nimport {\n TELEMETRY_FLUSH_PATH, REQUEST_TIMEOUT_MS, batchBySizeAndCount, isUnrecoverable,\n type TelemetryEventWire, type FlushRequest,\n} from './wire.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\nconst CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(SYNKRO_DIR, 'credentials.json');\n\nconst THROTTLE_MS = 60_000;\n\nexport interface FlushResult {\n sent: number; // delivered to remote (2xx)\n failed: number; // left in the queue (remote/local delivery incomplete)\n mirrored: number; // copied into local pglite for the dashboard\n skipped_reason?: string;\n}\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n let raw: string;\n try { raw = readFileSync(CONFIG_PATH, 'utf-8'); } catch { return {}; }\n for (const line of raw.split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq <= 0) continue;\n const k = t.slice(0, eq).trim();\n let v = t.slice(eq + 1).trim();\n if ((v.startsWith(\"'\") && v.endsWith(\"'\")) || (v.startsWith('\"') && v.endsWith('\"'))) v = v.slice(1, -1);\n out[k] = v;\n }\n return out;\n}\n\nconst DEFAULT_GATEWAY = 'https://api.synkro.sh';\n\n// SSRF guard (CWE-918): config.env / env can be poisoned (stray exports, a\n// hostile op:// ref, etc.), and this POST carries the Bearer JWT — so the host\n// MUST be allowlisted before we send it anywhere. Mirrors assertGatewayAllowed\n// in commands/install.ts: only synkro.sh / *.synkro.sh, plus localhost for dev\n// (and http is permitted ONLY for localhost).\nfunction isLocalhost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1';\n}\n\nfunction isGatewayHostAllowed(host: string): boolean {\n const h = host.toLowerCase();\n return isLocalhost(h) || h === 'synkro.sh' || h.endsWith('.synkro.sh');\n}\n\nfunction resolveGatewayUrl(): string {\n const cfg = readConfigEnv();\n const raw = (cfg.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || '').trim();\n if (!raw) return DEFAULT_GATEWAY;\n let u: URL;\n try { u = new URL(raw); } catch { return DEFAULT_GATEWAY; }\n if (!isGatewayHostAllowed(u.hostname)) return DEFAULT_GATEWAY;\n if (u.protocol === 'https:') return raw.replace(/\\/$/, '');\n if (u.protocol === 'http:' && isLocalhost(u.hostname)) return raw.replace(/\\/$/, '');\n return DEFAULT_GATEWAY; // http to a non-localhost host → reject\n}\n\nfunction loadJwt(): string | null {\n if ((process.env.SYNKRO_STORAGE_MODE || '') === 'cloud') {\n try {\n const mcp = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcp) return mcp;\n } catch {}\n }\n try {\n if (!existsSync(CREDS_PATH)) return null;\n const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8')) as { access_token?: string };\n return creds.access_token || null;\n } catch { return null; }\n}\n\nfunction rowToWire(r: PendingRow): TelemetryEventWire {\n return {\n client_event_id: r.client_event_id,\n event_type: r.event_type,\n occurred_at: r.occurred_at,\n install_id: r.install_id,\n user_id: r.user_id,\n org_id: r.org_id,\n email: r.email,\n cli_version: r.cli_version,\n emitter: r.emitter,\n agent_kind: r.agent_kind,\n cc_session_id: r.cc_session_id,\n cc_tool_use_id: r.cc_tool_use_id,\n cc_turn_id: r.cc_turn_id,\n cc_model: r.cc_model,\n permission_mode: r.permission_mode,\n cwd: r.cwd,\n repo: r.repo,\n git_branch: r.git_branch,\n git_sha: r.git_sha,\n policy_name: r.policy_name,\n capture_depth: r.capture_depth,\n platform: r.platform,\n hostname_hash: r.hostname_hash,\n node_version: r.node_version,\n context: r.context ?? {},\n };\n}\n\n// Split rows into POST batches bounded by count and serialized bytes, using the\n// shared batcher so the host and container can't drift on their sizing rules.\nfunction batchRows(rows: PendingRow[]): PendingRow[][] {\n return batchBySizeAndCount(rows, (r) => JSON.stringify(rowToWire(r)).length);\n}\n\nexport async function flush(opts?: { force?: boolean }): Promise<FlushResult> {\n const state = await getTelemetryState();\n if (!state.enabled) return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'disabled' };\n\n const lockFd = acquireQueueLock();\n if (lockFd === null) return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'locked' };\n\n try {\n // Crash recovery first: fold any orphaned .processing file (a prior run that\n // rotated then died) back into the live queue so it isn't clobbered below.\n recoverProcessing();\n\n // Rotate-then-process: atomically move the live queue aside and work ONLY\n // from the rotated copy. Concurrent lock-free emits keep appending to a\n // fresh live queue, so nothing emitted during this multi-second cycle is\n // destroyed (fixes the old read-snapshot → rewrite-whole-file data loss).\n const { rows } = rotateQueueForProcessing();\n if (rows.length === 0) {\n finalizeProcessing([]); // drop the (empty) rotated file if one was created\n return { sent: 0, failed: 0, mirrored: 0, skipped_reason: 'no_pending' };\n }\n\n const jwt = loadJwt();\n const remoteEnabled = state.remoteFlushEnabled && !!jwt;\n\n // Throttle gates only the remote POST. It keys off last_flush_OK_at (the\n // last SUCCESSFUL delivery), not last_flush_at (last attempt), so a failed\n // cycle never advances the throttle and can't starve the next automatic\n // retry. force bypasses it.\n const meta = readMetaCache();\n let throttled = false;\n if (!opts?.force && meta.last_flush_ok_at) {\n const ms = Date.parse(meta.last_flush_ok_at);\n if (Number.isFinite(ms) && Date.now() - ms < THROTTLE_MS) throttled = true;\n }\n const doRemote = remoteEnabled && !throttled;\n\n // ── Local pglite mirror (best-effort cache; NEVER gates queue removal) ──\n // insertEvents is resilient per chunk and records which rows actually\n // landed in pgliteMirrored, so a poison chunk can't wedge removal of the\n // rows that DID land / that the remote already accepted.\n const sql = await connectDb();\n const pgliteMirrored = new Set<string>();\n let mirrored = 0;\n if (sql) {\n try {\n mirrored = await insertEvents(sql, rows, pgliteMirrored);\n } catch (err) {\n const m = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[synkro] telemetry local mirror failed: ${m}\\n`);\n }\n }\n\n // ── Remote delivery ───────────────────────────────────────────────────\n const remoteResolved = new Set<string>(); // delivered (2xx) OR dropped (unrecoverable 4xx)\n let sent = 0;\n let remoteErr = '';\n if (doRemote) {\n const endpoint = `${resolveGatewayUrl()}${TELEMETRY_FLUSH_PATH}`;\n const identity = getIdentity();\n for (const batch of batchRows(rows)) {\n const body = JSON.stringify({\n install_id: identity.install_id,\n cli_version: identity.cli_version,\n platform: identity.platform,\n events: batch.map(rowToWire),\n } satisfies FlushRequest);\n let resp: Response | null = null;\n try {\n resp = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwt}` },\n body,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (err) {\n remoteErr = err instanceof Error ? err.message : String(err);\n break; // network error — stop; keep this and remaining batches for retry\n }\n if (resp.ok) {\n for (const r of batch) remoteResolved.add(r.client_event_id);\n sent += batch.length;\n } else if (isUnrecoverable(resp.status)) {\n // Poison batch — will never succeed; drop it so it can't wedge the queue.\n for (const r of batch) remoteResolved.add(r.client_event_id);\n remoteErr = `HTTP ${resp.status} (dropped ${batch.length})`;\n process.stderr.write(`[synkro] telemetry flush dropped ${batch.length} unrecoverable event(s): ${remoteErr}\\n`);\n } else {\n remoteErr = `HTTP ${resp.status}`;\n break; // retryable (5xx/401/429/…) — keep this + remaining for next cycle\n }\n }\n }\n\n // ── Compute survivors (rows to append back onto the live queue) ───────\n // Remote is the authoritative store; local pglite is a best-effort cache\n // that must NEVER block removal of a remote-confirmed row. So a row survives\n // only while it still owes a store it hasn't reached:\n // remote on → until the remote 2xx-confirms it (pglite ignored).\n // remote off → until pglite (then the only store) has mirrored it.\n // no store → always (nothing reachable; retry next cycle).\n const survivors = rows.filter((r) =>\n remoteEnabled\n ? !remoteResolved.has(r.client_event_id)\n : !pgliteMirrored.has(r.client_event_id),\n );\n finalizeProcessing(survivors);\n\n // ── Bookkeeping (in metaCache so it works with no pglite) ─────────────\n if (doRemote) {\n const now = new Date().toISOString();\n const patch: Record<string, string> = { last_flush_at: now };\n if (sent > 0) patch.last_flush_ok_at = now;\n if (remoteErr) patch.last_flush_error = remoteErr;\n patchMetaCache(patch);\n }\n\n const failed = survivors.length;\n let skipped: string | undefined;\n if (!doRemote) skipped = remoteEnabled ? 'throttled' : (state.remoteFlushEnabled ? 'no_jwt' : 'remote_disabled');\n return { sent, failed, mirrored, skipped_reason: skipped };\n } finally {\n releaseQueueLock(lockFd);\n }\n}\n\n/**\n * Spawn a detached child that runs flush() and exits. Re-entrant guard via\n * SYNKRO_TELEMETRY_DETACHED so the post-dispatch flush in bootstrap doesn't\n * recurse through its own `telemetry flush` child.\n */\nexport function flushDetached(): void {\n if (process.env.SYNKRO_TELEMETRY_DETACHED === '1') return;\n try {\n const node = process.execPath;\n const script = process.argv[1];\n if (!script) return;\n const child = spawn(node, [script, 'telemetry', 'flush', '--detached'], {\n stdio: 'ignore',\n detached: true,\n env: { ...process.env, SYNKRO_TELEMETRY_EMITTER: 'flush-detached', SYNKRO_TELEMETRY_DETACHED: '1' },\n });\n child.unref();\n } catch {}\n}\n","/**\n * Read-side queries against pglite — all async because pglite is async.\n *\n * Every entry point drains the JSONL queue first so reads reflect any\n * events that hadn't been committed yet. When the container is down,\n * stats still returns local counts (queue depth from JSONL); tail and\n * exportEvents return whatever was already in pglite.\n */\n\nimport { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\n\nimport { connectDb } from './db.js';\nimport { drainToPglite } from './drain.js';\nimport { PENDING_PATH } from './emit.js';\nimport { getIdentity } from './identity.js';\nimport { getTelemetryState } from './optout.js';\nimport { readMetaCache } from './metaCache.js';\nimport type { EventRow } from './types.js';\n\nexport interface TelemetryStats {\n /** Events visible in the LOCAL pglite mirror (0 in cloud mode — query central). */\n total_events: number;\n /** Events queued in the JSONL not yet delivered to all destinations. */\n pending_local: number;\n by_type: Record<string, number>;\n newest?: string;\n pglite_reachable: boolean;\n install_id: string;\n enabled: boolean;\n remote_flush_enabled: boolean;\n last_drained_at?: string;\n last_flush_at?: string;\n last_flush_ok_at?: string;\n last_flush_error?: string;\n}\n\nfunction countPending(): number {\n if (!existsSync(PENDING_PATH)) return 0;\n try {\n const raw = readFileSync(PENDING_PATH, 'utf-8');\n if (!raw) return 0;\n let n = 0;\n for (const line of raw.split('\\n')) if (line.trim()) n++;\n return n;\n } catch { return 0; }\n}\n\nexport async function getStats(): Promise<TelemetryStats> {\n const identity = getIdentity();\n const state = await getTelemetryState();\n\n // Refresh the local pglite mirror so the counts below reflect the queue.\n // No-op in cloud mode (no local container) — pending_local still shows depth.\n await drainToPglite();\n\n // Read meta AFTER draining so last_drained_at reflects the drain we just ran.\n const meta = readMetaCache();\n\n const base: TelemetryStats = {\n total_events: 0,\n pending_local: countPending(),\n by_type: {},\n pglite_reachable: false,\n install_id: identity.install_id,\n enabled: state.enabled,\n remote_flush_enabled: state.remoteFlushEnabled,\n last_drained_at: meta.last_drained_at,\n // Flush state lives in metaCache, so it's available even with no pglite.\n last_flush_at: meta.last_flush_at,\n last_flush_ok_at: meta.last_flush_ok_at,\n last_flush_error: meta.last_flush_error,\n };\n\n const sql = await connectDb();\n if (!sql) return base;\n base.pglite_reachable = true;\n\n try {\n const total = await sql`SELECT COUNT(*)::int AS c FROM telemetry_events`;\n base.total_events = Number(total[0]?.c ?? 0);\n\n const types = await sql`\n SELECT event_type, COUNT(*)::int AS c\n FROM telemetry_events\n GROUP BY event_type\n ORDER BY c DESC\n `;\n for (const row of types) base.by_type[String(row.event_type)] = Number(row.c);\n\n const newest = await sql`\n SELECT occurred_at FROM telemetry_events ORDER BY occurred_at DESC LIMIT 1\n `;\n if (newest.length) base.newest = new Date(newest[0].occurred_at).toISOString();\n } catch {\n // Partial stats; container path partially broken. Keep going.\n }\n\n return base;\n}\n\nexport interface TailOptions {\n limit?: number;\n eventType?: string;\n sessionId?: string;\n}\n\nexport async function tail(opts: TailOptions = {}): Promise<EventRow[]> {\n await drainToPglite();\n const sql = await connectDb();\n if (!sql) return [];\n\n const limit = Math.max(1, Math.min(opts.limit ?? 20, 1000));\n\n try {\n if (opts.eventType && opts.sessionId) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE event_type = ${opts.eventType}\n AND cc_session_id = ${opts.sessionId}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n if (opts.eventType) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE event_type = ${opts.eventType}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n if (opts.sessionId) {\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n WHERE cc_session_id = ${opts.sessionId}\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n }\n return (await sql<EventRow[]>`\n SELECT * FROM telemetry_events\n ORDER BY occurred_at DESC LIMIT ${limit}\n `) as unknown as EventRow[];\n } catch { return []; }\n}\n\nexport async function exportEvents(path: string): Promise<void> {\n await drainToPglite();\n const sql = await connectDb();\n if (!sql) {\n writeFileSync(path, '', 'utf-8');\n return;\n }\n const lines: string[] = [];\n try {\n const rows = await sql`SELECT * FROM telemetry_events ORDER BY occurred_at ASC`;\n for (const row of rows) {\n // pglite returns jsonb columns as parsed values already.\n lines.push(JSON.stringify(row));\n }\n } catch {}\n writeFileSync(path, lines.join('\\n') + (lines.length > 0 ? '\\n' : ''), 'utf-8');\n}\n\n/** Best-effort disk usage hint for stats — pglite stores under the container's\n * /data volume which is opaque from the host; we just report queue size. */\nexport function pendingFileSize(): number {\n try { return existsSync(PENDING_PATH) ? statSync(PENDING_PATH).size : 0; }\n catch { return 0; }\n}\n","/**\n * Wipe local telemetry — both the pglite tables and the JSONL queue.\n *\n * Default: keeps install_id so casual cleanups don't rotate the user's\n * machine identifier. `keepInstallId: false` regenerates install_id on next\n * emit. `synkro uninstall --purge` calls with keepInstallId:false; the\n * `telemetry purge` subcommand defaults to true.\n */\n\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\n\nimport { connectDb } from './db.js';\nimport { PENDING_PATH } from './emit.js';\nimport { patchMetaCache, readMetaCache } from './metaCache.js';\n\nexport interface PurgeResult {\n removed_events: number;\n removed_meta: boolean;\n removed_pending: number;\n pglite_reachable: boolean;\n}\n\nfunction unlinkPendingCount(): number {\n if (!existsSync(PENDING_PATH)) return 0;\n let n = 0;\n try {\n const raw = readFileSync(PENDING_PATH, 'utf-8') as string;\n for (const line of raw.split('\\n')) if (line.trim()) n++;\n } catch {}\n try { unlinkSync(PENDING_PATH); } catch {}\n return n;\n}\n\nexport async function purge(opts: { keepInstallId?: boolean } = {}): Promise<PurgeResult> {\n const keep = opts.keepInstallId ?? true;\n const pending = unlinkPendingCount();\n const sql = await connectDb();\n if (!sql) {\n // Container down — we wiped the JSONL queue; mark the meta accordingly.\n if (!keep) {\n patchMetaCache({ install_id: randomUUID() });\n }\n return {\n removed_events: pending,\n removed_meta: !keep,\n removed_pending: pending,\n pglite_reachable: false,\n };\n }\n\n let removedEvents = 0;\n try {\n const [{ c }] = (await sql`SELECT COUNT(*)::int AS c FROM telemetry_events`) as unknown as Array<{ c: number }>;\n removedEvents = Number(c ?? 0);\n await sql`TRUNCATE TABLE telemetry_events RESTART IDENTITY`;\n } catch {}\n\n let removedMeta = false;\n try {\n if (keep) {\n // Preserve install_id + schema_version; drop everything else.\n const meta = readMetaCache();\n await sql`DELETE FROM telemetry_meta WHERE k NOT IN ('schema_version')`;\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('install_id', ${meta.install_id})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n removedMeta = true;\n } else {\n const newId = randomUUID();\n await sql`DELETE FROM telemetry_meta WHERE k NOT IN ('schema_version')`;\n await sql`\n INSERT INTO telemetry_meta (k, v) VALUES ('install_id', ${newId})\n ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v\n `;\n patchMetaCache({ install_id: newId });\n removedMeta = true;\n }\n } catch {}\n\n return {\n removed_events: removedEvents,\n removed_meta: removedMeta,\n removed_pending: pending,\n pglite_reachable: true,\n };\n}\n","/**\n * Telemetry client — no-op stub.\n *\n * All Supabase telemetry has been removed. Methods return dummy values\n * so existing call sites in loop.ts continue to work without changes.\n */\n\nimport { randomUUID } from 'crypto';\n\nexport type TelemetryEventType =\n | 'session_start'\n | 'extraction_complete'\n | 'scenarios_complete'\n | 'hitl_checkpoint_shown'\n | 'hitl_approved'\n | 'hitl_feedback'\n | 'hitl_refinement_complete'\n | 'pipeline_complete';\n\ninterface PreferencePairData {\n sessionId: string;\n eventId: string;\n section: string;\n rejectedState: unknown;\n feedback: string;\n}\n\nclass TelemetryClient {\n async track(\n _eventType: TelemetryEventType,\n _data: Record<string, unknown>\n ): Promise<string> {\n return randomUUID();\n }\n\n async createPreferencePair(_data: PreferencePairData): Promise<string> {\n return randomUUID();\n }\n\n async updatePreferencePair(_id: string, _acceptedState: unknown): Promise<void> {\n // no-op\n }\n}\n\n// Singleton instance\nexport const telemetry = new TelemetryClient();\n","/**\n * Legacy RLHF telemetry exporters — no-op stubs kept so cli/agent/loop.ts\n * keeps compiling. The original implementation read preference_pairs +\n * telemetry_events tables out of ~/.synkro/sessions.db (better-sqlite3); both\n * the tables and the SQLite dep are gone with the pglite migration.\n *\n * Anyone who actually needs these features should rebuild against the new\n * pglite-backed telemetry_events table.\n */\n\nimport { writeFileSync } from 'node:fs';\n\nexport async function exportPreferencePairs(outputPath: string): Promise<number> {\n writeFileSync(outputPath, '');\n return 0;\n}\n\nexport async function exportCompletions(outputPath: string): Promise<number> {\n writeFileSync(outputPath, '');\n return 0;\n}\n\nexport async function getTelemetryStats(): Promise<{\n totalEvents: number;\n totalPreferencePairs: number;\n completePreferencePairs: number;\n pipelineCompletions: number;\n}> {\n return {\n totalEvents: 0,\n totalPreferencePairs: 0,\n completePreferencePairs: 0,\n pipelineCompletions: 0,\n };\n}\n","/**\n * Public telemetry module exports.\n */\n\nexport { emit, PENDING_PATH } from './emit.js';\nexport { getIdentity, type IdentityContext } from './identity.js';\nexport { connectDb, closeDb, type Sql } from './db.js';\nexport {\n drainToPglite, readQueue, rewriteQueue, insertEvents,\n acquireQueueLock, releaseQueueLock,\n type DrainResult, type PendingRow,\n} from './drain.js';\nexport {\n getTelemetryState, getTelemetryStateSync, setTelemetryState,\n type TelemetryState,\n} from './optout.js';\nexport { flush, flushDetached, type FlushResult } from './flush.js';\nexport {\n getStats, tail, exportEvents, pendingFileSize,\n type TelemetryStats, type TailOptions,\n} from './stats.js';\nexport { purge, type PurgeResult } from './purge.js';\nexport { readMetaCache, writeMetaCache, patchMetaCache, type TelemetryMeta } from './metaCache.js';\nexport type { EventType, EventContext, EmitOptions, EventRow } from './types.js';\n\n// Legacy no-op stubs retained so older call sites (cli/agent/loop.ts) keep\n// compiling. New code should not import these.\nexport { telemetry, type TelemetryEventType } from './client.js';\nexport {\n exportPreferencePairs,\n exportCompletions,\n getTelemetryStats,\n} from './export.js';\n","/**\n * Detect which AI coding agents are installed on the user's machine.\n *\n * Returns a list of agents with their config paths so the installer\n * knows where to write hook configs.\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execFileSync, execSync } from 'node:child_process';\n\nexport type AgentKind = 'claude_code' | 'cursor';\n\nexport interface DetectedAgent {\n kind: AgentKind;\n name: string;\n binaryPath?: string;\n configDir: string;\n settingsPath: string;\n version?: string;\n}\n\nfunction which(cmd: string): string | undefined {\n try {\n const result = execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();\n return result || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction getVersion(cmd: string): string | undefined {\n try {\n const result = execFileSync(cmd, ['--version'], { encoding: 'utf-8', timeout: 5000 }).trim();\n const line = result.split('\\n')[0];\n return line.replace(/\\s*\\(Claude Code\\)/, '');\n } catch {\n return undefined;\n }\n}\n\nexport function detectAgents(): DetectedAgent[] {\n const agents: DetectedAgent[] = [];\n const home = homedir();\n\n // Claude Code — require the binary on PATH; ~/.claude/ alone is not enough\n // (Synkro and other tools create that directory).\n const claudeBinary = which('claude');\n const claudeConfigDir = join(home, '.claude');\n if (claudeBinary) {\n agents.push({\n kind: 'claude_code',\n name: 'Claude Code',\n binaryPath: claudeBinary,\n configDir: claudeConfigDir,\n settingsPath: join(claudeConfigDir, 'settings.json'),\n version: getVersion('claude'),\n });\n }\n\n // Cursor — GUI app, rarely has a `cursor` binary on PATH. Check the app\n // bundle (/Applications/Cursor.app on macOS) and ~/.cursor/ (created by\n // Cursor on first launch) as additional signals.\n const cursorBinary = which('cursor');\n const cursorConfigDir = join(home, '.cursor');\n const cursorApp = process.platform === 'darwin' && existsSync('/Applications/Cursor.app');\n if (cursorBinary || cursorApp || existsSync(cursorConfigDir)) {\n let version: string | undefined;\n if (cursorBinary) {\n version = getVersion('cursor');\n } else if (cursorApp) {\n try {\n const plist = execSync('defaults read /Applications/Cursor.app/Contents/Info CFBundleShortVersionString 2>/dev/null', { encoding: 'utf-8', timeout: 3000 }).trim();\n if (plist) version = plist;\n } catch {}\n }\n agents.push({\n kind: 'cursor',\n name: 'Cursor',\n binaryPath: cursorBinary,\n configDir: cursorConfigDir,\n settingsPath: join(cursorConfigDir, 'hooks.json'),\n version,\n });\n }\n\n return agents;\n}\n\nexport function findClaudeAuth(): { path: string; exists: boolean } {\n // CC stores OAuth token in macOS keychain (item: \"Claude Code-credentials\")\n // or in ~/.claude/auth.json on Linux. We don't extract it; we ask user to\n // run `claude setup-token` for headless use.\n const authJsonPath = join(homedir(), '.claude', 'auth.json');\n return { path: authJsonPath, exists: existsSync(authJsonPath) };\n}\n","// :)\n/**\n * Cross-platform helpers for hook installation.\n *\n * The hook scripts (~/.synkro/hooks/cc-*.ts) start with `#!/usr/bin/env bun`, and on\n * macOS/Linux Claude Code runs them by their bare path — the OS honors the shebang. Windows\n * has NO shebang mechanism, so a bare `.ts` path never reaches bun (cmd hands it to a file\n * association or errors). The fix: on Windows, register the hook command as `<bun.exe> <script>`\n * — invoke bun explicitly. macOS/Linux keep the bare-path (shebang) form, unchanged.\n *\n * Cloud grading needs NO local container — the stub hooks POST straight to the gateway — so\n * getting these thin stubs to LAUNCH is the only thing standing between cloud mode and native\n * Windows. (Local grading still needs Docker; a Windows user on local mode should use WSL2.)\n */\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\n/**\n * Absolute path to the `bun` executable. Two callers need it resolved to an absolute path:\n * GUI apps (Claude Desktop) don't inherit the login-shell PATH, and Windows hook commands\n * must name bun explicitly (no shebang). Probe PATH first, then well-known install locations,\n * then fall back to a bare name (works if PATH is set at runtime).\n */\nexport function resolveBunBin(): string {\n const finder = IS_WINDOWS ? 'where' : 'which';\n const r = spawnSync(finder, ['bun'], { encoding: 'utf-8', timeout: 5_000 });\n // `where` can return several lines (bun, bun.exe, bun.cmd); take the first non-empty.\n const resolved = (r.stdout || '').split(/\\r?\\n/).map(s => s.trim()).find(Boolean);\n if (resolved) return resolved;\n const home = process.env.USERPROFILE || homedir();\n const candidates = IS_WINDOWS\n ? [join(home, '.bun', 'bin', 'bun.exe'), 'C:\\\\Program Files\\\\bun\\\\bin\\\\bun.exe']\n : ['/opt/homebrew/bin/bun', '/usr/local/bin/bun', join(home, '.bun', 'bin', 'bun')];\n for (const p of candidates) if (existsSync(p)) return p;\n return IS_WINDOWS ? 'bun.exe' : 'bun';\n}\n\n/** Quote a path for embedding in a hook `command` string. */\nfunction quoteArg(s: string): string {\n // Windows cmd: double-quote (paths have spaces + backslashes; backslash is literal in quotes).\n // POSIX sh: single-quote with the standard '\\'' escape.\n return IS_WINDOWS ? `\"${s}\"` : \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/**\n * The `command` string Claude Code should run for a hook SCRIPT PATH.\n * - macOS/Linux: the bare path — the script's `#!/usr/bin/env bun` shebang executes it (unchanged).\n * - Windows: `<bun.exe> <script>` — Windows ignores the shebang, so bun is invoked explicitly.\n * Idempotent on already-wrapped commands (only wraps a plain path).\n */\nexport function ccHookCommand(scriptPath: string): string {\n if (!IS_WINDOWS) return scriptPath;\n return `${quoteArg(resolveBunBin())} ${quoteArg(scriptPath)}`;\n}\n\n/**\n * True if a hook `command` string points at a Synkro-managed hook script. Used to recognize\n * (and clean up) our own entries on reinstall/uninstall. MUST be separator-agnostic: on Windows\n * the path is `...\\.synkro\\hooks\\...`, so a raw `.includes('/.synkro/hooks/')` never matches and\n * Synkro fails to recognize its own hooks → duplicates on reinstall, orphans on uninstall.\n */\nexport function isSynkroHookCommand(command: unknown): boolean {\n if (typeof command !== 'string') return false;\n return command.replace(/\\\\/g, '/').includes('/.synkro/hooks/');\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.claude/settings.json.\n *\n * Preserves any other hooks the user has configured (Corridor, Noma, custom\n * scripts, etc.) — we only add our entries, never replace the file.\n */\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { ccHookCommand, isSynkroHookCommand } from './platform.js';\n\nexport interface SynkroHookConfig {\n bashJudgeScriptPath: string;\n skillJudgeScriptPath?: string;\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n transcriptSyncScriptPath: string;\n userPromptSubmitScriptPath: string;\n installScanScriptPath: string;\n taskActivateIntentScriptPath?: string;\n mcpGateScriptPath?: string;\n skipTranscriptSync?: boolean;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\n// LEGACY: prior versions pointed the coding agent's ANTHROPIC_BASE_URL at an in-container\n// usage proxy. That was a hot-path SPOF and has been removed in favor of an out-of-band\n// tap (the container reads account-wide utilization itself). This URL is retained ONLY so\n// install/uninstall can recognize and remove a value we set before — never to set one.\nconst USAGE_PROXY_URL = `http://127.0.0.1:${process.env.SYNKRO_HOST_USAGE_PROXY_PORT || '18927'}`;\n\ninterface HookEntry {\n matcher?: string;\n hooks: Array<Record<string, unknown>>;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CCSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n PostToolUse?: HookEntry[];\n SessionEnd?: HookEntry[];\n SessionStart?: HookEntry[];\n Stop?: HookEntry[];\n [k: string]: unknown;\n };\n env?: Record<string, string>;\n [k: string]: unknown;\n}\n\nfunction readSettings(path: string): CCSettings {\n if (!existsSync(path)) return {};\n try {\n const raw = readFileSync(path, 'utf-8');\n return JSON.parse(raw) as CCSettings;\n } catch (err) {\n throw new Error(`Failed to parse ${path}: ${(err as Error).message}`);\n }\n}\n\nfunction writeSettingsAtomic(path: string, settings: CCSettings): void {\n mkdirSync(dirname(path), { recursive: true });\n const tmpPath = `${path}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, path);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];\n // Separator-agnostic: on Windows the command is `<bun.exe> ...\\.synkro\\hooks\\...` — a raw\n // '/.synkro/hooks/' check misses it, so old entries would never be cleaned up (dupes/orphans).\n return hooks.some((h: any) => isSynkroHookCommand(h?.command));\n}\n\n// Rewrite every Synkro-managed hook's `command` to the platform-correct launch form. The push\n// sites above store the bare SCRIPT PATH; on macOS/Linux that's the final command (shebang runs\n// it), on Windows this wraps it as `<bun.exe> <script>` (no shebang there). One pass over the\n// freshly-added entries keeps the 16 push sites simple and the platform branch in ONE place.\nfunction finalizeHookCommands(hooks: NonNullable<CCSettings['hooks']>): void {\n for (const evt of Object.keys(hooks)) {\n const arr = (hooks as any)[evt];\n if (!Array.isArray(arr)) continue;\n for (const entry of arr) {\n if (!entry?.[SYNKRO_MARKER] || !Array.isArray(entry.hooks)) continue;\n for (const h of entry.hooks) {\n if (typeof h?.command === 'string') h.command = ccHookCommand(h.command);\n }\n }\n }\n}\n\nfunction removeSynkroEntries(events: CCSettings['hooks'] extends infer H ? H : never, eventName: string): void {\n if (!events) return;\n const arr = (events as any)[eventName];\n if (!Array.isArray(arr)) return;\n (events as any)[eventName] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\n/**\n * Merge Synkro hooks into the settings file. Idempotent — replaces any\n * existing Synkro-managed entries with the new versions.\n */\nexport function installCCHooks(settingsPath: string, config: SynkroHookConfig): void {\n const settings = readSettings(settingsPath);\n settings.hooks = settings.hooks ?? {};\n\n // Remove any prior Synkro entries (to support `synkro update`)\n removeSynkroEntries(settings.hooks as any, 'PreToolUse');\n removeSynkroEntries(settings.hooks as any, 'PostToolUse');\n removeSynkroEntries(settings.hooks as any, 'SessionEnd');\n removeSynkroEntries(settings.hooks as any, 'SessionStart');\n removeSynkroEntries(settings.hooks as any, 'UserPromptSubmit');\n // Also clean up any older `Stop`-event entry from earlier v1.6 builds.\n removeSynkroEntries(settings.hooks as any, 'Stop');\n\n settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];\n settings.hooks.PostToolUse = settings.hooks.PostToolUse ?? [];\n settings.hooks.SessionEnd = settings.hooks.SessionEnd ?? [];\n settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];\n settings.hooks.UserPromptSubmit = (settings.hooks.UserPromptSubmit as any[]) ?? [];\n\n // PreToolUse Bash → install scan (caches result for bash judge to read)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.installScanScriptPath,\n timeout: 8,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Bash/Read/Grep/Glob → command hook script (Cerebras-judged)\n settings.hooks.PreToolUse.push({\n matcher: 'Bash|Read|Grep|Glob',\n hooks: [\n {\n type: 'command',\n command: config.bashJudgeScriptPath,\n timeout: 50,\n },\n // skill-judge shares this entry so CC runs it in parallel and waits for it\n // before processing denies — its verdict is its OWN system-message block,\n // separate from bashGuard. No-ops on pure reads (empty command).\n ...(config.skillJudgeScriptPath ? [{\n type: 'command',\n command: config.skillJudgeScriptPath,\n timeout: 50,\n }] : []),\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Edit/Write → two hooks in ONE entry so CC waits for all\n // before processing deny decisions. Each hook runs in parallel:\n // 1. edit-precheck: org rules grading on channel 1 (port 8929)\n // 2. cve-precheck: CVE/OSV dependency scan (curl, no LLM)\n settings.hooks.PreToolUse.push({\n matcher: 'Edit|Write|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: config.editPrecheckScriptPath,\n timeout: 50,\n },\n {\n type: 'command',\n command: config.cwePrecheckScriptPath,\n timeout: 50,\n },\n {\n type: 'command',\n command: config.cvePrecheckScriptPath,\n timeout: 10,\n },\n // skill-judge — its OWN system-message block, parallel with edit-precheck.\n ...(config.skillJudgeScriptPath ? [{\n type: 'command',\n command: config.skillJudgeScriptPath,\n timeout: 50,\n }] : []),\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse Agent → scan subagent prompts against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'Agent',\n hooks: [\n {\n type: 'command',\n command: config.agentJudgeScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse ExitPlanMode → advisory plan review against org rules.\n settings.hooks.PreToolUse.push({\n matcher: 'ExitPlanMode',\n hooks: [\n {\n type: 'command',\n command: config.planJudgeScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // PreToolUse on any mcp__* tool → MCP server governance (allow/blocklist +\n // grade allowed calls). Blocking: a blocklisted/un-allowlisted server is denied.\n // Synkro's own guardrails server is skipped inside the handler, never blocked.\n if (config.mcpGateScriptPath) {\n settings.hooks.PreToolUse.push({\n matcher: 'mcp__.*',\n hooks: [\n {\n type: 'command',\n command: config.mcpGateScriptPath,\n timeout: 50,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n }\n\n // PreToolUse on the activate_standard MCP tool → stamp the activating session\n // BEFORE the tool runs, so the task binds to THIS exact session (reliable —\n // not the most-recent-session heuristic). Matcher is a literal MCP tool name.\n if (config.taskActivateIntentScriptPath) {\n settings.hooks.PreToolUse.push({\n matcher: 'mcp__synkro-guardrails__activate_standard',\n hooks: [\n {\n type: 'command',\n command: config.taskActivateIntentScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n }\n\n // PostToolUse Edit/Write removed — PreToolUse hooks handle grading now.\n\n // PostToolUse Bash → flips pending precheck_corrections row to 'allow'\n // once the bash command actually executed. Required for the bash trendline\n // (approved-vs-rejected) the dashboard reads from precheck_corrections.\n settings.hooks.PostToolUse.push({\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: config.bashFollowupScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionEnd → end-of-session summary line (`[synkro] stop → N findings: ...`).\n // We use SessionEnd, not Stop, because Stop fires after every agent turn\n // (which would spam the user in interactive mode); SessionEnd fires once\n // when the session itself terminates.\n settings.hooks.SessionEnd.push({\n hooks: [\n {\n type: 'command',\n command: config.stopSummaryScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // SessionStart → \"[synkro] session start → N open findings in this repo\" if any.\n settings.hooks.SessionStart.push({\n hooks: [\n {\n type: 'command',\n command: config.sessionStartScriptPath,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // UserPromptSubmit → captures explicit consent keywords from user messages.\n // Writes a file-based grant so the next blocked tool call is allowed through.\n (settings.hooks.UserPromptSubmit as any[]).push({\n hooks: [\n {\n type: 'command',\n command: config.userPromptSubmitScriptPath,\n timeout: 5,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Stop → usage telemetry + optional transcript sync (fires after every agent turn).\n // Always installed: usage tracking is ungated; transcript sync is gated inside the script.\n settings.hooks.Stop = settings.hooks.Stop ?? [];\n removeSynkroEntries(settings.hooks as any, 'Stop');\n settings.hooks.Stop.push({\n hooks: [\n {\n type: 'command',\n command: config.transcriptSyncScriptPath,\n timeout: 3,\n },\n ],\n [SYNKRO_MARKER]: true,\n } as any);\n\n // Usage telemetry is now an OUT-OF-BAND tap inside the container (it makes its own\n // account-wide utilization read), so we no longer route the coding agent through a\n // proxy — that was a hot-path single point of failure. Proactively remove any\n // ANTHROPIC_BASE_URL a prior version set, but ONLY if it's still ours; never touch a\n // custom base URL the user set themselves.\n if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {\n delete settings.env.ANTHROPIC_BASE_URL;\n if (Object.keys(settings.env).length === 0) delete settings.env;\n }\n\n // Windows: wrap each hook's bare script path as `<bun.exe> <script>` (no shebang on Windows).\n // No-op on macOS/Linux (bare path + shebang, unchanged). Runs after all entries are staged.\n finalizeHookCommands(settings.hooks as NonNullable<CCSettings['hooks']>);\n\n writeSettingsAtomic(settingsPath, settings);\n}\n\n/**\n * Remove all Synkro-managed hook entries from settings.json.\n * Used by `synkro disconnect`.\n */\nexport function uninstallCCHooks(settingsPath: string): boolean {\n if (!existsSync(settingsPath)) return false;\n const settings = readSettings(settingsPath);\n if (!settings.hooks) return false;\n\n const events = ['PreToolUse', 'PostToolUse', 'SessionEnd', 'SessionStart', 'Stop', 'UserPromptSubmit'] as const;\n for (const evt of events) {\n removeSynkroEntries(settings.hooks as any, evt);\n }\n\n // If a hook event array is now empty, delete it\n for (const evt of events) {\n if (Array.isArray((settings.hooks as any)[evt]) && (settings.hooks as any)[evt].length === 0) {\n delete (settings.hooks as any)[evt];\n }\n }\n // If hooks object is now empty, delete it\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // Drop our usage-proxy base URL — but ONLY if it's still ours (never touch a value\n // the user set to their own gateway).\n if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {\n delete settings.env.ANTHROPIC_BASE_URL;\n if (Object.keys(settings.env).length === 0) delete settings.env;\n }\n\n writeSettingsAtomic(settingsPath, settings);\n return true;\n}\n\n/**\n * Check whether Synkro hooks are currently installed in settings.json.\n * Used by `synkro status`.\n */\nexport function inspectCCHooks(settingsPath: string): {\n installed: boolean;\n preToolUseBash: boolean;\n postToolUseEdit: boolean;\n sessionEnd: boolean;\n sessionStart: boolean;\n} {\n if (!existsSync(settingsPath)) {\n return { installed: false, preToolUseBash: false, postToolUseEdit: false, sessionEnd: false, sessionStart: false };\n }\n const settings = readSettings(settingsPath);\n const pre = (settings.hooks as any)?.PreToolUse ?? [];\n const post = (settings.hooks as any)?.PostToolUse ?? [];\n const sessionEndHooks = (settings.hooks as any)?.SessionEnd ?? [];\n const sessionStartHooks = (settings.hooks as any)?.SessionStart ?? [];\n const preToolUseBash = pre.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const postToolUseEdit = post.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionEnd = sessionEndHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n const sessionStart = sessionStartHooks.some((e: any) => e?.[SYNKRO_MARKER] === true);\n return {\n installed: preToolUseBash || postToolUseEdit || sessionEnd || sessionStart,\n preToolUseBash,\n postToolUseEdit,\n sessionEnd,\n sessionStart,\n };\n}\n","// :)\n/**\n * Atomically merge Synkro hook entries into ~/.cursor/hooks.json.\n *\n * Cursor hooks reuse the same TypeScript scripts as Claude Code (cc-*.ts),\n * run with SYNKRO_HOOK_FORMAT=cursor so _synkro-common translates CC JSON output\n * to Cursor's permission JSON for preToolUse. CC install is unchanged.\n */\nimport { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve, normalize } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isSynkroHookCommand } from './platform.js';\n\nexport interface CursorHookConfig {\n /** Cursor-specific bash/shell judge (preToolUse with Shell|Bash matcher) */\n bashJudgeScriptPath: string;\n skillJudgeScriptPath?: string;\n /** Cursor-specific post-edit capture (afterFileEdit event shape) */\n editCaptureScriptPath: string;\n /** Capture afterAgentThought/afterAgentResponse before transcript redaction */\n agentCaptureScriptPath: string;\n /** Shared cc-*.ts scripts (invoked with SYNKRO_HOOK_FORMAT=cursor) */\n bashFollowupScriptPath: string;\n editPrecheckScriptPath: string;\n cwePrecheckScriptPath: string;\n cvePrecheckScriptPath: string;\n planJudgeScriptPath: string;\n agentJudgeScriptPath: string;\n stopSummaryScriptPath: string;\n sessionStartScriptPath: string;\n userPromptSubmitScriptPath: string;\n transcriptSyncScriptPath: string;\n installScanScriptPath: string;\n /** MCP server governance (beforeMCPExecution): allow/blocklist + grade calls */\n mcpGateScriptPath: string;\n}\n\nconst SYNKRO_MARKER = '__synkro_managed__';\n\nfunction shellQuote(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Run a shared cc-*.ts hook with Cursor output translation. */\nfunction cursorCcCmd(scriptPath: string): string {\n return 'env SYNKRO_HOOK_FORMAT=cursor bun run ' + shellQuote(scriptPath);\n}\n\nfunction bunRunCmd(scriptPath: string): string {\n return 'bun run ' + shellQuote(scriptPath);\n}\n\nconst ALLOWED_PARENT_DIRS = [\n resolve(homedir(), '.cursor'),\n resolve(homedir(), '.config', 'cursor'),\n];\n\nfunction validateHooksPath(path: string): string {\n const resolved = resolve(normalize(path));\n if (!ALLOWED_PARENT_DIRS.some(dir => resolved.startsWith(dir + '/') || resolved === dir)) {\n throw new Error(`Hooks path must be under ~/.cursor or ~/.config/cursor, got: ${resolved}`);\n }\n return resolved;\n}\n\ninterface CursorHookEntry {\n command: string;\n timeout?: number;\n failClosed?: boolean;\n matcher?: string;\n [SYNKRO_MARKER]?: boolean;\n}\n\ninterface CursorHooksFile {\n version?: number;\n hooks?: {\n [event: string]: CursorHookEntry[];\n };\n}\n\nfunction readHooksFile(rawPath: string): CursorHooksFile {\n const safePath = validateHooksPath(rawPath);\n try {\n const raw = readFileSync(safePath, 'utf-8');\n return JSON.parse(raw) as CursorHooksFile;\n } catch (err: any) {\n if (err?.code === 'ENOENT') return { version: 1, hooks: {} };\n throw new Error(`Failed to parse ${safePath}: ${(err as Error).message}`);\n }\n}\n\nfunction writeHooksFileAtomic(rawPath: string, data: CursorHooksFile): void {\n const safePath = validateHooksPath(rawPath);\n mkdirSync(dirname(safePath), { recursive: true });\n const tmpPath = `${safePath}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tmpPath, safePath);\n}\n\nfunction isSynkroEntry(entry: any): boolean {\n if (entry?.[SYNKRO_MARKER]) return true;\n // Separator-agnostic so Windows (`...\\.synkro\\hooks\\...`) entries are still recognized.\n return isSynkroHookCommand(entry?.command);\n}\n\nconst ALL_EVENTS = [\n 'sessionStart', 'sessionEnd', 'beforeSubmitPrompt', 'stop',\n 'beforeShellExecution', 'afterShellExecution',\n 'preToolUse', 'afterFileEdit', 'postToolUse',\n 'beforeMCPExecution',\n 'afterAgentThought', 'afterAgentResponse',\n];\n\nfunction removeSynkroEntries(hooks: CursorHooksFile['hooks'], event: string): void {\n if (!hooks) return;\n const arr = hooks[event];\n if (!Array.isArray(arr)) return;\n hooks[event] = arr.filter((entry: any) => !isSynkroEntry(entry));\n}\n\nfunction pushCcHook(\n hooks: CursorHooksFile['hooks'],\n event: string,\n scriptPath: string,\n opts: { timeout: number; matcher?: string; failClosed?: boolean },\n): void {\n hooks![event] = hooks![event] ?? [];\n hooks![event]!.push({\n command: cursorCcCmd(scriptPath),\n timeout: opts.timeout,\n failClosed: opts.failClosed ?? false,\n ...(opts.matcher ? { matcher: opts.matcher } : {}),\n [SYNKRO_MARKER]: true,\n });\n}\n\nexport function installCursorHooks(hooksJsonPath: string, config: CursorHookConfig): void {\n const file = readHooksFile(hooksJsonPath);\n file.version = file.version ?? 1;\n file.hooks = file.hooks ?? {};\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n const h = file.hooks;\n\n pushCcHook(h, 'sessionStart', config.sessionStartScriptPath, { timeout: 5 });\n pushCcHook(h, 'sessionEnd', config.stopSummaryScriptPath, { timeout: 10 });\n pushCcHook(h, 'beforeSubmitPrompt', config.userPromptSubmitScriptPath, { timeout: 5 });\n pushCcHook(h, 'stop', config.transcriptSyncScriptPath, { timeout: 3 });\n\n // beforeShellExecution: install scan runs first (caches result for bash judge)\n h.beforeShellExecution = h.beforeShellExecution ?? [];\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n h.beforeShellExecution.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n // skill-judge — its own message, parallel with the bash judge (own hook).\n if (config.skillJudgeScriptPath) h.beforeShellExecution.push({\n command: bunRunCmd(config.skillJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'afterShellExecution', config.bashFollowupScriptPath, { timeout: 10 });\n\n // preToolUse: install scan before bash judge (caches result for bash judge)\n h.preToolUse = h.preToolUse ?? [];\n h.preToolUse.push({\n command: cursorCcCmd(config.installScanScriptPath),\n timeout: 8,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: cursorCcCmd(config.cwePrecheckScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n h.preToolUse.push({\n command: bunRunCmd(config.bashJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|Read|ReadFile|Grep|Glob|terminal|run_terminal_cmd|execute_command|read_file|grep_search|file_search|list_dir|codebase_search|delete_file',\n [SYNKRO_MARKER]: true,\n });\n\n // skill-judge — its own system message, both on bash and on edit matchers.\n if (config.skillJudgeScriptPath) h.preToolUse.push({\n command: bunRunCmd(config.skillJudgeScriptPath),\n timeout: 30,\n failClosed: false,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command',\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'preToolUse', config.editPrecheckScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n if (config.skillJudgeScriptPath) pushCcHook(h, 'preToolUse', config.skillJudgeScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cwePrecheckScriptPath, {\n timeout: 30,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.cvePrecheckScriptPath, {\n timeout: 10,\n matcher: 'Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch',\n });\n pushCcHook(h, 'preToolUse', config.agentJudgeScriptPath, {\n timeout: 30,\n matcher: 'Agent|Task',\n });\n pushCcHook(h, 'preToolUse', config.planJudgeScriptPath, {\n timeout: 45,\n matcher: 'ExitPlanMode|SwitchMode|CreatePlan',\n });\n\n h.afterFileEdit = h.afterFileEdit ?? [];\n h.afterFileEdit.push({\n command: bunRunCmd(config.editCaptureScriptPath),\n timeout: 15,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n pushCcHook(h, 'postToolUse', config.bashFollowupScriptPath, {\n timeout: 10,\n matcher: 'Shell|Bash|terminal|run_terminal_cmd|execute_command|delete_file',\n });\n\n // beforeMCPExecution: MCP server governance — fires once per MCP tool call.\n // Same shared cc-mcp-gate.ts as Claude Code (allow/blocklist + grade allowed\n // calls + emit connect/tool_call/blocked events). Blocking: a blocklisted /\n // un-allowlisted server is denied. No matcher — this event is MCP-only.\n pushCcHook(h, 'beforeMCPExecution', config.mcpGateScriptPath, { timeout: 30 });\n\n h.afterAgentThought = h.afterAgentThought ?? [];\n h.afterAgentThought.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n h.afterAgentResponse = h.afterAgentResponse ?? [];\n h.afterAgentResponse.push({\n command: bunRunCmd(config.agentCaptureScriptPath),\n timeout: 5,\n failClosed: false,\n [SYNKRO_MARKER]: true,\n });\n\n writeHooksFileAtomic(hooksJsonPath, file);\n}\n\nexport function uninstallCursorHooks(hooksJsonPath: string): boolean {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return false;\n }\n if (!file.hooks) return false;\n\n for (const evt of ALL_EVENTS) {\n removeSynkroEntries(file.hooks, evt);\n }\n\n for (const evt of ALL_EVENTS) {\n if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {\n delete file.hooks[evt];\n }\n }\n if (Object.keys(file.hooks).length === 0) {\n delete file.hooks;\n }\n\n writeHooksFileAtomic(hooksJsonPath, file);\n return true;\n}\n\nfunction preToolUseUsesScript(hooks: CursorHookEntry[] | undefined, scriptBasename: string): boolean {\n return (hooks ?? []).some((e) =>\n isSynkroEntry(e) && typeof e.command === 'string' && e.command.includes(scriptBasename),\n );\n}\n\nexport function inspectCursorHooks(hooksJsonPath: string): {\n installed: boolean;\n sessionStart: boolean;\n sessionEnd: boolean;\n beforeSubmitPrompt: boolean;\n stop: boolean;\n beforeShellExecution: boolean;\n afterShellExecution: boolean;\n preToolUse: boolean;\n preToolUseBash: boolean;\n preToolUseEdit: boolean;\n preToolUseCwe: boolean;\n preToolUseCve: boolean;\n preToolUseAgent: boolean;\n preToolUsePlan: boolean;\n afterFileEdit: boolean;\n postToolUse: boolean;\n beforeMCPExecution: boolean;\n} {\n let file: CursorHooksFile;\n try {\n file = readHooksFile(hooksJsonPath);\n } catch {\n return {\n installed: false,\n sessionStart: false, sessionEnd: false, beforeSubmitPrompt: false, stop: false,\n beforeShellExecution: false, afterShellExecution: false,\n preToolUse: false, preToolUseBash: false, preToolUseEdit: false,\n preToolUseCwe: false, preToolUseCve: false, preToolUseAgent: false, preToolUsePlan: false,\n afterFileEdit: false, postToolUse: false, beforeMCPExecution: false,\n };\n }\n const h = file.hooks ?? {};\n const sessionStart = (h.sessionStart ?? []).some((e) => isSynkroEntry(e));\n const sessionEnd = (h.sessionEnd ?? []).some((e) => isSynkroEntry(e));\n const beforeSubmitPrompt = (h.beforeSubmitPrompt ?? []).some((e) => isSynkroEntry(e));\n const stop = (h.stop ?? []).some((e) => isSynkroEntry(e));\n const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry(e));\n const afterShellExecution = (h.afterShellExecution ?? []).some((e) => isSynkroEntry(e));\n const pre = h.preToolUse ?? [];\n const preToolUseBash = preToolUseUsesScript(pre, 'cc-bash-judge') || preToolUseUsesScript(pre, 'cursor-bash-judge');\n const preToolUseEdit = preToolUseUsesScript(pre, 'cc-edit-precheck') || preToolUseUsesScript(pre, 'cursor-edit-precheck');\n const preToolUseCwe = preToolUseUsesScript(pre, 'cc-cwe-precheck');\n const preToolUseCve = preToolUseUsesScript(pre, 'cc-cve-precheck');\n const preToolUseAgent = preToolUseUsesScript(pre, 'cc-agent-judge');\n const preToolUsePlan = preToolUseUsesScript(pre, 'cc-plan-judge');\n const preToolUse = preToolUseBash || preToolUseEdit || preToolUseCwe || preToolUseCve || preToolUseAgent || preToolUsePlan;\n const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry(e));\n const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry(e));\n const beforeMCPExecution = (h.beforeMCPExecution ?? []).some((e) => isSynkroEntry(e));\n return {\n installed: sessionStart || sessionEnd || beforeSubmitPrompt || stop\n || beforeShellExecution || afterShellExecution || preToolUse || afterFileEdit || postToolUse\n || beforeMCPExecution,\n sessionStart, sessionEnd, beforeSubmitPrompt, stop,\n beforeShellExecution, afterShellExecution,\n preToolUse, preToolUseBash, preToolUseEdit, preToolUseCwe, preToolUseCve, preToolUseAgent, preToolUsePlan,\n afterFileEdit, postToolUse, beforeMCPExecution,\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';\nimport { randomBytes } from 'node:crypto';\nimport { resolveBunBin as resolveBunBinShared } from './platform.js';\n\nconst SYNKRO_MARKER = '__synkro_managed__';\nconst SYNKRO_SERVER_NAME = 'synkro-guardrails';\nconst CC_CONFIG_PATH = join(homedir(), '.claude.json');\n\ninterface ClaudeJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\ninterface McpServerEntry {\n type?: 'http' | 'stdio' | 'sse';\n url?: string;\n command?: string;\n args?: string[];\n headers?: Record<string, string>;\n env?: Record<string, string>;\n [SYNKRO_MARKER]?: boolean;\n [k: string]: unknown;\n}\n\nfunction readClaudeJson(): ClaudeJson {\n if (!existsSync(CC_CONFIG_PATH)) return {};\n try {\n const raw = readFileSync(CC_CONFIG_PATH, 'utf-8');\n return JSON.parse(raw) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeClaudeJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CC_CONFIG_PATH), { recursive: true });\n const tmpPath = `${CC_CONFIG_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CC_CONFIG_PATH);\n}\n\nexport interface InstallMcpOptions {\n gatewayUrl: string; // e.g. http://localhost:8788\n // Long-lived (1y) Synkro-signed JWT scoped to mcp:guardrails. Minted by\n // POST /api/v1/cli/mcp-token during install. We deliberately do NOT write\n // the WorkOS access token here anymore — that one expires in 5 min and\n // silently breaks the CC MCP connection.\n bearerToken: string;\n local?: boolean;\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.claude.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readClaudeJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Remove any prior Synkro-managed entry (so re-running install picks up\n // a refreshed JWT). Leave non-Synkro entries alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n const proxyScript = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'stdio',\n command: 'bun',\n args: ['run', proxyScript],\n [SYNKRO_MARKER]: true,\n };\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url: `stdio://${proxyScript}` };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n type: 'http',\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeClaudeJsonAtomic(config);\n return { path: CC_CONFIG_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.claude.json.\n * Returns true if anything was removed.\n */\nexport function uninstallMcpConfig(): boolean {\n if (!existsSync(CC_CONFIG_PATH)) return false;\n const config = readClaudeJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n // If the mcpServers object is now empty, drop it to keep the file tidy.\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeClaudeJsonAtomic(config);\n return true;\n}\n\n/**\n * Inspect whether the Synkro MCP server entry is currently registered.\n */\nexport function inspectMcpConfig(): {\n installed: boolean;\n configPath: string;\n url?: string;\n} {\n if (!existsSync(CC_CONFIG_PATH)) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n const config = readClaudeJson();\n const entry = config.mcpServers?.[SYNKRO_SERVER_NAME];\n if (!entry || entry[SYNKRO_MARKER] !== true) {\n return { installed: false, configPath: CC_CONFIG_PATH };\n }\n return { installed: true, configPath: CC_CONFIG_PATH, url: entry.url };\n}\n\n// ─── Cursor MCP Config ───\n\nconst CURSOR_MCP_PATH = join(homedir(), '.cursor', 'mcp.json');\n\ninterface CursorMcpJson {\n mcpServers?: Record<string, McpServerEntry>;\n [k: string]: unknown;\n}\n\nfunction readCursorMcpJson(): CursorMcpJson {\n if (!existsSync(CURSOR_MCP_PATH)) return {};\n try {\n const raw = readFileSync(CURSOR_MCP_PATH, 'utf-8');\n return JSON.parse(raw) as CursorMcpJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeCursorMcpJsonAtomic(config: CursorMcpJson): void {\n mkdirSync(dirname(CURSOR_MCP_PATH), { recursive: true });\n const tmpPath = `${CURSOR_MCP_PATH}.synkro.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CURSOR_MCP_PATH);\n}\n\n/**\n * Register the Synkro guardrails MCP server in ~/.cursor/mcp.json.\n * Idempotent — replaces any prior Synkro-managed entry with the new one.\n */\nexport function installCursorMcpConfig(opts: InstallMcpOptions): { path: string; url: string } {\n const config = readCursorMcpJson();\n config.mcpServers = config.mcpServers ?? {};\n\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n if (opts.local) {\n // Honors SYNKRO_MCP_PORT so the containerised deployment can register\n // its host-mapped port (e.g. 18931) instead of the bare-host default.\n const port = process.env.SYNKRO_MCP_PORT || '18931';\n const url = `http://127.0.0.1:${port}/`;\n const jwtPath = join(homedir(), '.synkro', '.mcp-jwt');\n let jwt = '';\n try { jwt = readFileSync(jwtPath, 'utf-8').trim(); } catch {}\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n ...(jwt ? { headers: { Authorization: `Bearer ${jwt}` } } : {}),\n [SYNKRO_MARKER]: true,\n };\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n }\n\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/mcp/guardrails`;\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n url,\n headers: { Authorization: `Bearer ${opts.bearerToken}` },\n [SYNKRO_MARKER]: true,\n };\n\n writeCursorMcpJsonAtomic(config);\n return { path: CURSOR_MCP_PATH, url };\n}\n\n/**\n * Remove all Synkro-managed MCP server entries from ~/.cursor/mcp.json.\n */\nexport function uninstallCursorMcpConfig(): boolean {\n if (!existsSync(CURSOR_MCP_PATH)) return false;\n const config = readCursorMcpJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n\n writeCursorMcpJsonAtomic(config);\n return true;\n}\n\n// ─── Claude Desktop MCP Config ───\n//\n// Claude Desktop reads MCP servers from claude_desktop_config.json and (unlike\n// Cursor / Claude Code) supports ONLY stdio command servers there — no `url`\n// transport. So we register the SAME stdio proxy Claude Code's local mode uses\n// (~/.synkro/hooks/mcp-stdio-proxy.ts), which bridges stdio ⇄ the local\n// synkro-server on :18931 and reads the JWT from ~/.synkro/.mcp-jwt itself (no\n// secret in the config). This lets the user manage Synkro standards\n// (rules/tasks/findings) from Claude Desktop, not just Claude Code.\n\nconst CLAUDE_DESKTOP_CONFIG_PATH = join(\n homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json',\n);\nconst MCP_STDIO_PROXY_PATH = join(homedir(), '.synkro', 'hooks', 'mcp-stdio-proxy.ts');\n\n/**\n * Absolute path to `bun`. Claude Desktop is a GUI app and does NOT inherit the\n * login-shell PATH, so a bare \"bun\" command fails to spawn. Resolve it to an\n * absolute path at config-write time. Platform-aware (Windows probes `where` +\n * %USERPROFILE%\\.bun) via the shared installer helper.\n */\nfunction resolveBunBin(): string {\n return resolveBunBinShared();\n}\n\nfunction readDesktopJson(): ClaudeJson {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) return {};\n try {\n return JSON.parse(readFileSync(CLAUDE_DESKTOP_CONFIG_PATH, 'utf-8')) as ClaudeJson;\n } catch (err) {\n throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${(err as Error).message}`);\n }\n}\n\nfunction writeDesktopJsonAtomic(config: ClaudeJson): void {\n mkdirSync(dirname(CLAUDE_DESKTOP_CONFIG_PATH), { recursive: true });\n // Unique per-write tmp name (pid + random) so concurrent writers can't race\n // on a shared tmpfile between write and rename (CWE-362). rename(2) onto the\n // final path stays atomic regardless of how many writers there are.\n const tmpPath = `${CLAUDE_DESKTOP_CONFIG_PATH}.synkro.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n renameSync(tmpPath, CLAUDE_DESKTOP_CONFIG_PATH);\n}\n\nexport interface InstallDesktopMcpResult {\n ok: boolean;\n path: string;\n proxy: string;\n reason?: string;\n}\n\n/**\n * Register the Synkro guardrails MCP server in Claude Desktop's config.\n * Idempotent. Returns ok:false (without writing) if the stdio proxy isn't on\n * disk yet — that means `synkro install` hasn't run, so there's nothing to\n * bridge to.\n */\nexport function installClaudeDesktopMcpConfig(): InstallDesktopMcpResult {\n if (!existsSync(MCP_STDIO_PROXY_PATH)) {\n return {\n ok: false,\n path: CLAUDE_DESKTOP_CONFIG_PATH,\n proxy: MCP_STDIO_PROXY_PATH,\n reason: 'stdio proxy not found — run `synkro install` first',\n };\n }\n const config = readDesktopJson();\n config.mcpServers = config.mcpServers ?? {};\n\n // Replace any prior Synkro-managed entry; leave the user's other servers alone.\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) delete config.mcpServers[name];\n }\n\n config.mcpServers[SYNKRO_SERVER_NAME] = {\n command: resolveBunBin(),\n args: ['run', MCP_STDIO_PROXY_PATH],\n env: { SYNKRO_MCP_PORT: process.env.SYNKRO_MCP_PORT || '18931' },\n [SYNKRO_MARKER]: true,\n };\n\n writeDesktopJsonAtomic(config);\n return { ok: true, path: CLAUDE_DESKTOP_CONFIG_PATH, proxy: MCP_STDIO_PROXY_PATH };\n}\n\n/** Remove all Synkro-managed MCP entries from Claude Desktop's config. */\nexport function uninstallClaudeDesktopMcpConfig(): boolean {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) return false;\n const config = readDesktopJson();\n if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;\n\n let removed = false;\n for (const [name, entry] of Object.entries(config.mcpServers)) {\n if (entry?.[SYNKRO_MARKER] === true) {\n delete config.mcpServers[name];\n removed = true;\n }\n }\n if (!removed) return false;\n\n if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;\n writeDesktopJsonAtomic(config);\n return true;\n}\n\n/** Whether the Synkro MCP entry is registered in Claude Desktop's config. */\nexport function inspectClaudeDesktopMcpConfig(): { installed: boolean; configPath: string } {\n if (!existsSync(CLAUDE_DESKTOP_CONFIG_PATH)) {\n return { installed: false, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n }\n try {\n const entry = readDesktopJson().mcpServers?.[SYNKRO_SERVER_NAME];\n return { installed: entry?.[SYNKRO_MARKER] === true, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n } catch {\n return { installed: false, configPath: CLAUDE_DESKTOP_CONFIG_PATH };\n }\n}\n","import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport function resolveSkillPaths(skills: string[], repoRoot: string): string[] {\n return skills\n .filter(s => s.endsWith('.md'))\n .map(s => resolve(repoRoot, s))\n .filter(p => existsSync(p));\n}\n","// :)\n/**\n * TypeScript hook scripts for Bun runtime — written to ~/.synkro/hooks/ during `synkro install`.\n *\n * Each export is the full source code of a TypeScript file that runs via `#!/usr/bin/env bun`.\n * All grading, classification, CVE scanning, and persistence lives server-side.\n * Local mode: grade via local-cc channel; rules + embeddings from local PGLite only.\n */\n\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STUB HOOKS (`synkro install --mode stub`)\n//\n// In stub mode every hook becomes ~3 lines that delegate to _synkro-stub-common.ts.\n// The common file is pure PLUMBING — no scanning logic, no regexes, no verdict\n// parsing. It reads the host-only inputs the container can't see (target file\n// bytes, transcript tail, the repo's .synkro), POSTs a fixed envelope to the\n// local server's /api/scan/{surface}, and prints the reply. All the brains live\n// in the container (cli/scanning/*), so logic changes ship via a GHCR push with\n// no CLI re-install. Authored with String.raw + string concatenation (no ${}, no\n// inner backticks, no regex) so escapes survive materialization verbatim.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID, createHash } from 'node:crypto';\n\nconst HOME = homedir();\nconst PORT = process.env.SYNKRO_MCP_PORT || '18931';\n\n// ─── Telemetry: sync JSONL append, drained later by the CLI binary ──────────\n// Matches packages/cli/cli/telemetry/emit.ts. Inlined here so each bun hook\n// is self-contained (no path-relative import to a sibling module that may not\n// exist post-install). Writes to the same ~/.synkro/telemetry-pending.jsonl\n// the CLI binary appends to; ON CONFLICT in drain.ts dedupes.\nconst PENDING_JSONL = join(HOME, '.synkro', 'telemetry-pending.jsonl');\nconst META_JSON = join(HOME, '.synkro', 'telemetry-meta.json');\nconst CREDS_JSON = join(HOME, '.synkro', 'credentials.json');\n\ninterface TelemMeta { install_id?: string; enabled?: boolean }\nlet _telemMetaCache: TelemMeta | null = null;\nfunction telemMeta(): TelemMeta {\n if (_telemMetaCache) return _telemMetaCache;\n try {\n _telemMetaCache = JSON.parse(readFileSync(META_JSON, 'utf-8')) as TelemMeta;\n } catch { _telemMetaCache = {}; }\n return _telemMetaCache;\n}\n\ninterface TelemCreds { user_id?: string; org_id?: string; email?: string }\nlet _telemCredsCache: TelemCreds | null = null;\nfunction telemCreds(): TelemCreds {\n if (_telemCredsCache) return _telemCredsCache;\n try {\n const c = JSON.parse(readFileSync(CREDS_JSON, 'utf-8')) as TelemCreds;\n _telemCredsCache = { user_id: c.user_id, org_id: c.org_id, email: c.email };\n } catch { _telemCredsCache = {}; }\n return _telemCredsCache;\n}\n\ninterface HookEmitOpts {\n agent_kind?: 'claude_code' | 'cursor';\n cc_session_id?: string;\n cc_tool_use_id?: string;\n cc_model?: string;\n permission_mode?: string;\n cwd?: string;\n repo?: string;\n}\n\nexport function hookEmit(eventType: string, context: unknown, opts: HookEmitOpts = {}): void {\n try {\n const meta = telemMeta();\n if (meta.enabled === false) return;\n // Never ship rows with an empty install_id — they can't be attributed. An\n // absent meta file means the CLI hasn't bootstrapped telemetry yet; skip\n // rather than emit an orphan row (the CLI metaCache mints the id on install).\n if (!meta.install_id) return;\n const creds = telemCreds();\n const row = {\n client_event_id: randomUUID(),\n event_type: eventType,\n occurred_at: new Date().toISOString(),\n install_id: meta.install_id,\n user_id: creds.user_id ?? null,\n org_id: creds.org_id ?? null,\n email: creds.email ?? null,\n cli_version: process.env.SYNKRO_VERSION || '',\n emitter: process.env.SYNKRO_TELEMETRY_EMITTER || 'hook',\n agent_kind: opts.agent_kind ?? null,\n cc_session_id: opts.cc_session_id ?? null,\n cc_tool_use_id: opts.cc_tool_use_id ?? null,\n cc_turn_id: null,\n cc_model: opts.cc_model ?? null,\n permission_mode: opts.permission_mode ?? null,\n cwd: opts.cwd ?? null,\n repo: opts.repo ?? null,\n git_branch: null,\n git_sha: null,\n policy_name: null,\n capture_depth: null,\n platform: process.platform,\n hostname_hash: null,\n node_version: process.version || '',\n context,\n };\n try { mkdirSync(join(HOME, '.synkro'), { recursive: true }); } catch {}\n appendFileSync(PENDING_JSONL, JSON.stringify(row) + '\\n');\n } catch { /* never let telemetry block a hook */ }\n}\n\n// Surface name → telemetry event type. One-shot lookup for the runStub wrap.\nfunction eventForSurface(surface: string): { type: string; sessionPhase?: 'start' | 'end' } {\n switch (surface) {\n case 'session-start': return { type: 'session_lifecycle', sessionPhase: 'start' };\n case 'stop-summary': return { type: 'session_lifecycle', sessionPhase: 'end' };\n case 'prompt-submit': return { type: 'human_interaction' };\n case 'bash-followup': return { type: 'human_interaction' };\n case 'mcp-gate': return { type: 'mcp_tool_use' };\n case 'plan-judge': return { type: 'plan_review_result' };\n case 'transcript-sync': return { type: 'tool_call' };\n default: return { type: 'tool_call' };\n }\n}\n\n// Inspect the verdict text returned by the local server to derive a coarse\n// verdict for the telemetry row. Both CC and Cursor return JSON; we parse\n// lightly and ignore failures (default 'pass').\nfunction parseVerdict(text: string, harness: string): 'pass' | 'deny' | 'allow' | 'warning' | 'error' | 'ask' {\n if (!text) return 'pass';\n try {\n const j = JSON.parse(text);\n if (harness === 'cursor') {\n const p = j?.permission;\n if (p === 'deny') return 'deny';\n if (p === 'allow') return 'allow';\n return 'pass';\n }\n const dec = j?.hookSpecificOutput?.permissionDecision;\n if (dec === 'deny') return 'deny';\n // 'ask' is a consent prompt, NOT a block — keep it distinct so it doesn't\n // manufacture spurious violation rows (only genuine deny/block violates).\n if (dec === 'ask') return 'ask';\n if (dec === 'allow') return 'allow';\n return 'pass';\n } catch { return 'pass'; }\n}\n\nfunction loadMcpJwt(): string {\n try { return readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; }\n}\n\nlet _cfgCache: Record<string, string> | null = null;\nfunction cfgVal(key: string): string {\n // ~/.synkro/config.env (written by \"synkro install\") is AUTHORITATIVE for the keys it\n // defines. This hook runs under bun, which AUTO-LOADS a .env/.env.local from the repo\n // cwd into process.env — so a repo's own dev vars (e.g. SYNKRO_GATEWAY_URL=localhost for a\n // local \"wrangler dev\") would otherwise hijack the installed config and silently redirect\n // cloud grades to a dead gateway (fail-open, \"nothing fires\"). So read config.env FIRST and\n // only fall back to process.env for keys config.env doesn't set (e.g. SYNKRO_HOOK_FORMAT,\n // which is passed on the cursor hook command, never written to config.env).\n if (!_cfgCache) {\n _cfgCache = {};\n try {\n const txt = readFileSync(join(HOME, '.synkro', 'config.env'), 'utf-8');\n for (const line of txt.split('\\n')) {\n const mm = line.match(/^([A-Z0-9_]+)=['\"]?(.*?)['\"]?\\s*$/);\n if (mm) _cfgCache[mm[1]] = mm[2];\n }\n } catch {}\n }\n if (_cfgCache[key]) return _cfgCache[key];\n return process.env[key] || '';\n}\nfunction deployIsCloud(): boolean { return cfgVal('SYNKRO_DEPLOY_LOCATION') === 'cloud'; }\n\n// Cloud MCP gate: in cloud mode the local server (:18931) isn't running, so the\n// gate asks the org policy directly (api /api/mcp/gate, authed by .mcp-jwt).\n// Fail-open by contract — any error or non-block verdict allows the call.\nasync function cloudMcpGate(payload: any, harness: string): Promise<string> {\n try {\n const toolName = String(payload.tool_name || payload.tool || '');\n const parts = toolName.split('__');\n const server = (parts.length >= 3 && parts[0] === 'mcp') ? parts[1] : '';\n if (!server) return failOpen(harness);\n const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\\/+$/, '');\n const resp = await fetch(base + '/api/mcp/gate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },\n body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),\n signal: AbortSignal.timeout(5000),\n });\n if (!resp.ok) return failOpen(harness);\n const d = await resp.json() as any;\n if (d && d.decision === 'block') {\n const reason = String(d.reason || ('MCP server \"' + server + '\" blocked by policy'));\n if (harness === 'cursor') return JSON.stringify({ permission: 'deny', user_message: reason });\n return JSON.stringify({ systemMessage: '[synkro:cloud:mcp] ' + reason, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: reason } });\n }\n return failOpen(harness);\n } catch { return failOpen(harness); }\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n return Buffer.concat(chunks).toString('utf-8');\n}\n\nfunction isCursor(harnessOpt?: string): boolean {\n return harnessOpt === 'cursor' || process.argv.includes('--cursor') || process.env.SYNKRO_HOOK_FORMAT === 'cursor';\n}\n\nfunction failOpen(harness: string): string {\n return harness === 'cursor' ? '{\"permission\":\"allow\"}' : '{}';\n}\n\nfunction out(s: string): void { try { process.stdout.write(s + '\\n'); } catch {} }\n\nfunction gitRoot(cwd: string): string {\n if (!cwd) return '';\n try {\n return execSync('git rev-parse --show-toplevel', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch { return ''; }\n}\n\n// Once-per-session onboarding hint for repos with no synkro.toml file.\nfunction noSynkroHint(sessionId: string): string | null {\n const dir = join(HOME, '.synkro', '.no-synkro-hint');\n try {\n mkdirSync(dir, { recursive: true });\n const key = (sessionId || 'nosession').replace(/[^a-zA-Z0-9_-]/g, '_');\n const marker = join(dir, key);\n if (existsSync(marker)) return null;\n writeFileSync(marker, '', { flag: 'w' });\n } catch {}\n return '[synkro] No synkro.toml config in this repo — grading skipped. Run synkro install here to enable Synkro.';\n}\n\nfunction filePathFromToolInput(ti: any): string {\n if (!ti || typeof ti !== 'object') return '';\n return ti.file_path || ti.notebook_path || ti.path || ti.target_file || '';\n}\n\ninterface StubOpts {\n needsFile?: boolean;\n needsTranscript?: boolean;\n // Ship the WHOLE transcript (not just the 200KB tail). Sync surfaces need it\n // so the server can compute stable absolute message indices + total usage.\n fullTranscript?: boolean;\n needsPlan?: boolean;\n telemetry?: boolean;\n harness?: string;\n}\n\nexport async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {\n const harness = isCursor(opts.harness) ? 'cursor' : 'cc';\n const startedAt = Date.now();\n process.env.SYNKRO_TELEMETRY_EMITTER = process.env.SYNKRO_TELEMETRY_EMITTER || ('hook:' + surface);\n let telemPayload: any = null;\n let telemCwd = '';\n let telemSessionId = '';\n try {\n const input = await readStdin();\n if (!input.trim()) { out(failOpen(harness)); return; }\n const payload = JSON.parse(input);\n telemPayload = payload;\n telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';\n telemSessionId = String(payload?.session_id || payload?.conversation_id || '');\n\n // Cloud MCP enforcement: the gate can't reach the local server, so decide\n // against the org policy directly. Fail-open inside cloudMcpGate. Emit the\n // same mcp_tool_use (+ violation on block) the local path does, for parity.\n if (surface === 'mcp-gate' && deployIsCloud()) {\n const cloudResp = await cloudMcpGate(payload, harness);\n out(cloudResp);\n emitStubTelemetry(surface, harness, payload, cloudResp, Date.now() - startedAt, telemCwd, telemSessionId);\n return;\n }\n\n const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots : [];\n const cwd = (typeof payload.cwd === 'string' && payload.cwd) || workspaceRoots[0] || '';\n const sessionId = String(payload.session_id || payload.conversation_id || '');\n const root = gitRoot(cwd);\n\n // Dormancy: a repo is onboarded only if it has a synkro.toml FILE at its git\n // root. Guard root !== HOME — ~/.synkro is the config DIRECTORY, so without\n // this a home-rooted cwd (dotfiles in git, non-git dir under home) could\n // look onboarded.\n let synkroFileText = '';\n if (root && root !== HOME && existsSync(join(root, 'synkro.toml'))) {\n try { synkroFileText = readFileSync(join(root, 'synkro.toml'), 'utf-8'); } catch {}\n }\n if (!synkroFileText) {\n // Repo not onboarded — emit a minimal tool_call so usage analytics still\n // sees the hook fire, then short-circuit. No verdict, no command body.\n // transcript-sync is a background sync, not a tool invocation — omit it so\n // it doesn't inflate tool_call counts. No model derivation on this hot path.\n if (surface !== 'transcript-sync') {\n hookEmit('tool_call', {\n tool_name: String(payload?.tool_name || ''),\n hook_event: surface === 'bash-followup' ? 'PostToolUse' : 'PreToolUse',\n tool_input: {},\n verdict: 'pass',\n route: 'local',\n route_fallback_reason: 'repo_not_onboarded',\n silent: false,\n duration_ms: Date.now() - startedAt,\n }, telemPayloadOpts(payload, harness, telemCwd, telemSessionId));\n }\n if (opts.telemetry) { out(failOpen(harness)); return; }\n const hint = noSynkroHint(sessionId);\n if (!hint) { out(failOpen(harness)); return; }\n if (harness === 'cursor') out(JSON.stringify({ permission: 'allow', user_message: hint }));\n else out(JSON.stringify({ systemMessage: hint, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: hint } }));\n return;\n }\n\n // Gather host-only inputs the container can't read.\n const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };\n\n if (opts.needsFile) {\n const fp = filePathFromToolInput(payload.tool_input || {});\n if (fp && existsSync(fp)) {\n try { envelope.baseContent = readFileSync(fp, 'utf-8').slice(0, 65536); } catch {}\n }\n }\n if (opts.needsTranscript) {\n const tp = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';\n if (tp && existsSync(tp)) {\n try {\n const t = readFileSync(tp, 'utf-8');\n // Cap the shipped transcript so the 3s Stop-hook sync COMPLETES even on huge sessions.\n // An unbounded full-transcript upload (100s of MB) is killed mid-upload by the hook\n // timeout, so the turn's final reply never lands (that was the \"I don't see my latest\n // reply\" gap). A 400KB tail still covers many recent turns including the final reply;\n // older turns were persisted by earlier syncs. Rows key on the stable CC uuid and order\n // by ts, so we no longer depend on the whole file for absolute message indices.\n const cap = opts.fullTranscript ? 400000 : 200000;\n envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);\n } catch {}\n }\n }\n if (opts.needsPlan) {\n const ti = payload.tool_input || {};\n envelope.planText = String(ti.plan || ti.content || payload.plan || '');\n }\n\n // bash-followup is telemetry, but it can carry the INLINE \"task complete ✓ N/N\"\n // result (the server waits up to 30s for the completion grade), so it needs a\n // wait above that budget — not the 6s telemetry default — or the stub abandons\n // the request before the result lands and the completion shows up a hook late.\n // prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from\n // being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)\n // and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.\n const timeoutMs = surface === 'bash-followup' ? 32000\n : surface === 'prompt-submit' ? 3500\n : (opts.telemetry ? 6000 : 28000);\n // Cloud has no local container to reach. Post the SAME envelope to the org's grader\n // via the gateway's /grade/submit as role 'scan:<surface>'; the container runs\n // scanRouter (runScan) server-side and returns the SAME shaped decision — so cloud\n // and local share ONE grading implementation, no client-side parsing. The gateway\n // accepts the durable 1yr MCP token (verifyOrg → org-derived, tenant-safe).\n const cloud = deployIsCloud();\n // Regex-free trailing-slash trim — this is inside a template literal, where a\n // regex like /\\\\/+$/ mis-escapes into broken emitted code (smoke:hooks catches it).\n let gwBase = cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh';\n while (gwBase.endsWith('/')) gwBase = gwBase.slice(0, -1);\n const url = cloud\n ? gwBase + '/grade/submit'\n : 'http://127.0.0.1:' + PORT + '/api/scan/' + surface + (harness === 'cursor' ? '?harness=cursor' : '');\n const body = cloud\n ? JSON.stringify({ role: 'scan:' + surface, payload: JSON.stringify(envelope), content: '' })\n : JSON.stringify(envelope);\n const headers = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() };\n // Telemetry hooks (bash-followup, transcript-sync, session telemetry) IGNORE the\n // response and the server processes them DETACHED, so delivery RELIABILITY beats\n // latency: retry a few times with backoff. Right after a server restart the\n // event loop can be briefly busy / not-yet-listening and a single POST silently\n // drops the event (this was the followup flakiness). Double-delivery is safe here\n // — the server side is idempotent-ish (completion in-flight guard, dup evidence\n // is harmless). Blocking hooks make ONE attempt: their response IS the verdict\n // and a retry would double-grade. Normal case: first attempt wins, no delay.\n // prompt-submit is EXCLUDED from the retry: it runs on the interactive prompt\n // path under a 5s hook timeout, and 3×6s+backoff (19.5s) blew that budget and\n // fired \"hook timed out after 5s\" on every prompt whenever the server was slow.\n // Dropping an occasional prompt tick is harmless; stalling the user is not.\n const attempts = (opts.telemetry && surface !== 'prompt-submit') ? 3 : 1;\n let text = '';\n for (let i = 0; i < attempts; i++) {\n try {\n const resp = await fetch(url, { method: 'POST', headers, body, signal: AbortSignal.timeout(timeoutMs) });\n if (resp.ok) { text = (await resp.text()).trim(); break; }\n } catch (e) { /* connection refused / timeout → retry below */ }\n if (i < attempts - 1) await new Promise((r) => setTimeout(r, 500 * (i + 1)));\n }\n const responseText = text || failOpen(harness);\n out(responseText);\n emitStubTelemetry(surface, harness, telemPayload, responseText, Date.now() - startedAt, telemCwd, telemSessionId);\n } catch (err) {\n out(failOpen(harness));\n const msg = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? (err.stack || '') : '';\n hookEmit('error', {\n category: 'hook_runtime',\n code: 'STUB_RUNSTUB_THREW',\n message: msg.slice(0, 4096),\n stack: stack.slice(0, 16384),\n emitter_context: 'runStub:' + surface,\n }, telemPayloadOpts(telemPayload, harness, telemCwd, telemSessionId));\n }\n}\n\n// ─── Bounded transcript tail read + model derivation ────────────────────────\n// The CC model lives in the LAST assistant message, always near the end of the\n// transcript. Read at most the last 200KB off disk instead of slurping+splitting\n// the whole file (transcripts reach 100s of MB → O(n^2) per session on the\n// verdict hot path). Only ever called on the ACTIVE path, never when dormant.\nconst TRANSCRIPT_TAIL_BYTES = 200000;\nfunction readTranscriptTail(path: string): string {\n try {\n const size = statSync(path).size;\n if (size <= TRANSCRIPT_TAIL_BYTES) return readFileSync(path, 'utf-8');\n const fd = openSync(path, 'r');\n try {\n const buf = Buffer.allocUnsafe(TRANSCRIPT_TAIL_BYTES);\n const n = readSync(fd, buf, 0, TRANSCRIPT_TAIL_BYTES, size - TRANSCRIPT_TAIL_BYTES);\n return buf.subarray(0, n).toString('utf-8');\n } finally { closeSync(fd); }\n } catch { return ''; }\n}\nfunction deriveCcModel(payload: any): string {\n const tp = (payload && payload.transcript_path) || '';\n if (!tp || !existsSync(tp)) return '';\n const lines = readTranscriptTail(tp).split('\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n if (!lines[i].includes('\"type\":\"assistant\"')) continue;\n try {\n const obj = JSON.parse(lines[i]);\n if (obj?.message?.model) return String(obj.message.model);\n } catch { /* torn line (tail may start mid-record) — skip, keep scanning */ }\n }\n return '';\n}\n\n// ─── Telemetry payload hygiene: scrub + byte-cap + summarize ────────────────\n// Self-contained port of _synkro-common's scrubSecrets — stub mode DELETES\n// _synkro-common.ts, so the stub cannot import it. Same hard-secret patterns +\n// credential key/value redaction; the entropy heuristic is omitted since these\n// rows never carry file bodies and every field is additionally byte-capped.\nconst TE_SECRET_PATTERNS: Array<{ type: string; re: RegExp }> = [\n { type: 'anthropic-key', re: /sk-ant-[A-Za-z0-9_-]{24,}/g },\n { type: 'openai-key', re: /sk-(?:proj-)?[A-Za-z0-9]{32,}/g },\n { type: 'aws-access-key', re: /AKIA[0-9A-Z]{16}/g },\n { type: 'github-token', re: /gh[posru]_[A-Za-z0-9]{36,}/g },\n { type: 'google-api-key', re: /AIza[0-9A-Za-z_-]{35}/g },\n { type: 'slack-token', re: /xox[baprs]-[0-9A-Za-z-]{10,}/g },\n { type: 'stripe-key', re: /[rs]k_(?:live|test)_[A-Za-z0-9]{20,}/g },\n { type: 'jwt', re: /eyJ[A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}/g },\n { type: 'private-key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },\n { type: 'bearer-token', re: /[Bb]earer[ ]+[A-Za-z0-9._-]{20,}/g },\n];\nfunction isPlaceholderSecret(v: string): boolean {\n if (/[$<>{}]/.test(v)) return true;\n const tail = v.replace(/^(sk-ant-|[rs]k_(?:live|test)_|sk-|AKIA|gh[posru]_|AIza|xox[baprs]-|Bearer[ ]+)/, '');\n return /^(x+|0+|a+|placeholder|example|changeme|your[-_a-z0-9]*|dummy|test|fake|redacted|sample)$/i.test(tail);\n}\nfunction scrubSecrets(text: string): string {\n if (!text) return text;\n let out = text;\n for (const { type, re } of TE_SECRET_PATTERNS) {\n out = out.replace(re, (m) => isPlaceholderSecret(m) ? m : '<redacted:' + type + '>');\n }\n out = out.replace(/((?:password|passwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|private[_-]?key)[\"' ]*[=:][ ]*[\"']?)([^ \"'\\n]{12,})/gi, (full, pfx, val) => {\n if (/[$<>{}]/.test(val)) return full;\n return pfx + '<redacted:credential>';\n });\n return out;\n}\n// Byte-cap (not char-cap): multi-byte content must not push a JSONL row past the\n// ~4KB append-atomicity window, above which concurrent hook appends tear.\nfunction cap(s: string, maxBytes: number): string {\n if (!s) return s;\n if (Buffer.byteLength(s, 'utf-8') <= maxBytes) return s;\n let out = s.slice(0, maxBytes);\n while (out && Buffer.byteLength(out, 'utf-8') > maxBytes) out = out.slice(0, -1);\n return out;\n}\n// Field byte budgets. A row's fixed envelope (ids, timestamps, cwd, model) runs\n// ~0.8-1.2KB, so no single free-text field may approach 4KB or the whole JSONL\n// row overruns the append-atomicity window. These leave margin even together.\nconst TE_CMD_CAP = 1200;\nconst TE_REASON_CAP = 800;\nconst TE_EXCERPT_CAP = 1200;\nconst TE_PROMPT_CAP = 2400;\n// Never embed a raw tool_input — Write/Edit carry whole file bodies, Bash the raw\n// command, so verbatim rows exfiltrate secrets AND overrun the 4KB row budget.\n// Emit a fixed-size fingerprint: tool name, top-level keys, serialized byte\n// length, short content hash. Bash is the one surface where the command text is\n// itself the signal, so include it scrubbed + byte-capped when asked.\nfunction toolInputSummary(toolName: string, toolInput: any, includeCommand: boolean): Record<string, any> {\n let ser = '';\n try { ser = JSON.stringify(toolInput == null ? {} : toolInput); } catch { ser = String(toolInput); }\n const isObj = !!toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput);\n const summary: Record<string, any> = {\n tool_name: toolName || undefined,\n keys: isObj ? Object.keys(toolInput).slice(0, 24) : [],\n bytes: Buffer.byteLength(ser, 'utf-8'),\n hash: createHash('sha256').update(ser).digest('hex').slice(0, 12),\n };\n if (includeCommand && isObj && typeof toolInput.command === 'string' && toolInput.command) {\n summary.command = cap(scrubSecrets(toolInput.command), TE_CMD_CAP);\n }\n return summary;\n}\n\n// cc_model is derived by the caller (bounded tail read) and passed in; this stays\n// allocation-only so the dormant hot path never touches the transcript.\nfunction telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {\n return {\n agent_kind: harness === 'cursor' ? 'cursor' : 'claude_code',\n cc_session_id: sessionId || undefined,\n cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,\n cc_model: ccModel || undefined,\n permission_mode: payload?.permission_mode ? String(payload.permission_mode) : undefined,\n cwd: cwd || undefined,\n };\n}\n\nfunction emitStubTelemetry(\n surface: string, harness: string, payload: any, responseText: string,\n durationMs: number, cwd: string, sessionId: string,\n): void {\n // transcript-sync is a background sync, not a tool invocation — no tool_call\n // row (keeps tool_call counts accurate); its usage lands via session_lifecycle.\n // Bail before any transcript read so it stays cheap.\n if (surface === 'transcript-sync') return;\n\n const opts = telemPayloadOpts(payload, harness, cwd, sessionId, deriveCcModel(payload));\n const verdict = parseVerdict(responseText, harness);\n const meta = eventForSurface(surface);\n\n if (meta.sessionPhase === 'start') {\n hookEmit('session_lifecycle', {\n phase: 'start', route: 'local-cc', channel_port: Number(PORT) || 18931,\n channel_reachable: true, open_findings: 0,\n transcript_path: payload?.transcript_path || undefined,\n }, opts);\n return;\n }\n if (meta.sessionPhase === 'end') {\n hookEmit('session_lifecycle', {\n phase: 'end', route: 'local-cc', channel_port: Number(PORT) || 18931,\n channel_reachable: true, open_findings: 0,\n transcript_path: payload?.transcript_path || undefined,\n }, opts);\n return;\n }\n if (meta.type === 'human_interaction') {\n const kind = surface === 'prompt-submit' ? 'prompt_submit' : 'consent_consume';\n const promptText = surface === 'prompt-submit'\n ? cap(String(payload?.prompt || payload?.message || payload?.content || ''), TE_PROMPT_CAP)\n : undefined;\n hookEmit('human_interaction', { kind, prompt_text: promptText }, opts);\n return;\n }\n if (meta.type === 'mcp_tool_use') {\n const toolName = String(payload?.tool_name || payload?.tool || '');\n hookEmit('mcp_tool_use', {\n tool_name: toolName,\n args_summary: toolInputSummary(toolName, payload?.tool_input, true),\n duration_ms: durationMs,\n success: verdict !== 'deny' && verdict !== 'error',\n }, opts);\n if (verdict === 'deny') {\n hookEmit('violation', {\n hook_type: 'bash', rule_id: '', rule_mode: 'blocking', severity: 'high',\n category: 'mcp_governance', reason: 'mcp_blocked',\n rules_checked: [], violated_rules: [],\n command: cap(scrubSecrets(toolName), TE_CMD_CAP),\n tool_name: toolName, hook_event: 'PreToolUse', tool_input: toolInputSummary(toolName, payload?.tool_input, false),\n verdict: 'block', route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n }\n return;\n }\n if (meta.type === 'plan_review_result') {\n hookEmit('plan_review_result', {\n verdict: verdict === 'deny' ? 'advisory' : 'clean',\n reason: '', plan_excerpt: cap(scrubSecrets(String(payload?.tool_input?.plan || '')), TE_EXCERPT_CAP),\n rule_ids_relevant: [], route: 'local',\n }, opts);\n return;\n }\n // Default: tool_call. Add a violation row ONLY on a genuine denial ('ask' is a\n // consent prompt, not a block). tool_input is summarized, never embedded raw.\n const toolName = String(payload?.tool_name || '');\n hookEmit('tool_call', {\n tool_name: toolName,\n hook_event: surface === 'bash-followup' ? 'PostToolUse' : 'PreToolUse',\n tool_input: toolInputSummary(toolName, payload?.tool_input, true),\n verdict, route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n if (verdict === 'deny') {\n let hookType: 'bash' | 'edit' | 'plan' | 'cve_scan' | 'cwe_scan' = 'bash';\n if (surface === 'edit-precheck') hookType = 'edit';\n else if (surface === 'cwe-precheck') hookType = 'cwe_scan';\n else if (surface === 'cve-precheck') hookType = 'cve_scan';\n else if (surface === 'plan-judge') hookType = 'plan';\n hookEmit('violation', {\n hook_type: hookType,\n rule_id: '', rule_mode: 'blocking', severity: 'high', category: 'policy',\n reason: cap('server-denied', TE_REASON_CAP),\n rules_checked: [], violated_rules: [],\n command: cap(scrubSecrets(String(payload?.tool_input?.command || filePathFromToolInput(payload?.tool_input) || '')), TE_CMD_CAP),\n tool_name: toolName, hook_event: 'PreToolUse', tool_input: toolInputSummary(toolName, payload?.tool_input, false),\n verdict: 'block', route: 'local', silent: false, duration_ms: durationMs,\n }, opts);\n }\n}\n`;\n\nfunction stubHook(surface: string, optsLiteral: string): string {\n return '#!/usr/bin/env bun\\n' +\n \"import { runStub } from './_synkro-stub-common.ts';\\n\" +\n 'runStub(' + JSON.stringify(surface) + ', ' + optsLiteral + ');\\n';\n}\n\n// edit/bash/cwe/cve/install ship the transcript tail too: the server reads the\n// model (last assistant message) + user intent / consent history from it — the\n// same context the old fat hooks extracted. Stub code stays 3 lines; this is\n// just a flag telling the plumbing common to attach the tail.\nexport const STUB_EDIT_PRECHECK_TS = stubHook('edit-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_CWE_PRECHECK_TS = stubHook('cwe-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_CVE_PRECHECK_TS = stubHook('cve-precheck', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_BASH_JUDGE_TS = stubHook('bash-judge', '{ needsTranscript: true }');\n// SKILL judge — its OWN hook so skillGuard is its OWN PreToolUse system-message block\n// (CC renders one systemMessage per hook). Registered on the same Bash + Edit matchers\n// as bash-judge/edit-precheck (runs in parallel, waits for all → a skill deny still\n// blocks). needsFile: reconstruct edit content; needsTranscript: user intent.\nexport const STUB_SKILL_JUDGE_TS = stubHook('skill-judge', '{ needsFile: true, needsTranscript: true }');\nexport const STUB_INSTALL_SCAN_TS = stubHook('install-scan', '{ needsTranscript: true }');\nexport const STUB_AGENT_JUDGE_TS = stubHook('agent-judge', '{ needsTranscript: true }');\n// PreToolUse on mcp__* tools → MCP server governance (allow/blocklist + grade).\n// Blocking, like the bash judge: the server's deny response stops the call.\nexport const STUB_MCP_GATE_TS = stubHook('mcp-gate', '{ needsTranscript: true }');\nexport const STUB_PLAN_JUDGE_TS = stubHook('plan-judge', '{ needsPlan: true }');\n// NOT telemetry: the Stop hook also runs the task-adherence completion gate,\n// which needs the full grading timeout and may return {decision:\"block\"} (passed\n// through verbatim to CC). When no task is active the handler returns instantly,\n// so stop stays fast in the common case.\nexport const STUB_STOP_SUMMARY_TS = stubHook('stop-summary', '{ needsTranscript: true, fullTranscript: true }');\nexport const STUB_SESSION_START_TS = stubHook('session-start', '{ telemetry: true }');\nexport const STUB_TRANSCRIPT_SYNC_TS = stubHook('transcript-sync', '{ needsTranscript: true, fullTranscript: true, telemetry: true }');\nexport const STUB_USER_PROMPT_SUBMIT_TS = stubHook('prompt-submit', '{ telemetry: true }');\nexport const STUB_BASH_FOLLOWUP_TS = stubHook('bash-followup', '{ telemetry: true }');\n\n// PreToolUse hook on the activate_standard MCP tool. Fires IN the activating\n// session (carrying its real session_id) just BEFORE the tool runs, and stamps\n// the activation intent on the server so the task binds to THIS exact session —\n// no most-recent-session race. Self-contained (doesn't need _synkro-stub-common);\n// always allow ('{}'), never blocks the tool.\nexport const STUB_TASK_ACTIVATE_INTENT_TS = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nconst PORT = process.env.SYNKRO_MCP_PORT || '18931';\nfunction mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }\nconst chunks: Buffer[] = [];\nfor await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\ntry {\n const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');\n await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },\n body: JSON.stringify({ session_id: payload.session_id, tool_input: payload.tool_input }),\n signal: AbortSignal.timeout(5000),\n });\n} catch {}\nprocess.stdout.write('{}\\\\n');\n`;\nexport const STUB_CURSOR_BASH_JUDGE_TS = stubHook('bash-judge', \"{ needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_SKILL_JUDGE_TS = stubHook('skill-judge', \"{ needsFile: true, needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_EDIT_CAPTURE_TS = stubHook('edit-precheck', \"{ needsFile: true, needsTranscript: true, harness: 'cursor' }\");\nexport const STUB_CURSOR_AGENT_CAPTURE_TS = stubHook('agent-judge', \"{ needsTranscript: true, harness: 'cursor' }\");\n","/**\n * Synkro CLI Authentication\n *\n * OAuth-style authentication flow for CLI integration with Synkro web platform.\n * Mirrors the Node.js reference implementation.\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from \"node:http\";\nimport { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFile } from \"node:child_process\";\nimport jwt from \"jsonwebtoken\";\n\n// Types\ninterface WorkOSJwtPayload {\n iss: string; // \"https://api.workos.com/\"\n sub: string; // user ID\n aud?: string; // client ID\n exp: number;\n iat: number;\n email?: string;\n org_id?: string;\n role?: string;\n permissions?: string[];\n sid?: string; // session ID\n}\n\n// Configuration — matches the desktop app pattern (packages/desktop/src-tauri/src/auth.rs).\n// Dev dashboard runs on :4322; CLI listens on 8100 for the OAuth callback.\nconst PORT = 8100;\n// Same poisoning concern as the gateway URL: a developer's local .env or\n// op:// expansion can land in SYNKRO_WEB_AUTH_URL. Only honor http(s) values;\n// fall through to the prod dashboard otherwise so the OAuth callback always\n// has a real origin to open the browser at.\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : \"https://app.synkro.sh\";\nconst AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join(homedir(), \".synkro\", \"credentials.json\");\nconst RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;\nconst SYNKRO_API_URL = (RAW_API_URL && /^https?:\\/\\//.test(RAW_API_URL))\n ? RAW_API_URL\n : \"https://api.synkro.sh\";\n\n// Types — matches the AuthCredentials shape returned by the dashboard's\n// /api/auth/cli-callback (see packages/app/src/pages/api/auth/cli-callback.ts).\nexport interface AuthCredentials {\n access_token: string;\n refresh_token: string;\n user_id?: string;\n email?: string;\n org_id?: string;\n state?: string;\n}\n\nexport interface UserInfo {\n id: string;\n email: string;\n org_id?: string;\n}\n\n// HTML responses\nconst SUCCESS_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Successful - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n .checkmark {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: #10b981;\n margin: 0 auto 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .checkmark svg {\n width: 50px;\n height: 50px;\n stroke: white;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n .close-note {\n margin-top: 1.5rem;\n font-size: 0.875rem;\n color: #9ca3af;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"checkmark\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\"></path>\n </svg>\n </div>\n <h1>Authentication Successful!</h1>\n <p>Your Synkro CLI has been authenticated.</p>\n <p class=\"close-note\">You can close this window and return to your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\nconst ERROR_HTML = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Failed - Synkro CLI</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Authentication Failed</h1>\n <p>Please try again from your terminal.</p>\n </div>\n</body>\n</html>\n`;\n\n/**\n * Open URL in default browser\n */\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n\n switch (os) {\n case \"darwin\":\n bin = \"open\";\n args = [url];\n break;\n case \"win32\":\n // `start` is a cmd built-in, so we host it via cmd /c, but pass the URL\n // as a literal arg through execFile (no shell parsing) to keep shell\n // metacharacters from being interpreted.\n bin = \"cmd\";\n args = [\"/c\", \"start\", \"\", url];\n break;\n default:\n bin = \"xdg-open\";\n args = [url];\n }\n\n // execFile (vs exec) does NOT spawn a shell, so url is treated as a single\n // argv entry regardless of contents. Removes shell-injection risk if url\n // ever ends up containing $/`/;/&/etc.\n execFile(bin, args, (error) => {\n if (error) {\n console.error(\"Failed to open browser automatically.\");\n console.log(`Please open this URL manually: ${url}`);\n }\n });\n}\n\n/**\n * Save authentication credentials to file\n */\nexport function saveCredentials(data: AuthCredentials): void {\n const dir = dirname(AUTH_FILE);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/**\n * Load saved authentication credentials\n */\nexport function loadCredentials(): AuthCredentials | null {\n if (!existsSync(AUTH_FILE)) {\n return null;\n }\n\n try {\n const content = readFileSync(AUTH_FILE, \"utf8\");\n return JSON.parse(content);\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Create HTTP server to receive OAuth callback.\n *\n * Mirrors packages/desktop/src-tauri/src/auth.rs:\n * - Listens on PORT\n * - Handles OPTIONS preflight (CORS)\n * - Catches /auth?token=...&refresh_token=...&user_id=...&email=...&org_id=...&state=...\n * - Returns success HTML on token receipt\n */\nfunction createCallbackServer(): Promise<AuthCredentials> {\n // Tokens land via POST body (JSON), never query params, so they don't get\n // logged into req.url, browser DevTools URL bars, server access logs, or\n // tooling that snapshots URLs. CORS origin is pinned to the configured\n // dashboard so a random page on a different origin can't post forged\n // credentials at the local listener.\n const CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": SYNKRO_WEB_AUTH_URL,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n };\n\n return new Promise((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n // CORS preflight — only echo the pinned origin if the request actually\n // comes from there; otherwise omit ACAO so the browser blocks the call.\n if (req.method === \"OPTIONS\") {\n const origin = req.headers.origin;\n if (origin === SYNKRO_WEB_AUTH_URL) {\n res.writeHead(204, CORS_HEADERS);\n } else {\n res.writeHead(204, {\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Vary\": \"Origin\",\n });\n }\n res.end();\n return;\n }\n\n // Reject requests whose Origin header isn't the dashboard. Browsers\n // send Origin on cross-origin POSTs, so this stops a hostile page from\n // forging credentials at our local listener even if it bypasses CORS.\n const reqOrigin = req.headers.origin;\n if (reqOrigin && reqOrigin !== SYNKRO_WEB_AUTH_URL) {\n res.writeHead(403, { \"Vary\": \"Origin\" });\n res.end();\n return;\n }\n\n if (!req.url) {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n const url = new URL(req.url, `http://localhost:${PORT}`);\n\n if (url.pathname !== \"/auth\") {\n res.writeHead(404, CORS_HEADERS);\n res.end();\n return;\n }\n\n if (req.method !== \"POST\") {\n // Reject GET so a stale/older dashboard build can't deliver tokens\n // via query-string. Forces upgrade. CLI 1.0.4+ requires the v1.6+\n // dashboard build.\n res.writeHead(405, { ...CORS_HEADERS, \"Allow\": \"POST, OPTIONS\", \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n return;\n }\n\n // Cap body at 16 KB — JWT pairs are ~4–8 KB; anything bigger is junk.\n const MAX_BODY = 16 * 1024;\n const chunks: Buffer[] = [];\n let total = 0;\n let aborted = false;\n req.on(\"data\", (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > MAX_BODY) {\n aborted = true;\n res.writeHead(413, CORS_HEADERS);\n res.end();\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (aborted) return;\n let parsed: any;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"invalid_json\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: invalid JSON body\"));\n }, 200);\n return;\n }\n\n const token: string | undefined = parsed?.token;\n if (!token || typeof token !== \"string\") {\n res.writeHead(400, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing_token\" }));\n setTimeout(() => {\n server.close();\n reject(new Error(\"Authentication failed: missing token\"));\n }, 200);\n return;\n }\n\n const authData: AuthCredentials = {\n access_token: token,\n refresh_token: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : \"\",\n user_id: typeof parsed.user_id === \"string\" ? parsed.user_id : undefined,\n email: typeof parsed.email === \"string\" ? parsed.email : undefined,\n org_id: typeof parsed.org_id === \"string\" ? parsed.org_id : undefined,\n state: typeof parsed.state === \"string\" ? parsed.state : undefined,\n };\n\n res.writeHead(200, { ...CORS_HEADERS, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n\n setTimeout(() => {\n server.close();\n resolve(authData);\n }, 200);\n });\n req.on(\"error\", (e) => {\n if (aborted) return;\n aborted = true;\n try { res.writeHead(500, CORS_HEADERS); res.end(); } catch {}\n setTimeout(() => {\n server.close();\n reject(e);\n }, 200);\n });\n });\n\n server.listen(PORT);\n\n server.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${PORT} is already in use. Close any other Synkro CLI instance and retry.`,\n ),\n );\n } else {\n reject(error);\n }\n });\n });\n}\n\nexport type AuthStatus =\n | { phase: 'starting' }\n | { phase: 'browser-opened'; url: string }\n | { phase: 'waiting' }\n | { phase: 'success' }\n | { phase: 'error'; message: string };\n\n/**\n * Initiate the OAuth-style authentication flow\n */\nexport async function authenticate(\n onStatus?: (status: AuthStatus) => void,\n): Promise<AuthCredentials | null> {\n const emit = onStatus || (() => {});\n\n try {\n emit({ phase: 'starting' });\n\n // Start local server to receive the callback\n const serverPromise = createCallbackServer();\n\n // Open browser to the CLI auth page\n const authUrl = `${SYNKRO_WEB_AUTH_URL}/cli-auth?port=${PORT}`;\n openBrowser(authUrl);\n\n emit({ phase: 'browser-opened', url: authUrl });\n emit({ phase: 'waiting' });\n\n // Wait for authentication callback\n const data = await serverPromise;\n\n emit({ phase: 'success' });\n saveCredentials(data);\n return data;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emit({ phase: 'error', message });\n return null;\n }\n}\n\n/**\n * Check if user is authenticated (credentials exist and token not expired)\n */\nexport function isAuthenticated(): boolean {\n const creds = loadCredentials();\n if (!creds) return false;\n\n // Also check token expiry\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true; // Can't decode, assume valid (refresh will handle it)\n\n // Consider expired if past expiration (no buffer here — ensureValidToken handles refresh buffer)\n return Date.now() < decoded.exp * 1000;\n } catch {\n return true; // Decode failed, let ensureValidToken handle it\n }\n}\n\n/**\n * Get current user ID from JWT token\n */\nexport function getCurrentUserId(): string {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded?.sub) {\n throw new Error(\"Invalid token\");\n }\n\n return decoded.sub;\n}\n\n/**\n * Get user info from JWT\n */\nexport function getUserInfo(): UserInfo {\n const creds = loadCredentials();\n if (!creds) {\n throw new Error(\"Not authenticated\");\n }\n\n // Prefer the explicit user_id/email/org_id stashed during the OAuth callback\n // (the dashboard returns them as query params). Fall back to JWT decode if\n // we somehow received older creds without those fields.\n if (creds.user_id) {\n return {\n id: creds.user_id,\n email: creds.email ?? '',\n org_id: creds.org_id,\n };\n }\n\n const decoded = jwt.decode(creds.access_token) as WorkOSJwtPayload | null;\n if (!decoded) {\n throw new Error(\"Invalid token\");\n }\n\n return {\n id: decoded.sub,\n email: decoded.email ?? '',\n org_id: decoded.org_id,\n };\n}\n\n/**\n * Get access token for API calls\n */\nexport function getAccessToken(): string | null {\n const creds = loadCredentials();\n return creds?.access_token || null;\n}\n\n/**\n * Check if token is expired (with 5 min buffer)\n */\nexport function isTokenExpired(): boolean {\n const creds = loadCredentials();\n if (!creds) return true;\n\n try {\n const decoded = jwt.decode(creds.access_token) as { exp?: number } | null;\n if (!decoded?.exp) return true;\n\n // Expired if less than 5 minutes remaining\n const expiresAt = decoded.exp * 1000;\n const buffer = 5 * 60 * 1000; // 5 minutes\n return Date.now() > expiresAt - buffer;\n } catch {\n return true;\n }\n}\n\n/**\n * Refresh the access token using refresh_token\n */\nexport async function refreshToken(): Promise<boolean> {\n const creds = loadCredentials();\n if (!creds?.refresh_token) return false;\n\n try {\n const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: creds.refresh_token }),\n });\n\n if (!response.ok) return false;\n\n const data = await response.json();\n if (data.access_token) {\n saveCredentials({\n ...creds,\n access_token: data.access_token,\n refresh_token: data.refresh_token || creds.refresh_token,\n });\n return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n// Mutex: prevent concurrent token refresh races\nlet refreshPromise: Promise<boolean> | null = null;\n\n/**\n * Ensure we have a valid token, refreshing if needed\n */\nexport async function ensureValidToken(): Promise<boolean> {\n // PRESENCE check only — do NOT gate on isAuthenticated(). isAuthenticated() reports an EXPIRED\n // token as \"not authenticated\", so gating on it here bails BEFORE the refresh that would fix the\n // expiry — the token then 401s forever and every sync silently dies until a manual `synkro login`.\n // We only need credentials that can be refreshed.\n const creds = loadCredentials();\n if (!creds?.access_token && !creds?.refresh_token) return false;\n\n if (isTokenExpired()) {\n if (!creds.refresh_token) return false; // expired and nothing to refresh with\n if (!refreshPromise) {\n refreshPromise = refreshToken().finally(() => { refreshPromise = null; });\n }\n const refreshed = await refreshPromise;\n if (!refreshed) {\n // Do NOT wipe credentials on a failed refresh. A transient network error or a brief gateway\n // blip would otherwise log the user out mid-session and silently kill every sync until a\n // manual re-login. Keep them; the next call retries. A genuinely revoked refresh_token just\n // keeps returning false and the caller surfaces it.\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Fetch a gateway endpoint with the login token, self-healing on expiry: ensure a valid token\n * first, and if the call still 401s (mid-flight expiry / clock skew), force ONE refresh and retry.\n * This is what makes long-running / repeated sync calls survive the short access-token TTL instead\n * of silently 401'ing forever (the transcript-sync failure mode).\n */\nexport async function authedFetch(url: string, init: RequestInit = {}): Promise<Response> {\n const withToken = (): RequestInit => ({\n ...init,\n headers: { ...(init.headers as Record<string, string> | undefined), Authorization: `Bearer ${getAccessToken() ?? ''}` },\n });\n await ensureValidToken();\n let res = await fetch(url, withToken());\n if (res.status === 401 && await refreshToken()) {\n res = await fetch(url, withToken());\n }\n return res;\n}\n\n/**\n * Clear saved credentials (logout)\n */\nexport function clearCredentials(): void {\n if (existsSync(AUTH_FILE)) {\n unlinkSync(AUTH_FILE);\n }\n}\n\n/**\n * Get secrets for a user's integrations\n * In production, this would fetch from Infisical vault using the access token\n *\n * These are secrets the USER provides for THEIR integrations:\n * - AWS credentials (for their S3 buckets)\n * - HuggingFace token (for their datasets)\n * - Langsmith API key (for their projects)\n */\nexport async function getSecrets(\n userId: string,\n integrationId: string,\n): Promise<Record<string, string>> {\n // TODO: In production, use access token to fetch from Infisical\n // For now, return from environment (dev mode)\n return {\n AWS_ACCESS_KEY_ID: process.env.USER_AWS_KEY || \"\",\n AWS_SECRET_ACCESS_KEY: process.env.USER_AWS_SECRET || \"\",\n AWS_REGION: process.env.USER_AWS_REGION || \"us-east-1\",\n HF_TOKEN: process.env.USER_HF_TOKEN || \"\",\n LANGSMITH_API_KEY: process.env.USER_LANGSMITH_KEY || \"\",\n };\n}\n","/**\n * Synkro CLI Authentication Module\n *\n * Exports authentication functions for use throughout the CLI.\n */\n\nexport {\n authenticate,\n isAuthenticated,\n getCurrentUserId,\n getUserInfo,\n getAccessToken,\n loadCredentials,\n saveCredentials,\n clearCredentials,\n getSecrets,\n isTokenExpired,\n refreshToken,\n ensureValidToken,\n} from './stub.js';\n\nexport type { AuthCredentials, UserInfo, AuthStatus } from './stub.js';\n","// :)\n/**\n * CLI API client for project and violation endpoints.\n *\n * Calls the Synkro API (Hono/Cloudflare Workers) for project management, violations, and policy upsert.\n * SYNKRO_CRUD_URL points to the API server (defaults to http://localhost:8788/api).\n */\n\nimport { getAccessToken, ensureValidToken } from '../auth/index.js';\n\nlet API_URL = 'https://api.synkro.sh/api';\n\nexport function setApiBaseUrl(url: string): void {\n API_URL = url;\n}\n\n// Types\n\nexport interface Project {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n api_key_count: number;\n created_at: string | null;\n repos?: Array<{ id: string; github_repo_id: number | null; full_name: string }>;\n}\n\nexport interface Violation {\n id: string | null;\n run_id: string | null;\n score: number | null;\n severity: string | null;\n issues: string[];\n rules_violated: string[];\n messages: Array<{ role: string; content: string }>;\n model: string | null;\n comment: string | null;\n created_at: string | null;\n}\n\nexport interface ViolationsResponse {\n violations: Violation[];\n total: number;\n project_id: string;\n project_slug: string | null;\n policy_text: string | null;\n rules: Array<Record<string, unknown>>;\n}\n\nexport interface GetViolationsOptions {\n limit?: number;\n offset?: number;\n severity?: string;\n}\n\nexport interface PolicyUpsertResponse {\n ok: boolean;\n policy_id: string;\n}\n\n// API helper\n\nasync function callApi<T>(\n method: string,\n endpoint: string,\n body?: Record<string, unknown>\n): Promise<T> {\n if (!API_URL) {\n throw new Error('API URL not configured. Run `synkro install` first.');\n }\n\n const url = `${API_URL}${endpoint}`;\n await ensureValidToken();\n const accessToken = getAccessToken();\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (accessToken) {\n headers['Authorization'] = `Bearer ${accessToken}`;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ detail: response.statusText }));\n throw new Error(error.detail || `API error: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport interface CreateProjectResponse {\n id: string;\n slug: string;\n name: string;\n provider: string | null;\n is_active: boolean;\n created_at: string | null;\n}\n\nexport interface CreateApiKeyResponse {\n id: string;\n key: string;\n key_prefix: string;\n name: string;\n project_id: string;\n created_at: string | null;\n}\n\n// Project API functions\n\n/**\n * Create a new project.\n */\nexport async function createProject(\n name: string,\n repos?: Array<{ github_repo_id?: number; full_name: string; default_branch?: string; private?: boolean }>,\n): Promise<CreateProjectResponse> {\n const body: Record<string, unknown> = { name };\n if (repos && repos.length > 0) body.repos = repos;\n return callApi<CreateProjectResponse>('POST', '/projects', body);\n}\n\n/**\n * Create an API key for a project. Returns plaintext key (shown once).\n */\nexport async function createApiKey(\n projectId: string,\n name?: string\n): Promise<CreateApiKeyResponse> {\n return callApi<CreateApiKeyResponse>('POST', '/api-keys', {\n project_id: projectId,\n name: name || 'CLI Key',\n });\n}\n\n/**\n * List all projects for the authenticated user.\n */\nexport async function listProjects(): Promise<Project[]> {\n return callApi<Project[]>('GET', '/projects');\n}\n\n/**\n * Get violations for a project with optional filtering.\n * Also returns the active policy text and rules.\n */\nexport async function getViolations(\n projectId: string,\n options: GetViolationsOptions = {}\n): Promise<ViolationsResponse> {\n const params = new URLSearchParams();\n if (options.limit) params.set('limit', String(options.limit));\n if (options.offset) params.set('offset', String(options.offset));\n if (options.severity) params.set('severity', options.severity);\n\n const qs = params.toString();\n const endpoint = `/projects/${projectId}/violations${qs ? `?${qs}` : ''}`;\n return callApi<ViolationsResponse>('GET', endpoint);\n}\n\n/**\n * Upsert a policy for a project (deploy rules to gateway).\n */\nexport async function upsertPolicy(\n projectId: string,\n policyText: string,\n rules: Array<Record<string, unknown>>,\n ruleCount: number\n): Promise<PolicyUpsertResponse> {\n return callApi<PolicyUpsertResponse>('POST', `/projects/${projectId}/policies`, {\n policy_text: policyText,\n rules,\n rule_count: ruleCount,\n });\n}\n\n/**\n * Update a project's configuration.\n */\nexport async function updateProject(\n projectId: string,\n updates: {\n name?: string;\n provider?: string;\n langsmith_project?: string;\n is_active?: boolean;\n }\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('PATCH', `/projects/${projectId}`, updates as Record<string, unknown>);\n}\n\n/**\n * Unlink a repo from a project.\n */\nexport async function unlinkRepo(\n projectId: string,\n repoId: string,\n): Promise<{ ok: boolean }> {\n return callApi<{ ok: boolean }>('DELETE', `/projects/${projectId}/repos/${repoId}`);\n}\n\n/**\n * Resolve a project by name or slug (fuzzy match against user's projects).\n * Returns the first matching project or null.\n */\nexport async function resolveProject(nameOrSlug: string): Promise<Project | null> {\n const projects = await listProjects();\n const query = nameOrSlug.toLowerCase();\n\n // Exact slug match\n const exactSlug = projects.find(p => p.slug === nameOrSlug);\n if (exactSlug) return exactSlug;\n\n // Exact name match (case-insensitive)\n const exactName = projects.find(p => p.name.toLowerCase() === query);\n if (exactName) return exactName;\n\n // Partial name match\n const partial = projects.find(p => p.name.toLowerCase().includes(query));\n if (partial) return partial;\n\n // Partial slug match\n const partialSlug = projects.find(p => p.slug.includes(query));\n if (partialSlug) return partialSlug;\n\n return null;\n}\n","// :)\n/**\n * GitHub Actions workflow YAML template for Synkro PR scanning.\n *\n * Customer commits this to .github/workflows/synkro.yml. Triggers on\n * pull_request open/synchronize/reopened. Runs `synkro scan-pr` which\n * spawns claude --print with their CLAUDE_CODE_OAUTH_TOKEN per file.\n *\n * Both CLIs install via npm (npm registry attestations + signed packages\n * are verified by npm itself). No curl-pipe-to-bash; we publish to npm\n * specifically so CI can install us through a trusted package manager.\n */\n\nexport const SYNKRO_WORKFLOW_YAML = `name: Synkro Security Review\non:\n pull_request:\n types: [opened, synchronize, reopened]\n workflow_dispatch:\n inputs:\n pr_number:\n description: PR number to scan\n required: true\n sha:\n description: Commit SHA to scan\n required: true\n\njobs:\n scan:\n runs-on: ubuntu-latest\n permissions:\n contents: write\n pull-requests: write\n checks: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n ref: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n\n - name: Cache npm globals\n id: cache-npm-global\n uses: actions/cache@v4\n with:\n path: ~/.npm-global\n key: synkro-cli-\\${{ runner.os }}-v1\n\n - name: Install Synkro CLI + Claude Code CLI\n run: |\n npm config set prefix ~/.npm-global\n npm install -g @synkro-sh/cli @anthropic-ai/claude-code\n echo \"$HOME/.npm-global/bin\" >> $GITHUB_PATH\n\n - name: Run Synkro PR scan\n run: synkro-cli scan-pr\n env:\n CLAUDE_CODE_OAUTH_TOKEN: \\${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}\n SYNKRO_API_KEY: \\${{ secrets.SYNKRO_API_KEY }}\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n SYNKRO_PR_NUMBER: \\${{ inputs.pr_number || github.event.pull_request.number }}\n SYNKRO_REPO: \\${{ github.repository }}\n SYNKRO_SHA: \\${{ inputs.sha || github.event.pull_request.head.sha }}\n SYNKRO_GATEWAY_URL: \\${{ vars.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh' }}\n`;\n\nexport const WORKFLOW_FILENAME = 'synkro.yml';\nexport const WORKFLOW_PATH = '.github/workflows/synkro.yml';\n","/**\n * GitHub repo setup for PR scanning.\n *\n * Uses `gh secret set` to push CLAUDE_CODE_OAUTH_TOKEN + SYNKRO_API_KEY\n * as repo secrets, then writes .github/workflows/synkro.yml to the local\n * clone if present.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { SYNKRO_WORKFLOW_YAML, WORKFLOW_PATH } from './workflowTemplate.js';\n\nexport interface GitHubAuthOptions {\n token: string;\n}\n\nfunction ghSecretSet(token: string, owner: string, repo: string, name: string, value: string): void {\n execSync(`gh secret set ${name} --repo ${owner}/${repo} --body -`, {\n input: value,\n env: { ...process.env, GH_TOKEN: token },\n stdio: ['pipe', 'ignore', 'pipe'],\n timeout: 30_000,\n });\n}\n\n/**\n * List repos the token has access to.\n */\nexport async function listAccessibleRepos(opts: GitHubAuthOptions): Promise<Array<{ owner: string; repo: string; full_name: string }>> {\n const repos: Array<{ owner: string; repo: string; full_name: string }> = [];\n let page = 1;\n while (page <= 5) { // cap pagination\n const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator`;\n const resp = await fetch(url, {\n headers: {\n Authorization: `Bearer ${opts.token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n if (!resp.ok) {\n throw new Error(`GitHub API ${resp.status} listing repos`);\n }\n const data = await resp.json() as Array<{ full_name: string; owner: { login: string }; name: string }>;\n if (data.length === 0) break;\n for (const r of data) {\n repos.push({ owner: r.owner.login, repo: r.name, full_name: r.full_name });\n }\n if (data.length < 100) break;\n page++;\n }\n return repos;\n}\n\nexport async function pushSecretsToRepo(\n opts: GitHubAuthOptions,\n owner: string,\n repo: string,\n secrets: { claudeCodeOauthToken?: string; synkroApiKey: string },\n): Promise<void> {\n try { execSync('gh --version', { stdio: 'ignore', timeout: 5000 }); } catch {\n throw new Error('GitHub CLI (gh) not found. Install it: https://cli.github.com');\n }\n if (secrets.claudeCodeOauthToken) {\n ghSecretSet(opts.token, owner, repo, 'CLAUDE_CODE_OAUTH_TOKEN', secrets.claudeCodeOauthToken);\n }\n ghSecretSet(opts.token, owner, repo, 'SYNKRO_API_KEY', secrets.synkroApiKey);\n}\n\n/**\n * Write the workflow YAML to a local repo clone (if the user is in one).\n * Returns the absolute path written, or null if cwd isn't a git repo.\n */\nexport function writeWorkflowFile(repoRootPath: string): string | null {\n const workflowDir = join(repoRootPath, '.github', 'workflows');\n mkdirSync(workflowDir, { recursive: true });\n const workflowFile = join(workflowDir, 'synkro.yml');\n writeFileSync(workflowFile, SYNKRO_WORKFLOW_YAML, 'utf-8');\n return workflowFile;\n}\n\n/**\n * Find the git repo root for a given cwd. Returns null if not in a git repo.\n */\nexport function findGitRoot(startCwd: string): string | null {\n let cur = startCwd;\n while (cur && cur !== '/') {\n if (existsSync(join(cur, '.git'))) return cur;\n const parent = join(cur, '..');\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nexport const SECRET_NAMES = {\n CLAUDE_OAUTH: 'CLAUDE_CODE_OAUTH_TOKEN',\n SYNKRO_API_KEY: 'SYNKRO_API_KEY',\n} as const;\n\nexport const WORKFLOW_RELATIVE_PATH = WORKFLOW_PATH;\n","// :)\n/**\n * Shared helpers for repo connection during install and `synkro link`.\n *\n * Two paths:\n * 1. Local git repo — detect from `git remote get-url origin`, any provider\n * 2. GitHub OAuth — browser flow, list repos, interactive picker\n */\nimport { execSync, execFileSync } from 'node:child_process';\nimport { readdirSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { createProject, listProjects } from '../api/projects.js';\nimport { listAccessibleRepos } from '../installer/githubSetup.js';\n\nconst RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;\nconst SYNKRO_WEB_AUTH_URL = (RAW_WEB_AUTH_URL && /^https?:\\/\\//.test(RAW_WEB_AUTH_URL))\n ? RAW_WEB_AUTH_URL\n : 'https://app.synkro.sh';\nconst GITHUB_PORT = 8101;\n\nfunction detectGitRepo(): { fullName: string; shortName: string } | null {\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n // Match any git host — github, gitlab, bitbucket, self-hosted, etc.\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (!match) return null;\n const fullName = match[1];\n return { fullName, shortName: fullName.split('/').pop() || fullName };\n } catch { return null; }\n}\n\nfunction detectSubdirRepos(): Array<{ fullName: string; shortName: string }> {\n try {\n const entries = readdirSync('.', { withFileTypes: true });\n const repos: Array<{ fullName: string; shortName: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n try {\n const remoteUrl = execFileSync('git', ['-C', entry.name, 'remote', 'get-url', 'origin'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remoteUrl.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const match = sshMatch || httpMatch;\n if (match) {\n const fullName = match[1];\n repos.push({ fullName, shortName: fullName.split('/').pop() || fullName });\n }\n } catch { /* not a git repo or no remote */ }\n }\n return repos;\n } catch { return []; }\n}\n\nfunction ask(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nfunction waitForGithubToken(): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n });\n res.end();\n return;\n }\n\n if (req.url !== '/auth' || req.method !== 'POST') {\n res.writeHead(404);\n res.end();\n return;\n }\n\n let body = '';\n req.on('data', (chunk) => { body += chunk; });\n req.on('end', () => {\n try {\n const parsed = JSON.parse(body);\n if (!parsed.github_token) {\n res.writeHead(400, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ error: 'missing github_token' }));\n return;\n }\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': SYNKRO_WEB_AUTH_URL,\n });\n res.end(JSON.stringify({ ok: true }));\n setTimeout(() => server.close(), 200);\n resolve(parsed.github_token);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'invalid json' }));\n }\n });\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${GITHUB_PORT} is in use. Close other processes and try again.`));\n } else {\n reject(err);\n }\n });\n\n server.listen(GITHUB_PORT);\n });\n}\n\nfunction openBrowser(url: string): void {\n const { execFile } = require('node:child_process');\n const plat = process.platform;\n const cb = (err: Error | null) => {\n if (err) console.log(` Open this URL manually: ${url}`);\n };\n if (plat === 'darwin') execFile('open', [url], cb);\n else if (plat === 'win32') execFile('cmd', ['/c', 'start', '', url], cb);\n else execFile('xdg-open', [url], cb);\n}\n\nasync function connectGithubAndSelectRepos(): Promise<Array<{ full_name: string }>> {\n const url = `${SYNKRO_WEB_AUTH_URL}/cli-github?port=${GITHUB_PORT}`;\n console.log(' Opening browser for GitHub authorization...');\n openBrowser(url);\n\n console.log(' Waiting for GitHub authorization...');\n const ghToken = await waitForGithubToken();\n console.log(' ✓ GitHub connected\\n');\n\n const repos = await listAccessibleRepos({ token: ghToken });\n if (repos.length === 0) {\n console.log(' No accessible repos found on GitHub.');\n return [];\n }\n\n console.log(` Found ${repos.length} repos:\\n`);\n repos.forEach((r: any, i: number) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const selection = await ask(rl, ' Select repos (comma-separated numbers, e.g. 1,3,5): ');\n const indices = selection\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n\n if (indices.length === 0) {\n console.log(' No repos selected.');\n return [];\n }\n\n return indices.map((i) => ({ full_name: repos[i].full_name }));\n } finally {\n rl.close();\n }\n}\n\nexport async function promptRepoConnection(opts?: { linkRepo?: boolean }): Promise<void> {\n const localRepo = detectGitRepo();\n const subdirRepos = !localRepo ? detectSubdirRepos() : [];\n\n if (opts?.linkRepo && localRepo) {\n console.log('Connect repos to Synkro:\\n');\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n console.log();\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n try {\n console.log('Connect repos to Synkro:\\n');\n\n let existingFullNames = new Set<string>();\n if (subdirRepos.length > 0) {\n try {\n const existing = await listProjects();\n existingFullNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n } catch {}\n }\n\n if (subdirRepos.length > 0) {\n console.log(` Found ${subdirRepos.length} repo(s) in this directory:\\n`);\n subdirRepos.forEach((r, i) => {\n const linked = existingFullNames.has(r.fullName);\n console.log(` ${String(i + 1).padStart(3)}. ${r.fullName}${linked ? ' ✓ linked' : ''}`);\n });\n const ghIdx = subdirRepos.length + 1;\n const skipIdx = subdirRepos.length + 2;\n console.log(` ${String(ghIdx).padStart(3)}. Connect GitHub to select repos`);\n console.log(` ${String(skipIdx).padStart(3)}. Skip for now`);\n console.log();\n\n const selection = await ask(rl, ' Select repos to link (comma-separated numbers, e.g. 1,3): ');\n const nums = selection.split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));\n console.log();\n rl.close();\n\n if (nums.includes(ghIdx)) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n const newRepos = selectedRepos.filter((r) => !existingFullNames.has(r.full_name));\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n }\n } else if (nums.includes(skipIdx) || nums.length === 0) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n const repoIndices = nums.map((n) => n - 1).filter((n) => n >= 0 && n < subdirRepos.length);\n const toLink = repoIndices.map((i) => subdirRepos[i]).filter((r) => !existingFullNames.has(r.fullName));\n if (toLink.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n for (const repo of toLink) {\n try {\n await createProject(repo.shortName, [{ full_name: repo.fullName }]);\n console.log(` ✓ Created project \"${repo.shortName}\" linked to ${repo.fullName}`);\n } catch (err) {\n console.warn(` ⚠ Could not link ${repo.fullName}: ${(err as Error).message}`);\n }\n }\n }\n }\n } else {\n const options: string[] = [];\n if (localRepo) {\n options.push(`Link this repo (${localRepo.fullName})`);\n }\n options.push('Connect GitHub to select repos');\n options.push('Skip for now');\n\n options.forEach((opt, i) => {\n console.log(` ${i + 1}. ${opt}`);\n });\n console.log();\n\n const choice = await ask(rl, ' Choose (number): ');\n const choiceNum = parseInt(choice.trim(), 10);\n console.log();\n rl.close();\n\n const localIdx = localRepo ? 1 : -1;\n const githubIdx = localRepo ? 2 : 1;\n const skipIdx = localRepo ? 3 : 2;\n\n if (choiceNum === localIdx && localRepo) {\n try {\n const existing = await listProjects();\n const alreadyLinked = existing.some((p: any) =>\n p.repos?.some((r: any) => r.full_name === localRepo.fullName),\n );\n if (!alreadyLinked) {\n await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);\n console.log(` ✓ Created project \"${localRepo.shortName}\" linked to ${localRepo.fullName}`);\n } else {\n console.log(` ✓ ${localRepo.fullName} is already linked to a Synkro project.`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repo: ${(err as Error).message}`);\n }\n } else if (choiceNum === githubIdx) {\n const selectedRepos = await connectGithubAndSelectRepos();\n if (selectedRepos.length > 0) {\n try {\n const existing = await listProjects();\n const linkedNames = new Set(\n existing.flatMap((p: any) => (p.repos || []).map((r: any) => r.full_name)),\n );\n const newRepos = selectedRepos.filter((r) => !linkedNames.has(r.full_name));\n\n if (newRepos.length === 0) {\n console.log(' ✓ All selected repos are already linked.');\n } else {\n const projectName = newRepos.length === 1\n ? newRepos[0].full_name.split('/').pop() || 'Project'\n : 'Multi-Repo Project';\n await createProject(projectName, newRepos);\n console.log(` ✓ Linked ${newRepos.length} repo(s) to project \"${projectName}\"`);\n }\n } catch (err) {\n console.warn(` ⚠ Could not link repos: ${(err as Error).message}`);\n }\n }\n } else if (choiceNum === skipIdx) {\n console.log(' Skipped. Run `synkro link` later to connect repos.');\n } else {\n console.log(' Invalid choice. Skipping repo connection.');\n }\n }\n } catch {\n rl.close();\n }\n console.log();\n}\n","/**\n * Fetch judge prompt metadata from the Synkro API.\n * Prompts are fetched live on every grade call — never cached to disk.\n * This module only fetches the version string for display during install.\n */\n\ninterface PromptsMetaResponse {\n version: string;\n}\n\nexport async function fetchJudgePrompts(opts: {\n gatewayUrl: string;\n jwt: string;\n forceRefresh?: boolean;\n}): Promise<{\n version: string;\n}> {\n const url = `${opts.gatewayUrl.replace(/\\/$/, '')}/api/v1/hook/config`;\n const resp = await fetch(url, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${opts.jwt}`,\n 'User-Agent': 'synkro-cli/1.0',\n },\n signal: AbortSignal.timeout(5000),\n });\n\n if (!resp.ok) {\n return { version: 'unknown' };\n }\n\n const data = await resp.json() as { prompts?: { version?: string } };\n return { version: data.prompts?.version ?? 'unknown' };\n}\n","/**\n * Claude Desktop observability (local) — session-scoped MITM tap.\n *\n * Wraps Anthropic's Claude Desktop in a local mitmproxy that taps its\n * `chat_conversations` traffic and POSTs each turn to the local container's\n * `/api/local/capture`, which stores it in PGLite so the conversations show up\n * in the dashboard Events page. Store-only — no blocking/grading (deferred).\n *\n * This is the LOCAL counterpart of the cloud tap embedded in\n * packages/api/src/routes/proxyTokens.ts. It captures the full per-turn\n * trajectory — user + assistant text, tool calls, tool results (incl. web\n * search), and thinking. (It omits only the cloud version's account-identity and\n * file-upload metadata, which local single-user observability doesn't need.)\n * KEEP THE PARSING IN SYNC with proxyTokens.ts when Claude Desktop's internal\n * API changes — that's the single biggest drift risk.\n *\n * macOS only (keychain trust + Claude.app + osascript). Invoked from\n * `synkro install` behind an explicit consent prompt.\n *\n * Security: capture URL + auth token are passed via ENV VARS (never interpolated\n * into the addon source) so config can't become executable Python (CWE-94). The\n * addon is fully STATELESS — each capture is built from one conversation GET\n * response, with no globals/class attributes shared across flows (CWE-362,\n * CWE-200). For local single-user use the captured turn carries no extracted\n * account identity; the server attributes it to the local identity.\n */\nimport { spawn } from 'node:child_process';\nimport { writeFileSync, mkdtempSync, mkdirSync, readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { installClaudeDesktopMcpConfig } from './mcpConfig.js';\n\n// The container's local REST port (matches LOCAL_API_BASE in the dashboard's\n// local-fetch.ts). The capture route lives behind the SERVER_TOKEN Bearer gate.\nconst CAPTURE_URL = 'http://127.0.0.1:18931/api/local/capture';\nconst SCAN_URL = 'http://127.0.0.1:18931/api/local/scan-upload';\nconst SCAN_TURN_URL = 'http://127.0.0.1:18931/api/local/scan-turn';\nconst DLP_POLICY_URL = 'http://127.0.0.1:18931/api/local/dlp-policy';\nconst TURN_VERDICTS_URL = 'http://127.0.0.1:18931/api/local/turn-verdicts';\nconst TURN_VERDICT_URL = 'http://127.0.0.1:18931/api/local/turn-verdict';\nconst MCP_EVENT_URL = 'http://127.0.0.1:18931/api/local/mcp/event';\nconst JWT_PATH = join(homedir(), '.synkro', '.mcp-jwt');\n\n/** Is Claude Desktop installed? (cheap check for the common install path) */\nexport function claudeDesktopInstalled(): boolean {\n return process.platform === 'darwin'\n && existsSync('/Applications/Claude.app/Contents/MacOS/Claude');\n}\n\n// The mitmproxy addon — STATIC, STATELESS Python source. Config (capture URL +\n// token) is read from the environment at runtime, NOT interpolated in (CWE-94).\n// No globals or class attributes persist across flows: each capture is built\n// entirely from one conversation GET response (CWE-362, CWE-200).\nconst ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib\nfrom mitmproxy import http\n\ndef _safe_url(u):\n # CWE-918: these URLs come from the environment the installer sets — accept ONLY\n # the loopback container endpoints, never an arbitrary host from a tampered env.\n return u if (u.startswith(\"http://127.0.0.1:\") or u.startswith(\"http://localhost:\")) else \"\"\n\nCAPTURE_URL = _safe_url(os.environ.get(\"SYNKRO_CAPTURE_URL\", \"\"))\nTOKEN = os.environ.get(\"SYNKRO_TAP_TOKEN\", \"\")\nSCAN_URL = _safe_url(os.environ.get(\"SYNKRO_SCAN_URL\", \"\"))\nSCAN_TURN_URL = _safe_url(os.environ.get(\"SYNKRO_SCAN_TURN_URL\", \"\"))\nPOLICY_URL = _safe_url(os.environ.get(\"SYNKRO_DLP_POLICY_URL\", \"\"))\nTURN_VERDICTS_URL = _safe_url(os.environ.get(\"SYNKRO_TURN_VERDICTS_URL\", \"\"))\nTURN_VERDICT_URL = _safe_url(os.environ.get(\"SYNKRO_TURN_VERDICT_URL\", \"\"))\nMCP_EVENT_URL = _safe_url(os.environ.get(\"SYNKRO_MCP_EVENT_URL\", \"\"))\n# History backfill is OPT-IN: only when the user runs synkro claude-desktop with\n# --backfill. Otherwise importing past conversations on every tap launch is wasted\n# work (and re-floods the feed). When set, it also bypasses the 12h throttle marker.\nBACKFILL_ENABLED = os.environ.get(\"SYNKRO_CD_BACKFILL\", \"\") == \"1\"\n# Token file path — the SERVER_TOKEN rotates whenever the container is recreated,\n# so we re-read it from disk on a 401 instead of needing a tap restart.\nTOKEN_FILE = os.environ.get(\"SYNKRO_TAP_TOKEN_FILE\", \"\")\n_token_lock = threading.Lock()\n\ndef _reload_token():\n global TOKEN\n if not TOKEN_FILE: return\n try:\n with open(TOKEN_FILE) as f:\n t = f.read().strip()\n if t:\n with _token_lock: TOKEN = t\n print(\"[synkro] auth token reloaded after 401 (container likely recreated)\")\n except Exception:\n pass\n\ndef _post_json(url, payload_bytes, timeout):\n # POST JSON with auto-reauth: on a 401 (token rotated by a container recreate),\n # re-read the token file and retry ONCE. CWE-362: TOKEN is always snapshotted\n # under the lock before use, never read bare.\n for attempt in range(2):\n with _token_lock: tok = TOKEN\n try:\n req = urllib.request.Request(url, data=payload_bytes, headers={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer \" + tok})\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n except urllib.error.HTTPError as e:\n if e.code == 401 and attempt == 0:\n _reload_token(); continue\n raise\n return None\n\n# Governance signals parsed from the completion SSE (usage window + web-search\n# sources), keyed by conversation id. The completion POST always precedes the\n# tree GET for the same turn, so the tree capture below reads + clears this.\n# Guarded by a lock — tap callbacks can run concurrently across flows (CWE-362).\n_sse_meta = {}\n_sse_lock = threading.Lock()\n# Session-global MCP server registry (uuid -> {name,url,connected,tools}), parsed\n# from the mcp/v2/bootstrap SSE. All access guarded by _mcp_lock (CWE-362).\n_mcp_registry = {}\n_mcp_lock = threading.Lock()\n\ndef _fmt_tool_name(raw):\n # Claude Desktop names connector tool_use blocks as \"<ServerName><tool>\" with\n # no delimiter (e.g. \"Linearlist_issues\"). Split it back to \"Server:tool\" using\n # the captured MCP registry so the dashboard can resolve the server favicon +\n # render a clean label. Exact server+tool match only — never a fuzzy guess.\n if not raw or \":\" in raw: return raw\n try:\n with _mcp_lock:\n servers = list(_mcp_registry.values())\n for s in servers:\n sname = s.get(\"name\", \"\")\n if not sname or not raw.startswith(sname): continue\n rest = raw[len(sname):]\n for t in s.get(\"tools\", []):\n tn = t.get(\"name\", \"\")\n if tn and rest == tn:\n return sname + \":\" + tn\n except Exception:\n pass\n return raw\n\ndef _map_full_conversation(all_msgs):\n # Full conversation [{role, content, tool_calls?}] + 'tool' result messages, in\n # order, for the session-log tables (the Session Timeline card on the event page).\n # Lighter than the per-turn capture: role + text + tool_calls + tool results.\n out = []\n for msg in all_msgs:\n sender = msg.get(\"sender\", \"\")\n role = \"user\" if sender == \"human\" else \"assistant\"\n blocks = msg.get(\"content\", [])\n texts = []; tcs = []; results = []\n if isinstance(blocks, list):\n for b in blocks:\n if isinstance(b, str): texts.append(b); continue\n if not isinstance(b, dict): continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n texts.append(b.get(\"text\", \"\"))\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n tcs.append({\"name\": _fmt_tool_name(b.get(\"name\", \"\")), \"id\": b.get(\"id\", \"\"), \"arguments\": json.dumps(b.get(\"input\", {}))[:2000]})\n elif bt.endswith(\"_tool_result\") or bt == \"tool_result\":\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n rc = \" | \".join(str(x.get(\"text\") or x.get(\"content\") or \"\") for x in rc if isinstance(x, dict))\n if rc and str(rc).strip():\n results.append({\"role\": \"tool\", \"content\": str(rc)[:8000], \"name\": _fmt_tool_name(b.get(\"name\", \"\"))})\n elif isinstance(blocks, str):\n texts.append(blocks)\n content = \" \".join(t for t in texts if t).strip()\n if content or tcs:\n e = {\"role\": role, \"content\": content}\n if tcs: e[\"tool_calls\"] = tcs\n out.append(e)\n for r in results: out.append(r)\n if len(out) >= 2000: break\n return out\n\ndef _emit_mcp_event(server, tool, ev_type, eid, convo_id=\"\"):\n # Post a Claude Desktop connector (MCP) lifecycle/tool_call event to the same\n # mcp_events store the CC/Cursor mcp-gate writes, so Desktop connectors show on\n # the MCP Servers page. eid is deterministic so re-captures dedupe (server\n # ON CONFLICT DO NOTHING). Fire-and-forget; never blocks the proxied request.\n if not (MCP_EVENT_URL and TOKEN and server): return\n try:\n payload = json.dumps({\n \"event_id\": eid,\n \"session_id\": (str(convo_id)[:80] or None),\n \"server_name\": str(server)[:120],\n \"event_type\": ev_type,\n \"tool_name\": (str(tool)[:120] if tool else None),\n \"decision\": (\"allowed\" if ev_type == \"tool_call\" else None),\n \"repo\": \"claude-desktop\",\n }).encode(\"utf-8\")\n _post_json(MCP_EVENT_URL, payload, 5)\n except Exception:\n pass\n\n# ── History backfill ──────────────────────────────────────────────────────────\n# One-time pull of the user's past claude.ai conversations into the local store, so\n# the dashboard shows full Desktop history (like CC's transcript import) — not just\n# turns tapped live. Auth (session cookie + org uuid) is captured from a live request\n# and kept IN MEMORY (never written to disk). Store-only: backfilled turns are not\n# graded (backfill=True). Runs at most once per 12h via a timestamp marker.\n#\n# SSRF (CWE-918): the only host we ever fetch is claude.ai, and org/conversation ids\n# must be UUID-shaped — both validated before any URL is built, so no untrusted value\n# can redirect the fetch to an arbitrary host/path.\n_backfill_lock = threading.Lock()\n_backfill_started = False\n_BACKFILL_MARKER = os.path.expanduser(\"~/.synkro/.cd-backfill-done\")\n\ndef _is_claude_host(h):\n return h == \"claude.ai\" or (isinstance(h, str) and h.endswith(\".claude.ai\"))\n\ndef _is_uuidish(s):\n return bool(re.match(r\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\", s or \"\"))\n\ndef _http_get(url, headers, timeout=25):\n req = urllib.request.Request(url, headers=headers, method=\"GET\")\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n\ndef _post_backfill_convo(convo, cid):\n all_msgs = convo.get(\"chat_messages\", [])\n if not all_msgs: return\n model = convo.get(\"model\") or \"claude-unknown\"\n if not model.startswith(\"claude-\"): model = \"claude-\" + model\n session_messages = _map_full_conversation(all_msgs)\n if not session_messages: return\n msgs = []\n for e in session_messages:\n mm = {\"role\": e.get(\"role\", \"user\"), \"content\": e.get(\"content\", \"\")}\n if e.get(\"tool_calls\"): mm[\"tool_calls\"] = e[\"tool_calls\"]\n msgs.append(mm)\n # Real conversation timestamp (last activity, fallback created) so the server\n # stamps created_at to when the convo HAPPENED — not \"now\". Without this every\n # backfill run re-stamps all history to the present and floods it to the top of\n # the feed. Must be ISO8601; the server re-validates and only honors it for\n # backfill rows.\n cts = \"\"\n for k in (\"updated_at\", \"created_at\"):\n v = convo.get(k) if isinstance(convo, dict) else None\n if isinstance(v, str) and re.match(r\"^\\d{4}-\\d{2}-\\d{2}T\", v):\n cts = v; break\n data = {\"request\": {\"model\": model, \"messages\": msgs[-200:]}, \"conversation_id\": cid,\n \"session_messages\": session_messages, \"turn_id\": \"cdbf_\" + cid, \"backfill\": True}\n if cts: data[\"event_ts\"] = cts\n _post_json(CAPTURE_URL, json.dumps(data).encode(\"utf-8\"), 8)\n\ndef _backfill(host, org, cookie):\n if not (_is_claude_host(host) and _is_uuidish(org)): return # defence in depth\n base = \"https://\" + host\n headers = {\"Cookie\": cookie, \"Accept\": \"application/json\", \"User-Agent\": \"synkro-tap\"}\n try:\n convos = json.loads(_http_get(base + \"/api/organizations/\" + org + \"/chat_conversations?limit=200\", headers, 25))\n if not isinstance(convos, list): return\n except Exception:\n with _backfill_lock:\n globals()[\"_backfill_started\"] = False # allow a later request to retry\n return\n print(\"[synkro] backfill: importing up to \" + str(min(len(convos), 200)) + \" conversations\")\n n = 0\n for c in convos[:200]:\n cid = c.get(\"uuid\", \"\") if isinstance(c, dict) else \"\"\n if not _is_uuidish(cid): continue\n try:\n convo = json.loads(_http_get(base + \"/api/organizations/\" + org + \"/chat_conversations/\" + cid + \"?tree=True&rendering_mode=messages&render_all_tools=true\", headers, 25))\n _post_backfill_convo(convo, cid)\n n += 1\n except Exception:\n pass\n time.sleep(0.6)\n try: open(_BACKFILL_MARKER, \"w\").write(str(int(time.time())))\n except Exception: pass\n print(\"[synkro] backfill done: \" + str(n) + \" conversations imported\")\n\ndef _maybe_backfill(host, org, cookie):\n # Opt-in only: skip entirely unless the user launched with --backfill.\n if not BACKFILL_ENABLED: return\n if not (_is_claude_host(host) and _is_uuidish(org) and cookie and CAPTURE_URL and TOKEN): return\n with _backfill_lock:\n global _backfill_started\n if _backfill_started: return\n _backfill_started = True\n threading.Thread(target=_backfill, args=(host, org, cookie), daemon=True).start()\n\n# ── M3 live-hold state ────────────────────────────────────────────────────────\n# Whether to HOLD connector turns (buffer the live completion until graded).\n# Fetched once from the server: True only when a BLOCK-mode rule exists (+ not\n# silent). Audit-only orgs get False → stream + async audit, ZERO added latency.\n_dlp_hold = None\n_dlp_lock = threading.Lock()\n# Shown live in place of a held connector turn while the single grader (the tree\n# GET) runs; replaced by the real content (if clean) or the block notice.\nREVIEWING_NOTICE = \"\\\\U0001F6E1\\\\uFE0F Synkro is reviewing this response for policy compliance\\\\u2026\"\n\ndef _should_hold():\n global _dlp_hold\n with _dlp_lock:\n if _dlp_hold is not None:\n return _dlp_hold\n val = False\n if POLICY_URL:\n try:\n with urllib.request.urlopen(urllib.request.Request(POLICY_URL), timeout=4) as resp:\n val = bool(json.loads(resp.read().decode(\"utf-8\", \"replace\")).get(\"hold\"))\n except Exception:\n val = False\n with _dlp_lock:\n _dlp_hold = val\n return val\n\ndef _scan_turn_call(cid, msgs, connector):\n # Synchronous DLP grade. Returns the block reason (str) on BLOCK, else None.\n # Fail-OPEN — a scan error never blocks the turn.\n if not (SCAN_TURN_URL and TOKEN): return None\n try:\n vb = _post_json(SCAN_TURN_URL, json.dumps({\"conversation_id\": str(cid)[:80], \"messages\": msgs, \"connector\": connector}).encode(), 18)\n vd = json.loads((vb or b\"{}\").decode(\"utf-8\", \"replace\"))\n if vd.get(\"block\"):\n return str(vd.get(\"reason\", \"restricted data\"))[:300]\n except Exception as e:\n print(\"[synkro] turn scan error (fail-open): \" + str(e))\n return None\n\ndef _scan_sse_line(line, st):\n # Extract governance metadata (usage window + web-search sources/citations)\n # from one completion SSE line. Shared by the streaming + buffered paths.\n if st[\"usage\"] is None:\n um = re.search(r'\"5h\":\\\\{\"status\":\"([^\"]*)\",\"resets_at\":(\\\\d+),\"utilization\":([0-9.]+)', line)\n if um:\n st[\"usage\"] = {\"window\": \"5h\", \"status\": um.group(1), \"resets_at\": int(um.group(2)), \"utilization\": float(um.group(3))}\n if len(st[\"sources\"]) < 50:\n for sm in re.finditer(r'\"webpage_metadata\",\"site_domain\":\"([^\"]*)\"(?:,\"favicon_url\":\"[^\"]*\")?,\"site_name\":\"([^\"]*)\"', line):\n d = sm.group(1)\n if d and d not in st[\"seen\"] and len(st[\"sources\"]) < 50:\n st[\"seen\"].add(d)\n st[\"sources\"].append({\"domain\": d, \"name\": sm.group(2)})\n if \"citation_start_delta\" in line and line.startswith(\"data: \") and len(st[\"citations\"]) < 50:\n try:\n cit = json.loads(line[6:]).get(\"delta\", {}).get(\"citation\", {})\n url = cit.get(\"url\", \"\")\n if url and url not in st[\"cseen\"]:\n st[\"cseen\"].add(url)\n md = cit.get(\"metadata\") or {}\n st[\"citations\"].append({\"title\": cit.get(\"title\", \"\"), \"url\": url, \"domain\": md.get(\"site_domain\", \"\")})\n except Exception:\n pass\n\ndef _new_scan_state():\n return {\"carry\": \"\", \"usage\": None, \"sources\": [], \"seen\": set(), \"citations\": [], \"cseen\": set()}\n\ndef _extract_sse_text(body):\n # Pull all visible text out of a buffered completion SSE (assistant text +\n # tool_result content) for grading, and detect whether a tool/connector ran.\n parts = []\n tool = (\"tool_use\" in body) or (\"tool_result\" in body)\n total = 0\n for line in body.split(\"\\\\n\"):\n if total > 2000000: break\n if not line.startswith(\"data: \"): continue\n try: obj = json.loads(line[6:])\n except Exception: continue\n d = obj.get(\"delta\")\n if isinstance(d, dict) and isinstance(d.get(\"text\"), str):\n parts.append(d[\"text\"]); total += len(d[\"text\"])\n cb = obj.get(\"content_block\")\n if isinstance(cb, dict):\n if isinstance(cb.get(\"text\"), str): parts.append(cb[\"text\"]); total += len(cb[\"text\"])\n c = cb.get(\"content\")\n if isinstance(c, str): parts.append(c); total += len(c)\n elif isinstance(c, list):\n for x in c:\n if isinstance(x, dict) and isinstance(x.get(\"text\"), str): parts.append(x[\"text\"]); total += len(x[\"text\"])\n return (\"\".join(parts)[:2000000], tool)\n\ndef _neutralize_body(body, notice):\n # Buffered BLOCK render of a COMPLETE response: replace the answer with the notice\n # (first text delta, rest blanked), blank text/content content_blocks (incl. the\n # retrieved doc), leave tool_use + thinking + every structural event intact (the\n # tree GET scrubs thinking durably). Rewriting a WHOLE buffered body — not the\n # live stream — is what avoids the \"couldn't finish\" fragility.\n out = []\n injected = [False]\n for line in body.split(\"\\\\n\"):\n if not line.startswith(\"data: \"):\n out.append(line); continue\n try: obj = json.loads(line[6:])\n except Exception:\n out.append(line); continue\n changed = False\n d = obj.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n d[\"text\"] = (notice if not injected[0] else \"\")\n injected[0] = True; changed = True\n cb = obj.get(\"content_block\")\n if isinstance(cb, dict):\n if isinstance(cb.get(\"text\"), str) and cb[\"text\"]:\n cb[\"text\"] = \"\"; changed = True\n if cb.get(\"content\"):\n cb[\"content\"] = \"\"; changed = True\n out.append((\"data: \" + json.dumps(obj)) if changed else line)\n return \"\\\\n\".join(out)\n\n# Per-conversation DLP verdict cached by the held completion so the tree GET reuses\n# it (durable scrub) instead of re-grading. Bounded (CWE-400), lock-guarded (CWE-362).\n_turn_verdicts = {}\n_tv_lock = threading.Lock()\n\ndef _cache_turn_verdict(cid, verdict):\n if not cid: return\n with _tv_lock:\n if len(_turn_verdicts) > 200: _turn_verdicts.clear()\n _turn_verdicts[cid] = verdict\n\ndef _pop_turn_verdict(cid):\n with _tv_lock:\n return _turn_verdicts.pop(cid, None)\n\n# ── Per-turn durable verdict helpers (PGLite-backed via local endpoints) ──────\ndef _msg_blocks(msg):\n b = msg.get(\"content\", [])\n return b if isinstance(b, list) else []\n\ndef _msg_key(msg):\n # Stable per-turn id. Prefer the message uuid; fall back to a content hash so the\n # verdict still keys consistently across reopens even if uuid is ever absent.\n u = msg.get(\"uuid\") or msg.get(\"id\")\n if isinstance(u, str) and u:\n return u\n try:\n raw = json.dumps(msg.get(\"content\", \"\"), sort_keys=True).encode(\"utf-8\", \"replace\")\n return \"h:\" + hashlib.sha256(raw).hexdigest()[:40]\n except Exception:\n return \"\"\n\ndef _msg_is_connector(msg):\n # A turn that pulled content via a connector → has a tool_result block (the doc).\n for b in _msg_blocks(msg):\n if isinstance(b, dict) and (b.get(\"type\", \"\") == \"tool_result\" or b.get(\"type\", \"\").endswith(\"_tool_result\")):\n return True\n return False\n\ndef _msg_grade_payload(msg):\n # Extract (assistant_text, tool_result_text, connector) from a message's blocks.\n texts = []; tools = []; connector = \"\"\n for b in _msg_blocks(msg):\n if isinstance(b, str):\n texts.append(b); continue\n if not isinstance(b, dict):\n continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n texts.append(b.get(\"text\", \"\") or \"\")\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n nm = b.get(\"name\", \"\") or \"\"\n if nm and not connector:\n connector = nm.split(\":\")[0] if \":\" in nm else nm\n elif bt == \"tool_result\" or bt.endswith(\"_tool_result\"):\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n for x in rc:\n if isinstance(x, dict):\n if isinstance(x.get(\"text\"), str): tools.append(x[\"text\"])\n elif x.get(\"content\"): tools.append(str(x.get(\"content\")))\n elif isinstance(rc, str):\n tools.append(rc)\n ic = b.get(\"integration_name\") or b.get(\"name\") or \"\"\n if ic and not connector:\n connector = ic\n return (\"\\\\n\".join(texts), \"\\\\n\".join(tools), connector)\n\ndef _get_turn_verdicts(uuids):\n # Batch lookup of durable verdicts from PGLite (one round-trip). {uuid: {blocked,reason}}.\n if not (TURN_VERDICTS_URL and TOKEN and uuids):\n return {}\n try:\n resp = _post_json(TURN_VERDICTS_URL, json.dumps({\"uuids\": uuids[:500]}).encode(\"utf-8\"), 8)\n if resp:\n o = json.loads(resp.decode(\"utf-8\", \"replace\"))\n v = o.get(\"verdicts\")\n return v if isinstance(v, dict) else {}\n except Exception as e:\n print(\"[synkro] verdict lookup error: \" + str(e))\n return {}\n\ndef _put_turn_verdict(uuid, convo_id, blocked, reason):\n # Persist one graded turn (fire-and-forget, off the tree-GET critical path).\n if not (TURN_VERDICT_URL and TOKEN and uuid):\n return\n def _send():\n try:\n _post_json(TURN_VERDICT_URL, json.dumps({\"message_uuid\": uuid, \"convo_id\": convo_id, \"blocked\": bool(blocked), \"reason\": reason or \"\"}).encode(\"utf-8\"), 8)\n except Exception as e:\n print(\"[synkro] verdict persist error: \" + str(e))\n threading.Thread(target=_send, daemon=True).start()\n\ndef _intent_before(all_msgs, idx):\n # Most recent human message text before idx — the turn's user intent for grading.\n for j in range(idx - 1, -1, -1):\n if all_msgs[j].get(\"sender\") == \"human\":\n for b in _msg_blocks(all_msgs[j]):\n if isinstance(b, dict) and b.get(\"type\") == \"text\": return (b.get(\"text\") or \"\")[:2000]\n if isinstance(b, str): return b[:2000]\n return \"\"\n return \"\"\n\ndef _apply_turn_verdicts(all_msgs, convo_id):\n # WHOLE-CONVERSATION per-turn DLP scrub. Mutates all_msgs in place: replaces every\n # BLOCKED assistant turn's content with the notice. Returns True if anything was\n # blocked. Verdicts are durable (PGLite via the local endpoints); each connector\n # turn (a tool_result is present) is graded once on a miss then persisted; pure-chat\n # turns are skipped. Scrubs EVERY blocked turn — not just the latest — so a follow-up\n # can never resurrect an earlier blocked turn. Fail-OPEN.\n asst_idx = [i for i in range(len(all_msgs)) if all_msgs[i].get(\"sender\") != \"human\"]\n keys = {i: _msg_key(all_msgs[i]) for i in asst_idx}\n ids = [k for k in keys.values() if k]\n verdicts = _get_turn_verdicts(ids) if ids else {}\n do_grade = _should_hold() and SCAN_TURN_URL and TOKEN\n any_blocked = False\n for i in asst_idx:\n key = keys.get(i, \"\")\n v = verdicts.get(key) if key else None\n if v is None and do_grade and key and _msg_is_connector(all_msgs[i]):\n text, tools, connector = _msg_grade_payload(all_msgs[i])\n if tools.strip() or text.strip():\n scan_msgs = [\n {\"role\": \"user\", \"content\": _intent_before(all_msgs, i), \"name\": \"\"},\n {\"role\": \"tool\", \"content\": tools[:500000], \"name\": connector[:200]},\n {\"role\": \"assistant\", \"content\": text[:8000], \"name\": \"\"},\n ]\n reason = _scan_turn_call(convo_id, scan_msgs, connector)\n v = {\"blocked\": reason is not None, \"reason\": reason or \"\"}\n _put_turn_verdict(key, convo_id, v[\"blocked\"], v[\"reason\"])\n if v and v.get(\"blocked\"):\n all_msgs[i][\"content\"] = [{\"type\": \"text\", \"text\": \"[Blocked by Synkro] \" + (v.get(\"reason\") or \"restricted data\")}]\n any_blocked = True\n return any_blocked\n\ndef _feed_answer(chunk, st):\n # Accumulate the assistant's ANSWER text (text_delta) as the held stream flows,\n # using a carry for SSE lines split across chunks. The answer reveals the doc's\n # classification, so once enough has arrived we grade it for an early verdict.\n txt = st[\"carry\"] + chunk\n lines = txt.split(\"\\\\n\")\n st[\"carry\"] = lines[-1][-100000:] # bound carried partial line (CWE-400)\n for ln in lines[:-1]:\n if not ln.startswith(\"data: \"):\n continue\n try:\n obj = json.loads(ln[6:])\n except Exception:\n continue\n d = obj.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n if len(st[\"answer\"]) < 200000: # bound (CWE-400)\n st[\"answer\"] += d[\"text\"]\n\ndef _grade_worker(st, ans):\n # Runs in a BACKGROUND THREAD (off mitmproxy's event loop). The urllib grade\n # releases the GIL during I/O, so the loop keeps flushing keep-alive pings.\n # CWE-362: ans is a snapshot PASSED BY the loop thread (the sole writer of\n # st[\"answer\"]) at thread-creation — the worker never reads shared mutable\n # st[\"answer\"], so there is no data race. It writes only verdict/graded, which the\n # loop reads AFTER graded.is_set() provides the happens-before barrier.\n try:\n ans = ans[:200000]\n reason = _scan_turn_call(st[\"cid\"], [{\"role\": \"assistant\", \"content\": ans, \"name\": \"\"}], \"\") if ans.strip() else None\n except Exception:\n reason = None\n st[\"verdict\"] = {\"block\": reason is not None, \"reason\": reason or \"\"}\n _cache_turn_verdict(st[\"cid\"], st[\"verdict\"])\n st[\"graded\"].set()\n\ndef _classify_frame(fr):\n # Inspect one CRLF SSE frame's data line → 'tool_use_start' | 'text_start' | 'other'\n # based on the content_block_start type. Used to find where the FINAL answer begins.\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n o = json.loads(ln[6:])\n except Exception:\n return \"other\"\n if o.get(\"type\") == \"content_block_start\":\n cb = o.get(\"content_block\")\n if isinstance(cb, dict):\n t = cb.get(\"type\")\n if t == \"tool_use\":\n return \"tool_use_start\"\n if t == \"text\":\n return \"text_start\"\n return \"other\"\n return \"other\"\n\ndef _frame_answer_text(fr, st):\n # Accumulate text_delta text from a HELD frame into st[\"answer\"] (for the grade).\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n d = json.loads(ln[6:]).get(\"delta\")\n except Exception:\n continue\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n if len(st[\"answer\"]) < 200000: # CWE-400 bound\n st[\"answer\"] += d[\"text\"]\n\ndef _block_held(frames, notice):\n # Frame-aware, CRLF-PRESERVING block render of the held final-answer frames: replace\n # the answer's text_delta(s) (first→notice, rest→\"\") and leave every structural frame\n # (content_block_stop / message_delta / message_stop) intact so the message still\n # finalizes cleanly. Operates per-frame on \\\\r\\\\n lines → no \\\\n/\\\\r\\\\n mixing, so the\n # framing stays byte-clean (the thing _neutralize_body's \\\\n split would have broken).\n inj = [False]\n out = []\n for fr in frames:\n nl = []\n for ln in fr.split(\"\\\\r\\\\n\"):\n if ln.startswith(\"data: \"):\n try:\n o = json.loads(ln[6:])\n d = o.get(\"delta\")\n if isinstance(d, dict) and d.get(\"type\") == \"text_delta\" and isinstance(d.get(\"text\"), str):\n o[\"delta\"][\"text\"] = notice if not inj[0] else \"\"\n inj[0] = True\n ln = \"data: \" + json.dumps(o)\n except Exception:\n pass\n nl.append(ln)\n out.append(\"\\\\r\\\\n\".join(nl))\n return (\"\\\\r\\\\n\\\\r\\\\n\".join(out) + \"\\\\r\\\\n\\\\r\\\\n\") if out else \"\"\n\ndef _make_hold_stream(cid):\n # Pass-through-until-FINAL-ANSWER hold for the claude.ai completion (CRLF SSE; frames\n # split by \\\\r\\\\n\\\\r\\\\n). A connector turn is: thinking → text(preamble) → tool_use →\n # tool_result → … → text(ANSWER). Only the FINAL answer is in the user's face, so we\n # STREAM every earlier frame VERBATIM (natural thinking/search activity + tool chips,\n # zero mutation) and HOLD only the final answer — the text block that opens AFTER a\n # tool_use — through message_stop. Buffer it (no leak), grade off-thread, heartbeat to\n # keep Desktop alive, then emit the real answer (clean) or a frame-aware block rewrite\n # (restricted). CWE-362: the grade thread gets an immutable answer snapshot; the\n # verdict returns via the Event barrier; the loop is the sole writer of held/answer.\n st = {\"cid\": cid, \"carry\": \"\", \"holding\": False, \"seen_tool\": False,\n \"held\": [], \"held_size\": 0, \"answer\": \"\", \"started\": False, \"meta\": _new_scan_state(),\n \"graded\": threading.Event(), \"verdict\": {\"block\": False, \"reason\": \"\"}}\n def _hold(data, _st=st):\n if data:\n txt = _st[\"carry\"] + data.decode(\"utf-8\", \"replace\")\n frames = txt.split(\"\\\\r\\\\n\\\\r\\\\n\")\n _st[\"carry\"] = frames[-1][-200000:] # CWE-400 bounded carry (last partial frame)\n out = []\n for fr in frames[:-1]:\n for ln in fr.split(\"\\\\r\\\\n\"):\n _scan_sse_line(ln, _st[\"meta\"]) # governance metadata, all frames\n if not _st[\"holding\"]:\n k = _classify_frame(fr)\n if k == \"tool_use_start\":\n _st[\"seen_tool\"] = True\n if k == \"text_start\" and _st[\"seen_tool\"]:\n _st[\"holding\"] = True # the FINAL answer begins here → hold from now\n else:\n out.append(fr) # thinking / preamble / tool_use / tool_result → verbatim\n continue\n if _st[\"held_size\"] < 8000000: # CWE-400 bound before append\n _st[\"held\"].append(fr); _st[\"held_size\"] += len(fr)\n _frame_answer_text(fr, _st)\n if _st[\"holding\"] and not _st[\"started\"] and len(_st[\"answer\"]) >= 1200:\n _st[\"started\"] = True\n threading.Thread(target=_grade_worker, args=(_st, _st[\"answer\"][:200000]), daemon=True).start()\n if out:\n return (\"\\\\r\\\\n\\\\r\\\\n\".join(out) + \"\\\\r\\\\n\\\\r\\\\n\").encode(\"utf-8\") # stream activity verbatim\n return b\": synkro\\\\n\\\\n\" # answer held → heartbeat (keeps Desktop alive, PROVEN)\n # upstream ended\n m = _st[\"meta\"]\n if (m[\"usage\"] or m[\"sources\"] or m[\"citations\"]) and _st[\"cid\"]:\n with _sse_lock:\n _sse_meta[_st[\"cid\"]] = {\"usage\": m[\"usage\"], \"sources\": m[\"sources\"], \"citations\": m[\"citations\"]}\n if not _st[\"holding\"]: # no final-answer block detected — everything already streamed\n return _st[\"carry\"].encode(\"utf-8\") if _st[\"carry\"] else b\"\"\n if _st[\"carry\"]:\n _st[\"held\"].append(_st[\"carry\"]); _frame_answer_text(_st[\"carry\"], _st); _st[\"carry\"] = \"\"\n if not _st[\"started\"] and _st[\"answer\"].strip(): # short answer never hit the 1200 trigger\n _st[\"started\"] = True\n threading.Thread(target=_grade_worker, args=(_st, _st[\"answer\"][:200000]), daemon=True).start()\n held_frames = list(_st[\"held\"])\n def _grade_window(_st2=_st, _frames=held_frames):\n waited = 0.0\n while not _st2[\"graded\"].is_set() and waited < 60.0:\n yield b\": synkro\\\\n\\\\n\" # heartbeat WHILE grading — keeps Desktop alive (PROVEN)\n time.sleep(0.5)\n waited += 0.5\n v = _st2[\"verdict\"]\n if _st2[\"graded\"].is_set() and v[\"block\"]:\n print(\"[synkro] HELD+BLOCKED live (reason=\" + v[\"reason\"][:60] + \")\")\n yield _block_held(_frames, \"[Blocked by Synkro] \" + v[\"reason\"]).encode(\"utf-8\")\n elif _st2[\"graded\"].is_set():\n yield (\"\\\\r\\\\n\\\\r\\\\n\".join(_frames) + \"\\\\r\\\\n\\\\r\\\\n\").encode(\"utf-8\") # clean → real answer\n else: # grade never landed in 60s — never leak; tree GET backstop finalizes\n print(\"[synkro] hold: grade timeout → placeholder + tree-GET backstop\")\n yield _block_held(_frames, \"[Synkro is reviewing this response…]\").encode(\"utf-8\")\n return _grade_window()\n return _hold\n\nclass SynkroTap:\n def request(self, flow):\n # ── Upload DLP gate ── Scan file uploads BEFORE they reach claude.ai and\n # refuse a blocked one (the file never enters the model). SYNCHRONOUS: this\n # upload waits for the verdict. Fail-OPEN — a scan error never breaks an\n # upload. CWE-200: never print the filename/content (size/rule only).\n r = flow.request\n if not SCAN_URL or not TOKEN: return\n if \"claude.ai\" not in (r.pretty_host or \"\"): return\n if r.method != \"POST\": return\n ct = r.headers.get(\"content-type\", \"\")\n path = r.path or \"\"\n is_upload = (\"multipart/form-data\" in ct) or bool(re.search(r\"/(files|upload|convert)(/|$|\\\\?)\", path))\n if not is_upload: return\n # CWE-400: bound everything. Oversized uploads scan on name/type only — we\n # never pull a multi-MB body into memory or forward it to the scanner.\n MAX_RAW = 8 * 1024 * 1024\n try: clen = int(r.headers.get(\"content-length\", \"0\") or \"0\")\n except Exception: clen = 0\n if clen and clen > MAX_RAW:\n payload = {\"file_name\": \"\", \"size\": clen, \"content_b64\": \"\"}\n else:\n try: body = r.get_content() or b\"\"\n except Exception: return\n if not body or len(body) > MAX_RAW: return\n fname = \"\"\n fm = re.search(rb'filename=\"([^\"]*)\"', body)\n if fm:\n try: fname = fm.group(1).decode(\"utf-8\", \"replace\")[:300]\n except Exception: fname = \"\"\n # Extract the uploaded file's RAW bytes from the multipart body so the\n # grader Reads the real file (a PDF/image must be intact, not the\n # multipart envelope). Fall back to the whole body if no part is found.\n file_bytes = None\n bm = re.search(r\"boundary=([^;]+)\", ct)\n if bm and fm:\n boundary = (\"--\" + bm.group(1).strip().strip('\"')).encode()\n hdr_end = body.find(b\"\\\\r\\\\n\\\\r\\\\n\", fm.start())\n if hdr_end != -1:\n s = hdr_end + 4\n e = body.find(b\"\\\\r\\\\n\" + boundary, s)\n if e != -1: file_bytes = body[s:e]\n if file_bytes is None: file_bytes = body\n try: b64 = base64.b64encode(file_bytes).decode(\"ascii\")\n except Exception: return\n payload = {\"file_name\": fname, \"size\": len(file_bytes), \"content_b64\": b64}\n try:\n respb = _post_json(SCAN_URL, json.dumps(payload).encode(), 55)\n verdict = json.loads((respb or b\"{}\").decode(\"utf-8\", \"replace\"))\n except Exception as e:\n print(\"[synkro] upload scan error (fail-open): \" + str(e))\n return\n if verdict.get(\"block\"):\n reason = str(verdict.get(\"reason\", \"Blocked by Synkro policy\"))[:300]\n msg = \"Blocked by Synkro: \" + reason\n # Return a TERMINAL 400 (invalid_request_error), NOT 403. Claude Desktop's\n # upload client treats 403 as transient/auth and auto-RETRIES it — that's the\n # loop the user sees. A 400 invalid_request_error is non-retryable (the SDK/UI\n # only retry 408/409/429/5xx), so the client stops and surfaces the message.\n # Anthropic's standard error envelope ({\"type\":\"error\",\"error\":{...}}) is what\n # the UI knows how to render, so the user sees the reason instead of a spinner.\n flow.response = http.Response.make(\n 400,\n json.dumps({\"type\": \"error\", \"error\": {\"type\": \"invalid_request_error\", \"message\": msg}}).encode(),\n {\"Content-Type\": \"application/json\"},\n )\n print(\"[synkro] BLOCKED upload (size=\" + str(payload.get(\"size\", 0)) + \" rule=\" + str(verdict.get(\"ruleId\", \"\")) + \")\")\n else:\n print(\"[synkro] upload scanned -> allow (size=\" + str(payload.get(\"size\", 0)) + \")\")\n\n def responseheaders(self, flow):\n # The answer the user watches TYPE OUT comes from claude.ai's completion — THAT\n # is the stream we HOLD (buffer + heartbeat → no leak, no disconnect; see\n # _make_hold_stream). a-api.anthropic.com is a background call that doesn't carry\n # the answer, so it streams through untouched (default below). The tree GET is\n # buffered for the dashboard + the durable block scrub.\n r = flow.request\n host = r.pretty_host or \"\"\n if \"claude.ai\" in host and \"chat_conversations\" in r.path:\n if r.method == \"GET\" and \"tree\" in r.pretty_url:\n return\n if r.method == \"POST\" and \"completion\" in r.path:\n cm = re.search(r\"chat_conversations/([0-9a-fA-F-]+)\", r.path)\n cid = cm.group(1) if cm else \"\"\n with _mcp_lock:\n has_servers = len(_mcp_registry) > 0\n # HOLD when a BLOCK-mode rule exists AND connectors are connected — buffer\n # the answer, grade off-thread, heartbeat through the grade, emit the verdict.\n if has_servers and _should_hold():\n flow.response.stream = _make_hold_stream(cid)\n return\n # Otherwise just TAP for governance metadata (no hold; audit stays async).\n st = _new_scan_state()\n def _tap(data, _st=st, _cid=cid):\n if data:\n try:\n txt = _st[\"carry\"] + data.decode(\"utf-8\", \"replace\")\n except Exception:\n txt = _st[\"carry\"]\n lines = txt.split(\"\\\\n\")\n _st[\"carry\"] = lines[-1][-200000:] # bound the carried partial line (CWE-400)\n for ln in lines[:-1]:\n _scan_sse_line(ln, _st)\n else: # end of stream — flush carry + publish\n _scan_sse_line(_st[\"carry\"], _st)\n if (_st[\"usage\"] or _st[\"sources\"] or _st[\"citations\"]) and _cid:\n with _sse_lock:\n _sse_meta[_cid] = {\"usage\": _st[\"usage\"], \"sources\": _st[\"sources\"], \"citations\": _st[\"citations\"]}\n return data\n flow.response.stream = _tap\n return\n if \"claude.ai\" in (r.pretty_host or \"\") and \"mcp\" in r.path and \"bootstrap\" in r.path:\n return # buffer the MCP registry bootstrap (parsed in response())\n flow.response.stream = True\n\n def response(self, flow):\n r = flow.request\n if \"claude.ai\" not in (r.pretty_host or \"\"): return\n # MCP server registry from the bootstrap SSE stream.\n if \"mcp\" in r.path and \"bootstrap\" in r.path and flow.response:\n try:\n servers = {}\n for line in (flow.response.get_text() or \"\").split(\"\\\\n\"):\n if not line.startswith(\"data: \"): continue\n try: dd = json.loads(line[6:])\n except: continue\n if isinstance(dd.get(\"servers\"), list):\n for s in dd[\"servers\"]:\n sid = s.get(\"uuid\", \"\")\n if sid: servers[sid] = {\"name\": s.get(\"name\", \"\"), \"url\": s.get(\"url\", \"\"), \"connected\": False, \"tools\": []}\n if \"uuid\" in dd and \"connected\" in dd and dd.get(\"uuid\") in servers:\n servers[dd[\"uuid\"]][\"connected\"] = dd.get(\"connected\", False)\n if \"server_uuid\" in dd and isinstance(dd.get(\"tools\"), list) and dd.get(\"server_uuid\") in servers:\n for t in dd[\"tools\"]:\n servers[dd[\"server_uuid\"]][\"tools\"].append({\"name\": t.get(\"name\", \"\"), \"description\": (t.get(\"description\") or \"\")[:200]})\n if servers:\n with _mcp_lock:\n _mcp_registry.clear()\n _mcp_registry.update(servers)\n print(\"[synkro] MCP servers: \" + str(len(servers)))\n # Surface each connector on the MCP Servers page (connect/disconnect).\n for sid, s in servers.items():\n nm = s.get(\"name\", \"\")\n if nm:\n ev = \"connect\" if s.get(\"connected\") else \"disconnect\"\n _emit_mcp_event(nm, \"\", ev, \"cdmcp_\" + ev + \"_\" + str(sid))\n except Exception:\n pass\n return\n if not (\"chat_conversations\" in r.path and r.method == \"GET\" and \"tree\" in r.pretty_url and flow.response): return\n if not CAPTURE_URL or not TOKEN: return\n try: convo = json.loads(flow.response.get_text())\n except: return\n m = re.search(r\"chat_conversations/([0-9a-fA-F-]+)\", r.path)\n convo_id = m.group(1) if m else \"\"\n # One-time history backfill using THIS request's session auth (in-memory only).\n try:\n _om = re.search(r\"/organizations/([0-9a-fA-F-]+)/\", r.path)\n _maybe_backfill(r.pretty_host, _om.group(1) if _om else \"\", r.headers.get(\"cookie\", \"\") or r.headers.get(\"Cookie\", \"\"))\n except Exception:\n pass\n model = convo.get(\"model\", \"unknown\")\n settings = convo.get(\"settings\") or {}\n emt = settings.get(\"enabled_mcp_tools\")\n enabled_mcp = list(emt.keys()) if isinstance(emt, dict) else []\n web_search_on = bool(settings.get(\"enabled_web_search\", False))\n platform = convo.get(\"platform\", \"\")\n all_msgs = convo.get(\"chat_messages\", [])\n if not all_msgs: return\n # Current turn only: last human message and everything after it.\n last_human = -1\n for i in range(len(all_msgs) - 1, -1, -1):\n if all_msgs[i].get(\"sender\") == \"human\": last_human = i; break\n chat = all_msgs[last_human:] if last_human >= 0 else all_msgs[-2:]\n messages = []\n for msg in chat:\n sender = msg.get(\"sender\", \"\")\n role = \"user\" if sender == \"human\" else \"assistant\"\n blocks = msg.get(\"content\", [])\n text_parts = []\n tool_calls = []\n tool_results = []\n thinking = \"\"\n if isinstance(blocks, list):\n for b in blocks:\n if isinstance(b, str): text_parts.append(b); continue\n if not isinstance(b, dict): continue\n bt = b.get(\"type\", \"\")\n if bt == \"text\":\n text_parts.append(b.get(\"text\", \"\"))\n elif bt == \"thinking\":\n thinking += b.get(\"thinking\", \"\") or b.get(\"text\", \"\")\n elif bt == \"tool_use\" or bt == \"server_tool_use\":\n _nm = _fmt_tool_name(b.get(\"name\", \"\"))\n tool_calls.append({\"name\": _nm, \"id\": b.get(\"id\", \"\"), \"arguments\": json.dumps(b.get(\"input\", {}))})\n # A \"Server:tool\" name means a hosted MCP connector ran → log it\n # on the MCP Servers page (\":\" only present for registry matches,\n # so built-in tools like web_search are excluded).\n if \":\" in _nm:\n _srv, _, _tl = _nm.partition(\":\")\n _emit_mcp_event(_srv, _tl, \"tool_call\", \"cdmcp_tc_\" + (b.get(\"id\", \"\") or hashlib.sha256((convo_id + _nm).encode()).hexdigest()[:24]), convo_id)\n elif bt.endswith(\"_tool_result\") or bt == \"tool_result\":\n rc = b.get(\"content\", \"\")\n if isinstance(rc, list):\n rparts = []\n for x in rc:\n if not isinstance(x, dict): continue\n if x.get(\"type\") == \"web_search_result\":\n sn = x.get(\"page_content\", \"\") or x.get(\"snippet\", \"\") or x.get(\"content\", \"\") or \"\"\n if isinstance(sn, dict): sn = sn.get(\"text\", \"\")\n rparts.append((x.get(\"title\", \"\") + \" (\" + x.get(\"url\", \"\") + \")\" + ((\" - \" + str(sn)[:200]) if sn else \"\")).strip())\n elif x.get(\"text\"):\n rparts.append(x[\"text\"])\n elif isinstance(x.get(\"content\"), str):\n rparts.append(x[\"content\"])\n rc = \" | \".join(p for p in rparts if p and p.strip())\n dc = b.get(\"display_content\")\n if isinstance(dc, dict):\n rc = rc or dc.get(\"json_block\", \"\") or dc.get(\"text\", \"\")\n # web_search results are server-side (content:[]); we surface the\n # sources via sse_meta instead. Only record a non-empty result.\n if rc and str(rc).strip():\n tool_results.append({\"name\": _fmt_tool_name(b.get(\"name\", \"\")), \"content\": str(rc)})\n elif isinstance(blocks, str):\n text_parts.append(blocks)\n text = \" \".join(p for p in text_parts if p).strip()\n if role == \"assistant\":\n am = {\"role\": \"assistant\", \"content\": text}\n if tool_calls: am[\"tool_calls\"] = tool_calls\n if thinking: am[\"thinking\"] = thinking\n if text or tool_calls or thinking: messages.append(am)\n for tr in tool_results:\n messages.append({\"role\": \"tool\", \"content\": tr[\"content\"], \"name\": tr.get(\"name\", \"\")})\n elif role == \"user\" and text:\n messages.append({\"role\": \"user\", \"content\": text})\n if not messages: return\n data = {\"request\": {\"model\": model, \"messages\": messages}, \"conversation_id\": convo_id}\n # Full conversation for the session-log tables (Session Timeline card).\n data[\"session_messages\"] = _map_full_conversation(all_msgs)\n # PER-TURN key: the current turn's assistant message id (same key used for\n # dlp_turn_verdicts). Lets the server store each turn as its OWN event so a\n # blocked turn isn't overwritten/deleted by a later passing turn.\n turn_id = \"\"\n for _m in reversed(chat):\n if _m.get(\"sender\") != \"human\":\n turn_id = _msg_key(_m); break\n if turn_id: data[\"turn_id\"] = turn_id\n meta = {}\n if platform: meta[\"platform\"] = platform\n if web_search_on: meta[\"web_search_enabled\"] = True\n if enabled_mcp: meta[\"enabled_mcp_tools\"] = enabled_mcp[:20]\n if meta: data[\"conversation_metadata\"] = meta\n with _sse_lock:\n sm = _sse_meta.pop(convo_id, None)\n if sm: data[\"sse_meta\"] = sm\n with _mcp_lock:\n reg = list(_mcp_registry.values())\n if reg: data[\"mcp_servers\"] = reg\n ntools = 0\n for mm in messages:\n ntools += len(mm.get(\"tool_calls\") or [])\n nsrc = len((sm or {}).get(\"sources\") or [])\n print(\"[synkro] captured chat (msgs=\" + str(len(messages)) + \" tools=\" + str(ntools) + \" sources=\" + str(nsrc) + \" model=\" + model + \")\")\n\n # ── Tier-2 connector DLP gate (WHOLE-CONVERSATION, per-turn, PERSISTENT) ──\n # Scrub every blocked assistant turn (durable PGLite verdicts; connector turns\n # graded once on miss). See _apply_turn_verdicts. Fail-OPEN on any error.\n try:\n any_blocked = _apply_turn_verdicts(all_msgs, convo_id)\n except Exception as e:\n any_blocked = False\n print(\"[synkro] turn-verdict scrub error (fail-open): \" + str(e))\n\n # Capture the REAL content for audit regardless of the block decision.\n def send(_d=data):\n try:\n _post_json(CAPTURE_URL, json.dumps(_d).encode(), 5)\n except Exception as e:\n print(\"[synkro] capture error: \" + str(e))\n threading.Thread(target=send, daemon=True).start()\n\n # Re-serialize the tree so the renderer never sees any blocked turn's content.\n if any_blocked:\n try:\n flow.response.text = json.dumps(convo)\n print(\"[synkro] BLOCKED turn view (whole-convo per-turn, durable)\")\n except Exception as e:\n print(\"[synkro] block scrub failed (fail-open): \" + str(e))\n\naddons = [SynkroTap()]\n`;\n\n// ── MCP stdio proxy (local) ───────────────────────────────────────────────\n// Ported from the cloud tap (packages/api/src/routes/proxyTokens.ts): a stdio\n// passthrough that wraps each configured MCP server, taps JSON-RPC tools/call\n// requests + responses, and POSTs {tool, args, result, latency} to the local\n// capture endpoint with model \"mcp:<server>\". Bearer auth (vs the cloud's\n// x-proxy-token). MCP results are client-side, so this catches what the\n// conversation tree may miss. The wrapped command comes from the user's own\n// Claude Desktop config (spawning it is the proxy's purpose — CWE-78 exempt);\n// the pending map is lock-guarded (CWE-362) and the line buffer bounded (CWE-400).\nconst MCP_PROXY_PY = `#!/usr/bin/env python3\n\"\"\"Synkro MCP Proxy (local) — stdio passthrough that taps MCP tool calls.\"\"\"\nimport sys, os, json, subprocess, threading, urllib.request, urllib.error, time\n\nCAPTURE_URL = os.environ.get(\"SYNKRO_CAPTURE_URL\", \"\")\nTOKEN = os.environ.get(\"SYNKRO_TAP_TOKEN\", \"\")\nSERVER_NAME = os.environ.get(\"SYNKRO_MCP_SERVER\", \"unknown\")\n# The SERVER_TOKEN rotates on container recreate; re-read it from disk on a 401.\nTOKEN_FILE = os.environ.get(\"SYNKRO_TAP_TOKEN_FILE\", \"\") or os.path.expanduser(\"~/.synkro/.mcp-jwt\")\n_token_lock = threading.Lock()\n\ndef _reload_token():\n global TOKEN\n try:\n with open(TOKEN_FILE) as f:\n t = f.read().strip()\n if t:\n with _token_lock: TOKEN = t\n except Exception:\n pass\n\ndef _post_json(url, payload_bytes, timeout):\n # CWE-362: TOKEN snapshotted under the lock. Auto-reauth retry once on 401.\n for attempt in range(2):\n with _token_lock: tok = TOKEN\n try:\n req = urllib.request.Request(url, data=payload_bytes, headers={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer \" + tok})\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return resp.read()\n except urllib.error.HTTPError as e:\n if e.code == 401 and attempt == 0:\n _reload_token(); continue\n raise\n return None\n\npending = {}\npending_lock = threading.Lock()\n\ndef send_capture(tool_name, tool_args, tool_result, latency_ms):\n if not CAPTURE_URL or not TOKEN: return\n try:\n payload = json.dumps({\n \"request\": {\n \"model\": \"mcp:\" + SERVER_NAME,\n \"messages\": [\n {\"role\": \"assistant\", \"content\": \"\", \"tool_calls\": [{\"name\": tool_name, \"arguments\": json.dumps(tool_args)}]},\n {\"role\": \"tool\", \"content\": json.dumps(tool_result), \"name\": tool_name},\n ]\n },\n \"mcp\": {\"server\": SERVER_NAME, \"tool\": tool_name, \"latency_ms\": latency_ms}\n }).encode()\n _post_json(CAPTURE_URL, payload, 5)\n except Exception as e:\n print(\"[synkro-mcp] capture error: \" + str(e), file=sys.stderr)\n\ndef tap_message(data, direction):\n if not isinstance(data, dict): return\n method = data.get(\"method\")\n msg_id = data.get(\"id\")\n if direction == \"request\" and method == \"tools/call\":\n params = data.get(\"params\", {})\n tool_name = params.get(\"name\", \"unknown\")\n tool_args = params.get(\"arguments\", {})\n if msg_id is not None:\n with pending_lock:\n if len(pending) < 1000:\n pending[msg_id] = {\"name\": tool_name, \"args\": tool_args, \"start\": time.time()}\n print(\"[synkro-mcp] \" + SERVER_NAME + \".\" + str(tool_name), file=sys.stderr)\n if direction == \"response\" and msg_id is not None:\n with pending_lock:\n call = pending.pop(msg_id, None)\n if call:\n latency_ms = int((time.time() - call[\"start\"]) * 1000)\n result = data.get(\"result\", data.get(\"error\", {}))\n threading.Thread(target=send_capture, args=(call[\"name\"], call[\"args\"], result, latency_ms), daemon=True).start()\n\ndef read_jsonrpc(stream):\n buf = b\"\"\n while True:\n chunk = stream.read(1)\n if not chunk: return None\n if len(buf) < 5000000: # bound the line buffer (CWE-400)\n buf += chunk\n if chunk == b\"\\\\n\":\n line = buf.strip()\n buf = b\"\"\n if not line: continue\n try: return json.loads(line)\n except: continue\n\ndef forward_stdin(proc):\n while True:\n msg = read_jsonrpc(sys.stdin.buffer)\n if msg is None: break\n tap_message(msg, \"request\")\n line = json.dumps(msg) + \"\\\\n\"\n try:\n proc.stdin.write(line.encode())\n proc.stdin.flush()\n except: break\n\ndef main():\n args = sys.argv[1:]\n if \"--\" in args:\n sep = args.index(\"--\")\n args = args[sep + 1:]\n if not args:\n print(\"Usage: mcp_proxy.py -- <command> [args...]\", file=sys.stderr)\n sys.exit(1)\n # Spawn the user's own configured MCP server (from claude_desktop_config.json).\n proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr)\n t = threading.Thread(target=forward_stdin, args=(proc,), daemon=True)\n t.start()\n while True:\n msg = read_jsonrpc(proc.stdout)\n if msg is None: break\n tap_message(msg, \"response\")\n line = json.dumps(msg) + \"\\\\n\"\n try:\n sys.stdout.buffer.write(line.encode())\n sys.stdout.buffer.flush()\n except: break\n proc.wait()\n sys.exit(proc.returncode)\n\nif __name__ == \"__main__\":\n main()\n`;\n\n// Patches Claude Desktop's claude_desktop_config.json to wrap each mcpServers\n// entry with the proxy above (command=python3, args=[proxy, --, orig...] + the\n// capture env). Idempotent via the _synkro_original marker; the runner backs the\n// config up first and restores it on exit.\nconst MCP_PATCH_PY = `import json, sys\nconfig_path, proxy_path, capture_url, token = sys.argv[1:5]\nwith open(config_path) as f: config = json.load(f)\nservers = config.get(\"mcpServers\", {})\nif not servers:\n print(\"No MCP servers configured\")\n sys.exit(1)\npatched = 0\nfor name, server in servers.items():\n if server.get(\"_synkro_original\"): continue\n orig_cmd = server.get(\"command\", \"\")\n orig_args = server.get(\"args\", [])\n if not orig_cmd: continue\n server[\"_synkro_original\"] = {\"command\": orig_cmd, \"args\": orig_args}\n server[\"command\"] = \"python3\"\n server[\"args\"] = [proxy_path, \"--\", orig_cmd] + orig_args\n env = server.get(\"env\", {})\n env[\"SYNKRO_CAPTURE_URL\"] = capture_url\n env[\"SYNKRO_TAP_TOKEN\"] = token\n env[\"SYNKRO_MCP_SERVER\"] = name\n server[\"env\"] = env\n patched += 1\nwith open(config_path, \"w\") as f: json.dump(config, f, indent=2)\nprint(\"Patched \" + str(patched) + \" MCP servers\")\n`;\n\n// The bash harness: install mitmproxy if needed, generate + trust a session CA,\n// run mitmdump with the addon, launch Claude Desktop through it, and tear it all\n// down on exit. Uses SIGTERM (default kill) — never SIGKILL. No secrets here —\n// the token reaches the addon via the inherited environment.\nfunction buildRunner(sessionDir: string): string {\n return `#!/bin/bash\nset -e\nSESSION_DIR=${JSON.stringify(sessionDir)}\nPROXY_PORT=8080\nMITM_CA_DIR=\"$SESSION_DIR/ca\"\nTAP_SCRIPT=\"$SESSION_DIR/tap.py\"\nCA_FINGERPRINT=\"\"\nMITM_PID=\"\"\nSUDO_KEEPALIVE_PID=\"\"\nMCP_PROXY=\"$SESSION_DIR/mcp_proxy.py\"\nMCP_PATCH=\"$SESSION_DIR/mcp_patch.py\"\nCLAUDE_CONFIG=\"$HOME/Library/Application Support/Claude/claude_desktop_config.json\"\nCLAUDE_CONFIG_BACKUP=\"$SESSION_DIR/claude_desktop_config.backup.json\"\nMCP_PATCHED=false\n\ncleanup() {\n echo \"\"\n echo \"Stopping Claude Desktop observability...\"\n [ -n \"$MITM_PID\" ] && kill \"$MITM_PID\" 2>/dev/null\n [ -n \"$SUDO_KEEPALIVE_PID\" ] && kill \"$SUDO_KEEPALIVE_PID\" 2>/dev/null\n # Restore the original MCP config (unwrap the proxy) on exit.\n if [ \"$MCP_PATCHED\" = \"true\" ] && [ -f \"$CLAUDE_CONFIG_BACKUP\" ]; then\n cp \"$CLAUDE_CONFIG_BACKUP\" \"$CLAUDE_CONFIG\" && echo \" MCP config restored\"\n fi\n if [ -n \"$CA_FINGERPRINT\" ]; then\n # Non-interactive (-n) so teardown can NEVER hang on a sudo password prompt\n # (which would swallow the user's Ctrl+C). The keepalive below keeps creds\n # warm so this normally succeeds without a prompt.\n if sudo -n security delete-certificate -Z \"$CA_FINGERPRINT\" -t /Library/Keychains/System.keychain 2>/dev/null; then\n echo \" Session CA removed from keychain\"\n else\n echo \" Could not auto-remove the session CA (sudo timed out).\"\n echo \" Remove it manually: sudo security delete-certificate -c mitmproxy /Library/Keychains/System.keychain\"\n fi\n fi\n echo \"Done.\"\n exit 0\n}\ntrap cleanup INT TERM EXIT\n\nif ! command -v mitmdump &>/dev/null; then\n echo \"Installing mitmproxy...\"\n if command -v brew &>/dev/null; then brew install mitmproxy; else echo \"mitmproxy required: brew install mitmproxy\"; exit 1; fi\nfi\n\nmkdir -p \"$MITM_CA_DIR\"\necho \"Generating session CA certificate...\"\nmitmdump --set confdir=\"$MITM_CA_DIR\" --listen-port 0 </dev/null &>/dev/null &\nTMPPID=$!; sleep 2; kill $TMPPID 2>/dev/null || true\n\necho \"Trusting session CA (sudo — removed automatically on exit)...\"\nsudo security add-trusted-cert -d -r trustRoot -k \"/Library/Keychains/System.keychain\" \"$MITM_CA_DIR/mitmproxy-ca-cert.pem\"\nCA_FINGERPRINT=$(openssl x509 -in \"$MITM_CA_DIR/mitmproxy-ca-cert.pem\" -noout -fingerprint -sha1 2>/dev/null | sed \"s/.*=//;s/://g\")\n\n# Keep the sudo timestamp warm for the life of the session so teardown's CA\n# removal never blocks on a password prompt (a prompt would eat Ctrl+C). Exits\n# itself if creds are ever revoked. Killed in cleanup().\n( while true; do sudo -n true 2>/dev/null || exit 0; sleep 30; done ) &\nSUDO_KEEPALIVE_PID=$!\n\nlsof -ti:$PROXY_PORT 2>/dev/null | xargs kill 2>/dev/null || true\nosascript -e 'tell app \"Claude\" to quit' 2>/dev/null || true\nsleep 1\n\n# Patch Claude Desktop's MCP config to wrap each server with the Synkro stdio\n# proxy (restored on exit). Capture URL + token come from the inherited env.\nif [ -f \"$CLAUDE_CONFIG\" ]; then\n cp \"$CLAUDE_CONFIG\" \"$CLAUDE_CONFIG_BACKUP\"\n if python3 \"$MCP_PATCH\" \"$CLAUDE_CONFIG\" \"$MCP_PROXY\" \"$SYNKRO_CAPTURE_URL\" \"$SYNKRO_TAP_TOKEN\" 2>/dev/null; then\n MCP_PATCHED=true\n echo \"MCP servers wrapped with Synkro proxy\"\n else\n echo \"No MCP servers configured — skipping MCP proxy\"\n cp \"$CLAUDE_CONFIG_BACKUP\" \"$CLAUDE_CONFIG\" 2>/dev/null || true\n fi\nfi\n\necho \"Starting proxy on :$PROXY_PORT...\"\n# stdin from /dev/null so mitmdump can't grab the TTY in raw mode — otherwise it\n# captures keypresses (incl. Ctrl+C) instead of letting them become a signal.\nmitmdump --set confdir=\"$MITM_CA_DIR\" --listen-port $PROXY_PORT --mode regular -s \"$TAP_SCRIPT\" </dev/null &>\"$(dirname \"$TAP_SCRIPT\")/mitm.log\" &\nMITM_PID=$!\nsleep 2\n\nCLAUDE_BIN=\"\"\nfor p in \"/Applications/Claude.app/Contents/MacOS/Claude\" \"/Volumes/Claude/Claude.app/Contents/MacOS/Claude\"; do\n [ -f \"$p\" ] && CLAUDE_BIN=\"$p\" && break\ndone\nif [ -z \"$CLAUDE_BIN\" ]; then\n CLAUDE_BIN=\"$(mdfind 'kMDItemFSName == \"Claude.app\"' 2>/dev/null | head -1)/Contents/MacOS/Claude\"\nfi\nif [ ! -f \"$CLAUDE_BIN\" ]; then echo \"Claude Desktop not found.\"; exit 1; fi\n\necho \"Launching Claude Desktop...\"\n\"$CLAUDE_BIN\" --proxy-server=\"http://localhost:$PROXY_PORT\" &>/dev/null &\n\necho \"\"\necho \" ✓ Claude Desktop is being monitored by Synkro (local).\"\necho \" Your conversations will appear in the dashboard Events page.\"\necho \" Press Ctrl+C to stop (this removes the session CA cleanly).\"\necho \"\"\nwait $MITM_PID\n`;\n}\n\n/**\n * Run a Claude Desktop observability session. Blocks (foreground) until the user\n * presses Ctrl+C, then the bash trap removes the session CA + stops the proxy.\n */\nexport async function runClaudeDesktopTap(opts: { backfill?: boolean } = {}): Promise<void> {\n if (process.platform !== 'darwin') {\n console.log(' Claude Desktop observability is macOS-only — skipped.');\n return;\n }\n let token = '';\n try { token = readFileSync(JWT_PATH, 'utf-8').trim(); } catch {}\n if (!token) {\n console.log(' Could not read local auth token (~/.synkro/.mcp-jwt). Run `synkro install` first.');\n return;\n }\n if (opts.backfill) {\n console.log(' History backfill enabled — importing past conversations once this session.');\n }\n\n // Register the synkro-guardrails MCP server in Claude Desktop so the user can\n // manage Synkro standards (rules/tasks/findings) from Desktop too — same stdio\n // proxy → local synkro-server (:18931) that Claude Code's local mode uses.\n try {\n const mcp = installClaudeDesktopMcpConfig();\n if (mcp.ok) {\n console.log(` ✓ Synkro standards MCP registered in Claude Desktop`);\n console.log(` (${mcp.path}) — restart Claude Desktop to load the synkro-guardrails tools.`);\n } else {\n console.log(` ⚠ Claude Desktop MCP not registered: ${mcp.reason}`);\n }\n } catch (err) {\n console.log(` ⚠ Could not register Claude Desktop MCP: ${(err as Error).message}`);\n }\n\n // Stage the tap's scripts under ~/.synkro (a Synkro-managed dir) rather than\n // /tmp — keeps all session state in one predictable, allowlisted location.\n const cdRoot = join(homedir(), '.synkro', 'cd-sessions');\n mkdirSync(cdRoot, { recursive: true });\n const sessionDir = mkdtempSync(join(cdRoot, 'synkro-cd-'));\n writeFileSync(join(sessionDir, 'tap.py'), ADDON_PY, 'utf-8');\n // MCP stdio proxy + config patcher (run by the bash to wrap configured MCP servers).\n writeFileSync(join(sessionDir, 'mcp_proxy.py'), MCP_PROXY_PY, { mode: 0o755 });\n writeFileSync(join(sessionDir, 'mcp_patch.py'), MCP_PATCH_PY, 'utf-8');\n const runnerPath = join(sessionDir, 'run.sh');\n writeFileSync(runnerPath, buildRunner(sessionDir), { mode: 0o755 });\n\n await new Promise<void>((resolve) => {\n // Inherit stdio so the user sees output, the sudo prompt works, and Ctrl+C\n // reaches bash's trap for a clean teardown. Token + URL are passed via env —\n // mitmdump (and thus the addon) inherits them; they never touch the source.\n const child = spawn('bash', [runnerPath], {\n stdio: 'inherit',\n env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? '1' : '' },\n });\n const forward = (sig: NodeJS.Signals) => { try { child.kill(sig); } catch {} };\n process.on('SIGINT', forward);\n process.on('SIGTERM', forward);\n child.on('exit', () => {\n process.off('SIGINT', forward);\n process.off('SIGTERM', forward);\n resolve();\n });\n });\n}\n","/**\n * macOS Keychain export for the containerised Synkro install.\n *\n * Claude Code on macOS stores its auth blob in the user's login keychain under\n * the service name `Claude Code-credentials`. A Linux container can't reach\n * the keychain, so on `synkro install` we shell out to the `security` CLI,\n * extract the blob, and write it to a host-side file that the container\n * bind-mounts read-write.\n *\n * Refresh policy: claude rotates its token periodically. We install a launchd\n * agent that re-exports every 45 min so the container picks up new tokens\n * without manual intervention. Stale tokens between rotations fail at most a\n * single grade before the next refresh runs.\n *\n * IMPORTANT (per Synkro Plan Review R003): the exported file lives ONLY on the\n * host under ~/.synkro/claude-creds/ and is bind-mounted at container runtime.\n * It must never be COPY'd into a Dockerfile or otherwise baked into image\n * layers — image layers are part of the publishable artefact and would leak\n * the customer's credentials to anyone who pulls the image.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nexport const CLAUDE_CREDS_DIR = join(SYNKRO_DIR, 'claude-creds');\nexport const CLAUDE_CREDS_FILE = join(CLAUDE_CREDS_DIR, '.credentials.json');\n\n// Cursor grading uses @cursor/sdk, which authenticates ONLY with a\n// dashboard-issued Cursor API key — the macOS keychain OAuth token is\n// rejected by the SDK. So there is no keychain bridge for Cursor: the user\n// places their API key (cursor.com → Settings → API Keys) in this file and\n// it is bind-mounted into the container.\nexport const CURSOR_CREDS_DIR = join(SYNKRO_DIR, 'cursor-creds');\nexport const CURSOR_API_KEY_FILE = join(CURSOR_CREDS_DIR, 'api-key');\n\n// Keychain service name claude uses on macOS. Don't change without verifying\n// against the installed Claude Code build first — name drift is silent and\n// shows up later as \"all my grades say unavailable.\"\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\n// launchd label for the periodic refresh agent.\nconst LAUNCHD_LABEL = 'com.synkro.cli.claude-creds-refresh';\nconst LAUNCHD_PLIST = join(homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);\n// The resident refresh loop lives in its own script file (not inlined in the\n// plist) so we never have to XML-escape shell operators (<, >, &&, |, 2>&1).\nconst REFRESH_SCRIPT = join(SYNKRO_DIR, 'claude-creds-refresh-loop.sh');\nconst REFRESH_LOG = join(SYNKRO_DIR, 'claude-creds-refresh.log');\n\n// Expiry-aware refresh cadence. The OLD design used a flat 45-min StartInterval\n// on the assumption that Claude OAuth tokens live ~1h — they don't. Observed\n// access-token lifetimes are as short as ~12 min, and the Keychain rotates the\n// token underneath us at unpredictable times. A flat 45-min copy therefore left\n// the container running an expired token for the majority of every cycle (all\n// graders SICK, graded=0). The agent is now a resident loop that re-exports and\n// sleeps until just before the *exported* token's expiry, so the container is\n// never more than a couple minutes from current. We refresh CLAUDE creds here\n// only (the host stays the sole owner of the Claude refresh-token chain — the\n// container must NOT refresh it or it would rotate and invalidate the host\n// Keychain's token, breaking host `claude` auth).\nconst REFRESH_EXPIRY_BUFFER_SECONDS = 120; // wake this long before expiry\nconst MIN_REFRESH_INTERVAL_SECONDS = 30; // floor: don't hammer the Keychain\nconst MAX_REFRESH_INTERVAL_SECONDS = 5 * 60; // ceiling: re-poll to catch rotations even on long-lived tokens\n\nexport class KeychainExportError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'KeychainExportError';\n }\n}\n\n/**\n * True when the platform actually needs the keychain bridge. Linux stores\n * creds in a file already; only darwin uses the keychain.\n */\nexport function needsKeychainBridge(): boolean {\n return platform() === 'darwin';\n}\n\n/**\n * Read the Claude Code credential blob from the macOS keychain. Returns null\n * if the entry doesn't exist (claude not signed in) or if the security CLI\n * fails for any reason. The caller should treat null as \"auth not configured\n * yet\" rather than a hard error — the user may not have launched claude yet.\n */\nexport function readKeychainCreds(): string | null {\n if (platform() !== 'darwin') return null;\n const r = spawnSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) return null;\n const blob = (r.stdout || '').trim();\n return blob || null;\n}\n\n/**\n * One-shot export: pull creds from the keychain and write them to the\n * host-side file the container bind-mounts. Returns the absolute file path,\n * or null if there was nothing to export.\n *\n * The directory and file are chmodded to 700/600 so other users on the host\n * can't read the credentials. The container's bind mount preserves these\n * perms inside the namespace.\n */\nexport function exportKeychainCreds(): string | null {\n const blob = readKeychainCreds();\n if (!blob) return null;\n mkdirSync(CLAUDE_CREDS_DIR, { recursive: true });\n chmodSync(CLAUDE_CREDS_DIR, 0o700);\n // Write only when the blob actually changed. The container's cred-watchdog\n // keys its \"host updated credentials file\" → worker-restart on this file's\n // mtime, so rewriting an identical token every poll would churn the pool\n // needlessly. chmod is mtime-neutral, so re-asserting perms is harmless.\n let changed = true;\n try {\n if (existsSync(CLAUDE_CREDS_FILE)) {\n changed = readFileSync(CLAUDE_CREDS_FILE, 'utf-8') !== blob;\n }\n } catch { /* treat unreadable as changed */ }\n if (changed) writeFileSync(CLAUDE_CREDS_FILE, blob, 'utf-8');\n chmodSync(CLAUDE_CREDS_FILE, 0o600);\n return CLAUDE_CREDS_FILE;\n}\n\n/**\n * True when the user has placed a Cursor API key at CURSOR_API_KEY_FILE.\n * Used by the installer to decide whether the Cursor worker pool can run.\n * We never read the key value here — only confirm the file is present and\n * non-empty so the worker can pick it up via the bind mount.\n */\nexport function cursorApiKeyConfigured(): boolean {\n try {\n return existsSync(CURSOR_API_KEY_FILE)\n && readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Persist a Cursor API key to CURSOR_API_KEY_FILE (0600, dir 0700). The\n * containerised worker bind-mounts the file and reads the key at grade time.\n * Same R003 rule as the Claude creds: host-only file, never baked into an\n * image layer.\n */\nexport function writeCursorApiKey(key: string): void {\n const trimmed = key.trim();\n if (!trimmed) return;\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n chmodSync(CURSOR_CREDS_DIR, 0o700);\n writeFileSync(CURSOR_API_KEY_FILE, trimmed, 'utf-8');\n chmodSync(CURSOR_API_KEY_FILE, 0o600);\n}\n\n/**\n * Check whether the stored Cursor API key is actually accepted by Cursor.\n * Hits the Cursor API's `GET /v1/me` (Basic auth — the key is the username).\n *\n * Returns true (accepted), false (definitively rejected — 401/403), or null\n * (could not determine: no key, network error, or an ambiguous status). A null\n * result must NOT be treated as invalid — a transient network blip should\n * never nag the user to re-enter a working key.\n */\nexport async function validateCursorApiKey(): Promise<boolean | null> {\n let key: string;\n try {\n key = readFileSync(CURSOR_API_KEY_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n if (!key) return null;\n try {\n const auth = Buffer.from(`${key}:`).toString('base64');\n const r = await fetch('https://api.cursor.com/v1/me', {\n headers: { Authorization: `Basic ${auth}` },\n signal: AbortSignal.timeout(8_000),\n });\n if (r.status === 401 || r.status === 403) return false;\n if (r.ok) return true;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether the exported Claude token is expired (or within the refresh buffer of\n * expiring). Expiry-based, not file-age based: a freshly *written* file can still\n * hold a near-dead token, and a long-lived token is fine no matter how old the\n * file is. Used by `synkro local-cc status` and the refresh loop.\n */\nexport function credsAreStale(): boolean {\n if (!existsSync(CLAUDE_CREDS_FILE)) return true;\n try {\n const raw = readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0; // epoch ms\n if (!exp) return true;\n return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1000;\n } catch {\n return true;\n }\n}\n\n/** XML-escape a value destined for a plist <string> (paths are data, not code). */\nfunction xmlEscape(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Install the launchd agent that keeps the container's Claude creds fresh.\n *\n * This is a KeepAlive *resident loop* (not a flat StartInterval one-shot): it\n * re-exports the Keychain creds, then sleeps until just before the exported\n * token's expiry (clamped to [MIN, MAX]), so the bind-mounted copy is never\n * stale regardless of how short Claude's access tokens are or when the Keychain\n * rotates. It reuses the existing one-shot `synkro local-cc refresh-creds` for\n * the actual export, so it works with any installed CLI build. Idempotent —\n * overwrites the script + plist. Caller does the launchctl bootout/bootstrap.\n *\n * Security: the caller-supplied bin path and the creds path are passed to the\n * loop via launchd EnvironmentVariables (XML data, never shell-evaluated) and\n * read as \"$SYNKRO_BIN\" / \"$SYNKRO_CF\" — no caller string is interpolated into\n * the script body, so there is no shell-injection surface (CWE-78).\n *\n * Returns the absolute path to the written plist for the caller to load.\n */\nexport function writeRefreshAgent(synkroBinPath: string): string {\n if (platform() !== 'darwin') {\n throw new KeychainExportError('writeRefreshAgent is darwin-only');\n }\n mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });\n mkdirSync(SYNKRO_DIR, { recursive: true });\n\n // TS template literal: only `${...}` is interpolated. Here that is ONLY the\n // numeric tuning constants — never any path/string. `$VAR`, `$(...)`, and\n // `\\${...}` survive verbatim into bash. expiresAt (epoch ms) is parsed with\n // sed (no jq dependency). We log only when the token actually changes.\n const script = `#!/bin/bash\n# Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.\n# SYNKRO_BIN / SYNKRO_CF are injected by launchd as environment data — never\n# interpolated into this script — so there is no shell-injection surface.\nexport PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\"\nprev=\"\"\nwhile true; do\n { \"$SYNKRO_BIN\" local-cc refresh-creds || synkro local-cc refresh-creds; } >/dev/null 2>&1\n exp=$(sed -n 's/.*\"expiresAt\"[: ]*\\\\([0-9]\\\\{10,\\\\}\\\\).*/\\\\1/p' \"$SYNKRO_CF\" 2>/dev/null | head -1)\n now=$(date +%s)\n if [ -n \"$exp\" ]; then\n delay=$(( exp/1000 - now - ${REFRESH_EXPIRY_BUFFER_SECONDS} ))\n else\n delay=${MIN_REFRESH_INTERVAL_SECONDS}\n fi\n [ \"$delay\" -lt ${MIN_REFRESH_INTERVAL_SECONDS} ] && delay=${MIN_REFRESH_INTERVAL_SECONDS}\n [ \"$delay\" -gt ${MAX_REFRESH_INTERVAL_SECONDS} ] && delay=${MAX_REFRESH_INTERVAL_SECONDS}\n if [ \"$exp\" != \"$prev\" ]; then\n echo \"[$(date '+%Y-%m-%dT%H:%M:%S')] claude creds refreshed (expiresAt=\\${exp:-none}); next check in \\${delay}s\"\n prev=\"$exp\"\n fi\n sleep \"$delay\"\ndone\n`;\n writeFileSync(REFRESH_SCRIPT, script, 'utf-8');\n chmodSync(REFRESH_SCRIPT, 0o700);\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>/bin/bash</string>\n <string>${xmlEscape(REFRESH_SCRIPT)}</string>\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n <key>SYNKRO_BIN</key>\n <string>${xmlEscape(synkroBinPath)}</string>\n <key>SYNKRO_CF</key>\n <string>${xmlEscape(CLAUDE_CREDS_FILE)}</string>\n </dict>\n <key>KeepAlive</key>\n <true/>\n <key>RunAtLoad</key>\n <true/>\n <key>ThrottleInterval</key>\n <integer>${MIN_REFRESH_INTERVAL_SECONDS}</integer>\n <key>StandardErrorPath</key>\n <string>${xmlEscape(REFRESH_LOG)}</string>\n <key>StandardOutPath</key>\n <string>${xmlEscape(REFRESH_LOG)}</string>\n</dict>\n</plist>\n`;\n writeFileSync(LAUNCHD_PLIST, plist, 'utf-8');\n return LAUNCHD_PLIST;\n}\n\n/**\n * Load (or reload) the launchd refresh agent. Safe to call multiple times.\n */\nexport function loadRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n // bootout returns non-zero if it isn't loaded; that's fine, we just want to\n // ensure a clean state before bootstrap.\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const r = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0) {\n throw new KeychainExportError(\n `launchctl bootstrap failed: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n}\n\n/**\n * Stop the launchd refresh agent and remove the plist. Used by `synkro\n * disconnect` / `synkro uninstall` so we don't leave orphan agents behind.\n */\nexport function uninstallRefreshAgent(): void {\n if (platform() !== 'darwin') return;\n spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, LAUNCHD_PLIST], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n try {\n const fs = require('node:fs');\n if (existsSync(LAUNCHD_PLIST)) fs.unlinkSync(LAUNCHD_PLIST);\n if (existsSync(REFRESH_SCRIPT)) fs.unlinkSync(REFRESH_SCRIPT);\n } catch { /* best-effort */ }\n}\n\n/**\n * Convenience wrapper for `synkro local-cc refresh-creds`. Re-exports the\n * Claude keychain creds. Cursor needs no refresh agent — its API key is a\n * static dashboard key the user supplies once; it doesn't rotate like the\n * Claude OAuth token.\n */\nexport function refreshCreds(): boolean {\n const path = exportKeychainCreds();\n return path !== null;\n}\n\n/**\n * Read the current exported creds (if any). Used by status commands.\n */\nexport function readExportedCreds(): string | null {\n try {\n return readFileSync(CLAUDE_CREDS_FILE, 'utf-8');\n } catch {\n return null;\n }\n}\n","/**\n * Containerised Synkro install path.\n *\n * Replaces the host-side pueue + tmux + cc_sessions stack with a single\n * Docker container that bundles MCP server, pglite-db, grader dispatcher,\n * and N tmux-managed claude grader workers.\n *\n * Activated when SYNKRO_DEPLOYMENT_MODE=docker is set. The bare-host path\n * remains the default until two stable release cycles have validated this\n * path under real load (see Phase 5 in the architecture plan).\n *\n * Host-side files this module touches:\n * ~/.synkro/ (bind-mounted RO as /data/synkro-host;\n * the container reads .mcp-jwt and\n * credentials.json from inside it)\n * ~/.synkro/pgdata/ (bind-mounted RW, persistent telemetry)\n * ~/.synkro/claude-creds/ (bind-mounted RW on macOS; symlinked\n * from ~/.claude on Linux)\n *\n * Never bakes credentials into the image — every secret is a runtime bind\n * mount of a host file. See macKeychain.ts for the macOS bridge.\n */\n\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport {\n CLAUDE_CREDS_DIR,\n CURSOR_CREDS_DIR,\n exportKeychainCreds,\n cursorApiKeyConfigured,\n needsKeychainBridge,\n writeRefreshAgent,\n loadRefreshAgent,\n} from './macKeychain.js';\n\nexport const SYNKRO_DIR = join(homedir(), '.synkro');\nconst MCP_JWT_PATH = join(SYNKRO_DIR, '.mcp-jwt');\nconst PGDATA_PATH = join(SYNKRO_DIR, 'pgdata');\n// Docker Desktop on macOS can't reliably bind-mount individual files (the mount\n// shows up as -????? inside the container). Stage the host's ~/.claude.json\n// inside a sibling directory and bind-mount the directory instead.\nconst CLAUDE_HOST_STATE_DIR = join(SYNKRO_DIR, 'claude-host-state');\nconst CLAUDE_HOST_STATE_FILE = join(CLAUDE_HOST_STATE_DIR, '.claude.json');\n\n// Host-side ports the installer maps from the container's internal ports.\n// Bumped by 10000 to avoid colliding with the bare-host install's defaults\n// when both are sitting on the same machine during cutover (Phase 5).\nconst HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || '18931', 10);\nconst HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10);\nconst HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || '18930', 10);\n// PGLite TCP socket published from the container's internal 5433 so host tools\n// (psql/DBeaver/TablePlus) can inspect the telemetry DB. The container's\n// pglite-bootstrap multiplexer enforces access; the 127.0.0.1 bind is the\n// network-layer allowlist.\nconst HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || '15433', 10);\n\nconst CONTAINER_NAME = 'synkro-server';\n// Canonical GHCR coordinates published by .github/workflows/docker-publish.yml.\n// Override at install time via SYNKRO_IMAGE_TAG (or mirror to a private\n// registry and set SYNKRO_IMAGE_REGISTRY for the hedge-fund deployment).\nconst DEFAULT_IMAGE = 'ghcr.io/synkro-sh/synkro-server:latest';\n\nexport class DockerInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'DockerInstallError';\n }\n}\n\nexport type WorkerProvider = 'claude_code' | 'cursor';\n\n/**\n * Split a total worker count across the selected providers.\n * - both providers → even split (an odd worker goes to Claude)\n * - single provider → the whole pool\n * - no providers → treated as Claude-only (legacy default)\n * Used by `synkro install` and `synkro local-cc start|restart` so the split\n * rule lives in exactly one place.\n */\nexport function splitWorkers(total: number, providers: WorkerProvider[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n} {\n const t = Math.max(0, Math.floor(total));\n const hasClaude = providers.includes('claude_code');\n const hasCursor = providers.includes('cursor');\n if (hasClaude && hasCursor) {\n const cursorWorkers = Math.floor(t / 2);\n return { claudeWorkers: t - cursorWorkers, cursorWorkers };\n }\n if (hasCursor) return { claudeWorkers: 0, cursorWorkers: t };\n return { claudeWorkers: t, cursorWorkers: 0 };\n}\n\n/** Normalize a provider token. Accepts claude / claude-code / cc / cursor. */\nexport function normalizeProvider(p: string): WorkerProvider | null {\n const v = p.trim().toLowerCase();\n if (v === 'claude' || v === 'claude-code' || v === 'claude_code' || v === 'cc') return 'claude_code';\n if (v === 'cursor') return 'cursor';\n return null;\n}\n\nexport type PoolResolution = {\n /** Explicit per-kind counts, when the file specifies any. */\n claudeWorkers?: number;\n cursorWorkers?: number;\n /** Shorthand mode when no explicit counts are given. */\n pool: 'auto' | 'claude_code' | 'cursor';\n /** Human-readable problems (unrecognized tokens) — callers should surface these. */\n warnings: string[];\n};\n\n/**\n * Resolve the grader pool from a parsed `.synkro` object into a single shape\n * that every caller (`install`, `local-cc start/restart`) agrees on. This is\n * the ONE place the pool/worker config schema is interpreted.\n *\n * Sources, in priority order:\n * 1. Numeric provider counts directly under `grader:` (the unified form) —\n * grader: { claude-code: 5, cursor: 3 } // mixed pool, any count\n * Aliases (claude / claude-code / cc / cursor) all normalize.\n * 2. Legacy top-level `workers:` block — workers: { claude: 5, cursor: 3 }\n * 3. `grader.pool` string shorthand: auto | claude-code | cursor (+ aliases)\n *\n * Unrecognized tokens produce a warning instead of a silent fallback so a\n * typo'd provider name can't quietly halve the pool.\n */\nexport function resolveGraderPool(parsed: Record<string, any>): PoolResolution {\n const warnings: string[] = [];\n const grader = parsed?.grader && typeof parsed.grader === 'object' ? parsed.grader : {};\n\n // 1+2. Collect explicit numeric counts from grader (unified) then the legacy\n // workers block. Unknown numeric keys under grader warn (likely a typo).\n const counts: { claude_code?: number; cursor?: number } = {};\n const collect = (obj: any, where: string, flagUnknown: boolean) => {\n if (!obj || typeof obj !== 'object') return;\n for (const [k, v] of Object.entries(obj)) {\n if (typeof v !== 'number') continue;\n const np = normalizeProvider(k);\n if (np === 'claude_code') counts.claude_code = Math.max(0, Math.floor(v));\n else if (np === 'cursor') counts.cursor = Math.max(0, Math.floor(v));\n else if (flagUnknown) {\n warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code or cursor) — ignored`);\n }\n }\n };\n collect(grader, 'grader', true);\n collect(parsed?.workers, 'workers', false);\n\n // 3. pool string shorthand.\n let pool: 'auto' | 'claude_code' | 'cursor' = 'auto';\n const rawPool = grader.pool;\n if (rawPool != null && rawPool !== 'auto') {\n const np = normalizeProvider(String(rawPool));\n if (np) pool = np;\n else warnings.push(`synkro.toml: grader.pool=\"${rawPool}\" is not recognized (use auto | claude-code | cursor) — falling back to auto`);\n }\n\n if (counts.claude_code != null || counts.cursor != null) {\n return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };\n }\n return { pool, warnings };\n}\n\n/** Friendly label for display/logging from the internal pool form. */\nexport function poolLabel(pool: 'auto' | 'claude_code' | 'cursor'): string {\n return pool === 'claude_code' ? 'claude-code' : pool;\n}\n\n// Coerce a single TOML value token to JS.\nfunction parseTomlValue(raw: string): any {\n const v = raw.trim();\n if (v.startsWith('[') && v.endsWith(']')) {\n const inner = v.slice(1, -1).trim();\n if (!inner) return [];\n return inner.split(',').map(s => parseTomlValue(s)).filter(s => s !== '');\n }\n if ((v.startsWith('\"') && v.endsWith('\"')) || (v.startsWith(\"'\") && v.endsWith(\"'\"))) return v.slice(1, -1);\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (/^-?\\d+$/.test(v)) return parseInt(v, 10);\n return v;\n}\n\n// Minimal TOML reader for synkro.toml's fixed schema (top-level scalars/arrays\n// + [grader]/[scanning] tables). Returns the same nested-object shape the old\n// YAML parser did, so resolveGraderPool() is unchanged.\nfunction parseSynkroToml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n let section: string | null = null;\n for (const rawLine of raw.split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n const sec = line.match(/^\\[([^\\]]+)\\]$/);\n if (sec) {\n section = sec[1].trim();\n if (!result[section]) result[section] = {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim().replace(/^[\"']|[\"']$/g, '');\n let valRaw = line.slice(eq + 1).trim();\n if (!valRaw.startsWith('\"') && !valRaw.startsWith(\"'\") && !valRaw.startsWith('[')) {\n const h = valRaw.indexOf('#');\n if (h !== -1) valRaw = valRaw.slice(0, h).trim();\n }\n const value = parseTomlValue(valRaw);\n if (section) (result[section] as Record<string, any>)[key] = value;\n else result[key] = value;\n }\n return result;\n}\n\nfunction readSynkroFileConfig(): PoolResolution {\n try {\n const root = execSync('git rev-parse --show-toplevel 2>/dev/null', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return { pool: 'auto', warnings: [] };\n const fp = join(root, 'synkro.toml');\n if (!existsSync(fp)) return { pool: 'auto', warnings: [] };\n const parsed = parseSynkroToml(readFileSync(fp, 'utf-8'));\n return resolveGraderPool(parsed);\n } catch {}\n return { pool: 'auto', warnings: [] };\n}\n\n/**\n * Parse `--workers N|--workers=N` and `--provider(s) a,b` flags from a\n * start/restart invocation and compute the per-kind worker split.\n * - `explicit` is true when any worker/provider flag was supplied — callers\n * use it to decide between a config-changing recreate vs a plain start.\n * - Providers default to the coding agents detected on the machine.\n * - Total defaults to 8, capped at 64.\n */\nexport function resolveWorkerConfig(rest: string[]): {\n claudeWorkers: number;\n cursorWorkers: number;\n explicit: boolean;\n} {\n let workers = 8;\n let explicit = false;\n const providers: WorkerProvider[] = [];\n const addProviders = (csv: string) => {\n for (const p of csv.split(',')) {\n const np = normalizeProvider(p);\n if (np && !providers.includes(np)) providers.push(np);\n }\n };\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n if (a === '--workers' || a === '-w') { workers = parseInt(rest[++i] || '8', 10); explicit = true; }\n else if (a.startsWith('--workers=')) { workers = parseInt(a.slice('--workers='.length), 10); explicit = true; }\n else if (a === '--provider' || a === '--providers') { addProviders(rest[++i] || ''); explicit = true; }\n else if (a.startsWith('--provider=')) { addProviders(a.slice('--provider='.length)); explicit = true; }\n else if (a.startsWith('--providers=')) { addProviders(a.slice('--providers='.length)); explicit = true; }\n }\n if (!Number.isFinite(workers) || workers < 1) workers = 8;\n workers = Math.min(workers, 64);\n let provs = providers;\n if (provs.length === 0) {\n const sc = readSynkroFileConfig();\n for (const w of sc.warnings) console.warn(` ⚠ ${w}`);\n if (sc.claudeWorkers != null || sc.cursorWorkers != null) {\n const cw = sc.claudeWorkers || 0;\n const curw = sc.cursorWorkers || 0;\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };\n }\n if (sc.pool === 'cursor') {\n provs = ['cursor'];\n } else if (sc.pool === 'claude_code') {\n provs = ['claude_code'];\n } else {\n provs = detectAgents().map(a => a.kind) as WorkerProvider[];\n if (provs.length === 0) provs = ['claude_code'];\n }\n }\n return { ...splitWorkers(workers, provs), explicit };\n}\n\n/** Resolve which image tag this install should pull. */\nexport function imageTag(): string {\n const registry = process.env.SYNKRO_IMAGE_REGISTRY || ''; // e.g. registry.hedgefund.internal\n const tag = process.env.SYNKRO_IMAGE_TAG || DEFAULT_IMAGE;\n return registry ? `${registry.replace(/\\/+$/, '')}/${tag.replace(/^.*\\//, '')}` : tag;\n}\n\n/** True iff docker is on PATH and the daemon is reachable. */\nexport function assertDockerAvailable(): void {\n const v = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (v.status !== 0) {\n throw new DockerInstallError(\n 'Docker CLI is not installed or the daemon is not reachable.\\n' +\n 'Install Docker Desktop (macOS) or docker-engine (Linux), start the daemon, then re-run.',\n );\n }\n}\n\n/**\n * Compute the bind-mount source for the claude credentials dir. macOS uses\n * the exported keychain shadow; Linux uses ~/.claude directly.\n */\nfunction claudeCredsHostDir(): string {\n if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;\n return join(homedir(), '.claude');\n}\n\n/**\n * Idempotent docker pull + run. If a container with the same name is already\n * up, this stops it first so the new image starts cleanly (matches\n * `synkro update` semantics).\n *\n * Returns the resolved image tag for logging.\n */\n/**\n * Resolve the absolute path to the `synkro` binary for the launchd refresh\n * agent. The location varies by install method (Homebrew's /opt/homebrew/bin,\n * /usr/local/bin, an npm prefix) — never hardcode it, or the refresh job\n * silently fails and the container's Claude creds expire.\n */\nfunction resolveSynkroBin(): string {\n const which = spawnSync('which', ['synkro'], { encoding: 'utf-8', timeout: 5_000 });\n const resolved = (which.stdout || '').split('\\n')[0].trim();\n return resolved || 'synkro';\n}\n\n// Host-side PGLite orphans from pre-Docker installs (or earlier crashed\n// container starts that re-spawned the host bin) squat on the published\n// PGLite port, hold the only client slot under the old maxConnections=1\n// default, and write into a separate pgdata from the container's mount.\n// The migration commit that moved PGLite into the container deleted the\n// source file but never killed running processes — they daemonize to\n// launchd (PPID=1) and outlive the install that spawned them. Sweep them\n// before launching the new container so the publish maps to the right\n// PGLite. Container bun processes are isolated inside their PID namespace\n// and never appear in the host's ps output, so this is safe.\nfunction sweepHostPglite(): void {\n const ps = spawnSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5_000 });\n if (ps.status !== 0) return;\n const targets: number[] = [];\n for (const line of (ps.stdout || '').split('\\n')) {\n if (!/bun\\b/.test(line)) continue;\n if (!/pglite-(db|bootstrap)\\.ts\\b/.test(line)) continue;\n const m = line.trim().match(/^(\\d+)\\s/);\n if (!m) continue;\n const pid = parseInt(m[1], 10);\n if (pid > 0 && pid !== process.pid) targets.push(pid);\n }\n if (targets.length === 0) return;\n console.log(` Sweeping ${targets.length} stale host PGLite process(es): ${targets.join(', ')}`);\n for (const pid of targets) {\n try { process.kill(pid, 'SIGTERM'); } catch {}\n }\n const deadline = Date.now() + 3000;\n while (Date.now() < deadline) {\n const alive = targets.filter(pid => {\n try { process.kill(pid, 0); return true; } catch { return false; }\n });\n if (alive.length === 0) return;\n spawnSync('sleep', ['0.2'], { timeout: 1_000 });\n }\n for (const pid of targets) {\n try { process.kill(pid, 'SIGKILL'); } catch {}\n }\n}\n\nexport async function dockerInstall(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<{\n image: string;\n hostMcpPort: number;\n hostGraderPort: number;\n hostCwePort: number;\n hostPglitePort: number;\n}> {\n assertDockerAvailable();\n\n const image = imageTag();\n // Per-kind worker counts. claudeWorkers falls back to the legacy\n // workersPerPool; cursorWorkers defaults to 0 (Claude-only install).\n const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;\n const cursorWorkers = opts.cursorWorkers ?? 0;\n const totalWorkers = claudeWorkers + cursorWorkers;\n\n // Ensure persistent host dirs exist before docker tries to bind-mount them.\n // Docker creates missing bind sources as root-owned dirs which then break\n // permissions inside the container — pre-create them as the host user.\n mkdirSync(PGDATA_PATH, { recursive: true });\n mkdirSync(BACKUP_DIR, { recursive: true });\n\n // Stage the host's ~/.claude.json into a dir we can bind-mount cleanly on\n // macOS. Without this the container's workers see -????? at the mount path\n // and Claude Code prompts for browser OAuth on every launch.\n mkdirSync(CLAUDE_HOST_STATE_DIR, { recursive: true });\n const hostClaudeJson = join(homedir(), '.claude.json');\n if (existsSync(hostClaudeJson)) {\n copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);\n }\n if (!existsSync(MCP_JWT_PATH)) {\n throw new DockerInstallError(\n `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`,\n );\n }\n\n // Pre-create the Cursor creds dir so the bind mount target is owned by the\n // host user (Docker would otherwise create it root-owned).\n mkdirSync(CURSOR_CREDS_DIR, { recursive: true });\n\n // On macOS, export keychain creds + install the launchd refresh agent\n // before starting the container so the workers have something to auth with.\n if (needsKeychainBridge()) {\n // Export the Claude Code keychain creds so the container holds a subscription\n // token. Two consumers need it: Claude grader workers AND the out-of-band usage\n // tap (which pings Anthropic to read account-wide utilization). The tap runs even\n // when grading is pure BYOK or pure Cursor, so we export whenever the keychain\n // entry exists — NOT only when there are Claude workers. Hard-fail only when\n // Claude grading actually requires it; otherwise degrade to \"telemetry off\".\n const claudeCredsPath = exportKeychainCreds();\n if (!claudeCredsPath) {\n if (claudeWorkers > 0) {\n throw new DockerInstallError(\n 'Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container.',\n );\n }\n console.warn(' ⚠ Claude Code keychain entry not found — live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).');\n }\n // Cursor uses a dashboard API key (the @cursor/sdk rejects the keychain\n // OAuth token). Warn — don't hard-fail — so a \"both agents\" install still\n // proceeds; the Cursor pool stays idle until the key file is present.\n if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {\n console.warn(' ⚠ No Cursor API key found — Cursor grader workers will be idle.');\n console.warn(' Generate a key at cursor.com → Settings → API Keys, then:');\n console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);\n }\n // launchd refresh agent — Claude only (Cursor's API key is static).\n const plist = writeRefreshAgent(resolveSynkroBin());\n try { loadRefreshAgent(); } catch (err) {\n console.warn(` ⚠ launchd refresh agent not loaded: ${(err as Error).message}`);\n console.warn(` Plist written to ${plist} — load manually with launchctl bootstrap when ready.`);\n }\n } else {\n // Linux — make sure ~/.claude exists so the bind mount target is real.\n mkdirSync(join(homedir(), '.claude'), { recursive: true });\n }\n\n // Pull (or update) the image — unless we're using a locally-built image.\n // A local/air-gapped image won't exist in any registry, so a forced pull\n // would fail. Skip the pull when SYNKRO_SKIP_PULL is set, and fall back to an\n // existing local image if the pull fails for any reason (offline, private\n // registry blip, locally-built dev image). Only hard-fail when there is no\n // local image to fall back to.\n const imageExistsLocally = (): boolean =>\n spawnSync('docker', ['image', 'inspect', image], { stdio: 'ignore', timeout: 30_000 }).status === 0;\n const skipPull = process.env.SYNKRO_SKIP_PULL === '1' || process.env.SYNKRO_SKIP_PULL === 'true';\n\n if (skipPull && imageExistsLocally()) {\n console.log(` Using local image ${image} (SYNKRO_SKIP_PULL set — pull skipped).`);\n } else {\n console.log(` Pulling ${image}...`);\n const pull = spawnSync('docker', ['pull', image], { encoding: 'utf-8', stdio: 'inherit', timeout: 600_000 });\n if (pull.status !== 0) {\n if (imageExistsLocally()) {\n console.warn(` ⚠ docker pull ${image} failed — falling back to the existing local image.`);\n } else {\n throw new DockerInstallError(`docker pull ${image} failed`);\n }\n }\n }\n\n // Gracefully stop existing container if running (snapshot + checkpoint).\n const existing = dockerStatus();\n if (existing.running) {\n console.log(' Stopping existing container gracefully...');\n await dockerSafeStop();\n }\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n\n // Kill any leftover host-side PGLite from pre-Docker installs so the new\n // container's published port 5433 isn't shadowed by a stale daemon.\n sweepHostPglite();\n\n // Start the container with bind mounts and explicit port maps (per the\n // approved plan — explicit mapping, not --network host).\n //\n // SECURITY: every -p below MUST keep the `127.0.0.1:` host-bind prefix. The\n // container's /api/local/* REST endpoints (snapshot, telemetry, hook-config)\n // are unauthenticated — the localhost-only bind IS the access boundary.\n // Never drop the prefix or switch to 0.0.0.0 / --network host: that would\n // expose an unauthenticated control surface to the LAN.\n const credsDir = claudeCredsHostDir();\n const args = [\n 'run',\n '--detach',\n '--name', CONTAINER_NAME,\n '--restart', 'unless-stopped',\n '-p', `127.0.0.1:${HOST_MCP_PORT}:8931`,\n '-p', `127.0.0.1:${HOST_GRADER_PORT}:8929`,\n '-p', `127.0.0.1:${HOST_CWE_PORT}:8930`,\n '-p', `127.0.0.1:${HOST_PGLITE_PORT}:5433`,\n '-v', `${PGDATA_PATH}:/data/pgdata`,\n '-v', `${BACKUP_DIR}:/data/backups`,\n // The whole host ~/.synkro directory, read-only. The container copies\n // .mcp-jwt and credentials.json out of it at boot. A directory mount\n // sidesteps Docker Desktop for macOS's unreliable single-file bind mounts\n // (which previously left a dangling symlink that blocked container start).\n '-v', `${SYNKRO_DIR}:/data/synkro-host:ro`,\n '-v', `${credsDir}:/home/synkro/.claude:rw`,\n '-v', `${join(homedir(), '.claude')}:/data/claude-host:ro`,\n '-v', `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,\n // Cursor creds — mounted RW so the in-container refresher can rotate the\n // access token in place. Only mounted when the install includes Cursor.\n ...(cursorWorkers > 0\n ? ['-v', `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`]\n : []),\n '-e', `WORKERS_PER_POOL=${totalWorkers}`,\n '-e', `CLAUDE_WORKERS=${claudeWorkers}`,\n '-e', `CURSOR_WORKERS=${cursorWorkers}`,\n // Pass through the batch-size lever if the operator set it. Defaults\n // inside the container to 5; clamped to [1, 20] by synkro-server.ts.\n ...(process.env.SYNKRO_MAX_BATCH_SIZE\n ? ['-e', `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`]\n : []),\n // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.\n ...(process.env.SYNKRO_CURSOR_MODEL\n ? ['-e', `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`]\n : []),\n // Fix-poll kill switch. Default ON in the image; a benchmark/headless run\n // (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the\n // interactive AskUserQuestion poll and fall through to generate-the-fix. Only\n // passed when the operator set it; otherwise the image default (1) applies.\n ...(process.env.SYNKRO_FIX_POLL\n ? ['-e', `SYNKRO_FIX_POLL=${process.env.SYNKRO_FIX_POLL}`]\n : []),\n // Connected repo — the server seeds the local app + ruleset named after it.\n ...(opts.connectedRepo\n ? ['-e', `SYNKRO_CONNECTED_REPO=${opts.connectedRepo}`]\n : []),\n // Real account identity (from config.env via process.env) — telemetry is\n // stamped with these instead of a `local-user` placeholder, so the data is\n // correctly attributed from the first graded command.\n ...(process.env.SYNKRO_ORG_ID\n ? ['-e', `SYNKRO_ORG_ID=${process.env.SYNKRO_ORG_ID}`]\n : []),\n ...(process.env.SYNKRO_USER_ID\n ? ['-e', `SYNKRO_USER_ID=${process.env.SYNKRO_USER_ID}`]\n : []),\n ...(process.env.SYNKRO_EMAIL\n ? ['-e', `SYNKRO_EMAIL=${process.env.SYNKRO_EMAIL}`]\n : []),\n // Container-owned cloud telemetry flush (docker/telemetryCloudFlush.ts). The\n // server drains the READ-ONLY host queue (already mounted at\n // /data/synkro-host via the RO mount above — NO new mount) into its pglite\n // and pushes unflushed rows to the D1 telemetry worker. The loop is gated on\n // SYNKRO_TELEMETRY_URL and is harmless when there's nothing to flush, so we\n // always set it (defaulting to the D1 worker). Override via env to redirect.\n '-e', `SYNKRO_TELEMETRY_URL=${process.env.SYNKRO_TELEMETRY_URL || 'https://admin.synkro.sh'}`,\n '-e', 'SYNKRO_TELEMETRY_QUEUE=/data/synkro-host/telemetry-pending.jsonl',\n image,\n ];\n const run = spawnSync('docker', args, { encoding: 'utf-8', stdio: 'inherit', timeout: 60_000 });\n if (run.status !== 0) {\n throw new DockerInstallError(`docker run failed (image ${image})`);\n }\n\n return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };\n}\n\n/**\n * Poll the container's MCP /healthz until it returns 200, or fail after the\n * timeout. Used by install to give a single clean ready signal instead of the\n * multi-port wait the bare-host install has to do.\n */\nexport async function waitForContainerReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_MCP_PORT}/health`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) return true;\n } catch { /* keep retrying */ }\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\nexport async function waitForWorkersReady(timeoutMs = 60_000): Promise<boolean> {\n const start = Date.now();\n const url = `http://127.0.0.1:${HOST_GRADER_PORT}/healthz`;\n while (Date.now() - start < timeoutMs) {\n try {\n const r = await fetch(url, { signal: AbortSignal.timeout(2_000) });\n if (r.ok) {\n const data = await r.json() as { workers?: number; healthy?: number };\n if ((data.healthy || 0) > 0) return true;\n }\n } catch {}\n await new Promise(r => setTimeout(r, 1_000));\n }\n return false;\n}\n\n/**\n * Remove the container (after it's already stopped). Does NOT stop gracefully —\n * callers must use dockerSafeStop() first if the container is running.\n */\nexport function dockerRemove(): void {\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/** @deprecated Use dockerSafeStop() + dockerRemove() instead. */\nexport function dockerStop(): void {\n spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], { encoding: 'utf-8', timeout: 45_000 });\n spawnSync('docker', ['rm', CONTAINER_NAME], { encoding: 'utf-8', timeout: 30_000 });\n}\n\n/**\n * `synkro update` for the containerised install — pull + restart, preserve\n * all bind-mounted state.\n */\nexport async function dockerUpdate(opts: {\n workersPerPool?: number;\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} = {}): Promise<void> {\n if (dockerStatus().running) {\n await dockerSafeStop();\n }\n dockerRemove();\n await dockerInstall(opts);\n}\n\n/** Show host-mapped ports + container status. */\nexport function dockerStatus(): { running: boolean; image?: string; healthz?: string } {\n const r = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const status = (r.stdout || '').trim();\n if (status !== 'running') return { running: false };\n return {\n running: true,\n image: imageTag(),\n healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`,\n };\n}\n\n/**\n * Read the worker-pool config baked into the installed container's env, so\n * `synkro update` can rebuild on a fresh image without resetting the pool.\n * Works on stopped containers too. Returns null when no container exists.\n */\nexport function readContainerConfig(): {\n claudeWorkers?: number;\n cursorWorkers?: number;\n connectedRepo?: string;\n} | null {\n const r = spawnSync('docker', ['inspect', '--format', '{{json .Config.Env}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (r.status !== 0 || !r.stdout) return null;\n let env: unknown;\n try { env = JSON.parse(r.stdout.trim()); } catch { return null; }\n if (!Array.isArray(env)) return null;\n const get = (k: string): string | undefined => {\n const hit = env.find((e): e is string => typeof e === 'string' && e.startsWith(k + '='));\n return hit ? hit.slice(k.length + 1) : undefined;\n };\n const num = (s: string | undefined): number | undefined => {\n if (s === undefined) return undefined;\n const n = parseInt(s, 10);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n claudeWorkers: num(get('CLAUDE_WORKERS')),\n cursorWorkers: num(get('CURSOR_WORKERS')),\n connectedRepo: get('SYNKRO_CONNECTED_REPO') || undefined,\n };\n}\n\n// ─── Safe lifecycle commands ────────────────────────────────────────────────\n\nconst BACKUP_DIR = join(SYNKRO_DIR, 'pgdata-backups');\n\n/**\n * Graceful stop: snapshot → docker stop (30s for CHECKPOINT+flush) → verify.\n * Never force-kills. Returns details about what happened.\n */\nexport async function dockerSafeStop(): Promise<{\n ok: boolean;\n snapshot?: { ok: boolean; error?: string };\n exitCode?: number;\n pgdataCheck?: { healthy: boolean; details: string };\n}> {\n const status = dockerStatus();\n if (!status.running) {\n console.log(' Container is not running.');\n return { ok: true, pgdataCheck: checkPgdata() };\n }\n\n // 1. Request a pre-shutdown snapshot via the REST endpoint\n console.log(' Requesting data snapshot before shutdown...');\n let snapshot: { ok: boolean; error?: string } = { ok: false, error: 'not attempted' };\n try {\n const resp = await fetch(`http://127.0.0.1:${HOST_MCP_PORT}/api/local/snapshot`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reason: 'cli-stop' }),\n signal: AbortSignal.timeout(20_000),\n });\n if (resp.ok) {\n snapshot = { ok: true };\n console.log(' ✓ Snapshot saved.');\n } else {\n snapshot = { ok: false, error: `HTTP ${resp.status}` };\n console.warn(` ⚠ Snapshot request failed (HTTP ${resp.status}). Proceeding with stop.`);\n }\n } catch (e) {\n snapshot = { ok: false, error: String(e).slice(0, 100) };\n console.warn(` ⚠ Snapshot request failed: ${snapshot.error}. Proceeding with stop.`);\n }\n\n // 2. docker stop with 30s grace period (entrypoint trap does CHECKPOINT + final snapshot)\n console.log(' Stopping container (30s grace for CHECKPOINT + WAL flush)...');\n const stop = spawnSync('docker', ['stop', '--timeout=30', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 45_000,\n });\n\n // 3. Verify clean exit\n const inspect = spawnSync('docker', ['inspect', '--format', '{{.State.ExitCode}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n const exitCode = parseInt((inspect.stdout || '').trim(), 10);\n if (exitCode === 0) {\n console.log(' ✓ Container stopped cleanly (exit 0).');\n } else {\n console.warn(` ⚠ Container exited with code ${exitCode}.`);\n }\n\n // 4. Check pgdata health on host\n const pgCheck = checkPgdata();\n if (pgCheck.healthy) {\n console.log(` ✓ pgdata looks healthy: ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata check: ${pgCheck.details}`);\n }\n\n return { ok: stop.status === 0, snapshot, exitCode, pgdataCheck: pgCheck };\n}\n\n/**\n * Start the container (must already exist from a prior install).\n * Checks pgdata integrity, starts container, waits for healthcheck.\n */\nexport async function dockerSafeStart(): Promise<{\n ok: boolean;\n pgdataState: string;\n error?: string;\n}> {\n // Check if already running\n const status = dockerStatus();\n if (status.running) {\n console.log(' Container is already running.');\n return { ok: true, pgdataState: 'running' };\n }\n\n // Check if container exists (stopped)\n const exists = spawnSync('docker', ['inspect', '--format', '{{.State.Status}}', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 5_000,\n });\n if (exists.status !== 0) {\n return { ok: false, pgdataState: 'no_container', error: 'No synkro-server container found. Run `synkro install` first.' };\n }\n\n // Check pgdata state\n const pgCheck = checkPgdata();\n if (existsSync(PGDATA_PATH) && readdirSync(PGDATA_PATH).length > 0) {\n if (pgCheck.healthy) {\n console.log(` pgdata: existing data found — ${pgCheck.details}`);\n } else {\n console.warn(` ⚠ pgdata: ${pgCheck.details}`);\n console.log(' Starting anyway — entrypoint will attempt recovery from snapshots if needed.');\n }\n } else {\n console.log(' pgdata: no existing data — fresh start.');\n mkdirSync(PGDATA_PATH, { recursive: true });\n }\n\n // Start the container\n console.log(' Starting container...');\n const start = spawnSync('docker', ['start', CONTAINER_NAME], {\n encoding: 'utf-8',\n timeout: 30_000,\n });\n if (start.status !== 0) {\n return { ok: false, pgdataState: 'start_failed', error: `docker start failed: ${(start.stderr || '').slice(0, 200)}` };\n }\n\n // Wait for healthcheck\n console.log(' Waiting for server to become healthy...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ Server is healthy and ready.');\n return { ok: true, pgdataState: pgCheck.healthy ? 'existing' : 'recovered' };\n } else {\n return { ok: false, pgdataState: 'unhealthy', error: 'Server did not become healthy within 60s. Check: docker logs synkro-server' };\n }\n}\n\n/**\n * Restart: full safe stop, then safe start. Sequential — never overlapping.\n */\nexport async function dockerSafeRestart(): Promise<{ ok: boolean; stop: Awaited<ReturnType<typeof dockerSafeStop>>; start: Awaited<ReturnType<typeof dockerSafeStart>> }> {\n console.log(' === Stop ===');\n const stopResult = await dockerSafeStop();\n if (!stopResult.ok) {\n console.error(' Stop failed. Aborting restart.');\n return { ok: false, stop: stopResult, start: { ok: false, pgdataState: 'not_started', error: 'stop failed' } };\n }\n console.log('\\n === Start ===');\n const startResult = await dockerSafeStart();\n return { ok: startResult.ok, stop: stopResult, start: startResult };\n}\n\nfunction checkPgdata(): { healthy: boolean; details: string } {\n if (!existsSync(PGDATA_PATH)) return { healthy: false, details: 'pgdata directory does not exist' };\n const entries = readdirSync(PGDATA_PATH);\n if (entries.length === 0) return { healthy: true, details: 'empty (fresh start)' };\n const hasPidFile = entries.includes('postmaster.pid');\n const hasWalDir = entries.includes('pg_wal');\n const hasPgControl = entries.includes('global') || entries.includes('pg_control');\n if (hasPidFile) return { healthy: false, details: 'stale postmaster.pid present (unclean shutdown)' };\n if (!hasWalDir) return { healthy: false, details: 'pg_wal directory missing' };\n if (!hasPgControl) return { healthy: false, details: 'pg_control/global directory missing' };\n return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };\n}\n","/**\n * Capture a `claude setup-token` OAuth token (sk-ant-oat01-…) non-interactively.\n *\n * Shared by the GitHub Actions setup (setupGithub.ts) and the CLOUD installer\n * path (install.ts). It is CLOUD-ONLY for `synkro install`: local installs use\n * the macOS keychain bridge (macKeychain.ts) and never call this.\n *\n * `claude setup-token` renders its output as a TUI, so we run it under `script`\n * to capture the pty output, then scrape the token (Claude renders it in the\n * RGB 255,193,7 yellow). The user approves the OAuth in a browser; we never log\n * the token and delete the capture file immediately.\n */\nimport { spawn as nodeSpawn } from 'node:child_process';\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nexport function captureClaudeSetupToken(): Promise<string> {\n const tmpFile = join(SYNKRO_DIR, `token-capture-${Date.now()}.raw`);\n // `script`'s arg order differs by platform:\n // BSD/macOS: script -q <file> <cmd> [args...]\n // util-linux: script -qec '<cmd>' <file>\n const isMac = platform() === 'darwin';\n const bin = 'script';\n const args = isMac\n ? ['-q', tmpFile, 'claude', 'setup-token']\n : ['-qec', 'claude setup-token', tmpFile];\n // When claude.ai rejects the browser approval, `claude setup-token` ends\n // without ever printing a token (the user has no code to paste back, so they\n // Ctrl-C and we exit non-zero, or it exits 0 with nothing). The most common\n // cause is NOT a Synkro bug: a request-rewriting browser extension (Grammarly,\n // ad/script blockers, AI-assistant toolbars) strips the OAuth form's\n // Content-Type — claude.ai then shows \"Authorization failed — Unsupported\n // media type\" — or the user approved on the wrong Claude account. Surface that\n // instead of a bare exit code / empty-capture error so the customer can self-fix.\n const OAUTH_HINT =\n 'The browser approval did not return a token. This usually means claude.ai rejected ' +\n 'the OAuth request (its \"Authorization failed — Unsupported media type\" page), most often ' +\n 'because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped ' +\n 'the request headers, or you approved on the wrong Claude account. Fix: retry in a clean ' +\n 'incognito window with extensions disabled, and approve on the Claude account you want the ' +\n 'cloud workers to use.';\n return new Promise((resolve, reject) => {\n // Force deterministic 24-bit color so the token's yellow is ALWAYS emitted as\n // 38;2;255;193;7 — not a 256-color/basic approximation that varies by the\n // user's terminal. The extractor keys on that exact foreground color.\n const proc = nodeSpawn(bin, args, {\n stdio: 'inherit',\n env: { ...process.env, FORCE_COLOR: '3', COLORTERM: 'truecolor', TERM: 'xterm-256color' },\n });\n proc.on('error', (err) => reject(new Error(`Failed to spawn claude setup-token: ${err.message}`)));\n proc.on('close', (code) => {\n let raw = '';\n try { raw = readFileSync(tmpFile, 'utf-8'); } catch (e) {\n reject(new Error(`Could not read script output file: ${(e as Error).message}`));\n return;\n }\n try { unlinkSync(tmpFile); } catch {}\n if (code !== 0) { reject(new Error(`claude setup-token exited with code ${code}. ${OAUTH_HINT}`)); return; }\n // The token — and ONLY the token — is rendered in yellow (24-bit fg\n // 255,193,7); every other character on screen is a different color. So we do\n // NOT parse the token's shape at all: no `sk-ant-oat01` match, no\n // `[A-Za-z0-9_-]` class, no length check. Every such regex matches forward\n // until the first byte it doesn't expect and then STOPS — and the terminal\n // injects stray control bytes mid-token at each line-wrap, so any shape regex\n // truncates there (that bug stored 90 of 108 chars and 401'd every grade).\n //\n // Instead: a tiny ANSI SGR foreground-color state machine. Walk the output,\n // track the current fg color, and collect EVERY printable character emitted\n // while fg is the token-yellow — across as many wraps/spans as it takes.\n // Whitespace/control bytes the wrap injects are simply not collected; ANSI\n // escapes are consumed to update color, never emitted. What remains is the\n // entire yellow run = the whole token, however long it is.\n const TOKEN_YELLOW = '2;255;193;7';\n let fg = 'default';\n let token = '';\n let i = 0;\n while (i < raw.length) {\n if (raw[i] === '\\x1B') {\n // SGR (\"…m\"): update the foreground-color state.\n const sgr = /^\\x1B\\[([0-9;?]*)m/.exec(raw.slice(i));\n if (sgr) {\n const codes = sgr[1].split(';').map((n) => Number(n) || 0);\n for (let k = 0; k < codes.length; k++) {\n const c = codes[k];\n if (c === 0 || c === 39) fg = 'default';\n else if (c === 38 && codes[k + 1] === 2) { fg = `2;${codes[k + 2]};${codes[k + 3]};${codes[k + 4]}`; k += 4; }\n else if (c === 38 && codes[k + 1] === 5) { fg = `5;${codes[k + 2]}`; k += 2; }\n else if ((c >= 30 && c <= 37) || (c >= 90 && c <= 97)) fg = `basic;${c}`;\n // other codes (bold, bg, underline, …) don't change the fg color\n }\n i += sgr[0].length;\n continue;\n }\n // Any other CSI or OSC escape: consume it without touching color.\n const csi = /^\\x1B\\[[0-9;?]*[ -/]*[@-~]/.exec(raw.slice(i));\n const osc = /^\\x1B\\][^\\x07]*(?:\\x07|\\x1B\\\\)/.exec(raw.slice(i));\n if (csi) { i += csi[0].length; continue; }\n if (osc) { i += osc[0].length; continue; }\n i += 1;\n continue;\n }\n // A printable byte: collect it iff fg is the token-yellow and it isn't a\n // whitespace/control byte (the wrap injects spaces/newlines into the run).\n const ch = raw[i];\n if (fg === TOKEN_YELLOW && !/[\\s\\x00-\\x1F\\x7F]/.test(ch)) token += ch;\n i += 1;\n }\n // The ONLY guard — not a length/shape check, just \"did we see any yellow at\n // all\". An empty capture means color wasn't emitted, not a short token.\n if (!token) {\n // Two distinct causes share this branch. A substantial capture with no\n // yellow run = the terminal didn't emit color. A near-empty capture =\n // setup-token finished without ever rendering a token, i.e. the OAuth\n // approval never came back (same root cause as the non-zero-exit path).\n const reason = raw.length < 200\n ? OAUTH_HINT\n : `is the terminal emitting color? (captured ${raw.length}b but no token-yellow run)`;\n reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));\n return;\n }\n resolve(token);\n });\n });\n}\n","/**\n * synkro setup-github — interactive setup for PR scanning.\n *\n * Flow:\n * 1. Ensure user is logged in (has JWT + user_id + org_id in ~/.synkro/credentials.json).\n * 2. Check if GitHub is connected via WorkOS Pipes.\n * 3. If not, get Pipes OAuth URL and open browser for the user to authorize.\n * 4. Poll until GitHub connection is established.\n * 5. Run `claude setup-token` to get Claude Code OAuth token.\n * 6. List accessible repos via GitHub API (using Pipes token).\n * 7. Interactive multi-select.\n * 8. Push secrets (SYNKRO_API_KEY + CLAUDE_CODE_OAUTH_TOKEN) to each repo.\n * 9. Write .github/workflows/synkro.yml to the local repo if cwd is one.\n */\nimport { createInterface } from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport {\n listAccessibleRepos,\n pushSecretsToRepo,\n writeWorkflowFile,\n findGitRoot,\n SECRET_NAMES,\n WORKFLOW_RELATIVE_PATH,\n} from '../installer/githubSetup.js';\nimport { isAuthenticated, getAccessToken, getUserInfo } from '../auth/stub.js';\nimport { captureClaudeSetupToken } from '../local-cc/setupToken.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfig(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nasync function prompt(rl: ReturnType<typeof createInterface>, q: string, opts: { silent?: boolean } = {}): Promise<string> {\n if (opts.silent) {\n process.stdout.write(q);\n const wasRaw = (process.stdin as any).isRaw;\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(true);\n return await new Promise<string>((resolve) => {\n let chunk = '';\n const onData = (data: Buffer) => {\n const s = data.toString('utf-8');\n if (s === '\\r' || s === '\\n' || s === '\\r\\n') {\n process.stdin.removeListener('data', onData);\n if ((process.stdin as any).setRawMode) (process.stdin as any).setRawMode(wasRaw ?? false);\n process.stdout.write('\\n');\n resolve(chunk);\n return;\n }\n if (s === '\u0003') process.exit(130);\n if (s === '' || s === '\\b') { chunk = chunk.slice(0, -1); return; }\n chunk += s;\n };\n process.stdin.on('data', onData);\n });\n }\n return await rl.question(q);\n}\n\nfunction openBrowser(url: string): void {\n const os = platform();\n let bin: string;\n let args: string[];\n switch (os) {\n case 'darwin': bin = 'open'; args = [url]; break;\n case 'win32': bin = 'cmd'; args = ['/c', 'start', '', url]; break;\n default: bin = 'xdg-open'; args = [url]; break;\n }\n execFile(bin, args, () => {});\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nasync function apiCall<T = any>(gatewayUrl: string, jwt: string, path: string, opts: RequestInit = {}): Promise<T> {\n const resp = await fetch(`${gatewayUrl}${path}`, {\n ...opts,\n headers: {\n 'Authorization': `Bearer ${jwt}`,\n 'Content-Type': 'application/json',\n ...(opts.headers || {}),\n },\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`API ${resp.status}: ${text.slice(0, 200)}`);\n }\n return resp.json() as Promise<T>;\n}\n\n/**\n * Connect GitHub via WorkOS Pipes OAuth. Returns the token if connected, null if user\n * declines or times out. Exported so `install` can embed this in its flow.\n */\nexport async function connectGitHub(gatewayUrl: string, jwt: string, opts: { silent?: boolean } = {}): Promise<string | null> {\n // Check if already connected\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log(' ✓ GitHub already connected via Synkro.');\n return result.token;\n }\n } catch {}\n\n // Get Pipes OAuth URL and open browser\n if (!opts.silent) console.log(' Opening browser to authorize GitHub...');\n try {\n const authResp = await apiCall<{ url: string }>(\n gatewayUrl, jwt, '/api/pipes-widget/authorize/github', { method: 'POST', body: '{}' },\n );\n openBrowser(authResp.url);\n if (!opts.silent) console.log(' Waiting for authorization...');\n } catch (err) {\n if (!opts.silent) console.error(` Failed to start GitHub authorization: ${(err as Error).message}`);\n return null;\n }\n\n // Poll until connected (max 2 minutes)\n const deadline = Date.now() + 120_000;\n while (Date.now() < deadline) {\n await sleep(2000);\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n if (!opts.silent) console.log('\\n ✓ GitHub connected!');\n return result.token;\n }\n } catch {}\n if (!opts.silent) process.stdout.write('.');\n }\n if (!opts.silent) console.error('\\n Timed out waiting for GitHub authorization.');\n return null;\n}\n\nexport interface SetupGithubOptions {\n nonInteractive?: boolean;\n githubToken?: string;\n skipClaudeToken?: boolean;\n}\n\nexport async function setupGithubCommand(opts: SetupGithubOptions = {}): Promise<void> {\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro-cli login` first.');\n process.exit(1);\n }\n const config = readConfig();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const jwt = getAccessToken();\n if (!jwt) {\n console.error('Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.');\n process.exit(1);\n }\n\n // ── 1. Mint CI API key ──────────────────────────────────────────────\n console.log('Requesting CI API key from Synkro...');\n let synkroCiApiKey: string;\n try {\n const minted = await apiCall<{ api_key: string; expires_at: string }>(\n gatewayUrl, jwt, '/api/v1/cli/ci-api-key', { method: 'POST', body: '{}' },\n );\n synkroCiApiKey = minted.api_key;\n console.log(` ✓ Issued CI key (${synkroCiApiKey.slice(0, 18)}…), expires ${minted.expires_at.slice(0, 10)}`);\n } catch (err) {\n console.error(`Failed to mint CI API key: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // ── 2. Get GitHub token via WorkOS Pipes ────────────────────────────\n let ghToken: string;\n\n if (opts.githubToken) {\n ghToken = opts.githubToken;\n } else if (opts.nonInteractive) {\n // In non-interactive mode (CI), try Pipes first, fall back to gh CLI\n try {\n const result = await apiCall<{ connected: boolean; token?: string }>(\n gatewayUrl, jwt, '/api/v1/cli/github-token',\n );\n if (result.connected && result.token) {\n ghToken = result.token;\n } else {\n throw new Error('not connected');\n }\n } catch {\n try {\n ghToken = execSync('gh auth token', { encoding: 'utf-8', timeout: 5000 }).trim();\n } catch {\n console.error('GitHub not connected. Run `synkro-cli setup-github` interactively to connect.');\n return;\n }\n }\n } else {\n // Interactive mode — use Pipes OAuth\n console.log('\\nConnecting to GitHub...');\n const token = await connectGitHub(gatewayUrl, jwt);\n if (!token) {\n console.error('GitHub connection failed. Try again.');\n process.exit(1);\n }\n ghToken = token;\n console.log();\n }\n\n // ── 3. Claude Code OAuth token ──────────────────────────────────────\n let claudeToken: string | undefined;\n if (!opts.skipClaudeToken) {\n console.log('Generating Claude Code OAuth token...');\n console.log(' A browser window will open — authorize with your Claude account.\\n');\n try {\n claudeToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(`Failed to get Claude token: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n if (!claudeToken.startsWith('sk-ant-oat01-')) {\n console.error('Invalid token received from `claude setup-token`. Expected sk-ant-oat01-...');\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n console.log(' Validating token...');\n try {\n const validateResult = execSync(\n 'claude --print --output-format json \"say ok\"',\n { env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken }, encoding: 'utf-8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] },\n );\n const result = JSON.parse(validateResult);\n if (result.is_error) throw new Error(result.result || 'auth failed');\n console.log(' ✓ Token validated.\\n');\n } catch (err) {\n console.error(`Token validation failed: ${err instanceof Error ? err.message : String(err)}`);\n if (opts.nonInteractive) return;\n process.exit(1);\n }\n }\n\n // ── 4. Select repos ─────────────────────────────────────────────────\n let selected: Array<{ owner: string; repo: string; full_name: string }>;\n if (opts.nonInteractive) {\n let currentFullName: string | null = null;\n try {\n const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 5000 }).trim();\n const m = remoteUrl.match(/(?:github\\.com)[:/](.+?)(?:\\.git)?$/);\n if (m) currentFullName = m[1];\n } catch {}\n if (!currentFullName) {\n console.warn(' ⚠ Not in a GitHub repo. Skipping PR scan setup.');\n return;\n }\n const [owner, repo] = currentFullName.split('/');\n selected = [{ owner, repo, full_name: currentFullName }];\n console.log(` Auto-selected repo: ${currentFullName}`);\n } else {\n console.log('Fetching accessible repos...');\n const repos = await listAccessibleRepos({ token: ghToken! });\n if (repos.length === 0) {\n console.error('No accessible repos found. Check your GitHub permissions.');\n process.exit(1);\n }\n console.log(`\\nFound ${repos.length} accessible repo(s):\\n`);\n repos.slice(0, 100).forEach((r, i) => {\n console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);\n });\n console.log();\n const rl2 = createInterface({ input, output });\n const selectionRaw = await prompt(rl2, 'Select repos to enable (comma-separated numbers, e.g. 1,3,5): ');\n const selectedIdx = selectionRaw\n .split(',')\n .map((s) => parseInt(s.trim(), 10) - 1)\n .filter((n) => !isNaN(n) && n >= 0 && n < repos.length);\n if (selectedIdx.length === 0) {\n console.error('No valid selections.');\n rl2.close();\n process.exit(1);\n }\n selected = selectedIdx.map((i) => repos[i]);\n console.log(`\\nWill push secrets to ${selected.length} repo(s):`);\n for (const r of selected) console.log(` • ${r.full_name}`);\n console.log();\n const confirm = (await prompt(rl2, 'Continue? (yes/no): ')).trim().toLowerCase();\n if (confirm !== 'yes' && confirm !== 'y') {\n console.log('Cancelled.');\n rl2.close();\n process.exit(0);\n }\n rl2.close();\n }\n\n // ── 5. Push secrets ─────────────────────────────────────────────────\n console.log();\n for (const r of selected) {\n process.stdout.write(`Pushing secrets to ${r.full_name}... `);\n try {\n await pushSecretsToRepo(\n { token: ghToken! },\n r.owner,\n r.repo,\n {\n claudeCodeOauthToken: claudeToken,\n synkroApiKey: synkroCiApiKey,\n },\n );\n console.log('✓');\n } catch (err) {\n console.log(`✗ (${(err as Error).message})`);\n }\n }\n\n // ── 6. Write workflow file ──────────────────────────────────────────\n console.log();\n const gitRoot = findGitRoot(process.cwd());\n if (gitRoot) {\n const written = writeWorkflowFile(gitRoot);\n if (written) {\n console.log(`Wrote workflow: ${written}`);\n console.log('Commit and push it to enable PR scanning.');\n }\n } else {\n console.log('Not in a git repo. To enable scanning, add this file to your repo:');\n console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);\n console.log(` Content: run \\`synkro-cli setup-github\\` from inside a repo to write it automatically`);\n }\n\n console.log();\n console.log('✓ PR scan setup complete.');\n console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);\n console.log('Open a PR on any selected repo to trigger your first Synkro scan.');\n}\n","// :)\n/**\n * synkro install — first-time setup on customer's machine.\n *\n * Detects installed AI agents (Claude Code, Cursor), drops hook scripts in\n * ~/.synkro/hooks/, writes settings.json hook entries, fetches the latest\n * Edit/Write prompt from the gateway and inlines it into settings, writes\n * config.env. Triggers `synkro login` if not already authed.\n */\nimport { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, readdirSync, unlinkSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { execSync, spawnSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { createInterface } from 'node:readline';\nimport { createHash } from 'node:crypto';\nimport { detectAgents, type DetectedAgent } from '../installer/agentDetect.js';\nimport { installCCHooks, uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { installCursorHooks, uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { installMcpConfig, installCursorMcpConfig, uninstallMcpConfig, uninstallCursorMcpConfig, uninstallClaudeDesktopMcpConfig } from '../installer/mcpConfig.js';\nimport { resolveSkillPaths } from '../installer/skillParser.js';\nimport { emit, setTelemetryState } from '../telemetry/index.js';\nimport { STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_BASH_FOLLOWUP_TS, STUB_AGENT_JUDGE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_INSTALL_SCAN_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_MCP_GATE_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS } from '../installer/hookScriptsTs.js';\nimport { isAuthenticated, getAccessToken, authenticate, getUserInfo, ensureValidToken, refreshToken } from '../auth/stub.js';\nimport { promptRepoConnection } from './repoConnect.js';\nimport { setApiBaseUrl, listProjects } from '../api/projects.js';\n// connectGitHub import removed — GitHub PR scanning is hidden; the install no\n// longer connects GitHub. setupGithub.ts is kept for when it's re-enabled.\nimport { fetchJudgePrompts } from '../installer/promptFetcher.js';\nimport { claudeDesktopInstalled } from '../installer/claudeDesktopTap.js';\nimport { dockerInstall, waitForContainerReady, waitForWorkersReady, splitWorkers, resolveGraderPool, poolLabel, dockerStatus, dockerSafeStop, dockerRemove, type WorkerProvider } from '../local-cc/dockerInstall.js';\nimport { captureClaudeSetupToken } from '../local-cc/setupToken.js';\n\n// Replaced by tsup `define` at build time.\ndeclare const __SYNKRO_CLI_VERSION__: string;\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst HOOKS_DIR = join(SYNKRO_DIR, 'hooks');\nconst BIN_DIR = join(SYNKRO_DIR, 'bin');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\n// Hooks are ALWAYS thin stubs now — the legacy self-contained \"full\" hooks were\n// removed once cloud grading moved server-side (stub → gateway /grade/submit →\n// scanRouter). Kept as a function so callers/config stay stable.\nfunction resolvePersistedHookMode(): 'stub' {\n return 'stub';\n}\n\nconst MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline';\n\nconst HOME = homedir();\nconst TOKEN_PATH = join(HOME, '.synkro', '.mcp-jwt');\nconst PORT = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\nconst URL = \\`http://127.0.0.1:\\${PORT}\\`;\n\n// Re-read the JWT on EVERY request, not once at spawn: a \\`synkro restart\\` rotates\n// the token, and a proxy that cached the old one would 401 every call until it\n// was respawned (a manual /mcp).\nfunction loadToken() {\n try { return readFileSync(TOKEN_PATH, 'utf-8').trim(); } catch { return ''; }\n}\nfunction write(obj) { try { process.stdout.write(JSON.stringify(obj) + '\\\\n'); } catch {} }\n\n// Answer \\`initialize\\` LOCALLY without touching the backend. Claude Code latches an\n// MCP server as failed — forcing a manual /mcp — only when the child process exits\n// or the initialize handshake fails. A \\`synkro restart\\` takes the backend down for\n// ~30-60s, so if CC (re)connects in that window a forwarded initialize would fail\n// and the server would be dead until /mcp. Replying here makes the handshake\n// succeed unconditionally; tools/list and tools/call still hit the backend.\nfunction localInitialize(msg) {\n return {\n jsonrpc: '2.0',\n id: msg.id,\n result: {\n protocolVersion: (msg && msg.params && msg.params.protocolVersion) || '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'synkro-guardrails-local', version: '1.0.0' },\n },\n };\n}\n\n// Cap a single JSON-RPC line. This proxy only carries small tool-call envelopes;\n// refusing oversized input keeps a malicious or runaway client from buffering\n// unbounded memory (CWE-400).\nconst MAX_LINE_BYTES = 4 * 1024 * 1024;\n\nconst rl = createInterface({ input: process.stdin, terminal: false });\n\nrl.on('line', async (line) => {\n if (!line.trim()) return;\n if (line.length > MAX_LINE_BYTES) return;\n let msg;\n try { msg = JSON.parse(line); } catch { return; }\n // Notifications carry no id and need no reply.\n if (msg.id == null && typeof msg.method === 'string' && msg.method.startsWith('notifications/')) return;\n\n // initialize: try the backend briefly for the real instructions, but ALWAYS\n // fall back to a local reply so the handshake can never block or fail.\n if (msg.method === 'initialize') {\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': \\`Bearer \\${loadToken()}\\` },\n body: line,\n signal: AbortSignal.timeout(2500),\n });\n if (resp.ok) { process.stdout.write((await resp.text()) + '\\\\n'); return; }\n } catch {}\n write(localInitialize(msg));\n return;\n }\n\n // tool calls: forward, allowing a generous per-attempt window for slow calls\n // (create + decompose). If the backend is UNREACHABLE (mid-restart), retry only\n // briefly, then return a CLEAN JSON-RPC error and move on. Never hold the request\n // open for tens of seconds — a stdio request held that long wedges CC's MCP\n // client and every later call queues behind it. A returned error keeps the\n // client responsive and the server connected; the agent just retries.\n const REFUSED_BUDGET_MS = 4000;\n const deadline = Date.now() + REFUSED_BUDGET_MS;\n let attempt = 0;\n while (true) {\n try {\n const resp = await fetch(URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Authorization': \\`Bearer \\${loadToken()}\\` },\n body: line,\n signal: AbortSignal.timeout(30000),\n });\n if (resp.status === 204) return;\n process.stdout.write((await resp.text()) + '\\\\n');\n return;\n } catch (err) {\n // A 30s timeout means the backend was UP but slow — don't spin. ECONNREFUSED /\n // fetch failed means it's unreachable → retry briefly within the budget.\n const name = err && err.name;\n const isTimeout = name === 'TimeoutError' || name === 'AbortError';\n if (!isTimeout && Date.now() < deadline) {\n attempt++;\n await new Promise((r) => setTimeout(r, Math.min(500, 150 * attempt)));\n continue;\n }\n if (msg.id != null) {\n write({\n jsonrpc: '2.0',\n id: msg.id,\n error: { code: -32603, message: 'Synkro MCP backend unavailable (container may be restarting) — retry in a moment' },\n });\n }\n return;\n }\n }\n});\n`;\n\ninterface InstallOptions {\n gatewayUrl?: string; // override default\n apiKey?: string; // skip prompting if provided (for tests/CI)\n skipAuth?: boolean;\n noMcp?: boolean; // skip registering the guardrails MCP server\n force?: boolean; // bypass the \"already installed\" short-circuit\n linkRepo?: boolean; // auto-link current repo (non-interactive)\n cursorApiKey?: string; // Cursor API key for the cursor grader pool\n hookMode?: 'full' | 'stub'; // 'stub' = thin pipe hooks; logic in the container\n byokGateway?: boolean; // legacy: no-container BYOK grading via the gateway\n}\n\n// Accept a gateway URL only if it's a plain http(s) URL. The published CLI is\n// invoked from arbitrary cwds where a developer's monorepo .env / op://\n// reference / direnv export may have poisoned SYNKRO_GATEWAY_URL with a value\n// the CLI can't actually call (e.g. \"op://dev/synkro/gateway-url\"). Silently\n// ignoring those falls through to the prod default so customers never have to\n// wrestle with shell hygiene before `synkro install` works.\nfunction sanitizeGatewayCandidate(raw: string | undefined): string | undefined {\n if (!raw) return undefined;\n return /^https?:\\/\\//.test(raw) ? raw : undefined;\n}\n\nexport function parseArgs(argv: string[]): InstallOptions {\n const opts: InstallOptions = {};\n for (const a of argv) {\n if (a.startsWith('--api-key=')) opts.apiKey = a.slice('--api-key='.length);\n else if (a.startsWith('--gateway=')) opts.gatewayUrl = a.slice('--gateway='.length);\n else if (a.startsWith('--cursor-api-key=')) opts.cursorApiKey = a.slice('--cursor-api-key='.length);\n else if (a === '--skip-auth') opts.skipAuth = true;\n else if (a === '--no-mcp') opts.noMcp = true;\n else if (a === '--force' || a === '-f') opts.force = true;\n else if (a === '--link-repo') opts.linkRepo = true;\n else if (a === '--byok-gateway') opts.byokGateway = true;\n }\n // Hooks are always thin stubs now (the legacy \"full\" hooks were removed); the\n // --stub/--mode/--legacy flags are accepted-and-ignored for backward compat.\n if (!opts.gatewayUrl) {\n const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);\n if (fromEnv) opts.gatewayUrl = fromEnv;\n }\n // NOTE: we deliberately do NOT pick up SYNKRO_API_KEY from process.env. The\n // root .env / global env may contain a stale or unrelated SYNKRO_API_KEY.\n // The legitimate sources are: --api-key flag, or fresh OAuth via authenticate().\n return opts;\n}\n\n// Ask the user which detected agents they want guardrails installed for.\n// If only one is detected, no prompt — return as-is. If multiple, show a\n// picker that accepts: \"1\" (first), \"2\" (second), \"3\"/\"both\"/\"all\"/empty\n// (everything). Anything else re-prompts.\nasync function promptAgentSelection<T extends { kind: string; name: string }>(\n detected: T[],\n): Promise<T[]> {\n if (detected.length <= 1) return detected;\n\n console.log('Multiple coding agents detected. Which ones do you want Synkro guardrails installed for?');\n detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));\n console.log(` ${detected.length + 1}. Both / all (default)`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (): Promise<T[]> => new Promise((resolve) => {\n rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {\n const t = answer.trim().toLowerCase();\n if (t === '' || t === String(detected.length + 1) || t === 'both' || t === 'all') {\n rl.close();\n return resolve(detected);\n }\n const n = parseInt(t, 10);\n if (Number.isInteger(n) && n >= 1 && n <= detected.length) {\n rl.close();\n return resolve([detected[n - 1]]);\n }\n console.log('Invalid choice. Try again.');\n resolve(ask());\n });\n });\n return ask();\n}\n\n// Collect the Cursor API key when the install includes a Cursor worker pool.\n// Sources, in order: --cursor-api-key flag, SYNKRO_CURSOR_API_KEY env, then\n// an interactive paste prompt. An already-configured key is kept only if\n// Cursor still accepts it.\nasync function promptCursorApiKey(opts: InstallOptions): Promise<void> {\n const { cursorApiKeyConfigured, writeCursorApiKey, validateCursorApiKey } = await import('../local-cc/macKeychain.js');\n if (cursorApiKeyConfigured()) {\n // A key file exists — confirm Cursor still accepts it before trusting it.\n // Only a definitive rejection (false) re-prompts; a network blip (null)\n // leaves the working key alone.\n const valid = await validateCursorApiKey();\n if (valid !== false) {\n console.log(' ✓ Cursor API key validated.');\n return;\n }\n console.warn(' ⚠ The saved Cursor API key was rejected by Cursor (revoked or expired).');\n console.warn(' Paste a fresh key below, or press Enter to skip (Cursor workers stay idle).');\n // fall through to re-source / re-prompt the key\n }\n\n const provided = (opts.cursorApiKey || process.env.SYNKRO_CURSOR_API_KEY || '').trim();\n if (provided) {\n writeCursorApiKey(provided);\n console.log(' ✓ Cursor API key saved to ~/.synkro/cursor-creds/api-key');\n return;\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const key = await new Promise<string>((resolve) => {\n rl.question(\n 'Cursor grading needs a Cursor API key (cursor.com → Settings → API Keys).\\n' +\n 'Paste it now, or press Enter to skip (Cursor workers stay idle until set): ',\n (answer) => { rl.close(); resolve(answer.trim()); },\n );\n });\n if (key) {\n writeCursorApiKey(key);\n console.log(' ✓ Cursor API key saved.');\n } else {\n console.log(' ⚠ Skipped — Cursor workers will be idle. Re-run install or pass --cursor-api-key=… later.');\n }\n}\n\n// ONE deployment choice: WHERE the grading worker container runs.\n// local — a container on THIS machine (Docker); storage = local PGLite.\n// cloud — a private PER-ORG container hosted by Synkro (Cloudflare Containers);\n// storage = Synkro Timescale; authed by the org's own setup-token.\n// BYOK applies to both (local = your keychain Claude; cloud = your setup-token).\n// Default — and the non-TTY answer — is local. The legacy no-container BYOK-\n// gateway grading path is still reachable via `--byok-gateway` or an existing\n// .synkro with `mode = \"byok\"`.\n// Ask where grading runs — local (Docker container on this machine) vs cloud (CF container).\n// `current` is the default (from synkro.toml / config.env): a bare Enter keeps it, and a\n// non-interactive shell keeps it without prompting. Asked on EVERY install/restart — the deploy\n// target is an explicit choice, not something silently inferred from the toml (which confused\n// local-vs-cloud). The toml still holds local config details; this just picks the target.\nasync function promptDeployLocation(current: 'local' | 'cloud' = 'local'): Promise<'local' | 'cloud'> {\n if (!process.stdin.isTTY) return current;\n const other = current === 'cloud' ? 'local' : 'cloud';\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Where should Synkro run?\\n' +\n ' local — a grading container on this machine (Docker)\\n' +\n ' cloud — a private container hosted by Synkro\\n' +\n `Both use your own Claude key. Choose [${current}] / ${other}: `,\n (answer) => {\n rl.close();\n const a = answer.trim().toLowerCase();\n resolve(a === 'cloud' ? 'cloud' : a === 'local' ? 'local' : current);\n },\n );\n });\n}\n\n// Harness-aware transcript-import consent. Offers to import the history of the\n// agent(s) the user actually selected — not always Claude Code. With both\n// selected, the user picks which source(s) to pull.\nasync function promptTranscriptSources(wantCC: boolean, wantCursor: boolean): Promise<{ cc: boolean; cursor: boolean }> {\n if (!wantCC && !wantCursor) return { cc: false, cursor: false };\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));\n try {\n if (wantCC && wantCursor) {\n console.log('Import and embed past session history? This indexes sessions so Ask Synkro');\n console.log('can answer questions about your patterns and the dashboard shows full history.');\n console.log(' 1. Cursor only');\n console.log(' 2. Claude Code only');\n console.log(' 3. Both');\n console.log(' n. Skip');\n const a = await ask('Choose [1/2/3/n] (default: 3): ');\n if (a === 'n' || a === 'no') return { cc: false, cursor: false };\n if (a === '1') return { cc: false, cursor: true };\n if (a === '2') return { cc: true, cursor: false };\n return { cc: true, cursor: true }; // '', '3', or anything else → both\n }\n const which = wantCursor ? 'Cursor' : 'Claude Code';\n const a = await ask(\n `Import and embed your ${which} session history?\\n` +\n 'This indexes past sessions so Ask Synkro can answer questions about your\\n' +\n 'coding patterns and the dashboard shows full history. (Y/n) ',\n );\n const yes = a === '' || a === 'y' || a === 'yes';\n return { cc: wantCC && yes, cursor: wantCursor && yes };\n } finally {\n rl.close();\n }\n}\n\n// Opt-in: connect a Codex (ChatGPT subscription) session for cloud grading. The\n// browser approval is the user's consent; the session is stored + refreshed\n// internally by Synkro (never a public endpoint). Non-TTY defaults to skip.\nasync function promptCodexCloudSetup(): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\n 'Connect a Codex (ChatGPT subscription) session for cloud grading? This opens a\\n' +\n 'browser to authorize; your subscription is stored securely and refreshed for you. (y/N) ',\n (answer) => {\n rl.close();\n const a = answer.trim().toLowerCase();\n resolve(a === 'y' || a === 'yes');\n },\n );\n });\n}\n\nconst OFFSETS_DIR = join(SYNKRO_DIR, '.transcript-offsets');\n\nfunction ensureSynkroDir(): void {\n mkdirSync(SYNKRO_DIR, { recursive: true });\n mkdirSync(HOOKS_DIR, { recursive: true });\n mkdirSync(BIN_DIR, { recursive: true });\n mkdirSync(OFFSETS_DIR, { recursive: true });\n mkdirSync(join(SYNKRO_DIR, 'sessions'), { recursive: true });\n}\n\nexport function writeHookScripts(): {\n bashScript: string;\n skillJudgeScript: string;\n cursorSkillJudgeScript: string;\n bashFollowupScript: string;\n editPrecheckScript: string;\n cwePrecheckScript: string;\n cvePrecheckScript: string;\n planJudgeScript: string;\n agentJudgeScript: string;\n stopSummaryScript: string;\n sessionStartScript: string;\n transcriptSyncScript: string;\n userPromptSubmitScript: string;\n installScanScript: string;\n cursorBashJudgeScript: string;\n cursorEditCaptureScript: string;\n cursorAgentCaptureScript: string;\n taskActivateIntentScript: string;\n mcpGateScript: string;\n} {\n // Source is inlined at build time via tsup's `define` (see tsup.config.ts).\n // Reading it from disk doesn't work after npm publish because packages/api\n // isn't shipped with the CLI tarball.\n const installExtractCorePath = join(HOOKS_DIR, 'installExtractCore.ts');\n\n const bashScriptPath = join(HOOKS_DIR, 'cc-bash-judge.ts');\n const skillJudgeScriptPath = join(HOOKS_DIR, 'cc-skill-judge.ts');\n const cursorSkillJudgePath = join(HOOKS_DIR, 'cursor-skill-judge.ts');\n const bashFollowupScriptPath = join(HOOKS_DIR, 'cc-bash-followup.ts');\n const editPrecheckScriptPath = join(HOOKS_DIR, 'cc-edit-precheck.ts');\n const cwePrecheckScriptPath = join(HOOKS_DIR, 'cc-cwe-precheck.ts');\n const cvePrecheckScriptPath = join(HOOKS_DIR, 'cc-cve-precheck.ts');\n const planJudgeScriptPath = join(HOOKS_DIR, 'cc-plan-judge.ts');\n const agentJudgeScriptPath = join(HOOKS_DIR, 'cc-agent-judge.ts');\n const stopSummaryScriptPath = join(HOOKS_DIR, 'cc-stop-summary.ts');\n const sessionStartScriptPath = join(HOOKS_DIR, 'cc-session-start.ts');\n const transcriptSyncScriptPath = join(HOOKS_DIR, 'cc-transcript-sync.ts');\n const userPromptSubmitScriptPath = join(HOOKS_DIR, 'cc-user-prompt-submit.ts');\n const commonScriptPath = join(HOOKS_DIR, '_synkro-common.ts');\n const commonBashScriptPath = join(HOOKS_DIR, '_synkro-common.sh');\n const installScanScriptPath = join(HOOKS_DIR, 'cc-install-scan.ts');\n const cursorBashJudgePath = join(HOOKS_DIR, 'cursor-bash-judge.ts');\n const cursorEditCapturePath = join(HOOKS_DIR, 'cursor-edit-capture.ts');\n const cursorAgentCapturePath = join(HOOKS_DIR, 'cursor-agent-capture.ts');\n const mcpStdioProxyPath = join(HOOKS_DIR, 'mcp-stdio-proxy.ts');\n const taskActivateIntentScriptPath = join(HOOKS_DIR, 'cc-task-activate-intent.ts');\n // MCP server governance gate. Always a thin forwarder (the gate logic lives in\n // the server's scanRouter), so it's the stub in both full and stub modes.\n const mcpGateScriptPath = join(HOOKS_DIR, 'cc-mcp-gate.ts');\n\n // ─── Stub mode: write ~3-line delegating stubs + the plumbing common ───\n // Same filenames as full mode (so the CC/Cursor hook registration is\n // unchanged), but every hook just pipes a fixed envelope to the local\n // server's /api/scan/{surface}. No scanning logic lands on disk — all the\n // brains live in the container, updatable via a GHCR push alone.\n const stubCommonPath = join(HOOKS_DIR, '_synkro-stub-common.ts');\n const stubFiles: Array<[string, string]> = [\n [stubCommonPath, STUB_COMMON_TS],\n [bashScriptPath, STUB_BASH_JUDGE_TS],\n [skillJudgeScriptPath, STUB_SKILL_JUDGE_TS],\n [cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],\n [bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],\n [editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],\n [cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],\n [cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],\n [planJudgeScriptPath, STUB_PLAN_JUDGE_TS],\n [agentJudgeScriptPath, STUB_AGENT_JUDGE_TS],\n [stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],\n [sessionStartScriptPath, STUB_SESSION_START_TS],\n [transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],\n [userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],\n [installScanScriptPath, STUB_INSTALL_SCAN_TS],\n [taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS],\n [mcpGateScriptPath, STUB_MCP_GATE_TS],\n [cursorBashJudgePath, STUB_CURSOR_BASH_JUDGE_TS],\n [cursorEditCapturePath, STUB_CURSOR_EDIT_CAPTURE_TS],\n [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS],\n ];\n for (const [p, content] of stubFiles) {\n writeFileSync(p, content, 'utf-8');\n chmodSync(p, 0o755);\n }\n // The MCP stdio proxy is the guardrails MCP server bridge, not hook logic —\n // needed in both modes for the `synkro-guardrails` tools.\n writeFileSync(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, 'utf-8');\n chmodSync(mcpStdioProxyPath, 0o755);\n // Remove full-mode-only artifacts so a stub install leaves a genuinely\n // minimal hooks dir — no dead 2,500-line common, no install-extract core.\n // (Stubs import only _synkro-stub-common.ts.)\n for (const stale of ['_synkro-common.ts', '_synkro-common.sh', 'installExtractCore.ts']) {\n try { unlinkSync(join(HOOKS_DIR, stale)); } catch {}\n }\n return {\n bashScript: bashScriptPath,\n skillJudgeScript: skillJudgeScriptPath,\n cursorSkillJudgeScript: cursorSkillJudgePath,\n bashFollowupScript: bashFollowupScriptPath,\n editPrecheckScript: editPrecheckScriptPath,\n cwePrecheckScript: cwePrecheckScriptPath,\n cvePrecheckScript: cvePrecheckScriptPath,\n planJudgeScript: planJudgeScriptPath,\n agentJudgeScript: agentJudgeScriptPath,\n stopSummaryScript: stopSummaryScriptPath,\n sessionStartScript: sessionStartScriptPath,\n transcriptSyncScript: transcriptSyncScriptPath,\n userPromptSubmitScript: userPromptSubmitScriptPath,\n installScanScript: installScanScriptPath,\n cursorBashJudgeScript: cursorBashJudgePath,\n cursorEditCaptureScript: cursorEditCapturePath,\n cursorAgentCaptureScript: cursorAgentCapturePath,\n taskActivateIntentScript: taskActivateIntentScriptPath,\n mcpGateScript: mcpGateScriptPath,\n };\n}\n\n// Sanitize values before writing into config.env — the file is sourced as a\n// shell script, so any unquoted shell metacharacter in a value is dangerous\n// (newlines smuggle new assignments; (){}#`$ etc. let an attacker run code\n// on `source config.env`). Two defenses:\n// 1. Strip non-printable ASCII (drops newlines, tabs, control chars)\n// 2. Wrap the result in single-quotes — single-quoted shell strings are\n// entirely literal, so anything inside is safe regardless of contents.\n// Internal single-quotes are escaped using the standard '\\'' trick.\nfunction sanitizeConfigValue(raw: string | undefined, maxLen = 256): string {\n if (!raw) return '';\n return raw\n .replace(/[^\\x20-\\x7E]/g, '') // drop non-printable (newlines/tabs/control)\n .slice(0, maxLen);\n}\n\nfunction shellQuoteSingle(value: string): string {\n // Escape any single quote in `value` for inclusion inside a single-quoted\n // shell string: 'foo'\"'\"'bar' renders as foo'bar.\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Resolve the absolute path to the synkro bundle (dist/bootstrap.js) so hook\n// scripts can invoke `node \"$SYNKRO_CLI_BIN\" grade ...` without relying on\n// the user's shell PATH. Returning a single path (vs a command string) keeps\n// the hook invocation a single quoted arg — no shell-injection surface.\n//\n// process.argv[1] is the script the user executed: with the pnpm/npm shim\n// it's the absolute path to dist/bootstrap.js. With `just synkro install`\n// it's also the absolute bundle path (the just recipe runs `node bootstrap.js`).\nfunction resolveSynkroBundle(): string | null {\n const scriptPath = process.argv[1];\n if (scriptPath && existsSync(scriptPath)) return scriptPath;\n return null;\n}\n\nfunction writeConfigEnv(opts: { gatewayUrl: string; userId?: string; orgId?: string; email?: string; tier?: string; inference?: string; synkroBin?: string | null; transcriptConsent?: boolean; localInference?: boolean; deploymentMode?: 'bare-host' | 'docker'; gradingMode?: 'local' | 'byok'; storageMode?: 'local' | 'cloud'; deployLocation?: 'local' | 'cloud'; hookMode?: 'full' | 'stub' }): void {\n const credsPath = join(SYNKRO_DIR, 'credentials.json');\n const safeGateway = sanitizeConfigValue(opts.gatewayUrl);\n const safeUserId = sanitizeConfigValue(opts.userId);\n const safeOrgId = sanitizeConfigValue(opts.orgId);\n const safeEmail = sanitizeConfigValue(opts.email);\n const safeTier = sanitizeConfigValue(opts.tier ?? 'pro', 32);\n const safeInference = sanitizeConfigValue(opts.inference ?? 'fast', 16);\n // Allow up to 1KB for the bin command since it may be `node /long/path/to/bootstrap.js`.\n const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? '', 1024);\n const lines = [\n '# Synkro CLI config (managed by synkro install)',\n '# JWT auth — the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime',\n '# and send Authorization: Bearer <access_token> on every gateway call.',\n `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,\n `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,\n `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,\n `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,\n `SYNKRO_VERSION=${shellQuoteSingle(__SYNKRO_CLI_VERSION__)}`,\n ];\n if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);\n if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);\n if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);\n if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);\n if (opts.transcriptConsent !== undefined) {\n lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? 'yes' : 'no')}`);\n }\n lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? 'yes' : 'no')}`);\n // Persisted so hooks see the same value without shell-sourced env.\n const safeMode = sanitizeConfigValue(opts.deploymentMode ?? 'docker', 16);\n lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);\n // User-owned data axes — where grading runs and where telemetry is stored.\n lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? 'local', 16))}`);\n lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? 'local', 16))}`);\n // Canonical deployment location — where the grading container runs. The two\n // legacy mode fields above are derived from this for back-compat readers.\n lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle(sanitizeConfigValue(opts.deployLocation ?? 'local', 16))}`);\n // Hook delivery mode — always 'stub' now: thin pipe hooks whose scanning logic\n // lives in the container. Emitted for back-compat readers of the config file.\n lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.hookMode ?? 'stub', 8))}`);\n lines.push('');\n writeFileSync(CONFIG_PATH, lines.join('\\n'), 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Cloud install: capture the org's Claude setup-token (CLOUD ONLY) and POST it\n * to the gateway, which stores it as the org secret and provisions the per-org\n * Cloudflare Containers grader. The token lives only in the request body —\n * never logged, never written to config/.synkro. Local installs never call this.\n */\nconst CLOUD_JWT_PATH = join(SYNKRO_DIR, '.cloud-jwt');\n\n/** True if a JWT is expired (or unparseable), with a 5-minute safety buffer. */\nfunction jwtExpired(token: string): boolean {\n try {\n const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf-8')) as { exp?: number };\n if (!p?.exp) return true;\n return Date.now() > p.exp * 1000 - 5 * 60 * 1000;\n } catch { return true; }\n}\n\n/**\n * Long-lived (1-year) cloud token for cloud CLI→gateway calls (provision /\n * local↔cloud switch). On first use it mints one via /api/v1/cli/cloud-token\n * (which needs a fresh WorkOS JWT) and stores it in ~/.synkro/.cloud-jwt; on\n * every later call it reuses the stored token while still valid and only\n * re-mints when expired. This is why switching deploy location no longer 401s on\n * the 5-minute WorkOS access token — exactly the failure that bit us re-provisioning.\n */\nexport async function getOrMintCloudToken(gatewayUrl: string): Promise<string> {\n // CWE-918: never fetch a caller-supplied gateway URL without the allowlist check.\n assertGatewayAllowed(gatewayUrl);\n\n let stored = '';\n try { stored = readFileSync(CLOUD_JWT_PATH, 'utf-8').trim(); } catch { /* none yet */ }\n if (stored && !jwtExpired(stored)) return stored;\n\n // Expired/missing → mint a fresh one. Minting requires a real user, so\n // force-refresh the WorkOS token first (the CLI's own expiry check has\n // disagreed with the gateway before, so don't trust isTokenExpired here).\n await refreshToken().catch(() => false);\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated — run `synkro login`');\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/cloud-token`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n });\n if (!resp.ok) {\n const t = await resp.text().catch(() => '');\n throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);\n }\n const { token } = await resp.json() as { token: string };\n writeFileSync(CLOUD_JWT_PATH, token + '\\n', { mode: 0o600 });\n return token;\n}\n\nasync function provisionCloudContainer(opts: {\n gatewayUrl: string; jwt: string;\n orgId?: string; userId?: string; email?: string;\n hasClaudeCode: boolean; hasCursor: boolean;\n}): Promise<void> {\n let setupToken: string;\n try {\n console.log(' Authorize your Claude key for the hosted Synkro worker —');\n console.log(' a browser window will open for approval...\\n');\n setupToken = await captureClaudeSetupToken();\n } catch (err) {\n console.error(` ✗ Could not capture Claude setup-token: ${(err as Error).message}`);\n console.error(' Cloud needs a Claude setup-token. Run `claude setup-token` manually, then re-run install.');\n process.exit(1);\n }\n\n // Validate the captured token by ACTUALLY USING IT — the same guard the GitHub\n // path runs (setupGithub.ts). A truncated/wrong-account/no-credits token sails\n // through capture but 401s the moment Claude calls the API; round-tripping it\n // through a real `claude --print` here catches that at the user's machine,\n // BEFORE it's stored in Vault and only fails (silently) inside the container.\n // The container authenticates exactly this way (CLAUDE_CODE_OAUTH_TOKEN), so a\n // pass here means a pass there.\n console.log(' Validating Claude token...');\n // `claude --print` is slow (~10s) and OCCASIONALLY spikes past the deadline — a timeout is\n // flakiness, NOT a bad token (the same install passed once and timed out once). So: retry with\n // a generous deadline, HARD-FAIL only on a REAL auth rejection (is_error), and on a persistent\n // timeout DON'T abort — the cloud smoke-grade below runs THIS token in the actual container\n // (no local login to fall back on) and is the authoritative end-to-end check.\n let validated = false;\n let authRejected: string | null = null;\n let lastErr = '';\n for (let attempt = 1; attempt <= 2 && !validated; attempt++) {\n try {\n const out = execSync('claude --print --output-format json \"say ok\"', {\n env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: setupToken },\n encoding: 'utf-8',\n timeout: 60_000,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const result = JSON.parse(out) as { is_error?: boolean; result?: string };\n if (result.is_error) { authRejected = result.result || 'auth failed'; break; }\n validated = true;\n } catch (err) {\n lastErr = err instanceof Error ? err.message : String(err);\n if (attempt < 2) console.log(` ⏳ validation slow (${lastErr.slice(0, 30)}…) — retrying once...`);\n }\n }\n if (authRejected) {\n console.error(` ✗ Claude token rejected: ${authRejected}`);\n console.error(' Re-run install to re-authorize (wrong account or no API credits) — nothing was stored.');\n process.exit(1);\n }\n if (validated) {\n console.log(' ✓ Claude token validated.\\n');\n } else {\n console.warn(` ⚠ Couldn't confirm the token locally in time (${lastErr.slice(0, 50)}).`);\n console.warn(' Proceeding — the cloud smoke-grade below verifies it end-to-end in the container.\\n');\n }\n\n const repo = detectGitRepo() || undefined;\n const sf = readFullSynkroFile();\n const harness: string[] = [];\n if (opts.hasClaudeCode) harness.push('claude-code');\n if (opts.hasCursor) harness.push('cursor');\n // Honor the worker count from synkro.toml ([grader] claude-code = N); fall back\n // to the env override, then a sane cloud default. This is what the org's\n // container boots — clamped server-side to whatever the instance can hold.\n const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '', 10);\n const total = (sf?.workers?.claude && sf.workers.claude > 0)\n ? sf.workers.claude\n : (Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4);\n // Cursor pool size from synkro.toml ([grader] cursor = N); 0 if not configured.\n const cursorTotal = (sf?.workers?.cursor && sf.workers.cursor > 0) ? sf.workers.cursor : 0;\n // When a cursor pool is configured, ship the org's Cursor API key (written to\n // ~/.synkro/cursor-creds/api-key by promptCursorApiKey) so cloud cursor workers\n // can grade. Only in the body — never logged or written to .synkro config.\n let cursorApiKey = '';\n if (cursorTotal > 0) {\n try { cursorApiKey = readFileSync(join(SYNKRO_DIR, 'cursor-creds', 'api-key'), 'utf-8').trim(); } catch {}\n }\n // Use the long-lived cloud token so this — and especially a later local↔cloud\n // switch — never 401s on an expired 5-min WorkOS token. Falls back to the\n // session JWT if the cloud token can't be obtained.\n let cloudJwt = opts.jwt;\n try { cloudJwt = await getOrMintCloudToken(opts.gatewayUrl); }\n catch (e) { console.warn(` (cloud token unavailable, using session token: ${(e as Error).message})`); }\n\n try {\n const resp = await fetch(`${opts.gatewayUrl}/api/v1/cli/cloud-provision`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${cloudJwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n org_id: opts.orgId,\n user_id: opts.userId,\n email: opts.email,\n harness,\n claude_workers: total,\n cursor_workers: cursorTotal,\n cursor_api_key: cursorApiKey, // never logged; gateway stores it as the org secret\n connected_repo: repo,\n ruleset: readFullSynkroFile()?.ruleset || 'default',\n setup_token: setupToken, // never logged; gateway stores it as the org secret\n }),\n });\n if (!resp.ok) {\n const t = await resp.text().catch(() => '');\n throw new Error(`cloud-provision failed (${resp.status}): ${t.slice(0, 200)}`);\n }\n const out = await resp.json().catch(() => ({})) as { status?: string; container_id?: string; expires_at?: string };\n console.log(` ✓ cloud container provisioned${out.container_id ? ` (${out.container_id})` : ''}`);\n console.log();\n } catch (err) {\n console.error(` ✗ Cloud provisioning failed: ${(err as Error).message}`);\n console.error(' Hooks + MCP are installed; re-run `synkro install` → cloud to retry.');\n console.log();\n } finally {\n setupToken = ''; // clear from memory\n }\n}\n\n/**\n * On a failed smoke grade, fetch the container's per-worker log tails (the actual\n * claude error — auth, crash, etc.) and print them. This is the only way to see\n * the container-internal failure (CF doesn't surface container stdout).\n */\nasync function printWorkerDebug(base: string, jwt: string): Promise<void> {\n try {\n console.warn(' ── container worker logs (the actual claude error) ──');\n const r = await fetch(`${base}/debug`, { headers: { Authorization: `Bearer ${jwt}` }, signal: AbortSignal.timeout(90_000) });\n const d = (await r.json().catch(() => null)) as\n | { grader_error_code?: string | null; grader_error_detail?: string | null; workers?: Array<{ worker: string; logTail: string; channelLog?: string; pane?: string; errorCode?: string }>; sick_pool?: Array<{ kind: string; sickReason: string }> }\n | null;\n if (!d || !Array.isArray(d.workers)) { console.warn(` (worker debug unavailable: HTTP ${r.status})\\n`); return; }\n // Lead with the classified cause if the container flagged one — no need to\n // read the raw screen below.\n if (d.grader_error_code) {\n console.warn(` ▸ DIAGNOSIS [${d.grader_error_code}]: ${d.grader_error_detail || ''}`);\n }\n // Show one worker in full (channel flow is the same across them).\n for (const w of d.workers.slice(0, 2)) {\n console.warn(` [${w.worker}] launcher log:`);\n for (const l of String(w.logTail || '').split('\\n').filter((l) => l.trim()).slice(-6)) console.warn(' ' + l.slice(0, 240));\n if (w.channelLog && !w.channelLog.startsWith('(no channel')) {\n console.warn(` [${w.worker}] channel flow (token + grade delivery):`);\n for (const l of String(w.channelLog).split('\\n').filter((l) => l.trim()).slice(-12)) console.warn(' ' + l.slice(0, 240));\n } else {\n console.warn(` [${w.worker}] channel: ${w.channelLog}`);\n }\n if (w.pane && !w.pane.startsWith('(') ) {\n console.warn(` [${w.worker}] claude screen:`);\n for (const l of String(w.pane).split('\\n').filter((l) => l.trim()).slice(-12)) console.warn(' ' + l.slice(0, 240));\n }\n }\n if (Array.isArray(d.sick_pool) && d.sick_pool.length) {\n console.warn(' sick workers:');\n for (const s of d.sick_pool) console.warn(` ${s.kind}: ${s.sickReason || '(no reason)'}`);\n }\n console.warn('');\n } catch { console.warn(' (could not fetch worker debug)\\n'); }\n}\n\n/**\n * Warm + verify the cloud grader after provisioning: boot the org's container,\n * wait for a healthy Claude worker, then run ONE smoke grade and assert a real\n * <synkro-verdict>. Surfaces a broken grader at install time instead of silently\n * declaring success. Returns true only when grading actually works end-to-end.\n */\nasync function verifyCloudGrader(jwt: string): Promise<boolean> {\n const base = (process.env.SYNKRO_CONTAINERS_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n console.log(' Warming the cloud grader (booting container + checking workers)...');\n\n // 1. Boot + wait for a healthy worker via /diag (the container cold-starts here).\n const deadline = Date.now() + 180_000;\n let healthy = 0;\n while (Date.now() < deadline) {\n try {\n const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt}` }, signal: AbortSignal.timeout(60_000) });\n if (r.ok) {\n const j = (await r.json()) as { claude_code?: { healthy?: number } };\n healthy = j.claude_code?.healthy ?? 0;\n if (healthy >= 1) break;\n }\n } catch { /* still cold-booting */ }\n await new Promise((res) => setTimeout(res, 5000));\n }\n if (healthy < 1) {\n console.warn(' ⚠ cloud grader did not report a healthy worker within ~3m.');\n console.warn(' Grading is NOT verified — check the container or re-run `synkro install`.\\n');\n return false;\n }\n console.log(` ✓ ${healthy} Claude worker(s) healthy`);\n\n // 2. Smoke grade — a known-risky edit must come back as a real verdict.\n // The grade can take a few seconds (and up to 120s on a cold worker), so tick\n // an elapsed-time line while we wait — otherwise it just looks frozen.\n console.log(' Running smoke grade (a worker scores a known-risky edit; up to 120s)...');\n const started = Date.now();\n const ticker = setInterval(() => {\n const secs = Math.round((Date.now() - started) / 1000);\n process.stdout.write(`\\r …grading (${secs}s elapsed) `);\n }, 1000);\n const stopTicker = () => { clearInterval(ticker); process.stdout.write('\\r\\x1b[2K'); };\n try {\n const probe = 'Grade this edit. File: t.py. Change: os.system(request.args[\"cmd\"])';\n const r = await fetch(`${base}/grade/submit`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ role: 'grade-edit', payload: probe, content: probe, agent_kind: 'claude_code' }),\n signal: AbortSignal.timeout(120_000),\n });\n stopTicker();\n const raw = await r.text();\n let body: { result?: string; error?: string; error_code?: string; hint?: string } = {};\n try { body = JSON.parse(raw); } catch { /* non-JSON error page */ }\n if (r.ok && typeof body.result === 'string' && body.result.includes('<synkro-verdict>')) {\n console.log(' ✓ smoke grade passed — cloud grading is live\\n');\n return true;\n }\n // Surface the ACTUAL container error instead of guessing — these distinguish\n // the failure modes (token vs no-worker vs container drop).\n const detail = (body.error || raw || '').trim().slice(0, 300) || '(empty response)';\n console.warn(` ✗ smoke grade FAILED (HTTP ${r.status}): ${detail}`);\n if (body.error_code) {\n // The container classified the failure — show the code + actionable hint and\n // skip the heuristic guessing below.\n console.warn(` ▸ ${body.error_code}: ${body.hint || ''}`);\n } else if (/no grader worker ready/i.test(detail)) {\n console.warn(' → the worker pool never registered (cold-start, or the pool failed to boot).');\n } else if (/connection lost|network|502/i.test(detail)) {\n console.warn(' → the container dropped the connection mid-grade — the claude worker crashed or hung (most often a bad/expired setup-token).');\n } else {\n console.warn(' → the worker returned an error; most often a bad/expired Claude setup-token.');\n }\n if (!body.error_code) console.warn(' Re-run `synkro install` → cloud to re-authorize the hosted worker.\\n');\n await printWorkerDebug(base, jwt);\n return false;\n } catch (e) {\n stopTicker();\n const reason = (e as Error)?.name === 'TimeoutError'\n ? 'the grade never returned within 120s (worker hung — usually claude auth)'\n : `the request failed (${(e as Error)?.message || 'network'})`;\n console.warn(` ✗ smoke grade FAILED: ${reason}.`);\n console.warn(' The container is provisioned but a grade does not complete; re-run `synkro install` → cloud.\\n');\n await printWorkerDebug(base, jwt);\n return false;\n }\n}\n\n/**\n * Read the deploy location the hooks currently route on, from config.env.\n * Defaults to 'local' when unset (the field is only ever written by\n * install/restart).\n */\nfunction readPersistedDeployLocation(): 'local' | 'cloud' {\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);\n if (m?.[1] === 'cloud') return 'cloud';\n }\n } catch { /* default local */ }\n return 'local';\n}\n\n/**\n * Surgically repoint the location keys in config.env, preserving every other\n * line. The hooks read SYNKRO_DEPLOY_LOCATION at call time, so this is what\n * actually reroutes grading between the local container and the CF container.\n */\nfunction updateConfigEnvLocation(location: 'local' | 'cloud'): void {\n if (!existsSync(CONFIG_PATH)) return;\n let env = readFileSync(CONFIG_PATH, 'utf-8');\n const set = (k: string, v: string) => {\n const re = new RegExp(`^${k}=.*$`, 'm');\n const line = `${k}='${v}'`;\n env = re.test(env) ? env.replace(re, line) : `${env.replace(/\\n?$/, '\\n')}${line}`;\n };\n set('SYNKRO_DEPLOY_LOCATION', location);\n set('SYNKRO_STORAGE_MODE', location === 'cloud' ? 'cloud' : 'local');\n writeFileSync(CONFIG_PATH, env, 'utf-8');\n chmodSync(CONFIG_PATH, 0o600);\n}\n\n/**\n * Reconcile the deploy location declared in synkro.toml ([grader] location)\n * against what's persisted in config.env, switching local<->cloud when it\n * changed. `synkro restart` calls this so a user can flip the toml and have it\n * picked up without a full reinstall.\n *\n * Returns the resolved location. When it returns 'cloud' the switch is COMPLETE\n * (local container torn down, CF container provisioned, config.env repointed) and\n * the caller must SKIP its local docker steps. When 'local', config.env is local\n * and the caller runs its normal docker (re)start.\n */\n/**\n * (Re)register the guardrails MCP for a deploy location: local → stdio proxy to\n * the local container; cloud → the hosted HTTP endpoint\n * (${gatewayUrl}/api/v1/mcp/guardrails, served by the api worker off Timescale),\n * so the rules/standards server follows the grader on a local<->cloud switch.\n *\n * The MCP JWT is the same long-lived org-scoped mcp:guardrails token for both\n * locations, so we REUSE the one minted at install (read-only — ~/.synkro is\n * never written here). If it's absent we mint one in-memory for the editor\n * config; persisting it is `synkro install`'s job, not this switch's.\n */\nasync function applyMcpConfig(opts: {\n gatewayUrl: string; jwt: string; hasClaudeCode: boolean; hasCursor: boolean; local: boolean;\n}): Promise<void> {\n if (!opts.hasClaudeCode && !opts.hasCursor) return;\n let mcpJwt = '';\n try { mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch { /* mint below */ }\n if (!mcpJwt) {\n // Never log the token or raw fetch errors next to it (CWE-200).\n try {\n const r = await fetch(`${opts.gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${opts.jwt}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (r.ok) mcpJwt = ((await r.json()) as { token: string }).token;\n else console.warn(` ⚠ MCP token mint failed (${r.status}) — run \\`synkro install\\` to set MCP up.`);\n } catch { console.warn(' ⚠ MCP token mint failed (network) — run `synkro install` to set MCP up.'); }\n }\n if (opts.hasClaudeCode) {\n try {\n const mcp = installMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: mcpJwt, local: opts.local });\n console.log(` ✓ Claude Code MCP → ${mcp.url}`);\n } catch (err) { console.warn(` ⚠ Claude Code MCP config failed: ${(err as Error).message}`); }\n }\n if (opts.hasCursor) {\n try {\n const mcp = installCursorMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: opts.local ? '' : mcpJwt, local: opts.local });\n console.log(` ✓ Cursor MCP → ${mcp.url}`);\n } catch (err) { console.warn(` ⚠ Cursor MCP config failed: ${(err as Error).message}`); }\n }\n}\n\nexport async function reconcileDeployLocation(): Promise<{ location: 'local' | 'cloud'; changed: boolean }> {\n const sf = readFullSynkroFile();\n const tomlLoc: 'local' | 'cloud' = sf?.grader.location === 'cloud' ? 'cloud' : 'local';\n const current = readPersistedDeployLocation();\n // Ask on restart too — default to what's actually running (config.env), else the toml. A bare\n // Enter or non-interactive shell keeps the current target (no surprise switches in CI). The\n // choice is authoritative and synced back into the toml so the two never drift.\n const desired = await promptDeployLocation(current || tomlLoc);\n if (desired !== tomlLoc) updateSynkroTomlLocation(desired);\n if (desired === current) return { location: desired, changed: false };\n const hasClaudeCode = sf ? sf.harness.includes('claude-code') : true;\n const hasCursor = sf ? sf.harness.includes('cursor') : false;\n\n console.log(`\\nDeploy location: ${current || '(none)'} → ${desired}\\n`);\n\n // Both directions repoint the MCP (rules/standards) server → resolve auth +\n // gateway once, up front.\n if (!isAuthenticated()) {\n console.log(' Opening browser for Synkro auth...');\n const ok = await authenticate(() => {});\n if (!ok) { console.error(' ✗ authentication failed'); process.exit(1); }\n }\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.error(' ✗ no access token — run `synkro login`'); process.exit(1); }\n const gatewayUrl = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || 'https://api.synkro.sh';\n // CWE-918: reject a hostile SYNKRO_GATEWAY_URL override before we send the JWT\n // (+ setup-token) to it — same allowlist the install path enforces.\n try { assertGatewayAllowed(gatewayUrl); }\n catch (err) { console.error((err as Error).message); process.exit(1); }\n\n if (desired === 'cloud') {\n // local → cloud: gracefully tear down the local grader, provision the CF\n // container, then repoint grading + rules/MCP at the cloud.\n if (dockerStatus().running) {\n console.log(' Stopping local grader container (snapshotting state first)...');\n await dockerSafeStop();\n }\n dockerRemove(); // harmless if already gone\n console.log(' ✓ local container stopped + removed');\n\n let orgId: string | undefined, userId: string | undefined, email: string | undefined;\n try { const info = getUserInfo(); userId = info.id; orgId = info.org_id; email = info.email; } catch { /* unreachable post-auth */ }\n await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });\n updateConfigEnvLocation('cloud');\n await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: false });\n await verifyCloudGrader(token);\n console.log(' ✓ grading + rules/MCP now served from Synkro Cloud\\n');\n } else {\n // cloud → local: repoint grading + MCP to local now; the caller's docker\n // (re)start brings the local grader container (which serves the MCP) up.\n updateConfigEnvLocation('local');\n await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: true });\n console.log(' ✓ switched to local — bringing the local grader + MCP up\\n');\n }\n return { location: desired, changed: true };\n}\n\n/**\n * Recycle the org's cloud grader container (destroy → fresh boot), then warm +\n * verify it. Called by `synkro restart` when already on cloud, so a restart spins\n * a NET-NEW container (picks up a rotated token / new image, clears wedged state).\n */\nexport async function recycleCloudContainer(): Promise<void> {\n if (!isAuthenticated()) { console.error('Not authenticated — run `synkro login`.'); return; }\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.error('No access token — run `synkro login`.'); return; }\n const base = (process.env.SYNKRO_CONTAINERS_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n console.log('Synkro: recycling the cloud grader container (destroy → fresh boot)…');\n try {\n const r = await fetch(`${base}/recycle`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(30_000) });\n if (r.ok) console.log(' ✓ old container destroyed');\n else console.warn(` ⚠ recycle returned HTTP ${r.status} — continuing to warm anyway.`);\n } catch { console.warn(' ⚠ recycle request failed (network) — continuing to warm anyway.'); }\n // Boot the fresh container + confirm it actually grades.\n await verifyCloudGrader(token);\n}\n\n/**\n * Resolve the deployment mode. Live env var wins (lets ops experiment without\n * mutating the persisted config), then the persisted SYNKRO_DEPLOYMENT_MODE\n * from ~/.synkro/config.env, then defaults to docker.\n *\n * Returns the lowercased string for direct comparison.\n */\nfunction resolveDeploymentMode(): 'docker' | 'bare-host' {\n const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();\n if (envOverride === 'bare-host' || envOverride === 'docker') return envOverride;\n try {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n const val = m?.[1]?.toLowerCase();\n if (val === 'bare-host' || val === 'docker') return val;\n }\n } catch { /* fall through to default */ }\n return 'docker';\n}\n\nfunction collectLocalMetadata(includeClaudeCode = true): Record<string, unknown> {\n const meta: Record<string, unknown> = { platform: process.platform };\n try {\n meta.display_name = execSync('git config user.name', { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {}\n try {\n const remote = execSync('git remote get-url origin', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n const httpMatch = remote.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?$/);\n const m = sshMatch || httpMatch;\n if (m) meta.active_repo = m[1];\n } catch {}\n\n if (!includeClaudeCode) return meta;\n\n try {\n meta.cc_version = execSync('claude --version', { encoding: 'utf-8', timeout: 5000 }).trim().split('\\n')[0];\n } catch {}\n\n const claudeDir = join(homedir(), '.claude');\n\n try {\n const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));\n const plugins = Object.keys(settings.enabledPlugins ?? {}).filter(k => settings.enabledPlugins[k]);\n if (plugins.length) meta.enabled_plugins = plugins;\n if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;\n } catch {}\n\n try {\n const mcpCache = JSON.parse(readFileSync(join(claudeDir, 'mcp-needs-auth-cache.json'), 'utf-8'));\n const mcpNames = Object.keys(mcpCache);\n if (mcpNames.length) meta.mcp_servers = mcpNames;\n } catch {}\n\n try {\n const mcpList = execSync('claude mcp list 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });\n const connected = mcpList.split('\\n')\n .filter(l => l.includes('Connected'))\n .map(l => l.split(':')[0].trim())\n .filter(Boolean);\n if (connected.length) meta.mcp_servers_connected = connected;\n } catch {}\n\n try {\n const sessionsDir = join(claudeDir, 'sessions');\n const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json')).slice(-5);\n for (const f of files) {\n const s = JSON.parse(readFileSync(join(sessionsDir, f), 'utf-8'));\n if (s.version) { meta.cc_version = meta.cc_version || s.version; break; }\n }\n } catch {}\n\n return meta;\n}\n\nasync function fetchUserProfile(gatewayUrl: string, token: string, hasClaudeCode = true): Promise<{ tier: string; inference: string; localInference: boolean; captureDepth: string }> {\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n headers: { 'Authorization': `Bearer ${token}` },\n });\n if (!resp.ok) return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n const data = await resp.json() as { fast_inference?: boolean; plan_tier?: string; local_inference?: boolean; capture_depth?: string };\n\n const meta = collectLocalMetadata(hasClaudeCode);\n fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(meta),\n }).catch(() => {});\n\n return {\n tier: data.plan_tier ?? 'pro',\n inference: data.fast_inference ? 'fast' : 'standard',\n localInference: !!data.local_inference,\n captureDepth: data.capture_depth ?? 'full',\n };\n } catch {\n return { tier: 'pro', inference: 'fast', localInference: false, captureDepth: 'full' };\n }\n}\n\n// JWT-only auth — no API key minting. The hook scripts read the JWT from\n// ~/.synkro/credentials.json and send it as Authorization: Bearer <jwt>.\n// Auth middleware on the gateway resolves user/org from JWT claims via JWKS.\n\n// The CLI ships the user's WorkOS Bearer JWT to the gateway URL. If a\n// hostile actor sets --gateway=http://evil.example.com on a user's machine,\n// the JWT lands at the attacker's server. Allow only:\n// - https://*.synkro.sh and synkro.sh apex\n// - http(s)://localhost or 127.0.0.1 (dev)\n// Anything else is rejected before any fetch is attempted.\nfunction assertGatewayAllowed(gatewayUrl: string): void {\n let parsed: URL;\n try { parsed = new URL(gatewayUrl); }\n catch { throw new Error(`Invalid gateway URL: ${gatewayUrl}`); }\n const proto = parsed.protocol;\n const host = parsed.hostname;\n if (proto !== 'http:' && proto !== 'https:') {\n throw new Error(`Gateway URL must be http(s); got ${proto}`);\n }\n const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1';\n const isSynkro = host === 'synkro.sh' || host.endsWith('.synkro.sh');\n if (proto === 'http:' && !isLocalhost) {\n throw new Error(`Gateway URL must be HTTPS for non-localhost hosts; got ${gatewayUrl}`);\n }\n if (!isLocalhost && !isSynkro) {\n throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);\n }\n}\n\n// Detect a complete prior install: every hook script present, config.env\n// written, and CC settings.json carries our `__synkro_managed__` markers.\n// MCP registration is opt-out (--no-mcp) so we don't gate on it. Returns\n// true only when every required surface is present so we never short-\n// circuit a partial / broken install.\nfunction isAlreadyInstalled(): boolean {\n const requiredScripts = [\n join(HOOKS_DIR, 'cc-bash-judge.ts'),\n join(HOOKS_DIR, 'cc-bash-followup.ts'),\n join(HOOKS_DIR, 'cc-edit-precheck.ts'),\n join(HOOKS_DIR, 'cc-cve-precheck.ts'),\n join(HOOKS_DIR, 'cc-plan-judge.ts'),\n join(HOOKS_DIR, 'cc-stop-summary.ts'),\n join(HOOKS_DIR, 'cc-session-start.ts'),\n ];\n if (!requiredScripts.every((p) => existsSync(p))) return false;\n if (!existsSync(CONFIG_PATH)) return false;\n\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n try {\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const hooks = settings?.hooks;\n if (!hooks || typeof hooks !== 'object') return false;\n const hasManaged = (kind: string) =>\n Array.isArray(hooks[kind]) &&\n hooks[kind].some((entry: Record<string, unknown>) => entry?.__synkro_managed__ === true);\n if (!hasManaged('PreToolUse')) return false;\n if (!hasManaged('PostToolUse')) return false;\n if (!hasManaged('SessionEnd')) return false;\n if (!hasManaged('SessionStart')) return false;\n } catch {\n return false;\n }\n return true;\n}\n\n\n// Opt-in for shipping local product analytics (events captured locally to\n// ~/.synkro/telemetry.db) up to the gateway. Local capture stays on either\n// way — this only controls remote flush. Non-interactive defaults to YES, in\n// line with the documented behavior in DESIGN.md §9.\nasync function promptTelemetryConsent(): Promise<boolean> {\n if (!process.stdin.isTTY) return true;\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const a = await new Promise<string>((res) =>\n rl.question(\n 'Allow Synkro to send local product analytics (events, timings, rule activity)\\n' +\n 'to our gateway? You can inspect data locally with `synkro telemetry stats`. (Y/n) ',\n (ans) => res(ans.trim().toLowerCase()),\n ),\n );\n return a === '' || a === 'y' || a === 'yes';\n } finally {\n rl.close();\n }\n}\n\nexport async function installCommand(opts: InstallOptions = {}): Promise<void> {\n // Synkro anchors all state — projects, telemetry, rulesets — to a repo\n // identity. Without a git repo there is nothing to key that state on, so a\n // repo is mandatory. This is a purely local check (`git remote get-url\n // origin`) — no GitHub login involved.\n if (!detectGitRepo()) {\n console.error('Synkro must be installed inside a git repository.');\n console.error(' `cd` into your project and re-run `synkro install`.');\n process.exit(1);\n }\n\n const gatewayUrl = opts.gatewayUrl\n || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL)\n || 'https://api.synkro.sh';\n\n // Reject hostile gateway overrides before we leak the JWT.\n try {\n assertGatewayAllowed(gatewayUrl);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n // No short-circuit. Plain `synkro install` is always a full refresh —\n // re-pulls the latest container image, re-writes hooks, re-registers the\n // MCP server. Each individual step is idempotent on its own; auth reuses\n // existing valid credentials so the user isn't bounced to the browser\n // unnecessarily.\n console.log('Synkro install starting...\\n');\n\n // 1. Auth via browser OAuth (WorkOS). Persists JWT + refresh_token to\n // ~/.synkro/credentials.json. The hook scripts authenticate against\n // the gateway with `Authorization: Bearer <access_token>` — auth\n // middleware resolves user/org from the JWT claims directly.\n if (!isAuthenticated()) {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n switch (status.phase) {\n case 'starting': console.log(' Starting local callback server...'); break;\n case 'browser-opened': console.log(` Browser opened: ${status.url}`); break;\n case 'waiting': console.log(' Waiting for browser auth to complete...'); break;\n case 'success': console.log(' ✓ Authenticated'); break;\n case 'error': console.error(` ✗ ${status.message}`); break;\n }\n });\n if (!result) {\n console.error('Authentication failed. If you are running a self-hosted dashboard, set SYNKRO_WEB_AUTH_URL to its origin.');\n process.exit(1);\n }\n }\n const token = getAccessToken();\n if (!token) {\n console.error('No access token available after auth.');\n process.exit(1);\n }\n\n // 1b. GitHub PR scanning is hidden for now — the install no longer prompts\n // for it. The code path (connectGitHub / setupGithubCommand below) is kept\n // intact, just unreachable, for when PR scanning is re-enabled.\n const ghToken: string | null = null;\n\n // 1c. Connect repos (local git or GitHub OAuth)\n setApiBaseUrl(`${gatewayUrl}/api`);\n await promptRepoConnection({ linkRepo: opts.linkRepo });\n\n // 2. Detect installed agents + resolve config.\n // If a valid .synkro file exists, skip all interactive prompts and derive\n // agent selection, grading mode, and storage mode from it.\n const existingSynkro = readFullSynkroFile();\n const detected = detectAgents();\n\n let agents: DetectedAgent[];\n let gradingMode: 'local' | 'byok';\n let storageMode: 'local' | 'cloud';\n // The single user-facing axis: where the grading container runs.\n let deployLocation: 'local' | 'cloud' = 'local';\n // Per-source transcript-import consent. Defaults to NO import — embedding past\n // session history is privacy-sensitive and must be explicitly consented (the\n // prompt below sets these). Never import silently.\n let transcriptCC = false;\n let transcriptCursor = false;\n\n if (existingSynkro) {\n // .synkro exists — skip prompts, use its config\n const wantCC = existingSynkro.harness.includes('claude-code');\n const wantCursor = existingSynkro.harness.includes('cursor');\n agents = detected.filter(a =>\n (a.kind === 'claude_code' && wantCC) || (a.kind === 'cursor' && wantCursor)\n );\n if (agents.length === 0 && detected.length > 0) agents = detected;\n gradingMode = existingSynkro.grader.mode === 'byok' ? 'byok' : 'local';\n // The deploy target (local vs cloud) is ALWAYS an explicit choice — even when a synkro.toml\n // exists — so it's never silently inferred (the source of the local-vs-cloud confusion).\n // Default to the toml's [grader] location; a bare Enter (or non-interactive) keeps it. If the\n // user picks a different target we sync it back into synkro.toml below so the two never drift.\n const tomlLocation: 'local' | 'cloud' = existingSynkro.grader.location === 'cloud' ? 'cloud' : 'local';\n deployLocation = await promptDeployLocation(tomlLocation);\n storageMode = deployLocation === 'cloud' ? 'cloud' : 'local';\n if (deployLocation !== tomlLocation) updateSynkroTomlLocation(deployLocation);\n\n console.log(`Using .synkro config:`);\n for (const w of existingSynkro.warnings) console.warn(` ⚠ ${w}`);\n console.log(` harness: ${existingSynkro.harness.join(', ')}`);\n console.log(` grading: ${gradingMode} pool: ${poolLabel(existingSynkro.grader.pool)}`);\n for (const a of agents) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n } else {\n // No .synkro — interactive prompts\n if (detected.length === 0) {\n console.error('No supported coding agents detected. Install Claude Code or Cursor first.');\n process.exit(1);\n }\n console.log('Detected agents:');\n for (const a of detected) {\n console.log(` ✓ ${a.name}${a.version ? ` (${a.version})` : ''}`);\n }\n console.log();\n\n agents = await promptAgentSelection(detected);\n if (agents.length < detected.length) {\n console.log(`Installing hooks for: ${agents.map(a => a.name).join(', ')}\\n`);\n }\n\n if (opts.byokGateway) {\n // Legacy escape hatch: no container, grading via the gateway with the\n // user's own provider key. Never offered in the prompt.\n gradingMode = 'byok'; storageMode = 'cloud'; deployLocation = 'local';\n console.log(' deploy: legacy BYOK-gateway grading (no container)\\n');\n } else {\n deployLocation = await promptDeployLocation();\n gradingMode = 'local';\n storageMode = deployLocation === 'cloud' ? 'cloud' : 'local';\n console.log(` deploy: ${deployLocation}\\n`);\n }\n }\n\n // Resolve the deployment target. local → local Docker (today's path,\n // unchanged). cloud → a per-org Cloudflare container. The legacy no-container\n // BYOK-gateway path is selected only by --byok-gateway or an old .synkro\n // mode='byok' (never the prompt).\n type DeployTarget = 'local-docker' | 'cloud-container' | 'gateway-byok';\n const target: DeployTarget =\n deployLocation === 'cloud' ? 'cloud-container'\n : (opts.byokGateway || gradingMode === 'byok') ? 'gateway-byok'\n : 'local-docker';\n\n // Transcript-import consent — harness-aware, asked in BOTH paths. A .synkro\n // file configures harness/grading but NOT whether to embed past session\n // history, so importing must still be consented. Interactive → always prompt\n // (which sources). Non-interactive → no import unless explicitly opted in via\n // SYNKRO_IMPORT_TRANSCRIPTS=yes (privacy-safe: never process session data\n // silently).\n {\n const wantCC = agents.some(a => a.kind === 'claude_code');\n const wantCursor = agents.some(a => a.kind === 'cursor');\n if ((wantCC || wantCursor) && process.stdin.isTTY) {\n const src = await promptTranscriptSources(wantCC, wantCursor);\n transcriptCC = src.cc;\n transcriptCursor = src.cursor;\n const chosen = [src.cc && 'Claude Code', src.cursor && 'Cursor'].filter(Boolean).join(' + ');\n console.log(chosen ? ` ✓ Session import: ${chosen}\\n` : ' ✗ Session import skipped\\n');\n } else if ((wantCC || wantCursor) && process.env.SYNKRO_IMPORT_TRANSCRIPTS === 'yes') {\n transcriptCC = wantCC;\n transcriptCursor = wantCursor;\n console.log(' ✓ Session import: SYNKRO_IMPORT_TRANSCRIPTS=yes\\n');\n } else if (!process.stdin.isTTY) {\n console.log(' ✗ Session import skipped (non-interactive; set SYNKRO_IMPORT_TRANSCRIPTS=yes to opt in)\\n');\n }\n }\n\n // Combined consent for config + the ongoing-sync gate (either source on).\n const transcriptConsent = transcriptCC || transcriptCursor;\n\n // Telemetry opt-in (DESIGN.md §9). Local capture stays on either way; this\n // prompt only controls remote flush. setTelemetryState writes both the meta\n // table and config.env so hooks see the same value without a re-source.\n const telemetryConsent = await promptTelemetryConsent();\n await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });\n emit('install', {\n phase: 'started',\n cli_version_to: __SYNKRO_CLI_VERSION__,\n agents_detected: agents.map((a) => a.kind),\n with_github: false,\n with_local_cc: false,\n with_transcript_consent: transcriptConsent,\n duration_ms: 0,\n });\n\n // 3. Set up Synkro directory + hook scripts + grader daemon\n ensureSynkroDir();\n // Stub hooks are thin pipes to a LOCAL container on 18931 (all scanning brains\n // — route(), systemMessage shaping, rule fetch — live server-side there). Cloud\n // mode runs NO local container: the per-org grader is remote and the DO exposes\n // only the dispatcher (:8929/submit), not /api/scan/ (:8931). So stub hooks in\n // cloud fail open to `{}` — no systemMessage, no blocking. Full hooks carry the\n // route()+hostedGrade() logic inline (grade → containers.synkro.sh) and fall\n // back to the gateway for config/rules, so they're the ONLY mode that works in\n // cloud. Force it (an explicit --stub can't function against a remote grader).\n // Cloud grading now runs server-side through the org container's scanRouter: the\n // stub posts each surface's ScanEnvelope to the gateway's /grade/submit as role\n // 'scan:<surface>' and gets the shaped decision back (see runStub + the dispatcher's\n // scan:<surface> handler). So stub mode works in cloud too, using the ONE container\n // implementation — cloud is no longer forced onto the legacy self-contained hooks.\n const hookMode = opts.hookMode || resolvePersistedHookMode();\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/\\n');\n\n // Kill any stale legacy Python grader daemons so they don't keep handling\n // requests after we've switched to the local-CC channel path.\n for (const mode of ['edit', 'bash']) {\n const pidFile = join(SYNKRO_DIR, 'daemon', mode, 'daemon.pid');\n try {\n const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);\n if (pid > 0) {\n process.kill(pid, 'SIGTERM');\n console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);\n }\n } catch {}\n }\n\n // 4. Configure CC hooks (atomic merge into settings.json).\n // Edit/Write/MultiEdit/NotebookEdit fires a thin command shim that POSTs\n // proposed content to /api/v1/precheck-edit. Server cosines the content\n // against the org's active agent_runtime rules and returns the deterministic\n // CC-hook JSON (deny + retry guidance, or empty allow). No LLM in the hook\n // path — agent retries with a safer version on its own inference when it\n // reads the denial reason.\n let hasClaudeCode = false;\n let hasCursor = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n hasClaudeCode = true;\n installCCHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.bashScript,\n skillJudgeScriptPath: scripts.skillJudgeScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n taskActivateIntentScriptPath: scripts.taskActivateIntentScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n skipTranscriptSync: !transcriptConsent,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n } else if (agent.kind === 'cursor') {\n hasCursor = true;\n installCursorHooks(agent.settingsPath, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n skillJudgeScriptPath: scripts.cursorSkillJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);\n }\n }\n console.log();\n\n // 5b. Fetch user profile early — we need captureDepth to decide local vs cloud MCP.\n let userId: string | undefined;\n let orgId: string | undefined;\n let email: string | undefined;\n try {\n const info = getUserInfo();\n userId = info.id;\n orgId = info.org_id;\n email = info.email;\n } catch {\n // unreachable — we just authenticated above\n }\n const profile = await fetchUserProfile(gatewayUrl, token, hasClaudeCode);\n // Cloud-only when both axes are cloud: BYOK grading + cloud storage bypass\n // the container's worker pool AND its PGLite, so skip Docker entirely and\n // register the MCP against the cloud endpoint. Any other combination still\n // needs the local container (for grading and/or local storage).\n // Local Docker is the ONLY target that runs a container on this machine.\n // cloud-container provisions a remote per-org CF container; gateway-byok runs\n // no container. Both register the cloud MCP and skip local Docker.\n const useLocalMcp = target === 'local-docker';\n if (target === 'cloud-container') {\n console.log('Cloud setup — Synkro will provision a private hosted container for your org.\\n');\n } else if (target === 'gateway-byok') {\n console.log('Cloud-only setup (BYOK grading via gateway) — skipping the local container.\\n');\n }\n\n // 5c. Register the Synkro Guardrails MCP server in ~/.claude.json.\n // Local mode: point to localhost:8931, start local server, backfill rules.\n // Cloud mode: mint long-lived JWT, point to cloud endpoint.\n if (hasClaudeCode && !opts.noMcp) {\n if (useLocalMcp) {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n let mcpJwt = '';\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n mcpJwt = minted.token;\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), mcpJwt + '\\n', { mode: 0o600 });\n } else {\n console.warn(' ⚠ Could not mint MCP token — local server will reject requests until re-installed.');\n }\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: mcpJwt, local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log();\n } catch (err) {\n console.warn(` ⚠ Local MCP setup failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry.');\n console.log();\n }\n } else {\n try {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n console.log(` expires: ${minted.expires_at} (~1 year)`);\n console.log(' (restart any running Claude Code session for it to load)');\n console.log();\n } catch (err) {\n console.warn(` ⚠ MCP registration failed: ${(err as Error).message}`);\n console.warn(' Hooks are still installed. Re-run `synkro install` to retry MCP setup.');\n console.log();\n }\n }\n }\n\n // 5d. Register the Synkro Guardrails MCP server in ~/.cursor/mcp.json.\n if (hasCursor && !opts.noMcp) {\n try {\n if (useLocalMcp) {\n // Ensure JWT exists (may already be minted in 5c for Claude Code)\n const jwtPath = join(SYNKRO_DIR, '.mcp-jwt');\n if (!existsSync(jwtPath)) {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (mintResp.ok) {\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(jwtPath, minted.token + '\\n', { mode: 0o600 });\n }\n }\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: '', local: true });\n console.log(`Registered local MCP guardrails server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n } else {\n const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n });\n if (!mintResp.ok) {\n const errText = await mintResp.text().catch(() => '');\n throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);\n }\n const minted = await mintResp.json() as { token: string; expires_at: string };\n writeFileSync(join(SYNKRO_DIR, '.mcp-jwt'), minted.token + '\\n', { mode: 0o600 });\n const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });\n console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);\n console.log(` url: ${mcp.url}`);\n }\n console.log();\n } catch (err) {\n console.warn(` ⚠ Cursor MCP registration failed: ${(err as Error).message}`);\n console.log();\n }\n }\n\n // 6. Write config.env — hook scripts read SYNKRO_GATEWAY_URL and the\n // credentials path. They auth via Bearer JWT (resolved from the\n // credentials file at runtime so refreshes Just Work).\n const synkroBundle = resolveSynkroBundle();\n // Persist the deployment mode so subsequent installs / hooks see the same\n // value without depending on shell-sourced env.\n const persistedMode = resolveDeploymentMode();\n writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode, gradingMode, storageMode, deployLocation, hookMode });\n console.log(`Wrote config to ${CONFIG_PATH}`);\n console.log(` inference: ${profile.inference} (server-side grading)`);\n if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);\n if (synkroBundle) console.log(` SYNKRO_CLI_BIN=${synkroBundle}`);\n else console.warn(' ⚠ Could not resolve synkro bundle path; hooks will fall back to PATH lookup of `synkro`.');\n\n try {\n const prompts = await fetchJudgePrompts({ gatewayUrl, jwt: token });\n console.log(` prompts: ${prompts.version} (live)`);\n } catch (err) {\n console.warn(` ⚠ Could not cache judge prompts: ${(err as Error).message}`);\n }\n\n // Write synkro.toml to repo root if one doesn't exist yet — LOCAL only. In\n // cloud mode application is fully server-side (org standards + pool live in the\n // dashboard), so there's no repo config to write; the harness enforces by org\n // wherever Synkro is installed.\n if (deployLocation !== 'cloud') {\n writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode, deployLocation });\n } else {\n console.log(' Cloud mode: standards + grader pool are configured in the dashboard');\n console.log(' (Settings → Standards / Pool) — no synkro.toml needed.');\n }\n console.log();\n\n // Tracks whether the cloud grader verified end-to-end (pueue+tmux smoke grade).\n // null = not a cloud install / not checked; false = verified and FAILED.\n let cloudGradeOk: boolean | null = null;\n\n // A Cursor API key is only needed when the grader pool actually runs Cursor\n // WORKERS. Cursor being in `harness` just means Cursor's edits get graded —\n // by whatever pool is configured. With a claude-code pool, Claude Code grades\n // Cursor's edits and no Cursor key is required. Mirror the exact worker\n // resolution used below (explicit counts > pool shorthand > harness) so the\n // prompt fires iff a Cursor worker will exist.\n const graderUsesCursor = (() => {\n if (!hasCursor) return false;\n const sfp = readFullSynkroFile();\n if (sfp && (sfp.workers.claude != null || sfp.workers.cursor != null)) {\n return Math.max(0, Math.floor(sfp.workers.cursor || 0)) > 0;\n }\n const pool = sfp?.grader?.pool || 'auto';\n if (pool === 'cursor') return true;\n if (pool === 'claude_code') return false;\n return hasCursor; // 'auto' → cursor workers only if Cursor is a harness\n })();\n\n // The container provides the grading worker pool and local PGLite — needed\n // for any setup except cloud-only (BYOK grading + cloud storage).\n if (useLocalMcp) {\n const { assertDockerAvailable } = await import('../local-cc/dockerInstall.js');\n try {\n assertDockerAvailable();\n } catch (err) {\n console.error(`\\n✗ ${(err as Error).message}`);\n process.exit(1);\n }\n\n // A Cursor grader worker needs a Cursor API key — collect it before starting\n // the container so the workers come up authenticated. Skipped when the pool\n // is claude-code-only (Claude Code grades Cursor's edits, no key needed).\n if (graderUsesCursor) {\n await promptCursorApiKey(opts);\n }\n\n console.log('Installing Synkro server container...');\n // Read .synkro for worker config: explicit counts > pool > agent detection.\n const sf = readFullSynkroFile();\n for (const w of sf?.warnings || []) console.warn(` ⚠ ${w}`);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {\n claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));\n cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (claudeWorkers + cursorWorkers === 0) { claudeWorkers = 0; cursorWorkers = 8; }\n console.log(` synkro.toml: explicit workers — ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n } else {\n const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const synkroFilePool = sf?.grader?.pool || 'auto';\n let providers: WorkerProvider[] = [];\n if (synkroFilePool === 'cursor') {\n providers = ['cursor'];\n } else if (synkroFilePool === 'claude_code') {\n providers = ['claude_code'];\n } else {\n if (hasClaudeCode) providers.push('claude_code');\n if (hasCursor) providers.push('cursor');\n }\n ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));\n if (synkroFilePool !== 'auto') console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);\n }\n console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);\n // Connected repo — the container names the seeded app + ruleset after it.\n const connectedRepo = detectGitRepo() || undefined;\n const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } =\n await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });\n console.log(` ✓ pulled ${image}`);\n console.log(` container started — MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);\n console.log(' waiting for container to be ready...');\n const ready = await waitForContainerReady(60_000);\n if (ready) {\n console.log(' ✓ container ready');\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n try {\n const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpJwt}` },\n body: JSON.stringify({ capture_type: 'healthcheck', event_id: `healthcheck_${Date.now()}` }),\n signal: AbortSignal.timeout(5000),\n });\n if (ingestResp.ok) {\n console.log(' ✓ ingest endpoint verified');\n } else {\n console.warn(` ⚠ ingest endpoint returned ${ingestResp.status} — telemetry spool may not drain.`);\n console.warn(' The .mcp-jwt token may not match the container. Try: synkro uninstall && synkro install');\n }\n } catch {\n console.warn(' ⚠ ingest endpoint unreachable — telemetry spool may not drain.');\n }\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log(' ✓ workers ready');\n await syncSkillFiles();\n // Auto-discover skills the user already has (Claude Code / skills.sh folders)\n // and offer to ingest them — each is security-vetted before any rule is added.\n await discoverAndIngestSkills();\n } else {\n console.warn(' ⚠ workers did not register within 30s — skill sync skipped');\n }\n } else {\n console.error(' ✗ container did not become healthy within 60s');\n console.error(' Run `docker logs synkro-server` to debug.');\n process.exit(1);\n }\n console.log();\n } else if (target === 'cloud-container') {\n // Cloud: capture the org's Claude setup-token (CLOUD ONLY) and hand it to\n // the gateway, which stores it as the org secret and provisions the per-org\n // Cloudflare container. No local container, no keychain, no mounts.\n // Cursor pool? Collect the Cursor API key first so it's bundled into the\n // provision (cloud cursor workers grade with it). Only when a Cursor worker\n // actually exists — a claude-code pool grades Cursor's edits without a key.\n if (graderUsesCursor) await promptCursorApiKey(opts);\n await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });\n // Warm + smoke-test the hosted grader (pueue+tmux channel grade) so a broken\n // container/token surfaces HERE, not silently on the user's first edit. The\n // result gates the final success message below — a failed grade is a failed\n // install, not a clean one.\n cloudGradeOk = await verifyCloudGrader(token);\n // DISABLED: Codex (ChatGPT subscription) cloud grading is not supported yet, so we don't\n // prompt for it during install (it confused users into authorizing a grader we can't use).\n // Re-enable this block once Codex cloud grading ships. The browser approval is the user's\n // consent; the session is stored + refreshed by the internal credential service (never a\n // public endpoint), one per (org, user).\n // if (await promptCodexCloudSetup()) {\n // const { setupCodexCloud } = await import('../local-cc/codexCloudSetup.js');\n // const res = await setupCodexCloud(gatewayUrl, token, (m) => console.log(` ${m}`));\n // if (!res.ok) console.warn(` ⚠ Codex cloud setup skipped: ${res.error}`);\n // }\n }\n\n // 7. Import + embed session transcripts for the consented source(s).\n if (transcriptConsent) {\n const repo = detectGitRepo();\n if (repo) {\n if (storageMode === 'local') {\n // Local: sync into PGLite — the container's embedding model vectors each message.\n try {\n let mcpToken = '';\n try { mcpToken = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim(); } catch {}\n if (mcpToken) {\n const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);\n let imported = 0;\n if (transcriptCC && hasClaudeCode) {\n const r = await syncTranscriptsLocal(mcpPort, mcpToken, repo);\n if (r.messages > 0) {\n console.log(` ✓ Imported ${r.sessions} Claude Code sessions (${r.messages} messages) into local store.`);\n imported += r.messages;\n }\n }\n if (transcriptCursor && hasCursor) {\n const r = await syncCursorTranscriptsLocal(mcpPort, mcpToken, repo);\n if (r.messages > 0) {\n console.log(` ✓ Imported ${r.sessions} Cursor sessions (${r.messages} messages) into local store.`);\n imported += r.messages;\n }\n }\n if (imported > 0) {\n console.log(' Embeddings generated. Analyzing for rule suggestions...');\n try {\n const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n signal: AbortSignal.timeout(90000),\n });\n if (suggestResp.ok) {\n const suggestResult = await suggestResp.json() as { suggested?: number; skippedDuplicates?: number };\n if (suggestResult.suggested && suggestResult.suggested > 0) {\n console.log(` ✓ Generated ${suggestResult.suggested} rule suggestions from your session history.`);\n console.log(' Ask Synkro: \"show me suggested rules\" to review them.\\n');\n } else {\n console.log(' No rule suggestions generated yet — more session data needed.\\n');\n }\n }\n } catch {\n console.log(' Rule analysis will run automatically as more sessions are recorded.\\n');\n }\n }\n } else {\n console.warn(' ⚠ Session import skipped — container auth token not found.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Local session import failed: ${(err as Error).message}\\n`);\n }\n } else {\n // Cloud (Timescale via gateway): Claude Code transcripts. Cursor cloud\n // backfill isn't wired yet — Cursor import currently requires local storage.\n if (transcriptCC && hasClaudeCode) {\n try {\n const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);\n if (ingested > 0) {\n console.log(` ✓ Indexed ${ingested} session insights from Claude Code history.`);\n }\n } catch (err) {\n console.warn(` ⚠ Session indexing skipped: ${(err as Error).message}\\n`);\n }\n try {\n const result = await syncTranscriptsBulk(gatewayUrl, token, repo);\n if (result.messages > 0) {\n console.log(` ✓ Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);\n console.log(' Embeddings use your configured inference provider.\\n');\n }\n } catch (err) {\n console.warn(` ⚠ Transcript sync skipped: ${(err as Error).message}\\n`);\n }\n }\n if (transcriptCursor && hasCursor) {\n console.log(' ℹ Cursor transcript import requires local storage mode — skipped under cloud storage.\\n');\n }\n }\n }\n }\n\n // 8. PR scan setup (secrets + workflow) — only if GitHub was connected in step 1b\n if (ghToken) {\n const { setupGithubCommand } = await import('./setupGithub.js');\n await setupGithubCommand({ nonInteractive: true, githubToken: ghToken });\n }\n\n // 9. Done — but a cloud install whose grader didn't verify is NOT a success.\n if (cloudGradeOk === false) {\n console.error('');\n console.error('✗ Synkro installed, but the cloud grader is NOT working — grading will fail on your edits.');\n console.error(' The smoke grade above shows why (most often a bad/incomplete Claude setup-token → 401).');\n console.error(' Re-run `synkro install` → cloud to re-authorize with a fresh token.');\n process.exitCode = 1;\n } else {\n console.log('✓ Synkro installed.');\n }\n\n // 10. Claude Desktop observability (macOS) is a deliberate, SEPARATE action —\n // install must return cleanly, not hand off into a long-running foreground MITM\n // tap. So we don't start it here; we just point the user at the command. The tap\n // (session-scoped local proxy → conversations in the dashboard) runs only via\n // `synkro claude-desktop`, where it owns the foreground until Ctrl+C.\n if (process.stdin.isTTY && claudeDesktopInstalled()) {\n console.log('\\n Claude Desktop detected — to monitor its conversations in your dashboard, run: synkro claude-desktop');\n }\n}\n\n/**\n * Identify the git repo `synkro install` is running in. `git` itself walks up\n * from the cwd, so this resolves correctly from any subdirectory or worktree.\n * Tries the `origin` remote (→ owner/repo), then falls back to the repo's\n * top-level directory name. Returns null only when the cwd is not a git repo\n * at all — mirrors the hook's detectRepo() so the seeded project id and the id\n * telemetry lands under always agree.\n */\n// Coerce a single TOML value token (\"local\", 8, true, [\"a\",\"b\"]) to JS.\nfunction parseTomlValue(raw: string): any {\n const v = raw.trim();\n if (v.startsWith('[') && v.endsWith(']')) {\n const inner = v.slice(1, -1).trim();\n if (!inner) return [];\n return inner.split(',').map(s => parseTomlValue(s)).filter(s => s !== '');\n }\n if ((v.startsWith('\"') && v.endsWith('\"')) || (v.startsWith(\"'\") && v.endsWith(\"'\"))) return v.slice(1, -1);\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (/^-?\\d+$/.test(v)) return parseInt(v, 10);\n return v;\n}\n\n// Minimal TOML reader for synkro.toml's fixed, flat schema: top-level\n// scalars/arrays plus the [grader] and [scanning] tables. Not a general TOML\n// parser — we own the writer, so input shape is bounded. Returns the same\n// nested-object shape the old YAML parser did, so downstream consumers are\n// unchanged.\nfunction parseSynkroToml(raw: string): Record<string, any> {\n const result: Record<string, any> = {};\n let section: string | null = null;\n for (const rawLine of raw.split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n const sec = line.match(/^\\[([^\\]]+)\\]$/);\n if (sec) {\n section = sec[1].trim();\n if (!result[section]) result[section] = {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim().replace(/^[\"']|[\"']$/g, '');\n let valRaw = line.slice(eq + 1).trim();\n // Strip an inline comment only off bare scalars — never inside a quoted\n // string or an inline array (where a '#' would be data).\n if (!valRaw.startsWith('\"') && !valRaw.startsWith(\"'\") && !valRaw.startsWith('[')) {\n const h = valRaw.indexOf('#');\n if (h !== -1) valRaw = valRaw.slice(0, h).trim();\n }\n const value = parseTomlValue(valRaw);\n if (section) (result[section] as Record<string, any>)[key] = value;\n else result[key] = value;\n }\n return result;\n}\n\nfunction writeSynkroFileIfMissing(opts: { hasClaudeCode: boolean; hasCursor: boolean; gradingMode: string; deployLocation?: 'local' | 'cloud' }): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return;\n // Never treat $HOME as a repo to onboard: ~/.synkro is Synkro's own config\n // DIRECTORY, so writing config there would collide with it and an existence\n // check would always \"find\" it. (git rev-parse can resolve to $HOME when\n // dotfiles are git-tracked or cwd is a non-git dir under home.)\n if (root === homedir()) return;\n const fp = join(root, 'synkro.toml');\n // Onboarded only if a real synkro.toml FILE exists — statSync().isFile() so\n // a directory (or any non-file) never counts as \"already configured\".\n let hasFile = false;\n try { hasFile = statSync(fp).isFile(); } catch {}\n if (hasFile) {\n console.log(` synkro.toml: ${fp} (existing, respected)`);\n return;\n }\n let pool: string = 'auto';\n const harness: string[] = [];\n if (opts.hasClaudeCode) { harness.push('claude-code'); if (!opts.hasCursor) pool = 'claude'; }\n if (opts.hasCursor) { harness.push('cursor'); if (!opts.hasClaudeCode) pool = 'cursor'; }\n const mode = opts.gradingMode === 'byok' ? 'byok' : 'local';\n // TOML scope rule: all top-level keys MUST precede any [table] header, or\n // they'd bind to the open table. Emit version/harness first, then the [grader]\n // and [scanning] tables. (Rulesets + skills are managed in the registry/dashboard\n // now, not via synkro.toml — so they're no longer written here.)\n const toml = [\n 'version = 1',\n '',\n `harness = [${harness.map(h => `\"${h}\"`).join(', ')}]`,\n '',\n '[grader]',\n `location = \"${opts.deployLocation || 'local'}\"`,\n `pool = \"${pool}\"`,\n `mode = \"${mode}\"`,\n '',\n '[scanning]',\n 'cwe = true',\n 'cve = true',\n '',\n ].join('\\n');\n writeFileSync(fp, toml, 'utf-8');\n console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);\n } catch {}\n}\n\n// Sync [grader] location in an EXISTING repo synkro.toml to the chosen deploy target, so the\n// visible toml never drifts from config.env after the local/cloud prompt. No-op if there's no\n// toml or no location line (config.env stays authoritative at runtime regardless).\nfunction updateSynkroTomlLocation(location: 'local' | 'cloud'): void {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root || root === homedir()) return;\n const fp = join(root, 'synkro.toml');\n let txt = '';\n try { if (!statSync(fp).isFile()) return; txt = readFileSync(fp, 'utf-8'); } catch { return; }\n const re = /^(\\s*location\\s*=\\s*).*$/m;\n if (!re.test(txt)) return;\n const next = txt.replace(re, `$1\"${location}\"`);\n if (next !== txt) { writeFileSync(fp, next, 'utf-8'); console.log(` synkro.toml: [grader] location = \"${location}\"`); }\n } catch {}\n}\n\ninterface SynkroFile {\n harness: ('claude-code' | 'cursor')[];\n grader: { pool: 'auto' | 'claude_code' | 'cursor'; mode: string; location?: 'local' | 'cloud' };\n workers: { claude?: number; cursor?: number };\n /** Config-parse warnings (unrecognized provider tokens) to surface to the user. */\n warnings: string[];\n ruleset: string;\n skills: string[];\n scanning: { cwe: boolean; cve: boolean };\n /** Registry subscriptions: \"namespace/name\" -> version (e.g. \"v3\" | \"latest\"). */\n standards: Record<string, string>;\n _repoRoot: string;\n}\n\nexport function readFullSynkroFile(): SynkroFile | null {\n try {\n const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n if (!root) return null;\n const fp = join(root, 'synkro.toml');\n if (!existsSync(fp)) return null;\n const parsed = parseSynkroToml(readFileSync(fp, 'utf-8'));\n const valid = ['claude-code', 'cursor'] as const;\n const harness = Array.isArray(parsed.harness)\n ? parsed.harness.filter((h: string) => valid.includes(h as any))\n : ['claude-code', 'cursor'];\n const resolved = resolveGraderPool(parsed);\n return {\n harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],\n grader: {\n pool: resolved.pool,\n mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : 'local',\n location: parsed.grader?.location === 'cloud' ? 'cloud' : 'local',\n },\n workers: {\n ...(resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {}),\n ...(resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}),\n },\n warnings: resolved.warnings,\n ruleset: parsed.ruleset || 'default',\n skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string' && s.endsWith('.md')) : [],\n scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },\n standards: (parsed.standards && typeof parsed.standards === 'object')\n ? Object.fromEntries(Object.entries(parsed.standards as Record<string, unknown>)\n .filter(([k, v]) => typeof v === 'string' && k.includes('/'))\n .map(([k, v]) => [k, String(v)]))\n : {},\n _repoRoot: root,\n };\n } catch { return null; }\n}\n\nexport function reconcileHarness(): { claudeWorkers: number; cursorWorkers: number } | null {\n const sf = readFullSynkroFile();\n if (!sf) {\n console.log('No .synkro file found in repo root — skipping harness reconciliation.');\n return null;\n }\n\n const wantCC = sf.harness.includes('claude-code');\n const wantCursor = sf.harness.includes('cursor');\n for (const w of sf.warnings) console.warn(` ⚠ ${w}`);\n console.log(`synkro.toml: harness=[${sf.harness.join(', ')}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);\n\n const scripts = writeHookScripts();\n console.log('Wrote hook scripts to ~/.synkro/hooks/');\n\n const ccSettings = join(homedir(), '.claude', 'settings.json');\n if (wantCC) {\n installCCHooks(ccSettings, {\n bashJudgeScriptPath: scripts.bashScript,\n skillJudgeScriptPath: scripts.skillJudgeScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n installScanScriptPath: scripts.installScanScript,\n taskActivateIntentScriptPath: scripts.taskActivateIntentScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(' ✓ Claude Code hooks registered');\n try {\n const mcpJwt = readFileSync(join(SYNKRO_DIR, '.mcp-jwt'), 'utf-8').trim();\n if (mcpJwt) {\n installMcpConfig({ gatewayUrl: '', bearerToken: mcpJwt, local: true });\n console.log(' ✓ Claude Code MCP registered');\n }\n } catch {}\n } else {\n if (uninstallCCHooks(ccSettings)) console.log(' ✗ Claude Code hooks removed');\n if (uninstallMcpConfig()) console.log(' ✗ Claude Code MCP removed');\n if (uninstallClaudeDesktopMcpConfig()) console.log(' ✗ Claude Desktop MCP removed');\n }\n\n const cursorHooks = join(homedir(), '.cursor', 'hooks.json');\n if (wantCursor) {\n installCursorHooks(cursorHooks, {\n bashJudgeScriptPath: scripts.cursorBashJudgeScript,\n skillJudgeScriptPath: scripts.cursorSkillJudgeScript,\n editCaptureScriptPath: scripts.cursorEditCaptureScript,\n agentCaptureScriptPath: scripts.cursorAgentCaptureScript,\n bashFollowupScriptPath: scripts.bashFollowupScript,\n editPrecheckScriptPath: scripts.editPrecheckScript,\n cwePrecheckScriptPath: scripts.cwePrecheckScript,\n cvePrecheckScriptPath: scripts.cvePrecheckScript,\n planJudgeScriptPath: scripts.planJudgeScript,\n agentJudgeScriptPath: scripts.agentJudgeScript,\n stopSummaryScriptPath: scripts.stopSummaryScript,\n sessionStartScriptPath: scripts.sessionStartScript,\n userPromptSubmitScriptPath: scripts.userPromptSubmitScript,\n transcriptSyncScriptPath: scripts.transcriptSyncScript,\n installScanScriptPath: scripts.installScanScript,\n mcpGateScriptPath: scripts.mcpGateScript,\n });\n console.log(' ✓ Cursor hooks registered');\n try {\n installCursorMcpConfig({ gatewayUrl: '', bearerToken: '', local: true });\n console.log(' ✓ Cursor MCP registered');\n } catch {}\n } else {\n if (uninstallCursorHooks(cursorHooks)) console.log(' ✗ Cursor hooks removed');\n if (uninstallCursorMcpConfig()) console.log(' ✗ Cursor MCP removed');\n }\n\n // Explicit worker counts from .synkro take priority\n if (sf.workers.claude != null || sf.workers.cursor != null) {\n const cw = Math.max(0, Math.floor(sf.workers.claude || 0));\n const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));\n if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };\n }\n\n const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || '8', 10);\n const providers: WorkerProvider[] = [];\n if (sf.grader.pool === 'cursor') {\n providers.push('cursor');\n } else if (sf.grader.pool === 'claude_code') {\n providers.push('claude_code');\n } else {\n if (wantCC) providers.push('claude_code');\n if (wantCursor) providers.push('cursor');\n }\n if (providers.length === 0) providers.push('claude_code');\n return splitWorkers(total, providers);\n}\n\n// Ingest a list of {source, content} skills through the container, SEQUENTIALLY: each\n// file must dedup against rules the previous files just added, else two md files with the\n// same rule would each add it (cross-file dupe). Surfaces vet-BLOCKED skills distinctly\n// (created=0 with a pass ✓ would misrepresent a rejection). Returns a summary.\nasync function ingestSkillTasks(\n tasks: { source: string; content: string }[],\n mcpPort: string,\n): Promise<{ ingested: number; rules: number; rejected: { source: string; reason: string }[] }> {\n const summary = { ingested: 0, rules: 0, rejected: [] as { source: string; reason: string }[] };\n for (const { source, content } of tasks) {\n try {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source, content }),\n signal: AbortSignal.timeout(65000),\n });\n if (!resp.ok) throw new Error(`${resp.status}`);\n const r = await resp.json() as { status?: string; created?: number; message?: string; reasons?: string[] };\n if (r.status === 'rejected') {\n const reason = (r.reasons || []).slice(0, 2).join('; ') || r.message || 'blocked by security vetting';\n summary.rejected.push({ source, reason });\n console.log(` ⛔ skill ${source}: BLOCKED — ${reason}`);\n } else {\n const created = r.created || 0;\n summary.ingested++;\n summary.rules += created;\n console.log(created > 0\n ? ` ✓ skill ${source}: ${created} rule${created === 1 ? '' : 's'} → its own pack`\n : ` ✓ skill ${source}: ${r.message || 'up to date'}`);\n }\n } catch (e) {\n console.warn(` ⚠ skill ${source}: sync failed (${e instanceof Error ? e.message : String(e)})`);\n }\n }\n return summary;\n}\n\nexport async function syncSkillFiles(): Promise<void> {\n const sf = readFullSynkroFile();\n if (!sf || sf.skills.length === 0) return;\n\n const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);\n if (resolved.length === 0) return;\n\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n const tasks = resolved.map(fp => {\n const content = readFileSync(fp, 'utf-8');\n const source = `skill:${fp.split('/').pop()}`;\n if (!content.trim()) {\n console.log(` ⊘ skill ${source}: empty file, skipped`);\n return null;\n }\n const lines = content.split('\\n').length;\n console.log(` → read ${source} (${lines} lines, ${(content.length / 1024).toFixed(1)} KB)`);\n return { source, content };\n }).filter(Boolean) as { source: string; content: string }[];\n\n if (tasks.length === 0) return;\n\n console.log(` indexing ${tasks.length} skill file${tasks.length === 1 ? '' : 's'} — comparing against existing ruleset (this may take a few seconds)...`);\n await ingestSkillTasks(tasks, mcpPort);\n}\n\n// ─── Skill auto-discovery (install-time onboarding) ─────────────────────────────\n// Common locations Claude Code / skills.sh / agent tools drop skills. Home AND repo,\n// since skills can be user-global or project-scoped. skills.sh symlinks .agents→.claude,\n// so we dedup by CONTENT HASH (not path) to avoid double-counting a symlinked skill.\n// Bounded scan. Handles both the skills.sh layout (<dir>/<name>/SKILL.md) and flat *.md.\ntype DiscoveredSkill = { source: string; content: string; name: string; hash: string; ingested: boolean };\n// Normalize a skill name for identity matching: lowercase, drop a trailing .md/.mdx\n// (some rows store the name WITH the extension, others without — normalize both sides).\nfunction normSkillName(name: string): string { return name.toLowerCase().replace(/\\.mdx?$/, ''); }\nfunction discoverSkillFiles(\n repoRoot: string | null,\n excludeHashes: Set<string>,\n ingestedHashes: Set<string>,\n ingestedNames: Set<string>,\n): DiscoveredSkill[] {\n const roots: string[] = [];\n const add = (p: string) => { try { if (p && existsSync(p) && statSync(p).isDirectory()) roots.push(p); } catch { /* ignore */ } };\n add(join(homedir(), '.claude', 'skills'));\n add(join(homedir(), '.agents', 'skills'));\n if (repoRoot) { add(join(repoRoot, '.claude', 'skills')); add(join(repoRoot, '.agents', 'skills')); }\n\n const out: DiscoveredSkill[] = [];\n const seen = new Set<string>();\n const MAX = 200;\n const consider = (file: string, name: string) => {\n if (out.length >= MAX) return;\n let content = '';\n try { const st = statSync(file); if (!st.isFile() || st.size > 200_000) return; content = readFileSync(file, 'utf-8'); } catch { return; }\n if (!content.trim()) return;\n const hash = createHash('sha256').update(content).digest('hex');\n // Hard-skip symlink dupes (seen) and skills managed via synkro.toml (auto-synced there).\n if (seen.has(hash) || excludeHashes.has(hash)) return;\n seen.add(hash);\n // Already ingested into the store? Still SHOW it, but tag it so the prompt makes it\n // unselectable. Exact content_hash match (unchanged) OR same normalized name (ingested\n // but since edited) both count as \"already ingested\".\n const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));\n out.push({ source: `skill:${name}`, content, name, hash, ingested });\n };\n for (const root of roots) {\n let entries: string[] = [];\n try { entries = readdirSync(root); } catch { continue; }\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n const full = join(root, entry);\n let st; try { st = statSync(full); } catch { continue; } // follows symlinks\n if (st.isDirectory()) {\n const skillMd = join(full, 'SKILL.md');\n if (existsSync(skillMd)) consider(skillMd, entry); // skills.sh: <name>/SKILL.md\n } else if (/\\.mdx?$/i.test(entry) && entry.toUpperCase() !== 'README.MD') {\n consider(full, entry.replace(/\\.mdx?$/i, ''));\n }\n }\n }\n return out;\n}\n\n// A stable fingerprint of the discovered set so a plain `synkro install` re-run only\n// re-prompts when the user has ADDED skills since last time (no nagging on the same set,\n// whether they accepted or declined).\nconst SKILLS_DISCOVERED_PATH = join(SYNKRO_DIR, '.skills-discovered');\nfunction discoverySetHash(found: DiscoveredSkill[]): string {\n return createHash('sha256').update(found.map(f => f.hash).sort().join(',')).digest('hex');\n}\n\nasync function promptSkillDiscovery(found: DiscoveredSkill[]): Promise<number[]> {\n if (!process.stdin.isTTY || found.length === 0) return [];\n const selectableIdx = found.map((s, i) => (s.ingested ? -1 : i)).filter(i => i >= 0);\n if (selectableIdx.length === 0) return []; // everything already ingested — nothing to pick\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> => new Promise(res => rl.question(q, a => res(a.trim())));\n try {\n console.log(`\\nFound ${found.length} skill${found.length === 1 ? '' : 's'} in your Claude Code / skills.sh folders:`);\n // Already-ingested skills are shown for context but NOT selectable — only uningested ones can be picked.\n found.forEach((s, i) => console.log(` ${i + 1}. ${s.name}${s.ingested ? ' — already ingested (not selectable)' : ''}`));\n console.log('Ingest the uningested skills as enforceable rules? Each is security-vetted first — anything');\n console.log('malicious (prompt injection, guardrail-weakening, secret exfil) is blocked and reported.');\n const a = (await ask(`Ingest [all] (${selectableIdx.length} uningested) / type numbers (e.g. 1,3) / [skip]: `)).toLowerCase();\n if (a === 'skip' || a === 'n' || a === 'no' || a === 's') return [];\n if (a === '' || a === 'all' || a === 'y' || a === 'yes' || a === 'a') return selectableIdx;\n const picks = a.split(/[\\s,]+/).map(x => parseInt(x, 10) - 1).filter(i => Number.isInteger(i) && i >= 0 && i < found.length);\n const valid: number[] = [];\n for (const i of [...new Set(picks)]) {\n if (found[i].ingested) { console.log(` · ${found[i].name} is already ingested — skipped`); continue; }\n valid.push(i);\n }\n return valid;\n } finally { rl.close(); }\n}\n\n// Install-time: scan common skill folders, offer to ingest what's not already tracked.\n// Local-mode only (posts to the just-started local container); the container security-\n// vets each skill before any rule is added. Called right after syncSkillFiles().\nexport async function discoverAndIngestSkills(): Promise<void> {\n try {\n const sf = readFullSynkroFile();\n const repoRoot = sf?._repoRoot || detectGitRepo();\n const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';\n // Exclude skills already tracked in synkro.toml (by content hash).\n const excludeHashes = new Set<string>();\n if (sf?.skills?.length) {\n for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {\n try { excludeHashes.add(createHash('sha256').update(readFileSync(fp, 'utf-8')).digest('hex')); } catch { /* ignore */ }\n }\n }\n // Identify skills ALREADY INGESTED into the store (PGLite local / Timescale cloud)\n // so the prompt can show them as unselectable. content_hash = exact match (unchanged);\n // normalized name = ingested-but-since-edited. Both mark the skill \"already ingested\".\n const ingestedHashes = new Set<string>();\n const ingestedNames = new Set<string>();\n try {\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills?status=active`, { signal: AbortSignal.timeout(5000) });\n if (resp.ok) {\n const { skills } = await resp.json() as { skills?: Array<{ name?: string; content_hash?: string }> };\n for (const s of skills || []) {\n if (s.content_hash) ingestedHashes.add(s.content_hash);\n if (s.name) ingestedNames.add(normSkillName(String(s.name)));\n }\n }\n } catch { /* container unreachable — treat nothing as ingested (worst case: re-offers) */ }\n\n const found = discoverSkillFiles(repoRoot, excludeHashes, ingestedHashes, ingestedNames);\n if (found.length === 0) return;\n\n const selectable = found.filter(f => !f.ingested);\n if (selectable.length === 0) {\n // Everything discovered is already ingested — no selection needed, just acknowledge\n // and move on to the next install step. (No numbered list: nothing to pick.)\n const names = found.map(f => f.name);\n const shown = names.slice(0, 5).join(', ') + (names.length > 5 ? `, +${names.length - 5} more` : '');\n console.log(`\\n✓ ${found.length} skill${found.length === 1 ? '' : 's'} already ingested (${shown}) — nothing new to add.`);\n return;\n }\n\n // Don't nag: skip if this exact set of UNINGESTED skills was already offered.\n const setHash = discoverySetHash(selectable);\n let prev = '';\n try { prev = readFileSync(SKILLS_DISCOVERED_PATH, 'utf-8').trim(); } catch { /* first run */ }\n if (prev === setHash) return;\n\n const picks = await promptSkillDiscovery(found);\n try { writeFileSync(SKILLS_DISCOVERED_PATH, setHash); } catch { /* best-effort */ }\n if (picks.length === 0) {\n console.log(' Skipped — you can ingest skills anytime from the Synkro dashboard or via the MCP tool.');\n return;\n }\n\n console.log(` Ingesting ${picks.length} skill${picks.length === 1 ? '' : 's'} (security-vetting each first)…`);\n const summary = await ingestSkillTasks(picks.map(i => ({ source: found[i].source, content: found[i].content })), mcpPort);\n const parts = [`${summary.ingested} ingested (${summary.rules} rule${summary.rules === 1 ? '' : 's'})`];\n if (summary.rejected.length) parts.push(`${summary.rejected.length} blocked`);\n console.log(` Skills: ${parts.join(', ')} — each in its own toggleable pack.`);\n } catch (e) {\n console.warn(` ⚠ skill discovery skipped (${e instanceof Error ? e.message : String(e)})`);\n }\n}\n\nexport function detectGitRepo(): string | null {\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return '';\n }\n };\n const remoteUrl = run('git remote get-url origin');\n if (remoteUrl) {\n return remoteUrl\n .replace(/^git@[^:]+:/, '')\n .replace(/^https?:\\/\\/[^/]+\\//, '')\n .replace(/\\.git$/, '');\n }\n // No origin remote — fall back to the repo's top-level directory name.\n const root = run('git rev-parse --show-toplevel');\n return root ? (root.split('/').pop() || null) : null;\n}\n\nfunction getClaudeProjectsFolder(): string | null {\n const cwd = process.cwd();\n // CC stores transcripts in ~/.claude/projects/{sanitized-path}/\n // where sanitized-path replaces / with -\n const sanitized = '-' + cwd.replace(/\\//g, '-');\n const projectsDir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(projectsDir) ? projectsDir : null;\n}\n\ninterface SessionInsight {\n session_id: string;\n insight_type: 'summary' | 'user_message';\n content: string;\n metadata?: Record<string, unknown>;\n}\n\nfunction extractSessionInsights(projectsDir: string): SessionInsight[] {\n const insights: SessionInsight[] = [];\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Extract compaction summaries\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user' &&\n typeof entry.message?.content === 'string' &&\n entry.message.content.startsWith('This session is being continued')) {\n insights.push({\n session_id: sessionId,\n insight_type: 'summary',\n content: entry.message.content.slice(0, 4000),\n metadata: { source: 'compaction_summary' },\n });\n }\n } catch {}\n }\n\n // Extract user messages (last 20 per session — recent preferences)\n const userMessages: string[] = [];\n for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type === 'user') {\n const text = typeof entry.message?.content === 'string'\n ? entry.message.content\n : Array.isArray(entry.message?.content)\n ? entry.message.content.map((b: any) => b.text ?? b).filter((t: any) => typeof t === 'string').join(' ')\n : null;\n if (text && text.length > 10 && text.length < 2000 && !text.startsWith('This session is being continued')) {\n userMessages.push(text);\n }\n }\n } catch {}\n }\n for (const msg of userMessages.reverse()) {\n insights.push({\n session_id: sessionId,\n insight_type: 'user_message',\n content: msg.slice(0, 2000),\n });\n }\n } catch {}\n }\n\n return insights;\n}\n\nasync function ingestSessionTranscripts(gatewayUrl: string, token: string, repo: string): Promise<number> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return 0;\n\n const insights = extractSessionInsights(projectsDir);\n if (insights.length === 0) return 0;\n\n console.log(`Found ${insights.length} session insights from Claude Code history...`);\n\n // Send in batches of 100\n let total = 0;\n for (let i = 0; i < insights.length; i += 100) {\n const batch = insights.slice(i, i + 100);\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/ingest-sessions`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions: batch }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number };\n total += result.accepted;\n }\n } catch {}\n }\n\n return total;\n}\n\ninterface TranscriptMessage {\n message_index: number;\n type: 'user' | 'assistant';\n content: string;\n tool_calls?: Array<{ name: string; input: string; id: string }>;\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n}\n\nfunction extractTextContent(content: any): string {\n if (typeof content === 'string') return content.slice(0, 8000);\n if (Array.isArray(content)) {\n return content\n .filter((b: any) => typeof b === 'string' || (b?.type === 'text'))\n .map((b: any) => typeof b === 'string' ? b : (b?.text || ''))\n .join(' ')\n .slice(0, 8000);\n }\n return '';\n}\n\n// ─── Cursor transcript import ────────────────────────────────────────────────\n// Cursor stores transcripts at\n// ~/.cursor/projects/{slug}/agent-transcripts/{convId}/{convId}.jsonl\n// where each line is {role:'user'|'assistant', message:{content:[{type:'text',\n// text}]}} — note `role` is top-level (vs CC's {type, message:{role,...}}).\n// The slug is the workspace path with non-alphanumerics collapsed to '-'.\nfunction cursorProjectSlug(workspaceRoot: string): string {\n return workspaceRoot.replace(/^[/]+/, '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n}\n\nfunction getCursorTranscriptsDir(): string | null {\n const dir = join(homedir(), '.cursor', 'projects', cursorProjectSlug(process.cwd()), 'agent-transcripts');\n return existsSync(dir) ? dir : null;\n}\n\n// Conversation ids are UUIDs. CWE-22: validate against a strict allowlist before\n// using a dir name in any path join, so a crafted entry (.., /) can't traverse.\nfunction isSafeConvId(id: string): boolean {\n return /^[A-Za-z0-9_-]+$/.test(id);\n}\n\nfunction parseCursorTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n const role = entry.role || entry.message?.role;\n if (role !== 'user' && role !== 'assistant') continue;\n const text = extractTextContent(entry.message?.content ?? entry.content);\n if (!text) continue;\n messages.push({ message_index: i, type: role, content: text });\n } catch {}\n }\n return messages;\n}\n\nasync function syncCursorTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const dir = getCursorTranscriptsDir();\n if (!dir) return { sessions: 0, messages: 0 };\n // Each conversation is a subdir {convId}/ holding {convId}.jsonl.\n let convDirs: string[] = [];\n try {\n convDirs = readdirSync(dir, { withFileTypes: true })\n .filter(d => d.isDirectory() && isSafeConvId(d.name))\n .map(d => d.name);\n } catch { return { sessions: 0, messages: 0 }; }\n if (convDirs.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${convDirs.length} Cursor session transcripts, importing + embedding...`);\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < convDirs.length; i++) {\n const convId = convDirs[i];\n if (!isSafeConvId(convId)) continue; // defense-in-depth before any path join\n const filePath = join(dir, convId, `${convId}.jsonl`);\n if (!existsSync(filePath)) continue;\n try {\n const all = parseCursorTranscriptFile(filePath);\n const messages = all.length > 500 ? all.slice(-500) : all;\n if (messages.length === 0) continue;\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: convId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === convDirs.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);\n }\n // Offset so the Stop hook doesn't re-send already-imported lines.\n try {\n const lc = readFileSync(filePath, 'utf-8').split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, convId), String(lc), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nfunction parseTranscriptFile(filePath: string): TranscriptMessage[] {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n const messages: TranscriptMessage[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = JSON.parse(lines[i]);\n if (entry.type !== 'user' && entry.type !== 'assistant') continue;\n\n const msg: TranscriptMessage = {\n message_index: i,\n type: entry.type,\n content: extractTextContent(entry.message?.content),\n };\n\n if (entry.type === 'assistant') {\n if (Array.isArray(entry.message?.content)) {\n const toolCalls = entry.message.content\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({\n name: b.name || '',\n input: JSON.stringify(b.input || {}).slice(0, 500),\n id: b.id || '',\n }));\n if (toolCalls.length > 0) msg.tool_calls = toolCalls;\n }\n if (entry.message?.model) msg.model = entry.message.model;\n if (entry.message?.usage) {\n msg.usage = {\n input_tokens: entry.message.usage.input_tokens,\n output_tokens: entry.message.usage.output_tokens,\n cache_creation_input_tokens: entry.message.usage.cache_creation_input_tokens,\n cache_read_input_tokens: entry.message.usage.cache_read_input_tokens,\n };\n }\n }\n\n if (msg.content.length > 0) messages.push(msg);\n } catch {}\n }\n\n return messages;\n}\n\nasync function syncTranscriptsLocal(mcpPort: number, mcpToken: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(` Found ${files.length} CC session transcripts, importing + embedding...`);\n\n let totalSessions = 0;\n let totalMessages = 0;\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i];\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;\n if (messages.length === 0) continue;\n\n const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}` },\n body: JSON.stringify({ session_id: sessionId, repo, messages }),\n signal: AbortSignal.timeout(15000),\n });\n if (resp.ok) {\n const result = await resp.json() as { ingested?: number };\n totalSessions++;\n totalMessages += result.ingested ?? messages.length;\n }\n } catch {}\n\n if ((i + 1) % 10 === 0 || i === files.length - 1) {\n process.stdout.write(`\\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);\n }\n\n // Write offset so the Stop hook doesn't re-send\n try {\n const content = readFileSync(join(projectsDir, file), 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n\n if (totalSessions > 0) process.stdout.write('\\n');\n return { sessions: totalSessions, messages: totalMessages };\n}\n\nasync function syncTranscriptsBulk(gatewayUrl: string, token: string, repo: string): Promise<{ sessions: number; messages: number }> {\n const projectsDir = getClaudeProjectsFolder();\n if (!projectsDir) return { sessions: 0, messages: 0 };\n\n const files = readdirSync(projectsDir).filter(f => f.endsWith('.jsonl'));\n if (files.length === 0) return { sessions: 0, messages: 0 };\n\n console.log(`Found ${files.length} CC session transcripts, syncing...`);\n\n const maxMessagesPerSession = 500;\n let totalSessions = 0;\n let totalMessages = 0;\n\n // Batch sessions into groups of 5\n for (let i = 0; i < files.length; i += 5) {\n const batch = files.slice(i, i + 5);\n const sessions: Array<{ cc_session_id: string; messages: TranscriptMessage[] }> = [];\n\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n\n try {\n const allMessages = parseTranscriptFile(filePath);\n const messages = allMessages.length > maxMessagesPerSession\n ? allMessages.slice(-maxMessagesPerSession)\n : allMessages;\n\n if (messages.length > 0) {\n sessions.push({ cc_session_id: sessionId, messages });\n }\n } catch {}\n }\n\n if (sessions.length === 0) continue;\n\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ repo, sessions }),\n });\n if (resp.ok) {\n const result = await resp.json() as { accepted: number; sessions: number };\n totalMessages += result.accepted;\n totalSessions += result.sessions;\n }\n } catch {}\n\n // Write offset files so the Stop hook doesn't re-send\n for (const file of batch) {\n const sessionId = file.replace('.jsonl', '');\n const filePath = join(projectsDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lineCount = content.split('\\n').filter(Boolean).length;\n writeFileSync(join(OFFSETS_DIR, sessionId), String(lineCount), 'utf-8');\n } catch {}\n }\n }\n\n return { sessions: totalSessions, messages: totalMessages };\n}\n","/**\n * Filesystem setup for the local-CC channel plugin.\n *\n * Idempotent: safe to call multiple times. Writes:\n * ~/.synkro/cc_sessions/synkro-channel.ts (the Bun MCP plugin)\n * ~/.synkro/cc_sessions/package.json (deps for the plugin)\n * ~/.synkro/cc_sessions/.claude/settings.json (fastMode:true scoped to this cwd)\n *\n * Then patches ~/.claude.json to register the plugin under mcpServers, and runs\n * `bun install` in the session dir so @modelcontextprotocol/sdk is on disk.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, copyFileSync, renameSync, unlinkSync, openSync, fsyncSync, closeSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { spawnSync } from 'node:child_process';\n\nexport const CLAUDE_JSON_BACKUP_PATH = join(homedir(), '.claude.json.synkro-bak');\n\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\nexport const PLUGIN_PATH = join(SESSION_DIR, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH = join(SESSION_DIR, 'package.json');\nexport const PLUGIN_SETTINGS_DIR = join(SESSION_DIR, '.claude');\nexport const PLUGIN_SETTINGS_PATH = join(PLUGIN_SETTINGS_DIR, 'settings.json');\nexport const PROJECT_MCP_PATH = join(SESSION_DIR, '.mcp.json');\nexport const CLAUDE_JSON_PATH = join(homedir(), '.claude.json');\nexport const RUN_SCRIPT_PATH = join(SESSION_DIR, 'run-claude.sh');\nexport const TMUX_SESSION_NAME = 'synkro-local-cc';\n\n// Channel 1 (general A) — pueue task → claude listening on this internal port.\n// The MCP server's dispatcher (8929) round-robins between Channel 1 and 3.\nexport const CHANNEL_1_PORT = 8941;\n\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\nexport const PLUGIN_PATH_2 = join(SESSION_DIR_2, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_2 = join(SESSION_DIR_2, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_2 = join(SESSION_DIR_2, '.claude');\nexport const PLUGIN_SETTINGS_PATH_2 = join(PLUGIN_SETTINGS_DIR_2, 'settings.json');\nexport const PROJECT_MCP_PATH_2 = join(SESSION_DIR_2, '.mcp.json');\nexport const RUN_SCRIPT_PATH_2 = join(SESSION_DIR_2, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_2 = 'synkro-local-cc-2';\n// CWE worker A. Dispatcher on 8930 round-robins Channel 2 and 4.\nexport const CHANNEL_2_PORT = 8951;\n\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\nexport const PLUGIN_PATH_3 = join(SESSION_DIR_3, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_3 = join(SESSION_DIR_3, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_3 = join(SESSION_DIR_3, '.claude');\nexport const PLUGIN_SETTINGS_PATH_3 = join(PLUGIN_SETTINGS_DIR_3, 'settings.json');\nexport const PROJECT_MCP_PATH_3 = join(SESSION_DIR_3, '.mcp.json');\nexport const RUN_SCRIPT_PATH_3 = join(SESSION_DIR_3, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_3 = 'synkro-local-cc-3';\nexport const CHANNEL_3_PORT = 8942; // general worker B\n\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\nexport const PLUGIN_PATH_4 = join(SESSION_DIR_4, 'synkro-channel.ts');\nexport const PLUGIN_PKG_PATH_4 = join(SESSION_DIR_4, 'package.json');\nexport const PLUGIN_SETTINGS_DIR_4 = join(SESSION_DIR_4, '.claude');\nexport const PLUGIN_SETTINGS_PATH_4 = join(PLUGIN_SETTINGS_DIR_4, 'settings.json');\nexport const PROJECT_MCP_PATH_4 = join(SESSION_DIR_4, '.mcp.json');\nexport const RUN_SCRIPT_PATH_4 = join(SESSION_DIR_4, 'run-claude.sh');\nexport const TMUX_SESSION_NAME_4 = 'synkro-local-cc-4';\nexport const CHANNEL_4_PORT = 8952; // CWE worker B\n\n/**\n * Bash wrapper that owns the tmux session hosting `claude`. Pueue runs this\n * script as its task. The script:\n * 1. Kills any prior tmux session by the same name (idempotent restart).\n * 2. Starts a detached tmux session running claude. tmux gives claude a\n * real pty, so it stays in interactive mode (vs. dropping into --print).\n * 3. Blocks on tmux's lifetime so pueue's task duration mirrors claude's.\n * `synkro local-cc stop` kills the tmux session, which unblocks the\n * poll and lets pueue mark the task done.\n *\n * Dismissal of startup dialogs (workspace trust / dev-channels /\n * MCP consent) is owned by the TS-side waitForChannelReady, which polls\n * the channel TCP port and injects \\`tmux send-keys ... Enter\\` each tick\n * the probe fails. That's adaptive (no fixed window) and self-terminating\n * (stops the moment the port binds).\n */\nconst RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Do not edit.\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME}\nLOG=\"$HOME/.synkro/cc_sessions/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\n# Pre-flight checks\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH. Install Claude Code first.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\n# Check claude is authenticated\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed. Is Claude Code installed correctly?\"\n exit 1\nfi\n\nlog \"Starting local-CC session...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\n# Kill any previous session so restarts come up clean.\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\n# Start claude inside a detached tmux session so it has a real pty.\n# Redirect stderr to the log so we can see why it dies.\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\n# Claude's --dangerously-load-development-channels shows a confirmation\n# prompt: option 1 = \"I am using this for local development\" (accept),\n# option 2 = \"Exit\". Auto-accept by sending '1' + Enter.\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n # Additional Enter for any follow-up prompts (workspace trust, MCP consent)\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to claude session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n log \"Try running claude manually to verify auth: claude --print 'say ok'\"\n exit 1\nfi\n\nlog \"tmux session started successfully.\"\n\n# Block on the tmux session so pueue's task lifetime tracks claude's.\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst RUN_SCRIPT_SOURCE_2 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. Channel 2 (CWE scan, port ${CHANNEL_2_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_2}\nLOG=\"$HOME/.synkro/cc_sessions_2/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC channel 2 (port ${CHANNEL_2_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to channel 2 session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_2_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// General worker B (port CHANNEL_3_PORT) — dispatcher pairs this with cc_sessions on 8929.\nconst RUN_SCRIPT_SOURCE_3 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. General worker B (port ${CHANNEL_3_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_3}\nLOG=\"$HOME/.synkro/cc_sessions_3/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC general worker B (port ${CHANNEL_3_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to general worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_3_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\n// CWE worker B (port CHANNEL_4_PORT) — dispatcher pairs this with cc_sessions_2 on 8930.\nconst RUN_SCRIPT_SOURCE_4 = `#!/usr/bin/env bash\n# Auto-generated by \\`synkro install\\`. CWE worker B (port ${CHANNEL_4_PORT}).\nset -uo pipefail\n\nSESSION=${TMUX_SESSION_NAME_4}\nLOG=\"$HOME/.synkro/cc_sessions_4/run-claude.log\"\n\nlog() { echo \"[$(date '+%H:%M:%S')] $*\" >> \"$LOG\"; echo \"$*\"; }\n\nif ! command -v claude >/dev/null 2>&1; then\n log \"ERROR: claude CLI not found on PATH.\"\n exit 1\nfi\n\nif ! command -v tmux >/dev/null 2>&1; then\n log \"ERROR: tmux not found on PATH.\"\n exit 1\nfi\n\nif ! claude --version >/dev/null 2>&1; then\n log \"ERROR: claude --version failed.\"\n exit 1\nfi\n\nlog \"Starting local-CC CWE worker B (port ${CHANNEL_4_PORT})...\"\nlog \"claude version: $(claude --version 2>&1 | head -1)\"\n\ntmux kill-session -t \"=$SESSION\" 2>/dev/null || true\n\ntmux new-session -d -s \"$SESSION\" \\\\\n \"SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-5 2>>$LOG; echo 'claude exited with code '\\$'?' >> $LOG\"\n\nsleep 3\nif tmux has-session -t \"=$SESSION\" 2>/dev/null; then\n tmux send-keys -t \"$SESSION\" '1' 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n sleep 1\n tmux send-keys -t \"$SESSION\" Enter 2>/dev/null || true\n log \"Sent auto-accept keys to CWE worker B session.\"\nfi\n\nsleep 2\nif ! tmux has-session -t \"$SESSION\" 2>/dev/null; then\n log \"ERROR: tmux session died immediately. Check $LOG for details.\"\n exit 1\nfi\n\nlog \"tmux session started successfully (port ${CHANNEL_4_PORT}).\"\n\nwhile tmux has-session -t \"=$SESSION\" 2>/dev/null; do\n sleep 5\ndone\n\nlog \"tmux session ended.\"\n`;\n\nconst MCP_SERVER_NAME = 'synkro-local';\n\nconst PLUGIN_PACKAGE_JSON = JSON.stringify(\n {\n name: 'synkro-local-channel',\n private: true,\n version: '0.1.0',\n type: 'module',\n dependencies: {\n '@modelcontextprotocol/sdk': '^1.0.0',\n },\n },\n null,\n 2,\n) + '\\n';\n\nexport class LocalCCInstallError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCInstallError';\n }\n}\n\ninterface ChannelInstallSlot {\n sessionDir: string;\n pluginPath: string;\n pluginPkgPath: string;\n pluginSettingsDir: string;\n pluginSettingsPath: string;\n projectMcpPath: string;\n runScriptPath: string;\n runScriptSource: string;\n}\n\nconst CHANNELS: readonly ChannelInstallSlot[] = [\n { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH, pluginPkgPath: PLUGIN_PKG_PATH, pluginSettingsDir: PLUGIN_SETTINGS_DIR, pluginSettingsPath: PLUGIN_SETTINGS_PATH, projectMcpPath: PROJECT_MCP_PATH, runScriptPath: RUN_SCRIPT_PATH, runScriptSource: RUN_SCRIPT_SOURCE },\n { sessionDir: SESSION_DIR_2, pluginPath: PLUGIN_PATH_2, pluginPkgPath: PLUGIN_PKG_PATH_2, pluginSettingsDir: PLUGIN_SETTINGS_DIR_2, pluginSettingsPath: PLUGIN_SETTINGS_PATH_2, projectMcpPath: PROJECT_MCP_PATH_2, runScriptPath: RUN_SCRIPT_PATH_2, runScriptSource: RUN_SCRIPT_SOURCE_2 },\n { sessionDir: SESSION_DIR_3, pluginPath: PLUGIN_PATH_3, pluginPkgPath: PLUGIN_PKG_PATH_3, pluginSettingsDir: PLUGIN_SETTINGS_DIR_3, pluginSettingsPath: PLUGIN_SETTINGS_PATH_3, projectMcpPath: PROJECT_MCP_PATH_3, runScriptPath: RUN_SCRIPT_PATH_3, runScriptSource: RUN_SCRIPT_SOURCE_3 },\n { sessionDir: SESSION_DIR_4, pluginPath: PLUGIN_PATH_4, pluginPkgPath: PLUGIN_PKG_PATH_4, pluginSettingsDir: PLUGIN_SETTINGS_DIR_4, pluginSettingsPath: PLUGIN_SETTINGS_PATH_4, projectMcpPath: PROJECT_MCP_PATH_4, runScriptPath: RUN_SCRIPT_PATH_4, runScriptSource: RUN_SCRIPT_SOURCE_4 },\n];\n\nfunction writePluginFiles(): void {\n for (const c of CHANNELS) {\n mkdirSync(c.sessionDir, { recursive: true });\n mkdirSync(c.pluginSettingsDir, { recursive: true });\n // Channel plugin source lives in the Docker image only (IP protection).\n // Workers inside the container use /app/channel-plugin.ts directly.\n writeFileSync(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, 'utf-8');\n writeFileSync(\n c.pluginSettingsPath,\n JSON.stringify({\n fastMode: true,\n enabledMcpjsonServers: ['synkro-local'],\n // Extended thinking is pure latency tax on a structured verdict grade and\n // emits reasoning the verdict parser isn't tuned for. effortLevel only\n // SOFTENS it; CLAUDE_CODE_DISABLE_THINKING=1 is the authoritative off\n // switch. Mirror the cloud pool (pool-config.sh) so local + cloud graders\n // behave identically.\n effortLevel: 'low',\n env: { CLAUDE_CODE_DISABLE_THINKING: '1' },\n }, null, 2) + '\\n',\n 'utf-8',\n );\n writeFileSync(c.runScriptPath, c.runScriptSource, 'utf-8');\n chmodSync(c.runScriptPath, 0o755);\n }\n}\n\nfunction runBunInstall(): void {\n for (const c of CHANNELS) {\n const r = spawnSync('bun', ['install', '--silent'], {\n cwd: c.sessionDir,\n encoding: 'utf-8',\n timeout: 120_000,\n });\n if (r.status !== 0) {\n throw new LocalCCInstallError(\n `bun install failed in ${c.sessionDir}: ${r.stderr || r.stdout || 'unknown'}`,\n );\n }\n }\n}\n\ninterface ClaudeJson {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n projects?: Record<string, Record<string, unknown>>;\n [k: string]: unknown;\n}\n\n/**\n * Safely apply `mutator` to ~/.claude.json with five layers of protection:\n * 1. Refuse to operate on malformed input — never try to \"fix\" a corrupted file.\n * 2. Take a rolling backup at ~/.claude.json.synkro-bak before any write.\n * 3. Validate the post-mutation result is still well-formed JSON and didn't\n * lose any top-level keys that existed in the original.\n * 4. Atomic write via tmp + fsync + rename(2) (POSIX-atomic).\n * 5. Restore from backup on any error in the write phase.\n *\n * If the file doesn't exist (no claude install on this machine), the mutation\n * is silently skipped — we don't create the file, since claude owns it.\n */\nfunction safelyMutateClaudeJson(mutator: (json: ClaudeJson) => boolean): void {\n if (!existsSync(CLAUDE_JSON_PATH)) {\n // claude.json doesn't exist; nothing to mutate. We don't create it.\n return;\n }\n\n // (1) Read + parse, refuse to proceed on malformed input.\n const originalText = readFileSync(CLAUDE_JSON_PATH, 'utf-8');\n let parsed: ClaudeJson;\n try {\n parsed = JSON.parse(originalText) as ClaudeJson;\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to modify malformed ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Please fix the JSON manually before retrying.`,\n err,\n );\n }\n\n const originalKeys = new Set(Object.keys(parsed));\n\n // (Mutator returns true if it changed anything; false means no-op.)\n const dirty = mutator(parsed);\n if (!dirty) return;\n\n // (3a) Validate the in-memory mutation didn't drop a top-level key.\n for (const k of originalKeys) {\n if (!(k in parsed)) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: mutator dropped top-level key \"${k}\". ` +\n `This is a bug — please report.`,\n );\n }\n }\n\n const newText = JSON.stringify(parsed, null, 2) + '\\n';\n\n // (3b) Round-trip parse to guarantee well-formed output before any write.\n try {\n JSON.parse(newText);\n } catch (err) {\n throw new LocalCCInstallError(\n `refusing to write ${CLAUDE_JSON_PATH}: serialized result is not valid JSON. ` +\n `This is a bug — please report.`,\n err,\n );\n }\n\n // (2) Take a rolling backup of the *original* (pre-mutation) bytes.\n copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);\n\n // (4) Atomic write: tmp + fsync + rename. This guarantees a reader\n // never sees a half-written file — they see either the old or new.\n const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;\n try {\n writeFileSync(tmpPath, newText, 'utf-8');\n // fsync the tmp file so the bytes are durably on disk before rename.\n const fd = openSync(tmpPath, 'r');\n try { fsyncSync(fd); } finally { closeSync(fd); }\n renameSync(tmpPath, CLAUDE_JSON_PATH);\n } catch (err) {\n // (5) Best-effort recovery: restore the original from backup.\n try { unlinkSync(tmpPath); } catch { /* ignore */ }\n try { copyFileSync(CLAUDE_JSON_BACKUP_PATH, CLAUDE_JSON_PATH); } catch { /* ignore */ }\n throw new LocalCCInstallError(\n `failed to write ${CLAUDE_JSON_PATH}: ${(err as Error).message}. ` +\n `Backup at ${CLAUDE_JSON_BACKUP_PATH} preserves the prior state.`,\n err,\n );\n }\n}\n\n/**\n * Register the synkro-local MCP server at PROJECT scope (.mcp.json inside\n * ~/.synkro/cc_sessions) — NOT at user scope in ~/.claude.json. User scope\n * would make every claude session on the machine spawn the plugin and race\n * to bind the UDS, which corrupts request routing.\n */\nfunction writeProjectMcpJson(): void {\n for (const c of CHANNELS) {\n const mcp = {\n mcpServers: {\n [MCP_SERVER_NAME]: {\n command: 'bun',\n args: [c.pluginPath],\n },\n },\n };\n writeFileSync(c.projectMcpPath, JSON.stringify(mcp, null, 2) + '\\n', 'utf-8');\n }\n}\n\n/**\n * Pre-accept the workspace trust dialog and approve the project-scoped MCP\n * server in ~/.claude.json so claude doesn't block on those prompts under\n * tmux. Also strips any stale top-level mcpServers[\"synkro-local\"] left over\n * from previous (incorrect) installs.\n *\n * All mutations go through safelyMutateClaudeJson — see that function for\n * the integrity guarantees.\n */\nfunction patchClaudeJson(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n\n // Remove any stale user-scope registration from previous installs.\n if (parsed.mcpServers && typeof parsed.mcpServers === 'object' && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n\n if (!parsed.projects || typeof parsed.projects !== 'object') {\n parsed.projects = {};\n }\n const projects = parsed.projects as Record<string, Record<string, unknown>>;\n\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n const existing = projects[dir] && typeof projects[dir] === 'object'\n ? projects[dir]\n : {};\n const wantEnabled = Array.from(new Set([\n ...((existing.enabledMcpjsonServers as string[] | undefined) ?? []),\n MCP_SERVER_NAME,\n ]));\n const next = {\n ...existing,\n hasTrustDialogAccepted: true,\n hasCompletedProjectOnboarding: true,\n enabledMcpjsonServers: wantEnabled,\n };\n if (\n existing.hasTrustDialogAccepted !== true ||\n existing.hasCompletedProjectOnboarding !== true ||\n JSON.stringify(existing.enabledMcpjsonServers ?? []) !== JSON.stringify(wantEnabled)\n ) {\n projects[dir] = next;\n dirty = true;\n }\n }\n return dirty;\n });\n}\n\n/**\n * Run the full local-CC install. Throws LocalCCInstallError on any failure\n * the caller is expected to surface. Does NOT start the pueue task — call\n * `pueue.ensureRunning()` after for that.\n */\nexport function installLocalCC(): { sessionDir: string; pluginPath: string } {\n // bun is required for the plugin runtime — auto-install if missing.\n let bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n if (process.platform === 'darwin') {\n console.log(' Installing bun via brew...');\n const brewR = spawnSync('brew', ['install', 'oven-sh/bun/bun'], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n if (brewR.status !== 0) {\n throw new LocalCCInstallError('bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash');\n }\n bunCheck = spawnSync('bun', ['--version'], { encoding: 'utf-8' });\n if (bunCheck.status !== 0) {\n throw new LocalCCInstallError('bun installed but not found on PATH. Restart your terminal and re-run install.');\n }\n } else {\n throw new LocalCCInstallError('bun is required. Install it: curl -fsSL https://bun.sh/install | bash');\n }\n }\n\n writePluginFiles();\n runBunInstall();\n writeProjectMcpJson();\n patchClaudeJson();\n\n return { sessionDir: SESSION_DIR, pluginPath: PLUGIN_PATH };\n}\n\n/**\n * Strip every trace of local-cc from ~/.claude.json:\n * - user-scope mcpServers[\"synkro-local\"] (legacy from older installs)\n * - projects[SESSION_DIR] entry (workspace trust + enabledMcpjsonServers we added)\n *\n * Goes through safelyMutateClaudeJson for the same integrity guarantees as\n * patchClaudeJson — atomic write, backup, post-write validation. Errors are\n * surfaced so the caller sees them, not silently swallowed.\n *\n * Plugin files in ~/.synkro/cc_sessions/ are removed by the caller via\n * `rm -rf ~/.synkro` when uninstalling with --purge.\n */\nexport function uninstallLocalCC(): void {\n safelyMutateClaudeJson(parsed => {\n let dirty = false;\n if (parsed.mcpServers && parsed.mcpServers[MCP_SERVER_NAME]) {\n delete parsed.mcpServers[MCP_SERVER_NAME];\n dirty = true;\n }\n for (const dir of CHANNELS.map(c => c.sessionDir)) {\n if (parsed.projects && typeof parsed.projects === 'object' && parsed.projects[dir]) {\n delete parsed.projects[dir];\n dirty = true;\n }\n }\n return dirty;\n });\n}\n","// :)\n/**\n * synkro disconnect — remove all Synkro hook entries from agent settings.\n *\n * Preserves any other hooks the user has (Corridor, Noma, custom).\n *\n * Normal uninstall removes everything — hooks, configs, container, image, and\n * ~/.synkro — EXCEPT the user's scan data (~/.synkro/pgdata and pgdata-backups).\n * `--purge` wipes that data too, after an explicit typed confirmation, leaving\n * the machine as if Synkro was never installed.\n *\n * Order matters: tear down LIVE runtime first (pueue task + tmux session +\n * spawned bun plugin), THEN strip config, THEN remove files. Reversing the\n * order leaves orphaned processes holding the TCP port and a tmux session\n * with claude running from a deleted cwd.\n */\nimport { existsSync, rmSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { detectAgents } from '../installer/agentDetect.js';\nimport { uninstallCCHooks } from '../installer/ccHookConfig.js';\nimport { uninstallCursorHooks } from '../installer/cursorHookConfig.js';\nimport { uninstallMcpConfig, uninstallCursorMcpConfig, uninstallClaudeDesktopMcpConfig } from '../installer/mcpConfig.js';\nimport { uninstallLocalCC } from '../local-cc/install.js';\nimport { dockerRemove, dockerStatus, dockerSafeStop, imageTag } from '../local-cc/dockerInstall.js';\nimport { uninstallRefreshAgent, needsKeychainBridge } from '../local-cc/macKeychain.js';\nimport { emit, flush } from '../telemetry/index.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\n\nasync function tearDownLocalCC(): Promise<void> {\n const docker = dockerStatus();\n if (docker.running) {\n await dockerSafeStop();\n console.log('✓ stopped synkro-server container');\n } else {\n console.log('· no synkro-server container running');\n }\n dockerRemove();\n console.log('✓ removed synkro-server container');\n\n // Remove the Docker image too — a later `synkro install` just re-pulls it.\n try {\n const image = imageTag();\n const r = spawnSync('docker', ['rmi', '-f', image], { encoding: 'utf-8', timeout: 30_000 });\n console.log(r.status === 0 ? `✓ removed Docker image ${image}` : '· no Docker image to remove');\n } catch { /* docker may not be installed */ }\n\n if (needsKeychainBridge()) {\n try { uninstallRefreshAgent(); console.log('✓ launchd refresh agent removed'); } catch { /* best-effort */ }\n }\n\n uninstallLocalCC();\n console.log('✓ cleaned ~/.claude.json entries');\n}\n\nfunction confirmPurge(): Promise<boolean> {\n console.log('⚠ WARNING — synkro uninstall --purge');\n console.log(' This permanently deletes ALL Synkro data on this machine,');\n console.log(' including every scan finding, telemetry record, and backup in');\n console.log(' ~/.synkro/pgdata and ~/.synkro/pgdata-backups. It cannot be undone.\\n');\n if (!process.stdin.isTTY) {\n console.log(' Non-interactive shell — re-run in a terminal to confirm.');\n return Promise.resolve(false);\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(\" Type 'yes' to wipe everything (anything else cancels): \", (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'yes');\n });\n });\n}\n\nexport async function disconnectCommand(\n args: string[] = [],\n opts: { flavor?: 'disconnect' | 'uninstall' | 'reinstall' } = {},\n): Promise<void> {\n const purge = args.includes('--purge');\n // bootstrap.js routes both `synkro uninstall` and `synkro disconnect` here\n // directly, so an explicit flavor from a wrapper wins; otherwise sniff argv\n // to attribute the event correctly. Reinstall always passes flavor itself.\n const flavor: 'disconnect' | 'uninstall' | 'reinstall' =\n opts.flavor ?? (process.argv[2] === 'uninstall' ? 'uninstall' : 'disconnect');\n const startedAt = Date.now();\n\n // Track which surfaces actually came off so the uninstall event records\n // reality (vs a blanket \"all true\"). DESIGN.md §5.\n const removed = { hooks: false, mcp: false, config: false, telemetry_db: false };\n\n if (purge && !(await confirmPurge())) {\n console.log('\\nAborted — nothing was removed.');\n return;\n }\n\n console.log('\\nSynkro uninstall starting...\\n');\n\n // Tear down local-cc runtime FIRST so we don't leave orphaned processes.\n await tearDownLocalCC();\n\n const agents = detectAgents();\n let sawClaudeCode = false;\n for (const agent of agents) {\n if (agent.kind === 'claude_code') {\n sawClaudeCode = true;\n const r = uninstallCCHooks(agent.settingsPath);\n if (r) removed.hooks = true;\n console.log(`${r ? '✓' : '·'} ${agent.name}: ${r ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n } else if (agent.kind === 'cursor') {\n const r = uninstallCursorHooks(agent.settingsPath);\n if (r) removed.hooks = true;\n console.log(`${r ? '✓' : '·'} ${agent.name}: ${r ? 'removed Synkro hook entries' : 'no Synkro hooks found'}`);\n }\n }\n\n // Also remove the Synkro guardrails MCP server entries from config files\n // (this is the OTHER mcp server — the rule-suggestions one — distinct from\n // the local-cc channel server cleaned up above).\n if (sawClaudeCode) {\n const mcpRemoved = uninstallMcpConfig();\n if (mcpRemoved) removed.mcp = true;\n console.log(`${mcpRemoved ? '✓' : '·'} MCP guardrails (CC): ${mcpRemoved ? 'removed from ~/.claude.json' : 'no entry found'}`);\n }\n {\n const cursorMcpRemoved = uninstallCursorMcpConfig();\n if (cursorMcpRemoved) removed.mcp = true;\n console.log(`${cursorMcpRemoved ? '✓' : '·'} MCP guardrails (Cursor): ${cursorMcpRemoved ? 'removed from ~/.cursor/mcp.json' : 'no entry found'}`);\n }\n {\n const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();\n if (desktopMcpRemoved) removed.mcp = true;\n console.log(`${desktopMcpRemoved ? '✓' : '·'} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? 'removed from claude_desktop_config.json' : 'no entry found'}`);\n }\n\n // Filesystem cleanup. Normal uninstall removes everything except the user's\n // scan data (pgdata + pgdata-backups) and the transcript import cursor\n // (.transcript-offsets) — keeping the cursor means a reinstall resumes\n // incremental transcript sync instead of re-sending (and re-embedding)\n // already-imported lines. --purge removes all of it.\n // NOTE: today even --purge wipes the whole ~/.synkro tree, so telemetry.db\n // goes with it. We still leave removed.telemetry_db = false until purge\n // actually targets it surgically — that's a follow-up (DESIGN.md §12).\n if (existsSync(SYNKRO_DIR)) {\n if (purge) {\n // Emit the uninstall event BEFORE the SYNKRO_DIR is wiped — once the dir\n // is gone, the telemetry DB it lives in is gone too, so a later emit\n // would silently no-op.\n emit('uninstall', {\n flavor,\n purge,\n removed: { ...removed, config: true },\n duration_ms: Date.now() - startedAt,\n });\n // Ship the uninstall event (and anything else pending) NOW — rmSync below\n // wipes the JSONL queue, so a later detached flush would find nothing.\n // force bypasses the throttle; best-effort — the container is already down\n // (torn down above), so this only reaches remote, and no-ops if that's off.\n await flush({ force: true });\n rmSync(SYNKRO_DIR, { recursive: true, force: true });\n removed.config = true;\n console.log(`✓ wiped ${SYNKRO_DIR} entirely — including all scan data and backups`);\n } else {\n const keep = new Set([\n join(SYNKRO_DIR, 'pgdata'),\n join(SYNKRO_DIR, 'pgdata-backups'),\n join(SYNKRO_DIR, '.transcript-offsets'),\n ]);\n const preserved: string[] = [];\n for (const entry of readdirSync(SYNKRO_DIR)) {\n const full = join(SYNKRO_DIR, entry);\n if (keep.has(full)) { preserved.push(entry); continue; }\n rmSync(full, { recursive: true, force: true });\n }\n removed.config = true;\n if (preserved.length > 0) {\n console.log(`✓ removed Synkro config from ${SYNKRO_DIR} (kept your scan data: ${preserved.join(', ')})`);\n console.log(' run `synkro uninstall --purge` to delete that too');\n } else {\n console.log(`✓ removed ${SYNKRO_DIR}`);\n }\n }\n } else {\n console.log(`· ${SYNKRO_DIR} already gone`);\n }\n\n // Non-purge teardown: emit after the FS cleanup so removed.config is honest.\n // (Purge already emitted above, before the DB was deleted.)\n if (!purge) {\n emit('uninstall', { flavor, purge, removed, duration_ms: Date.now() - startedAt });\n }\n\n console.log(purge\n ? '\\nSynkro fully removed — this machine is clean, as if it was never installed.'\n : '\\nSynkro uninstalled. Your scan data is preserved.');\n}\n","/**\n * JSONL log of every channel turn (grade / classify) — one entry per line.\n *\n * Written by `submitToChannel` after each request completes (success or fail).\n * Read by `synkro local-cc logs` to show recent activity.\n *\n * Best-effort: any I/O error here is swallowed so logging never breaks the\n * actual grading path.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, closeSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { LocalCCRole } from './prompts.js';\n\nexport const TURN_LOG_PATH = join(homedir(), '.synkro', 'cc_sessions', 'turns.log');\n\nexport type TurnStatus = 'ok' | 'timeout' | 'error';\n\nexport interface TurnEntry {\n /** ISO timestamp of when the request started. */\n ts: string;\n role: LocalCCRole;\n /** Wall-clock ms from submit to result/error. */\n duration_ms: number;\n status: TurnStatus;\n /** Truncated request payload (first ~400 chars of caller-supplied text). */\n request_preview: string;\n /** Truncated response result (first ~400 chars). Empty on non-ok. */\n response_preview: string;\n /** Parsed severity if we can extract one from the verdict. Optional. */\n severity?: string;\n /** Error message if status is timeout/error. */\n error?: string;\n}\n\nconst PREVIEW_MAX = 400;\n\nfunction truncate(s: string, max = PREVIEW_MAX): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + '… [+' + (s.length - max) + ' chars]';\n}\n\n/** Best-effort extraction of severity from the result text. */\nfunction extractSeverity(result: string): string | undefined {\n // <synkro-verdict>{\"severity\":\"audit\",…}</synkro-verdict>\n const m = result.match(/<synkro-(?:verdict|intent)>([\\s\\S]*?)<\\/synkro-(?:verdict|intent)>/);\n if (!m) return undefined;\n try {\n const obj = JSON.parse(m[1]) as { severity?: string; verdict?: string; type?: string; ok?: boolean };\n if (obj.severity) return String(obj.severity);\n if (typeof obj.ok === 'boolean') return obj.ok ? 'ok' : 'violations';\n if (obj.type) return String(obj.type);\n if (obj.verdict) return String(obj.verdict);\n } catch {\n // not parseable JSON inside the tag — ignore\n }\n return undefined;\n}\n\n/** Append one turn to the JSONL log. Best-effort — never throws. */\nexport function appendTurn(args: {\n startedAt: number;\n role: LocalCCRole;\n request: string;\n result?: string;\n status: TurnStatus;\n error?: string;\n}): void {\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n const entry: TurnEntry = {\n ts: new Date(args.startedAt).toISOString(),\n role: args.role,\n duration_ms: Date.now() - args.startedAt,\n status: args.status,\n request_preview: truncate(args.request),\n response_preview: args.result ? truncate(args.result) : '',\n severity: args.result ? extractSeverity(args.result) : undefined,\n error: args.error,\n };\n appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // never let logging fail the call site\n }\n}\n\n/**\n * Read the most recent N turn entries (newest first). Returns an empty array\n * on any error.\n */\nexport function readRecentTurns(n = 20): TurnEntry[] {\n if (!existsSync(TURN_LOG_PATH)) return [];\n try {\n // For typical sizes this is fine; if the file grows huge we'd switch to\n // tail-style reverse reading. Cap by stat size for safety.\n const size = statSync(TURN_LOG_PATH).size;\n if (size === 0) return [];\n const text = readFileSync(TURN_LOG_PATH, 'utf-8');\n const lines = text.split('\\n').filter(Boolean);\n const lastN = lines.slice(-n).reverse();\n return lastN\n .map(line => {\n try { return JSON.parse(line) as TurnEntry; } catch { return null; }\n })\n .filter((x): x is TurnEntry => x !== null);\n } catch {\n return [];\n }\n}\n\n/**\n * Tail the turn log: invoke `onEntry` for each new entry appended after the\n * call. Handles file truncation/rotation by re-syncing offset to 0 when size\n * shrinks. Returns a `stop()` to detach the watcher.\n */\nexport function followTurns(onEntry: (t: TurnEntry) => void): () => void {\n // Make sure the file exists so watchFile has something to poll. Creating\n // an empty file is harmless if it already exists.\n try {\n mkdirSync(dirname(TURN_LOG_PATH), { recursive: true });\n if (!existsSync(TURN_LOG_PATH)) {\n appendFileSync(TURN_LOG_PATH, '', 'utf-8');\n }\n } catch {\n // best-effort\n }\n\n let lastSize = (() => {\n try { return statSync(TURN_LOG_PATH).size; } catch { return 0; }\n })();\n let pendingPartial = ''; // bytes after the last \\n if a write split on a line boundary\n\n const drainNewBytes = (from: number, to: number): void => {\n if (to <= from) return;\n let fd: number | null = null;\n try {\n fd = openSync(TURN_LOG_PATH, 'r');\n const len = to - from;\n const buf = Buffer.alloc(len);\n readSync(fd, buf, 0, len, from);\n const text = pendingPartial + buf.toString('utf-8');\n const lastNewline = text.lastIndexOf('\\n');\n if (lastNewline === -1) {\n pendingPartial = text;\n return;\n }\n const complete = text.slice(0, lastNewline);\n pendingPartial = text.slice(lastNewline + 1);\n for (const line of complete.split('\\n')) {\n if (!line) continue;\n try {\n onEntry(JSON.parse(line) as TurnEntry);\n } catch {\n // skip malformed lines\n }\n }\n } catch {\n // best-effort\n } finally {\n if (fd !== null) {\n try { closeSync(fd); } catch { /* noop */ }\n }\n }\n };\n\n // Poll-based watcher (cross-platform reliable). 250ms is responsive\n // enough for tail-f UX without burning CPU.\n watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {\n if (curr.size < lastSize) {\n // file truncated/rotated — reset.\n lastSize = 0;\n pendingPartial = '';\n }\n if (curr.size > lastSize) {\n drainNewBytes(lastSize, curr.size);\n lastSize = curr.size;\n }\n });\n\n return () => unwatchFile(TURN_LOG_PATH);\n}\n","/**\n * Client for the local-CC channel plugin.\n *\n * POSTs to a TCP port on 127.0.0.1 exposed by the channel plugin running\n * inside the pueue-managed `claude` process. Returns the raw text Claude\n * wrote into the `reply` tool's `result` argument.\n */\n\nimport { request as httpRequest } from 'node:http';\nimport { connect } from 'node:net';\nimport type { LocalCCRole } from './prompts.js';\nimport { appendTurn } from './turnLog.js';\n\nexport const CHANNEL_HOST = '127.0.0.1';\nexport const CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || '8929', 10);\n\nconst DEFAULT_TIMEOUT_MS = 90_000;\n\nexport class LocalCCError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'LocalCCError';\n }\n}\n\nexport interface SubmitOptions {\n /** Timeout for the round-trip in ms (default 90s). */\n timeoutMs?: number;\n /** Override the channel port (default: CHANNEL_PORT / 8929). */\n port?: number;\n}\n\n/**\n * Send a request through the channel and wait for Claude's `reply` tool output.\n * Throws LocalCCError if the channel isn't reachable, the channel times out, or\n * the plugin returns a non-2xx status.\n */\nexport async function submitToChannel(\n role: LocalCCRole,\n payload: string,\n opts: SubmitOptions = {},\n): Promise<string> {\n const body = JSON.stringify({ role, payload, content: payload });\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const port = opts.port ?? CHANNEL_PORT;\n const startedAt = Date.now();\n\n try {\n const result = await new Promise<string>((resolve, reject) => {\n const req = httpRequest({\n host: CHANNEL_HOST,\n port,\n method: 'POST',\n path: '/submit',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n timeout: timeoutMs,\n }, res => {\n const chunks: Buffer[] = [];\n res.on('data', c => chunks.push(c));\n res.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf-8');\n if (res.statusCode !== 200) {\n reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));\n return;\n }\n try {\n const parsed = JSON.parse(text) as { result?: string; error?: string };\n if (parsed.error) {\n reject(new LocalCCError(parsed.error));\n return;\n }\n resolve(String(parsed.result ?? ''));\n } catch (err) {\n reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));\n }\n });\n });\n req.on('timeout', () => {\n req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));\n });\n req.on('error', err => {\n const msg = (err as NodeJS.ErrnoException).code === 'ECONNREFUSED'\n ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)`\n : `channel request failed: ${(err as Error).message}`;\n reject(new LocalCCError(msg, err));\n });\n req.write(body);\n req.end();\n });\n appendTurn({ startedAt, role, request: payload, result, status: 'ok' });\n return result;\n } catch (err) {\n const message = (err as Error).message ?? String(err);\n const status = /timed out/i.test(message) ? 'timeout' : 'error';\n appendTurn({ startedAt, role, request: payload, status, error: message });\n throw err;\n }\n}\n\n/** Quick TCP probe — true if anything is listening on the given port (default: CHANNEL_PORT). */\nexport function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, CHANNEL_HOST);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n","/**\n * `synkro grade <edit|bash>` — replacement for the legacy\n * ~/.synkro/bin/grader_daemon.py. Reads a grading payload from stdin,\n * routes it through the local-CC channel, and writes the raw verdict\n * (including the <synkro-verdict>...</synkro-verdict> wrapper that the\n * shell hook scripts grep for) to stdout.\n *\n * Failures print to stderr and exit non-zero so the hook scripts fall\n * through to the cloud fast-tier path cleanly.\n */\n\nimport { submitToChannel, LocalCCError } from '../local-cc/client.js';\nimport type { LocalCCRole } from '../local-cc/prompts.js';\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on('data', c => chunks.push(c));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n process.stdin.on('error', reject);\n });\n}\n\nexport async function gradeCommand(args: string[]): Promise<void> {\n const mode = args[0] ?? '';\n let role: LocalCCRole;\n if (mode === 'edit') role = 'grade-edit';\n else if (mode === 'bash') role = 'grade-bash';\n else if (mode === 'plan') role = 'grade-plan';\n else if (mode === 'cwe') role = 'grade-cwe';\n else {\n console.error('Usage: synkro grade <edit|bash|plan|cwe>');\n process.exit(2);\n }\n\n const payload = await readStdin();\n if (!payload.trim()) {\n console.error('synkro grade: empty stdin');\n process.exit(2);\n }\n\n try {\n const result = await submitToChannel(role, payload, { timeoutMs: 60_000 });\n process.stdout.write(result);\n if (!result.endsWith('\\n')) process.stdout.write('\\n');\n } catch (err) {\n if (err instanceof LocalCCError) {\n console.error(`synkro grade: ${err.message}`);\n } else {\n console.error(`synkro grade: ${(err as Error).message}`);\n }\n process.exit(3);\n }\n}\n","/**\n * Pueue lifecycle for the local-CC `claude` process.\n *\n * One labelled pueue task per user, started at install (or on demand) and\n * managed via the `pueue` CLI. We assume `pueue` and `pueued` are installed\n * and the daemon is running — surface a friendly error otherwise.\n */\n\nimport { execFileSync, spawnSync, spawn } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { connect } from 'node:net';\n\nexport const TASK_LABEL = 'synkro-local-cc';\nexport const TMUX_SESSION = 'synkro-local-cc';\nexport const SESSION_DIR = join(homedir(), '.synkro', 'cc_sessions');\n\nexport const TASK_LABEL_2 = 'synkro-local-cc-2';\nexport const TMUX_SESSION_2 = 'synkro-local-cc-2';\nexport const SESSION_DIR_2 = join(homedir(), '.synkro', 'cc_sessions_2');\n\nexport const TASK_LABEL_3 = 'synkro-local-cc-3';\nexport const TMUX_SESSION_3 = 'synkro-local-cc-3';\nexport const SESSION_DIR_3 = join(homedir(), '.synkro', 'cc_sessions_3');\n\nexport const TASK_LABEL_4 = 'synkro-local-cc-4';\nexport const TMUX_SESSION_4 = 'synkro-local-cc-4';\nexport const SESSION_DIR_4 = join(homedir(), '.synkro', 'cc_sessions_4');\n\nexport class PueueError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = 'PueueError';\n }\n}\n\ninterface PueueStatusEntry {\n id: number;\n label?: string;\n status: string | { Running?: unknown; Done?: { result?: string } };\n command: string;\n path: string;\n}\n\ninterface PueueStatus {\n tasks: Record<string, PueueStatusEntry>;\n}\n\nfunction pueueAvailable(): void {\n const r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.');\n }\n}\n\nfunction statusJson(): PueueStatus {\n pueueAvailable();\n const r = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || 'unknown error'} — is pueued running?`);\n }\n try {\n return JSON.parse(r.stdout) as PueueStatus;\n } catch (err) {\n throw new PueueError(`pueue status returned non-JSON output: ${r.stdout.slice(0, 200)}`, err);\n }\n}\n\nexport interface TaskInfo {\n id: number;\n label: string;\n status: string;\n command: string;\n cwd: string;\n}\n\nfunction statusName(s: PueueStatusEntry['status']): string {\n if (typeof s === 'string') return s;\n if (s && typeof s === 'object') {\n if ('Running' in s) return 'Running';\n if ('Done' in s) {\n const result = (s as { Done?: { result?: string | object } }).Done?.result;\n if (typeof result === 'string') return `Done (${result})`;\n if (result && typeof result === 'object') return `Done (${Object.keys(result)[0] ?? 'unknown'})`;\n return 'Done';\n }\n return Object.keys(s)[0] ?? 'unknown';\n }\n return 'unknown';\n}\n\nexport interface ChannelIdentity {\n taskLabel: string;\n tmuxSession: string;\n sessionDir: string;\n}\n\nexport const CHANNEL_PRIMARY: ChannelIdentity = { taskLabel: TASK_LABEL, tmuxSession: TMUX_SESSION, sessionDir: SESSION_DIR };\nexport const CHANNEL_SECONDARY: ChannelIdentity = { taskLabel: TASK_LABEL_2, tmuxSession: TMUX_SESSION_2, sessionDir: SESSION_DIR_2 };\nexport const CHANNEL_TERTIARY: ChannelIdentity = { taskLabel: TASK_LABEL_3, tmuxSession: TMUX_SESSION_3, sessionDir: SESSION_DIR_3 };\nexport const CHANNEL_QUATERNARY: ChannelIdentity = { taskLabel: TASK_LABEL_4, tmuxSession: TMUX_SESSION_4, sessionDir: SESSION_DIR_4 };\nexport const ALL_CHANNELS: readonly ChannelIdentity[] = [CHANNEL_PRIMARY, CHANNEL_SECONDARY, CHANNEL_TERTIARY, CHANNEL_QUATERNARY];\n\n/** Find a synkro local-cc task by label. Returns null if absent. */\nexport function findTask(channel: ChannelIdentity = CHANNEL_PRIMARY): TaskInfo | null {\n const data = statusJson();\n for (const [id, t] of Object.entries(data.tasks)) {\n if (t.label === channel.taskLabel) {\n return {\n id: Number(id),\n label: t.label,\n status: statusName(t.status),\n command: t.command,\n cwd: t.path,\n };\n }\n }\n return null;\n}\n\n/** True if a synkro local-cc task is currently Running. */\nexport function isRunning(channel: ChannelIdentity = CHANNEL_PRIMARY): boolean {\n const t = findTask(channel);\n return t?.status === 'Running';\n}\n\nexport interface StartOptions {\n /** Override the channel plugin script path. */\n pluginPath?: string;\n /** Working directory for the claude process. */\n cwd?: string;\n /** Which channel to start (default: primary). */\n channel?: ChannelIdentity;\n}\n\n/**\n * Add a fresh pueue task for the local-cc claude session. Removes any prior\n * task with the same label first (Done or otherwise) so subsequent calls\n * always replace cleanly.\n */\nexport function startTask(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const cwd = opts.cwd ?? ch.sessionDir;\n // Remove ALL tasks with this label (not just the first) to clear stale duplicates\n let existing = findTask(ch);\n while (existing) {\n if (existing.status === 'Running' || existing.status === 'Queued') {\n spawnSync('tmux', ['kill-session', '-t', `=${ch.tmuxSession}`], { encoding: 'utf-8' });\n spawnSync('pueue', ['kill', String(existing.id)], { encoding: 'utf-8' });\n for (let i = 0; i < 10; i++) {\n const check = findTask(ch);\n if (!check || check.id !== existing.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(existing.id)], { encoding: 'utf-8' });\n existing = findTask(ch);\n }\n\n const runScript = join(cwd, 'run-claude.sh');\n const args = [\n 'add',\n '--label', ch.taskLabel,\n '--working-directory', cwd,\n '--',\n 'bash', runScript,\n ];\n\n const r = spawnSync('pueue', args, { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);\n }\n const created = findTask(ch);\n if (!created) {\n throw new PueueError(`pueue add succeeded but no task with label ${ch.taskLabel} found`);\n }\n return created;\n}\n\n/** Stop and remove the synkro local-cc task. Also kills the tmux session\n * that owns the underlying claude process. No-op if neither is present. */\nexport function stopTask(channel: ChannelIdentity = CHANNEL_PRIMARY): void {\n spawnSync('tmux', ['kill-session', '-t', `=${channel.tmuxSession}`], { encoding: 'utf-8' });\n // Remove ALL tasks with this label, waiting for each kill to land\n let t = findTask(channel);\n while (t) {\n if (t.status === 'Running' || t.status === 'Queued') {\n spawnSync('pueue', ['kill', String(t.id)], { encoding: 'utf-8' });\n // Wait for the task to leave Running state before removing\n for (let i = 0; i < 10; i++) {\n const check = findTask(channel);\n if (!check || check.id !== t.id || (check.status !== 'Running' && check.status !== 'Queued')) break;\n spawnSync('sleep', ['0.5'], { encoding: 'utf-8' });\n }\n }\n spawnSync('pueue', ['remove', String(t.id)], { encoding: 'utf-8' });\n t = findTask(channel);\n }\n}\n\n/** Print the last N log lines for the task. Returns the raw text. */\nexport function tailLogs(lines = 80, channel: ChannelIdentity = CHANNEL_PRIMARY): string {\n const t = findTask(channel);\n if (!t) return `(no ${channel.taskLabel} task)`;\n const r = spawnSync('pueue', ['log', '--lines', String(lines), String(t.id)], { encoding: 'utf-8' });\n return r.stdout || r.stderr || '(no output)';\n}\n\n/** Ensure the task is running; start it if not. Returns the task info. */\nexport function ensureRunning(opts: StartOptions = {}): TaskInfo {\n const ch = opts.channel ?? CHANNEL_PRIMARY;\n const t = findTask(ch);\n if (t && t.status === 'Running') return t;\n return startTask(opts);\n}\n\n/** Probe a TCP port — true if something accepts a connection within timeoutMs. */\nfunction probePort(host: string, port: number, timeoutMs = 500): Promise<boolean> {\n return new Promise(resolve => {\n const sock = connect(port, host);\n const done = (ok: boolean) => {\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n\n/** Dismiss startup prompts in the tmux session. Best-effort.\n * Sends '1' (dev-channels accept) then Enter (other prompts). */\nfunction tmuxDismissPrompts(tmuxSession: string = TMUX_SESSION): void {\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, '1'], { encoding: 'utf-8' });\n spawnSync('tmux', ['send-keys', '-t', tmuxSession, 'Enter'], { encoding: 'utf-8' });\n}\n\n/**\n * Wait up to `timeoutMs` for the channel TCP listener to come up. Each tick\n * the probe fails, send an Enter into the tmux session — that walks claude\n * past any startup confirmation dialogs (workspace trust, dev-channels,\n * MCP consent). The loop self-terminates the moment the port binds, so\n * we don't keep spamming Enters once claude is at the live `❯` prompt.\n */\nexport async function waitForChannelReady(\n port: number,\n timeoutMs = 60_000,\n host = '127.0.0.1',\n tmuxSession: string = TMUX_SESSION,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await probePort(host, port)) return true;\n tmuxDismissPrompts(tmuxSession);\n await new Promise(r => setTimeout(r, 1000));\n }\n return probePort(host, port);\n}\n\nfunction brewInstall(pkg: string): boolean {\n const brew = spawnSync('brew', ['--version'], { encoding: 'utf-8' });\n if (brew.status !== 0) return false;\n console.log(` Installing ${pkg} via brew...`);\n const r = spawnSync('brew', ['install', pkg], { encoding: 'utf-8', stdio: 'inherit', timeout: 120_000 });\n return r.status === 0;\n}\n\n/** Ensure pueue is installed and pueued daemon is running. Auto-installs on macOS. */\nexport function assertPueueInstalled(): void {\n let r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('pueue')) {\n r = spawnSync('pueue', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('pueue install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue');\n }\n }\n // Ensure pueued daemon is running\n const status = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (status.status !== 0) {\n console.log(' Starting pueued daemon...');\n const child = spawn('pueued', ['-d'], { stdio: 'ignore', detached: true });\n child.unref();\n // Give the daemon a moment to bind its socket\n spawnSync('sleep', ['1']);\n const retry = spawnSync('pueue', ['status', '--json'], { encoding: 'utf-8', timeout: 5_000 });\n if (retry.status !== 0) {\n throw new PueueError('pueue daemon not reachable after starting pueued. Check `pueued` manually.');\n }\n }\n spawnSync('pueue', ['parallel', '2'], { encoding: 'utf-8' });\n}\n\n/** Ensure claude CLI is installed. */\nexport function assertClaudeInstalled(): void {\n const r = spawnSync('claude', ['--version'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n throw new PueueError('claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code');\n }\n}\n\n/** Ensure tmux is installed. Auto-installs on macOS. */\nexport function assertTmuxInstalled(): void {\n let r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) {\n if (process.platform === 'darwin' && brewInstall('tmux')) {\n r = spawnSync('tmux', ['-V'], { encoding: 'utf-8' });\n if (r.status !== 0) throw new PueueError('tmux install succeeded but binary not found on PATH.');\n } else {\n throw new PueueError('tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)');\n }\n }\n}\n\n/** No-throw helper used by the install command for status messages. */\nexport function readVersion(bin: string): string | null {\n try {\n return execFileSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 }).trim();\n } catch {\n return null;\n }\n}\n","/**\n * Inference provider detection.\n *\n * The source of truth is the user's inference_settings in TimescaleDB.\n * At install time, `/api/v1/cli/me` returns `local_inference: true` when\n * `gradingProvider = 'claude-code'`. The install flow writes\n * `SYNKRO_LOCAL_INFERENCE='yes'` into ~/.synkro/config.env.\n *\n * At runtime the hook scripts use a TCP probe (synkro_channel_up) to decide\n * routing — this module is only used by CLI commands and intent routers.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nexport function isLocalCCEnabled(): boolean {\n if (!existsSync(CONFIG_PATH)) return false;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);\n return match?.[1] === 'yes';\n } catch {\n return false;\n }\n}\n","/**\n * `synkro local-cc <subcommand>` — manage the local pueue-backed Claude Code\n * session that powers grading and intent classification when the inference\n * provider toggle is set to local-cc.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { installLocalCC, SESSION_DIR, PLUGIN_PATH, RUN_SCRIPT_PATH, PLUGIN_SETTINGS_PATH, CLAUDE_JSON_PATH, TMUX_SESSION_NAME, CHANNEL_2_PORT, TMUX_SESSION_NAME_2 } from '../local-cc/install.js';\nimport { readRecentTurns, followTurns, TURN_LOG_PATH, type TurnEntry } from '../local-cc/turnLog.js';\nimport {\n assertClaudeInstalled,\n assertPueueInstalled,\n assertTmuxInstalled,\n ensureRunning,\n findTask,\n startTask,\n stopTask,\n tailLogs,\n waitForChannelReady,\n CHANNEL_PRIMARY,\n CHANNEL_SECONDARY,\n} from '../local-cc/pueue.js';\nimport { isLocalCCEnabled } from '../local-cc/settings.js';\nimport { refreshCreds, needsKeychainBridge, credsAreStale, CLAUDE_CREDS_FILE } from '../local-cc/macKeychain.js';\nimport { dockerStatus, dockerStop, dockerUpdate, waitForContainerReady, waitForWorkersReady, resolveWorkerConfig } from '../local-cc/dockerInstall.js';\nimport { reconcileHarness, syncSkillFiles } from './install.js';\nimport { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from 'node:fs';\n\nconst SYNKRO_CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\n/**\n * Resolution order matches commands/install.ts: live env var first, then the\n * persisted config.env value, then 'bare-host'. Kept inline rather than\n * imported from install.ts so this module stays loadable when the install\n * command tree is pruned in future cleanups.\n */\nfunction deploymentMode(): 'bare-host' | 'docker' {\n const env = (process.env.SYNKRO_DEPLOYMENT_MODE || '').toLowerCase();\n if (env === 'docker') return 'docker';\n if (env === 'bare-host') return 'bare-host';\n try {\n if (fsExistsSync(SYNKRO_CONFIG_PATH)) {\n const m = fsReadFileSync(SYNKRO_CONFIG_PATH, 'utf-8').match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);\n if (m && m[1] === 'docker') return 'docker';\n }\n } catch { /* fall through */ }\n return 'bare-host';\n}\n\nfunction inDockerMode(): boolean { return deploymentMode() === 'docker'; }\nimport { submitToChannel, isChannelAvailable, CHANNEL_HOST, CHANNEL_PORT } from '../local-cc/client.js';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\n\nfunction printHelp(): void {\n console.log(`synkro local-cc — manage the local Claude Code inference session\n\nOVERVIEW\n Routes Synkro's grading and intent-classification work through a long-running\n Claude Code session on this machine instead of remote Inngest+Gemini.\n\n When enabled, three call sites switch over:\n • security grading on edit/bash hooks\n • intent classification (agent input → structured intent)\n • remediate intent classification\n\n The session is hosted in a detached tmux session managed by a pueue task.\n The CLI talks to it via Claude Code's channels API: a Bun MCP plugin that\n pushes events into the session and receives Claude's responses through a\n \\`reply\\` MCP tool.\n\nUSAGE\n synkro local-cc <subcommand> [args]\n\nSUBCOMMANDS\n enable Install plugin, start pueue task, flip toggle to local-cc\n disable Flip toggle back to inngest (pueue task left running)\n status Show provider toggle, pueue task state, channel reachability\n start Idempotently bring up the pueue task + wait for the channel\n stop Kill the tmux session and remove the pueue task\n restart stop, then start\n install Regenerate ~/.synkro/cc_sessions/ files (plugin, runner, settings)\n logs [N] [--raw] [--live]\n Show the last N (default 20) channel turns: when, role,\n duration, severity, request preview.\n --raw / -r include full request/response payloads\n --live / -f tail the log; print new turns as they arrive\n (Ctrl-C to exit)\n --tmux escape hatch — print the raw pueue/tmux\n pane log instead\n attach [--readonly] Attach to the tmux session hosting claude (Ctrl-B D to detach;\n --readonly / -r to attach view-only)\n test Send a smoke-test classification through the channel\n help Show this message\n\nCONFIGURATION\n Provider toggle:\n Stored server-side in your inference settings (gradingProvider).\n Toggle via: synkro local-cc enable / disable\n Or via dashboard inference settings (set grading provider to claude-code).\n\n Claude Code session settings (scoped to ~/.synkro/cc_sessions only):\n ${PLUGIN_SETTINGS_PATH}\n Currently sets {\"fastMode\": true}. Edit to add other CC settings for this\n session without affecting your other CC projects.\n\n MCP server registration (for the channel plugin):\n ${CLAUDE_JSON_PATH}\n A 'synkro-local' entry under mcpServers, pointing at:\n bun ${PLUGIN_PATH}\n Workspace trust is also pre-accepted under projects[\\`${SESSION_DIR}\\`].\n\n Channel runtime files:\n ${RUN_SCRIPT_PATH}\n bash wrapper that pueue invokes; owns the tmux session lifecycle.\n ${PLUGIN_PATH}\n Bun MCP channel plugin (auto-generated, do not edit).\n 127.0.0.1:${CHANNEL_PORT}\n Loopback TCP endpoint the CLI POSTs to in order to submit a request.\n\nENVIRONMENT VARIABLES\n SYNKRO_CHANNEL_PORT Override the TCP port used by both the plugin and\n the CLI client (loopback only). Default: 8929\n SYNKRO_CHANNEL_TIMEOUT_MS Per-request timeout inside the channel plugin\n (default: 120000)\n\nREQUIRED TOOLS\n claude Claude Code CLI, authenticated to your subscription\n pueue pueue + pueued (https://github.com/Nukesor/pueue) running\n tmux For detached pty around claude\n bun Runtime for the MCP channel plugin\n\nTROUBLESHOOTING\n • Channel unreachable after \\`enable\\`?\n synkro local-cc logs # check pueue task output\n tmux attach -t ${TMUX_SESSION_NAME} # see what claude is showing\n • Stale state after a crash:\n synkro local-cc stop && synkro local-cc start\n • Want to inspect or interact with the live session:\n synkro local-cc attach\n`);\n}\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readGatewayUrl(): string {\n if (existsSync(CONFIG_PATH)) {\n const m = readFileSync(CONFIG_PATH, 'utf-8').match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);\n if (m) return m[1];\n }\n return 'https://api.synkro.sh';\n}\n\nfunction updateLocalInferenceFlag(enabled: boolean): void {\n if (!existsSync(CONFIG_PATH)) return;\n let content = readFileSync(CONFIG_PATH, 'utf-8');\n const flag = enabled ? 'yes' : 'no';\n if (content.includes('SYNKRO_LOCAL_INFERENCE=')) {\n content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);\n } else {\n content = content.trimEnd() + `\\nSYNKRO_LOCAL_INFERENCE='${flag}'\\n`;\n }\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\nasync function setServerGradingProvider(provider: 'claude-code' | null): Promise<void> {\n await ensureValidToken();\n const jwt = getAccessToken();\n if (!jwt) throw new Error('Not authenticated. Run `synkro install` first.');\n const gatewayUrl = readGatewayUrl();\n const body = provider\n ? { roles: { grading: { provider, model: 'default' } } }\n : { roles: { grading: { provider: null, model: null } } };\n const resp = await fetch(`${gatewayUrl}/api/settings/inference?scope=user`, {\n method: 'PUT',\n headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!resp.ok) {\n const text = await resp.text().catch(() => '');\n throw new Error(`Failed to update inference settings: ${resp.status} ${text.slice(0, 200)}`);\n }\n}\n\nasync function cmdStatus(): Promise<void> {\n console.log(`Local inference: ${isLocalCCEnabled() ? 'enabled' : 'disabled'}`);\n console.log(`Deployment mode: ${deploymentMode()}`);\n\n if (inDockerMode()) {\n const status = dockerStatus();\n if (!status.running) {\n console.log('synkro-server container: not running');\n console.log('Run `synkro install` (with SYNKRO_DEPLOYMENT_MODE=docker) to provision.');\n } else {\n console.log(`synkro-server container: running (${status.image})`);\n try {\n const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3_000) });\n console.log(`Health probe: ${r.ok ? 'ok' : `HTTP ${r.status}`}`);\n } catch (err) {\n console.log(`Health probe: ${(err as Error).message}`);\n }\n }\n if (needsKeychainBridge()) {\n console.log(`Keychain creds: ${credsAreStale() ? 'STALE — run `synkro local-cc refresh-creds`' : 'fresh'}`);\n }\n return;\n }\n\n try {\n assertPueueInstalled();\n } catch (err) {\n console.log(`Pueue: NOT AVAILABLE (${(err as Error).message})`);\n return;\n }\n\n // Channel 1 (judge: bash + edit, port 8929)\n const t = findTask(CHANNEL_PRIMARY);\n if (!t) {\n console.log('Channel 1 (judge) pueue task: not present');\n } else {\n console.log(`Channel 1 (judge) pueue task: id=${t.id} status=${t.status}`);\n }\n const ch1Up = await isChannelAvailable();\n console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? 'reachable' : 'unreachable'}`);\n const tmux1 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? 'live' : 'absent'}`);\n\n // Channel 2 (CWE, port 8930)\n const t2 = findTask(CHANNEL_SECONDARY);\n if (!t2) {\n console.log('Channel 2 (CWE) pueue task: not present');\n } else {\n console.log(`Channel 2 (CWE) pueue task: id=${t2.id} status=${t2.status}`);\n }\n const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);\n console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? 'reachable' : 'unreachable'}`);\n const tmux2 = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME_2}`], { encoding: 'utf-8' });\n console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? 'live' : 'absent'}`);\n}\n\nasync function cmdEnable(): Promise<void> {\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n console.log('Installing local-CC channel plugin...');\n const r = installLocalCC();\n console.log(` plugin: ${r.pluginPath}`);\n console.log(` cwd: ${r.sessionDir}`);\n console.log('Starting channel 1 (judge)...');\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(` task: id=${t1.id} status=${t1.status}`);\n console.log('Starting channel 2 (CWE)...');\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(` task: id=${t2.id} status=${t2.status}`);\n console.log('Waiting for channels (auto-confirming any CC prompts)...');\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n if (ready1) console.log(` channel 1 ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);\n else console.warn(` ⚠ channel 1 did not come up within 60s — check \\`synkro local-cc logs\\``);\n if (ready2) console.log(` channel 2 ready at ${CHANNEL_HOST}:${CHANNEL_2_PORT}`);\n else console.warn(` ⚠ channel 2 (CWE) did not come up within 60s`);\n console.log('Updating inference settings...');\n await setServerGradingProvider('claude-code');\n updateLocalInferenceFlag(true);\n console.log('Grading provider set to claude-code (local inference enabled).');\n}\n\nasync function cmdDisable(): Promise<void> {\n console.log('Updating inference settings...');\n await setServerGradingProvider(null);\n updateLocalInferenceFlag(false);\n console.log('Grading provider cleared (remote inference restored). Pueue task left running — use `synkro local-cc stop` to terminate.');\n}\n\nasync function warmChannels(ready1: boolean, ready2: boolean): Promise<void> {\n const warmups: Promise<void>[] = [];\n if (ready1) {\n warmups.push(\n submitToChannel('grade-bash', 'Proposed command: echo hello\\nUser intent: warmup\\nRecent user messages: []\\nRecent actions: []\\nOrg rules: []\\n', { timeoutMs: 30_000 })\n .then(() => console.log(' channel 1 warm.'))\n .catch(() => console.log(' channel 1 warmup skipped (non-fatal).'))\n );\n }\n if (ready2) {\n warmups.push(\n submitToChannel('grade-cwe', 'File: /tmp/warmup.ts\\nContent (first 4000 chars):\\nconsole.log(\"hello\");\\n\\nCWE rules to check against:\\n[]\\n', { timeoutMs: 30_000, port: CHANNEL_2_PORT })\n .then(() => console.log(' channel 2 warm.'))\n .catch(() => console.log(' channel 2 warmup skipped (non-fatal).'))\n );\n }\n if (warmups.length) {\n console.log('Warming up inference...');\n await Promise.all(warmups);\n }\n}\n\nasync function cmdStart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n // With worker flags, `start` (re)creates the container with the requested\n // pool config. Without flags it just ensures the container is running.\n if (rest.length > 0) {\n const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);\n console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n return;\n }\n // Container is normally managed by docker's `--restart unless-stopped`,\n // so \"start\" really just means: ensure it's running and healthy. If it's\n // not even installed, point the user at `synkro install`.\n const status = dockerStatus();\n if (!status.running) {\n console.warn('synkro-server container is not running. Run `synkro install` to provision it.');\n process.exitCode = 1;\n return;\n }\n console.log('synkro-server container already running — waiting for /healthz...');\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? `✓ container ready (${status.healthz})` : '⚠ container did not pass /healthz within 60s');\n return;\n }\n\n assertClaudeInstalled();\n assertPueueInstalled();\n assertTmuxInstalled();\n const t1 = ensureRunning({ channel: CHANNEL_PRIMARY });\n console.log(`Channel 1 (judge): id=${t1.id} status=${t1.status}`);\n const t2 = ensureRunning({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 2 (CWE): id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction cmdStop(): void {\n if (inDockerMode()) {\n dockerStop();\n console.log('synkro-server container stopped and removed.');\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n console.log('Both channels stopped.');\n}\n\nasync function cmdRestart(rest: string[] = []): Promise<void> {\n if (inDockerMode()) {\n const { explicit } = resolveWorkerConfig(rest);\n let claudeWorkers: number;\n let cursorWorkers: number;\n if (explicit) {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n } else {\n const reconciled = reconcileHarness();\n if (reconciled) {\n ({ claudeWorkers, cursorWorkers } = reconciled);\n } else {\n ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));\n }\n }\n console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);\n await dockerUpdate({ claudeWorkers, cursorWorkers });\n const ready = await waitForContainerReady(60_000);\n console.log(ready ? '✓ container ready' : '⚠ container did not pass /healthz within 60s');\n if (ready) {\n const workersUp = await waitForWorkersReady(30_000);\n console.log(workersUp ? '✓ workers ready' : '⚠ workers did not register within 30s');\n if (workersUp) await syncSkillFiles();\n }\n return;\n }\n stopTask(CHANNEL_PRIMARY);\n stopTask(CHANNEL_SECONDARY);\n const t1 = startTask({ channel: CHANNEL_PRIMARY });\n const t2 = startTask({ channel: CHANNEL_SECONDARY });\n console.log(`Channel 1 restarted: id=${t1.id} status=${t1.status}`);\n console.log(`Channel 2 restarted: id=${t2.id} status=${t2.status}`);\n const [ready1, ready2] = await Promise.all([\n waitForChannelReady(CHANNEL_PORT, 60_000, CHANNEL_HOST, CHANNEL_PRIMARY.tmuxSession),\n waitForChannelReady(CHANNEL_2_PORT, 60_000, CHANNEL_HOST, CHANNEL_SECONDARY.tmuxSession),\n ]);\n console.log(ready1 ? `channel 1 ready (${CHANNEL_PORT}).` : '⚠ channel 1 did not come up within 60s.');\n console.log(ready2 ? `channel 2 ready (${CHANNEL_2_PORT}).` : '⚠ channel 2 (CWE) did not come up within 60s.');\n await warmChannels(ready1, ready2);\n}\n\nfunction relativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (!Number.isFinite(ts)) return iso;\n const sec = Math.max(0, Math.round((Date.now() - ts) / 1000));\n if (sec < 60) return `${sec}s ago`;\n if (sec < 3600) return `${Math.round(sec / 60)}m ago`;\n if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;\n return `${Math.round(sec / 86400)}d ago`;\n}\n\nfunction colorize(s: string, code: number): string {\n if (!process.stdout.isTTY) return s;\n return `\u001b[${code}m${s}\u001b[0m`;\n}\n\nfunction statusGlyph(t: TurnEntry): string {\n if (t.status === 'ok') return colorize('✓', 32); // green check\n if (t.status === 'timeout') return colorize('⏱', 33); // yellow clock\n return colorize('✗', 31); // red X\n}\n\nfunction severityCell(t: TurnEntry): string {\n if (t.severity) {\n const sev = t.severity;\n if (sev === 'block' || sev === 'violations' || sev === 'unclear') return colorize(sev, 33);\n return colorize(sev, 36); // cyan for everything else\n }\n if (t.error) return colorize(t.error.slice(0, 40), 31);\n return '—';\n}\n\nfunction firstLine(s: string): string {\n return s.split('\\n').find(l => l.trim().length > 0)?.trim() ?? '';\n}\n\nfunction formatTurn(t: TurnEntry, raw: boolean): string {\n const when = relativeTime(t.ts).padEnd(8);\n const dur = (t.duration_ms < 1000\n ? `${t.duration_ms}ms`\n : `${(t.duration_ms / 1000).toFixed(1)}s`).padStart(7);\n const role = t.role.padEnd(24);\n const sev = severityCell(t).padEnd(20);\n const preview = (() => {\n const req = firstLine(t.request_preview).slice(0, 60);\n return colorize(req, 90);\n })();\n const head = `${statusGlyph(t)} ${when} ${dur} ${role} ${sev} ${preview}`;\n if (!raw) return head;\n // raw mode: include full request + response previews on subsequent lines\n const blocks: string[] = [head];\n blocks.push(colorize(' request:', 90));\n blocks.push(' ' + t.request_preview.replace(/\\n/g, '\\n '));\n if (t.response_preview) {\n blocks.push(colorize(' response:', 90));\n blocks.push(' ' + t.response_preview.replace(/\\n/g, '\\n '));\n }\n if (t.error) {\n blocks.push(colorize(' error:', 31) + ' ' + t.error);\n }\n return blocks.join('\\n');\n}\n\nfunction cmdLogs(rest: string[]): void | Promise<void> {\n let n = 20;\n let raw = false;\n let live = false;\n\n if (inDockerMode()) {\n // In docker mode the meaningful logs come from the container itself: MCP\n // boot, dispatcher routing, worker tmux output. Forward to `docker logs`\n // and let docker handle the --follow / --tail semantics.\n const followFlag = rest.includes('--live') || rest.includes('-f') ? ['--follow'] : [];\n const tailArg = (() => {\n for (const arg of rest) {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) return String(parsed);\n }\n return '200';\n })();\n spawnSync('docker', ['logs', '--tail', tailArg, ...followFlag, 'synkro-server'], { stdio: 'inherit' });\n return;\n }\n\n for (const arg of rest) {\n if (arg === '--raw' || arg === '-r') raw = true;\n else if (arg === '--live' || arg === '-f') live = true;\n else if (arg === '--tmux') {\n // Escape hatch: show the original raw pueue/tmux output for the task.\n console.log(tailLogs(80));\n return;\n } else {\n const parsed = parseInt(arg, 10);\n if (parsed > 0) n = parsed;\n }\n }\n\n const header = ' ' + colorize('status when dur role severity request', 90);\n\n // Print backlog (newest first) — same as non-live mode.\n const turns = readRecentTurns(n);\n if (turns.length === 0) {\n if (!live) {\n console.log(`No turns logged yet at ${TURN_LOG_PATH}.`);\n console.log('Run a few requests through the channel (synkro local-cc test) and try again.');\n return;\n }\n console.log(`No turns logged yet at ${TURN_LOG_PATH} — waiting for new entries… (Ctrl-C to exit)`);\n } else {\n console.log(`Last ${turns.length} channel turn(s) (newest first):`);\n console.log(header);\n for (const t of turns) console.log(' ' + formatTurn(t, raw));\n }\n\n if (!live) {\n if (!raw) console.log(' ' + colorize('(use --raw / -r to see full payloads, --live / -f to follow)', 90));\n return;\n }\n\n // Live tail. Follow the JSONL log; print each new entry as it arrives.\n // Returning a Promise keeps the process alive until SIGINT.\n return new Promise<void>(resolve => {\n console.log(' ' + colorize('— following new turns (Ctrl-C to exit) —', 90));\n const stop = followTurns(t => {\n console.log(' ' + formatTurn(t, raw));\n });\n const onSigint = () => {\n stop();\n process.removeListener('SIGINT', onSigint);\n resolve();\n };\n process.on('SIGINT', onSigint);\n });\n}\n\nfunction cmdAttach(rest: string[]): void {\n assertTmuxInstalled();\n const readonly = rest.some(a => a === '--readonly' || a === '-r');\n\n // Verify the session exists before tmux drops the user into a confusing state.\n const has = spawnSync('tmux', ['has-session', '-t', `=${TMUX_SESSION_NAME}`], { encoding: 'utf-8' });\n if (has.status !== 0) {\n console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);\n process.exit(1);\n }\n\n if (!process.stdout.isTTY) {\n console.error('attach requires a TTY. Run this command directly in your terminal, not piped.');\n process.exit(1);\n }\n\n console.log(`Attaching to tmux session '${TMUX_SESSION_NAME}'${readonly ? ' (read-only)' : ''}.`);\n console.log('Detach with Ctrl-B then D. (Do not press Ctrl-C — that would interrupt claude.)');\n console.log();\n\n const args = readonly\n ? ['attach-session', '-r', '-t', TMUX_SESSION_NAME]\n : ['attach-session', '-t', TMUX_SESSION_NAME];\n const r = spawnSync('tmux', args, { stdio: 'inherit' });\n process.exit(r.status ?? 0);\n}\n\nasync function cmdTest(): Promise<void> {\n // In docker mode the channel is published on the HOST at SYNKRO_HOST_GRADER_PORT\n // (default 18929 → container 8929). CHANNEL_PORT (8929) is the IN-container port and\n // isn't bound on the host, so the default would always \"connection refused\" even when\n // grading is healthy. Use the docker-mapped host port when in docker mode.\n const port = inDockerMode()\n ? parseInt(process.env.SYNKRO_HOST_GRADER_PORT || '18929', 10)\n : CHANNEL_PORT;\n console.log(`Sending smoke-test grading request through the channel (127.0.0.1:${port})...`);\n const result = await submitToChannel(\n 'grade-bash',\n 'Command: echo \"hello world\"\\nIntent: testing channel connectivity',\n { port },\n );\n console.log('Raw reply:');\n console.log(result);\n}\n\nfunction cmdInstall(): void {\n const r = installLocalCC();\n console.log(`Reinstalled plugin at ${r.pluginPath}`);\n}\n\n/**\n * Re-export Claude Code credentials from the macOS keychain into the\n * host-side file the container bind-mounts. Called by the launchd refresh\n * agent every 6 hours, and exposed as `synkro local-cc refresh-creds` for\n * manual invocation if the user just re-authed.\n *\n * On Linux this is a no-op — creds already live in a file the container can\n * read directly via bind mount.\n */\nfunction cmdRefreshCreds(): void {\n if (!needsKeychainBridge()) {\n console.log('No-op on this platform — credentials are already file-based.');\n return;\n }\n const refreshed = refreshCreds();\n if (refreshed) {\n console.log(`Exported claude credentials to ${CLAUDE_CREDS_FILE}`);\n } else {\n console.warn('No Claude Code credentials found in the keychain.');\n console.warn('Run `claude login` first, then re-run this command.');\n process.exitCode = 1;\n }\n if (credsAreStale()) {\n console.warn('⚠ Exported credentials are older than the refresh interval.');\n }\n}\n\nexport async function localCcCommand(args: string[]): Promise<void> {\n const sub = args[0] ?? '';\n try {\n switch (sub) {\n case 'enable': await cmdEnable(); break;\n case 'disable': cmdDisable(); break;\n case 'status': await cmdStatus(); break;\n case 'start': await cmdStart(args.slice(1)); break;\n case 'stop': cmdStop(); break;\n case 'restart': await cmdRestart(args.slice(1)); break;\n case 'install': cmdInstall(); break;\n case 'logs': await cmdLogs(args.slice(1)); break;\n case 'attach': cmdAttach(args.slice(1)); break;\n case 'test': await cmdTest(); break;\n case 'refresh-creds': cmdRefreshCreds(); break;\n case '':\n case 'help':\n case '--help':\n case '-h':\n printHelp(); break;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n printHelp();\n process.exit(1);\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n","/**\n * synkro import — backfill historical Claude Code sessions for THIS repo.\n *\n * Walks ~/.claude/projects/<sanitized-cwd>/*.jsonl (every past session in the\n * repo), parses each turn (user/assistant content + per-turn usage + tool calls),\n * and ships them to storage so the dashboard can show every historical session\n * line-by-line.\n *\n * • CLOUD mode → ships to the API /api/v1/cli/sync-transcripts (automatic).\n * • LOCAL mode → on-device only, and CONSENT-GATED: we prompt before importing\n * since it ingests the repo's full transcript history.\n *\n * Idempotent: message ids are deterministic (session:seq), so re-running is safe.\n */\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { getAccessToken, ensureValidToken } from '../auth/stub.js';\n\nconst CONFIG_PATH = join(homedir(), '.synkro', 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n const out: Record<string, string> = {};\n try {\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n } catch {}\n return out;\n}\n\nfunction projectsFolder(): string | null {\n // CC stores transcripts in ~/.claude/projects/<cwd with '/' -> '-'>. The leading\n // slash already becomes the leading dash — do NOT prepend another '-'.\n const sanitized = process.cwd().replace(/\\//g, '-');\n const dir = join(homedir(), '.claude', 'projects', sanitized);\n return existsSync(dir) ? dir : null;\n}\n\nfunction repoName(): string {\n // Use the git remote \"owner/repo\" so it matches the project the hook already\n // created (project_repos.full_name). A bare basename would create a duplicate.\n try {\n const url = execSync('git config --get remote.origin.url', { encoding: 'utf-8' }).trim();\n const m = url.match(/[:/]([^/:]+\\/[^/]+?)(?:\\.git)?$/);\n if (m) return m[1];\n } catch {}\n try { return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim().split('/').pop() || 'repo'; }\n catch { return process.cwd().split('/').pop() || 'repo'; }\n}\n\nfunction extractText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .map((b: any) => (typeof b === 'string' ? b : b?.type === 'text' ? (b.text || '') : ''))\n .filter(Boolean)\n .join('\\n');\n }\n return '';\n}\n\ninterface ParsedSession {\n cc_session_id: string;\n messages: Array<Record<string, unknown>>;\n actions: Array<Record<string, unknown>>;\n}\n\nfunction parseSession(filePath: string, sessionId: string): ParsedSession {\n const lines = readFileSync(filePath, 'utf-8').split('\\n').filter(Boolean);\n const messages: Array<Record<string, unknown>> = [];\n const actions: Array<Record<string, unknown>> = [];\n let step = 0;\n for (let i = 0; i < lines.length; i++) {\n let e: any;\n try { e = JSON.parse(lines[i]); } catch { continue; }\n const kind = e.type === 'assistant' || e.message?.role === 'assistant' ? 'assistant'\n : e.type === 'user' || e.message?.role === 'user' ? 'user' : '';\n if (!kind) continue;\n const msgObj = e.message;\n // Tool RESULTS are role:user entries carrying a `toolUseResult` and/or `tool_result`/`tool_use_id`\n // content block. Classify them STRUCTURALLY here (→ 'tool') so the server trusts the CLI's typing\n // instead of guessing positionally — a purely positional \"user-turn-after-a-tool-call = tool\" rule\n // false-positives real user interjections like \"keep going\" typed mid-tool-sequence.\n const _cr = msgObj?.content ?? e.content;\n const isToolResult = kind === 'user' && (\n !!e.toolUseResult ||\n (Array.isArray(_cr) && _cr.some((b: any) => b?.type === 'tool_result' || b?.tool_use_id != null))\n );\n const finalKind = isToolResult ? 'tool' : kind;\n const text = extractText(msgObj?.content ?? e.content);\n // Tool calls FIRST — a tool-only assistant turn (Edit/Bash/Read, no text) must\n // still be captured (it's most of a coding session); skipping on empty text\n // dropped both the turn and its tool calls.\n const blocks = kind === 'assistant' && Array.isArray(msgObj?.content) ? msgObj.content : [];\n const toolCalls = blocks\n .filter((b: any) => b?.type === 'tool_use')\n .map((b: any) => ({ name: b.name, input: JSON.stringify(b.input || {}).slice(0, 20000), id: b.id }));\n if (!text.trim() && toolCalls.length === 0) continue;\n const msg: Record<string, unknown> = {\n message_index: i, type: finalKind,\n // CC's per-entry uuid is STABLE across transcript rewrites/compaction; message_index (line\n // number) is not. The server keys conversation_messages on this uuid so re-imports update in\n // place instead of orphaning/duplicating rows. Falls back to message_index server-side if absent.\n uuid: typeof e.uuid === 'string' ? e.uuid : undefined,\n content: (text.trim() ? text.slice(0, 8000) : '↳ ' + toolCalls.map((t: any) => t.name).join(', ')),\n timestamp: e.timestamp || null,\n };\n if (kind === 'assistant') {\n if (typeof msgObj?.model === 'string') msg.model = msgObj.model;\n const u = msgObj?.usage;\n if (u && typeof u === 'object') {\n msg.usage = {\n input_tokens: Number(u.input_tokens) || 0,\n output_tokens: Number(u.output_tokens) || 0,\n cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,\n cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0,\n };\n }\n if (toolCalls.length) {\n msg.tool_calls = toolCalls;\n for (const t of toolCalls) actions.push({ step: ++step, tool: t.name, summary: '', file: null, outcome: null });\n }\n }\n messages.push(msg);\n }\n return { cc_session_id: sessionId, messages, actions };\n}\n\nfunction ask(q: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => rl.question(q, (a) => { rl.close(); resolve(/^y(es)?$/i.test(a.trim())); }));\n}\n\nexport async function importCommand(): Promise<void> {\n const config = readConfigEnv();\n const isCloud = config.SYNKRO_DEPLOY_LOCATION === 'cloud' || config.SYNKRO_STORAGE_MODE === 'cloud';\n const repo = repoName();\n\n const dir = projectsFolder();\n if (!dir) { console.log('No Claude Code transcripts found for this repo (~/.claude/projects).'); return; }\n const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));\n if (!files.length) { console.log('No sessions to import.'); return; }\n\n console.log(`Found ${files.length} session(s) for \"${repo}\" (${isCloud ? 'cloud' : 'local'} mode).`);\n\n // LOCAL: ingesting the full transcript history is consent-gated.\n if (!isCloud) {\n const ok = await ask(`Import all ${files.length} historical session(s) into your on-device store? [y/N] `);\n if (!ok) { console.log('Aborted — nothing imported.'); return; }\n }\n\n const sessions = files.map((f) => parseSession(join(dir, f), f.replace('.jsonl', '')))\n .filter((s) => s.messages.length > 0);\n const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);\n\n let ok = 0, fail = 0;\n if (isCloud) {\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) { console.log('Not authenticated — run `synkro login` first.'); return; }\n const gateway = config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh';\n // One session per call so large sessions (up to the endpoint's per-session cap)\n // land fully without a giant multi-session payload.\n for (const s of sessions) {\n try {\n const r = await fetch(`${gateway}/api/v1/cli/sync-transcripts`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({ repo, sessions: [s] }),\n signal: AbortSignal.timeout(60000),\n });\n if (r.ok) { ok++; process.stdout.write('.'); } else { fail++; console.warn(`\\n ${String(s.cc_session_id).slice(0, 8)} failed: HTTP ${r.status}`); }\n } catch (e) { fail++; console.warn(`\\n ${String(s.cc_session_id).slice(0, 8)} error: ${(e as Error).message}`); }\n }\n process.stdout.write('\\n');\n } else {\n const port = config.SYNKRO_MCP_PORT || '18931';\n for (const s of sessions) {\n try {\n const r = await fetch(`http://127.0.0.1:${port}/api/ingest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ capture_type: 'transcript_sync', session_id: s.cc_session_id, repo, messages: s.messages, actions: s.actions }),\n signal: AbortSignal.timeout(15000),\n });\n if (r.ok) ok++; else fail++;\n } catch { fail++; }\n }\n if (fail && ok === 0) console.log(' (is the local container running? `synkro status`)');\n }\n\n console.log(`\\n✓ Imported ${ok} session(s) (${totalMsgs} messages)${fail ? `, ${fail} failed` : ''}.`);\n}\n","// :)\n/**\n * Client-side pack verification — the trust gate for `synkro sync`.\n *\n * Mirrors packages/api/src/lib/packSigning.ts BYTE-FOR-BYTE: the canonical form\n * (sorted-key JSON over {rules, docs, manifest}) must reproduce exactly the\n * bytes the publisher hashed + signed, or the digest check fails. Nothing binds\n * unless verifyPack() returns true — recompute the digest from what we fetched,\n * confirm it matches the claimed digest, then verify the Ed25519 signature\n * against the distributed public key. Fail closed.\n */\n\nimport crypto from 'node:crypto';\n\nexport interface PackContent {\n rules: unknown[];\n docs?: Array<{ path: string; content: string }>;\n manifest?: Record<string, unknown>;\n}\n\n/** Deterministic JSON: object keys sorted recursively, arrays order-preserved. */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';\n}\n\n// CONTENT address — rules + docs only, NOT the manifest. Must stay byte-identical\n// to the API's packSigning.canonicalize so a client-recomputed digest matches.\nexport function canonicalize(pack: PackContent): string {\n return stableStringify({\n rules: pack.rules ?? [],\n docs: pack.docs ?? [],\n });\n}\n\nexport function computeDigest(canonical: string): string {\n return 'sha256:' + crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');\n}\n\nexport function verifySignature(digest: string, signatureB64: string, publicKeyPem: string): boolean {\n try {\n const key = crypto.createPublicKey(publicKeyPem);\n return crypto.verify(null, Buffer.from(digest, 'utf8'), key, Buffer.from(signatureB64, 'base64'));\n } catch {\n return false;\n }\n}\n\n/**\n * Recompute the digest from the pack we fetched, confirm it equals the claimed\n * digest (catches a tampered blob/row), then verify the signature. Fail closed.\n */\nexport function verifyPack(\n pack: PackContent,\n claimedDigest: string,\n signatureB64: string,\n publicKeyPem: string,\n): boolean {\n const recomputed = computeDigest(canonicalize(pack));\n if (recomputed !== claimedDigest) return false;\n return verifySignature(claimedDigest, signatureB64, publicKeyPem);\n}\n","// :)\n/**\n * synkro.lock — the trust anchor committed alongside synkro.toml.\n *\n * For every subscribed pack it pins the exact attested (version, digest,\n * signature, signing key). `synkro sync` resolves floating refs to a concrete\n * digest, verifies it, and records it here so the same bytes are reproducible\n * and a tampered re-resolve is detectable. Hand-rolled TOML writer/reader to\n * match the repo's dependency-free synkro.toml handling.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface LockEntry {\n ref: string; // \"namespace/name\"\n version: string;\n digest: string; // sha256:<hex>\n signature: string; // Ed25519, base64\n signingKeyId: string;\n}\n\nconst LOCK_FILE = 'synkro.lock';\n\nexport function lockPath(repoRoot: string): string {\n return join(repoRoot, LOCK_FILE);\n}\n\n/** Parse the [[pack]] array-of-tables in synkro.lock. Tolerant of absence. */\nexport function readLockfile(repoRoot: string): LockEntry[] {\n const fp = lockPath(repoRoot);\n if (!existsSync(fp)) return [];\n const out: LockEntry[] = [];\n let cur: Partial<LockEntry> | null = null;\n const flush = () => {\n if (cur && cur.ref && cur.version && cur.digest) {\n out.push({\n ref: cur.ref, version: cur.version, digest: cur.digest,\n signature: cur.signature ?? '', signingKeyId: cur.signingKeyId ?? '',\n });\n }\n cur = null;\n };\n for (const rawLine of readFileSync(fp, 'utf-8').split('\\n')) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n if (line === '[[pack]]') { flush(); cur = {}; continue; }\n if (!cur) continue;\n const eq = line.indexOf('=');\n if (eq === -1) continue;\n const key = line.slice(0, eq).trim();\n const val = line.slice(eq + 1).trim().replace(/^[\"']|[\"']$/g, '');\n if (key === 'ref') cur.ref = val;\n else if (key === 'version') cur.version = val;\n else if (key === 'digest') cur.digest = val;\n else if (key === 'signature') cur.signature = val;\n else if (key === 'signing_key_id') cur.signingKeyId = val;\n }\n flush();\n return out;\n}\n\nexport function writeLockfile(repoRoot: string, entries: LockEntry[]): void {\n const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));\n const body = [\n '# synkro.lock — generated by `synkro sync`. Commit this file.',\n '# Pins each subscribed pack to its verified, attested digest.',\n '',\n ...sorted.flatMap((e) => ([\n '[[pack]]',\n `ref = \"${e.ref}\"`,\n `version = \"${e.version}\"`,\n `digest = \"${e.digest}\"`,\n `signature = \"${e.signature}\"`,\n `signing_key_id = \"${e.signingKeyId}\"`,\n '',\n ])),\n ].join('\\n');\n writeFileSync(lockPath(repoRoot), body, 'utf-8');\n}\n","// :)\n/**\n * `synkro sync` — pull the registry packs a repo subscribes to, verify them,\n * and apply.\n *\n * Reads the [standards] table from synkro.toml, resolves each ref against the\n * gateway, VERIFIES the Ed25519 signature + content digest locally (nothing\n * binds unless it passes), pins the result in synkro.lock, and materializes:\n * • local → verified pack JSON in ~/.synkro/cache/packs/ (read by the\n * container's readRules; the repo tree stays clean — toml + lock only).\n * • cloud → lockfile only; mandatory/subscribed packs are injected\n * server-side at grade time (Timescale). local → pglite / cloud → timescale.\n */\n\nimport { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { readFullSynkroFile } from './install.js';\nimport { ensureValidToken, getAccessToken } from '../auth/stub.js';\nimport { verifyPack, type PackContent } from '../installer/packVerify.js';\nimport { writeLockfile, type LockEntry } from '../installer/lockfile.js';\n\ninterface ResolveResponse extends PackContent {\n ref: string;\n version: string;\n digest: string;\n signature: string;\n signingKeyId: string;\n publicKey: string;\n}\n\nfunction cacheKey(ref: string, version: string): string {\n return ref.replace(/\\//g, '__').replace(/[^\\w.@-]/g, '_') + '@' + version + '.json';\n}\n\nexport async function syncCommand(_args: string[] = []): Promise<void> {\n // Cloud mode applies standards 100% server-side: org-mandatory packs (all\n // repos or selected) are enforced at the harness by org, with no synkro.toml.\n // `synkro sync` is a local-only concept — there's nothing to pull on cloud.\n if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {\n console.log('Cloud mode: standards are applied org-wide from the dashboard');\n console.log('(Settings → Standards). No synkro.toml or `synkro sync` needed —');\n console.log('packs enforce automatically wherever Synkro is installed.');\n return;\n }\n\n const sf = readFullSynkroFile();\n if (!sf) {\n console.error('No synkro.toml found in the repo root. Run `synkro install` first.');\n process.exitCode = 1;\n return;\n }\n\n const refs = Object.entries(sf.standards);\n if (refs.length === 0) {\n console.log('No [standards] subscriptions in synkro.toml — nothing to sync.');\n return;\n }\n\n await ensureValidToken();\n const token = getAccessToken();\n if (!token) {\n console.error('Not logged in. Run `synkro login` and try again.');\n process.exitCode = 1;\n return;\n }\n\n const gateway = (process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n const cloud = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud';\n const cacheDir = join(homedir(), '.synkro', 'cache', 'packs');\n if (!cloud) mkdirSync(cacheDir, { recursive: true });\n\n console.log(`Syncing ${refs.length} standard(s) from the registry…`);\n const lock: LockEntry[] = [];\n const keptCacheFiles = new Set<string>();\n\n for (const [ref, version] of refs) {\n let url = `${gateway}/api/v1/registry/resolve?ref=${encodeURIComponent(ref)}`;\n if (version && version !== 'latest') url += `&version=${encodeURIComponent(version)}`;\n let data: ResolveResponse;\n try {\n const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });\n if (!resp.ok) {\n console.error(` ✗ ${ref}: resolve failed (${resp.status} ${resp.statusText})`);\n continue;\n }\n data = await resp.json() as ResolveResponse;\n } catch (err) {\n console.error(` ✗ ${ref}: ${(err as Error).message}`);\n continue;\n }\n\n const pack: PackContent = { rules: data.rules ?? [], docs: data.docs ?? [], manifest: data.manifest ?? {} };\n if (!verifyPack(pack, data.digest, data.signature, data.publicKey)) {\n console.error(` ✗ ${ref}: signature/digest verification FAILED — refusing to apply.`);\n continue;\n }\n\n lock.push({ ref, version: data.version, digest: data.digest, signature: data.signature, signingKeyId: data.signingKeyId });\n\n if (!cloud) {\n const fname = cacheKey(ref, data.version);\n keptCacheFiles.add(fname);\n writeFileSync(join(cacheDir, fname), JSON.stringify({\n ref, version: data.version, digest: data.digest,\n rules: pack.rules, docs: pack.docs, manifest: pack.manifest,\n }), 'utf-8');\n }\n\n const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;\n console.log(` ✓ ${ref}:${data.version} — verified (${ruleCount} rule${ruleCount === 1 ? '' : 's'})`);\n }\n\n // Local: drop cache packs no longer subscribed so a removed [standards] line\n // actually stops enforcing. (Synced packs are read-only here — edit upstream\n // and re-publish, never hand-edit the cache.)\n if (!cloud && existsSync(cacheDir)) {\n for (const f of readdirSync(cacheDir)) {\n if (f.endsWith('.json') && !keptCacheFiles.has(f)) {\n try { rmSync(join(cacheDir, f)); } catch {}\n }\n }\n }\n\n if (lock.length > 0) {\n writeLockfile(sf._repoRoot, lock);\n console.log(`Wrote synkro.lock (${lock.length} pack${lock.length === 1 ? '' : 's'} pinned).`);\n } else {\n console.error('No packs synced successfully.');\n process.exitCode = 1;\n }\n}\n","/**\n * synkro stop / start / restart — safe container lifecycle commands.\n *\n * These ensure PGLite data is always persisted to the host before shutdown,\n * and verify integrity on start. Agents should use these instead of raw\n * docker commands to avoid data corruption.\n */\nimport {\n dockerSafeStop, dockerSafeStart, dockerSafeRestart, assertDockerAvailable,\n resolveWorkerConfig, dockerUpdate, waitForContainerReady, waitForWorkersReady, readContainerConfig,\n} from '../local-cc/dockerInstall.js';\nimport { detectGitRepo, reconcileHarness, syncSkillFiles, reconcileDeployLocation, recycleCloudContainer, writeHookScripts } from './install.js';\n\n/**\n * The connected repo for a container recreate. start/restart/update all run\n * from inside the repo, so detect it live; fall back to whatever the existing\n * container recorded. Without this, recreating the container drops\n * SYNKRO_CONNECTED_REPO and the dashboard loses its project anchor.\n */\nfunction resolveConnectedRepo(): string | undefined {\n return detectGitRepo() ?? readContainerConfig()?.connectedRepo ?? undefined;\n}\n\nexport async function stopCommand(): Promise<void> {\n assertDockerAvailable();\n console.log('Synkro: stopping server\\n');\n const result = await dockerSafeStop();\n if (!result.ok) {\n console.error('\\nStop failed. Check: docker logs synkro-server');\n process.exit(1);\n }\n console.log('\\nServer stopped.');\n}\n\nexport async function startCommand(rest: string[] = []): Promise<void> {\n assertDockerAvailable();\n // With --workers / --provider(s) flags, recreate the container with the\n // requested pool config (container env is fixed at `docker run` time, so a\n // plain `docker start` can't change it). Without flags, plain safe-start.\n const cfg = resolveWorkerConfig(rest);\n if (cfg.explicit) {\n console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer is running.');\n return;\n }\n console.log('Synkro: starting server\\n');\n const result = await dockerSafeStart();\n if (!result.ok) {\n console.error(`\\nStart failed: ${result.error}`);\n process.exit(1);\n }\n console.log('\\nServer is running.');\n}\n\nexport async function updateCommand(): Promise<void> {\n assertDockerAvailable();\n // Read the installed container's pool config so the rebuilt container keeps\n // the same worker split instead of resetting to defaults.\n const cfg = readContainerConfig();\n if (!cfg) {\n console.error('No synkro-server container found. Run `synkro install` first.');\n process.exit(1);\n }\n const claudeWorkers = cfg.claudeWorkers ?? 8;\n const cursorWorkers = cfg.cursorWorkers ?? 0;\n console.log('Synkro: updating to the latest container image');\n console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)\\n`);\n // dockerUpdate: graceful stop (snapshot + checkpoint) → remove → pull latest → recreate.\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(90_000);\n if (!ready) {\n console.error('\\n⚠ container did not pass its health check within 90s — check: docker logs synkro-server');\n process.exit(1);\n }\n // Regenerate the on-disk hook scripts too. `update` used to be a thin container-image\n // refresh, so a new CLI version's hook logic (e.g. the transcript-sync cap) never reached\n // ~/.synkro/hooks — only `synkro install` rewrote them. Refresh them here so `synkro update`\n // delivers hook fixes as well, matching what users expect from \"update to the latest version\".\n try {\n writeHookScripts();\n console.log(' refreshed hook scripts');\n } catch (e) {\n console.warn(` ⚠ could not refresh hook scripts: ${(e as Error).message} — run \\`synkro install\\` to update them`);\n }\n console.log('\\nSynkro updated — now running the latest version.');\n}\n\nexport async function restartCommand(rest: string[] = []): Promise<void> {\n // First reconcile the deploy location from synkro.toml. If the user flipped\n // [grader] location, this switches local<->cloud: local→cloud safely stops +\n // removes the local container and provisions the CF container; cloud→local\n // repoints config so the docker (re)start below brings the local grader back.\n const { location, changed } = await reconcileDeployLocation();\n if (location === 'cloud') {\n // Cloud grader: a restart spins a NET-NEW container. If we just switched\n // local→cloud, reconcile already provisioned + verified a fresh one — don't\n // double-boot. Otherwise (already cloud), destroy the running container and\n // boot a fresh one so the user gets a clean, single, net-new instance.\n if (!changed) await recycleCloudContainer();\n return;\n }\n\n assertDockerAvailable();\n const cfg = resolveWorkerConfig(rest);\n\n // Reconcile harness from .synkro (hooks, MCP, worker allocation)\n let claudeWorkers = cfg.claudeWorkers;\n let cursorWorkers = cfg.cursorWorkers;\n if (!cfg.explicit) {\n const reconciled = reconcileHarness();\n if (reconciled) {\n claudeWorkers = reconciled.claudeWorkers;\n cursorWorkers = reconciled.cursorWorkers;\n }\n }\n\n console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)\\n`);\n await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });\n const ready = await waitForContainerReady(60_000);\n if (!ready) { console.error('\\n⚠ container did not pass /healthz within 60s'); process.exit(1); }\n console.log('\\nServer restarted successfully.');\n\n const workersUp = await waitForWorkersReady(30_000);\n if (workersUp) {\n console.log('✓ workers ready');\n await syncSkillFiles();\n } else {\n console.warn('⚠ workers did not register within 30s — skill sync skipped');\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isAuthenticated, getAccessToken } from '../auth/stub.js';\nimport { getTelemetryState, setTelemetryState } from '../telemetry/optout.js';\n\nconst SYNKRO_DIR = join(homedir(), '.synkro');\nconst CONFIG_PATH = join(SYNKRO_DIR, 'config.env');\n\nfunction readConfigEnv(): Record<string, string> {\n if (!existsSync(CONFIG_PATH)) return {};\n const out: Record<string, string> = {};\n for (const line of readFileSync(CONFIG_PATH, 'utf-8').split('\\n')) {\n const t = line.trim();\n if (!t || t.startsWith('#')) continue;\n const eq = t.indexOf('=');\n if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n return out;\n}\n\nfunction updateConfigValue(key: string, value: string): void {\n if (!existsSync(CONFIG_PATH)) {\n console.error('No config found. Run `synkro install` first.');\n process.exit(1);\n }\n const lines = readFileSync(CONFIG_PATH, 'utf-8').split('\\n');\n const pattern = new RegExp(`^${key}=`);\n let found = false;\n const updated = lines.map((line) => {\n if (pattern.test(line.trim())) {\n found = true;\n return `${key}='${value}'`;\n }\n return line;\n });\n if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);\n writeFileSync(CONFIG_PATH, updated.join('\\n'), 'utf-8');\n}\n\n// The canonical 3-state grading location, derived from the two routing vars the\n// hooks actually read — keep this in lockstep with route()/selectGrader() in\n// hookScriptsTs.ts. 'byok' (own API key) wins; else cloud worker pool; else\n// on-device workers. Single source of truth, so the printout can't drift.\nfunction resolveInferenceMode(cfg: Record<string, string>): 'local' | 'cloud' | 'byok' {\n if ((cfg.SYNKRO_GRADING_MODE || 'local') === 'byok') return 'byok';\n if (cfg.SYNKRO_DEPLOY_LOCATION === 'cloud') return 'cloud';\n return 'local';\n}\n\n// After a grading/storage change, bring the local container in line with the\n// new modes — this is what makes switching seamless (no separate install step).\n// Cloud-only (BYOK grading + cloud storage) needs no container; every other\n// combo needs it. Only acts when crossing that boundary; wrapped so a Docker\n// hiccup never fails the config write itself.\nasync function reconcileContainer(): Promise<void> {\n const cfg = readConfigEnv();\n const cloudOnly =\n (cfg.SYNKRO_GRADING_MODE || 'local') === 'byok' &&\n (cfg.SYNKRO_STORAGE_MODE || 'local') === 'cloud';\n\n try {\n const { dockerInstall, dockerSafeStop, readContainerConfig, assertDockerAvailable, waitForContainerReady } =\n await import('../local-cc/dockerInstall.js');\n const existing = readContainerConfig();\n\n if (cloudOnly && existing) {\n console.log('Cloud-only mode — the local container is no longer needed; stopping it...');\n await dockerSafeStop();\n console.log(' ✓ container stopped (grading → BYOK, telemetry → cloud).');\n } else if (!cloudOnly && !existing) {\n assertDockerAvailable();\n console.log('This mode needs the Synkro container — provisioning...');\n const { detectGitRepo } = await import('./install.js');\n await dockerInstall({ claudeWorkers: 8, cursorWorkers: 0, connectedRepo: detectGitRepo() ?? undefined });\n const ready = await waitForContainerReady(60_000);\n console.log(ready\n ? ' ✓ container running.'\n : ' ⚠ container did not pass its health check — see `docker logs synkro-server`.');\n }\n // else: the container's presence already matches the mode — nothing to do.\n } catch (err) {\n console.warn(` ⚠ Container reconcile skipped: ${(err as Error).message}`);\n console.warn(' Run `synkro install` to (re)provision the container if needed.');\n }\n}\n\nexport async function configCommand(args: string[]): Promise<void> {\n if (args.length === 0) {\n const config = readConfigEnv();\n console.log('Synkro config:\\n');\n console.log(` inference: ${resolveInferenceMode(config)}`);\n console.log(` storage: ${config.SYNKRO_STORAGE_MODE || 'local'}`);\n console.log(` tier: ${config.SYNKRO_TIER || 'pro'}`);\n console.log(` gateway: ${config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh'}`);\n console.log(` version: ${config.SYNKRO_VERSION || '?'}`);\n console.log(`\\nTo change:`);\n console.log(` synkro config --inference <local|cloud|byok> — where grading runs`);\n console.log(` local — Claude/Cursor workers on this machine (your subscription)`);\n console.log(` cloud — Claude/Cursor workers in the Synkro cloud pool`);\n console.log(` byok — graded via your own provider API key`);\n console.log(` synkro config storage <local|cloud> — where telemetry is stored`);\n return;\n }\n\n // User-owned data axes — written to config.env; the hooks load config.env at\n // process start, so the change takes effect on the next graded tool call.\n if (args[0] === 'grading') {\n const value = args[1];\n if (value !== 'local' && value !== 'byok') {\n console.error('Usage: synkro config grading <local|byok>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_GRADING_MODE', value);\n console.log(`✓ Grading mode set to '${value}'.`);\n if (value === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n }\n await reconcileContainer();\n return;\n }\n // Telemetry opt-out toggles (DESIGN.md §9). State lives in\n // meta + config.env; setTelemetryState emits the config_change event itself.\n if (args[0] === 'telemetry') {\n const rest = args.slice(1);\n if (rest.length === 0) {\n const s = await getTelemetryState();\n const label = !s.enabled ? 'OFF' : (s.remoteFlushEnabled ? 'ON' : 'local-only');\n console.log(`Telemetry: ${label}`);\n return;\n }\n if (rest.length !== 1 || !['--on', '--off', '--local-only'].includes(rest[0])) {\n console.error('Usage: synkro config telemetry [--on|--off|--local-only]');\n process.exit(1);\n }\n const flag = rest[0];\n if (flag === '--on') {\n await setTelemetryState({ enabled: true, remoteFlushEnabled: true });\n console.log('Telemetry: ON');\n } else if (flag === '--off') {\n await setTelemetryState({ enabled: false, remoteFlushEnabled: false });\n console.log('Telemetry: OFF');\n } else {\n await setTelemetryState({ enabled: true, remoteFlushEnabled: false });\n console.log('Telemetry: local-only');\n }\n return;\n }\n if (args[0] === 'storage') {\n const value = args[1];\n if (value !== 'local' && value !== 'cloud') {\n console.error('Usage: synkro config storage <local|cloud>');\n process.exit(1);\n }\n updateConfigValue('SYNKRO_STORAGE_MODE', value);\n console.log(`✓ Storage mode set to '${value}'.`);\n await reconcileContainer();\n return;\n }\n\n let inferenceValue: string | undefined;\n for (const a of args) {\n if (a.startsWith('--inference=')) inferenceValue = a.slice('--inference='.length);\n else if (a === '--inference' && args.indexOf(a) + 1 < args.length) inferenceValue = args[args.indexOf(a) + 1];\n }\n\n if (!inferenceValue || !['local', 'cloud', 'byok'].includes(inferenceValue)) {\n console.error('Usage: synkro config --inference <local|cloud|byok>');\n process.exit(1);\n }\n\n if (!isAuthenticated()) {\n console.error('Not authenticated. Run `synkro login` first.');\n process.exit(1);\n }\n\n const token = getAccessToken();\n const config = readConfigEnv();\n const gatewayUrl = (config.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh').replace(/\\/$/, '');\n\n // Persist the canonical mode to the DB so the dashboard reflects it.\n try {\n const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {\n method: 'PATCH',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ inference_mode: inferenceValue }),\n });\n if (!resp.ok) {\n const errText = await resp.text().catch(() => '');\n console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to reach server: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Mirror the mode into the two routing vars the hooks read, so the next graded\n // tool call actually runs where the dashboard now says it does.\n if (inferenceValue === 'byok') {\n updateConfigValue('SYNKRO_GRADING_MODE', 'byok');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'local');\n } else if (inferenceValue === 'cloud') {\n updateConfigValue('SYNKRO_GRADING_MODE', 'local');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'cloud');\n } else {\n updateConfigValue('SYNKRO_GRADING_MODE', 'local');\n updateConfigValue('SYNKRO_DEPLOY_LOCATION', 'local');\n }\n\n console.log(`✓ Inference set to '${inferenceValue}'.`);\n if (inferenceValue === 'byok') {\n console.log(' BYOK grading uses your own provider key — register one in the');\n console.log(' dashboard under Settings → Provider Keys if you have not already.');\n } else if (inferenceValue === 'cloud') {\n console.log(' Grading runs on the Synkro cloud worker pool.');\n }\n\n // local/byok grade through the on-device container; cloud uses the hosted\n // pool, so it doesn't need (and shouldn't provision) a local container.\n if (inferenceValue !== 'cloud') await reconcileContainer();\n}\n","/**\n * `synkro telemetry <sub>` — inspect, flush, or wipe the local event log.\n *\n * Every subcommand drains the JSONL queue into pglite before reading, so\n * stats/tail/export reflect any in-flight emits. When the container is down,\n * reads return whatever's already in pglite (possibly empty) plus a \"pending\n * locally\" count surfaced by stats.\n */\n\nimport { createInterface } from 'node:readline';\n\nimport { flush } from '../telemetry/flush.js';\nimport { purge } from '../telemetry/purge.js';\nimport { exportEvents, getStats, tail, type TailOptions } from '../telemetry/stats.js';\nimport { setTelemetryState } from '../telemetry/optout.js';\nimport type { EventRow } from '../telemetry/types.js';\n\nconst HELP = `synkro telemetry — inspect or flush local telemetry events\n\nUsage:\n synkro telemetry stats show counts, queue depth, last flush\n synkro telemetry tail [opts] print recent events (most recent first)\n --limit=N max rows (default 20)\n --type=X filter by event_type\n --session=Y filter by cc_session_id\n synkro telemetry flush [--force] ship unflushed events now (bypass throttle)\n synkro telemetry export <path> dump every event as NDJSON to <path>\n synkro telemetry purge [--keep-install-id] [--yes]\n wipe local events; --keep-install-id keeps your install_id\n synkro telemetry on enable collection AND remote flush\n synkro telemetry off disable collection entirely\n synkro telemetry local-only collect locally, never flush remotely\n`;\n\nfunction parseFlag(args: string[], name: string): string | undefined {\n const prefix = `--${name}=`;\n for (const a of args) if (a.startsWith(prefix)) return a.slice(prefix.length);\n const idx = args.indexOf(`--${name}`);\n if (idx >= 0 && idx + 1 < args.length && !args[idx + 1].startsWith('--')) {\n return args[idx + 1];\n }\n return undefined;\n}\n\nfunction hasFlag(args: string[], name: string): boolean {\n return args.includes(`--${name}`);\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + `… (+${s.length - max} chars)`;\n}\n\nasync function printStats(): Promise<void> {\n const s = await getStats();\n console.log('Telemetry:');\n console.log(` install_id: ${s.install_id}`);\n console.log(` enabled: ${s.enabled ? 'yes' : 'no'}`);\n console.log(` remote_flush: ${s.remote_flush_enabled ? 'yes' : 'no'}`);\n console.log(` pglite (local): ${s.pglite_reachable ? 'reachable' : 'none (cloud mode — query central for totals)'}`);\n console.log(` local mirror events: ${s.total_events}`);\n console.log(` pending (queue): ${s.pending_local}`);\n if (s.newest) console.log(` newest event: ${s.newest}`);\n if (s.last_drained_at) console.log(` last local drain: ${s.last_drained_at}`);\n if (s.last_flush_at) console.log(` last flush attempt: ${s.last_flush_at}`);\n if (s.last_flush_ok_at) console.log(` last flush success: ${s.last_flush_ok_at}`);\n if (s.last_flush_error) console.log(` last flush error: ${s.last_flush_error}`);\n const types = Object.entries(s.by_type);\n if (types.length > 0) {\n console.log(` by type:`);\n for (const [name, count] of types) {\n console.log(` ${name.padEnd(22)} ${count}`);\n }\n }\n}\n\nasync function printTail(args: string[]): Promise<void> {\n const opts: TailOptions = {};\n const limitRaw = parseFlag(args, 'limit');\n if (limitRaw) {\n const n = Number.parseInt(limitRaw, 10);\n if (Number.isFinite(n) && n > 0) opts.limit = n;\n }\n const typeRaw = parseFlag(args, 'type');\n if (typeRaw) opts.eventType = typeRaw;\n const sessionRaw = parseFlag(args, 'session');\n if (sessionRaw) opts.sessionId = sessionRaw;\n\n const rows = await tail(opts);\n if (rows.length === 0) {\n console.log('(no events — run `synkro start` if the container is down so JSONL pending events can drain)');\n return;\n }\n for (const row of rows as EventRow[]) {\n const session = row.cc_session_id ? ` session=${String(row.cc_session_id).slice(0, 8)}` : '';\n const ts = typeof row.occurred_at === 'string' ? row.occurred_at : new Date(row.occurred_at as unknown as string | number | Date).toISOString();\n console.log(` ${ts} ${row.event_type.padEnd(20)} ${row.emitter}${session}`);\n const contextStr = typeof row.context === 'string' ? row.context : JSON.stringify(row.context);\n console.log(` context: ${truncate(contextStr, 200)}`);\n }\n}\n\nasync function runFlush(args: string[]): Promise<void> {\n const force = hasFlag(args, 'force');\n const r = await flush({ force });\n const parts = [`remote sent: ${r.sent}`, `queued (kept): ${r.failed}`, `local mirror: ${r.mirrored}`];\n if (r.skipped_reason) parts.push(`remote skipped (${r.skipped_reason})`);\n console.log(parts.join(', '));\n}\n\nasync function runExport(args: string[]): Promise<void> {\n const path = args[0];\n if (!path) {\n console.error('usage: synkro telemetry export <path>');\n process.exit(1);\n }\n await exportEvents(path);\n console.log(`wrote ${path}`);\n}\n\nfunction confirmYesNo(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return Promise.resolve(false);\n return new Promise<boolean>((resolve) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(`${question} (y/N): `, (answer) => {\n rl.close();\n const t = answer.trim().toLowerCase();\n resolve(t === 'y' || t === 'yes');\n });\n });\n}\n\nasync function runPurge(args: string[]): Promise<void> {\n const keepInstallId = hasFlag(args, 'keep-install-id');\n const skipPrompt = hasFlag(args, 'yes') || hasFlag(args, 'y');\n\n if (!skipPrompt) {\n const msg = keepInstallId\n ? 'Wipe all local telemetry events (keeping your install_id)?'\n : 'Wipe all local telemetry events AND reset install_id?';\n const ok = await confirmYesNo(msg);\n if (!ok) {\n console.log('aborted');\n return;\n }\n }\n\n const result = await purge({ keepInstallId });\n if (!result.pglite_reachable) {\n console.log(`removed ${result.removed_pending} pending event(s) from local queue (container down — pglite events untouched)`);\n return;\n }\n console.log(`removed ${result.removed_events} pglite event(s) + ${result.removed_pending} pending; meta ${result.removed_meta ? 'reset' : 'unchanged'}`);\n}\n\nexport async function telemetryCommand(args: string[]): Promise<void> {\n const sub = args[0];\n const rest = args.slice(1);\n\n switch (sub) {\n case 'stats':\n await printStats();\n return;\n case 'tail':\n await printTail(rest);\n return;\n case 'flush':\n await runFlush(rest);\n return;\n case 'export':\n await runExport(rest);\n return;\n case 'purge':\n await runPurge(rest);\n return;\n case 'on':\n await setTelemetryState({ enabled: true, remoteFlushEnabled: true });\n console.log('telemetry: on (collection + remote flush enabled)');\n return;\n case 'off':\n await setTelemetryState({ enabled: false, remoteFlushEnabled: false });\n console.log('telemetry: off (no collection, no remote flush)');\n return;\n case 'local-only':\n await setTelemetryState({ enabled: true, remoteFlushEnabled: false });\n console.log('telemetry: local-only (collection on, remote flush off)');\n return;\n case undefined:\n case '':\n case 'help':\n case '--help':\n case '-h':\n console.log(HELP);\n return;\n default:\n console.error(`Unknown subcommand: ${sub}`);\n console.log(HELP);\n process.exit(1);\n }\n}\n","#!/usr/bin/env node\n/**\n * Synkro CLI bootstrap.\n *\n * Loads .env, then dispatches to the right subcommand.\n */\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst envCandidates = [\n resolve(process.env.HOME ?? '', '.synkro', 'config.env'),\n];\nfor (const envPath of envCandidates) {\n if (!existsSync(envPath)) continue;\n const envContent = readFileSync(envPath, 'utf-8');\n for (const line of envContent.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex <= 0) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n const value = trimmed.slice(eqIndex + 1).trim().replace(/^['\"]|['\"]$/g, '');\n if (!process.env[key] && !value.startsWith('op://')) process.env[key] = value;\n }\n}\n\nconst args = process.argv.slice(2);\nconst cmd = args[0] || '';\nconst subArgs = args.slice(1);\n\n// The detached flush child re-execs this bootstrap (`telemetry flush --detached`)\n// only to drain the queue. It must NOT emit its own `cli_invocation`; flushDetached\n// already sets this env, and we honor it here too.\nconst isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === '1';\n\n// Subcommands too hot or too trivial to warrant spawning a detached flush child:\n// `grade` fires on a per-hook hot loop; version/help do no telemetry-worthy work.\n// Any events they do emit ride along on the next real invocation's flush.\nconst FLUSH_SKIP = new Set(['grade', 'version', '--version', '-v', 'help', '--help', '-h', '']);\n\nfunction printVersion() {\n console.log(__SYNKRO_CLI_VERSION__);\n}\n\nfunction printHelp() {\n console.log(`Synkro CLI — runtime safety for AI coding agents\n\nUsage:\n synkro <command> [options]\n\nCommands:\n install [--force] Install or update Synkro\n uninstall [--purge] Remove Synkro (keeps scan data; --purge wipes that too)\n stop Gracefully stop the server (snapshot + checkpoint)\n start [opts] Start the server (with pgdata integrity check)\n restart [opts] Safe restart (stop → start, data preserved)\n update Pull the latest container image and safely restart\n config Show or change grading + storage modes\n claude-desktop Monitor Claude Desktop conversations (local, macOS)\n telemetry <sub> Inspect or flush local telemetry events\n version Show version\n\n config:\n synkro config show current settings\n synkro config grading <local|byok> where grading runs\n synkro config storage <local|cloud> where telemetry is stored\n\n start/restart opts (recreate the worker pool):\n --workers N total grader workers (default 8, even-split)\n --providers a,b grading agents: claude, cursor (or both)\n e.g. synkro restart --workers 16 --providers claude,cursor\n\nQuick start:\n $ synkro install # one-time setup\n $ claude # use Claude Code normally; Synkro judges in real time\n`);\n}\n\nasync function main() {\n // Telemetry import is lazy but happens early — almost every command benefits\n // from `cli_invocation` and the post-dispatch flush. index.ts handles its own\n // errors, so a wedged telemetry path never propagates into dispatch.\n /** @type {null | typeof import('./telemetry/index.js').emit} */\n let emit = null;\n try {\n ({ emit } = await import('./telemetry/index.js'));\n } catch { /* telemetry disabled or broken — proceed without it */ }\n\n // Every invocation gets a cli_invocation event, except `grade` (fires on a hot\n // loop and would drown the table) and the detached flush child.\n if (emit && !isDetachedChild && cmd !== 'grade') {\n try {\n emit('cli_invocation', {\n argv: process.argv.slice(2),\n subcommand: cmd || 'help',\n cwd: process.cwd(),\n });\n } catch { /* never block dispatch */ }\n }\n\n switch (cmd) {\n case 'install': {\n const { installCommand, parseArgs } = await import('./commands/install.js');\n await installCommand(parseArgs(subArgs));\n break;\n }\n case 'uninstall':\n case 'disconnect': {\n const { disconnectCommand } = await import('./commands/disconnect.js');\n // MUST await — main()'s .then(shutdown) calls process.exit() as soon as main\n // returns, so a fire-and-forget uninstall gets killed mid-teardown (the process\n // died during the first await — the pre-shutdown snapshot fetch — leaving the\n // container running and hooks in place).\n await disconnectCommand(subArgs);\n break;\n }\n case 'login': {\n const { authenticate, isAuthenticated } = await import('./auth/stub.js');\n if (isAuthenticated()) {\n console.log('Already authenticated.');\n } else {\n console.log('Opening browser for Synkro auth...');\n const result = await authenticate((status) => {\n if (status.phase === 'success') console.log(' ✓ Authenticated');\n else if (status.phase === 'error') console.error(' ✗ ' + status.message);\n });\n if (!result) { console.error('Authentication failed.'); process.exit(1); }\n }\n break;\n }\n case 'grade': {\n const { gradeCommand } = await import('./commands/grade.js');\n await gradeCommand(subArgs);\n break;\n }\n case 'local-cc': {\n const { localCcCommand } = await import('./commands/localCc.js');\n await localCcCommand(subArgs);\n break;\n }\n case 'import': {\n const { importCommand } = await import('./commands/import.js');\n await importCommand();\n break;\n }\n case 'sync': {\n const { syncCommand } = await import('./commands/sync.js');\n await syncCommand(subArgs);\n break;\n }\n case 'version':\n case '--version':\n case '-v': {\n printVersion();\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n case '': {\n printHelp();\n break;\n }\n case 'stop': {\n const { stopCommand } = await import('./commands/lifecycle.js');\n await stopCommand();\n break;\n }\n case 'start': {\n const { startCommand } = await import('./commands/lifecycle.js');\n await startCommand(args.slice(1));\n break;\n }\n case 'restart': {\n const { restartCommand } = await import('./commands/lifecycle.js');\n await restartCommand(args.slice(1));\n break;\n }\n case 'update': {\n const { updateCommand } = await import('./commands/lifecycle.js');\n await updateCommand();\n break;\n }\n case 'config': {\n const { configCommand } = await import('./commands/config.js');\n await configCommand(args.slice(1));\n break;\n }\n case 'claude-desktop': {\n // Start a Claude Desktop observability session against the already-running\n // container — no reinstall, no image pull. (The same tap is also offered\n // during `synkro install`.) Runs until Ctrl+C.\n const { runClaudeDesktopTap } = await import('./installer/claudeDesktopTap.js');\n // History backfill is opt-in: `synkro claude-desktop --backfill` imports past\n // conversations once. Without it, only live turns are captured.\n await runClaudeDesktopTap({ backfill: subArgs.includes('--backfill') });\n break;\n }\n case 'telemetry': {\n const { telemetryCommand } = await import('./commands/telemetry.js');\n await telemetryCommand(subArgs);\n break;\n }\n default: {\n console.error(`Unknown command: ${cmd}`);\n printHelp();\n process.exit(1);\n }\n }\n}\n\n// Post-dispatch flush — detached so it can outlive this process and so any\n// network/sqlite hiccup never delays CLI exit. Wrapped in its own try/catch\n// because a flush failure must NEVER prevent the CLI from exiting.\nasync function postDispatchFlush() {\n if (FLUSH_SKIP.has(cmd)) return;\n try {\n const { flushDetached } = await import('./telemetry/flush.js');\n flushDetached();\n } catch { /* flush is best-effort; ignore */ }\n}\n\n// Drain the cached pglite connection so the event loop can empty and Node exits\n// on its own — an open postgres.js socket otherwise keeps the CLI alive until\n// idle_timeout (~30s). The detached flush child is a separate unref'd process in\n// its own group, so nothing here touches it. If some stray handle still holds\n// the loop open, force the exit as a fallback — but only once stdout/stderr have\n// drained, so piped output is never truncated.\n/** @param {number} code */\nasync function shutdown(code) {\n try {\n const { closeDb } = await import('./telemetry/db.js');\n await closeDb();\n } catch { /* cleanup must never block exit */ }\n process.exitCode = code;\n const force = setTimeout(() => {\n let pending = 0;\n const done = () => { if (--pending === 0) process.exit(code); };\n for (const stream of [process.stdout, process.stderr]) {\n if (stream.writableLength > 0) { pending++; stream.once('drain', done); }\n }\n if (pending === 0) process.exit(code);\n }, 200);\n force.unref();\n}\n\nmain()\n .then(() => postDispatchFlush())\n .then(() => shutdown(0))\n .catch(async (err) => {\n // Best-effort: emit an `error` event before exiting. Wrapped in its own\n // try/catch so a wedged telemetry path can't shadow the original error.\n try {\n const { emit } = await import('./telemetry/index.js');\n emit('error', {\n category: 'unknown',\n code: (err && err.code) || 'UNKNOWN',\n message: String(err && err.message ? err.message : err),\n stack: (err && err.stack) || '',\n emitter_context: 'bootstrap:' + (cmd || 'unknown'),\n });\n } catch { /* swallow */ }\n await postDispatchFlush();\n console.error(err);\n await shutdown(1);\n });\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["existsSync","readFileSync","homedir","join","cached","randomUUID","existsSync","mkdirSync","homedir","join","cached","ensureDir","SYNKRO_DIR","cached","readFileSync","writeFileSync","appendFileSync","mkdirSync","renameSync","existsSync","homedir","join","ensureDir","SYNKRO_DIR","existsSync","readFileSync","writeFileSync","homedir","join","SYNKRO_DIR","existsSync","readFileSync","homedir","join","CONFIG_PATH","SYNKRO_DIR","CREDS_PATH","jwt","existsSync","readFileSync","writeFileSync","existsSync","readFileSync","unlinkSync","randomUUID","randomUUID","writeFileSync","existsSync","homedir","join","execFileSync","cmd","existsSync","homedir","join","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","readFileSync","writeFileSync","renameSync","mkdirSync","dirname","homedir","isSynkroEntry","SYNKRO_MARKER","removeSynkroEntries","existsSync","readFileSync","writeFileSync","renameSync","mkdirSync","homedir","dirname","join","SYNKRO_MARKER","url","jwt","resolveBunBin","existsSync","resolve","writeFileSync","readFileSync","existsSync","mkdirSync","unlinkSync","homedir","platform","join","dirname","args","resolve","emit","existsSync","mkdirSync","writeFileSync","execSync","join","execSync","execFileSync","createServer","resolve","SYNKRO_WEB_AUTH_URL","openBrowser","execFile","RAW_WEB_AUTH_URL","spawn","writeFileSync","mkdirSync","readFileSync","existsSync","join","homedir","resolve","SYNKRO_DIR","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","homedir","platform","join","spawnSync","SYNKRO_DIR","existsSync","mkdirSync","readFileSync","readdirSync","homedir","join","execSync","spawnSync","which","args","readFileSync","unlinkSync","homedir","platform","join","SYNKRO_DIR","args","resolve","createInterface","execSync","existsSync","readFileSync","homedir","platform","join","execFile","CONFIG_PATH","resolve","openBrowser","args","jwt","SYNKRO_DIR","detectGitRepo","existsSync","mkdirSync","writeFileSync","chmodSync","readFileSync","readdirSync","unlinkSync","statSync","homedir","join","execSync","createInterface","createHash","ask","resolve","cursorApiKeyConfigured","writeCursorApiKey","validateCursorApiKey","a","which","SYNKRO_DIR","shellQuoteSingle","CONFIG_PATH","jwt","l","isLocalhost","assertDockerAvailable","setupGithubCommand","parseTomlValue","parseSynkroToml","cmd","existsSync","mkdirSync","writeFileSync","readFileSync","chmodSync","copyFileSync","renameSync","unlinkSync","openSync","closeSync","join","homedir","spawnSync","init_install","existsSync","readdirSync","homedir","join","spawnSync","createInterface","resolve","args","purge","SYNKRO_DIR","init_install","appendFileSync","existsSync","mkdirSync","openSync","readFileSync","closeSync","statSync","dirname","join","homedir","args","resolve","init_client","resolve","args","init_client","execFileSync","spawnSync","spawn","homedir","join","connect","args","resolve","SESSION_DIR","SESSION_DIR_2","SESSION_DIR_3","SESSION_DIR_4","existsSync","readFileSync","homedir","join","CONFIG_PATH","spawnSync","homedir","join","existsSync","readFileSync","writeFileSync","CONFIG_PATH","jwt","ready","resolve","args","init_install","init_client","existsSync","readFileSync","readdirSync","homedir","join","execSync","createInterface","readConfigEnv","CONFIG_PATH","ask","resolve","ok","existsSync","readFileSync","writeFileSync","join","existsSync","mkdirSync","readdirSync","rmSync","writeFileSync","homedir","join","detectGitRepo","readFileSync","writeFileSync","existsSync","join","homedir","readConfigEnv","CONFIG_PATH","dockerInstall","dockerSafeStop","readContainerConfig","assertDockerAvailable","waitForContainerReady","detectGitRepo","args","config","SYNKRO_DIR","telemetry_exports","createInterface","args","truncate","resolve","init_telemetry","readFileSync","existsSync","resolve","printHelp","emit","installCommand","parseArgs","disconnectCommand","authenticate","isAuthenticated","gradeCommand","localCcCommand","importCommand","syncCommand","stopCommand","startCommand","restartCommand","updateCommand","configCommand","runClaudeDesktopTap","telemetryCommand","flushDetached","closeDb"]}
|