@synkro-sh/cli 1.7.92 → 1.7.94

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 CHANGED
@@ -147,7 +147,7 @@ function getIdentity() {
147
147
  if (cached2) return cached2;
148
148
  let cliVersion = "0.0.0";
149
149
  try {
150
- cliVersion = "1.7.92";
150
+ cliVersion = "1.7.94";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -1735,18 +1735,6 @@ function installCCHooks(settingsPath, config) {
1735
1735
  ],
1736
1736
  [SYNKRO_MARKER]: true
1737
1737
  });
1738
- if (config.promptRouteScriptPath) {
1739
- settings.hooks.UserPromptSubmit.push({
1740
- hooks: [
1741
- {
1742
- type: "command",
1743
- command: config.promptRouteScriptPath,
1744
- timeout: 5
1745
- }
1746
- ],
1747
- [SYNKRO_MARKER]: true
1748
- });
1749
- }
1750
1738
  settings.hooks.Stop = settings.hooks.Stop ?? [];
1751
1739
  removeSynkroEntries(settings.hooks, "Stop");
1752
1740
  settings.hooks.Stop.push({
@@ -2112,7 +2100,6 @@ function installCodexHooks(hooksJsonPath, config) {
2112
2100
  if (config.taskActivateIntentScriptPath) push(h, "PermissionRequest", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
2113
2101
  push(h, "PostToolUse", [cmd(config.bashFollowupScriptPath, 10)], M_BASH);
2114
2102
  push(h, "UserPromptSubmit", [cmd(config.userPromptSubmitScriptPath, 5)]);
2115
- if (config.promptRouteScriptPath) push(h, "UserPromptSubmit", [cmd(config.promptRouteScriptPath, 5)]);
2116
2103
  push(h, "SessionStart", [cmd(config.sessionStartScriptPath, 5)]);
2117
2104
  push(h, "SubagentStart", [cmd(config.subagentStartScriptPath, 5)]);
2118
2105
  if (!config.skipTranscriptSync) push(h, "SubagentStop", [cmd(config.subagentStopScriptPath, 5)]);
@@ -2141,13 +2128,14 @@ function uninstallCodexHooks(hooksJsonPath) {
2141
2128
  writeHooksFileAtomic2(hooksJsonPath, file);
2142
2129
  return true;
2143
2130
  }
2144
- var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2131
+ var SYNKRO_MARKER3, CODEX_BASH_MATCHER, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2145
2132
  var init_codexHookConfig = __esm({
2146
2133
  "cli/installer/codexHookConfig.ts"() {
2147
2134
  "use strict";
2148
2135
  init_platform();
2149
2136
  SYNKRO_MARKER3 = "__synkro_managed__";
2150
- M_BASH = "Bash";
2137
+ CODEX_BASH_MATCHER = "^(?:Bash|exec_command|functions[._]exec_command)$";
2138
+ M_BASH = CODEX_BASH_MATCHER;
2151
2139
  CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
2152
2140
  M_EDIT = CODEX_EDIT_MATCHER;
2153
2141
  M_AGENT = "Agent";
@@ -2157,6 +2145,175 @@ var init_codexHookConfig = __esm({
2157
2145
  }
2158
2146
  });
2159
2147
 
2148
+ // cli/installer/codexHookTrust.ts
2149
+ import { spawn as spawn2 } from "child_process";
2150
+ function parseCodexHookTrustRecords(stdout) {
2151
+ for (const line of stdout.split("\n")) {
2152
+ let message;
2153
+ try {
2154
+ message = JSON.parse(line);
2155
+ } catch {
2156
+ continue;
2157
+ }
2158
+ const data = message?.result?.data;
2159
+ if (!Array.isArray(data)) continue;
2160
+ const hooks = data.flatMap((entry) => Array.isArray(entry?.hooks) ? entry.hooks : []).filter((hook) => typeof hook.command === "string" && isSynkroHookCommand(hook.command));
2161
+ if (!hooks.length) return null;
2162
+ return hooks;
2163
+ }
2164
+ return null;
2165
+ }
2166
+ function summarizeCodexHookTrust(hooks) {
2167
+ return {
2168
+ total: hooks.length,
2169
+ trusted: hooks.filter((hook) => hook.trustStatus === "trusted" || hook.trustStatus === "managed").length,
2170
+ needsReview: hooks.filter((hook) => hook.trustStatus === "modified" || hook.trustStatus === "untrusted").length,
2171
+ disabled: hooks.filter((hook) => hook.enabled === false).length
2172
+ };
2173
+ }
2174
+ function parseCodexHookTrustOutput(stdout) {
2175
+ const hooks = parseCodexHookTrustRecords(stdout);
2176
+ return hooks ? summarizeCodexHookTrust(hooks) : null;
2177
+ }
2178
+ function buildCodexHookTrustEdits(hooks) {
2179
+ const edits = /* @__PURE__ */ new Map();
2180
+ for (const hook of hooks) {
2181
+ if (typeof hook.command !== "string" || !isSynkroHookCommand(hook.command) || typeof hook.key !== "string" || !hook.key || typeof hook.currentHash !== "string" || !/^sha256:[0-9a-f]{64}$/i.test(hook.currentHash)) continue;
2182
+ const edit = {
2183
+ keyPath: `hooks.state.${JSON.stringify(hook.key)}.trusted_hash`,
2184
+ value: hook.currentHash,
2185
+ mergeStrategy: "upsert"
2186
+ };
2187
+ edits.set(edit.keyPath, edit);
2188
+ }
2189
+ return [...edits.values()];
2190
+ }
2191
+ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
2192
+ return new Promise((resolve6) => {
2193
+ let settled = false;
2194
+ let stdout = "";
2195
+ let pending = "";
2196
+ let hooks = null;
2197
+ let child;
2198
+ const finish = (summary) => {
2199
+ if (settled) return;
2200
+ settled = true;
2201
+ clearTimeout(timer);
2202
+ try {
2203
+ child.stdin.end();
2204
+ } catch {
2205
+ }
2206
+ try {
2207
+ child.kill();
2208
+ } catch {
2209
+ }
2210
+ resolve6(summary);
2211
+ };
2212
+ const timer = setTimeout(() => finish(null), 1e4);
2213
+ try {
2214
+ child = spawn2(codexBinary, ["app-server", "--stdio"], {
2215
+ cwd,
2216
+ stdio: ["pipe", "pipe", "ignore"],
2217
+ windowsHide: true
2218
+ });
2219
+ child.once("error", () => finish(null));
2220
+ child.once("exit", () => finish(parseCodexHookTrustOutput(stdout)));
2221
+ child.stdout.setEncoding("utf8");
2222
+ child.stdout.on("data", (chunk) => {
2223
+ stdout += chunk;
2224
+ pending += chunk;
2225
+ const lines = pending.split("\n");
2226
+ pending = lines.pop() || "";
2227
+ for (const line of lines) {
2228
+ let message;
2229
+ try {
2230
+ message = JSON.parse(line);
2231
+ } catch {
2232
+ continue;
2233
+ }
2234
+ if (message?.id === 1 && message?.result) {
2235
+ child.stdin.write(JSON.stringify({
2236
+ id: 2,
2237
+ method: "hooks/list",
2238
+ params: { cwds: [cwd] }
2239
+ }) + "\n");
2240
+ } else if (message?.id === 2) {
2241
+ hooks = parseCodexHookTrustRecords(line);
2242
+ if (!hooks || !autoTrust) {
2243
+ finish(hooks ? summarizeCodexHookTrust(hooks) : null);
2244
+ continue;
2245
+ }
2246
+ const edits = buildCodexHookTrustEdits(hooks);
2247
+ if (!edits.length) {
2248
+ finish(summarizeCodexHookTrust(hooks));
2249
+ continue;
2250
+ }
2251
+ child.stdin.write(JSON.stringify({
2252
+ id: 3,
2253
+ method: "config/batchWrite",
2254
+ params: { edits, reloadUserConfig: true }
2255
+ }) + "\n");
2256
+ } else if (message?.id === 3) {
2257
+ if (message?.result?.status !== "ok" || !hooks) {
2258
+ finish(hooks ? summarizeCodexHookTrust(hooks) : null);
2259
+ continue;
2260
+ }
2261
+ finish({
2262
+ total: hooks.length,
2263
+ trusted: hooks.length,
2264
+ needsReview: 0,
2265
+ disabled: hooks.filter((hook) => hook.enabled === false).length
2266
+ });
2267
+ }
2268
+ }
2269
+ });
2270
+ child.stdin.write(JSON.stringify({
2271
+ id: 1,
2272
+ method: "initialize",
2273
+ params: {
2274
+ clientInfo: { name: "synkro-cli", version: "1" },
2275
+ capabilities: { experimentalApi: true }
2276
+ }
2277
+ }) + "\n");
2278
+ } catch {
2279
+ finish(null);
2280
+ }
2281
+ });
2282
+ }
2283
+ function trustCodexHooks(codexBinary = "codex", cwd = process.cwd()) {
2284
+ return queryCodexHookTrust(codexBinary, cwd, true);
2285
+ }
2286
+ function codexHookTrustLines(summary) {
2287
+ if (!summary) {
2288
+ return [
2289
+ " \u26A0 Codex hook trust could not be verified.",
2290
+ " Restart Codex, run /hooks, and review the Synkro hooks before relying on enforcement."
2291
+ ];
2292
+ }
2293
+ if (summary.needsReview === 0 && summary.disabled === 0 && summary.trusted === summary.total) {
2294
+ return [` \u2713 Codex hook trust confirmed (${summary.trusted}/${summary.total} active)`];
2295
+ }
2296
+ const issues = [
2297
+ summary.needsReview > 0 ? `${summary.needsReview} need review` : "",
2298
+ summary.disabled > 0 ? `${summary.disabled} disabled` : ""
2299
+ ].filter(Boolean).join(", ");
2300
+ return [
2301
+ ` \u26A0 Codex security enforcement is not active (${issues}).`,
2302
+ " Restart Codex, run /hooks, and trust only the Synkro hooks before relying on enforcement."
2303
+ ];
2304
+ }
2305
+ async function reportCodexHookTrust(codexBinary, cwd) {
2306
+ const summary = await trustCodexHooks(codexBinary || "codex", cwd);
2307
+ for (const line of codexHookTrustLines(summary)) console.log(line);
2308
+ return summary;
2309
+ }
2310
+ var init_codexHookTrust = __esm({
2311
+ "cli/installer/codexHookTrust.ts"() {
2312
+ "use strict";
2313
+ init_platform();
2314
+ }
2315
+ });
2316
+
2160
2317
  // cli/installer/mcpConfig.ts
2161
2318
  import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
2162
2319
  import { homedir as homedir12 } from "os";
@@ -2320,9 +2477,54 @@ function removeCodexManagedBlock(content, path) {
2320
2477
  if (inside) throw new Error(`Incomplete Synkro MCP marker block in ${path}`);
2321
2478
  return { content: out.join("\n").replace(/\n{3,}$/g, "\n\n"), removed };
2322
2479
  }
2323
- function hasUnmanagedCodexEntry(content) {
2324
- const section = /^\s*\[\s*mcp_servers\s*\.\s*(?:"synkro-guardrails"|'synkro-guardrails'|synkro-guardrails)\s*\]\s*(?:#.*)?$/m;
2325
- return section.test(content);
2480
+ function findCodexMcpSection(content) {
2481
+ const section = /^\s*\[\s*mcp_servers\s*\.\s*(?:"synkro-guardrails"|'synkro-guardrails'|synkro-guardrails)\s*\]\s*(?:#.*)?$/gm;
2482
+ const match = section.exec(content);
2483
+ if (!match) return null;
2484
+ const nextSection = /^\s*\[{1,2}[^\r\n]+?\]{1,2}\s*(?:#.*)?$/gm;
2485
+ nextSection.lastIndex = match.index + match[0].length;
2486
+ const next = nextSection.exec(content);
2487
+ return {
2488
+ start: match.index,
2489
+ end: next?.index ?? content.length,
2490
+ body: content.slice(match.index, next?.index ?? content.length)
2491
+ };
2492
+ }
2493
+ function readTomlString(block, key) {
2494
+ const match = block.match(new RegExp(`^\\s*${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
2495
+ if (!match) return null;
2496
+ try {
2497
+ return JSON.parse(match[1]);
2498
+ } catch {
2499
+ return null;
2500
+ }
2501
+ }
2502
+ function isRecognizedCodexEntry(block, expectedUrl) {
2503
+ const command = readTomlString(block, "command");
2504
+ const argsLine = block.match(/^\s*args\s*=\s*(\[[^\r\n]*\])\s*$/m);
2505
+ if (command && argsLine) {
2506
+ try {
2507
+ const args2 = JSON.parse(argsLine[1]);
2508
+ if (/(?:^|[\\/])bun(?:\.exe)?$/i.test(command) && Array.isArray(args2) && args2.length === 2 && args2[0] === "run" && args2[1] === MCP_STDIO_PROXY_PATH) return true;
2509
+ } catch {
2510
+ }
2511
+ }
2512
+ const url = readTomlString(block, "url");
2513
+ return Boolean(expectedUrl && url === expectedUrl);
2514
+ }
2515
+ function stripDetachedCodexMarkers(content) {
2516
+ return content.split("\n").filter((line) => line.trim() !== CODEX_MCP_BEGIN && line.trim() !== CODEX_MCP_END).join("\n");
2517
+ }
2518
+ function removeRecognizedCodexEntry(content, path, expectedUrl) {
2519
+ const range = findCodexMcpSection(content);
2520
+ if (!range) return { content, removed: false };
2521
+ if (!isRecognizedCodexEntry(range.body, expectedUrl)) {
2522
+ throw new Error(
2523
+ `${path} already defines ${CODEX_MCP_SECTION}; remove or rename that unmanaged entry before installing Synkro`
2524
+ );
2525
+ }
2526
+ const next = `${content.slice(0, range.start)}${content.slice(range.end)}`;
2527
+ return { content: next.replace(/\n{3,}$/g, "\n\n"), removed: true };
2326
2528
  }
2327
2529
  function tomlString(value) {
2328
2530
  return JSON.stringify(value);
@@ -2330,16 +2532,17 @@ function tomlString(value) {
2330
2532
  function installCodexMcpConfig(opts) {
2331
2533
  const path = codexConfigPath(opts.configPath);
2332
2534
  const current = readCodexToml(path);
2333
- const withoutManaged = removeCodexManagedBlock(current, path).content;
2334
- if (hasUnmanagedCodexEntry(withoutManaged)) {
2335
- throw new Error(
2336
- `${path} already defines ${CODEX_MCP_SECTION}; remove or rename that unmanaged entry before installing Synkro`
2337
- );
2338
- }
2535
+ const targetUrl = opts.local ? `stdio://${MCP_STDIO_PROXY_PATH}` : `${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/mcp/guardrails`;
2536
+ const withoutManaged = stripDetachedCodexMarkers(removeCodexManagedBlock(current, path).content);
2537
+ const withoutExisting = removeRecognizedCodexEntry(
2538
+ withoutManaged,
2539
+ path,
2540
+ opts.local ? void 0 : targetUrl
2541
+ ).content;
2339
2542
  let url;
2340
2543
  let body;
2341
2544
  if (opts.local) {
2342
- url = `stdio://${MCP_STDIO_PROXY_PATH}`;
2545
+ url = targetUrl;
2343
2546
  body = [
2344
2547
  `[${CODEX_MCP_SECTION}]`,
2345
2548
  `command = ${tomlString(opts.bunBin || resolveBunBin2())}`,
@@ -2348,7 +2551,7 @@ function installCodexMcpConfig(opts) {
2348
2551
  ];
2349
2552
  } else {
2350
2553
  if (!opts.bearerToken) throw new Error("Codex cloud MCP registration requires a bearer token");
2351
- url = `${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/mcp/guardrails`;
2554
+ url = targetUrl;
2352
2555
  body = [
2353
2556
  `[${CODEX_MCP_SECTION}]`,
2354
2557
  `url = ${tomlString(url)}`,
@@ -2356,7 +2559,7 @@ function installCodexMcpConfig(opts) {
2356
2559
  "enabled = true"
2357
2560
  ];
2358
2561
  }
2359
- const prefix = withoutManaged.trimEnd();
2562
+ const prefix = withoutExisting.trimEnd();
2360
2563
  const managed = [CODEX_MCP_BEGIN, ...body, CODEX_MCP_END].join("\n");
2361
2564
  writeCodexTomlAtomic(path, `${prefix}${prefix ? "\n\n" : ""}${managed}
2362
2565
  `);
@@ -2367,8 +2570,19 @@ function uninstallCodexMcpConfig(configPath) {
2367
2570
  if (!existsSync13(path)) return false;
2368
2571
  const current = readCodexToml(path);
2369
2572
  const next = removeCodexManagedBlock(current, path);
2370
- if (!next.removed) return false;
2371
- writeCodexTomlAtomic(path, next.content.trimEnd() ? `${next.content.trimEnd()}
2573
+ let content = stripDetachedCodexMarkers(next.content);
2574
+ let removed = next.removed;
2575
+ if (!removed) {
2576
+ try {
2577
+ const recognized = removeRecognizedCodexEntry(content, path);
2578
+ content = recognized.content;
2579
+ removed = recognized.removed;
2580
+ } catch {
2581
+ return false;
2582
+ }
2583
+ }
2584
+ if (!removed) return false;
2585
+ writeCodexTomlAtomic(path, content.trimEnd() ? `${content.trimEnd()}
2372
2586
  ` : "");
2373
2587
  return true;
2374
2588
  }
@@ -2982,6 +3196,26 @@ export function normalizeCodexApplyPatch(payload: any): void {
2982
3196
  } catch { /* leave payload untouched on any parse error */ }
2983
3197
  }
2984
3198
 
3199
+ /**
3200
+ * Normalize Codex app/local-function command aliases into the Bash hook shape
3201
+ * consumed by scanRouter. Codex CLI already emits Bash + command; bridges may
3202
+ * retain exec_command (optionally namespaced) + cmd.
3203
+ */
3204
+ export function normalizeCodexExecCommand(payload: any): void {
3205
+ const toolName = String(payload?.tool_name || '');
3206
+ if (!/^(?:exec_command|functions[._]exec_command)$/i.test(toolName)) return;
3207
+ const toolInput = payload?.tool_input && typeof payload.tool_input === 'object'
3208
+ ? payload.tool_input
3209
+ : {};
3210
+ payload.tool_name = 'Bash';
3211
+ payload.tool_input = {
3212
+ ...toolInput,
3213
+ command: typeof toolInput.command === 'string'
3214
+ ? toolInput.command
3215
+ : String(toolInput.cmd || ''),
3216
+ };
3217
+ }
3218
+
2985
3219
  type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
2986
3220
 
2987
3221
  function completedCodexToolRecord(entry: any): any {
@@ -3295,9 +3529,12 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
3295
3529
  const input = await readStdin();
3296
3530
  if (!input.trim()) { out(failOpen(harness)); return; }
3297
3531
  const payload = JSON.parse(input);
3298
- // Codex: rewrite apply_patch tool_input into the CC edit shape before any
3299
- // surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
3300
- if (harness === 'codex') normalizeCodexApplyPatch(payload);
3532
+ // Codex: rewrite bridged tool aliases into the shared CC shapes before any
3533
+ // surface reads them. No-op for CC/Cursor and already-canonical tools.
3534
+ if (harness === 'codex') {
3535
+ normalizeCodexApplyPatch(payload);
3536
+ normalizeCodexExecCommand(payload);
3537
+ }
3301
3538
  if (harness === 'codex' && opts.subagent) {
3302
3539
  const parentSessionId = String(payload?.session_id || '');
3303
3540
  const childSessionId = String(payload?.agent_id || '');
@@ -3763,165 +4000,8 @@ function emitStubTelemetry(
3763
4000
  STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
3764
4001
  STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
3765
4002
  STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
3766
- import { readFileSync, unlinkSync, mkdirSync, writeFileSync, readdirSync, existsSync } from 'node:fs';
3767
- import { homedir } from 'node:os';
3768
- import { join } from 'node:path';
3769
- import { spawnSync, spawn } from 'node:child_process';
3770
-
3771
- const HOME = homedir();
3772
- const MODEL_RE = /^[A-Za-z0-9._:-]{1,64}$/;
3773
-
3774
- function cliBin(): string {
3775
- try {
3776
- const txt = readFileSync(join(HOME, '.synkro', 'config.env'), 'utf-8');
3777
- for (const line of txt.split('\\n')) {
3778
- const t = line.trim();
3779
- if (t.startsWith('SYNKRO_CLI_BIN=')) {
3780
- let v = t.slice('SYNKRO_CLI_BIN='.length).trim();
3781
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
3782
- return v;
3783
- }
3784
- }
3785
- } catch {}
3786
- return process.env.SYNKRO_CLI_BIN || '';
3787
- }
3788
-
3789
- function mcpJwt(): string {
3790
- try { return readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; }
3791
- }
3792
-
3793
- // The caller's own tmux session, from the inherited $TMUX \u2014 so routing targets
3794
- // THIS session, not whichever shim last wrote the shared active-session file.
3795
- function currentTmuxSession(): string {
3796
- if (!process.env.TMUX) return '';
3797
- try {
3798
- const pane = process.env.TMUX_PANE || '';
3799
- const args = pane
3800
- ? ['display-message', '-p', '-t', pane, '#{session_name}']
3801
- : ['display-message', '-p', '#{session_name}'];
3802
- const r = spawnSync('tmux', args, { encoding: 'utf-8' });
3803
- if (r.status === 0) return (r.stdout || '').trim();
3804
- } catch {}
3805
- return '';
3806
- }
3807
-
3808
- const chunks: Buffer[] = [];
3809
- for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3810
-
3811
- // Verdict: allow ({}) unless we decide to BLOCK this prompt for routing. We must
3812
- // emit exactly one JSON line at the end (not up-front) so the block path can win.
3813
- let blockForRouting = false;
3814
- try {
3815
- const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
3816
- const sid = String(payload.session_id || payload.conversation_id || '');
3817
- if (sid) {
3818
- const safeSid = sid.replace(/[^A-Za-z0-9_-]/g, '_');
3819
- const tmuxSession = currentTmuxSession();
3820
- const prompt = String(payload.prompt || '');
3821
-
3822
- // Session registry: record THIS live session (heartbeat \u2014 ts refreshed every
3823
- // prompt) so a router or a human can enumerate live sessions via
3824
- // 'synkro sessions' and target exactly one by session_id. Wrapped + best-effort
3825
- // so it never delays or breaks the prompt.
3826
- try {
3827
- const sessDir = join(HOME, '.synkro', 'pty', 'sessions');
3828
- mkdirSync(sessDir, { recursive: true });
3829
- const rec = {
3830
- session_id: sid,
3831
- tmux_session: tmuxSession,
3832
- cwd: String(payload.cwd || ''),
3833
- last_prompt: prompt.slice(0, 200),
3834
- transcript_path: String(payload.transcript_path || ''),
3835
- ts: Date.now(),
3836
- };
3837
- writeFileSync(join(sessDir, safeSid + '.json'), JSON.stringify(rec));
3838
- // TTL-prune stale records (24h) \u2014 cheap, no per-record process spawn. Live
3839
- // sessions refresh their ts every prompt, so only ended ones age out.
3840
- const TTL = 24 * 60 * 60 * 1000;
3841
- const now = Date.now();
3842
- for (const f of readdirSync(sessDir)) {
3843
- if (!f.endsWith('.json')) continue;
3844
- try {
3845
- const r = JSON.parse(readFileSync(join(sessDir, f), 'utf-8'));
3846
- if (!r || typeof r.ts !== 'number' || now - r.ts > TTL) unlinkSync(join(sessDir, f));
3847
- } catch { try { unlinkSync(join(sessDir, f)); } catch {} }
3848
- }
3849
- } catch {}
3850
-
3851
- // \u2500\u2500 Routing: block + re-inject \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3852
- // Loop-guard: a re-injected prompt carries route-guard-<sid>; clear it and
3853
- // pass straight through (never route the re-injected prompt \u2014 that would loop).
3854
- const guardFile = join(HOME, '.synkro', 'pty', 'route-guard-' + safeSid);
3855
- let isReinjected = false;
3856
- try {
3857
- if (existsSync(guardFile)) { isReinjected = true; try { unlinkSync(guardFile); } catch {} }
3858
- } catch {}
3859
-
3860
- // Only route real task prompts. Skip empty input, slash-commands (/\u2026), bash
3861
- // lines (!\u2026), and common conversational turns \u2014 those are never something to
3862
- // pick a coding model for, and blocking them would add a needless pause. The
3863
- // haiku 'keep' verdict is the backstop for anything this quick list misses.
3864
- const t = prompt.trim();
3865
- const norm = t.toLowerCase().replace(/[.!?,;:\\s]+$/, '').trim();
3866
- const CONVERSATIONAL = new Set([
3867
- 'ok', 'okay', 'k', 'kk', 'yes', 'yep', 'yeah', 'ya', 'y', 'no', 'nope', 'n',
3868
- 'sure', 'thanks', 'thank you', 'thx', 'ty', 'cool', 'nice', 'great', 'awesome',
3869
- 'perfect', 'got it', 'gotcha', 'right', 'makes sense', 'sounds good', 'lgtm',
3870
- 'continue', 'keep going', 'go on', 'go ahead', 'proceed', 'next', 'more',
3871
- 'again', 'retry', 'stop', 'wait', 'hmm', 'hm', 'oh', 'i see', 'done', 'good',
3872
- ]);
3873
- const routable = t.length > 0 && !t.startsWith('/') && !t.startsWith('!') && !CONVERSATIONAL.has(norm);
3874
-
3875
- // Routing is opt-in via a marker file \u2014 either global ('routing-on', every
3876
- // session) or per-session ('routing-on-<sid>', just this one). Per-session lets
3877
- // us enable routing for one session (e.g. a test) without touching the others.
3878
- let routingEnabled = false;
3879
- try {
3880
- routingEnabled = existsSync(join(HOME, '.synkro', 'pty', 'routing-on'))
3881
- || existsSync(join(HOME, '.synkro', 'pty', 'routing-on-' + safeSid));
3882
- } catch {}
3883
-
3884
- if (!isReinjected && routable && routingEnabled) {
3885
- // ESCALATION-ONLY: classify in-place (fast local head, 1s cap). Block +
3886
- // escalate to Opus ONLY for a confident opus verdict; never downgrade. Any
3887
- // timeout / non-opus / error \u2192 run on the current model (no block). This
3888
- // makes the classifier's weakness (under-firing opus) harmless: worst case
3889
- // is a MISSED escalation = status quo, never a broken output on a weak model.
3890
- let escalate = false;
3891
- try {
3892
- const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
3893
- method: 'POST',
3894
- headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
3895
- body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
3896
- signal: AbortSignal.timeout(1000),
3897
- });
3898
- if (resp.ok) {
3899
- const data = await resp.json();
3900
- if (/<model>\\s*claude-opus-4-8\\s*<\\/model>/i.test(String(data.result || ''))) escalate = true;
3901
- }
3902
- } catch {}
3903
- if (escalate) {
3904
- try {
3905
- const bin = cliBin();
3906
- if (bin) {
3907
- // Orchestrator applies the pre-decided escalation (skip re-classify).
3908
- const child = spawn('node', [bin, 'route-and-resubmit', sid, t.slice(0, 1000), tmuxSession || '', 'claude-opus-4-8'], { detached: true, stdio: 'ignore' });
3909
- child.unref();
3910
- blockForRouting = true; // hold this prompt; the orchestrator re-submits it on Opus
3911
- }
3912
- } catch {}
3913
- }
3914
- }
3915
- }
3916
- } catch {}
3917
-
3918
- // Emit the single verdict line. Block (with a visible reason) when routing is
3919
- // taking over the prompt; otherwise allow. Fail-open: any throw above \u2192 allow.
3920
- if (blockForRouting) {
3921
- process.stdout.write(JSON.stringify({ decision: 'block', reason: 'Synkro is routing this to the best model and will resubmit it automatically\u2026' }) + '\\n');
3922
- } else {
3923
- process.stdout.write('{}\\n');
3924
- }
4003
+ for await (const _chunk of process.stdin) {}
4004
+ process.stdout.write('{}\\n');
3925
4005
  `;
3926
4006
  STUB_TASK_ACTIVATE_INTENT_TS = `#!/usr/bin/env bun
3927
4007
  import { readFileSync } from 'node:fs';
@@ -4560,7 +4640,7 @@ __export(claudeDesktopTap_exports, {
4560
4640
  claudeDesktopInstalled: () => claudeDesktopInstalled,
4561
4641
  runClaudeDesktopTap: () => runClaudeDesktopTap
4562
4642
  });
4563
- import { spawn as spawn2 } from "child_process";
4643
+ import { spawn as spawn3 } from "child_process";
4564
4644
  import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
4565
4645
  import { join as join13 } from "path";
4566
4646
  import { homedir as homedir15 } from "os";
@@ -4709,7 +4789,7 @@ async function runClaudeDesktopTap(opts = {}) {
4709
4789
  const runnerPath = join13(sessionDir, "run.sh");
4710
4790
  writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
4711
4791
  await new Promise((resolve6) => {
4712
- const child = spawn2("bash", [runnerPath], {
4792
+ const child = spawn3("bash", [runnerPath], {
4713
4793
  stdio: "inherit",
4714
4794
  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" : "" }
4715
4795
  });
@@ -6093,6 +6173,7 @@ __export(dockerInstall_exports, {
6093
6173
  dockerUpdate: () => dockerUpdate,
6094
6174
  ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
6095
6175
  imageTag: () => imageTag,
6176
+ isContainerActiveState: () => isContainerActiveState,
6096
6177
  normalizeProvider: () => normalizeProvider,
6097
6178
  poolLabel: () => poolLabel,
6098
6179
  readContainerConfig: () => readContainerConfig,
@@ -6126,6 +6207,9 @@ function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
6126
6207
  }
6127
6208
  return value;
6128
6209
  }
6210
+ function isContainerActiveState(status) {
6211
+ return status === "running" || status === "restarting" || status === "paused";
6212
+ }
6129
6213
  function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
6130
6214
  const iterations = 4096;
6131
6215
  const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
@@ -6134,27 +6218,29 @@ function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
6134
6218
  const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
6135
6219
  return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
6136
6220
  }
6137
- function ensurePgliteProxyCredentials() {
6138
- const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
6139
- const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
6221
+ function ensurePgliteProxyCredentials(paths = {}) {
6222
+ const passwordPath = paths.passwordPath ?? PGLITE_PASSWORD_PATH;
6223
+ const userlistPath = paths.userlistPath ?? PGLITE_USERLIST_PATH;
6224
+ const hasPassword = existsSync19(passwordPath) && readFileSync17(passwordPath, "utf-8").trim().length > 0;
6225
+ const hasUserlist = existsSync19(userlistPath) && readFileSync17(userlistPath, "utf-8").trim().length > 0;
6140
6226
  if (hasPassword && hasUserlist) {
6141
- chmodSync3(PGLITE_PASSWORD_PATH, 384);
6142
- chmodSync3(PGLITE_USERLIST_PATH, 384);
6227
+ chmodSync3(passwordPath, 384);
6228
+ chmodSync3(userlistPath, 384);
6143
6229
  return;
6144
6230
  }
6145
6231
  const password = randomBytes2(24).toString("base64url");
6146
6232
  const verifier = createPgliteScramVerifier(password);
6147
6233
  const suffix = `${process.pid}.${Date.now()}.tmp`;
6148
- const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
6149
- const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
6234
+ const passwordTmp = `${passwordPath}.${suffix}`;
6235
+ const userlistTmp = `${userlistPath}.${suffix}`;
6150
6236
  writeFileSync14(passwordTmp, `${password}
6151
6237
  `, { mode: 384 });
6152
6238
  writeFileSync14(userlistTmp, `"synkro" "${verifier}"
6153
6239
  `, { mode: 384 });
6154
- renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
6155
- renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
6156
- chmodSync3(PGLITE_PASSWORD_PATH, 384);
6157
- chmodSync3(PGLITE_USERLIST_PATH, 384);
6240
+ renameSync7(passwordTmp, passwordPath);
6241
+ renameSync7(userlistTmp, userlistPath);
6242
+ chmodSync3(passwordPath, 384);
6243
+ chmodSync3(userlistPath, 384);
6158
6244
  }
6159
6245
  function resolveConductorProvider(pool, counts) {
6160
6246
  if (pool !== "auto") return pool;
@@ -6615,7 +6701,7 @@ function dockerStatus() {
6615
6701
  timeout: 5e3
6616
6702
  });
6617
6703
  const status = (r.stdout || "").trim();
6618
- if (status !== "running") return { running: false };
6704
+ if (!isContainerActiveState(status)) return { running: false };
6619
6705
  return {
6620
6706
  running: true,
6621
6707
  image: imageTag(),
@@ -6785,7 +6871,7 @@ var init_dockerInstall = __esm({
6785
6871
  HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
6786
6872
  CONTAINER_NAME = resolveContainerName();
6787
6873
  defaultImageVersion = () => {
6788
- if (true) return "1.7.92";
6874
+ if (true) return "1.7.94";
6789
6875
  try {
6790
6876
  const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
6791
6877
  if (pkg.version) return pkg.version;
@@ -7022,6 +7108,7 @@ __export(ptyShim_exports, {
7022
7108
  injectModel: () => injectModel,
7023
7109
  installPtyShim: () => installPtyShim,
7024
7110
  listSessions: () => listSessions,
7111
+ restoreLegacyClaudeShim: () => restoreLegacyClaudeShim,
7025
7112
  uninstallPtyShim: () => uninstallPtyShim
7026
7113
  });
7027
7114
  import {
@@ -7038,48 +7125,11 @@ import {
7038
7125
  } from "fs";
7039
7126
  import { homedir as homedir20 } from "os";
7040
7127
  import { join as join18 } from "path";
7041
- import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
7128
+ import { spawnSync as spawnSync5, spawn as spawn4 } from "child_process";
7042
7129
  function rcFiles() {
7043
7130
  const h = homedir20();
7044
7131
  return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
7045
7132
  }
7046
- function resolveRealClaude() {
7047
- const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
7048
- const p = (r.stdout || "").trim();
7049
- if (p) {
7050
- try {
7051
- return realpathSync(p);
7052
- } catch {
7053
- return p;
7054
- }
7055
- }
7056
- for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
7057
- if (existsSync21(c)) {
7058
- try {
7059
- return realpathSync(c);
7060
- } catch {
7061
- return c;
7062
- }
7063
- }
7064
- }
7065
- return "claude";
7066
- }
7067
- function findClaudeLink() {
7068
- const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
7069
- let linkPath = (r.stdout || "").trim();
7070
- if (!linkPath) {
7071
- const c = join18(homedir20(), ".local", "bin", "claude");
7072
- if (existsSync21(c)) linkPath = c;
7073
- else return null;
7074
- }
7075
- if (linkPath === SHIM_PATH) return null;
7076
- let realTarget = linkPath;
7077
- try {
7078
- realTarget = realpathSync(linkPath);
7079
- } catch {
7080
- }
7081
- return { linkPath, realTarget };
7082
- }
7083
7133
  function isOurShim(path) {
7084
7134
  try {
7085
7135
  return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
@@ -7087,44 +7137,39 @@ function isOurShim(path) {
7087
7137
  return false;
7088
7138
  }
7089
7139
  }
7090
- function shadowClaude() {
7091
- const found = findClaudeLink();
7092
- if (!found) {
7093
- console.log(" \xB7 no claude binary to shadow \u2014 PATH shim only");
7094
- return;
7095
- }
7096
- const { linkPath, realTarget } = found;
7097
- if (isOurShim(linkPath)) {
7098
- console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
7099
- return;
7100
- }
7101
- let wasSymlink = false;
7102
- try {
7103
- wasSymlink = lstatSync(linkPath).isSymbolicLink();
7104
- } catch {
7105
- }
7106
- const state = { linkPath, realTarget, wasSymlink };
7140
+ function latestVersionedClaude(versionsDir) {
7141
+ let versions = [];
7107
7142
  try {
7108
- writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
7143
+ versions = readdirSync3(versionsDir).map((entry) => join18(versionsDir, entry)).filter((entry) => existsSync21(entry)).sort((a, b) => a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" }));
7109
7144
  } catch {
7110
7145
  }
7111
- try {
7112
- rmSync2(linkPath, { force: true });
7113
- writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
7114
- chmodSync4(linkPath, 493);
7115
- console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
7116
- } catch (e) {
7117
- console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
7146
+ return versions.at(-1) || "";
7147
+ }
7148
+ function restoreLegacyClaudeShim(linkPath, versionsDir) {
7149
+ if (!isOurShim(linkPath)) return false;
7150
+ const realTarget = latestVersionedClaude(versionsDir);
7151
+ if (!realTarget) {
7152
+ throw new Error(`no Claude version found under ${versionsDir}`);
7118
7153
  }
7154
+ rmSync2(linkPath, { force: true });
7155
+ symlinkSync(realTarget, linkPath);
7156
+ return true;
7119
7157
  }
7120
7158
  function unshadowClaude() {
7121
7159
  let state;
7122
7160
  try {
7123
7161
  state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
7124
7162
  } catch {
7163
+ }
7164
+ if (!state?.linkPath) {
7165
+ const legacyLink = join18(homedir20(), ".local", "bin", "claude");
7166
+ const versionsDir = join18(homedir20(), ".local", "share", "claude", "versions");
7167
+ if (restoreLegacyClaudeShim(legacyLink, versionsDir)) {
7168
+ const target = realpathSync(legacyLink);
7169
+ console.log(`\u2713 restored ${legacyLink.replace(homedir20(), "~")} \u2192 ${target.replace(homedir20(), "~")}`);
7170
+ }
7125
7171
  return;
7126
7172
  }
7127
- if (!state?.linkPath) return;
7128
7173
  try {
7129
7174
  if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
7130
7175
  rmSync2(state.linkPath, { force: true });
@@ -7134,25 +7179,6 @@ function unshadowClaude() {
7134
7179
  console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
7135
7180
  }
7136
7181
  }
7137
- function addPathBlock() {
7138
- const touched = [];
7139
- for (const rc of rcFiles()) {
7140
- if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
7141
- let body = "";
7142
- try {
7143
- body = readFileSync20(rc, "utf-8");
7144
- } catch {
7145
- }
7146
- const cleaned = stripBlock(body);
7147
- const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
7148
- try {
7149
- writeFileSync16(rc, next, "utf-8");
7150
- touched.push(rc);
7151
- } catch {
7152
- }
7153
- }
7154
- return touched;
7155
- }
7156
7182
  function stripBlock(body) {
7157
7183
  if (!body.includes(RC_BEGIN)) return body;
7158
7184
  const re = new RegExp(`\\n?${escapeRe(RC_BEGIN)}[\\s\\S]*?${escapeRe(RC_END)}\\n?`, "g");
@@ -7162,28 +7188,14 @@ function escapeRe(s) {
7162
7188
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7163
7189
  }
7164
7190
  function installPtyShim() {
7165
- mkdirSync14(SHIM_BIN_DIR, { recursive: true });
7166
- mkdirSync14(PTY_STATE_DIR, { recursive: true });
7167
- const real = resolveRealClaude();
7168
- writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
7169
- chmodSync4(SHIM_PATH, 493);
7170
- const touched = addPathBlock();
7171
- console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
7172
- shadowClaude();
7173
- if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
7191
+ console.warn("Synkro host model routing has been removed; cleaning up any legacy pty shim.");
7192
+ uninstallPtyShim();
7174
7193
  }
7175
7194
  function uninstallPtyShim() {
7176
7195
  try {
7177
7196
  unshadowClaude();
7178
7197
  } catch {
7179
7198
  }
7180
- try {
7181
- const ls = spawnSync5("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
7182
- for (const s of (ls.stdout || "").split("\n")) {
7183
- if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync5("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
7184
- }
7185
- } catch {
7186
- }
7187
7199
  const cleaned = [];
7188
7200
  for (const rc of rcFiles()) {
7189
7201
  if (!existsSync21(rc)) continue;
@@ -7284,7 +7296,7 @@ function injectModel(model, sessionOverride) {
7284
7296
  } catch {
7285
7297
  }
7286
7298
  const poll = `old=$(cat '${pidFile}' 2>/dev/null); if [ -n "$old" ]; then kill "$old" 2>/dev/null; fi; rm -f '${pidFile}'; set -o noclobber; echo $$ > '${pidFile}' 2>/dev/null || exit 1; set +o noclobber; trap 'rm -f "${pidFile}"' EXIT; for i in $(seq 1 40); do if tmux capture-pane -t ${session} -p -S -25 2>/dev/null | grep -qiE 'switch model|yes, switch'; then tmux send-keys -t ${session} -l '1'; sleep 0.1; tmux send-keys -t ${session} Enter; break; fi; sleep 0.1; done`;
7287
- const child = spawn3("bash", ["-c", poll], { detached: true, stdio: "ignore" });
7299
+ const child = spawn4("bash", ["-c", poll], { detached: true, stdio: "ignore" });
7288
7300
  child.unref();
7289
7301
  return true;
7290
7302
  }
@@ -7570,7 +7582,7 @@ __export(install_exports, {
7570
7582
  import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
7571
7583
  import { homedir as homedir21 } from "os";
7572
7584
  import { join as join19, isAbsolute, resolve as resolve4 } from "path";
7573
- import { execSync as execSync4, spawn as spawn4 } from "child_process";
7585
+ import { execSync as execSync4, spawn as spawn5 } from "child_process";
7574
7586
  import { createInterface as createInterface2 } from "readline";
7575
7587
  import { createHash as createHash4 } from "crypto";
7576
7588
  function resolvePersistedHookMode() {
@@ -7698,7 +7710,6 @@ async function promptTranscriptSources(wantCC, wantCursor, wantCodex) {
7698
7710
  function ensureSynkroDir() {
7699
7711
  mkdirSync15(SYNKRO_DIR11, { recursive: true });
7700
7712
  mkdirSync15(HOOKS_DIR, { recursive: true });
7701
- mkdirSync15(BIN_DIR, { recursive: true });
7702
7713
  mkdirSync15(OFFSETS_DIR, { recursive: true });
7703
7714
  mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
7704
7715
  }
@@ -7722,7 +7733,7 @@ function writeHookScripts() {
7722
7733
  const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
7723
7734
  const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
7724
7735
  const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
7725
- const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
7736
+ const legacyPromptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
7726
7737
  const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
7727
7738
  const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
7728
7739
  const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
@@ -7753,7 +7764,7 @@ function writeHookScripts() {
7753
7764
  [subagentStartScriptPath, STUB_SUBAGENT_START_TS],
7754
7765
  [subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
7755
7766
  [userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
7756
- [promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
7767
+ [legacyPromptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
7757
7768
  [installScanScriptPath, STUB_INSTALL_SCAN_TS],
7758
7769
  [taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS],
7759
7770
  [mcpGateScriptPath, STUB_MCP_GATE_TS],
@@ -7792,7 +7803,6 @@ function writeHookScripts() {
7792
7803
  subagentStartScript: subagentStartScriptPath,
7793
7804
  subagentStopScript: subagentStopScriptPath,
7794
7805
  userPromptSubmitScript: userPromptSubmitScriptPath,
7795
- promptRouteScript: promptRouteScriptPath,
7796
7806
  installScanScript: installScanScriptPath,
7797
7807
  cursorBashJudgeScript: cursorBashJudgePath,
7798
7808
  cursorEditCaptureScript: cursorEditCapturePath,
@@ -7830,7 +7840,7 @@ function writeConfigEnv(opts) {
7830
7840
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
7831
7841
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
7832
7842
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
7833
- `SYNKRO_VERSION=${shellQuoteSingle2("1.7.92")}`
7843
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.7.94")}`
7834
7844
  ];
7835
7845
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
7836
7846
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -8572,7 +8582,7 @@ async function installCommand(opts = {}) {
8572
8582
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
8573
8583
  emit("install", {
8574
8584
  phase: "started",
8575
- cli_version_to: "1.7.92",
8585
+ cli_version_to: "1.7.94",
8576
8586
  agents_detected: agents.map((a) => a.kind),
8577
8587
  with_github: false,
8578
8588
  with_local_cc: false,
@@ -8613,7 +8623,6 @@ async function installCommand(opts = {}) {
8613
8623
  sessionStartScriptPath: scripts.sessionStartScript,
8614
8624
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
8615
8625
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8616
- promptRouteScriptPath: scripts.promptRouteScript,
8617
8626
  installScanScriptPath: scripts.installScanScript,
8618
8627
  taskActivateIntentScriptPath: scripts.taskActivateIntentScript,
8619
8628
  mcpGateScriptPath: scripts.mcpGateScript,
@@ -8661,25 +8670,22 @@ async function installCommand(opts = {}) {
8661
8670
  subagentStartScriptPath: scripts.subagentStartScript,
8662
8671
  subagentStopScriptPath: scripts.subagentStopScript,
8663
8672
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8664
- promptRouteScriptPath: scripts.promptRouteScript,
8665
8673
  installScanScriptPath: scripts.installScanScript,
8666
8674
  taskActivateIntentScriptPath: scripts.taskActivateIntentScript,
8667
8675
  mcpGateScriptPath: scripts.mcpGateScript,
8668
8676
  skipTranscriptSync: !transcriptCodex
8669
8677
  });
8670
8678
  console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
8671
- console.log(" One-time setup: restart Codex, run /hooks, then review and trust the Synkro hooks.");
8679
+ await reportCodexHookTrust(agent.binaryPath, process.cwd());
8672
8680
  }
8673
8681
  }
8674
8682
  console.log();
8675
- if (hasClaudeCode) {
8676
- try {
8677
- installPtyShim();
8678
- } catch (e) {
8679
- console.warn(` \u26A0 pty shim install skipped: ${e.message}`);
8680
- }
8681
- console.log();
8683
+ try {
8684
+ uninstallPtyShim();
8685
+ } catch (e) {
8686
+ console.warn(` \u26A0 legacy pty shim cleanup skipped: ${e.message}`);
8682
8687
  }
8688
+ console.log();
8683
8689
  let userId;
8684
8690
  let orgId;
8685
8691
  let email;
@@ -9093,7 +9099,7 @@ async function installCommand(opts = {}) {
9093
9099
  }
9094
9100
  }
9095
9101
  try {
9096
- const child = spawn4(process.execPath, [process.argv[1], "reachability-scan", "--quiet"], {
9102
+ const child = spawn5(process.execPath, [process.argv[1], "reachability-scan", "--quiet"], {
9097
9103
  detached: true,
9098
9104
  stdio: "ignore",
9099
9105
  cwd: process.cwd()
@@ -9274,7 +9280,6 @@ function reconcileHarness() {
9274
9280
  sessionStartScriptPath: scripts.sessionStartScript,
9275
9281
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
9276
9282
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
9277
- promptRouteScriptPath: scripts.promptRouteScript,
9278
9283
  installScanScriptPath: scripts.installScanScript,
9279
9284
  taskActivateIntentScriptPath: scripts.taskActivateIntentScript,
9280
9285
  mcpGateScriptPath: scripts.mcpGateScript,
@@ -9344,14 +9349,13 @@ function reconcileHarness() {
9344
9349
  subagentStartScriptPath: scripts.subagentStartScript,
9345
9350
  subagentStopScriptPath: scripts.subagentStopScript,
9346
9351
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
9347
- promptRouteScriptPath: scripts.promptRouteScript,
9348
9352
  installScanScriptPath: scripts.installScanScript,
9349
9353
  taskActivateIntentScriptPath: scripts.taskActivateIntentScript,
9350
9354
  mcpGateScriptPath: scripts.mcpGateScript,
9351
9355
  skipTranscriptSync: !persistedTranscriptConsent("CODEX")
9352
9356
  });
9353
9357
  console.log(" \u2713 Codex hooks registered");
9354
- console.log(" One-time setup: restart Codex, run /hooks, then review and trust the Synkro hooks.");
9358
+ console.log(" Restart Codex and review /hooks whenever Codex marks a Synkro hook modified.");
9355
9359
  try {
9356
9360
  installCodexMcpConfig({ gatewayUrl: "", bearerToken: "", local: true });
9357
9361
  console.log(" \u2713 Codex MCP registered");
@@ -10119,7 +10123,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
10119
10123
  }
10120
10124
  return { sessions: totalSessions, messages: totalMessages };
10121
10125
  }
10122
- var SYNKRO_DIR11, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
10126
+ var SYNKRO_DIR11, HOOKS_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
10123
10127
  var init_install = __esm({
10124
10128
  "cli/commands/install.ts"() {
10125
10129
  "use strict";
@@ -10127,6 +10131,7 @@ var init_install = __esm({
10127
10131
  init_ccHookConfig();
10128
10132
  init_cursorHookConfig();
10129
10133
  init_codexHookConfig();
10134
+ init_codexHookTrust();
10130
10135
  init_mcpConfig();
10131
10136
  init_synkroCommand();
10132
10137
  init_skillParser();
@@ -10146,7 +10151,6 @@ var init_install = __esm({
10146
10151
  init_codexTranscriptMessages();
10147
10152
  SYNKRO_DIR11 = join19(homedir21(), ".synkro");
10148
10153
  HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
10149
- BIN_DIR = join19(SYNKRO_DIR11, "bin");
10150
10154
  CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
10151
10155
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
10152
10156
  import { readFileSync } from 'node:fs';
@@ -11202,7 +11206,7 @@ var scanPr_exports = {};
11202
11206
  __export(scanPr_exports, {
11203
11207
  scanPrCommand: () => scanPrCommand
11204
11208
  });
11205
- import { execSync as execSync5, spawn as spawn5 } from "child_process";
11209
+ import { execSync as execSync5, spawn as spawn6 } from "child_process";
11206
11210
  import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
11207
11211
  import { join as join23 } from "path";
11208
11212
  function parseMatchSpec(condition) {
@@ -11413,7 +11417,7 @@ ${hunks}`;
11413
11417
  const fullPrompt = promptHeader + userMessage;
11414
11418
  return new Promise((resolve6) => {
11415
11419
  const t0 = Date.now();
11416
- const proc = spawn5(
11420
+ const proc = spawn6(
11417
11421
  "claude",
11418
11422
  ["--print", "--model", "claude-sonnet-4-6", "--output-format", "json", "--no-session-persistence"],
11419
11423
  {
@@ -11512,7 +11516,7 @@ ${JSON.stringify(findings, null, 2)}
11512
11516
  function spawnOpusConsolidator(findings, claudeToken) {
11513
11517
  return new Promise((resolve6) => {
11514
11518
  const prompt = buildConsolidationPrompt(findings);
11515
- const proc = spawn5(
11519
+ const proc = spawn6(
11516
11520
  "claude",
11517
11521
  ["--print", "--model", "claude-opus-4-7", "--output-format", "json", "--no-session-persistence"],
11518
11522
  {
@@ -11925,296 +11929,20 @@ var init_scanPr = __esm({
11925
11929
  }
11926
11930
  });
11927
11931
 
11928
- // cli/local-cc/routeDecide.ts
11929
- var routeDecide_exports = {};
11930
- __export(routeDecide_exports, {
11931
- routeDecide: () => routeDecide
11932
- });
11933
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11932
+ // cli/local-cc/pueue.ts
11933
+ import { execFileSync as execFileSync4, spawnSync as spawnSync9, spawn as spawn7 } from "child_process";
11934
11934
  import { homedir as homedir25 } from "os";
11935
11935
  import { join as join24 } from "path";
11936
- function safeSid(sid) {
11937
- return sid.replace(/[^A-Za-z0-9_-]/g, "_");
11938
- }
11939
- function loadMcpJwt() {
11940
- try {
11941
- return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11942
- } catch {
11943
- return "";
11944
- }
11945
- }
11946
- async function routeDecide(sessionId) {
11947
- if (!sessionId) return;
11948
- const sid = safeSid(sessionId);
11949
- let prompt = "";
11950
- try {
11951
- const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
11952
- prompt = String(rec.last_prompt || "").trim();
11953
- } catch {
11954
- return;
11955
- }
11956
- if (!prompt) return;
11957
- let model = "";
11958
- try {
11959
- const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
11960
- method: "POST",
11961
- headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
11962
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
11963
- // First call may spin up the route lane (a haiku worker boot), so allow headroom.
11964
- signal: AbortSignal.timeout(12e4)
11965
- });
11966
- if (!resp.ok) return;
11967
- const data = await resp.json();
11968
- const m = (data.result || "").match(/<model>\s*([A-Za-z0-9._:-]+)\s*<\/model>/i);
11969
- model = m ? m[1].trim() : "";
11970
- } catch {
11971
- return;
11972
- }
11973
- if (!model || !VALID_MODELS.has(model)) return;
11974
- const lastFile = join24(PTY_DIR, "route-last-" + sid);
11975
- let last = "";
11976
- try {
11977
- last = readFileSync25(lastFile, "utf-8").trim();
11978
- } catch {
11979
- }
11980
- if (model === last) return;
11981
- try {
11982
- writeFileSync19(lastFile, model);
11983
- } catch {
11984
- }
11985
- try {
11986
- writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
11987
- } catch {
11988
- }
11989
- }
11990
- var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
11991
- var init_routeDecide = __esm({
11992
- "cli/local-cc/routeDecide.ts"() {
11993
- "use strict";
11994
- SYNKRO_DIR13 = join24(homedir25(), ".synkro");
11995
- PTY_DIR = join24(SYNKRO_DIR13, "pty");
11996
- SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
11997
- VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
11998
- GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
11999
- }
12000
- });
12001
-
12002
- // cli/local-cc/routeOrchestrate.ts
12003
- var routeOrchestrate_exports = {};
12004
- __export(routeOrchestrate_exports, {
12005
- routeAndResubmit: () => routeAndResubmit
12006
- });
12007
- import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
12008
- import { homedir as homedir26 } from "os";
12009
- import { join as join25 } from "path";
12010
- import { spawnSync as spawnSync9 } from "child_process";
12011
- function safeSid2(sid) {
12012
- return sid.replace(/[^A-Za-z0-9_-]/g, "_");
12013
- }
12014
- function safeSession(s) {
12015
- return /^[A-Za-z0-9._:-]{1,64}$/.test(s);
12016
- }
12017
- function loadMcpJwt2() {
12018
- try {
12019
- return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
12020
- } catch {
12021
- return "";
12022
- }
12023
- }
12024
- async function classifyTask(prompt) {
12025
- try {
12026
- const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
12027
- method: "POST",
12028
- headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
12029
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
12030
- signal: AbortSignal.timeout(6e4)
12031
- });
12032
- if (!resp.ok) return null;
12033
- const data = await resp.json();
12034
- const m = (data.result || "").match(/<model>\s*([A-Za-z0-9._:-]+)\s*<\/model>/i);
12035
- if (!m) return null;
12036
- const v = m[1].trim();
12037
- if (v.toLowerCase() === "keep") return "keep";
12038
- return VALID_MODELS2.has(v) ? v : null;
12039
- } catch {
12040
- return null;
12041
- }
12042
- }
12043
- function lastRoutedModel(sid) {
12044
- try {
12045
- const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
12046
- return VALID_MODELS2.has(v) ? v : "";
12047
- } catch {
12048
- return "";
12049
- }
12050
- }
12051
- function resolveSession(sid, tmuxSession) {
12052
- const candidates = [];
12053
- if (tmuxSession) candidates.push(tmuxSession);
12054
- try {
12055
- const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
12056
- if (rec.tmux_session) candidates.push(rec.tmux_session);
12057
- } catch {
12058
- }
12059
- try {
12060
- candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
12061
- } catch {
12062
- }
12063
- for (const c of candidates) if (c && safeSession(c)) return c;
12064
- return "";
12065
- }
12066
- async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
12067
- const trimmed = (task || "").trim();
12068
- if (!sessionId || !trimmed) return;
12069
- const sid = safeSid2(sessionId);
12070
- const session = resolveSession(sid, tmuxSession);
12071
- if (!session) return;
12072
- if (spawnSync9("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
12073
- const current = lastRoutedModel(sid);
12074
- const picked = forceModel && VALID_MODELS2.has(forceModel) ? forceModel : await classifyTask(trimmed);
12075
- const doSwap = !!picked && picked !== "keep" && picked !== current;
12076
- const sk = (...a) => spawnSync9("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
12077
- const capture = () => spawnSync9("tmux", ["capture-pane", "-t", session, "-p", "-S", "-25"], { encoding: "utf-8" }).stdout || "";
12078
- await wait(400);
12079
- if (doSwap) {
12080
- sk("C-u");
12081
- await wait(80);
12082
- sk("-l", `/model ${picked}`);
12083
- sk("Enter");
12084
- for (let i = 0; i < 50; i++) {
12085
- await wait(100);
12086
- if (/switch model|yes, switch/i.test(capture())) {
12087
- sk("-l", "1");
12088
- await wait(120);
12089
- sk("Enter");
12090
- break;
12091
- }
12092
- }
12093
- await wait(500);
12094
- try {
12095
- writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
12096
- } catch {
12097
- }
12098
- }
12099
- try {
12100
- writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
12101
- } catch {
12102
- }
12103
- sk("C-u");
12104
- await wait(80);
12105
- spawnSync9("tmux", ["set-buffer", "-b", "synkro-route", trimmed], { encoding: "utf-8" });
12106
- spawnSync9("tmux", ["paste-buffer", "-t", session, "-b", "synkro-route", "-d"], { encoding: "utf-8" });
12107
- await wait(120);
12108
- sk("Enter");
12109
- }
12110
- var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
12111
- var init_routeOrchestrate = __esm({
12112
- "cli/local-cc/routeOrchestrate.ts"() {
12113
- "use strict";
12114
- SYNKRO_DIR14 = join25(homedir26(), ".synkro");
12115
- PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
12116
- SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
12117
- ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
12118
- VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
12119
- GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
12120
- wait = (ms) => new Promise((r) => setTimeout(r, ms));
12121
- }
12122
- });
12123
-
12124
- // cli/local-cc/routingToggle.ts
12125
- var routingToggle_exports = {};
12126
- __export(routingToggle_exports, {
12127
- routingCommand: () => routingCommand
12128
- });
12129
- import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
12130
- import { homedir as homedir27 } from "os";
12131
- import { join as join26 } from "path";
12132
- function safeSid3(sid) {
12133
- return sid.replace(/[^A-Za-z0-9_-]/g, "_");
12134
- }
12135
- function markerFor(session) {
12136
- return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
12137
- }
12138
- function routingCommand(args2) {
12139
- const sub = (args2[0] || "status").trim();
12140
- let session;
12141
- const si = args2.indexOf("--session");
12142
- if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
12143
- if (sub === "on") {
12144
- try {
12145
- writeFileSync21(markerFor(session), "1");
12146
- } catch (e) {
12147
- console.error("routing on failed:", String(e));
12148
- return;
12149
- }
12150
- console.log(session ? `Routing ON for session ${session}.` : "Routing ON (all sessions).");
12151
- console.log(" New task prompts will be classified and run on the best model automatically.");
12152
- return;
12153
- }
12154
- if (sub === "off") {
12155
- let removed = 0;
12156
- if (session) {
12157
- if (existsSync27(markerFor(session))) {
12158
- try {
12159
- unlinkSync9(markerFor(session));
12160
- removed++;
12161
- } catch {
12162
- }
12163
- }
12164
- } else {
12165
- for (const f of safeList()) {
12166
- if (f === "routing-on" || f.startsWith("routing-on-")) {
12167
- try {
12168
- unlinkSync9(join26(PTY_DIR3, f));
12169
- removed++;
12170
- } catch {
12171
- }
12172
- }
12173
- }
12174
- }
12175
- console.log(`Routing OFF${session ? ` for session ${session}` : ""} (${removed} marker${removed === 1 ? "" : "s"} cleared).`);
12176
- return;
12177
- }
12178
- const markers = safeList().filter((f) => f === "routing-on" || f.startsWith("routing-on-"));
12179
- if (markers.length === 0) {
12180
- console.log("Routing is OFF (no markers).");
12181
- return;
12182
- }
12183
- console.log("Routing is ON:");
12184
- for (const m of markers) {
12185
- if (m === "routing-on") console.log(" \u2022 global (all sessions)");
12186
- else console.log(" \u2022 session " + m.slice("routing-on-".length));
12187
- }
12188
- }
12189
- function safeList() {
12190
- try {
12191
- return readdirSync6(PTY_DIR3);
12192
- } catch {
12193
- return [];
12194
- }
12195
- }
12196
- var PTY_DIR3;
12197
- var init_routingToggle = __esm({
12198
- "cli/local-cc/routingToggle.ts"() {
12199
- "use strict";
12200
- PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
12201
- }
12202
- });
12203
-
12204
- // cli/local-cc/pueue.ts
12205
- import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
12206
- import { homedir as homedir28 } from "os";
12207
- import { join as join27 } from "path";
12208
11936
  import { connect as connect2 } from "net";
12209
11937
  function pueueAvailable() {
12210
- const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11938
+ const r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
12211
11939
  if (r.status !== 0) {
12212
11940
  throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
12213
11941
  }
12214
11942
  }
12215
11943
  function statusJson() {
12216
11944
  pueueAvailable();
12217
- const r = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8" });
11945
+ const r = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8" });
12218
11946
  if (r.status !== 0) {
12219
11947
  throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
12220
11948
  }
@@ -12259,18 +11987,18 @@ function startTask(opts = {}) {
12259
11987
  let existing = findTask(ch);
12260
11988
  while (existing) {
12261
11989
  if (existing.status === "Running" || existing.status === "Queued") {
12262
- spawnSync10("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
12263
- spawnSync10("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
11990
+ spawnSync9("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
11991
+ spawnSync9("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
12264
11992
  for (let i = 0; i < 10; i++) {
12265
11993
  const check = findTask(ch);
12266
11994
  if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
12267
- spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
11995
+ spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
12268
11996
  }
12269
11997
  }
12270
- spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11998
+ spawnSync9("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
12271
11999
  existing = findTask(ch);
12272
12000
  }
12273
- const runScript = join27(cwd, "run-claude.sh");
12001
+ const runScript = join24(cwd, "run-claude.sh");
12274
12002
  const args2 = [
12275
12003
  "add",
12276
12004
  "--label",
@@ -12281,7 +12009,7 @@ function startTask(opts = {}) {
12281
12009
  "bash",
12282
12010
  runScript
12283
12011
  ];
12284
- const r = spawnSync10("pueue", args2, { encoding: "utf-8" });
12012
+ const r = spawnSync9("pueue", args2, { encoding: "utf-8" });
12285
12013
  if (r.status !== 0) {
12286
12014
  throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
12287
12015
  }
@@ -12292,25 +12020,25 @@ function startTask(opts = {}) {
12292
12020
  return created;
12293
12021
  }
12294
12022
  function stopTask(channel = CHANNEL_PRIMARY) {
12295
- spawnSync10("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
12023
+ spawnSync9("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
12296
12024
  let t = findTask(channel);
12297
12025
  while (t) {
12298
12026
  if (t.status === "Running" || t.status === "Queued") {
12299
- spawnSync10("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
12027
+ spawnSync9("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
12300
12028
  for (let i = 0; i < 10; i++) {
12301
12029
  const check = findTask(channel);
12302
12030
  if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
12303
- spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
12031
+ spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
12304
12032
  }
12305
12033
  }
12306
- spawnSync10("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
12034
+ spawnSync9("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
12307
12035
  t = findTask(channel);
12308
12036
  }
12309
12037
  }
12310
12038
  function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
12311
12039
  const t = findTask(channel);
12312
12040
  if (!t) return `(no ${channel.taskLabel} task)`;
12313
- const r = spawnSync10("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
12041
+ const r = spawnSync9("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
12314
12042
  return r.stdout || r.stderr || "(no output)";
12315
12043
  }
12316
12044
  function ensureRunning(opts = {}) {
@@ -12335,8 +12063,8 @@ function probePort(host, port, timeoutMs = 500) {
12335
12063
  });
12336
12064
  }
12337
12065
  function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
12338
- spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
12339
- spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
12066
+ spawnSync9("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
12067
+ spawnSync9("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
12340
12068
  }
12341
12069
  async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
12342
12070
  const deadline = Date.now() + timeoutMs;
@@ -12348,46 +12076,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
12348
12076
  return probePort(host, port);
12349
12077
  }
12350
12078
  function brewInstall(pkg) {
12351
- const brew = spawnSync10("brew", ["--version"], { encoding: "utf-8" });
12079
+ const brew = spawnSync9("brew", ["--version"], { encoding: "utf-8" });
12352
12080
  if (brew.status !== 0) return false;
12353
12081
  console.log(` Installing ${pkg} via brew...`);
12354
- const r = spawnSync10("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
12082
+ const r = spawnSync9("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
12355
12083
  return r.status === 0;
12356
12084
  }
12357
12085
  function assertPueueInstalled() {
12358
- let r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
12086
+ let r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
12359
12087
  if (r.status !== 0) {
12360
12088
  if (process.platform === "darwin" && brewInstall("pueue")) {
12361
- r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
12089
+ r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
12362
12090
  if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
12363
12091
  } else {
12364
12092
  throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
12365
12093
  }
12366
12094
  }
12367
- const status = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
12095
+ const status = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
12368
12096
  if (status.status !== 0) {
12369
12097
  console.log(" Starting pueued daemon...");
12370
- const child = spawn6("pueued", ["-d"], { stdio: "ignore", detached: true });
12098
+ const child = spawn7("pueued", ["-d"], { stdio: "ignore", detached: true });
12371
12099
  child.unref();
12372
- spawnSync10("sleep", ["1"]);
12373
- const retry = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
12100
+ spawnSync9("sleep", ["1"]);
12101
+ const retry = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
12374
12102
  if (retry.status !== 0) {
12375
12103
  throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
12376
12104
  }
12377
12105
  }
12378
- spawnSync10("pueue", ["parallel", "2"], { encoding: "utf-8" });
12106
+ spawnSync9("pueue", ["parallel", "2"], { encoding: "utf-8" });
12379
12107
  }
12380
12108
  function assertClaudeInstalled() {
12381
- const r = spawnSync10("claude", ["--version"], { encoding: "utf-8" });
12109
+ const r = spawnSync9("claude", ["--version"], { encoding: "utf-8" });
12382
12110
  if (r.status !== 0) {
12383
12111
  throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
12384
12112
  }
12385
12113
  }
12386
12114
  function assertTmuxInstalled() {
12387
- let r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
12115
+ let r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
12388
12116
  if (r.status !== 0) {
12389
12117
  if (process.platform === "darwin" && brewInstall("tmux")) {
12390
- r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
12118
+ r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
12391
12119
  if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
12392
12120
  } else {
12393
12121
  throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
@@ -12400,12 +12128,12 @@ var init_pueue = __esm({
12400
12128
  "use strict";
12401
12129
  TASK_LABEL = "synkro-local-cc";
12402
12130
  TMUX_SESSION = "synkro-local-cc";
12403
- SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
12131
+ SESSION_DIR2 = join24(homedir25(), ".synkro", "cc_sessions");
12404
12132
  TASK_LABEL_2 = "synkro-local-cc-2";
12405
12133
  TMUX_SESSION_2 = "synkro-local-cc-2";
12406
- SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
12407
- SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
12408
- SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
12134
+ SESSION_DIR_22 = join24(homedir25(), ".synkro", "cc_sessions_2");
12135
+ SESSION_DIR_32 = join24(homedir25(), ".synkro", "cc_sessions_3");
12136
+ SESSION_DIR_42 = join24(homedir25(), ".synkro", "cc_sessions_4");
12409
12137
  PueueError = class extends Error {
12410
12138
  constructor(message, cause) {
12411
12139
  super(message);
@@ -12420,13 +12148,13 @@ var init_pueue = __esm({
12420
12148
  });
12421
12149
 
12422
12150
  // cli/local-cc/settings.ts
12423
- import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
12424
- import { homedir as homedir29 } from "os";
12425
- import { join as join28 } from "path";
12151
+ import { existsSync as existsSync27, readFileSync as readFileSync25 } from "fs";
12152
+ import { homedir as homedir26 } from "os";
12153
+ import { join as join25 } from "path";
12426
12154
  function isLocalCCEnabled() {
12427
- if (!existsSync28(CONFIG_PATH5)) return false;
12155
+ if (!existsSync27(CONFIG_PATH5)) return false;
12428
12156
  try {
12429
- const content = readFileSync27(CONFIG_PATH5, "utf-8");
12157
+ const content = readFileSync25(CONFIG_PATH5, "utf-8");
12430
12158
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
12431
12159
  return match?.[1] === "yes";
12432
12160
  } catch {
@@ -12437,7 +12165,7 @@ var CONFIG_PATH5;
12437
12165
  var init_settings = __esm({
12438
12166
  "cli/local-cc/settings.ts"() {
12439
12167
  "use strict";
12440
- CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
12168
+ CONFIG_PATH5 = join25(homedir26(), ".synkro", "config.env");
12441
12169
  }
12442
12170
  });
12443
12171
 
@@ -12446,11 +12174,11 @@ var localCc_exports = {};
12446
12174
  __export(localCc_exports, {
12447
12175
  localCcCommand: () => localCcCommand
12448
12176
  });
12449
- import { spawnSync as spawnSync11 } from "child_process";
12450
- import { homedir as homedir30 } from "os";
12451
- import { join as join29 } from "path";
12177
+ import { spawnSync as spawnSync10 } from "child_process";
12178
+ import { homedir as homedir27 } from "os";
12179
+ import { join as join26 } from "path";
12452
12180
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
12453
- import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
12181
+ import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync19 } from "fs";
12454
12182
  function deploymentMode() {
12455
12183
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
12456
12184
  if (env === "docker") return "docker";
@@ -12556,15 +12284,15 @@ TROUBLESHOOTING
12556
12284
  `);
12557
12285
  }
12558
12286
  function readGatewayUrl() {
12559
- if (existsSync29(CONFIG_PATH6)) {
12560
- const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
12287
+ if (existsSync28(CONFIG_PATH6)) {
12288
+ const m = readFileSync26(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
12561
12289
  if (m) return m[1];
12562
12290
  }
12563
12291
  return "https://api.synkro.sh";
12564
12292
  }
12565
12293
  function updateLocalInferenceFlag(enabled) {
12566
- if (!existsSync29(CONFIG_PATH6)) return;
12567
- let content = readFileSync28(CONFIG_PATH6, "utf-8");
12294
+ if (!existsSync28(CONFIG_PATH6)) return;
12295
+ let content = readFileSync26(CONFIG_PATH6, "utf-8");
12568
12296
  const flag = enabled ? "yes" : "no";
12569
12297
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
12570
12298
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -12573,7 +12301,7 @@ function updateLocalInferenceFlag(enabled) {
12573
12301
  SYNKRO_LOCAL_INFERENCE='${flag}'
12574
12302
  `;
12575
12303
  }
12576
- writeFileSync22(CONFIG_PATH6, content, "utf-8");
12304
+ writeFileSync19(CONFIG_PATH6, content, "utf-8");
12577
12305
  }
12578
12306
  async function setServerGradingProvider(provider) {
12579
12307
  await ensureValidToken();
@@ -12627,7 +12355,7 @@ async function cmdStatus() {
12627
12355
  }
12628
12356
  const ch1Up = await isChannelAvailable();
12629
12357
  console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
12630
- const tmux1 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
12358
+ const tmux1 = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
12631
12359
  console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
12632
12360
  const t2 = findTask(CHANNEL_SECONDARY);
12633
12361
  if (!t2) {
@@ -12637,7 +12365,7 @@ async function cmdStatus() {
12637
12365
  }
12638
12366
  const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
12639
12367
  console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
12640
- const tmux2 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
12368
+ const tmux2 = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
12641
12369
  console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
12642
12370
  }
12643
12371
  async function cmdEnable() {
@@ -12845,7 +12573,7 @@ function cmdLogs(rest) {
12845
12573
  }
12846
12574
  return "200";
12847
12575
  })();
12848
- spawnSync11("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
12576
+ spawnSync10("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
12849
12577
  return;
12850
12578
  }
12851
12579
  for (const arg of rest) {
@@ -12893,7 +12621,7 @@ function cmdLogs(rest) {
12893
12621
  function cmdAttach(rest) {
12894
12622
  assertTmuxInstalled();
12895
12623
  const readonly = rest.some((a) => a === "--readonly" || a === "-r");
12896
- const has = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
12624
+ const has = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
12897
12625
  if (has.status !== 0) {
12898
12626
  console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
12899
12627
  process.exit(1);
@@ -12906,7 +12634,7 @@ function cmdAttach(rest) {
12906
12634
  console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
12907
12635
  console.log();
12908
12636
  const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
12909
- const r = spawnSync11("tmux", args2, { stdio: "inherit" });
12637
+ const r = spawnSync10("tmux", args2, { stdio: "inherit" });
12910
12638
  process.exit(r.status ?? 0);
12911
12639
  }
12912
12640
  async function cmdTest() {
@@ -13007,8 +12735,8 @@ var init_localCc = __esm({
13007
12735
  init_install();
13008
12736
  init_client2();
13009
12737
  init_stub();
13010
- SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
13011
- CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
12738
+ SYNKRO_CONFIG_PATH = join26(homedir27(), ".synkro", "config.env");
12739
+ CONFIG_PATH6 = join26(homedir27(), ".synkro", "config.env");
13012
12740
  }
13013
12741
  });
13014
12742
 
@@ -13017,14 +12745,14 @@ var import_exports = {};
13017
12745
  __export(import_exports, {
13018
12746
  importCommand: () => importCommand
13019
12747
  });
13020
- import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
13021
- import { homedir as homedir31 } from "os";
13022
- import { join as join30 } from "path";
12748
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync6 } from "fs";
12749
+ import { homedir as homedir28 } from "os";
12750
+ import { join as join27 } from "path";
13023
12751
  import { execSync as execSync6 } from "child_process";
13024
12752
  import { createInterface as createInterface4 } from "readline";
13025
12753
  function readMcpJwt() {
13026
12754
  try {
13027
- return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12755
+ return readFileSync27(join27(homedir28(), ".synkro", ".mcp-jwt"), "utf-8").trim();
13028
12756
  } catch {
13029
12757
  return "";
13030
12758
  }
@@ -13032,7 +12760,7 @@ function readMcpJwt() {
13032
12760
  function readConfigEnv2() {
13033
12761
  const out = {};
13034
12762
  try {
13035
- for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
12763
+ for (const line of readFileSync27(CONFIG_PATH7, "utf-8").split("\n")) {
13036
12764
  const t = line.trim();
13037
12765
  if (!t || t.startsWith("#")) continue;
13038
12766
  const eq = t.indexOf("=");
@@ -13044,8 +12772,8 @@ function readConfigEnv2() {
13044
12772
  }
13045
12773
  function projectsFolder() {
13046
12774
  const sanitized = process.cwd().replace(/\//g, "-");
13047
- const dir = join30(homedir31(), ".claude", "projects", sanitized);
13048
- return existsSync30(dir) ? dir : null;
12775
+ const dir = join27(homedir28(), ".claude", "projects", sanitized);
12776
+ return existsSync29(dir) ? dir : null;
13049
12777
  }
13050
12778
  function repoName() {
13051
12779
  try {
@@ -13084,7 +12812,7 @@ function extractToolResultText(content, e) {
13084
12812
  return t;
13085
12813
  }
13086
12814
  function parseSession(filePath, sessionId) {
13087
- const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
12815
+ const lines = readFileSync27(filePath, "utf-8").split("\n").filter(Boolean);
13088
12816
  const messages = [];
13089
12817
  const actions = [];
13090
12818
  let step = 0;
@@ -13151,7 +12879,7 @@ async function importCommand() {
13151
12879
  console.log("No Claude Code transcripts found for this repo (~/.claude/projects).");
13152
12880
  return;
13153
12881
  }
13154
- const files = readdirSync7(dir).filter((f) => f.endsWith(".jsonl"));
12882
+ const files = readdirSync6(dir).filter((f) => f.endsWith(".jsonl"));
13155
12883
  if (!files.length) {
13156
12884
  console.log("No sessions to import.");
13157
12885
  return;
@@ -13164,7 +12892,7 @@ async function importCommand() {
13164
12892
  return;
13165
12893
  }
13166
12894
  }
13167
- const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
12895
+ const sessions = files.map((f) => parseSession(join27(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
13168
12896
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
13169
12897
  let ok = 0, fail = 0;
13170
12898
  if (isCloud) {
@@ -13239,7 +12967,7 @@ var init_import = __esm({
13239
12967
  "cli/commands/import.ts"() {
13240
12968
  "use strict";
13241
12969
  init_stub();
13242
- CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
12970
+ CONFIG_PATH7 = join27(homedir28(), ".synkro", "config.env");
13243
12971
  }
13244
12972
  });
13245
12973
 
@@ -13281,10 +13009,10 @@ var init_packVerify = __esm({
13281
13009
  });
13282
13010
 
13283
13011
  // cli/installer/lockfile.ts
13284
- import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
13285
- import { join as join31 } from "path";
13012
+ import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync20 } from "fs";
13013
+ import { join as join28 } from "path";
13286
13014
  function lockPath(repoRoot2) {
13287
- return join31(repoRoot2, LOCK_FILE);
13015
+ return join28(repoRoot2, LOCK_FILE);
13288
13016
  }
13289
13017
  function writeLockfile(repoRoot2, entries) {
13290
13018
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -13302,7 +13030,7 @@ function writeLockfile(repoRoot2, entries) {
13302
13030
  ""
13303
13031
  ])
13304
13032
  ].join("\n");
13305
- writeFileSync23(lockPath(repoRoot2), body, "utf-8");
13033
+ writeFileSync20(lockPath(repoRoot2), body, "utf-8");
13306
13034
  }
13307
13035
  var LOCK_FILE;
13308
13036
  var init_lockfile = __esm({
@@ -13317,9 +13045,9 @@ var sync_exports = {};
13317
13045
  __export(sync_exports, {
13318
13046
  syncCommand: () => syncCommand
13319
13047
  });
13320
- import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
13321
- import { homedir as homedir32 } from "os";
13322
- import { join as join32 } from "path";
13048
+ import { existsSync as existsSync31, mkdirSync as mkdirSync18, readdirSync as readdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync21 } from "fs";
13049
+ import { homedir as homedir29 } from "os";
13050
+ import { join as join29 } from "path";
13323
13051
  function cacheKey(ref, version) {
13324
13052
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
13325
13053
  }
@@ -13350,7 +13078,7 @@ async function syncCommand(_args = []) {
13350
13078
  }
13351
13079
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
13352
13080
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
13353
- const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
13081
+ const cacheDir = join29(homedir29(), ".synkro", "cache", "packs");
13354
13082
  if (!cloud) mkdirSync18(cacheDir, { recursive: true });
13355
13083
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
13356
13084
  const lock = [];
@@ -13379,7 +13107,7 @@ async function syncCommand(_args = []) {
13379
13107
  if (!cloud) {
13380
13108
  const fname = cacheKey(ref, data.version);
13381
13109
  keptCacheFiles.add(fname);
13382
- writeFileSync24(join32(cacheDir, fname), JSON.stringify({
13110
+ writeFileSync21(join29(cacheDir, fname), JSON.stringify({
13383
13111
  ref,
13384
13112
  version: data.version,
13385
13113
  digest: data.digest,
@@ -13391,11 +13119,11 @@ async function syncCommand(_args = []) {
13391
13119
  const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
13392
13120
  console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
13393
13121
  }
13394
- if (!cloud && existsSync32(cacheDir)) {
13395
- for (const f of readdirSync8(cacheDir)) {
13122
+ if (!cloud && existsSync31(cacheDir)) {
13123
+ for (const f of readdirSync7(cacheDir)) {
13396
13124
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
13397
13125
  try {
13398
- rmSync4(join32(cacheDir, f));
13126
+ rmSync4(join29(cacheDir, f));
13399
13127
  } catch {
13400
13128
  }
13401
13129
  }
@@ -13424,13 +13152,13 @@ var whoami_exports = {};
13424
13152
  __export(whoami_exports, {
13425
13153
  whoamiCommand: () => whoamiCommand
13426
13154
  });
13427
- import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
13428
- import { join as join33 } from "path";
13429
- import { homedir as homedir33 } from "os";
13155
+ import { readFileSync as readFileSync29, existsSync as existsSync32 } from "fs";
13156
+ import { join as join30 } from "path";
13157
+ import { homedir as homedir30 } from "os";
13430
13158
  function readConfigEnv3() {
13431
- if (!existsSync33(CONFIG_PATH8)) return {};
13159
+ if (!existsSync32(CONFIG_PATH8)) return {};
13432
13160
  const out = {};
13433
- for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
13161
+ for (const line of readFileSync29(CONFIG_PATH8, "utf-8").split("\n")) {
13434
13162
  const t = line.trim();
13435
13163
  if (!t || t.startsWith("#")) continue;
13436
13164
  const eq = t.indexOf("=");
@@ -13440,8 +13168,8 @@ function readConfigEnv3() {
13440
13168
  }
13441
13169
  function jwtStatus() {
13442
13170
  try {
13443
- if (!existsSync33(JWT_PATH2)) return { status: "none" };
13444
- const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
13171
+ if (!existsSync32(JWT_PATH2)) return { status: "none" };
13172
+ const jwt2 = readFileSync29(JWT_PATH2, "utf-8").trim();
13445
13173
  if (!jwt2) return { status: "none" };
13446
13174
  const payload = jwt2.split(".")[1];
13447
13175
  if (!payload) return { status: "valid" };
@@ -13499,13 +13227,13 @@ async function whoamiCommand(args2 = []) {
13499
13227
  console.log("synkro identity");
13500
13228
  for (const [k, v] of rows) console.log(` ${k.padEnd(width)} ${v}`);
13501
13229
  }
13502
- var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
13230
+ var SYNKRO_DIR13, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
13503
13231
  var init_whoami = __esm({
13504
13232
  "cli/commands/whoami.ts"() {
13505
13233
  "use strict";
13506
- SYNKRO_DIR15 = join33(homedir33(), ".synkro");
13507
- CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
13508
- JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
13234
+ SYNKRO_DIR13 = join30(homedir30(), ".synkro");
13235
+ CONFIG_PATH8 = join30(SYNKRO_DIR13, "config.env");
13236
+ JWT_PATH2 = join30(SYNKRO_DIR13, ".mcp-jwt");
13509
13237
  GRADING_LABEL = {
13510
13238
  local: "on-device worker pool",
13511
13239
  cloud: "Synkro Cloud worker pool",
@@ -13550,12 +13278,12 @@ __export(linear_exports, {
13550
13278
  formatLinks: () => formatLinks,
13551
13279
  linearCommand: () => linearCommand
13552
13280
  });
13553
- import { readFileSync as readFileSync32 } from "fs";
13554
- import { homedir as homedir34 } from "os";
13555
- import { join as join34 } from "path";
13281
+ import { readFileSync as readFileSync30 } from "fs";
13282
+ import { homedir as homedir31 } from "os";
13283
+ import { join as join31 } from "path";
13556
13284
  function mcpJwt() {
13557
13285
  try {
13558
- return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
13286
+ return readFileSync30(join31(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
13559
13287
  } catch {
13560
13288
  return "";
13561
13289
  }
@@ -13590,11 +13318,11 @@ async function linearCommand(_args = []) {
13590
13318
  }
13591
13319
  console.log(formatLinks(links));
13592
13320
  }
13593
- var SYNKRO_DIR16, PORT2, BASE;
13321
+ var SYNKRO_DIR14, PORT2, BASE;
13594
13322
  var init_linear = __esm({
13595
13323
  "cli/commands/linear.ts"() {
13596
13324
  "use strict";
13597
- SYNKRO_DIR16 = join34(homedir34(), ".synkro");
13325
+ SYNKRO_DIR14 = join31(homedir31(), ".synkro");
13598
13326
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
13599
13327
  BASE = `http://127.0.0.1:${PORT2}`;
13600
13328
  }
@@ -13602,7 +13330,7 @@ var init_linear = __esm({
13602
13330
 
13603
13331
  // cli/scanning/cveReachability.ts
13604
13332
  import { parse } from "@babel/parser";
13605
- import { readFileSync as readFileSync33 } from "fs";
13333
+ import { readFileSync as readFileSync31 } from "fs";
13606
13334
  function walk(node, visit) {
13607
13335
  if (!node || typeof node.type !== "string") return;
13608
13336
  visit(node);
@@ -13743,10 +13471,10 @@ var init_cveReachability = __esm({
13743
13471
  });
13744
13472
 
13745
13473
  // cli/reachability/reachabilityScan.ts
13746
- import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
13747
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
13748
- import { join as join35 } from "path";
13749
- import { homedir as homedir35 } from "os";
13474
+ import { spawnSync as spawnSync11, execFileSync as execFileSync5 } from "child_process";
13475
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync22, existsSync as existsSync33, readdirSync as readdirSync8 } from "fs";
13476
+ import { join as join32 } from "path";
13477
+ import { homedir as homedir32 } from "os";
13750
13478
  import { createRequire } from "module";
13751
13479
  function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
13752
13480
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -13757,13 +13485,13 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
13757
13485
  const dir = stack.pop();
13758
13486
  let ents;
13759
13487
  try {
13760
- ents = readdirSync9(dir, { withFileTypes: true });
13488
+ ents = readdirSync8(dir, { withFileTypes: true });
13761
13489
  } catch {
13762
13490
  continue;
13763
13491
  }
13764
13492
  for (const e of ents) {
13765
13493
  if (files.length >= maxFiles) break;
13766
- const full = join35(dir, e.name);
13494
+ const full = join32(dir, e.name);
13767
13495
  if (e.isDirectory()) {
13768
13496
  if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
13769
13497
  continue;
@@ -13771,7 +13499,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
13771
13499
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
13772
13500
  const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
13773
13501
  try {
13774
- const content = readFileSync34(full, "utf8");
13502
+ const content = readFileSync32(full, "utf8");
13775
13503
  if (content.length <= maxBytes) files.push({ path: rel, content });
13776
13504
  } catch {
13777
13505
  }
@@ -13790,12 +13518,12 @@ function cleanVersion(spec) {
13790
13518
  function gatherManifestVersions(repoRoot2) {
13791
13519
  const out = {};
13792
13520
  const dirs = [repoRoot2];
13793
- const pkgsDir = join35(repoRoot2, "packages");
13794
- if (existsSync34(pkgsDir)) {
13521
+ const pkgsDir = join32(repoRoot2, "packages");
13522
+ if (existsSync33(pkgsDir)) {
13795
13523
  try {
13796
- for (const d of readdirSync9(pkgsDir)) {
13797
- const pd = join35(pkgsDir, d);
13798
- if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
13524
+ for (const d of readdirSync8(pkgsDir)) {
13525
+ const pd = join32(pkgsDir, d);
13526
+ if (existsSync33(join32(pd, "package.json"))) dirs.push(pd);
13799
13527
  }
13800
13528
  } catch {
13801
13529
  }
@@ -13804,7 +13532,7 @@ function gatherManifestVersions(repoRoot2) {
13804
13532
  for (const dir of dirs) {
13805
13533
  let pkg;
13806
13534
  try {
13807
- pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
13535
+ pkg = JSON.parse(readFileSync32(join32(dir, "package.json"), "utf8"));
13808
13536
  } catch {
13809
13537
  continue;
13810
13538
  }
@@ -13824,28 +13552,28 @@ function findJelly(repoRoot2) {
13824
13552
  try {
13825
13553
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
13826
13554
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
13827
- const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
13555
+ const pkg = JSON.parse(readFileSync32(pkgJson, "utf8"));
13828
13556
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
13829
13557
  if (bin) {
13830
- const p = join35(dir, bin);
13831
- if (existsSync34(p)) return p;
13558
+ const p = join32(dir, bin);
13559
+ if (existsSync33(p)) return p;
13832
13560
  }
13833
13561
  } catch {
13834
13562
  }
13835
13563
  for (const base of [repoRoot2, process.cwd()]) {
13836
- const b = join35(base, "node_modules", ".bin", "jelly");
13837
- if (existsSync34(b)) return b;
13564
+ const b = join32(base, "node_modules", ".bin", "jelly");
13565
+ if (existsSync33(b)) return b;
13838
13566
  }
13839
13567
  return null;
13840
13568
  }
13841
13569
  function findEntries(repoRoot2) {
13842
13570
  const dirs = [repoRoot2];
13843
- const pkgsDir = join35(repoRoot2, "packages");
13844
- if (existsSync34(pkgsDir)) {
13571
+ const pkgsDir = join32(repoRoot2, "packages");
13572
+ if (existsSync33(pkgsDir)) {
13845
13573
  try {
13846
- for (const d of readdirSync9(pkgsDir)) {
13847
- const pd = join35(pkgsDir, d);
13848
- if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
13574
+ for (const d of readdirSync8(pkgsDir)) {
13575
+ const pd = join32(pkgsDir, d);
13576
+ if (existsSync33(join32(pd, "package.json"))) dirs.push(pd);
13849
13577
  }
13850
13578
  } catch {
13851
13579
  }
@@ -13853,12 +13581,12 @@ function findEntries(repoRoot2) {
13853
13581
  const entries = [];
13854
13582
  for (const dir of dirs) {
13855
13583
  try {
13856
- const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
13584
+ const pkg = JSON.parse(readFileSync32(join32(dir, "package.json"), "utf8"));
13857
13585
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
13858
13586
  for (const c of cands) {
13859
13587
  if (typeof c !== "string") continue;
13860
- const f = join35(dir, c);
13861
- if (existsSync34(f)) {
13588
+ const f = join32(dir, c);
13589
+ if (existsSync33(f)) {
13862
13590
  entries.push(f);
13863
13591
  break;
13864
13592
  }
@@ -13891,9 +13619,9 @@ function parseApiUsage(log) {
13891
13619
  }
13892
13620
  function runReachabilityScan(repoRoot2, opts = {}) {
13893
13621
  const commit = currentCommit(repoRoot2);
13894
- if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
13622
+ if (!opts.force && commit && existsSync33(REACHABILITY_PATH)) {
13895
13623
  try {
13896
- const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
13624
+ const prev = JSON.parse(readFileSync32(REACHABILITY_PATH, "utf8"));
13897
13625
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
13898
13626
  } catch {
13899
13627
  }
@@ -13944,7 +13672,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
13944
13672
  if (jelly) {
13945
13673
  const entries = findEntries(repoRoot2);
13946
13674
  if (entries.length > 0) {
13947
- const r = spawnSync12(
13675
+ const r = spawnSync11(
13948
13676
  process.execPath,
13949
13677
  [jelly, "-b", repoRoot2, "--api-usage", ...entries],
13950
13678
  { encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
@@ -13982,7 +13710,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
13982
13710
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
13983
13711
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
13984
13712
  try {
13985
- writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13713
+ writeFileSync22(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13986
13714
  } catch (e) {
13987
13715
  return { ok: false, reason: "write failed: " + String(e.message || e) };
13988
13716
  }
@@ -13994,7 +13722,7 @@ var init_reachabilityScan = __esm({
13994
13722
  "use strict";
13995
13723
  init_cveReachability();
13996
13724
  require2 = createRequire(import.meta.url);
13997
- REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
13725
+ REACHABILITY_PATH = join32(homedir32(), ".synkro", "reachability.json");
13998
13726
  }
13999
13727
  });
14000
13728
 
@@ -14003,15 +13731,15 @@ var reachabilityScan_exports = {};
14003
13731
  __export(reachabilityScan_exports, {
14004
13732
  reachabilityScanCommand: () => reachabilityScanCommand
14005
13733
  });
14006
- import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
14007
- import { join as join36 } from "path";
14008
- import { homedir as homedir36 } from "os";
13734
+ import { readFileSync as readFileSync33, existsSync as existsSync34 } from "fs";
13735
+ import { join as join33 } from "path";
13736
+ import { homedir as homedir33 } from "os";
14009
13737
  import { execFileSync as execFileSync6 } from "child_process";
14010
13738
  function readConfigEnv4() {
14011
- const p = join36(SYNKRO_DIR17, "config.env");
14012
- if (!existsSync35(p)) return {};
13739
+ const p = join33(SYNKRO_DIR15, "config.env");
13740
+ if (!existsSync34(p)) return {};
14013
13741
  const out = {};
14014
- for (const line of readFileSync35(p, "utf-8").split("\n")) {
13742
+ for (const line of readFileSync33(p, "utf-8").split("\n")) {
14015
13743
  const t = line.trim();
14016
13744
  if (!t || t.startsWith("#")) continue;
14017
13745
  const eq = t.indexOf("=");
@@ -14043,11 +13771,11 @@ async function pushToCloud(cfg, repo) {
14043
13771
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
14044
13772
  let jwt2 = "";
14045
13773
  try {
14046
- jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
13774
+ jwt2 = readFileSync33(join33(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
14047
13775
  } catch {
14048
13776
  }
14049
- if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
14050
- const body = readFileSync35(REACHABILITY_PATH, "utf-8");
13777
+ if (!jwt2 || !existsSync34(REACHABILITY_PATH)) return;
13778
+ const body = readFileSync33(REACHABILITY_PATH, "utf-8");
14051
13779
  try {
14052
13780
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
14053
13781
  method: "POST",
@@ -14074,12 +13802,12 @@ async function reachabilityScanCommand(args2 = []) {
14074
13802
  const isCloud = cfg.SYNKRO_DEPLOY_LOCATION === "cloud" || cfg.SYNKRO_STORAGE_MODE === "cloud";
14075
13803
  if (isCloud) await pushToCloud(cfg, cfg.SYNKRO_CONNECTED_REPO || repoSlug(root));
14076
13804
  }
14077
- var SYNKRO_DIR17;
13805
+ var SYNKRO_DIR15;
14078
13806
  var init_reachabilityScan2 = __esm({
14079
13807
  "cli/commands/reachabilityScan.ts"() {
14080
13808
  "use strict";
14081
13809
  init_reachabilityScan();
14082
- SYNKRO_DIR17 = join36(homedir36(), ".synkro");
13810
+ SYNKRO_DIR15 = join33(homedir33(), ".synkro");
14083
13811
  }
14084
13812
  });
14085
13813
 
@@ -14209,13 +13937,13 @@ var config_exports = {};
14209
13937
  __export(config_exports, {
14210
13938
  configCommand: () => configCommand
14211
13939
  });
14212
- import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
14213
- import { join as join37 } from "path";
14214
- import { homedir as homedir37 } from "os";
13940
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync23, existsSync as existsSync35 } from "fs";
13941
+ import { join as join34 } from "path";
13942
+ import { homedir as homedir34 } from "os";
14215
13943
  function readConfigEnv5() {
14216
- if (!existsSync36(CONFIG_PATH9)) return {};
13944
+ if (!existsSync35(CONFIG_PATH9)) return {};
14217
13945
  const out = {};
14218
- for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
13946
+ for (const line of readFileSync34(CONFIG_PATH9, "utf-8").split("\n")) {
14219
13947
  const t = line.trim();
14220
13948
  if (!t || t.startsWith("#")) continue;
14221
13949
  const eq = t.indexOf("=");
@@ -14224,11 +13952,11 @@ function readConfigEnv5() {
14224
13952
  return out;
14225
13953
  }
14226
13954
  function updateConfigValue(key, value) {
14227
- if (!existsSync36(CONFIG_PATH9)) {
13955
+ if (!existsSync35(CONFIG_PATH9)) {
14228
13956
  console.error("No config found. Run `synkro install` first.");
14229
13957
  process.exit(1);
14230
13958
  }
14231
- const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
13959
+ const lines = readFileSync34(CONFIG_PATH9, "utf-8").split("\n");
14232
13960
  const pattern = new RegExp(`^${key}=`);
14233
13961
  let found = false;
14234
13962
  const updated = lines.map((line) => {
@@ -14239,7 +13967,7 @@ function updateConfigValue(key, value) {
14239
13967
  return line;
14240
13968
  });
14241
13969
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
14242
- writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
13970
+ writeFileSync23(CONFIG_PATH9, updated.join("\n"), "utf-8");
14243
13971
  }
14244
13972
  function resolveInferenceMode(cfg) {
14245
13973
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -14391,14 +14119,14 @@ To change:`);
14391
14119
  }
14392
14120
  if (inferenceValue !== "cloud") await reconcileContainer();
14393
14121
  }
14394
- var SYNKRO_DIR18, CONFIG_PATH9;
14122
+ var SYNKRO_DIR16, CONFIG_PATH9;
14395
14123
  var init_config = __esm({
14396
14124
  "cli/commands/config.ts"() {
14397
14125
  "use strict";
14398
14126
  init_stub();
14399
14127
  init_optout();
14400
- SYNKRO_DIR18 = join37(homedir37(), ".synkro");
14401
- CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
14128
+ SYNKRO_DIR16 = join34(homedir34(), ".synkro");
14129
+ CONFIG_PATH9 = join34(SYNKRO_DIR16, "config.env");
14402
14130
  }
14403
14131
  });
14404
14132
 
@@ -14587,14 +14315,14 @@ Usage:
14587
14315
  });
14588
14316
 
14589
14317
  // cli/bootstrap.js
14590
- import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
14318
+ import { readFileSync as readFileSync35, existsSync as existsSync36 } from "fs";
14591
14319
  import { resolve as resolve5 } from "path";
14592
14320
  var envCandidates = [
14593
14321
  resolve5(process.env.HOME ?? "", ".synkro", "config.env")
14594
14322
  ];
14595
14323
  for (const envPath of envCandidates) {
14596
- if (!existsSync37(envPath)) continue;
14597
- const envContent = readFileSync37(envPath, "utf-8");
14324
+ if (!existsSync36(envPath)) continue;
14325
+ const envContent = readFileSync35(envPath, "utf-8");
14598
14326
  for (const line of envContent.split("\n")) {
14599
14327
  const trimmed = line.trim();
14600
14328
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -14611,7 +14339,7 @@ var subArgs = args.slice(1);
14611
14339
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
14612
14340
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
14613
14341
  function printVersion() {
14614
- console.log("1.7.92");
14342
+ console.log("1.7.94");
14615
14343
  }
14616
14344
  function printHelp2() {
14617
14345
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
@@ -14703,79 +14431,17 @@ async function main() {
14703
14431
  await scanPrCommand2();
14704
14432
  break;
14705
14433
  }
14706
- case "route": {
14707
- const { injectModel: injectModel2 } = await Promise.resolve().then(() => (init_ptyShim(), ptyShim_exports));
14708
- const model = (subArgs[0] || "").trim();
14709
- const sessionOverride = (subArgs[1] || "").trim() || void 0;
14710
- if (model) {
14711
- try {
14712
- injectModel2(model, sessionOverride);
14713
- } catch {
14714
- }
14715
- }
14716
- break;
14717
- }
14718
- case "route-decide": {
14719
- const { routeDecide: routeDecide2 } = await Promise.resolve().then(() => (init_routeDecide(), routeDecide_exports));
14720
- const sid = (subArgs[0] || "").trim();
14721
- if (sid) {
14722
- try {
14723
- await routeDecide2(sid);
14724
- } catch {
14725
- }
14726
- }
14727
- break;
14728
- }
14729
- case "route-and-resubmit": {
14730
- const { routeAndResubmit: routeAndResubmit2 } = await Promise.resolve().then(() => (init_routeOrchestrate(), routeOrchestrate_exports));
14731
- const sid = (subArgs[0] || "").trim();
14732
- const task = (subArgs[1] || "").trim();
14733
- const tmuxSession = (subArgs[2] || "").trim() || void 0;
14734
- const forceModel = (subArgs[3] || "").trim() || void 0;
14735
- if (sid && task) {
14736
- try {
14737
- await routeAndResubmit2(sid, task, tmuxSession, forceModel);
14738
- } catch {
14739
- }
14740
- }
14741
- break;
14742
- }
14743
- case "routing": {
14744
- const { routingCommand: routingCommand2 } = await Promise.resolve().then(() => (init_routingToggle(), routingToggle_exports));
14745
- routingCommand2(subArgs);
14746
- break;
14747
- }
14434
+ case "route":
14435
+ case "route-decide":
14436
+ case "route-and-resubmit":
14437
+ case "routing":
14748
14438
  case "sessions": {
14749
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_ptyShim(), ptyShim_exports));
14750
- const rows = listSessions2({ liveOnly: !subArgs.includes("--all") });
14751
- if (rows.length === 0) {
14752
- console.log("No active Synkro-wrapped Claude Code sessions.");
14753
- console.log("(Start one with `claude`; it registers on your first prompt.)");
14754
- break;
14755
- }
14756
- const now = Date.now();
14757
- const age = (ts) => {
14758
- const s = Math.max(0, Math.round((now - (ts || 0)) / 1e3));
14759
- if (s < 60) return `${s}s`;
14760
- if (s < 3600) return `${Math.round(s / 60)}m`;
14761
- return `${Math.round(s / 3600)}h`;
14762
- };
14763
- console.log(`${rows.length} session${rows.length === 1 ? "" : "s"}:
14764
- `);
14765
- for (const r of rows) {
14766
- console.log(` ${r.session_id} (${age(r.ts)} ago)`);
14767
- if (r.cwd) console.log(` cwd: ${r.cwd}`);
14768
- if (r.last_prompt) console.log(` prompt: ${r.last_prompt.replace(/\s+/g, " ").slice(0, 80)}`);
14769
- console.log(` route: echo <model> > ~/.synkro/pty/route-${r.session_id}`);
14770
- console.log();
14771
- }
14439
+ console.error("Synkro host model routing has been removed.");
14772
14440
  break;
14773
14441
  }
14774
14442
  case "pty-shim": {
14775
- const { installPtyShim: installPtyShim2, uninstallPtyShim: uninstallPtyShim2 } = await Promise.resolve().then(() => (init_ptyShim(), ptyShim_exports));
14776
- const sub = (subArgs[0] || "install").trim();
14777
- if (sub === "uninstall" || sub === "remove") uninstallPtyShim2();
14778
- else installPtyShim2();
14443
+ const { uninstallPtyShim: uninstallPtyShim2 } = await Promise.resolve().then(() => (init_ptyShim(), ptyShim_exports));
14444
+ uninstallPtyShim2();
14779
14445
  break;
14780
14446
  }
14781
14447
  case "local-cc": {