@synkro-sh/cli 1.7.86 → 1.7.87
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 +745 -545
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
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.
|
|
150
|
+
cliVersion = "1.7.87";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -2500,7 +2500,7 @@ function telemCreds(): TelemCreds {
|
|
|
2500
2500
|
}
|
|
2501
2501
|
|
|
2502
2502
|
interface HookEmitOpts {
|
|
2503
|
-
agent_kind?: 'claude_code' | 'cursor';
|
|
2503
|
+
agent_kind?: 'claude_code' | 'cursor' | 'codex';
|
|
2504
2504
|
cc_session_id?: string;
|
|
2505
2505
|
cc_tool_use_id?: string;
|
|
2506
2506
|
cc_model?: string;
|
|
@@ -5490,19 +5490,24 @@ import { join as join14 } from "path";
|
|
|
5490
5490
|
import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
|
|
5491
5491
|
function splitWorkers(total, providers) {
|
|
5492
5492
|
const t = Math.max(0, Math.floor(total));
|
|
5493
|
-
const
|
|
5494
|
-
const
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5493
|
+
const selected = ["claude_code", "cursor", "codex"].filter((provider) => providers.includes(provider));
|
|
5494
|
+
const active = selected.length > 0 ? selected : ["claude_code"];
|
|
5495
|
+
const base = Math.floor(t / active.length);
|
|
5496
|
+
let remainder = t % active.length;
|
|
5497
|
+
const counts = { claudeWorkers: 0, cursorWorkers: 0, codexWorkers: 0 };
|
|
5498
|
+
for (const provider of active) {
|
|
5499
|
+
const count = base + (remainder-- > 0 ? 1 : 0);
|
|
5500
|
+
if (provider === "claude_code") counts.claudeWorkers = count;
|
|
5501
|
+
else if (provider === "cursor") counts.cursorWorkers = count;
|
|
5502
|
+
else counts.codexWorkers = count;
|
|
5503
|
+
}
|
|
5504
|
+
return counts;
|
|
5501
5505
|
}
|
|
5502
5506
|
function normalizeProvider(p) {
|
|
5503
5507
|
const v = p.trim().toLowerCase();
|
|
5504
5508
|
if (v === "claude" || v === "claude-code" || v === "claude_code" || v === "cc") return "claude_code";
|
|
5505
5509
|
if (v === "cursor") return "cursor";
|
|
5510
|
+
if (v === "codex" || v === "openai-codex" || v === "openai_codex") return "codex";
|
|
5506
5511
|
return null;
|
|
5507
5512
|
}
|
|
5508
5513
|
function resolveGraderPool(parsed) {
|
|
@@ -5516,8 +5521,9 @@ function resolveGraderPool(parsed) {
|
|
|
5516
5521
|
const np = normalizeProvider(k);
|
|
5517
5522
|
if (np === "claude_code") counts.claude_code = Math.max(0, Math.floor(v));
|
|
5518
5523
|
else if (np === "cursor") counts.cursor = Math.max(0, Math.floor(v));
|
|
5524
|
+
else if (np === "codex") counts.codex = Math.max(0, Math.floor(v));
|
|
5519
5525
|
else if (flagUnknown) {
|
|
5520
|
-
warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code or
|
|
5526
|
+
warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code, cursor, or codex) \u2014 ignored`);
|
|
5521
5527
|
}
|
|
5522
5528
|
}
|
|
5523
5529
|
};
|
|
@@ -5528,10 +5534,10 @@ function resolveGraderPool(parsed) {
|
|
|
5528
5534
|
if (rawPool != null && rawPool !== "auto") {
|
|
5529
5535
|
const np = normalizeProvider(String(rawPool));
|
|
5530
5536
|
if (np) pool = np;
|
|
5531
|
-
else warnings.push(`synkro.toml: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor) \u2014 falling back to auto`);
|
|
5537
|
+
else warnings.push(`synkro.toml: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor | codex) \u2014 falling back to auto`);
|
|
5532
5538
|
}
|
|
5533
|
-
if (counts.claude_code != null || counts.cursor != null) {
|
|
5534
|
-
return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };
|
|
5539
|
+
if (counts.claude_code != null || counts.cursor != null || counts.codex != null) {
|
|
5540
|
+
return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, codexWorkers: counts.codex, pool, warnings };
|
|
5535
5541
|
}
|
|
5536
5542
|
return { pool, warnings };
|
|
5537
5543
|
}
|
|
@@ -5624,15 +5630,18 @@ function resolveWorkerConfig(rest) {
|
|
|
5624
5630
|
if (provs.length === 0) {
|
|
5625
5631
|
const sc = readSynkroFileConfig();
|
|
5626
5632
|
for (const w of sc.warnings) console.warn(` \u26A0 ${w}`);
|
|
5627
|
-
if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
|
|
5633
|
+
if (sc.claudeWorkers != null || sc.cursorWorkers != null || sc.codexWorkers != null) {
|
|
5628
5634
|
const cw = sc.claudeWorkers || 0;
|
|
5629
5635
|
const curw = sc.cursorWorkers || 0;
|
|
5630
|
-
|
|
5636
|
+
const codw = sc.codexWorkers || 0;
|
|
5637
|
+
if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw, explicit };
|
|
5631
5638
|
}
|
|
5632
5639
|
if (sc.pool === "cursor") {
|
|
5633
5640
|
provs = ["cursor"];
|
|
5634
5641
|
} else if (sc.pool === "claude_code") {
|
|
5635
5642
|
provs = ["claude_code"];
|
|
5643
|
+
} else if (sc.pool === "codex") {
|
|
5644
|
+
provs = ["codex"];
|
|
5636
5645
|
} else {
|
|
5637
5646
|
provs = detectAgents().map((a) => a.kind);
|
|
5638
5647
|
if (provs.length === 0) provs = ["claude_code"];
|
|
@@ -5710,7 +5719,14 @@ async function dockerInstall(opts = {}) {
|
|
|
5710
5719
|
const image = imageTag();
|
|
5711
5720
|
const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;
|
|
5712
5721
|
const cursorWorkers = opts.cursorWorkers ?? 0;
|
|
5713
|
-
const
|
|
5722
|
+
const codexWorkers = opts.codexWorkers ?? 0;
|
|
5723
|
+
const totalWorkers = claudeWorkers + cursorWorkers + codexWorkers;
|
|
5724
|
+
const codexHomeDir = opts.codexHomeDir ?? join14(SYNKRO_DIR7, "codex-local-session");
|
|
5725
|
+
if (codexWorkers > 0 && !existsSync19(join14(codexHomeDir, "auth.json"))) {
|
|
5726
|
+
throw new DockerInstallError(
|
|
5727
|
+
"Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
|
|
5728
|
+
);
|
|
5729
|
+
}
|
|
5714
5730
|
mkdirSync12(PGDATA_PATH, { recursive: true });
|
|
5715
5731
|
mkdirSync12(BACKUP_DIR, { recursive: true });
|
|
5716
5732
|
mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
|
|
@@ -5806,12 +5822,15 @@ async function dockerInstall(opts = {}) {
|
|
|
5806
5822
|
// Cursor creds — mounted RW so the in-container refresher can rotate the
|
|
5807
5823
|
// access token in place. Only mounted when the install includes Cursor.
|
|
5808
5824
|
...cursorWorkers > 0 ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
|
|
5825
|
+
...codexWorkers > 0 ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
|
|
5809
5826
|
"-e",
|
|
5810
5827
|
`WORKERS_PER_POOL=${totalWorkers}`,
|
|
5811
5828
|
"-e",
|
|
5812
5829
|
`CLAUDE_WORKERS=${claudeWorkers}`,
|
|
5813
5830
|
"-e",
|
|
5814
5831
|
`CURSOR_WORKERS=${cursorWorkers}`,
|
|
5832
|
+
"-e",
|
|
5833
|
+
`CODEX_WORKERS=${codexWorkers}`,
|
|
5815
5834
|
// Pass through the batch-size lever if the operator set it. Defaults
|
|
5816
5835
|
// inside the container to 5; clamped to [1, 20] by synkro-server.ts.
|
|
5817
5836
|
...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
|
|
@@ -5929,6 +5948,7 @@ function readContainerConfig() {
|
|
|
5929
5948
|
return {
|
|
5930
5949
|
claudeWorkers: num(get("CLAUDE_WORKERS")),
|
|
5931
5950
|
cursorWorkers: num(get("CURSOR_WORKERS")),
|
|
5951
|
+
codexWorkers: num(get("CODEX_WORKERS")),
|
|
5932
5952
|
connectedRepo: get("SYNKRO_CONNECTED_REPO") || void 0
|
|
5933
5953
|
};
|
|
5934
5954
|
}
|
|
@@ -6165,6 +6185,119 @@ var init_setupToken = __esm({
|
|
|
6165
6185
|
}
|
|
6166
6186
|
});
|
|
6167
6187
|
|
|
6188
|
+
// cli/local-cc/codexCloudSetup.ts
|
|
6189
|
+
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
6190
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
|
|
6191
|
+
import { homedir as homedir18 } from "os";
|
|
6192
|
+
import { join as join16 } from "path";
|
|
6193
|
+
function findCodexBinary() {
|
|
6194
|
+
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
6195
|
+
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
6196
|
+
const p = (r.stdout || "").trim().split("\n")[0];
|
|
6197
|
+
return p || null;
|
|
6198
|
+
}
|
|
6199
|
+
function runCodexLogin(codexBin, codexHome) {
|
|
6200
|
+
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
6201
|
+
writeFileSync14(join16(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
6202
|
+
return new Promise((resolve6, reject) => {
|
|
6203
|
+
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
6204
|
+
stdio: "inherit",
|
|
6205
|
+
env: { ...process.env, CODEX_HOME: codexHome }
|
|
6206
|
+
});
|
|
6207
|
+
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
6208
|
+
proc.on("close", (code) => code === 0 ? resolve6() : reject(new Error(`codex login exited with code ${code}`)));
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6211
|
+
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
6212
|
+
try {
|
|
6213
|
+
const status = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/status`, {
|
|
6214
|
+
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
6215
|
+
});
|
|
6216
|
+
const body = await status.json().catch(() => ({}));
|
|
6217
|
+
if (status.ok && body.connected) {
|
|
6218
|
+
onStatus?.("\u2713 Existing Codex cloud session is connected.");
|
|
6219
|
+
return { ok: true };
|
|
6220
|
+
}
|
|
6221
|
+
} catch {
|
|
6222
|
+
}
|
|
6223
|
+
const codexBin = findCodexBinary();
|
|
6224
|
+
if (!codexBin) {
|
|
6225
|
+
return { ok: false, error: "Codex CLI not found on PATH. Install Codex (https://developers.openai.com/codex), then re-run `synkro install`. (Set SYNKRO_CODEX_BIN to override the binary path.)" };
|
|
6226
|
+
}
|
|
6227
|
+
onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
|
|
6228
|
+
try {
|
|
6229
|
+
await runCodexLogin(codexBin, CODEX_CLOUD_HOME);
|
|
6230
|
+
} catch (e) {
|
|
6231
|
+
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
6232
|
+
}
|
|
6233
|
+
const authPath = join16(CODEX_CLOUD_HOME, "auth.json");
|
|
6234
|
+
if (!existsSync20(authPath)) {
|
|
6235
|
+
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
6236
|
+
}
|
|
6237
|
+
let auth;
|
|
6238
|
+
try {
|
|
6239
|
+
auth = JSON.parse(readFileSync18(authPath, "utf-8"));
|
|
6240
|
+
} catch (e) {
|
|
6241
|
+
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
6242
|
+
}
|
|
6243
|
+
if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
|
|
6244
|
+
return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
|
|
6245
|
+
}
|
|
6246
|
+
onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
|
|
6247
|
+
let resp;
|
|
6248
|
+
try {
|
|
6249
|
+
resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
|
|
6250
|
+
method: "POST",
|
|
6251
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
|
|
6252
|
+
body: JSON.stringify({ auth })
|
|
6253
|
+
});
|
|
6254
|
+
} catch (e) {
|
|
6255
|
+
return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
|
|
6256
|
+
}
|
|
6257
|
+
if (!resp.ok) {
|
|
6258
|
+
const detail = await resp.text().catch(() => "");
|
|
6259
|
+
return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
|
|
6260
|
+
}
|
|
6261
|
+
try {
|
|
6262
|
+
rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
|
|
6263
|
+
} catch {
|
|
6264
|
+
}
|
|
6265
|
+
onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
|
|
6266
|
+
return { ok: true };
|
|
6267
|
+
}
|
|
6268
|
+
async function setupCodexLocal(onStatus) {
|
|
6269
|
+
const codexBin = findCodexBinary();
|
|
6270
|
+
if (!codexBin) {
|
|
6271
|
+
return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
|
|
6272
|
+
}
|
|
6273
|
+
const authPath = join16(CODEX_LOCAL_HOME, "auth.json");
|
|
6274
|
+
if (!existsSync20(authPath)) {
|
|
6275
|
+
onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
|
|
6276
|
+
try {
|
|
6277
|
+
await runCodexLogin(codexBin, CODEX_LOCAL_HOME);
|
|
6278
|
+
} catch (e) {
|
|
6279
|
+
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
try {
|
|
6283
|
+
const auth = JSON.parse(readFileSync18(authPath, "utf-8"));
|
|
6284
|
+
if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
|
|
6285
|
+
} catch (e) {
|
|
6286
|
+
return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
|
|
6287
|
+
}
|
|
6288
|
+
onStatus?.("\u2713 Codex session ready for the local grader.");
|
|
6289
|
+
return { ok: true, home: CODEX_LOCAL_HOME };
|
|
6290
|
+
}
|
|
6291
|
+
var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
|
|
6292
|
+
var init_codexCloudSetup = __esm({
|
|
6293
|
+
"cli/local-cc/codexCloudSetup.ts"() {
|
|
6294
|
+
"use strict";
|
|
6295
|
+
SYNKRO_DIR9 = join16(homedir18(), ".synkro");
|
|
6296
|
+
CODEX_CLOUD_HOME = join16(SYNKRO_DIR9, "codex-cloud-session");
|
|
6297
|
+
CODEX_LOCAL_HOME = join16(SYNKRO_DIR9, "codex-local-session");
|
|
6298
|
+
}
|
|
6299
|
+
});
|
|
6300
|
+
|
|
6168
6301
|
// cli/local-cc/ptyShim.ts
|
|
6169
6302
|
var ptyShim_exports = {};
|
|
6170
6303
|
__export(ptyShim_exports, {
|
|
@@ -6181,26 +6314,26 @@ __export(ptyShim_exports, {
|
|
|
6181
6314
|
uninstallPtyShim: () => uninstallPtyShim
|
|
6182
6315
|
});
|
|
6183
6316
|
import {
|
|
6184
|
-
existsSync as
|
|
6185
|
-
mkdirSync as
|
|
6186
|
-
writeFileSync as
|
|
6317
|
+
existsSync as existsSync21,
|
|
6318
|
+
mkdirSync as mkdirSync14,
|
|
6319
|
+
writeFileSync as writeFileSync15,
|
|
6187
6320
|
chmodSync as chmodSync3,
|
|
6188
|
-
readFileSync as
|
|
6189
|
-
rmSync,
|
|
6321
|
+
readFileSync as readFileSync19,
|
|
6322
|
+
rmSync as rmSync2,
|
|
6190
6323
|
realpathSync,
|
|
6191
6324
|
symlinkSync,
|
|
6192
6325
|
lstatSync,
|
|
6193
6326
|
readdirSync as readdirSync3
|
|
6194
6327
|
} from "fs";
|
|
6195
|
-
import { homedir as
|
|
6196
|
-
import { join as
|
|
6197
|
-
import { spawnSync as
|
|
6328
|
+
import { homedir as homedir19 } from "os";
|
|
6329
|
+
import { join as join17 } from "path";
|
|
6330
|
+
import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
|
|
6198
6331
|
function rcFiles() {
|
|
6199
|
-
const h =
|
|
6200
|
-
return [
|
|
6332
|
+
const h = homedir19();
|
|
6333
|
+
return [join17(h, ".zshrc"), join17(h, ".bashrc"), join17(h, ".bash_profile")];
|
|
6201
6334
|
}
|
|
6202
6335
|
function resolveRealClaude() {
|
|
6203
|
-
const r =
|
|
6336
|
+
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
6204
6337
|
const p = (r.stdout || "").trim();
|
|
6205
6338
|
if (p) {
|
|
6206
6339
|
try {
|
|
@@ -6209,8 +6342,8 @@ function resolveRealClaude() {
|
|
|
6209
6342
|
return p;
|
|
6210
6343
|
}
|
|
6211
6344
|
}
|
|
6212
|
-
for (const c of [
|
|
6213
|
-
if (
|
|
6345
|
+
for (const c of [join17(homedir19(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
|
|
6346
|
+
if (existsSync21(c)) {
|
|
6214
6347
|
try {
|
|
6215
6348
|
return realpathSync(c);
|
|
6216
6349
|
} catch {
|
|
@@ -6221,11 +6354,11 @@ function resolveRealClaude() {
|
|
|
6221
6354
|
return "claude";
|
|
6222
6355
|
}
|
|
6223
6356
|
function findClaudeLink() {
|
|
6224
|
-
const r =
|
|
6357
|
+
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
6225
6358
|
let linkPath = (r.stdout || "").trim();
|
|
6226
6359
|
if (!linkPath) {
|
|
6227
|
-
const c =
|
|
6228
|
-
if (
|
|
6360
|
+
const c = join17(homedir19(), ".local", "bin", "claude");
|
|
6361
|
+
if (existsSync21(c)) linkPath = c;
|
|
6229
6362
|
else return null;
|
|
6230
6363
|
}
|
|
6231
6364
|
if (linkPath === SHIM_PATH) return null;
|
|
@@ -6238,7 +6371,7 @@ function findClaudeLink() {
|
|
|
6238
6371
|
}
|
|
6239
6372
|
function isOurShim(path) {
|
|
6240
6373
|
try {
|
|
6241
|
-
return
|
|
6374
|
+
return readFileSync19(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
|
|
6242
6375
|
} catch {
|
|
6243
6376
|
return false;
|
|
6244
6377
|
}
|
|
@@ -6251,7 +6384,7 @@ function shadowClaude() {
|
|
|
6251
6384
|
}
|
|
6252
6385
|
const { linkPath, realTarget } = found;
|
|
6253
6386
|
if (isOurShim(linkPath)) {
|
|
6254
|
-
console.log(` \xB7 ${linkPath.replace(
|
|
6387
|
+
console.log(` \xB7 ${linkPath.replace(homedir19(), "~")} already shadowed`);
|
|
6255
6388
|
return;
|
|
6256
6389
|
}
|
|
6257
6390
|
let wasSymlink = false;
|
|
@@ -6261,14 +6394,14 @@ function shadowClaude() {
|
|
|
6261
6394
|
}
|
|
6262
6395
|
const state = { linkPath, realTarget, wasSymlink };
|
|
6263
6396
|
try {
|
|
6264
|
-
|
|
6397
|
+
writeFileSync15(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
|
|
6265
6398
|
} catch {
|
|
6266
6399
|
}
|
|
6267
6400
|
try {
|
|
6268
|
-
|
|
6269
|
-
|
|
6401
|
+
rmSync2(linkPath, { force: true });
|
|
6402
|
+
writeFileSync15(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
|
|
6270
6403
|
chmodSync3(linkPath, 493);
|
|
6271
|
-
console.log(` \u2713 shadowed ${linkPath.replace(
|
|
6404
|
+
console.log(` \u2713 shadowed ${linkPath.replace(homedir19(), "~")} \u2192 shim (real: ${realTarget.replace(homedir19(), "~")})`);
|
|
6272
6405
|
} catch (e) {
|
|
6273
6406
|
console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
|
|
6274
6407
|
}
|
|
@@ -6276,16 +6409,16 @@ function shadowClaude() {
|
|
|
6276
6409
|
function unshadowClaude() {
|
|
6277
6410
|
let state;
|
|
6278
6411
|
try {
|
|
6279
|
-
state = JSON.parse(
|
|
6412
|
+
state = JSON.parse(readFileSync19(SHADOW_STATE_FILE, "utf-8"));
|
|
6280
6413
|
} catch {
|
|
6281
6414
|
return;
|
|
6282
6415
|
}
|
|
6283
6416
|
if (!state?.linkPath) return;
|
|
6284
6417
|
try {
|
|
6285
|
-
if (
|
|
6286
|
-
|
|
6418
|
+
if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
|
|
6419
|
+
rmSync2(state.linkPath, { force: true });
|
|
6287
6420
|
symlinkSync(state.realTarget, state.linkPath);
|
|
6288
|
-
console.log(`\u2713 restored ${state.linkPath.replace(
|
|
6421
|
+
console.log(`\u2713 restored ${state.linkPath.replace(homedir19(), "~")} \u2192 ${state.realTarget.replace(homedir19(), "~")}`);
|
|
6289
6422
|
} catch (e) {
|
|
6290
6423
|
console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
|
|
6291
6424
|
}
|
|
@@ -6293,16 +6426,16 @@ function unshadowClaude() {
|
|
|
6293
6426
|
function addPathBlock() {
|
|
6294
6427
|
const touched = [];
|
|
6295
6428
|
for (const rc of rcFiles()) {
|
|
6296
|
-
if (!
|
|
6429
|
+
if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
|
|
6297
6430
|
let body = "";
|
|
6298
6431
|
try {
|
|
6299
|
-
body =
|
|
6432
|
+
body = readFileSync19(rc, "utf-8");
|
|
6300
6433
|
} catch {
|
|
6301
6434
|
}
|
|
6302
6435
|
const cleaned = stripBlock(body);
|
|
6303
6436
|
const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
|
|
6304
6437
|
try {
|
|
6305
|
-
|
|
6438
|
+
writeFileSync15(rc, next, "utf-8");
|
|
6306
6439
|
touched.push(rc);
|
|
6307
6440
|
} catch {
|
|
6308
6441
|
}
|
|
@@ -6318,15 +6451,15 @@ function escapeRe(s) {
|
|
|
6318
6451
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6319
6452
|
}
|
|
6320
6453
|
function installPtyShim() {
|
|
6321
|
-
|
|
6322
|
-
|
|
6454
|
+
mkdirSync14(SHIM_BIN_DIR, { recursive: true });
|
|
6455
|
+
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6323
6456
|
const real = resolveRealClaude();
|
|
6324
|
-
|
|
6457
|
+
writeFileSync15(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
|
|
6325
6458
|
chmodSync3(SHIM_PATH, 493);
|
|
6326
6459
|
const touched = addPathBlock();
|
|
6327
6460
|
console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
|
|
6328
6461
|
shadowClaude();
|
|
6329
|
-
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(
|
|
6462
|
+
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir19(), "~")).join(", ")}`);
|
|
6330
6463
|
}
|
|
6331
6464
|
function uninstallPtyShim() {
|
|
6332
6465
|
try {
|
|
@@ -6334,30 +6467,30 @@ function uninstallPtyShim() {
|
|
|
6334
6467
|
} catch {
|
|
6335
6468
|
}
|
|
6336
6469
|
try {
|
|
6337
|
-
const ls =
|
|
6470
|
+
const ls = spawnSync5("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
|
|
6338
6471
|
for (const s of (ls.stdout || "").split("\n")) {
|
|
6339
|
-
if (s.startsWith(SHIM_SESSION_PREFIX))
|
|
6472
|
+
if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync5("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
|
|
6340
6473
|
}
|
|
6341
6474
|
} catch {
|
|
6342
6475
|
}
|
|
6343
6476
|
const cleaned = [];
|
|
6344
6477
|
for (const rc of rcFiles()) {
|
|
6345
|
-
if (!
|
|
6478
|
+
if (!existsSync21(rc)) continue;
|
|
6346
6479
|
try {
|
|
6347
|
-
const body =
|
|
6480
|
+
const body = readFileSync19(rc, "utf-8");
|
|
6348
6481
|
if (body.includes(RC_BEGIN)) {
|
|
6349
|
-
|
|
6350
|
-
cleaned.push(rc.replace(
|
|
6482
|
+
writeFileSync15(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
6483
|
+
cleaned.push(rc.replace(homedir19(), "~"));
|
|
6351
6484
|
}
|
|
6352
6485
|
} catch {
|
|
6353
6486
|
}
|
|
6354
6487
|
}
|
|
6355
6488
|
try {
|
|
6356
|
-
|
|
6489
|
+
rmSync2(SHIM_BIN_DIR, { recursive: true, force: true });
|
|
6357
6490
|
} catch {
|
|
6358
6491
|
}
|
|
6359
6492
|
try {
|
|
6360
|
-
|
|
6493
|
+
rmSync2(PTY_STATE_DIR, { recursive: true, force: true });
|
|
6361
6494
|
} catch {
|
|
6362
6495
|
}
|
|
6363
6496
|
console.log(`\u2713 removed pty routing shim${cleaned.length ? ` (cleaned PATH in: ${cleaned.join(", ")})` : ""}`);
|
|
@@ -6379,12 +6512,12 @@ function listSessions(opts = {}) {
|
|
|
6379
6512
|
const out = [];
|
|
6380
6513
|
for (const f of files) {
|
|
6381
6514
|
try {
|
|
6382
|
-
const r = JSON.parse(
|
|
6515
|
+
const r = JSON.parse(readFileSync19(join17(SESSIONS_DIR, f), "utf-8"));
|
|
6383
6516
|
if (!r || !r.session_id) continue;
|
|
6384
6517
|
if (liveOnly) {
|
|
6385
6518
|
const s = r.tmux_session;
|
|
6386
6519
|
if (!s || !isOurSession(s)) continue;
|
|
6387
|
-
if (
|
|
6520
|
+
if (spawnSync5("tmux", ["has-session", "-t", `=${s}`], { encoding: "utf-8" }).status !== 0) continue;
|
|
6388
6521
|
}
|
|
6389
6522
|
out.push(r);
|
|
6390
6523
|
} catch {
|
|
@@ -6398,7 +6531,7 @@ function currentTmuxSession() {
|
|
|
6398
6531
|
try {
|
|
6399
6532
|
const pane = process.env.TMUX_PANE || "";
|
|
6400
6533
|
const args2 = pane ? ["display-message", "-p", "-t", pane, "#{session_name}"] : ["display-message", "-p", "#{session_name}"];
|
|
6401
|
-
const r =
|
|
6534
|
+
const r = spawnSync5("tmux", args2, { encoding: "utf-8" });
|
|
6402
6535
|
if (r.status === 0) return (r.stdout || "").trim();
|
|
6403
6536
|
} catch {
|
|
6404
6537
|
}
|
|
@@ -6408,7 +6541,7 @@ function resolveTargetSession(override) {
|
|
|
6408
6541
|
const own = currentTmuxSession();
|
|
6409
6542
|
const fromFile = (() => {
|
|
6410
6543
|
try {
|
|
6411
|
-
return
|
|
6544
|
+
return readFileSync19(ACTIVE_SESSION_FILE, "utf-8").trim();
|
|
6412
6545
|
} catch {
|
|
6413
6546
|
return "";
|
|
6414
6547
|
}
|
|
@@ -6429,14 +6562,14 @@ function injectModel(model, sessionOverride) {
|
|
|
6429
6562
|
console.error(`synkro route: refusing suspicious session token: ${session}`);
|
|
6430
6563
|
return false;
|
|
6431
6564
|
}
|
|
6432
|
-
if (
|
|
6433
|
-
const sendKeys = (...a) =>
|
|
6565
|
+
if (spawnSync5("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return false;
|
|
6566
|
+
const sendKeys = (...a) => spawnSync5("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
|
|
6434
6567
|
sendKeys("Escape");
|
|
6435
6568
|
sendKeys("-l", `/model ${model}`);
|
|
6436
6569
|
sendKeys("Enter");
|
|
6437
|
-
const pidFile =
|
|
6570
|
+
const pidFile = join17(PTY_STATE_DIR, `poll-${session}.pid`);
|
|
6438
6571
|
try {
|
|
6439
|
-
|
|
6572
|
+
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6440
6573
|
} catch {
|
|
6441
6574
|
}
|
|
6442
6575
|
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`;
|
|
@@ -6444,17 +6577,17 @@ function injectModel(model, sessionOverride) {
|
|
|
6444
6577
|
child.unref();
|
|
6445
6578
|
return true;
|
|
6446
6579
|
}
|
|
6447
|
-
var
|
|
6580
|
+
var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, SHADOW_STATE_FILE, SESSIONS_DIR, SHIM_SESSION_PREFIX, RC_BEGIN, RC_END, RC_BLOCK, SHIM_SOURCE;
|
|
6448
6581
|
var init_ptyShim = __esm({
|
|
6449
6582
|
"cli/local-cc/ptyShim.ts"() {
|
|
6450
6583
|
"use strict";
|
|
6451
|
-
|
|
6452
|
-
SHIM_BIN_DIR =
|
|
6453
|
-
SHIM_PATH =
|
|
6454
|
-
PTY_STATE_DIR =
|
|
6455
|
-
ACTIVE_SESSION_FILE =
|
|
6456
|
-
SHADOW_STATE_FILE =
|
|
6457
|
-
SESSIONS_DIR =
|
|
6584
|
+
SYNKRO_DIR10 = join17(homedir19(), ".synkro");
|
|
6585
|
+
SHIM_BIN_DIR = join17(SYNKRO_DIR10, "bin");
|
|
6586
|
+
SHIM_PATH = join17(SHIM_BIN_DIR, "claude");
|
|
6587
|
+
PTY_STATE_DIR = join17(SYNKRO_DIR10, "pty");
|
|
6588
|
+
ACTIVE_SESSION_FILE = join17(PTY_STATE_DIR, "active");
|
|
6589
|
+
SHADOW_STATE_FILE = join17(PTY_STATE_DIR, "shadow.json");
|
|
6590
|
+
SESSIONS_DIR = join17(PTY_STATE_DIR, "sessions");
|
|
6458
6591
|
SHIM_SESSION_PREFIX = "synkro-cc-";
|
|
6459
6592
|
RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
|
|
6460
6593
|
RC_END = "# <<< synkro pty shim <<<";
|
|
@@ -6527,9 +6660,9 @@ __export(install_exports, {
|
|
|
6527
6660
|
syncSkillFiles: () => syncSkillFiles,
|
|
6528
6661
|
writeHookScripts: () => writeHookScripts
|
|
6529
6662
|
});
|
|
6530
|
-
import { existsSync as
|
|
6531
|
-
import { homedir as
|
|
6532
|
-
import { join as
|
|
6663
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, chmodSync as chmodSync4, readFileSync as readFileSync20, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
|
|
6664
|
+
import { homedir as homedir20 } from "os";
|
|
6665
|
+
import { join as join18, isAbsolute, resolve as resolve4 } from "path";
|
|
6533
6666
|
import { execSync as execSync4, spawn as spawn4 } from "child_process";
|
|
6534
6667
|
import { createInterface as createInterface2 } from "readline";
|
|
6535
6668
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -6625,7 +6758,7 @@ async function promptDeployLocation(current = "local") {
|
|
|
6625
6758
|
`Where should Synkro run?
|
|
6626
6759
|
local \u2014 a grading container on this machine (Docker)
|
|
6627
6760
|
cloud \u2014 a private container hosted by Synkro
|
|
6628
|
-
|
|
6761
|
+
Each worker uses the account credentials you authorize. Choose [${current}] / ${other}: `,
|
|
6629
6762
|
(answer) => {
|
|
6630
6763
|
rl.close();
|
|
6631
6764
|
const a = answer.trim().toLowerCase();
|
|
@@ -6656,38 +6789,38 @@ async function promptTranscriptSources(wantCC, wantCursor, wantCodex) {
|
|
|
6656
6789
|
}
|
|
6657
6790
|
}
|
|
6658
6791
|
function ensureSynkroDir() {
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6792
|
+
mkdirSync15(SYNKRO_DIR11, { recursive: true });
|
|
6793
|
+
mkdirSync15(HOOKS_DIR, { recursive: true });
|
|
6794
|
+
mkdirSync15(BIN_DIR, { recursive: true });
|
|
6795
|
+
mkdirSync15(OFFSETS_DIR, { recursive: true });
|
|
6796
|
+
mkdirSync15(join18(SYNKRO_DIR11, "sessions"), { recursive: true });
|
|
6664
6797
|
}
|
|
6665
6798
|
function writeHookScripts() {
|
|
6666
|
-
const installExtractCorePath =
|
|
6667
|
-
const bashScriptPath =
|
|
6668
|
-
const skillJudgeScriptPath =
|
|
6669
|
-
const cursorSkillJudgePath =
|
|
6670
|
-
const bashFollowupScriptPath =
|
|
6671
|
-
const editPrecheckScriptPath =
|
|
6672
|
-
const cwePrecheckScriptPath =
|
|
6673
|
-
const cvePrecheckScriptPath =
|
|
6674
|
-
const planJudgeScriptPath =
|
|
6675
|
-
const agentJudgeScriptPath =
|
|
6676
|
-
const stopSummaryScriptPath =
|
|
6677
|
-
const sessionStartScriptPath =
|
|
6678
|
-
const transcriptSyncScriptPath =
|
|
6679
|
-
const userPromptSubmitScriptPath =
|
|
6680
|
-
const promptRouteScriptPath =
|
|
6681
|
-
const commonScriptPath =
|
|
6682
|
-
const commonBashScriptPath =
|
|
6683
|
-
const installScanScriptPath =
|
|
6684
|
-
const cursorBashJudgePath =
|
|
6685
|
-
const cursorEditCapturePath =
|
|
6686
|
-
const cursorAgentCapturePath =
|
|
6687
|
-
const mcpStdioProxyPath =
|
|
6688
|
-
const taskActivateIntentScriptPath =
|
|
6689
|
-
const mcpGateScriptPath =
|
|
6690
|
-
const stubCommonPath =
|
|
6799
|
+
const installExtractCorePath = join18(HOOKS_DIR, "installExtractCore.ts");
|
|
6800
|
+
const bashScriptPath = join18(HOOKS_DIR, "cc-bash-judge.ts");
|
|
6801
|
+
const skillJudgeScriptPath = join18(HOOKS_DIR, "cc-skill-judge.ts");
|
|
6802
|
+
const cursorSkillJudgePath = join18(HOOKS_DIR, "cursor-skill-judge.ts");
|
|
6803
|
+
const bashFollowupScriptPath = join18(HOOKS_DIR, "cc-bash-followup.ts");
|
|
6804
|
+
const editPrecheckScriptPath = join18(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
6805
|
+
const cwePrecheckScriptPath = join18(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
6806
|
+
const cvePrecheckScriptPath = join18(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
6807
|
+
const planJudgeScriptPath = join18(HOOKS_DIR, "cc-plan-judge.ts");
|
|
6808
|
+
const agentJudgeScriptPath = join18(HOOKS_DIR, "cc-agent-judge.ts");
|
|
6809
|
+
const stopSummaryScriptPath = join18(HOOKS_DIR, "cc-stop-summary.ts");
|
|
6810
|
+
const sessionStartScriptPath = join18(HOOKS_DIR, "cc-session-start.ts");
|
|
6811
|
+
const transcriptSyncScriptPath = join18(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
6812
|
+
const userPromptSubmitScriptPath = join18(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
6813
|
+
const promptRouteScriptPath = join18(HOOKS_DIR, "cc-prompt-route.ts");
|
|
6814
|
+
const commonScriptPath = join18(HOOKS_DIR, "_synkro-common.ts");
|
|
6815
|
+
const commonBashScriptPath = join18(HOOKS_DIR, "_synkro-common.sh");
|
|
6816
|
+
const installScanScriptPath = join18(HOOKS_DIR, "cc-install-scan.ts");
|
|
6817
|
+
const cursorBashJudgePath = join18(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
6818
|
+
const cursorEditCapturePath = join18(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
6819
|
+
const cursorAgentCapturePath = join18(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
6820
|
+
const mcpStdioProxyPath = join18(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
6821
|
+
const taskActivateIntentScriptPath = join18(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
6822
|
+
const mcpGateScriptPath = join18(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
6823
|
+
const stubCommonPath = join18(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
6691
6824
|
const stubFiles = [
|
|
6692
6825
|
[stubCommonPath, STUB_COMMON_TS],
|
|
6693
6826
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -6712,14 +6845,14 @@ function writeHookScripts() {
|
|
|
6712
6845
|
[cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
|
|
6713
6846
|
];
|
|
6714
6847
|
for (const [p, content] of stubFiles) {
|
|
6715
|
-
|
|
6848
|
+
writeFileSync16(p, content, "utf-8");
|
|
6716
6849
|
chmodSync4(p, 493);
|
|
6717
6850
|
}
|
|
6718
|
-
|
|
6851
|
+
writeFileSync16(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
6719
6852
|
chmodSync4(mcpStdioProxyPath, 493);
|
|
6720
6853
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
6721
6854
|
try {
|
|
6722
|
-
unlinkSync7(
|
|
6855
|
+
unlinkSync7(join18(HOOKS_DIR, stale));
|
|
6723
6856
|
} catch {
|
|
6724
6857
|
}
|
|
6725
6858
|
}
|
|
@@ -6755,11 +6888,11 @@ function shellQuoteSingle2(value) {
|
|
|
6755
6888
|
}
|
|
6756
6889
|
function resolveSynkroBundle() {
|
|
6757
6890
|
const scriptPath = process.argv[1];
|
|
6758
|
-
if (scriptPath &&
|
|
6891
|
+
if (scriptPath && existsSync22(scriptPath)) return scriptPath;
|
|
6759
6892
|
return null;
|
|
6760
6893
|
}
|
|
6761
6894
|
function writeConfigEnv(opts) {
|
|
6762
|
-
const credsPath =
|
|
6895
|
+
const credsPath = join18(SYNKRO_DIR11, "credentials.json");
|
|
6763
6896
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
6764
6897
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
6765
6898
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -6775,7 +6908,7 @@ function writeConfigEnv(opts) {
|
|
|
6775
6908
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
6776
6909
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
6777
6910
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
6778
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.
|
|
6911
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.87")}`
|
|
6779
6912
|
];
|
|
6780
6913
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
6781
6914
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -6795,12 +6928,12 @@ function writeConfigEnv(opts) {
|
|
|
6795
6928
|
lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
6796
6929
|
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
6797
6930
|
lines.push("");
|
|
6798
|
-
|
|
6931
|
+
writeFileSync16(CONFIG_PATH4, lines.join("\n"), "utf-8");
|
|
6799
6932
|
chmodSync4(CONFIG_PATH4, 384);
|
|
6800
6933
|
}
|
|
6801
6934
|
function persistedTranscriptConsent(source) {
|
|
6802
6935
|
try {
|
|
6803
|
-
const env =
|
|
6936
|
+
const env = readFileSync20(CONFIG_PATH4, "utf-8");
|
|
6804
6937
|
const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
|
|
6805
6938
|
if (specific) return specific[1] === "yes";
|
|
6806
6939
|
if (source !== "CODEX") {
|
|
@@ -6823,7 +6956,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6823
6956
|
assertGatewayAllowed(gatewayUrl);
|
|
6824
6957
|
let stored = "";
|
|
6825
6958
|
try {
|
|
6826
|
-
stored =
|
|
6959
|
+
stored = readFileSync20(CLOUD_JWT_PATH, "utf-8").trim();
|
|
6827
6960
|
} catch {
|
|
6828
6961
|
}
|
|
6829
6962
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -6839,25 +6972,49 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6839
6972
|
throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
|
|
6840
6973
|
}
|
|
6841
6974
|
const { token } = await resp.json();
|
|
6842
|
-
|
|
6975
|
+
writeFileSync16(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
|
|
6843
6976
|
return token;
|
|
6844
6977
|
}
|
|
6845
6978
|
async function provisionCloudContainer(opts) {
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6979
|
+
const sf = readFullSynkroFile();
|
|
6980
|
+
const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "", 10);
|
|
6981
|
+
const totalWorkers = Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4;
|
|
6982
|
+
let claudeWorkers = 0;
|
|
6983
|
+
let cursorWorkers = 0;
|
|
6984
|
+
let codexWorkers = 0;
|
|
6985
|
+
if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
|
|
6986
|
+
claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
|
|
6987
|
+
cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
|
|
6988
|
+
codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
|
|
6989
|
+
} else {
|
|
6990
|
+
const providers = [];
|
|
6991
|
+
if (sf?.grader.pool && sf.grader.pool !== "auto") providers.push(sf.grader.pool);
|
|
6992
|
+
else {
|
|
6993
|
+
if (opts.hasClaudeCode) providers.push("claude_code");
|
|
6994
|
+
if (opts.hasCursor) providers.push("cursor");
|
|
6995
|
+
if (opts.hasCodex) providers.push("codex");
|
|
6996
|
+
}
|
|
6997
|
+
({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
|
|
6998
|
+
}
|
|
6999
|
+
if (claudeWorkers + cursorWorkers + codexWorkers === 0) codexWorkers = totalWorkers;
|
|
7000
|
+
const selectedKind = codexWorkers > 0 && claudeWorkers === 0 && cursorWorkers === 0 ? "codex" : cursorWorkers > 0 && claudeWorkers === 0 ? "cursor" : "claude_code";
|
|
7001
|
+
let setupToken = "";
|
|
7002
|
+
if (claudeWorkers > 0) {
|
|
7003
|
+
try {
|
|
7004
|
+
console.log(" Authorize your Claude account for the hosted Claude worker \u2014");
|
|
7005
|
+
console.log(" a browser window will open for approval...\n");
|
|
7006
|
+
setupToken = await captureClaudeSetupToken();
|
|
7007
|
+
} catch (err) {
|
|
7008
|
+
console.error(` \u2717 Could not capture Claude setup-token: ${err.message}`);
|
|
7009
|
+
console.error(" Run `claude setup-token` manually, then re-run install.");
|
|
7010
|
+
process.exit(1);
|
|
7011
|
+
}
|
|
6855
7012
|
}
|
|
6856
|
-
console.log(" Validating Claude token...");
|
|
7013
|
+
if (setupToken) console.log(" Validating Claude token...");
|
|
6857
7014
|
let validated = false;
|
|
6858
7015
|
let authRejected = null;
|
|
6859
7016
|
let lastErr = "";
|
|
6860
|
-
for (let attempt = 1; attempt <= 2 && !validated; attempt++) {
|
|
7017
|
+
for (let attempt = 1; setupToken && attempt <= 2 && !validated; attempt++) {
|
|
6861
7018
|
try {
|
|
6862
7019
|
const out = execSync4('claude --print --output-format json "say ok"', {
|
|
6863
7020
|
env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: setupToken },
|
|
@@ -6883,22 +7040,19 @@ async function provisionCloudContainer(opts) {
|
|
|
6883
7040
|
}
|
|
6884
7041
|
if (validated) {
|
|
6885
7042
|
console.log(" \u2713 Claude token validated.\n");
|
|
6886
|
-
} else {
|
|
7043
|
+
} else if (setupToken) {
|
|
6887
7044
|
console.warn(` \u26A0 Couldn't confirm the token locally in time (${lastErr.slice(0, 50)}).`);
|
|
6888
7045
|
console.warn(" Proceeding \u2014 the cloud smoke-grade below verifies it end-to-end in the container.\n");
|
|
6889
7046
|
}
|
|
6890
7047
|
const repo = detectGitRepo2() || void 0;
|
|
6891
|
-
const sf = readFullSynkroFile();
|
|
6892
7048
|
const harness = [];
|
|
6893
7049
|
if (opts.hasClaudeCode) harness.push("claude-code");
|
|
6894
7050
|
if (opts.hasCursor) harness.push("cursor");
|
|
6895
|
-
|
|
6896
|
-
const total = sf?.workers?.claude && sf.workers.claude > 0 ? sf.workers.claude : Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4;
|
|
6897
|
-
const cursorTotal = sf?.workers?.cursor && sf.workers.cursor > 0 ? sf.workers.cursor : 0;
|
|
7051
|
+
if (opts.hasCodex) harness.push("codex");
|
|
6898
7052
|
let cursorApiKey = "";
|
|
6899
|
-
if (
|
|
7053
|
+
if (cursorWorkers > 0) {
|
|
6900
7054
|
try {
|
|
6901
|
-
cursorApiKey =
|
|
7055
|
+
cursorApiKey = readFileSync20(join18(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
|
|
6902
7056
|
} catch {
|
|
6903
7057
|
}
|
|
6904
7058
|
}
|
|
@@ -6908,6 +7062,13 @@ async function provisionCloudContainer(opts) {
|
|
|
6908
7062
|
} catch (e) {
|
|
6909
7063
|
console.warn(` (cloud token unavailable, using session token: ${e.message})`);
|
|
6910
7064
|
}
|
|
7065
|
+
if (codexWorkers > 0) {
|
|
7066
|
+
const codex = await setupCodexCloud(opts.gatewayUrl, opts.jwt, (message) => console.log(` ${message}`));
|
|
7067
|
+
if (!codex.ok) {
|
|
7068
|
+
console.error(` \u2717 ${codex.error || "Codex cloud authorization failed"}`);
|
|
7069
|
+
process.exit(1);
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
6911
7072
|
try {
|
|
6912
7073
|
const resp = await fetch(`${opts.gatewayUrl}/api/v1/cli/cloud-provision`, {
|
|
6913
7074
|
method: "POST",
|
|
@@ -6917,8 +7078,9 @@ async function provisionCloudContainer(opts) {
|
|
|
6917
7078
|
user_id: opts.userId,
|
|
6918
7079
|
email: opts.email,
|
|
6919
7080
|
harness,
|
|
6920
|
-
claude_workers:
|
|
6921
|
-
cursor_workers:
|
|
7081
|
+
claude_workers: claudeWorkers,
|
|
7082
|
+
cursor_workers: cursorWorkers,
|
|
7083
|
+
codex_workers: codexWorkers,
|
|
6922
7084
|
cursor_api_key: cursorApiKey,
|
|
6923
7085
|
// never logged; gateway stores it as the org secret
|
|
6924
7086
|
connected_repo: repo,
|
|
@@ -6933,11 +7095,25 @@ async function provisionCloudContainer(opts) {
|
|
|
6933
7095
|
}
|
|
6934
7096
|
const out = await resp.json().catch(() => ({}));
|
|
6935
7097
|
console.log(` \u2713 cloud container provisioned${out.container_id ? ` (${out.container_id})` : ""}`);
|
|
7098
|
+
const containerBase = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
7099
|
+
try {
|
|
7100
|
+
const recycle = await fetch(`${containerBase}/recycle`, {
|
|
7101
|
+
method: "POST",
|
|
7102
|
+
headers: { Authorization: `Bearer ${opts.jwt}` },
|
|
7103
|
+
signal: AbortSignal.timeout(3e4)
|
|
7104
|
+
});
|
|
7105
|
+
if (recycle.ok) console.log(" \u2713 cloud grader recycled onto the new pool");
|
|
7106
|
+
else console.warn(` \u26A0 cloud grader recycle returned HTTP ${recycle.status}; warmup will retry the current container`);
|
|
7107
|
+
} catch {
|
|
7108
|
+
console.warn(" \u26A0 cloud grader recycle was unreachable; warmup will retry");
|
|
7109
|
+
}
|
|
6936
7110
|
console.log();
|
|
7111
|
+
return selectedKind;
|
|
6937
7112
|
} catch (err) {
|
|
6938
7113
|
console.error(` \u2717 Cloud provisioning failed: ${err.message}`);
|
|
6939
7114
|
console.error(" Hooks + MCP are installed; re-run `synkro install` \u2192 cloud to retry.");
|
|
6940
7115
|
console.log();
|
|
7116
|
+
throw err;
|
|
6941
7117
|
} finally {
|
|
6942
7118
|
setupToken = "";
|
|
6943
7119
|
}
|
|
@@ -6978,17 +7154,21 @@ async function printWorkerDebug(base, jwt2) {
|
|
|
6978
7154
|
console.warn(" (could not fetch worker debug)\n");
|
|
6979
7155
|
}
|
|
6980
7156
|
}
|
|
6981
|
-
async function verifyCloudGrader(jwt2) {
|
|
7157
|
+
async function verifyCloudGrader(jwt2, requestedKind) {
|
|
6982
7158
|
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
6983
7159
|
console.log(" Warming the cloud grader (booting container + checking workers)...");
|
|
6984
7160
|
const deadline = Date.now() + 18e4;
|
|
6985
7161
|
let healthy = 0;
|
|
7162
|
+
let agentKind = requestedKind || "claude_code";
|
|
6986
7163
|
while (Date.now() < deadline) {
|
|
6987
7164
|
try {
|
|
6988
7165
|
const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt2}` }, signal: AbortSignal.timeout(6e4) });
|
|
6989
7166
|
if (r.ok) {
|
|
6990
7167
|
const j = await r.json();
|
|
6991
|
-
|
|
7168
|
+
if (!requestedKind) {
|
|
7169
|
+
agentKind = ["codex", "cursor", "claude_code"].find((kind) => (j[kind]?.healthy ?? 0) > 0) || "claude_code";
|
|
7170
|
+
}
|
|
7171
|
+
healthy = j[agentKind]?.healthy ?? 0;
|
|
6992
7172
|
if (healthy >= 1) break;
|
|
6993
7173
|
}
|
|
6994
7174
|
} catch {
|
|
@@ -7000,7 +7180,7 @@ async function verifyCloudGrader(jwt2) {
|
|
|
7000
7180
|
console.warn(" Grading is NOT verified \u2014 check the container or re-run `synkro install`.\n");
|
|
7001
7181
|
return false;
|
|
7002
7182
|
}
|
|
7003
|
-
console.log(` \u2713 ${healthy}
|
|
7183
|
+
console.log(` \u2713 ${healthy} ${poolLabel(agentKind)} worker(s) healthy`);
|
|
7004
7184
|
console.log(" Running smoke grade (a worker scores a known-risky edit; up to 120s)...");
|
|
7005
7185
|
const started = Date.now();
|
|
7006
7186
|
const ticker = setInterval(() => {
|
|
@@ -7016,7 +7196,7 @@ async function verifyCloudGrader(jwt2) {
|
|
|
7016
7196
|
const r = await fetch(`${base}/grade/submit`, {
|
|
7017
7197
|
method: "POST",
|
|
7018
7198
|
headers: { Authorization: `Bearer ${jwt2}`, "Content-Type": "application/json" },
|
|
7019
|
-
body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind:
|
|
7199
|
+
body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: agentKind }),
|
|
7020
7200
|
signal: AbortSignal.timeout(12e4)
|
|
7021
7201
|
});
|
|
7022
7202
|
stopTicker();
|
|
@@ -7055,8 +7235,8 @@ async function verifyCloudGrader(jwt2) {
|
|
|
7055
7235
|
}
|
|
7056
7236
|
function readPersistedDeployLocation() {
|
|
7057
7237
|
try {
|
|
7058
|
-
if (
|
|
7059
|
-
const m =
|
|
7238
|
+
if (existsSync22(CONFIG_PATH4)) {
|
|
7239
|
+
const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
7060
7240
|
if (m?.[1] === "cloud") return "cloud";
|
|
7061
7241
|
}
|
|
7062
7242
|
} catch {
|
|
@@ -7064,8 +7244,8 @@ function readPersistedDeployLocation() {
|
|
|
7064
7244
|
return "local";
|
|
7065
7245
|
}
|
|
7066
7246
|
function updateConfigEnvLocation(location) {
|
|
7067
|
-
if (!
|
|
7068
|
-
let env =
|
|
7247
|
+
if (!existsSync22(CONFIG_PATH4)) return;
|
|
7248
|
+
let env = readFileSync20(CONFIG_PATH4, "utf-8");
|
|
7069
7249
|
const set = (k, v) => {
|
|
7070
7250
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
7071
7251
|
const line = `${k}='${v}'`;
|
|
@@ -7073,14 +7253,14 @@ function updateConfigEnvLocation(location) {
|
|
|
7073
7253
|
};
|
|
7074
7254
|
set("SYNKRO_DEPLOY_LOCATION", location);
|
|
7075
7255
|
set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
|
|
7076
|
-
|
|
7256
|
+
writeFileSync16(CONFIG_PATH4, env, "utf-8");
|
|
7077
7257
|
chmodSync4(CONFIG_PATH4, 384);
|
|
7078
7258
|
}
|
|
7079
7259
|
async function applyMcpConfig(opts) {
|
|
7080
7260
|
if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
|
|
7081
7261
|
let mcpJwt2 = "";
|
|
7082
7262
|
try {
|
|
7083
|
-
mcpJwt2 =
|
|
7263
|
+
mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7084
7264
|
} catch {
|
|
7085
7265
|
}
|
|
7086
7266
|
if (!mcpJwt2) {
|
|
@@ -7172,10 +7352,10 @@ Deploy location: ${current || "(none)"} \u2192 ${desired}
|
|
|
7172
7352
|
email = info.email;
|
|
7173
7353
|
} catch {
|
|
7174
7354
|
}
|
|
7175
|
-
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
7355
|
+
const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
|
|
7176
7356
|
updateConfigEnvLocation("cloud");
|
|
7177
7357
|
await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, hasCodex, local: false });
|
|
7178
|
-
await verifyCloudGrader(token);
|
|
7358
|
+
await verifyCloudGrader(token, agentKind);
|
|
7179
7359
|
console.log(" \u2713 grading + rules/MCP now served from Synkro Cloud\n");
|
|
7180
7360
|
} else {
|
|
7181
7361
|
updateConfigEnvLocation("local");
|
|
@@ -7210,8 +7390,8 @@ function resolveDeploymentMode() {
|
|
|
7210
7390
|
const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
|
|
7211
7391
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
7212
7392
|
try {
|
|
7213
|
-
if (
|
|
7214
|
-
const m =
|
|
7393
|
+
if (existsSync22(CONFIG_PATH4)) {
|
|
7394
|
+
const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
7215
7395
|
const val = m?.[1]?.toLowerCase();
|
|
7216
7396
|
if (val === "bare-host" || val === "docker") return val;
|
|
7217
7397
|
}
|
|
@@ -7238,16 +7418,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7238
7418
|
meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
7239
7419
|
} catch {
|
|
7240
7420
|
}
|
|
7241
|
-
const claudeDir =
|
|
7421
|
+
const claudeDir = join18(homedir20(), ".claude");
|
|
7242
7422
|
try {
|
|
7243
|
-
const settings = JSON.parse(
|
|
7423
|
+
const settings = JSON.parse(readFileSync20(join18(claudeDir, "settings.json"), "utf-8"));
|
|
7244
7424
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
7245
7425
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
7246
7426
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
7247
7427
|
} catch {
|
|
7248
7428
|
}
|
|
7249
7429
|
try {
|
|
7250
|
-
const mcpCache = JSON.parse(
|
|
7430
|
+
const mcpCache = JSON.parse(readFileSync20(join18(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
7251
7431
|
const mcpNames = Object.keys(mcpCache);
|
|
7252
7432
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
7253
7433
|
} catch {
|
|
@@ -7259,10 +7439,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7259
7439
|
} catch {
|
|
7260
7440
|
}
|
|
7261
7441
|
try {
|
|
7262
|
-
const sessionsDir =
|
|
7442
|
+
const sessionsDir = join18(claudeDir, "sessions");
|
|
7263
7443
|
const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
7264
7444
|
for (const f of files) {
|
|
7265
|
-
const s = JSON.parse(
|
|
7445
|
+
const s = JSON.parse(readFileSync20(join18(sessionsDir, f), "utf-8"));
|
|
7266
7446
|
if (s.version) {
|
|
7267
7447
|
meta.cc_version = meta.cc_version || s.version;
|
|
7268
7448
|
break;
|
|
@@ -7464,7 +7644,7 @@ async function installCommand(opts = {}) {
|
|
|
7464
7644
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
7465
7645
|
emit("install", {
|
|
7466
7646
|
phase: "started",
|
|
7467
|
-
cli_version_to: "1.7.
|
|
7647
|
+
cli_version_to: "1.7.87",
|
|
7468
7648
|
agents_detected: agents.map((a) => a.kind),
|
|
7469
7649
|
with_github: false,
|
|
7470
7650
|
with_local_cc: false,
|
|
@@ -7476,9 +7656,9 @@ async function installCommand(opts = {}) {
|
|
|
7476
7656
|
const scripts = writeHookScripts();
|
|
7477
7657
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
7478
7658
|
for (const mode of ["edit", "bash"]) {
|
|
7479
|
-
const pidFile =
|
|
7659
|
+
const pidFile = join18(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
|
|
7480
7660
|
try {
|
|
7481
|
-
const pid = parseInt(
|
|
7661
|
+
const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
|
|
7482
7662
|
if (pid > 0) {
|
|
7483
7663
|
process.kill(pid, "SIGTERM");
|
|
7484
7664
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -7596,7 +7776,7 @@ async function installCommand(opts = {}) {
|
|
|
7596
7776
|
if (mintResp.ok) {
|
|
7597
7777
|
const minted = await mintResp.json();
|
|
7598
7778
|
mcpJwt2 = minted.token;
|
|
7599
|
-
|
|
7779
|
+
writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
|
|
7600
7780
|
} else {
|
|
7601
7781
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
7602
7782
|
}
|
|
@@ -7624,7 +7804,7 @@ async function installCommand(opts = {}) {
|
|
|
7624
7804
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7625
7805
|
}
|
|
7626
7806
|
const minted = await mintResp.json();
|
|
7627
|
-
|
|
7807
|
+
writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7628
7808
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7629
7809
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7630
7810
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7646,8 +7826,8 @@ async function installCommand(opts = {}) {
|
|
|
7646
7826
|
if (hasCursor && !opts.noMcp) {
|
|
7647
7827
|
try {
|
|
7648
7828
|
if (useLocalMcp) {
|
|
7649
|
-
const jwtPath =
|
|
7650
|
-
if (!
|
|
7829
|
+
const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
|
|
7830
|
+
if (!existsSync22(jwtPath)) {
|
|
7651
7831
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
7652
7832
|
method: "POST",
|
|
7653
7833
|
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
@@ -7655,7 +7835,7 @@ async function installCommand(opts = {}) {
|
|
|
7655
7835
|
});
|
|
7656
7836
|
if (mintResp.ok) {
|
|
7657
7837
|
const minted = await mintResp.json();
|
|
7658
|
-
|
|
7838
|
+
writeFileSync16(jwtPath, minted.token + "\n", { mode: 384 });
|
|
7659
7839
|
}
|
|
7660
7840
|
}
|
|
7661
7841
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
|
|
@@ -7675,7 +7855,7 @@ async function installCommand(opts = {}) {
|
|
|
7675
7855
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7676
7856
|
}
|
|
7677
7857
|
const minted = await mintResp.json();
|
|
7678
|
-
|
|
7858
|
+
writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7679
7859
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7680
7860
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7681
7861
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7688,10 +7868,10 @@ async function installCommand(opts = {}) {
|
|
|
7688
7868
|
}
|
|
7689
7869
|
if (hasCodex && !opts.noMcp) {
|
|
7690
7870
|
try {
|
|
7691
|
-
const jwtPath =
|
|
7871
|
+
const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
|
|
7692
7872
|
let mcpJwt2 = "";
|
|
7693
7873
|
try {
|
|
7694
|
-
mcpJwt2 =
|
|
7874
|
+
mcpJwt2 = readFileSync20(jwtPath, "utf-8").trim();
|
|
7695
7875
|
} catch {
|
|
7696
7876
|
}
|
|
7697
7877
|
if (!mcpJwt2) {
|
|
@@ -7709,7 +7889,7 @@ async function installCommand(opts = {}) {
|
|
|
7709
7889
|
}
|
|
7710
7890
|
const minted = await mintResp.json();
|
|
7711
7891
|
mcpJwt2 = minted.token;
|
|
7712
|
-
|
|
7892
|
+
writeFileSync16(jwtPath, mcpJwt2 + "\n", { mode: 384 });
|
|
7713
7893
|
}
|
|
7714
7894
|
const mcp = installCodexMcpConfig({
|
|
7715
7895
|
gatewayUrl,
|
|
@@ -7756,7 +7936,7 @@ async function installCommand(opts = {}) {
|
|
|
7756
7936
|
}
|
|
7757
7937
|
const pool = sfp?.grader?.pool || "auto";
|
|
7758
7938
|
if (pool === "cursor") return true;
|
|
7759
|
-
if (pool === "claude_code") return false;
|
|
7939
|
+
if (pool === "claude_code" || pool === "codex") return false;
|
|
7760
7940
|
return hasCursor;
|
|
7761
7941
|
})();
|
|
7762
7942
|
if (useLocalMcp) {
|
|
@@ -7776,14 +7956,17 @@ async function installCommand(opts = {}) {
|
|
|
7776
7956
|
for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
|
|
7777
7957
|
let claudeWorkers;
|
|
7778
7958
|
let cursorWorkers;
|
|
7779
|
-
|
|
7959
|
+
let codexWorkers;
|
|
7960
|
+
if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
|
|
7780
7961
|
claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
|
|
7781
7962
|
cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
|
|
7782
|
-
|
|
7963
|
+
codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
|
|
7964
|
+
if (claudeWorkers + cursorWorkers + codexWorkers === 0) {
|
|
7783
7965
|
claudeWorkers = 0;
|
|
7784
|
-
cursorWorkers =
|
|
7966
|
+
cursorWorkers = 0;
|
|
7967
|
+
codexWorkers = 8;
|
|
7785
7968
|
}
|
|
7786
|
-
console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
|
|
7969
|
+
console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
|
|
7787
7970
|
} else {
|
|
7788
7971
|
const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
|
|
7789
7972
|
const synkroFilePool = sf?.grader?.pool || "auto";
|
|
@@ -7792,23 +7975,35 @@ async function installCommand(opts = {}) {
|
|
|
7792
7975
|
providers = ["cursor"];
|
|
7793
7976
|
} else if (synkroFilePool === "claude_code") {
|
|
7794
7977
|
providers = ["claude_code"];
|
|
7978
|
+
} else if (synkroFilePool === "codex") {
|
|
7979
|
+
providers = ["codex"];
|
|
7795
7980
|
} else {
|
|
7796
7981
|
if (hasClaudeCode) providers.push("claude_code");
|
|
7797
7982
|
if (hasCursor) providers.push("cursor");
|
|
7983
|
+
if (hasCodex) providers.push("codex");
|
|
7798
7984
|
}
|
|
7799
|
-
({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
|
|
7985
|
+
({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
|
|
7800
7986
|
if (synkroFilePool !== "auto") console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);
|
|
7801
7987
|
}
|
|
7802
|
-
|
|
7988
|
+
let codexHomeDir;
|
|
7989
|
+
if (codexWorkers > 0) {
|
|
7990
|
+
const codexSetup = await setupCodexLocal((message) => console.log(` ${message}`));
|
|
7991
|
+
if (!codexSetup.ok || !codexSetup.home) {
|
|
7992
|
+
console.error(` \u2717 ${codexSetup.error || "Codex grader authorization failed"}`);
|
|
7993
|
+
process.exit(1);
|
|
7994
|
+
}
|
|
7995
|
+
codexHomeDir = codexSetup.home;
|
|
7996
|
+
}
|
|
7997
|
+
console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
|
|
7803
7998
|
const connectedRepo = detectGitRepo2() || void 0;
|
|
7804
|
-
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });
|
|
7999
|
+
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, codexHomeDir, connectedRepo });
|
|
7805
8000
|
console.log(` \u2713 pulled ${image}`);
|
|
7806
8001
|
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);
|
|
7807
8002
|
console.log(" waiting for container to be ready...");
|
|
7808
8003
|
const ready = await waitForContainerReady(6e4);
|
|
7809
8004
|
if (ready) {
|
|
7810
8005
|
console.log(" \u2713 container ready");
|
|
7811
|
-
const mcpJwt2 =
|
|
8006
|
+
const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7812
8007
|
try {
|
|
7813
8008
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
7814
8009
|
method: "POST",
|
|
@@ -7841,8 +8036,8 @@ async function installCommand(opts = {}) {
|
|
|
7841
8036
|
console.log();
|
|
7842
8037
|
} else if (target === "cloud-container") {
|
|
7843
8038
|
if (graderUsesCursor) await promptCursorApiKey(opts);
|
|
7844
|
-
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
7845
|
-
cloudGradeOk = await verifyCloudGrader(token);
|
|
8039
|
+
const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
|
|
8040
|
+
cloudGradeOk = await verifyCloudGrader(token, agentKind);
|
|
7846
8041
|
}
|
|
7847
8042
|
if (transcriptConsent) {
|
|
7848
8043
|
const repo = detectGitRepo2();
|
|
@@ -7851,7 +8046,7 @@ async function installCommand(opts = {}) {
|
|
|
7851
8046
|
try {
|
|
7852
8047
|
let mcpToken = "";
|
|
7853
8048
|
try {
|
|
7854
|
-
mcpToken =
|
|
8049
|
+
mcpToken = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7855
8050
|
} catch {
|
|
7856
8051
|
}
|
|
7857
8052
|
if (mcpToken) {
|
|
@@ -8019,8 +8214,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8019
8214
|
try {
|
|
8020
8215
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8021
8216
|
if (!root) return;
|
|
8022
|
-
if (root ===
|
|
8023
|
-
const fp =
|
|
8217
|
+
if (root === homedir20()) return;
|
|
8218
|
+
const fp = join18(root, "synkro.toml");
|
|
8024
8219
|
let hasFile = false;
|
|
8025
8220
|
try {
|
|
8026
8221
|
hasFile = statSync2(fp).isFile();
|
|
@@ -8032,15 +8227,10 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8032
8227
|
}
|
|
8033
8228
|
let pool = "auto";
|
|
8034
8229
|
const harness = [];
|
|
8035
|
-
if (opts.hasClaudeCode)
|
|
8036
|
-
|
|
8037
|
-
if (!opts.hasCursor) pool = "claude";
|
|
8038
|
-
}
|
|
8039
|
-
if (opts.hasCursor) {
|
|
8040
|
-
harness.push("cursor");
|
|
8041
|
-
if (!opts.hasClaudeCode) pool = "cursor";
|
|
8042
|
-
}
|
|
8230
|
+
if (opts.hasClaudeCode) harness.push("claude-code");
|
|
8231
|
+
if (opts.hasCursor) harness.push("cursor");
|
|
8043
8232
|
if (opts.hasCodex) harness.push("codex");
|
|
8233
|
+
if (harness.length === 1) pool = harness[0] === "claude-code" ? "claude-code" : harness[0];
|
|
8044
8234
|
const mode = opts.gradingMode === "byok" ? "byok" : "local";
|
|
8045
8235
|
const toml = [
|
|
8046
8236
|
"version = 1",
|
|
@@ -8057,7 +8247,7 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8057
8247
|
"cve = true",
|
|
8058
8248
|
""
|
|
8059
8249
|
].join("\n");
|
|
8060
|
-
|
|
8250
|
+
writeFileSync16(fp, toml, "utf-8");
|
|
8061
8251
|
console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
|
|
8062
8252
|
} catch {
|
|
8063
8253
|
}
|
|
@@ -8065,12 +8255,12 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8065
8255
|
function updateSynkroTomlLocation(location) {
|
|
8066
8256
|
try {
|
|
8067
8257
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8068
|
-
if (!root || root ===
|
|
8069
|
-
const fp =
|
|
8258
|
+
if (!root || root === homedir20()) return;
|
|
8259
|
+
const fp = join18(root, "synkro.toml");
|
|
8070
8260
|
let txt = "";
|
|
8071
8261
|
try {
|
|
8072
8262
|
if (!statSync2(fp).isFile()) return;
|
|
8073
|
-
txt =
|
|
8263
|
+
txt = readFileSync20(fp, "utf-8");
|
|
8074
8264
|
} catch {
|
|
8075
8265
|
return;
|
|
8076
8266
|
}
|
|
@@ -8078,7 +8268,7 @@ function updateSynkroTomlLocation(location) {
|
|
|
8078
8268
|
if (!re.test(txt)) return;
|
|
8079
8269
|
const next = txt.replace(re, `$1"${location}"`);
|
|
8080
8270
|
if (next !== txt) {
|
|
8081
|
-
|
|
8271
|
+
writeFileSync16(fp, next, "utf-8");
|
|
8082
8272
|
console.log(` synkro.toml: [grader] location = "${location}"`);
|
|
8083
8273
|
}
|
|
8084
8274
|
} catch {
|
|
@@ -8088,9 +8278,9 @@ function readFullSynkroFile() {
|
|
|
8088
8278
|
try {
|
|
8089
8279
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8090
8280
|
if (!root) return null;
|
|
8091
|
-
const fp =
|
|
8092
|
-
if (!
|
|
8093
|
-
const parsed = parseSynkroToml2(
|
|
8281
|
+
const fp = join18(root, "synkro.toml");
|
|
8282
|
+
if (!existsSync22(fp)) return null;
|
|
8283
|
+
const parsed = parseSynkroToml2(readFileSync20(fp, "utf-8"));
|
|
8094
8284
|
const valid = ["claude-code", "cursor", "codex"];
|
|
8095
8285
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
8096
8286
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -8103,7 +8293,8 @@ function readFullSynkroFile() {
|
|
|
8103
8293
|
},
|
|
8104
8294
|
workers: {
|
|
8105
8295
|
...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
|
|
8106
|
-
...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
|
|
8296
|
+
...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {},
|
|
8297
|
+
...resolved.codexWorkers != null ? { codex: resolved.codexWorkers } : {}
|
|
8107
8298
|
},
|
|
8108
8299
|
warnings: resolved.warnings,
|
|
8109
8300
|
ruleset: parsed.ruleset || "default",
|
|
@@ -8129,7 +8320,7 @@ function reconcileHarness() {
|
|
|
8129
8320
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
8130
8321
|
const scripts = writeHookScripts();
|
|
8131
8322
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
8132
|
-
const ccSettings =
|
|
8323
|
+
const ccSettings = join18(homedir20(), ".claude", "settings.json");
|
|
8133
8324
|
if (wantCC) {
|
|
8134
8325
|
installCCHooks(ccSettings, {
|
|
8135
8326
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -8152,7 +8343,7 @@ function reconcileHarness() {
|
|
|
8152
8343
|
});
|
|
8153
8344
|
console.log(" \u2713 Claude Code hooks registered");
|
|
8154
8345
|
try {
|
|
8155
|
-
const mcpJwt2 =
|
|
8346
|
+
const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8156
8347
|
if (mcpJwt2) {
|
|
8157
8348
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
|
|
8158
8349
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -8164,7 +8355,7 @@ function reconcileHarness() {
|
|
|
8164
8355
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
8165
8356
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
8166
8357
|
}
|
|
8167
|
-
const cursorHooks =
|
|
8358
|
+
const cursorHooks = join18(homedir20(), ".cursor", "hooks.json");
|
|
8168
8359
|
if (wantCursor) {
|
|
8169
8360
|
installCursorHooks(cursorHooks, {
|
|
8170
8361
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -8195,7 +8386,7 @@ function reconcileHarness() {
|
|
|
8195
8386
|
if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
|
|
8196
8387
|
if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
|
|
8197
8388
|
}
|
|
8198
|
-
const codexHooks =
|
|
8389
|
+
const codexHooks = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "hooks.json");
|
|
8199
8390
|
if (wantCodex) {
|
|
8200
8391
|
installCodexHooks(codexHooks, {
|
|
8201
8392
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -8226,10 +8417,11 @@ function reconcileHarness() {
|
|
|
8226
8417
|
if (uninstallCodexHooks(codexHooks)) console.log(" \u2717 Codex hooks removed");
|
|
8227
8418
|
if (uninstallCodexMcpConfig()) console.log(" \u2717 Codex MCP removed");
|
|
8228
8419
|
}
|
|
8229
|
-
if (sf.workers.claude != null || sf.workers.cursor != null) {
|
|
8420
|
+
if (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null) {
|
|
8230
8421
|
const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
|
|
8231
8422
|
const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
|
|
8232
|
-
|
|
8423
|
+
const codw = Math.max(0, Math.floor(sf.workers.codex || 0));
|
|
8424
|
+
if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
|
|
8233
8425
|
}
|
|
8234
8426
|
const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
|
|
8235
8427
|
const providers = [];
|
|
@@ -8237,9 +8429,12 @@ function reconcileHarness() {
|
|
|
8237
8429
|
providers.push("cursor");
|
|
8238
8430
|
} else if (sf.grader.pool === "claude_code") {
|
|
8239
8431
|
providers.push("claude_code");
|
|
8432
|
+
} else if (sf.grader.pool === "codex") {
|
|
8433
|
+
providers.push("codex");
|
|
8240
8434
|
} else {
|
|
8241
8435
|
if (wantCC) providers.push("claude_code");
|
|
8242
8436
|
if (wantCursor) providers.push("cursor");
|
|
8437
|
+
if (wantCodex) providers.push("codex");
|
|
8243
8438
|
}
|
|
8244
8439
|
if (providers.length === 0) providers.push("claude_code");
|
|
8245
8440
|
return splitWorkers(total, providers);
|
|
@@ -8279,7 +8474,7 @@ async function syncSkillFiles() {
|
|
|
8279
8474
|
if (resolved.length === 0) return;
|
|
8280
8475
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
8281
8476
|
const tasks = resolved.map((fp) => {
|
|
8282
|
-
const content =
|
|
8477
|
+
const content = readFileSync20(fp, "utf-8");
|
|
8283
8478
|
const source = `skill:${fp.split("/").pop()}`;
|
|
8284
8479
|
if (!content.trim()) {
|
|
8285
8480
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -8300,15 +8495,15 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8300
8495
|
const roots = [];
|
|
8301
8496
|
const add = (p) => {
|
|
8302
8497
|
try {
|
|
8303
|
-
if (p &&
|
|
8498
|
+
if (p && existsSync22(p) && statSync2(p).isDirectory()) roots.push(p);
|
|
8304
8499
|
} catch {
|
|
8305
8500
|
}
|
|
8306
8501
|
};
|
|
8307
|
-
add(
|
|
8308
|
-
add(
|
|
8502
|
+
add(join18(homedir20(), ".claude", "skills"));
|
|
8503
|
+
add(join18(homedir20(), ".agents", "skills"));
|
|
8309
8504
|
if (repoRoot2) {
|
|
8310
|
-
add(
|
|
8311
|
-
add(
|
|
8505
|
+
add(join18(repoRoot2, ".claude", "skills"));
|
|
8506
|
+
add(join18(repoRoot2, ".agents", "skills"));
|
|
8312
8507
|
}
|
|
8313
8508
|
const out = [];
|
|
8314
8509
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8319,7 +8514,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8319
8514
|
try {
|
|
8320
8515
|
const st = statSync2(file);
|
|
8321
8516
|
if (!st.isFile() || st.size > 2e5) return;
|
|
8322
|
-
content =
|
|
8517
|
+
content = readFileSync20(file, "utf-8");
|
|
8323
8518
|
} catch {
|
|
8324
8519
|
return;
|
|
8325
8520
|
}
|
|
@@ -8339,7 +8534,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8339
8534
|
}
|
|
8340
8535
|
for (const entry of entries) {
|
|
8341
8536
|
if (entry.startsWith(".")) continue;
|
|
8342
|
-
const full =
|
|
8537
|
+
const full = join18(root, entry);
|
|
8343
8538
|
let st;
|
|
8344
8539
|
try {
|
|
8345
8540
|
st = statSync2(full);
|
|
@@ -8347,8 +8542,8 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8347
8542
|
continue;
|
|
8348
8543
|
}
|
|
8349
8544
|
if (st.isDirectory()) {
|
|
8350
|
-
const skillMd =
|
|
8351
|
-
if (
|
|
8545
|
+
const skillMd = join18(full, "SKILL.md");
|
|
8546
|
+
if (existsSync22(skillMd)) consider(skillMd, entry);
|
|
8352
8547
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
8353
8548
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
8354
8549
|
}
|
|
@@ -8397,7 +8592,7 @@ async function discoverAndIngestSkills() {
|
|
|
8397
8592
|
if (sf?.skills?.length) {
|
|
8398
8593
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
8399
8594
|
try {
|
|
8400
|
-
excludeHashes.add(createHash2("sha256").update(
|
|
8595
|
+
excludeHashes.add(createHash2("sha256").update(readFileSync20(fp, "utf-8")).digest("hex"));
|
|
8401
8596
|
} catch {
|
|
8402
8597
|
}
|
|
8403
8598
|
}
|
|
@@ -8428,13 +8623,13 @@ async function discoverAndIngestSkills() {
|
|
|
8428
8623
|
const setHash = discoverySetHash(selectable);
|
|
8429
8624
|
let prev = "";
|
|
8430
8625
|
try {
|
|
8431
|
-
prev =
|
|
8626
|
+
prev = readFileSync20(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
8432
8627
|
} catch {
|
|
8433
8628
|
}
|
|
8434
8629
|
if (prev === setHash) return;
|
|
8435
8630
|
const picks = await promptSkillDiscovery(found);
|
|
8436
8631
|
try {
|
|
8437
|
-
|
|
8632
|
+
writeFileSync16(SKILLS_DISCOVERED_PATH, setHash);
|
|
8438
8633
|
} catch {
|
|
8439
8634
|
}
|
|
8440
8635
|
if (picks.length === 0) {
|
|
@@ -8473,9 +8668,9 @@ function ensureReachabilityGitHook() {
|
|
|
8473
8668
|
const root = run("git rev-parse --show-toplevel");
|
|
8474
8669
|
if (!root) return null;
|
|
8475
8670
|
let hooksDir = run("git config --get core.hooksPath");
|
|
8476
|
-
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir :
|
|
8477
|
-
if (!
|
|
8478
|
-
const hookPath =
|
|
8671
|
+
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join18(root, hooksDir) : join18(root, ".git", "hooks");
|
|
8672
|
+
if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
|
|
8673
|
+
const hookPath = join18(hooksDir, "post-commit");
|
|
8479
8674
|
const resolvedBin = resolveSynkroBinPath();
|
|
8480
8675
|
const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
|
|
8481
8676
|
const START = "# >>> synkro reachability (managed) >>>";
|
|
@@ -8488,14 +8683,14 @@ function ensureReachabilityGitHook() {
|
|
|
8488
8683
|
END
|
|
8489
8684
|
].join("\n");
|
|
8490
8685
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8491
|
-
if (!
|
|
8492
|
-
|
|
8686
|
+
if (!existsSync22(hookPath)) {
|
|
8687
|
+
writeFileSync16(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
|
|
8493
8688
|
return "installed";
|
|
8494
8689
|
}
|
|
8495
|
-
let cur =
|
|
8690
|
+
let cur = readFileSync20(hookPath, "utf-8");
|
|
8496
8691
|
if (cur.includes(START)) {
|
|
8497
8692
|
cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
|
|
8498
|
-
|
|
8693
|
+
writeFileSync16(hookPath, cur);
|
|
8499
8694
|
try {
|
|
8500
8695
|
chmodSync4(hookPath, 493);
|
|
8501
8696
|
} catch {
|
|
@@ -8503,7 +8698,7 @@ function ensureReachabilityGitHook() {
|
|
|
8503
8698
|
return "updated";
|
|
8504
8699
|
}
|
|
8505
8700
|
const sep = cur.endsWith("\n") ? "" : "\n";
|
|
8506
|
-
|
|
8701
|
+
writeFileSync16(hookPath, cur + sep + "\n" + block + "\n");
|
|
8507
8702
|
try {
|
|
8508
8703
|
chmodSync4(hookPath, 493);
|
|
8509
8704
|
} catch {
|
|
@@ -8531,17 +8726,17 @@ function detectGitRepo2() {
|
|
|
8531
8726
|
function getClaudeProjectsFolder() {
|
|
8532
8727
|
const cwd = process.cwd();
|
|
8533
8728
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
8534
|
-
const projectsDir =
|
|
8535
|
-
return
|
|
8729
|
+
const projectsDir = join18(homedir20(), ".claude", "projects", sanitized);
|
|
8730
|
+
return existsSync22(projectsDir) ? projectsDir : null;
|
|
8536
8731
|
}
|
|
8537
8732
|
function extractSessionInsights(projectsDir) {
|
|
8538
8733
|
const insights = [];
|
|
8539
8734
|
const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
8540
8735
|
for (const file of files) {
|
|
8541
8736
|
const sessionId = file.replace(".jsonl", "");
|
|
8542
|
-
const filePath =
|
|
8737
|
+
const filePath = join18(projectsDir, file);
|
|
8543
8738
|
try {
|
|
8544
|
-
const content =
|
|
8739
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
8545
8740
|
const lines = content.split("\n").filter(Boolean);
|
|
8546
8741
|
for (let i = 0; i < lines.length; i++) {
|
|
8547
8742
|
try {
|
|
@@ -8617,17 +8812,17 @@ function extractTextContent(content) {
|
|
|
8617
8812
|
return "";
|
|
8618
8813
|
}
|
|
8619
8814
|
function getCodexTranscriptFiles(repo) {
|
|
8620
|
-
const sessionsDir =
|
|
8621
|
-
if (!
|
|
8815
|
+
const sessionsDir = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "sessions");
|
|
8816
|
+
if (!existsSync22(sessionsDir)) return [];
|
|
8622
8817
|
let relative = [];
|
|
8623
8818
|
try {
|
|
8624
8819
|
relative = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
|
|
8625
8820
|
} catch {
|
|
8626
8821
|
return [];
|
|
8627
8822
|
}
|
|
8628
|
-
return relative.filter((p) => p.endsWith(".jsonl")).map((p) =>
|
|
8823
|
+
return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join18(sessionsDir, p)).filter((filePath) => {
|
|
8629
8824
|
try {
|
|
8630
|
-
const first =
|
|
8825
|
+
const first = readFileSync20(filePath, "utf-8").split("\n", 1)[0];
|
|
8631
8826
|
const meta = JSON.parse(first);
|
|
8632
8827
|
const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
|
|
8633
8828
|
const root = resolve4(repo);
|
|
@@ -8638,7 +8833,7 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8638
8833
|
});
|
|
8639
8834
|
}
|
|
8640
8835
|
function parseCodexTranscriptFile(filePath) {
|
|
8641
|
-
const lines =
|
|
8836
|
+
const lines = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean);
|
|
8642
8837
|
let sessionId = "";
|
|
8643
8838
|
let model = "";
|
|
8644
8839
|
const messages = [];
|
|
@@ -8693,7 +8888,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8693
8888
|
totalSessions++;
|
|
8694
8889
|
totalMessages += result.ingested ?? messages.length;
|
|
8695
8890
|
}
|
|
8696
|
-
|
|
8891
|
+
writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
8697
8892
|
} catch {
|
|
8698
8893
|
}
|
|
8699
8894
|
if ((i + 1) % 10 === 0 || i === files.length - 1) {
|
|
@@ -8707,14 +8902,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
8707
8902
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8708
8903
|
}
|
|
8709
8904
|
function getCursorTranscriptsDir() {
|
|
8710
|
-
const dir =
|
|
8711
|
-
return
|
|
8905
|
+
const dir = join18(homedir20(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
8906
|
+
return existsSync22(dir) ? dir : null;
|
|
8712
8907
|
}
|
|
8713
8908
|
function isSafeConvId(id) {
|
|
8714
8909
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
8715
8910
|
}
|
|
8716
8911
|
function parseCursorTranscriptFile(filePath) {
|
|
8717
|
-
const content =
|
|
8912
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
8718
8913
|
const lines = content.split("\n").filter(Boolean);
|
|
8719
8914
|
const messages = [];
|
|
8720
8915
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8746,8 +8941,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8746
8941
|
for (let i = 0; i < convDirs.length; i++) {
|
|
8747
8942
|
const convId = convDirs[i];
|
|
8748
8943
|
if (!isSafeConvId(convId)) continue;
|
|
8749
|
-
const filePath =
|
|
8750
|
-
if (!
|
|
8944
|
+
const filePath = join18(dir, convId, `${convId}.jsonl`);
|
|
8945
|
+
if (!existsSync22(filePath)) continue;
|
|
8751
8946
|
try {
|
|
8752
8947
|
const all = parseCursorTranscriptFile(filePath);
|
|
8753
8948
|
const messages = all.length > 500 ? all.slice(-500) : all;
|
|
@@ -8769,8 +8964,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8769
8964
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
8770
8965
|
}
|
|
8771
8966
|
try {
|
|
8772
|
-
const lc =
|
|
8773
|
-
|
|
8967
|
+
const lc = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
8968
|
+
writeFileSync16(join18(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
8774
8969
|
} catch {
|
|
8775
8970
|
}
|
|
8776
8971
|
}
|
|
@@ -8778,7 +8973,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8778
8973
|
return { sessions: totalSessions, messages: totalMessages };
|
|
8779
8974
|
}
|
|
8780
8975
|
function parseTranscriptFile(filePath) {
|
|
8781
|
-
const content =
|
|
8976
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
8782
8977
|
const lines = content.split("\n").filter(Boolean);
|
|
8783
8978
|
const messages = [];
|
|
8784
8979
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8826,7 +9021,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8826
9021
|
for (let i = 0; i < files.length; i++) {
|
|
8827
9022
|
const file = files[i];
|
|
8828
9023
|
const sessionId = file.replace(".jsonl", "");
|
|
8829
|
-
const filePath =
|
|
9024
|
+
const filePath = join18(projectsDir, file);
|
|
8830
9025
|
try {
|
|
8831
9026
|
const allMessages = parseTranscriptFile(filePath);
|
|
8832
9027
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -8848,9 +9043,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8848
9043
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
8849
9044
|
}
|
|
8850
9045
|
try {
|
|
8851
|
-
const content =
|
|
9046
|
+
const content = readFileSync20(join18(projectsDir, file), "utf-8");
|
|
8852
9047
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
8853
|
-
|
|
9048
|
+
writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
8854
9049
|
} catch {
|
|
8855
9050
|
}
|
|
8856
9051
|
}
|
|
@@ -8871,7 +9066,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
8871
9066
|
const sessions = [];
|
|
8872
9067
|
for (const file of batch) {
|
|
8873
9068
|
const sessionId = file.replace(".jsonl", "");
|
|
8874
|
-
const filePath =
|
|
9069
|
+
const filePath = join18(projectsDir, file);
|
|
8875
9070
|
try {
|
|
8876
9071
|
const allMessages = parseTranscriptFile(filePath);
|
|
8877
9072
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -8900,11 +9095,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
8900
9095
|
}
|
|
8901
9096
|
for (const file of batch) {
|
|
8902
9097
|
const sessionId = file.replace(".jsonl", "");
|
|
8903
|
-
const filePath =
|
|
9098
|
+
const filePath = join18(projectsDir, file);
|
|
8904
9099
|
try {
|
|
8905
|
-
const content =
|
|
9100
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
8906
9101
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
8907
|
-
|
|
9102
|
+
writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
8908
9103
|
} catch {
|
|
8909
9104
|
}
|
|
8910
9105
|
}
|
|
@@ -8944,7 +9139,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
8944
9139
|
try {
|
|
8945
9140
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
8946
9141
|
if (parsed.sessionId) {
|
|
8947
|
-
|
|
9142
|
+
writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
8948
9143
|
}
|
|
8949
9144
|
} catch {
|
|
8950
9145
|
}
|
|
@@ -8952,7 +9147,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
8952
9147
|
}
|
|
8953
9148
|
return { sessions: totalSessions, messages: totalMessages };
|
|
8954
9149
|
}
|
|
8955
|
-
var
|
|
9150
|
+
var SYNKRO_DIR11, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
|
|
8956
9151
|
var init_install = __esm({
|
|
8957
9152
|
"cli/commands/install.ts"() {
|
|
8958
9153
|
"use strict";
|
|
@@ -8972,11 +9167,12 @@ var init_install = __esm({
|
|
|
8972
9167
|
init_claudeDesktopTap();
|
|
8973
9168
|
init_dockerInstall();
|
|
8974
9169
|
init_setupToken();
|
|
9170
|
+
init_codexCloudSetup();
|
|
8975
9171
|
init_ptyShim();
|
|
8976
|
-
|
|
8977
|
-
HOOKS_DIR =
|
|
8978
|
-
BIN_DIR =
|
|
8979
|
-
CONFIG_PATH4 =
|
|
9172
|
+
SYNKRO_DIR11 = join18(homedir20(), ".synkro");
|
|
9173
|
+
HOOKS_DIR = join18(SYNKRO_DIR11, "hooks");
|
|
9174
|
+
BIN_DIR = join18(SYNKRO_DIR11, "bin");
|
|
9175
|
+
CONFIG_PATH4 = join18(SYNKRO_DIR11, "config.env");
|
|
8980
9176
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
8981
9177
|
import { readFileSync } from 'node:fs';
|
|
8982
9178
|
import { homedir } from 'node:os';
|
|
@@ -9087,23 +9283,23 @@ rl.on('line', async (line) => {
|
|
|
9087
9283
|
}
|
|
9088
9284
|
});
|
|
9089
9285
|
`;
|
|
9090
|
-
OFFSETS_DIR =
|
|
9091
|
-
CLOUD_JWT_PATH =
|
|
9092
|
-
SKILLS_DISCOVERED_PATH =
|
|
9286
|
+
OFFSETS_DIR = join18(SYNKRO_DIR11, ".transcript-offsets");
|
|
9287
|
+
CLOUD_JWT_PATH = join18(SYNKRO_DIR11, ".cloud-jwt");
|
|
9288
|
+
SKILLS_DISCOVERED_PATH = join18(SYNKRO_DIR11, ".skills-discovered");
|
|
9093
9289
|
}
|
|
9094
9290
|
});
|
|
9095
9291
|
|
|
9096
9292
|
// cli/local-cc/install.ts
|
|
9097
|
-
import { existsSync as
|
|
9098
|
-
import { join as
|
|
9099
|
-
import { homedir as
|
|
9100
|
-
import { spawnSync as
|
|
9293
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, readFileSync as readFileSync21, chmodSync as chmodSync5, copyFileSync as copyFileSync2, renameSync as renameSync7, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
|
|
9294
|
+
import { join as join19 } from "path";
|
|
9295
|
+
import { homedir as homedir21 } from "os";
|
|
9296
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
9101
9297
|
function writePluginFiles() {
|
|
9102
9298
|
for (const c of CHANNELS) {
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9299
|
+
mkdirSync16(c.sessionDir, { recursive: true });
|
|
9300
|
+
mkdirSync16(c.pluginSettingsDir, { recursive: true });
|
|
9301
|
+
writeFileSync17(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
9302
|
+
writeFileSync17(
|
|
9107
9303
|
c.pluginSettingsPath,
|
|
9108
9304
|
JSON.stringify({
|
|
9109
9305
|
fastMode: true,
|
|
@@ -9118,13 +9314,13 @@ function writePluginFiles() {
|
|
|
9118
9314
|
}, null, 2) + "\n",
|
|
9119
9315
|
"utf-8"
|
|
9120
9316
|
);
|
|
9121
|
-
|
|
9317
|
+
writeFileSync17(c.runScriptPath, c.runScriptSource, "utf-8");
|
|
9122
9318
|
chmodSync5(c.runScriptPath, 493);
|
|
9123
9319
|
}
|
|
9124
9320
|
}
|
|
9125
9321
|
function runBunInstall() {
|
|
9126
9322
|
for (const c of CHANNELS) {
|
|
9127
|
-
const r =
|
|
9323
|
+
const r = spawnSync7("bun", ["install", "--silent"], {
|
|
9128
9324
|
cwd: c.sessionDir,
|
|
9129
9325
|
encoding: "utf-8",
|
|
9130
9326
|
timeout: 12e4
|
|
@@ -9137,10 +9333,10 @@ function runBunInstall() {
|
|
|
9137
9333
|
}
|
|
9138
9334
|
}
|
|
9139
9335
|
function safelyMutateClaudeJson(mutator) {
|
|
9140
|
-
if (!
|
|
9336
|
+
if (!existsSync23(CLAUDE_JSON_PATH)) {
|
|
9141
9337
|
return;
|
|
9142
9338
|
}
|
|
9143
|
-
const originalText =
|
|
9339
|
+
const originalText = readFileSync21(CLAUDE_JSON_PATH, "utf-8");
|
|
9144
9340
|
let parsed;
|
|
9145
9341
|
try {
|
|
9146
9342
|
parsed = JSON.parse(originalText);
|
|
@@ -9172,7 +9368,7 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9172
9368
|
copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
9173
9369
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
9174
9370
|
try {
|
|
9175
|
-
|
|
9371
|
+
writeFileSync17(tmpPath, newText, "utf-8");
|
|
9176
9372
|
const fd = openSync2(tmpPath, "r");
|
|
9177
9373
|
try {
|
|
9178
9374
|
fsyncSync(fd);
|
|
@@ -9205,7 +9401,7 @@ function writeProjectMcpJson() {
|
|
|
9205
9401
|
}
|
|
9206
9402
|
}
|
|
9207
9403
|
};
|
|
9208
|
-
|
|
9404
|
+
writeFileSync17(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
9209
9405
|
}
|
|
9210
9406
|
}
|
|
9211
9407
|
function patchClaudeJson() {
|
|
@@ -9240,15 +9436,15 @@ function patchClaudeJson() {
|
|
|
9240
9436
|
});
|
|
9241
9437
|
}
|
|
9242
9438
|
function installLocalCC() {
|
|
9243
|
-
let bunCheck =
|
|
9439
|
+
let bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
|
|
9244
9440
|
if (bunCheck.status !== 0) {
|
|
9245
9441
|
if (process.platform === "darwin") {
|
|
9246
9442
|
console.log(" Installing bun via brew...");
|
|
9247
|
-
const brewR =
|
|
9443
|
+
const brewR = spawnSync7("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
9248
9444
|
if (brewR.status !== 0) {
|
|
9249
9445
|
throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
|
|
9250
9446
|
}
|
|
9251
|
-
bunCheck =
|
|
9447
|
+
bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
|
|
9252
9448
|
if (bunCheck.status !== 0) {
|
|
9253
9449
|
throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
|
|
9254
9450
|
}
|
|
@@ -9282,42 +9478,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
9282
9478
|
var init_install2 = __esm({
|
|
9283
9479
|
"cli/local-cc/install.ts"() {
|
|
9284
9480
|
"use strict";
|
|
9285
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
9286
|
-
SESSION_DIR =
|
|
9287
|
-
PLUGIN_PATH =
|
|
9288
|
-
PLUGIN_PKG_PATH =
|
|
9289
|
-
PLUGIN_SETTINGS_DIR =
|
|
9290
|
-
PLUGIN_SETTINGS_PATH =
|
|
9291
|
-
PROJECT_MCP_PATH =
|
|
9292
|
-
CLAUDE_JSON_PATH =
|
|
9293
|
-
RUN_SCRIPT_PATH =
|
|
9481
|
+
CLAUDE_JSON_BACKUP_PATH = join19(homedir21(), ".claude.json.synkro-bak");
|
|
9482
|
+
SESSION_DIR = join19(homedir21(), ".synkro", "cc_sessions");
|
|
9483
|
+
PLUGIN_PATH = join19(SESSION_DIR, "synkro-channel.ts");
|
|
9484
|
+
PLUGIN_PKG_PATH = join19(SESSION_DIR, "package.json");
|
|
9485
|
+
PLUGIN_SETTINGS_DIR = join19(SESSION_DIR, ".claude");
|
|
9486
|
+
PLUGIN_SETTINGS_PATH = join19(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
9487
|
+
PROJECT_MCP_PATH = join19(SESSION_DIR, ".mcp.json");
|
|
9488
|
+
CLAUDE_JSON_PATH = join19(homedir21(), ".claude.json");
|
|
9489
|
+
RUN_SCRIPT_PATH = join19(SESSION_DIR, "run-claude.sh");
|
|
9294
9490
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
9295
9491
|
CHANNEL_1_PORT = 8941;
|
|
9296
|
-
SESSION_DIR_2 =
|
|
9297
|
-
PLUGIN_PATH_2 =
|
|
9298
|
-
PLUGIN_PKG_PATH_2 =
|
|
9299
|
-
PLUGIN_SETTINGS_DIR_2 =
|
|
9300
|
-
PLUGIN_SETTINGS_PATH_2 =
|
|
9301
|
-
PROJECT_MCP_PATH_2 =
|
|
9302
|
-
RUN_SCRIPT_PATH_2 =
|
|
9492
|
+
SESSION_DIR_2 = join19(homedir21(), ".synkro", "cc_sessions_2");
|
|
9493
|
+
PLUGIN_PATH_2 = join19(SESSION_DIR_2, "synkro-channel.ts");
|
|
9494
|
+
PLUGIN_PKG_PATH_2 = join19(SESSION_DIR_2, "package.json");
|
|
9495
|
+
PLUGIN_SETTINGS_DIR_2 = join19(SESSION_DIR_2, ".claude");
|
|
9496
|
+
PLUGIN_SETTINGS_PATH_2 = join19(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
9497
|
+
PROJECT_MCP_PATH_2 = join19(SESSION_DIR_2, ".mcp.json");
|
|
9498
|
+
RUN_SCRIPT_PATH_2 = join19(SESSION_DIR_2, "run-claude.sh");
|
|
9303
9499
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
9304
9500
|
CHANNEL_2_PORT = 8951;
|
|
9305
|
-
SESSION_DIR_3 =
|
|
9306
|
-
PLUGIN_PATH_3 =
|
|
9307
|
-
PLUGIN_PKG_PATH_3 =
|
|
9308
|
-
PLUGIN_SETTINGS_DIR_3 =
|
|
9309
|
-
PLUGIN_SETTINGS_PATH_3 =
|
|
9310
|
-
PROJECT_MCP_PATH_3 =
|
|
9311
|
-
RUN_SCRIPT_PATH_3 =
|
|
9501
|
+
SESSION_DIR_3 = join19(homedir21(), ".synkro", "cc_sessions_3");
|
|
9502
|
+
PLUGIN_PATH_3 = join19(SESSION_DIR_3, "synkro-channel.ts");
|
|
9503
|
+
PLUGIN_PKG_PATH_3 = join19(SESSION_DIR_3, "package.json");
|
|
9504
|
+
PLUGIN_SETTINGS_DIR_3 = join19(SESSION_DIR_3, ".claude");
|
|
9505
|
+
PLUGIN_SETTINGS_PATH_3 = join19(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
9506
|
+
PROJECT_MCP_PATH_3 = join19(SESSION_DIR_3, ".mcp.json");
|
|
9507
|
+
RUN_SCRIPT_PATH_3 = join19(SESSION_DIR_3, "run-claude.sh");
|
|
9312
9508
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
9313
9509
|
CHANNEL_3_PORT = 8942;
|
|
9314
|
-
SESSION_DIR_4 =
|
|
9315
|
-
PLUGIN_PATH_4 =
|
|
9316
|
-
PLUGIN_PKG_PATH_4 =
|
|
9317
|
-
PLUGIN_SETTINGS_DIR_4 =
|
|
9318
|
-
PLUGIN_SETTINGS_PATH_4 =
|
|
9319
|
-
PROJECT_MCP_PATH_4 =
|
|
9320
|
-
RUN_SCRIPT_PATH_4 =
|
|
9510
|
+
SESSION_DIR_4 = join19(homedir21(), ".synkro", "cc_sessions_4");
|
|
9511
|
+
PLUGIN_PATH_4 = join19(SESSION_DIR_4, "synkro-channel.ts");
|
|
9512
|
+
PLUGIN_PKG_PATH_4 = join19(SESSION_DIR_4, "package.json");
|
|
9513
|
+
PLUGIN_SETTINGS_DIR_4 = join19(SESSION_DIR_4, ".claude");
|
|
9514
|
+
PLUGIN_SETTINGS_PATH_4 = join19(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
9515
|
+
PROJECT_MCP_PATH_4 = join19(SESSION_DIR_4, ".mcp.json");
|
|
9516
|
+
RUN_SCRIPT_PATH_4 = join19(SESSION_DIR_4, "run-claude.sh");
|
|
9321
9517
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
9322
9518
|
CHANNEL_4_PORT = 8952;
|
|
9323
9519
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -9591,10 +9787,10 @@ var disconnect_exports = {};
|
|
|
9591
9787
|
__export(disconnect_exports, {
|
|
9592
9788
|
disconnectCommand: () => disconnectCommand
|
|
9593
9789
|
});
|
|
9594
|
-
import { existsSync as
|
|
9595
|
-
import { homedir as
|
|
9596
|
-
import { join as
|
|
9597
|
-
import { spawnSync as
|
|
9790
|
+
import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
|
|
9791
|
+
import { homedir as homedir22 } from "os";
|
|
9792
|
+
import { join as join20 } from "path";
|
|
9793
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
9598
9794
|
import { createInterface as createInterface3 } from "readline";
|
|
9599
9795
|
async function tearDownLocalCC() {
|
|
9600
9796
|
const docker = dockerStatus();
|
|
@@ -9608,7 +9804,7 @@ async function tearDownLocalCC() {
|
|
|
9608
9804
|
console.log("\u2713 removed synkro-server container");
|
|
9609
9805
|
try {
|
|
9610
9806
|
const image = imageTag();
|
|
9611
|
-
const r =
|
|
9807
|
+
const r = spawnSync8("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
|
|
9612
9808
|
console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
|
|
9613
9809
|
} catch {
|
|
9614
9810
|
}
|
|
@@ -9694,7 +9890,7 @@ async function disconnectCommand(args2 = [], opts = {}) {
|
|
|
9694
9890
|
if (desktopMcpRemoved) removed.mcp = true;
|
|
9695
9891
|
console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
|
|
9696
9892
|
}
|
|
9697
|
-
if (
|
|
9893
|
+
if (existsSync24(SYNKRO_DIR12)) {
|
|
9698
9894
|
if (purge2) {
|
|
9699
9895
|
emit("uninstall", {
|
|
9700
9896
|
flavor,
|
|
@@ -9703,41 +9899,41 @@ async function disconnectCommand(args2 = [], opts = {}) {
|
|
|
9703
9899
|
duration_ms: Date.now() - startedAt
|
|
9704
9900
|
});
|
|
9705
9901
|
await flush({ force: true });
|
|
9706
|
-
|
|
9902
|
+
rmSync3(SYNKRO_DIR12, { recursive: true, force: true });
|
|
9707
9903
|
removed.config = true;
|
|
9708
|
-
console.log(`\u2713 wiped ${
|
|
9904
|
+
console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
|
|
9709
9905
|
} else {
|
|
9710
9906
|
const keep = /* @__PURE__ */ new Set([
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9907
|
+
join20(SYNKRO_DIR12, "pgdata"),
|
|
9908
|
+
join20(SYNKRO_DIR12, "pgdata-backups"),
|
|
9909
|
+
join20(SYNKRO_DIR12, ".transcript-offsets")
|
|
9714
9910
|
]);
|
|
9715
9911
|
const preserved = [];
|
|
9716
|
-
for (const entry of readdirSync5(
|
|
9717
|
-
const full =
|
|
9912
|
+
for (const entry of readdirSync5(SYNKRO_DIR12)) {
|
|
9913
|
+
const full = join20(SYNKRO_DIR12, entry);
|
|
9718
9914
|
if (keep.has(full)) {
|
|
9719
9915
|
preserved.push(entry);
|
|
9720
9916
|
continue;
|
|
9721
9917
|
}
|
|
9722
|
-
|
|
9918
|
+
rmSync3(full, { recursive: true, force: true });
|
|
9723
9919
|
}
|
|
9724
9920
|
removed.config = true;
|
|
9725
9921
|
if (preserved.length > 0) {
|
|
9726
|
-
console.log(`\u2713 removed Synkro config from ${
|
|
9922
|
+
console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR12} (kept your scan data: ${preserved.join(", ")})`);
|
|
9727
9923
|
console.log(" run `synkro uninstall --purge` to delete that too");
|
|
9728
9924
|
} else {
|
|
9729
|
-
console.log(`\u2713 removed ${
|
|
9925
|
+
console.log(`\u2713 removed ${SYNKRO_DIR12}`);
|
|
9730
9926
|
}
|
|
9731
9927
|
}
|
|
9732
9928
|
} else {
|
|
9733
|
-
console.log(`\xB7 ${
|
|
9929
|
+
console.log(`\xB7 ${SYNKRO_DIR12} already gone`);
|
|
9734
9930
|
}
|
|
9735
9931
|
if (!purge2) {
|
|
9736
9932
|
emit("uninstall", { flavor, purge: purge2, removed, duration_ms: Date.now() - startedAt });
|
|
9737
9933
|
}
|
|
9738
9934
|
console.log(purge2 ? "\nSynkro fully removed \u2014 this machine is clean, as if it was never installed." : "\nSynkro uninstalled. Your scan data is preserved.");
|
|
9739
9935
|
}
|
|
9740
|
-
var
|
|
9936
|
+
var SYNKRO_DIR12;
|
|
9741
9937
|
var init_disconnect = __esm({
|
|
9742
9938
|
"cli/commands/disconnect.ts"() {
|
|
9743
9939
|
"use strict";
|
|
@@ -9752,14 +9948,14 @@ var init_disconnect = __esm({
|
|
|
9752
9948
|
init_dockerInstall();
|
|
9753
9949
|
init_macKeychain();
|
|
9754
9950
|
init_telemetry();
|
|
9755
|
-
|
|
9951
|
+
SYNKRO_DIR12 = join20(homedir22(), ".synkro");
|
|
9756
9952
|
}
|
|
9757
9953
|
});
|
|
9758
9954
|
|
|
9759
9955
|
// cli/local-cc/turnLog.ts
|
|
9760
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
9761
|
-
import { dirname as dirname7, join as
|
|
9762
|
-
import { homedir as
|
|
9956
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync22, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
|
|
9957
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
9958
|
+
import { homedir as homedir23 } from "os";
|
|
9763
9959
|
function truncate(s, max = PREVIEW_MAX) {
|
|
9764
9960
|
if (s.length <= max) return s;
|
|
9765
9961
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -9779,7 +9975,7 @@ function extractSeverity(result) {
|
|
|
9779
9975
|
}
|
|
9780
9976
|
function appendTurn(args2) {
|
|
9781
9977
|
try {
|
|
9782
|
-
|
|
9978
|
+
mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
|
|
9783
9979
|
const entry = {
|
|
9784
9980
|
ts: new Date(args2.startedAt).toISOString(),
|
|
9785
9981
|
role: args2.role,
|
|
@@ -9795,11 +9991,11 @@ function appendTurn(args2) {
|
|
|
9795
9991
|
}
|
|
9796
9992
|
}
|
|
9797
9993
|
function readRecentTurns(n = 20) {
|
|
9798
|
-
if (!
|
|
9994
|
+
if (!existsSync25(TURN_LOG_PATH)) return [];
|
|
9799
9995
|
try {
|
|
9800
9996
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
9801
9997
|
if (size === 0) return [];
|
|
9802
|
-
const text =
|
|
9998
|
+
const text = readFileSync22(TURN_LOG_PATH, "utf-8");
|
|
9803
9999
|
const lines = text.split("\n").filter(Boolean);
|
|
9804
10000
|
const lastN = lines.slice(-n).reverse();
|
|
9805
10001
|
return lastN.map((line) => {
|
|
@@ -9815,8 +10011,8 @@ function readRecentTurns(n = 20) {
|
|
|
9815
10011
|
}
|
|
9816
10012
|
function followTurns(onEntry) {
|
|
9817
10013
|
try {
|
|
9818
|
-
|
|
9819
|
-
if (!
|
|
10014
|
+
mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
|
|
10015
|
+
if (!existsSync25(TURN_LOG_PATH)) {
|
|
9820
10016
|
appendFileSync3(TURN_LOG_PATH, "", "utf-8");
|
|
9821
10017
|
}
|
|
9822
10018
|
} catch {
|
|
@@ -9878,7 +10074,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
9878
10074
|
var init_turnLog = __esm({
|
|
9879
10075
|
"cli/local-cc/turnLog.ts"() {
|
|
9880
10076
|
"use strict";
|
|
9881
|
-
TURN_LOG_PATH =
|
|
10077
|
+
TURN_LOG_PATH = join21(homedir23(), ".synkro", "cc_sessions", "turns.log");
|
|
9882
10078
|
PREVIEW_MAX = 400;
|
|
9883
10079
|
}
|
|
9884
10080
|
});
|
|
@@ -10032,8 +10228,8 @@ __export(scanPr_exports, {
|
|
|
10032
10228
|
scanPrCommand: () => scanPrCommand
|
|
10033
10229
|
});
|
|
10034
10230
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
10035
|
-
import { readFileSync as
|
|
10036
|
-
import { join as
|
|
10231
|
+
import { readFileSync as readFileSync23, existsSync as existsSync26 } from "fs";
|
|
10232
|
+
import { join as join22 } from "path";
|
|
10037
10233
|
function parseMatchSpec(condition) {
|
|
10038
10234
|
if (!condition.startsWith("match_spec:")) return null;
|
|
10039
10235
|
try {
|
|
@@ -10512,10 +10708,10 @@ function shouldFail(findings, threshold) {
|
|
|
10512
10708
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
10513
10709
|
}
|
|
10514
10710
|
function readRepoDeps() {
|
|
10515
|
-
const pkgPath =
|
|
10516
|
-
if (!
|
|
10711
|
+
const pkgPath = join22(process.cwd(), "package.json");
|
|
10712
|
+
if (!existsSync26(pkgPath)) return {};
|
|
10517
10713
|
try {
|
|
10518
|
-
const pkg = JSON.parse(
|
|
10714
|
+
const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
|
|
10519
10715
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
10520
10716
|
} catch {
|
|
10521
10717
|
return {};
|
|
@@ -10759,15 +10955,15 @@ var routeDecide_exports = {};
|
|
|
10759
10955
|
__export(routeDecide_exports, {
|
|
10760
10956
|
routeDecide: () => routeDecide
|
|
10761
10957
|
});
|
|
10762
|
-
import { readFileSync as
|
|
10763
|
-
import { homedir as
|
|
10764
|
-
import { join as
|
|
10958
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
|
|
10959
|
+
import { homedir as homedir24 } from "os";
|
|
10960
|
+
import { join as join23 } from "path";
|
|
10765
10961
|
function safeSid(sid) {
|
|
10766
10962
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
10767
10963
|
}
|
|
10768
10964
|
function loadMcpJwt() {
|
|
10769
10965
|
try {
|
|
10770
|
-
return
|
|
10966
|
+
return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
|
|
10771
10967
|
} catch {
|
|
10772
10968
|
return "";
|
|
10773
10969
|
}
|
|
@@ -10777,7 +10973,7 @@ async function routeDecide(sessionId) {
|
|
|
10777
10973
|
const sid = safeSid(sessionId);
|
|
10778
10974
|
let prompt = "";
|
|
10779
10975
|
try {
|
|
10780
|
-
const rec = JSON.parse(
|
|
10976
|
+
const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR2, sid + ".json"), "utf-8"));
|
|
10781
10977
|
prompt = String(rec.last_prompt || "").trim();
|
|
10782
10978
|
} catch {
|
|
10783
10979
|
return;
|
|
@@ -10800,29 +10996,29 @@ async function routeDecide(sessionId) {
|
|
|
10800
10996
|
return;
|
|
10801
10997
|
}
|
|
10802
10998
|
if (!model || !VALID_MODELS.has(model)) return;
|
|
10803
|
-
const lastFile =
|
|
10999
|
+
const lastFile = join23(PTY_DIR, "route-last-" + sid);
|
|
10804
11000
|
let last = "";
|
|
10805
11001
|
try {
|
|
10806
|
-
last =
|
|
11002
|
+
last = readFileSync24(lastFile, "utf-8").trim();
|
|
10807
11003
|
} catch {
|
|
10808
11004
|
}
|
|
10809
11005
|
if (model === last) return;
|
|
10810
11006
|
try {
|
|
10811
|
-
|
|
11007
|
+
writeFileSync18(lastFile, model);
|
|
10812
11008
|
} catch {
|
|
10813
11009
|
}
|
|
10814
11010
|
try {
|
|
10815
|
-
|
|
11011
|
+
writeFileSync18(join23(PTY_DIR, "route-" + sid), model);
|
|
10816
11012
|
} catch {
|
|
10817
11013
|
}
|
|
10818
11014
|
}
|
|
10819
|
-
var
|
|
11015
|
+
var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
|
|
10820
11016
|
var init_routeDecide = __esm({
|
|
10821
11017
|
"cli/local-cc/routeDecide.ts"() {
|
|
10822
11018
|
"use strict";
|
|
10823
|
-
|
|
10824
|
-
PTY_DIR =
|
|
10825
|
-
SESSIONS_DIR2 =
|
|
11019
|
+
SYNKRO_DIR13 = join23(homedir24(), ".synkro");
|
|
11020
|
+
PTY_DIR = join23(SYNKRO_DIR13, "pty");
|
|
11021
|
+
SESSIONS_DIR2 = join23(PTY_DIR, "sessions");
|
|
10826
11022
|
VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
10827
11023
|
GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
10828
11024
|
}
|
|
@@ -10833,10 +11029,10 @@ var routeOrchestrate_exports = {};
|
|
|
10833
11029
|
__export(routeOrchestrate_exports, {
|
|
10834
11030
|
routeAndResubmit: () => routeAndResubmit
|
|
10835
11031
|
});
|
|
10836
|
-
import { readFileSync as
|
|
10837
|
-
import { homedir as
|
|
10838
|
-
import { join as
|
|
10839
|
-
import { spawnSync as
|
|
11032
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
11033
|
+
import { homedir as homedir25 } from "os";
|
|
11034
|
+
import { join as join24 } from "path";
|
|
11035
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
10840
11036
|
function safeSid2(sid) {
|
|
10841
11037
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
10842
11038
|
}
|
|
@@ -10845,7 +11041,7 @@ function safeSession(s) {
|
|
|
10845
11041
|
}
|
|
10846
11042
|
function loadMcpJwt2() {
|
|
10847
11043
|
try {
|
|
10848
|
-
return
|
|
11044
|
+
return readFileSync25(join24(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
10849
11045
|
} catch {
|
|
10850
11046
|
return "";
|
|
10851
11047
|
}
|
|
@@ -10871,7 +11067,7 @@ async function classifyTask(prompt) {
|
|
|
10871
11067
|
}
|
|
10872
11068
|
function lastRoutedModel(sid) {
|
|
10873
11069
|
try {
|
|
10874
|
-
const v =
|
|
11070
|
+
const v = readFileSync25(join24(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
|
|
10875
11071
|
return VALID_MODELS2.has(v) ? v : "";
|
|
10876
11072
|
} catch {
|
|
10877
11073
|
return "";
|
|
@@ -10881,12 +11077,12 @@ function resolveSession(sid, tmuxSession) {
|
|
|
10881
11077
|
const candidates = [];
|
|
10882
11078
|
if (tmuxSession) candidates.push(tmuxSession);
|
|
10883
11079
|
try {
|
|
10884
|
-
const rec = JSON.parse(
|
|
11080
|
+
const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
|
|
10885
11081
|
if (rec.tmux_session) candidates.push(rec.tmux_session);
|
|
10886
11082
|
} catch {
|
|
10887
11083
|
}
|
|
10888
11084
|
try {
|
|
10889
|
-
candidates.push(
|
|
11085
|
+
candidates.push(readFileSync25(ACTIVE_SESSION_FILE2, "utf-8").trim());
|
|
10890
11086
|
} catch {
|
|
10891
11087
|
}
|
|
10892
11088
|
for (const c of candidates) if (c && safeSession(c)) return c;
|
|
@@ -10898,12 +11094,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
|
|
|
10898
11094
|
const sid = safeSid2(sessionId);
|
|
10899
11095
|
const session = resolveSession(sid, tmuxSession);
|
|
10900
11096
|
if (!session) return;
|
|
10901
|
-
if (
|
|
11097
|
+
if (spawnSync9("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
|
|
10902
11098
|
const current = lastRoutedModel(sid);
|
|
10903
11099
|
const picked = forceModel && VALID_MODELS2.has(forceModel) ? forceModel : await classifyTask(trimmed);
|
|
10904
11100
|
const doSwap = !!picked && picked !== "keep" && picked !== current;
|
|
10905
|
-
const sk = (...a) =>
|
|
10906
|
-
const capture = () =>
|
|
11101
|
+
const sk = (...a) => spawnSync9("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
|
|
11102
|
+
const capture = () => spawnSync9("tmux", ["capture-pane", "-t", session, "-p", "-S", "-25"], { encoding: "utf-8" }).stdout || "";
|
|
10907
11103
|
await wait(400);
|
|
10908
11104
|
if (doSwap) {
|
|
10909
11105
|
sk("C-u");
|
|
@@ -10921,29 +11117,29 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
|
|
|
10921
11117
|
}
|
|
10922
11118
|
await wait(500);
|
|
10923
11119
|
try {
|
|
10924
|
-
|
|
11120
|
+
writeFileSync19(join24(PTY_DIR2, "route-last-" + sid), picked);
|
|
10925
11121
|
} catch {
|
|
10926
11122
|
}
|
|
10927
11123
|
}
|
|
10928
11124
|
try {
|
|
10929
|
-
|
|
11125
|
+
writeFileSync19(join24(PTY_DIR2, "route-guard-" + sid), "1");
|
|
10930
11126
|
} catch {
|
|
10931
11127
|
}
|
|
10932
11128
|
sk("C-u");
|
|
10933
11129
|
await wait(80);
|
|
10934
|
-
|
|
10935
|
-
|
|
11130
|
+
spawnSync9("tmux", ["set-buffer", "-b", "synkro-route", trimmed], { encoding: "utf-8" });
|
|
11131
|
+
spawnSync9("tmux", ["paste-buffer", "-t", session, "-b", "synkro-route", "-d"], { encoding: "utf-8" });
|
|
10936
11132
|
await wait(120);
|
|
10937
11133
|
sk("Enter");
|
|
10938
11134
|
}
|
|
10939
|
-
var
|
|
11135
|
+
var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
|
|
10940
11136
|
var init_routeOrchestrate = __esm({
|
|
10941
11137
|
"cli/local-cc/routeOrchestrate.ts"() {
|
|
10942
11138
|
"use strict";
|
|
10943
|
-
|
|
10944
|
-
PTY_DIR2 =
|
|
10945
|
-
SESSIONS_DIR3 =
|
|
10946
|
-
ACTIVE_SESSION_FILE2 =
|
|
11139
|
+
SYNKRO_DIR14 = join24(homedir25(), ".synkro");
|
|
11140
|
+
PTY_DIR2 = join24(SYNKRO_DIR14, "pty");
|
|
11141
|
+
SESSIONS_DIR3 = join24(PTY_DIR2, "sessions");
|
|
11142
|
+
ACTIVE_SESSION_FILE2 = join24(PTY_DIR2, "active");
|
|
10947
11143
|
VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
10948
11144
|
GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
10949
11145
|
wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -10955,14 +11151,14 @@ var routingToggle_exports = {};
|
|
|
10955
11151
|
__export(routingToggle_exports, {
|
|
10956
11152
|
routingCommand: () => routingCommand
|
|
10957
11153
|
});
|
|
10958
|
-
import { writeFileSync as
|
|
10959
|
-
import { homedir as
|
|
10960
|
-
import { join as
|
|
11154
|
+
import { writeFileSync as writeFileSync20, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
|
|
11155
|
+
import { homedir as homedir26 } from "os";
|
|
11156
|
+
import { join as join25 } from "path";
|
|
10961
11157
|
function safeSid3(sid) {
|
|
10962
11158
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
10963
11159
|
}
|
|
10964
11160
|
function markerFor(session) {
|
|
10965
|
-
return session ?
|
|
11161
|
+
return session ? join25(PTY_DIR3, "routing-on-" + safeSid3(session)) : join25(PTY_DIR3, "routing-on");
|
|
10966
11162
|
}
|
|
10967
11163
|
function routingCommand(args2) {
|
|
10968
11164
|
const sub = (args2[0] || "status").trim();
|
|
@@ -10971,7 +11167,7 @@ function routingCommand(args2) {
|
|
|
10971
11167
|
if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
|
|
10972
11168
|
if (sub === "on") {
|
|
10973
11169
|
try {
|
|
10974
|
-
|
|
11170
|
+
writeFileSync20(markerFor(session), "1");
|
|
10975
11171
|
} catch (e) {
|
|
10976
11172
|
console.error("routing on failed:", String(e));
|
|
10977
11173
|
return;
|
|
@@ -10983,7 +11179,7 @@ function routingCommand(args2) {
|
|
|
10983
11179
|
if (sub === "off") {
|
|
10984
11180
|
let removed = 0;
|
|
10985
11181
|
if (session) {
|
|
10986
|
-
if (
|
|
11182
|
+
if (existsSync27(markerFor(session))) {
|
|
10987
11183
|
try {
|
|
10988
11184
|
unlinkSync9(markerFor(session));
|
|
10989
11185
|
removed++;
|
|
@@ -10994,7 +11190,7 @@ function routingCommand(args2) {
|
|
|
10994
11190
|
for (const f of safeList()) {
|
|
10995
11191
|
if (f === "routing-on" || f.startsWith("routing-on-")) {
|
|
10996
11192
|
try {
|
|
10997
|
-
unlinkSync9(
|
|
11193
|
+
unlinkSync9(join25(PTY_DIR3, f));
|
|
10998
11194
|
removed++;
|
|
10999
11195
|
} catch {
|
|
11000
11196
|
}
|
|
@@ -11026,24 +11222,24 @@ var PTY_DIR3;
|
|
|
11026
11222
|
var init_routingToggle = __esm({
|
|
11027
11223
|
"cli/local-cc/routingToggle.ts"() {
|
|
11028
11224
|
"use strict";
|
|
11029
|
-
PTY_DIR3 =
|
|
11225
|
+
PTY_DIR3 = join25(homedir26(), ".synkro", "pty");
|
|
11030
11226
|
}
|
|
11031
11227
|
});
|
|
11032
11228
|
|
|
11033
11229
|
// cli/local-cc/pueue.ts
|
|
11034
|
-
import { execFileSync as execFileSync4, spawnSync as
|
|
11035
|
-
import { homedir as
|
|
11036
|
-
import { join as
|
|
11230
|
+
import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
|
|
11231
|
+
import { homedir as homedir27 } from "os";
|
|
11232
|
+
import { join as join26 } from "path";
|
|
11037
11233
|
import { connect as connect2 } from "net";
|
|
11038
11234
|
function pueueAvailable() {
|
|
11039
|
-
const r =
|
|
11235
|
+
const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
11040
11236
|
if (r.status !== 0) {
|
|
11041
11237
|
throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
|
|
11042
11238
|
}
|
|
11043
11239
|
}
|
|
11044
11240
|
function statusJson() {
|
|
11045
11241
|
pueueAvailable();
|
|
11046
|
-
const r =
|
|
11242
|
+
const r = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8" });
|
|
11047
11243
|
if (r.status !== 0) {
|
|
11048
11244
|
throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
|
|
11049
11245
|
}
|
|
@@ -11088,18 +11284,18 @@ function startTask(opts = {}) {
|
|
|
11088
11284
|
let existing = findTask(ch);
|
|
11089
11285
|
while (existing) {
|
|
11090
11286
|
if (existing.status === "Running" || existing.status === "Queued") {
|
|
11091
|
-
|
|
11092
|
-
|
|
11287
|
+
spawnSync10("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
|
|
11288
|
+
spawnSync10("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
|
|
11093
11289
|
for (let i = 0; i < 10; i++) {
|
|
11094
11290
|
const check = findTask(ch);
|
|
11095
11291
|
if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
11096
|
-
|
|
11292
|
+
spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
|
|
11097
11293
|
}
|
|
11098
11294
|
}
|
|
11099
|
-
|
|
11295
|
+
spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
11100
11296
|
existing = findTask(ch);
|
|
11101
11297
|
}
|
|
11102
|
-
const runScript =
|
|
11298
|
+
const runScript = join26(cwd, "run-claude.sh");
|
|
11103
11299
|
const args2 = [
|
|
11104
11300
|
"add",
|
|
11105
11301
|
"--label",
|
|
@@ -11110,7 +11306,7 @@ function startTask(opts = {}) {
|
|
|
11110
11306
|
"bash",
|
|
11111
11307
|
runScript
|
|
11112
11308
|
];
|
|
11113
|
-
const r =
|
|
11309
|
+
const r = spawnSync10("pueue", args2, { encoding: "utf-8" });
|
|
11114
11310
|
if (r.status !== 0) {
|
|
11115
11311
|
throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
|
|
11116
11312
|
}
|
|
@@ -11121,25 +11317,25 @@ function startTask(opts = {}) {
|
|
|
11121
11317
|
return created;
|
|
11122
11318
|
}
|
|
11123
11319
|
function stopTask(channel = CHANNEL_PRIMARY) {
|
|
11124
|
-
|
|
11320
|
+
spawnSync10("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
|
|
11125
11321
|
let t = findTask(channel);
|
|
11126
11322
|
while (t) {
|
|
11127
11323
|
if (t.status === "Running" || t.status === "Queued") {
|
|
11128
|
-
|
|
11324
|
+
spawnSync10("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
|
|
11129
11325
|
for (let i = 0; i < 10; i++) {
|
|
11130
11326
|
const check = findTask(channel);
|
|
11131
11327
|
if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
11132
|
-
|
|
11328
|
+
spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
|
|
11133
11329
|
}
|
|
11134
11330
|
}
|
|
11135
|
-
|
|
11331
|
+
spawnSync10("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
|
|
11136
11332
|
t = findTask(channel);
|
|
11137
11333
|
}
|
|
11138
11334
|
}
|
|
11139
11335
|
function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
|
|
11140
11336
|
const t = findTask(channel);
|
|
11141
11337
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
11142
|
-
const r =
|
|
11338
|
+
const r = spawnSync10("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
|
|
11143
11339
|
return r.stdout || r.stderr || "(no output)";
|
|
11144
11340
|
}
|
|
11145
11341
|
function ensureRunning(opts = {}) {
|
|
@@ -11164,8 +11360,8 @@ function probePort(host, port, timeoutMs = 500) {
|
|
|
11164
11360
|
});
|
|
11165
11361
|
}
|
|
11166
11362
|
function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
|
|
11167
|
-
|
|
11168
|
-
|
|
11363
|
+
spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
|
|
11364
|
+
spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
|
|
11169
11365
|
}
|
|
11170
11366
|
async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
|
|
11171
11367
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -11177,46 +11373,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
|
|
|
11177
11373
|
return probePort(host, port);
|
|
11178
11374
|
}
|
|
11179
11375
|
function brewInstall(pkg) {
|
|
11180
|
-
const brew =
|
|
11376
|
+
const brew = spawnSync10("brew", ["--version"], { encoding: "utf-8" });
|
|
11181
11377
|
if (brew.status !== 0) return false;
|
|
11182
11378
|
console.log(` Installing ${pkg} via brew...`);
|
|
11183
|
-
const r =
|
|
11379
|
+
const r = spawnSync10("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
11184
11380
|
return r.status === 0;
|
|
11185
11381
|
}
|
|
11186
11382
|
function assertPueueInstalled() {
|
|
11187
|
-
let r =
|
|
11383
|
+
let r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
11188
11384
|
if (r.status !== 0) {
|
|
11189
11385
|
if (process.platform === "darwin" && brewInstall("pueue")) {
|
|
11190
|
-
r =
|
|
11386
|
+
r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
11191
11387
|
if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
|
|
11192
11388
|
} else {
|
|
11193
11389
|
throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
|
|
11194
11390
|
}
|
|
11195
11391
|
}
|
|
11196
|
-
const status =
|
|
11392
|
+
const status = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
11197
11393
|
if (status.status !== 0) {
|
|
11198
11394
|
console.log(" Starting pueued daemon...");
|
|
11199
11395
|
const child = spawn6("pueued", ["-d"], { stdio: "ignore", detached: true });
|
|
11200
11396
|
child.unref();
|
|
11201
|
-
|
|
11202
|
-
const retry =
|
|
11397
|
+
spawnSync10("sleep", ["1"]);
|
|
11398
|
+
const retry = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
11203
11399
|
if (retry.status !== 0) {
|
|
11204
11400
|
throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
|
|
11205
11401
|
}
|
|
11206
11402
|
}
|
|
11207
|
-
|
|
11403
|
+
spawnSync10("pueue", ["parallel", "2"], { encoding: "utf-8" });
|
|
11208
11404
|
}
|
|
11209
11405
|
function assertClaudeInstalled() {
|
|
11210
|
-
const r =
|
|
11406
|
+
const r = spawnSync10("claude", ["--version"], { encoding: "utf-8" });
|
|
11211
11407
|
if (r.status !== 0) {
|
|
11212
11408
|
throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
11213
11409
|
}
|
|
11214
11410
|
}
|
|
11215
11411
|
function assertTmuxInstalled() {
|
|
11216
|
-
let r =
|
|
11412
|
+
let r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
|
|
11217
11413
|
if (r.status !== 0) {
|
|
11218
11414
|
if (process.platform === "darwin" && brewInstall("tmux")) {
|
|
11219
|
-
r =
|
|
11415
|
+
r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
|
|
11220
11416
|
if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
|
|
11221
11417
|
} else {
|
|
11222
11418
|
throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
|
|
@@ -11229,12 +11425,12 @@ var init_pueue = __esm({
|
|
|
11229
11425
|
"use strict";
|
|
11230
11426
|
TASK_LABEL = "synkro-local-cc";
|
|
11231
11427
|
TMUX_SESSION = "synkro-local-cc";
|
|
11232
|
-
SESSION_DIR2 =
|
|
11428
|
+
SESSION_DIR2 = join26(homedir27(), ".synkro", "cc_sessions");
|
|
11233
11429
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
11234
11430
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
11235
|
-
SESSION_DIR_22 =
|
|
11236
|
-
SESSION_DIR_32 =
|
|
11237
|
-
SESSION_DIR_42 =
|
|
11431
|
+
SESSION_DIR_22 = join26(homedir27(), ".synkro", "cc_sessions_2");
|
|
11432
|
+
SESSION_DIR_32 = join26(homedir27(), ".synkro", "cc_sessions_3");
|
|
11433
|
+
SESSION_DIR_42 = join26(homedir27(), ".synkro", "cc_sessions_4");
|
|
11238
11434
|
PueueError = class extends Error {
|
|
11239
11435
|
constructor(message, cause) {
|
|
11240
11436
|
super(message);
|
|
@@ -11249,13 +11445,13 @@ var init_pueue = __esm({
|
|
|
11249
11445
|
});
|
|
11250
11446
|
|
|
11251
11447
|
// cli/local-cc/settings.ts
|
|
11252
|
-
import { existsSync as
|
|
11253
|
-
import { homedir as
|
|
11254
|
-
import { join as
|
|
11448
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
|
|
11449
|
+
import { homedir as homedir28 } from "os";
|
|
11450
|
+
import { join as join27 } from "path";
|
|
11255
11451
|
function isLocalCCEnabled() {
|
|
11256
|
-
if (!
|
|
11452
|
+
if (!existsSync28(CONFIG_PATH5)) return false;
|
|
11257
11453
|
try {
|
|
11258
|
-
const content =
|
|
11454
|
+
const content = readFileSync26(CONFIG_PATH5, "utf-8");
|
|
11259
11455
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
11260
11456
|
return match?.[1] === "yes";
|
|
11261
11457
|
} catch {
|
|
@@ -11266,7 +11462,7 @@ var CONFIG_PATH5;
|
|
|
11266
11462
|
var init_settings = __esm({
|
|
11267
11463
|
"cli/local-cc/settings.ts"() {
|
|
11268
11464
|
"use strict";
|
|
11269
|
-
CONFIG_PATH5 =
|
|
11465
|
+
CONFIG_PATH5 = join27(homedir28(), ".synkro", "config.env");
|
|
11270
11466
|
}
|
|
11271
11467
|
});
|
|
11272
11468
|
|
|
@@ -11275,11 +11471,11 @@ var localCc_exports = {};
|
|
|
11275
11471
|
__export(localCc_exports, {
|
|
11276
11472
|
localCcCommand: () => localCcCommand
|
|
11277
11473
|
});
|
|
11278
|
-
import { spawnSync as
|
|
11279
|
-
import { homedir as
|
|
11280
|
-
import { join as
|
|
11474
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
11475
|
+
import { homedir as homedir29 } from "os";
|
|
11476
|
+
import { join as join28 } from "path";
|
|
11281
11477
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
11282
|
-
import { existsSync as
|
|
11478
|
+
import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
|
|
11283
11479
|
function deploymentMode() {
|
|
11284
11480
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
11285
11481
|
if (env === "docker") return "docker";
|
|
@@ -11385,15 +11581,15 @@ TROUBLESHOOTING
|
|
|
11385
11581
|
`);
|
|
11386
11582
|
}
|
|
11387
11583
|
function readGatewayUrl() {
|
|
11388
|
-
if (
|
|
11389
|
-
const m =
|
|
11584
|
+
if (existsSync29(CONFIG_PATH6)) {
|
|
11585
|
+
const m = readFileSync27(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
11390
11586
|
if (m) return m[1];
|
|
11391
11587
|
}
|
|
11392
11588
|
return "https://api.synkro.sh";
|
|
11393
11589
|
}
|
|
11394
11590
|
function updateLocalInferenceFlag(enabled) {
|
|
11395
|
-
if (!
|
|
11396
|
-
let content =
|
|
11591
|
+
if (!existsSync29(CONFIG_PATH6)) return;
|
|
11592
|
+
let content = readFileSync27(CONFIG_PATH6, "utf-8");
|
|
11397
11593
|
const flag = enabled ? "yes" : "no";
|
|
11398
11594
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
11399
11595
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -11402,7 +11598,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
11402
11598
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
11403
11599
|
`;
|
|
11404
11600
|
}
|
|
11405
|
-
|
|
11601
|
+
writeFileSync21(CONFIG_PATH6, content, "utf-8");
|
|
11406
11602
|
}
|
|
11407
11603
|
async function setServerGradingProvider(provider) {
|
|
11408
11604
|
await ensureValidToken();
|
|
@@ -11456,7 +11652,7 @@ async function cmdStatus() {
|
|
|
11456
11652
|
}
|
|
11457
11653
|
const ch1Up = await isChannelAvailable();
|
|
11458
11654
|
console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
|
|
11459
|
-
const tmux1 =
|
|
11655
|
+
const tmux1 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
11460
11656
|
console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
|
|
11461
11657
|
const t2 = findTask(CHANNEL_SECONDARY);
|
|
11462
11658
|
if (!t2) {
|
|
@@ -11466,7 +11662,7 @@ async function cmdStatus() {
|
|
|
11466
11662
|
}
|
|
11467
11663
|
const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
|
|
11468
11664
|
console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
|
|
11469
|
-
const tmux2 =
|
|
11665
|
+
const tmux2 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
|
|
11470
11666
|
console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
|
|
11471
11667
|
}
|
|
11472
11668
|
async function cmdEnable() {
|
|
@@ -11523,9 +11719,9 @@ async function warmChannels(ready1, ready2) {
|
|
|
11523
11719
|
async function cmdStart(rest = []) {
|
|
11524
11720
|
if (inDockerMode()) {
|
|
11525
11721
|
if (rest.length > 0) {
|
|
11526
|
-
const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);
|
|
11527
|
-
console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);
|
|
11528
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers });
|
|
11722
|
+
const { claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest);
|
|
11723
|
+
console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)...`);
|
|
11724
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
|
|
11529
11725
|
const ready3 = await waitForContainerReady(6e4);
|
|
11530
11726
|
console.log(ready3 ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
|
|
11531
11727
|
return;
|
|
@@ -11571,18 +11767,19 @@ async function cmdRestart(rest = []) {
|
|
|
11571
11767
|
const { explicit } = resolveWorkerConfig(rest);
|
|
11572
11768
|
let claudeWorkers;
|
|
11573
11769
|
let cursorWorkers;
|
|
11770
|
+
let codexWorkers;
|
|
11574
11771
|
if (explicit) {
|
|
11575
|
-
({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
|
|
11772
|
+
({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
|
|
11576
11773
|
} else {
|
|
11577
11774
|
const reconciled = reconcileHarness();
|
|
11578
11775
|
if (reconciled) {
|
|
11579
|
-
({ claudeWorkers, cursorWorkers } = reconciled);
|
|
11776
|
+
({ claudeWorkers, cursorWorkers, codexWorkers } = reconciled);
|
|
11580
11777
|
} else {
|
|
11581
|
-
({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
|
|
11778
|
+
({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
|
|
11582
11779
|
}
|
|
11583
11780
|
}
|
|
11584
|
-
console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);
|
|
11585
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers });
|
|
11781
|
+
console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex, pulling latest image)...`);
|
|
11782
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
|
|
11586
11783
|
const ready = await waitForContainerReady(6e4);
|
|
11587
11784
|
console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
|
|
11588
11785
|
if (ready) {
|
|
@@ -11672,7 +11869,7 @@ function cmdLogs(rest) {
|
|
|
11672
11869
|
}
|
|
11673
11870
|
return "200";
|
|
11674
11871
|
})();
|
|
11675
|
-
|
|
11872
|
+
spawnSync11("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
|
|
11676
11873
|
return;
|
|
11677
11874
|
}
|
|
11678
11875
|
for (const arg of rest) {
|
|
@@ -11720,7 +11917,7 @@ function cmdLogs(rest) {
|
|
|
11720
11917
|
function cmdAttach(rest) {
|
|
11721
11918
|
assertTmuxInstalled();
|
|
11722
11919
|
const readonly = rest.some((a) => a === "--readonly" || a === "-r");
|
|
11723
|
-
const has =
|
|
11920
|
+
const has = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
11724
11921
|
if (has.status !== 0) {
|
|
11725
11922
|
console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
|
|
11726
11923
|
process.exit(1);
|
|
@@ -11733,7 +11930,7 @@ function cmdAttach(rest) {
|
|
|
11733
11930
|
console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
|
|
11734
11931
|
console.log();
|
|
11735
11932
|
const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
|
|
11736
|
-
const r =
|
|
11933
|
+
const r = spawnSync11("tmux", args2, { stdio: "inherit" });
|
|
11737
11934
|
process.exit(r.status ?? 0);
|
|
11738
11935
|
}
|
|
11739
11936
|
async function cmdTest() {
|
|
@@ -11834,8 +12031,8 @@ var init_localCc = __esm({
|
|
|
11834
12031
|
init_install();
|
|
11835
12032
|
init_client2();
|
|
11836
12033
|
init_stub();
|
|
11837
|
-
SYNKRO_CONFIG_PATH =
|
|
11838
|
-
CONFIG_PATH6 =
|
|
12034
|
+
SYNKRO_CONFIG_PATH = join28(homedir29(), ".synkro", "config.env");
|
|
12035
|
+
CONFIG_PATH6 = join28(homedir29(), ".synkro", "config.env");
|
|
11839
12036
|
}
|
|
11840
12037
|
});
|
|
11841
12038
|
|
|
@@ -11844,14 +12041,14 @@ var import_exports = {};
|
|
|
11844
12041
|
__export(import_exports, {
|
|
11845
12042
|
importCommand: () => importCommand
|
|
11846
12043
|
});
|
|
11847
|
-
import { existsSync as
|
|
11848
|
-
import { homedir as
|
|
11849
|
-
import { join as
|
|
12044
|
+
import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync7 } from "fs";
|
|
12045
|
+
import { homedir as homedir30 } from "os";
|
|
12046
|
+
import { join as join29 } from "path";
|
|
11850
12047
|
import { execSync as execSync6 } from "child_process";
|
|
11851
12048
|
import { createInterface as createInterface4 } from "readline";
|
|
11852
12049
|
function readMcpJwt() {
|
|
11853
12050
|
try {
|
|
11854
|
-
return
|
|
12051
|
+
return readFileSync28(join29(homedir30(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
11855
12052
|
} catch {
|
|
11856
12053
|
return "";
|
|
11857
12054
|
}
|
|
@@ -11859,7 +12056,7 @@ function readMcpJwt() {
|
|
|
11859
12056
|
function readConfigEnv2() {
|
|
11860
12057
|
const out = {};
|
|
11861
12058
|
try {
|
|
11862
|
-
for (const line of
|
|
12059
|
+
for (const line of readFileSync28(CONFIG_PATH7, "utf-8").split("\n")) {
|
|
11863
12060
|
const t = line.trim();
|
|
11864
12061
|
if (!t || t.startsWith("#")) continue;
|
|
11865
12062
|
const eq = t.indexOf("=");
|
|
@@ -11871,8 +12068,8 @@ function readConfigEnv2() {
|
|
|
11871
12068
|
}
|
|
11872
12069
|
function projectsFolder() {
|
|
11873
12070
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
11874
|
-
const dir =
|
|
11875
|
-
return
|
|
12071
|
+
const dir = join29(homedir30(), ".claude", "projects", sanitized);
|
|
12072
|
+
return existsSync30(dir) ? dir : null;
|
|
11876
12073
|
}
|
|
11877
12074
|
function repoName() {
|
|
11878
12075
|
try {
|
|
@@ -11911,7 +12108,7 @@ function extractToolResultText(content, e) {
|
|
|
11911
12108
|
return t;
|
|
11912
12109
|
}
|
|
11913
12110
|
function parseSession(filePath, sessionId) {
|
|
11914
|
-
const lines =
|
|
12111
|
+
const lines = readFileSync28(filePath, "utf-8").split("\n").filter(Boolean);
|
|
11915
12112
|
const messages = [];
|
|
11916
12113
|
const actions = [];
|
|
11917
12114
|
let step = 0;
|
|
@@ -11991,7 +12188,7 @@ async function importCommand() {
|
|
|
11991
12188
|
return;
|
|
11992
12189
|
}
|
|
11993
12190
|
}
|
|
11994
|
-
const sessions = files.map((f) => parseSession(
|
|
12191
|
+
const sessions = files.map((f) => parseSession(join29(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
11995
12192
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
11996
12193
|
let ok = 0, fail = 0;
|
|
11997
12194
|
if (isCloud) {
|
|
@@ -12066,7 +12263,7 @@ var init_import = __esm({
|
|
|
12066
12263
|
"cli/commands/import.ts"() {
|
|
12067
12264
|
"use strict";
|
|
12068
12265
|
init_stub();
|
|
12069
|
-
CONFIG_PATH7 =
|
|
12266
|
+
CONFIG_PATH7 = join29(homedir30(), ".synkro", "config.env");
|
|
12070
12267
|
}
|
|
12071
12268
|
});
|
|
12072
12269
|
|
|
@@ -12108,10 +12305,10 @@ var init_packVerify = __esm({
|
|
|
12108
12305
|
});
|
|
12109
12306
|
|
|
12110
12307
|
// cli/installer/lockfile.ts
|
|
12111
|
-
import { existsSync as
|
|
12112
|
-
import { join as
|
|
12308
|
+
import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "fs";
|
|
12309
|
+
import { join as join30 } from "path";
|
|
12113
12310
|
function lockPath(repoRoot2) {
|
|
12114
|
-
return
|
|
12311
|
+
return join30(repoRoot2, LOCK_FILE);
|
|
12115
12312
|
}
|
|
12116
12313
|
function writeLockfile(repoRoot2, entries) {
|
|
12117
12314
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -12129,7 +12326,7 @@ function writeLockfile(repoRoot2, entries) {
|
|
|
12129
12326
|
""
|
|
12130
12327
|
])
|
|
12131
12328
|
].join("\n");
|
|
12132
|
-
|
|
12329
|
+
writeFileSync22(lockPath(repoRoot2), body, "utf-8");
|
|
12133
12330
|
}
|
|
12134
12331
|
var LOCK_FILE;
|
|
12135
12332
|
var init_lockfile = __esm({
|
|
@@ -12144,9 +12341,9 @@ var sync_exports = {};
|
|
|
12144
12341
|
__export(sync_exports, {
|
|
12145
12342
|
syncCommand: () => syncCommand
|
|
12146
12343
|
});
|
|
12147
|
-
import { existsSync as
|
|
12148
|
-
import { homedir as
|
|
12149
|
-
import { join as
|
|
12344
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync23 } from "fs";
|
|
12345
|
+
import { homedir as homedir31 } from "os";
|
|
12346
|
+
import { join as join31 } from "path";
|
|
12150
12347
|
function cacheKey(ref, version) {
|
|
12151
12348
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
12152
12349
|
}
|
|
@@ -12177,8 +12374,8 @@ async function syncCommand(_args = []) {
|
|
|
12177
12374
|
}
|
|
12178
12375
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
12179
12376
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
12180
|
-
const cacheDir =
|
|
12181
|
-
if (!cloud)
|
|
12377
|
+
const cacheDir = join31(homedir31(), ".synkro", "cache", "packs");
|
|
12378
|
+
if (!cloud) mkdirSync18(cacheDir, { recursive: true });
|
|
12182
12379
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
12183
12380
|
const lock = [];
|
|
12184
12381
|
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
@@ -12206,7 +12403,7 @@ async function syncCommand(_args = []) {
|
|
|
12206
12403
|
if (!cloud) {
|
|
12207
12404
|
const fname = cacheKey(ref, data.version);
|
|
12208
12405
|
keptCacheFiles.add(fname);
|
|
12209
|
-
|
|
12406
|
+
writeFileSync23(join31(cacheDir, fname), JSON.stringify({
|
|
12210
12407
|
ref,
|
|
12211
12408
|
version: data.version,
|
|
12212
12409
|
digest: data.digest,
|
|
@@ -12218,11 +12415,11 @@ async function syncCommand(_args = []) {
|
|
|
12218
12415
|
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
12219
12416
|
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
12220
12417
|
}
|
|
12221
|
-
if (!cloud &&
|
|
12418
|
+
if (!cloud && existsSync32(cacheDir)) {
|
|
12222
12419
|
for (const f of readdirSync8(cacheDir)) {
|
|
12223
12420
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
12224
12421
|
try {
|
|
12225
|
-
|
|
12422
|
+
rmSync4(join31(cacheDir, f));
|
|
12226
12423
|
} catch {
|
|
12227
12424
|
}
|
|
12228
12425
|
}
|
|
@@ -12251,13 +12448,13 @@ var whoami_exports = {};
|
|
|
12251
12448
|
__export(whoami_exports, {
|
|
12252
12449
|
whoamiCommand: () => whoamiCommand
|
|
12253
12450
|
});
|
|
12254
|
-
import { readFileSync as
|
|
12255
|
-
import { join as
|
|
12256
|
-
import { homedir as
|
|
12451
|
+
import { readFileSync as readFileSync30, existsSync as existsSync33 } from "fs";
|
|
12452
|
+
import { join as join32 } from "path";
|
|
12453
|
+
import { homedir as homedir32 } from "os";
|
|
12257
12454
|
function readConfigEnv3() {
|
|
12258
|
-
if (!
|
|
12455
|
+
if (!existsSync33(CONFIG_PATH8)) return {};
|
|
12259
12456
|
const out = {};
|
|
12260
|
-
for (const line of
|
|
12457
|
+
for (const line of readFileSync30(CONFIG_PATH8, "utf-8").split("\n")) {
|
|
12261
12458
|
const t = line.trim();
|
|
12262
12459
|
if (!t || t.startsWith("#")) continue;
|
|
12263
12460
|
const eq = t.indexOf("=");
|
|
@@ -12267,8 +12464,8 @@ function readConfigEnv3() {
|
|
|
12267
12464
|
}
|
|
12268
12465
|
function jwtStatus() {
|
|
12269
12466
|
try {
|
|
12270
|
-
if (!
|
|
12271
|
-
const jwt2 =
|
|
12467
|
+
if (!existsSync33(JWT_PATH2)) return { status: "none" };
|
|
12468
|
+
const jwt2 = readFileSync30(JWT_PATH2, "utf-8").trim();
|
|
12272
12469
|
if (!jwt2) return { status: "none" };
|
|
12273
12470
|
const payload = jwt2.split(".")[1];
|
|
12274
12471
|
if (!payload) return { status: "valid" };
|
|
@@ -12326,13 +12523,13 @@ async function whoamiCommand(args2 = []) {
|
|
|
12326
12523
|
console.log("synkro identity");
|
|
12327
12524
|
for (const [k, v] of rows) console.log(` ${k.padEnd(width)} ${v}`);
|
|
12328
12525
|
}
|
|
12329
|
-
var
|
|
12526
|
+
var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
|
|
12330
12527
|
var init_whoami = __esm({
|
|
12331
12528
|
"cli/commands/whoami.ts"() {
|
|
12332
12529
|
"use strict";
|
|
12333
|
-
|
|
12334
|
-
CONFIG_PATH8 =
|
|
12335
|
-
JWT_PATH2 =
|
|
12530
|
+
SYNKRO_DIR15 = join32(homedir32(), ".synkro");
|
|
12531
|
+
CONFIG_PATH8 = join32(SYNKRO_DIR15, "config.env");
|
|
12532
|
+
JWT_PATH2 = join32(SYNKRO_DIR15, ".mcp-jwt");
|
|
12336
12533
|
GRADING_LABEL = {
|
|
12337
12534
|
local: "on-device worker pool",
|
|
12338
12535
|
cloud: "Synkro Cloud worker pool",
|
|
@@ -12377,12 +12574,12 @@ __export(linear_exports, {
|
|
|
12377
12574
|
formatLinks: () => formatLinks,
|
|
12378
12575
|
linearCommand: () => linearCommand
|
|
12379
12576
|
});
|
|
12380
|
-
import { readFileSync as
|
|
12381
|
-
import { homedir as
|
|
12382
|
-
import { join as
|
|
12577
|
+
import { readFileSync as readFileSync31 } from "fs";
|
|
12578
|
+
import { homedir as homedir33 } from "os";
|
|
12579
|
+
import { join as join33 } from "path";
|
|
12383
12580
|
function mcpJwt() {
|
|
12384
12581
|
try {
|
|
12385
|
-
return
|
|
12582
|
+
return readFileSync31(join33(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
|
|
12386
12583
|
} catch {
|
|
12387
12584
|
return "";
|
|
12388
12585
|
}
|
|
@@ -12417,11 +12614,11 @@ async function linearCommand(_args = []) {
|
|
|
12417
12614
|
}
|
|
12418
12615
|
console.log(formatLinks(links));
|
|
12419
12616
|
}
|
|
12420
|
-
var
|
|
12617
|
+
var SYNKRO_DIR16, PORT2, BASE;
|
|
12421
12618
|
var init_linear = __esm({
|
|
12422
12619
|
"cli/commands/linear.ts"() {
|
|
12423
12620
|
"use strict";
|
|
12424
|
-
|
|
12621
|
+
SYNKRO_DIR16 = join33(homedir33(), ".synkro");
|
|
12425
12622
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12426
12623
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
12427
12624
|
}
|
|
@@ -12429,7 +12626,7 @@ var init_linear = __esm({
|
|
|
12429
12626
|
|
|
12430
12627
|
// cli/scanning/cveReachability.ts
|
|
12431
12628
|
import { parse } from "@babel/parser";
|
|
12432
|
-
import { readFileSync as
|
|
12629
|
+
import { readFileSync as readFileSync32 } from "fs";
|
|
12433
12630
|
function walk(node, visit) {
|
|
12434
12631
|
if (!node || typeof node.type !== "string") return;
|
|
12435
12632
|
visit(node);
|
|
@@ -12570,10 +12767,10 @@ var init_cveReachability = __esm({
|
|
|
12570
12767
|
});
|
|
12571
12768
|
|
|
12572
12769
|
// cli/reachability/reachabilityScan.ts
|
|
12573
|
-
import { spawnSync as
|
|
12574
|
-
import { readFileSync as
|
|
12575
|
-
import { join as
|
|
12576
|
-
import { homedir as
|
|
12770
|
+
import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
|
|
12771
|
+
import { readFileSync as readFileSync33, writeFileSync as writeFileSync24, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
|
|
12772
|
+
import { join as join34 } from "path";
|
|
12773
|
+
import { homedir as homedir34 } from "os";
|
|
12577
12774
|
import { createRequire } from "module";
|
|
12578
12775
|
function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
12579
12776
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -12590,7 +12787,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12590
12787
|
}
|
|
12591
12788
|
for (const e of ents) {
|
|
12592
12789
|
if (files.length >= maxFiles) break;
|
|
12593
|
-
const full =
|
|
12790
|
+
const full = join34(dir, e.name);
|
|
12594
12791
|
if (e.isDirectory()) {
|
|
12595
12792
|
if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
12596
12793
|
continue;
|
|
@@ -12598,7 +12795,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12598
12795
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
12599
12796
|
const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
|
|
12600
12797
|
try {
|
|
12601
|
-
const content =
|
|
12798
|
+
const content = readFileSync33(full, "utf8");
|
|
12602
12799
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
12603
12800
|
} catch {
|
|
12604
12801
|
}
|
|
@@ -12617,12 +12814,12 @@ function cleanVersion(spec) {
|
|
|
12617
12814
|
function gatherManifestVersions(repoRoot2) {
|
|
12618
12815
|
const out = {};
|
|
12619
12816
|
const dirs = [repoRoot2];
|
|
12620
|
-
const pkgsDir =
|
|
12621
|
-
if (
|
|
12817
|
+
const pkgsDir = join34(repoRoot2, "packages");
|
|
12818
|
+
if (existsSync34(pkgsDir)) {
|
|
12622
12819
|
try {
|
|
12623
12820
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12624
|
-
const pd =
|
|
12625
|
-
if (
|
|
12821
|
+
const pd = join34(pkgsDir, d);
|
|
12822
|
+
if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
|
|
12626
12823
|
}
|
|
12627
12824
|
} catch {
|
|
12628
12825
|
}
|
|
@@ -12631,7 +12828,7 @@ function gatherManifestVersions(repoRoot2) {
|
|
|
12631
12828
|
for (const dir of dirs) {
|
|
12632
12829
|
let pkg;
|
|
12633
12830
|
try {
|
|
12634
|
-
pkg = JSON.parse(
|
|
12831
|
+
pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
|
|
12635
12832
|
} catch {
|
|
12636
12833
|
continue;
|
|
12637
12834
|
}
|
|
@@ -12651,28 +12848,28 @@ function findJelly(repoRoot2) {
|
|
|
12651
12848
|
try {
|
|
12652
12849
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
12653
12850
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
12654
|
-
const pkg = JSON.parse(
|
|
12851
|
+
const pkg = JSON.parse(readFileSync33(pkgJson, "utf8"));
|
|
12655
12852
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
12656
12853
|
if (bin) {
|
|
12657
|
-
const p =
|
|
12658
|
-
if (
|
|
12854
|
+
const p = join34(dir, bin);
|
|
12855
|
+
if (existsSync34(p)) return p;
|
|
12659
12856
|
}
|
|
12660
12857
|
} catch {
|
|
12661
12858
|
}
|
|
12662
12859
|
for (const base of [repoRoot2, process.cwd()]) {
|
|
12663
|
-
const b =
|
|
12664
|
-
if (
|
|
12860
|
+
const b = join34(base, "node_modules", ".bin", "jelly");
|
|
12861
|
+
if (existsSync34(b)) return b;
|
|
12665
12862
|
}
|
|
12666
12863
|
return null;
|
|
12667
12864
|
}
|
|
12668
12865
|
function findEntries(repoRoot2) {
|
|
12669
12866
|
const dirs = [repoRoot2];
|
|
12670
|
-
const pkgsDir =
|
|
12671
|
-
if (
|
|
12867
|
+
const pkgsDir = join34(repoRoot2, "packages");
|
|
12868
|
+
if (existsSync34(pkgsDir)) {
|
|
12672
12869
|
try {
|
|
12673
12870
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12674
|
-
const pd =
|
|
12675
|
-
if (
|
|
12871
|
+
const pd = join34(pkgsDir, d);
|
|
12872
|
+
if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
|
|
12676
12873
|
}
|
|
12677
12874
|
} catch {
|
|
12678
12875
|
}
|
|
@@ -12680,12 +12877,12 @@ function findEntries(repoRoot2) {
|
|
|
12680
12877
|
const entries = [];
|
|
12681
12878
|
for (const dir of dirs) {
|
|
12682
12879
|
try {
|
|
12683
|
-
const pkg = JSON.parse(
|
|
12880
|
+
const pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
|
|
12684
12881
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
12685
12882
|
for (const c of cands) {
|
|
12686
12883
|
if (typeof c !== "string") continue;
|
|
12687
|
-
const f =
|
|
12688
|
-
if (
|
|
12884
|
+
const f = join34(dir, c);
|
|
12885
|
+
if (existsSync34(f)) {
|
|
12689
12886
|
entries.push(f);
|
|
12690
12887
|
break;
|
|
12691
12888
|
}
|
|
@@ -12718,9 +12915,9 @@ function parseApiUsage(log) {
|
|
|
12718
12915
|
}
|
|
12719
12916
|
function runReachabilityScan(repoRoot2, opts = {}) {
|
|
12720
12917
|
const commit = currentCommit(repoRoot2);
|
|
12721
|
-
if (!opts.force && commit &&
|
|
12918
|
+
if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
|
|
12722
12919
|
try {
|
|
12723
|
-
const prev = JSON.parse(
|
|
12920
|
+
const prev = JSON.parse(readFileSync33(REACHABILITY_PATH, "utf8"));
|
|
12724
12921
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
12725
12922
|
} catch {
|
|
12726
12923
|
}
|
|
@@ -12771,7 +12968,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
12771
12968
|
if (jelly) {
|
|
12772
12969
|
const entries = findEntries(repoRoot2);
|
|
12773
12970
|
if (entries.length > 0) {
|
|
12774
|
-
const r =
|
|
12971
|
+
const r = spawnSync12(
|
|
12775
12972
|
process.execPath,
|
|
12776
12973
|
[jelly, "-b", repoRoot2, "--api-usage", ...entries],
|
|
12777
12974
|
{ encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
|
|
@@ -12809,7 +13006,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
12809
13006
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
12810
13007
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
|
|
12811
13008
|
try {
|
|
12812
|
-
|
|
13009
|
+
writeFileSync24(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
12813
13010
|
} catch (e) {
|
|
12814
13011
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
12815
13012
|
}
|
|
@@ -12821,7 +13018,7 @@ var init_reachabilityScan = __esm({
|
|
|
12821
13018
|
"use strict";
|
|
12822
13019
|
init_cveReachability();
|
|
12823
13020
|
require2 = createRequire(import.meta.url);
|
|
12824
|
-
REACHABILITY_PATH =
|
|
13021
|
+
REACHABILITY_PATH = join34(homedir34(), ".synkro", "reachability.json");
|
|
12825
13022
|
}
|
|
12826
13023
|
});
|
|
12827
13024
|
|
|
@@ -12830,15 +13027,15 @@ var reachabilityScan_exports = {};
|
|
|
12830
13027
|
__export(reachabilityScan_exports, {
|
|
12831
13028
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
12832
13029
|
});
|
|
12833
|
-
import { readFileSync as
|
|
12834
|
-
import { join as
|
|
12835
|
-
import { homedir as
|
|
13030
|
+
import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
|
|
13031
|
+
import { join as join35 } from "path";
|
|
13032
|
+
import { homedir as homedir35 } from "os";
|
|
12836
13033
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
12837
13034
|
function readConfigEnv4() {
|
|
12838
|
-
const p =
|
|
12839
|
-
if (!
|
|
13035
|
+
const p = join35(SYNKRO_DIR17, "config.env");
|
|
13036
|
+
if (!existsSync35(p)) return {};
|
|
12840
13037
|
const out = {};
|
|
12841
|
-
for (const line of
|
|
13038
|
+
for (const line of readFileSync34(p, "utf-8").split("\n")) {
|
|
12842
13039
|
const t = line.trim();
|
|
12843
13040
|
if (!t || t.startsWith("#")) continue;
|
|
12844
13041
|
const eq = t.indexOf("=");
|
|
@@ -12870,11 +13067,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
12870
13067
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
12871
13068
|
let jwt2 = "";
|
|
12872
13069
|
try {
|
|
12873
|
-
jwt2 =
|
|
13070
|
+
jwt2 = readFileSync34(join35(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
|
|
12874
13071
|
} catch {
|
|
12875
13072
|
}
|
|
12876
|
-
if (!jwt2 || !
|
|
12877
|
-
const body =
|
|
13073
|
+
if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
|
|
13074
|
+
const body = readFileSync34(REACHABILITY_PATH, "utf-8");
|
|
12878
13075
|
try {
|
|
12879
13076
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
12880
13077
|
method: "POST",
|
|
@@ -12901,12 +13098,12 @@ async function reachabilityScanCommand(args2 = []) {
|
|
|
12901
13098
|
const isCloud = cfg.SYNKRO_DEPLOY_LOCATION === "cloud" || cfg.SYNKRO_STORAGE_MODE === "cloud";
|
|
12902
13099
|
if (isCloud) await pushToCloud(cfg, cfg.SYNKRO_CONNECTED_REPO || repoSlug(root));
|
|
12903
13100
|
}
|
|
12904
|
-
var
|
|
13101
|
+
var SYNKRO_DIR17;
|
|
12905
13102
|
var init_reachabilityScan2 = __esm({
|
|
12906
13103
|
"cli/commands/reachabilityScan.ts"() {
|
|
12907
13104
|
"use strict";
|
|
12908
13105
|
init_reachabilityScan();
|
|
12909
|
-
|
|
13106
|
+
SYNKRO_DIR17 = join35(homedir35(), ".synkro");
|
|
12910
13107
|
}
|
|
12911
13108
|
});
|
|
12912
13109
|
|
|
@@ -12935,9 +13132,9 @@ async function startCommand(rest = []) {
|
|
|
12935
13132
|
assertDockerAvailable();
|
|
12936
13133
|
const cfg = resolveWorkerConfig(rest);
|
|
12937
13134
|
if (cfg.explicit) {
|
|
12938
|
-
console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)
|
|
13135
|
+
console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
|
|
12939
13136
|
`);
|
|
12940
|
-
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13137
|
+
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
12941
13138
|
const ready = await waitForContainerReady(6e4);
|
|
12942
13139
|
if (!ready) {
|
|
12943
13140
|
console.error("\n\u26A0 container did not pass /healthz within 60s");
|
|
@@ -12964,10 +13161,11 @@ async function updateCommand() {
|
|
|
12964
13161
|
}
|
|
12965
13162
|
const claudeWorkers = cfg.claudeWorkers ?? 8;
|
|
12966
13163
|
const cursorWorkers = cfg.cursorWorkers ?? 0;
|
|
13164
|
+
const codexWorkers = cfg.codexWorkers ?? 0;
|
|
12967
13165
|
console.log("Synkro: updating to the latest container image");
|
|
12968
|
-
console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)
|
|
13166
|
+
console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
|
|
12969
13167
|
`);
|
|
12970
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13168
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
12971
13169
|
const ready = await waitForContainerReady(9e4);
|
|
12972
13170
|
if (!ready) {
|
|
12973
13171
|
console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
|
|
@@ -12991,16 +13189,18 @@ async function restartCommand(rest = []) {
|
|
|
12991
13189
|
const cfg = resolveWorkerConfig(rest);
|
|
12992
13190
|
let claudeWorkers = cfg.claudeWorkers;
|
|
12993
13191
|
let cursorWorkers = cfg.cursorWorkers;
|
|
13192
|
+
let codexWorkers = cfg.codexWorkers;
|
|
12994
13193
|
if (!cfg.explicit) {
|
|
12995
13194
|
const reconciled = reconcileHarness();
|
|
12996
13195
|
if (reconciled) {
|
|
12997
13196
|
claudeWorkers = reconciled.claudeWorkers;
|
|
12998
13197
|
cursorWorkers = reconciled.cursorWorkers;
|
|
13198
|
+
codexWorkers = reconciled.codexWorkers;
|
|
12999
13199
|
}
|
|
13000
13200
|
}
|
|
13001
|
-
console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)
|
|
13201
|
+
console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
|
|
13002
13202
|
`);
|
|
13003
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13203
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13004
13204
|
const ready = await waitForContainerReady(6e4);
|
|
13005
13205
|
if (!ready) {
|
|
13006
13206
|
console.error("\n\u26A0 container did not pass /healthz within 60s");
|
|
@@ -13028,13 +13228,13 @@ var config_exports = {};
|
|
|
13028
13228
|
__export(config_exports, {
|
|
13029
13229
|
configCommand: () => configCommand
|
|
13030
13230
|
});
|
|
13031
|
-
import { readFileSync as
|
|
13032
|
-
import { join as
|
|
13033
|
-
import { homedir as
|
|
13231
|
+
import { readFileSync as readFileSync35, writeFileSync as writeFileSync25, existsSync as existsSync36 } from "fs";
|
|
13232
|
+
import { join as join36 } from "path";
|
|
13233
|
+
import { homedir as homedir36 } from "os";
|
|
13034
13234
|
function readConfigEnv5() {
|
|
13035
|
-
if (!
|
|
13235
|
+
if (!existsSync36(CONFIG_PATH9)) return {};
|
|
13036
13236
|
const out = {};
|
|
13037
|
-
for (const line of
|
|
13237
|
+
for (const line of readFileSync35(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
13038
13238
|
const t = line.trim();
|
|
13039
13239
|
if (!t || t.startsWith("#")) continue;
|
|
13040
13240
|
const eq = t.indexOf("=");
|
|
@@ -13043,11 +13243,11 @@ function readConfigEnv5() {
|
|
|
13043
13243
|
return out;
|
|
13044
13244
|
}
|
|
13045
13245
|
function updateConfigValue(key, value) {
|
|
13046
|
-
if (!
|
|
13246
|
+
if (!existsSync36(CONFIG_PATH9)) {
|
|
13047
13247
|
console.error("No config found. Run `synkro install` first.");
|
|
13048
13248
|
process.exit(1);
|
|
13049
13249
|
}
|
|
13050
|
-
const lines =
|
|
13250
|
+
const lines = readFileSync35(CONFIG_PATH9, "utf-8").split("\n");
|
|
13051
13251
|
const pattern = new RegExp(`^${key}=`);
|
|
13052
13252
|
let found = false;
|
|
13053
13253
|
const updated = lines.map((line) => {
|
|
@@ -13058,7 +13258,7 @@ function updateConfigValue(key, value) {
|
|
|
13058
13258
|
return line;
|
|
13059
13259
|
});
|
|
13060
13260
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
13061
|
-
|
|
13261
|
+
writeFileSync25(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
13062
13262
|
}
|
|
13063
13263
|
function resolveInferenceMode(cfg) {
|
|
13064
13264
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -13210,14 +13410,14 @@ To change:`);
|
|
|
13210
13410
|
}
|
|
13211
13411
|
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
13212
13412
|
}
|
|
13213
|
-
var
|
|
13413
|
+
var SYNKRO_DIR18, CONFIG_PATH9;
|
|
13214
13414
|
var init_config = __esm({
|
|
13215
13415
|
"cli/commands/config.ts"() {
|
|
13216
13416
|
"use strict";
|
|
13217
13417
|
init_stub();
|
|
13218
13418
|
init_optout();
|
|
13219
|
-
|
|
13220
|
-
CONFIG_PATH9 =
|
|
13419
|
+
SYNKRO_DIR18 = join36(homedir36(), ".synkro");
|
|
13420
|
+
CONFIG_PATH9 = join36(SYNKRO_DIR18, "config.env");
|
|
13221
13421
|
}
|
|
13222
13422
|
});
|
|
13223
13423
|
|
|
@@ -13406,14 +13606,14 @@ Usage:
|
|
|
13406
13606
|
});
|
|
13407
13607
|
|
|
13408
13608
|
// cli/bootstrap.js
|
|
13409
|
-
import { readFileSync as
|
|
13609
|
+
import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
|
|
13410
13610
|
import { resolve as resolve5 } from "path";
|
|
13411
13611
|
var envCandidates = [
|
|
13412
13612
|
resolve5(process.env.HOME ?? "", ".synkro", "config.env")
|
|
13413
13613
|
];
|
|
13414
13614
|
for (const envPath of envCandidates) {
|
|
13415
|
-
if (!
|
|
13416
|
-
const envContent =
|
|
13615
|
+
if (!existsSync37(envPath)) continue;
|
|
13616
|
+
const envContent = readFileSync36(envPath, "utf-8");
|
|
13417
13617
|
for (const line of envContent.split("\n")) {
|
|
13418
13618
|
const trimmed = line.trim();
|
|
13419
13619
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -13430,7 +13630,7 @@ var subArgs = args.slice(1);
|
|
|
13430
13630
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
13431
13631
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
13432
13632
|
function printVersion() {
|
|
13433
|
-
console.log("1.7.
|
|
13633
|
+
console.log("1.7.87");
|
|
13434
13634
|
}
|
|
13435
13635
|
function printHelp2() {
|
|
13436
13636
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|