@synkro-sh/cli 1.7.87 → 1.7.89
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 +1501 -620
- 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.89";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -283,6 +283,100 @@ var init_emit = __esm({
|
|
|
283
283
|
}
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
// cli/telemetry/httpSql.ts
|
|
287
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
288
|
+
import { homedir as homedir4 } from "os";
|
|
289
|
+
import { join as join4 } from "path";
|
|
290
|
+
function quoteIdentifier(value) {
|
|
291
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(value)) throw new Error("invalid SQL identifier");
|
|
292
|
+
return `"${value}"`;
|
|
293
|
+
}
|
|
294
|
+
function shiftPlaceholders(text, offset) {
|
|
295
|
+
return text.replace(/\$(\d+)/g, (_, raw) => `$${Number(raw) + offset}`);
|
|
296
|
+
}
|
|
297
|
+
function bulkFragment(rows, columns) {
|
|
298
|
+
if (!rows.length || !columns.length) throw new Error("empty SQL helper");
|
|
299
|
+
const params = [];
|
|
300
|
+
const tuples = rows.map((row) => {
|
|
301
|
+
const placeholders = columns.map((column) => {
|
|
302
|
+
let value = row[column];
|
|
303
|
+
let cast = "";
|
|
304
|
+
if (column === "context" && typeof value === "object" && value !== null) {
|
|
305
|
+
value = JSON.stringify(value);
|
|
306
|
+
cast = "::jsonb";
|
|
307
|
+
}
|
|
308
|
+
params.push(value ?? null);
|
|
309
|
+
return `$${params.length}${cast}`;
|
|
310
|
+
});
|
|
311
|
+
return `(${placeholders.join(", ")})`;
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
[FRAGMENT]: true,
|
|
315
|
+
text: `(${columns.map(quoteIdentifier).join(", ")}) VALUES ${tuples.join(", ")}`,
|
|
316
|
+
params
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function createTelemetryHttpSql(options = {}) {
|
|
320
|
+
const port = options.port ?? Number.parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
321
|
+
const endpoint = `http://127.0.0.1:${port}/api/local/telemetry/query`;
|
|
322
|
+
const token = options.token ?? readFileSync3(join4(homedir4(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
323
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
324
|
+
const query = async (text, params = []) => {
|
|
325
|
+
const response = await fetchImpl(endpoint, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: {
|
|
328
|
+
Authorization: `Bearer ${token}`,
|
|
329
|
+
"Content-Type": "application/json"
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({ text, params }),
|
|
332
|
+
signal: AbortSignal.timeout(3e4)
|
|
333
|
+
});
|
|
334
|
+
const payload = await response.json().catch(() => ({}));
|
|
335
|
+
if (!response.ok) throw new Error(payload.error || `local telemetry query failed (${response.status})`);
|
|
336
|
+
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
337
|
+
Object.defineProperty(rows, "count", {
|
|
338
|
+
value: Number(payload.count ?? rows.length),
|
|
339
|
+
enumerable: false,
|
|
340
|
+
configurable: true
|
|
341
|
+
});
|
|
342
|
+
return rows;
|
|
343
|
+
};
|
|
344
|
+
const sql = function(first, ...values) {
|
|
345
|
+
if (Array.isArray(first) && "raw" in first) {
|
|
346
|
+
const strings = first;
|
|
347
|
+
let text = strings[0];
|
|
348
|
+
const params = [];
|
|
349
|
+
for (let i = 0; i < values.length; i++) {
|
|
350
|
+
const value = values[i];
|
|
351
|
+
if (value && typeof value === "object" && value[FRAGMENT]) {
|
|
352
|
+
text += shiftPlaceholders(value.text, params.length);
|
|
353
|
+
params.push(...value.params);
|
|
354
|
+
} else {
|
|
355
|
+
params.push(value);
|
|
356
|
+
text += `$${params.length}`;
|
|
357
|
+
}
|
|
358
|
+
text += strings[i + 1];
|
|
359
|
+
}
|
|
360
|
+
return query(text, params);
|
|
361
|
+
}
|
|
362
|
+
if (Array.isArray(first)) {
|
|
363
|
+
return bulkFragment(first, values);
|
|
364
|
+
}
|
|
365
|
+
throw new Error("unsupported SQL invocation");
|
|
366
|
+
};
|
|
367
|
+
sql.unsafe = query;
|
|
368
|
+
sql.end = async () => {
|
|
369
|
+
};
|
|
370
|
+
return sql;
|
|
371
|
+
}
|
|
372
|
+
var FRAGMENT;
|
|
373
|
+
var init_httpSql = __esm({
|
|
374
|
+
"cli/telemetry/httpSql.ts"() {
|
|
375
|
+
"use strict";
|
|
376
|
+
FRAGMENT = /* @__PURE__ */ Symbol("synkro-http-sql-fragment");
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
286
380
|
// cli/telemetry/db.ts
|
|
287
381
|
var db_exports = {};
|
|
288
382
|
__export(db_exports, {
|
|
@@ -290,24 +384,11 @@ __export(db_exports, {
|
|
|
290
384
|
connectDb: () => connectDb,
|
|
291
385
|
resetDbForTests: () => resetDbForTests
|
|
292
386
|
});
|
|
293
|
-
import postgres from "postgres";
|
|
294
387
|
async function connectDb() {
|
|
295
388
|
if (cached3) return cached3;
|
|
296
389
|
if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;
|
|
297
390
|
try {
|
|
298
|
-
const sql =
|
|
299
|
-
host: PGLITE_HOST,
|
|
300
|
-
port: PGLITE_PORT,
|
|
301
|
-
username: PGLITE_USER,
|
|
302
|
-
database: PGLITE_DB,
|
|
303
|
-
// max:1 mirrors synkro-server — PGlite is single-threaded; multiple
|
|
304
|
-
// sockets just queue at the multiplexer.
|
|
305
|
-
max: 1,
|
|
306
|
-
connect_timeout: 2,
|
|
307
|
-
idle_timeout: 30,
|
|
308
|
-
onnotice: () => {
|
|
309
|
-
}
|
|
310
|
-
});
|
|
391
|
+
const sql = createTelemetryHttpSql();
|
|
311
392
|
await sql`SELECT 1`;
|
|
312
393
|
cached3 = sql;
|
|
313
394
|
initFailedAt = 0;
|
|
@@ -317,7 +398,7 @@ async function connectDb() {
|
|
|
317
398
|
initFailedAt = Date.now();
|
|
318
399
|
if (process.env.SYNKRO_TELEMETRY_DEBUG === "1") {
|
|
319
400
|
const msg = err instanceof Error ? err.message : String(err);
|
|
320
|
-
process.stderr.write(`[synkro] telemetry
|
|
401
|
+
process.stderr.write(`[synkro] local telemetry API unreachable: ${msg}
|
|
321
402
|
`);
|
|
322
403
|
}
|
|
323
404
|
return null;
|
|
@@ -354,17 +435,11 @@ function resetDbForTests() {
|
|
|
354
435
|
initFailedAt = 0;
|
|
355
436
|
migrated = false;
|
|
356
437
|
}
|
|
357
|
-
var
|
|
438
|
+
var SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
|
|
358
439
|
var init_db = __esm({
|
|
359
440
|
"cli/telemetry/db.ts"() {
|
|
360
441
|
"use strict";
|
|
361
|
-
|
|
362
|
-
PGLITE_PORT = parseInt(
|
|
363
|
-
process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || "15433",
|
|
364
|
-
10
|
|
365
|
-
);
|
|
366
|
-
PGLITE_USER = process.env.SYNKRO_PGLITE_USER || "postgres";
|
|
367
|
-
PGLITE_DB = process.env.SYNKRO_PGLITE_DB || "postgres";
|
|
442
|
+
init_httpSql();
|
|
368
443
|
SCHEMA_VERSION = 1;
|
|
369
444
|
cached3 = null;
|
|
370
445
|
migrated = false;
|
|
@@ -438,9 +513,9 @@ var init_db = __esm({
|
|
|
438
513
|
});
|
|
439
514
|
|
|
440
515
|
// cli/telemetry/drain.ts
|
|
441
|
-
import { openSync, closeSync, fstatSync, readFileSync as
|
|
442
|
-
import { homedir as
|
|
443
|
-
import { join as
|
|
516
|
+
import { openSync, closeSync, fstatSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
|
|
517
|
+
import { homedir as homedir5 } from "os";
|
|
518
|
+
import { join as join5 } from "path";
|
|
444
519
|
function ensureDir3() {
|
|
445
520
|
if (!existsSync4(SYNKRO_DIR3)) mkdirSync3(SYNKRO_DIR3, { recursive: true, mode: 448 });
|
|
446
521
|
}
|
|
@@ -494,7 +569,7 @@ function parseQueueFile(path) {
|
|
|
494
569
|
if (!existsSync4(path)) return { rows: [], malformed: 0 };
|
|
495
570
|
let raw;
|
|
496
571
|
try {
|
|
497
|
-
raw =
|
|
572
|
+
raw = readFileSync4(path, "utf-8");
|
|
498
573
|
} catch {
|
|
499
574
|
return { rows: [], malformed: 0 };
|
|
500
575
|
}
|
|
@@ -534,7 +609,7 @@ function recoverProcessing() {
|
|
|
534
609
|
ensureDir3();
|
|
535
610
|
let body;
|
|
536
611
|
try {
|
|
537
|
-
body =
|
|
612
|
+
body = readFileSync4(PROCESSING_PATH, "utf-8");
|
|
538
613
|
} catch {
|
|
539
614
|
return;
|
|
540
615
|
}
|
|
@@ -663,39 +738,41 @@ async function insertEvents(sql, rows, mirroredIds) {
|
|
|
663
738
|
return inserted;
|
|
664
739
|
}
|
|
665
740
|
async function drainToPglite() {
|
|
666
|
-
const
|
|
667
|
-
if (
|
|
741
|
+
const queued = readQueue().rows;
|
|
742
|
+
if (queued.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
|
|
743
|
+
const rows = queued.length > MIRROR_MAX_PER_DRAIN ? queued.slice(-MIRROR_MAX_PER_DRAIN) : queued;
|
|
668
744
|
const sql = await connectDb();
|
|
669
|
-
if (!sql) return { ok: false, ingested: 0, pending:
|
|
745
|
+
if (!sql) return { ok: false, ingested: 0, pending: queued.length, reason: "db_unavailable" };
|
|
670
746
|
try {
|
|
671
747
|
const ingested = await insertEvents(sql, rows);
|
|
672
748
|
patchMetaCache({ last_drained_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
673
|
-
return { ok: true, ingested, pending:
|
|
749
|
+
return { ok: true, ingested, pending: queued.length };
|
|
674
750
|
} catch (err) {
|
|
675
751
|
const msg = err instanceof Error ? err.message : String(err);
|
|
676
|
-
return { ok: false, ingested: 0, pending:
|
|
752
|
+
return { ok: false, ingested: 0, pending: queued.length, reason: "error", error: msg };
|
|
677
753
|
}
|
|
678
754
|
}
|
|
679
|
-
var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, STALE_LOCK_MS, MAX_QUEUE_ROWS;
|
|
755
|
+
var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, MIRROR_MAX_PER_DRAIN, STALE_LOCK_MS, MAX_QUEUE_ROWS;
|
|
680
756
|
var init_drain = __esm({
|
|
681
757
|
"cli/telemetry/drain.ts"() {
|
|
682
758
|
"use strict";
|
|
683
759
|
init_db();
|
|
684
760
|
init_emit();
|
|
685
761
|
init_metaCache();
|
|
686
|
-
SYNKRO_DIR3 =
|
|
687
|
-
QUEUE_LOCK =
|
|
762
|
+
SYNKRO_DIR3 = join5(homedir5(), ".synkro");
|
|
763
|
+
QUEUE_LOCK = join5(SYNKRO_DIR3, ".telemetry-queue.lock");
|
|
688
764
|
PROCESSING_PATH = `${PENDING_PATH}.processing`;
|
|
689
765
|
CHUNK = 500;
|
|
766
|
+
MIRROR_MAX_PER_DRAIN = 5e3;
|
|
690
767
|
STALE_LOCK_MS = 6e4;
|
|
691
768
|
MAX_QUEUE_ROWS = 5e4;
|
|
692
769
|
}
|
|
693
770
|
});
|
|
694
771
|
|
|
695
772
|
// cli/telemetry/optout.ts
|
|
696
|
-
import { chmodSync, existsSync as existsSync5, readFileSync as
|
|
697
|
-
import { homedir as
|
|
698
|
-
import { join as
|
|
773
|
+
import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
774
|
+
import { homedir as homedir6 } from "os";
|
|
775
|
+
import { join as join6 } from "path";
|
|
699
776
|
function sanitize(raw, maxLen = 256) {
|
|
700
777
|
return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
|
|
701
778
|
}
|
|
@@ -708,7 +785,7 @@ function writeConfigEnvFlag(key, value) {
|
|
|
708
785
|
let content = "";
|
|
709
786
|
if (existsSync5(CONFIG_PATH2)) {
|
|
710
787
|
try {
|
|
711
|
-
content =
|
|
788
|
+
content = readFileSync5(CONFIG_PATH2, "utf-8");
|
|
712
789
|
} catch {
|
|
713
790
|
content = "";
|
|
714
791
|
}
|
|
@@ -778,8 +855,8 @@ var init_optout = __esm({
|
|
|
778
855
|
"use strict";
|
|
779
856
|
init_emit();
|
|
780
857
|
init_metaCache();
|
|
781
|
-
SYNKRO_DIR4 =
|
|
782
|
-
CONFIG_PATH2 =
|
|
858
|
+
SYNKRO_DIR4 = join6(homedir6(), ".synkro");
|
|
859
|
+
CONFIG_PATH2 = join6(SYNKRO_DIR4, "config.env");
|
|
783
860
|
KEY_ENABLED = "SYNKRO_TELEMETRY_ENABLED";
|
|
784
861
|
KEY_REMOTE = "SYNKRO_TELEMETRY_REMOTE_FLUSH";
|
|
785
862
|
}
|
|
@@ -828,15 +905,15 @@ __export(flush_exports, {
|
|
|
828
905
|
flushDetached: () => flushDetached
|
|
829
906
|
});
|
|
830
907
|
import { spawn } from "child_process";
|
|
831
|
-
import { existsSync as existsSync6, readFileSync as
|
|
832
|
-
import { homedir as
|
|
833
|
-
import { join as
|
|
908
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
909
|
+
import { homedir as homedir7 } from "os";
|
|
910
|
+
import { join as join7 } from "path";
|
|
834
911
|
function readConfigEnv() {
|
|
835
912
|
if (!existsSync6(CONFIG_PATH3)) return {};
|
|
836
913
|
const out = {};
|
|
837
914
|
let raw;
|
|
838
915
|
try {
|
|
839
|
-
raw =
|
|
916
|
+
raw = readFileSync6(CONFIG_PATH3, "utf-8");
|
|
840
917
|
} catch {
|
|
841
918
|
return {};
|
|
842
919
|
}
|
|
@@ -877,14 +954,14 @@ function resolveGatewayUrl() {
|
|
|
877
954
|
function loadJwt() {
|
|
878
955
|
if ((process.env.SYNKRO_STORAGE_MODE || "") === "cloud") {
|
|
879
956
|
try {
|
|
880
|
-
const mcp =
|
|
957
|
+
const mcp = readFileSync6(join7(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
|
|
881
958
|
if (mcp) return mcp;
|
|
882
959
|
} catch {
|
|
883
960
|
}
|
|
884
961
|
}
|
|
885
962
|
try {
|
|
886
963
|
if (!existsSync6(CREDS_PATH2)) return null;
|
|
887
|
-
const creds = JSON.parse(
|
|
964
|
+
const creds = JSON.parse(readFileSync6(CREDS_PATH2, "utf-8"));
|
|
888
965
|
return creds.access_token || null;
|
|
889
966
|
} catch {
|
|
890
967
|
return null;
|
|
@@ -1039,20 +1116,20 @@ var init_flush = __esm({
|
|
|
1039
1116
|
init_optout();
|
|
1040
1117
|
init_metaCache();
|
|
1041
1118
|
init_wire();
|
|
1042
|
-
SYNKRO_DIR5 =
|
|
1043
|
-
CONFIG_PATH3 =
|
|
1044
|
-
CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH ||
|
|
1119
|
+
SYNKRO_DIR5 = join7(homedir7(), ".synkro");
|
|
1120
|
+
CONFIG_PATH3 = join7(SYNKRO_DIR5, "config.env");
|
|
1121
|
+
CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join7(SYNKRO_DIR5, "credentials.json");
|
|
1045
1122
|
THROTTLE_MS = 6e4;
|
|
1046
1123
|
DEFAULT_GATEWAY = "https://api.synkro.sh";
|
|
1047
1124
|
}
|
|
1048
1125
|
});
|
|
1049
1126
|
|
|
1050
1127
|
// cli/telemetry/stats.ts
|
|
1051
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1128
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync4 } from "fs";
|
|
1052
1129
|
function countPending() {
|
|
1053
1130
|
if (!existsSync7(PENDING_PATH)) return 0;
|
|
1054
1131
|
try {
|
|
1055
|
-
const raw =
|
|
1132
|
+
const raw = readFileSync7(PENDING_PATH, "utf-8");
|
|
1056
1133
|
if (!raw) return 0;
|
|
1057
1134
|
let n = 0;
|
|
1058
1135
|
for (const line of raw.split("\n")) if (line.trim()) n++;
|
|
@@ -1174,13 +1251,13 @@ var init_stats = __esm({
|
|
|
1174
1251
|
});
|
|
1175
1252
|
|
|
1176
1253
|
// cli/telemetry/purge.ts
|
|
1177
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1254
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
1178
1255
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1179
1256
|
function unlinkPendingCount() {
|
|
1180
1257
|
if (!existsSync8(PENDING_PATH)) return 0;
|
|
1181
1258
|
let n = 0;
|
|
1182
1259
|
try {
|
|
1183
|
-
const raw =
|
|
1260
|
+
const raw = readFileSync8(PENDING_PATH, "utf-8");
|
|
1184
1261
|
for (const line of raw.split("\n")) if (line.trim()) n++;
|
|
1185
1262
|
} catch {
|
|
1186
1263
|
}
|
|
@@ -1345,8 +1422,8 @@ var init_telemetry = __esm({
|
|
|
1345
1422
|
|
|
1346
1423
|
// cli/installer/agentDetect.ts
|
|
1347
1424
|
import { existsSync as existsSync9 } from "fs";
|
|
1348
|
-
import { homedir as
|
|
1349
|
-
import { join as
|
|
1425
|
+
import { homedir as homedir8 } from "os";
|
|
1426
|
+
import { join as join8 } from "path";
|
|
1350
1427
|
import { execFileSync as execFileSync2, execSync } from "child_process";
|
|
1351
1428
|
function which(cmd3) {
|
|
1352
1429
|
try {
|
|
@@ -1367,21 +1444,21 @@ function getVersion(cmd3) {
|
|
|
1367
1444
|
}
|
|
1368
1445
|
function detectAgents() {
|
|
1369
1446
|
const agents = [];
|
|
1370
|
-
const home =
|
|
1447
|
+
const home = homedir8();
|
|
1371
1448
|
const claudeBinary = which("claude");
|
|
1372
|
-
const claudeConfigDir =
|
|
1449
|
+
const claudeConfigDir = join8(home, ".claude");
|
|
1373
1450
|
if (claudeBinary) {
|
|
1374
1451
|
agents.push({
|
|
1375
1452
|
kind: "claude_code",
|
|
1376
1453
|
name: "Claude Code",
|
|
1377
1454
|
binaryPath: claudeBinary,
|
|
1378
1455
|
configDir: claudeConfigDir,
|
|
1379
|
-
settingsPath:
|
|
1456
|
+
settingsPath: join8(claudeConfigDir, "settings.json"),
|
|
1380
1457
|
version: getVersion("claude")
|
|
1381
1458
|
});
|
|
1382
1459
|
}
|
|
1383
1460
|
const cursorBinary = which("cursor");
|
|
1384
|
-
const cursorConfigDir =
|
|
1461
|
+
const cursorConfigDir = join8(home, ".cursor");
|
|
1385
1462
|
const cursorApp = process.platform === "darwin" && existsSync9("/Applications/Cursor.app");
|
|
1386
1463
|
if (cursorBinary || cursorApp || existsSync9(cursorConfigDir)) {
|
|
1387
1464
|
let version;
|
|
@@ -1399,19 +1476,19 @@ function detectAgents() {
|
|
|
1399
1476
|
name: "Cursor",
|
|
1400
1477
|
binaryPath: cursorBinary,
|
|
1401
1478
|
configDir: cursorConfigDir,
|
|
1402
|
-
settingsPath:
|
|
1479
|
+
settingsPath: join8(cursorConfigDir, "hooks.json"),
|
|
1403
1480
|
version
|
|
1404
1481
|
});
|
|
1405
1482
|
}
|
|
1406
1483
|
const codexBinary = which("codex");
|
|
1407
|
-
const codexConfigDir = process.env.CODEX_HOME ||
|
|
1484
|
+
const codexConfigDir = process.env.CODEX_HOME || join8(home, ".codex");
|
|
1408
1485
|
if (codexBinary || existsSync9(codexConfigDir)) {
|
|
1409
1486
|
agents.push({
|
|
1410
1487
|
kind: "codex",
|
|
1411
1488
|
name: "Codex",
|
|
1412
1489
|
binaryPath: codexBinary,
|
|
1413
1490
|
configDir: codexConfigDir,
|
|
1414
|
-
settingsPath:
|
|
1491
|
+
settingsPath: join8(codexConfigDir, "hooks.json"),
|
|
1415
1492
|
version: codexBinary ? getVersion("codex") : void 0
|
|
1416
1493
|
});
|
|
1417
1494
|
}
|
|
@@ -1425,16 +1502,16 @@ var init_agentDetect = __esm({
|
|
|
1425
1502
|
|
|
1426
1503
|
// cli/installer/platform.ts
|
|
1427
1504
|
import { existsSync as existsSync10 } from "fs";
|
|
1428
|
-
import { homedir as
|
|
1429
|
-
import { join as
|
|
1505
|
+
import { homedir as homedir9 } from "os";
|
|
1506
|
+
import { join as join9 } from "path";
|
|
1430
1507
|
import { spawnSync } from "child_process";
|
|
1431
1508
|
function resolveBunBin() {
|
|
1432
1509
|
const finder = IS_WINDOWS ? "where" : "which";
|
|
1433
1510
|
const r = spawnSync(finder, ["bun"], { encoding: "utf-8", timeout: 5e3 });
|
|
1434
1511
|
const resolved = (r.stdout || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
1435
1512
|
if (resolved) return resolved;
|
|
1436
|
-
const home = process.env.USERPROFILE ||
|
|
1437
|
-
const candidates = IS_WINDOWS ? [
|
|
1513
|
+
const home = process.env.USERPROFILE || homedir9();
|
|
1514
|
+
const candidates = IS_WINDOWS ? [join9(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join9(home, ".bun", "bin", "bun")];
|
|
1438
1515
|
for (const p of candidates) if (existsSync10(p)) return p;
|
|
1439
1516
|
return IS_WINDOWS ? "bun.exe" : "bun";
|
|
1440
1517
|
}
|
|
@@ -1458,12 +1535,12 @@ var init_platform = __esm({
|
|
|
1458
1535
|
});
|
|
1459
1536
|
|
|
1460
1537
|
// cli/installer/ccHookConfig.ts
|
|
1461
|
-
import { existsSync as existsSync11, readFileSync as
|
|
1538
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1462
1539
|
import { dirname } from "path";
|
|
1463
1540
|
function readSettings(path) {
|
|
1464
1541
|
if (!existsSync11(path)) return {};
|
|
1465
1542
|
try {
|
|
1466
|
-
const raw =
|
|
1543
|
+
const raw = readFileSync9(path, "utf-8");
|
|
1467
1544
|
return JSON.parse(raw);
|
|
1468
1545
|
} catch (err) {
|
|
1469
1546
|
throw new Error(`Failed to parse ${path}: ${err.message}`);
|
|
@@ -1609,7 +1686,7 @@ function installCCHooks(settingsPath, config) {
|
|
|
1609
1686
|
}
|
|
1610
1687
|
if (config.taskActivateIntentScriptPath) {
|
|
1611
1688
|
settings.hooks.PreToolUse.push({
|
|
1612
|
-
matcher: "mcp__synkro-guardrails__activate_standard",
|
|
1689
|
+
matcher: "mcp__synkro[-_]guardrails__activate_standard",
|
|
1613
1690
|
hooks: [
|
|
1614
1691
|
{
|
|
1615
1692
|
type: "command",
|
|
@@ -1723,9 +1800,9 @@ var init_ccHookConfig = __esm({
|
|
|
1723
1800
|
});
|
|
1724
1801
|
|
|
1725
1802
|
// cli/installer/cursorHookConfig.ts
|
|
1726
|
-
import { readFileSync as
|
|
1803
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
|
|
1727
1804
|
import { dirname as dirname2, resolve, normalize } from "path";
|
|
1728
|
-
import { homedir as
|
|
1805
|
+
import { homedir as homedir10 } from "os";
|
|
1729
1806
|
function shellQuote(s) {
|
|
1730
1807
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1731
1808
|
}
|
|
@@ -1745,7 +1822,7 @@ function validateHooksPath(path) {
|
|
|
1745
1822
|
function readHooksFile(rawPath) {
|
|
1746
1823
|
const safePath = validateHooksPath(rawPath);
|
|
1747
1824
|
try {
|
|
1748
|
-
const raw =
|
|
1825
|
+
const raw = readFileSync10(safePath, "utf-8");
|
|
1749
1826
|
return JSON.parse(raw);
|
|
1750
1827
|
} catch (err) {
|
|
1751
1828
|
if (err?.code === "ENOENT") return { version: 1, hooks: {} };
|
|
@@ -1927,8 +2004,8 @@ var init_cursorHookConfig = __esm({
|
|
|
1927
2004
|
init_platform();
|
|
1928
2005
|
SYNKRO_MARKER2 = "__synkro_managed__";
|
|
1929
2006
|
ALLOWED_PARENT_DIRS = [
|
|
1930
|
-
resolve(
|
|
1931
|
-
resolve(
|
|
2007
|
+
resolve(homedir10(), ".cursor"),
|
|
2008
|
+
resolve(homedir10(), ".config", "cursor")
|
|
1932
2009
|
];
|
|
1933
2010
|
ALL_EVENTS = [
|
|
1934
2011
|
"sessionStart",
|
|
@@ -1948,9 +2025,9 @@ var init_cursorHookConfig = __esm({
|
|
|
1948
2025
|
});
|
|
1949
2026
|
|
|
1950
2027
|
// cli/installer/codexHookConfig.ts
|
|
1951
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2028
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
1952
2029
|
import { dirname as dirname3, resolve as resolve2, normalize as normalize2 } from "path";
|
|
1953
|
-
import { homedir as
|
|
2030
|
+
import { homedir as homedir11 } from "os";
|
|
1954
2031
|
function shellQuote2(s) {
|
|
1955
2032
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1956
2033
|
}
|
|
@@ -1967,8 +2044,8 @@ function codexCmd(scriptPath) {
|
|
|
1967
2044
|
function validateHooksPath2(path) {
|
|
1968
2045
|
const resolved = resolve2(normalize2(path));
|
|
1969
2046
|
const allowedParentDirs = [
|
|
1970
|
-
resolve2(
|
|
1971
|
-
resolve2(
|
|
2047
|
+
resolve2(homedir11(), ".codex"),
|
|
2048
|
+
resolve2(homedir11(), ".config", "codex"),
|
|
1972
2049
|
...process.env.CODEX_HOME ? [resolve2(process.env.CODEX_HOME)] : []
|
|
1973
2050
|
];
|
|
1974
2051
|
if (!allowedParentDirs.some((dir) => resolved.startsWith(dir + "/") || resolved === dir)) {
|
|
@@ -1980,7 +2057,7 @@ function readHooksFile2(rawPath) {
|
|
|
1980
2057
|
const safePath = validateHooksPath2(rawPath);
|
|
1981
2058
|
if (!existsSync12(safePath)) return { hooks: {} };
|
|
1982
2059
|
try {
|
|
1983
|
-
return JSON.parse(
|
|
2060
|
+
return JSON.parse(readFileSync11(safePath, "utf-8"));
|
|
1984
2061
|
} catch (err) {
|
|
1985
2062
|
throw new Error(`Failed to parse ${safePath}: ${err.message}`);
|
|
1986
2063
|
}
|
|
@@ -2026,13 +2103,19 @@ function installCodexHooks(hooksJsonPath, config) {
|
|
|
2026
2103
|
cmd(config.cvePrecheckScriptPath, 15),
|
|
2027
2104
|
...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
|
|
2028
2105
|
], M_EDIT);
|
|
2106
|
+
push(h, "PostToolUse", [
|
|
2107
|
+
cmd(config.editFollowupScriptPath, 30, "Recording edit")
|
|
2108
|
+
], M_EDIT);
|
|
2029
2109
|
push(h, "PreToolUse", [cmd(config.agentJudgeScriptPath, 50)], M_AGENT);
|
|
2030
2110
|
if (config.mcpGateScriptPath) push(h, "PreToolUse", [cmd(config.mcpGateScriptPath, 50)], M_MCP);
|
|
2031
2111
|
if (config.taskActivateIntentScriptPath) push(h, "PreToolUse", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
|
|
2112
|
+
if (config.taskActivateIntentScriptPath) push(h, "PermissionRequest", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
|
|
2032
2113
|
push(h, "PostToolUse", [cmd(config.bashFollowupScriptPath, 10)], M_BASH);
|
|
2033
2114
|
push(h, "UserPromptSubmit", [cmd(config.userPromptSubmitScriptPath, 5)]);
|
|
2034
2115
|
if (config.promptRouteScriptPath) push(h, "UserPromptSubmit", [cmd(config.promptRouteScriptPath, 5)]);
|
|
2035
2116
|
push(h, "SessionStart", [cmd(config.sessionStartScriptPath, 5)]);
|
|
2117
|
+
push(h, "SubagentStart", [cmd(config.subagentStartScriptPath, 5)]);
|
|
2118
|
+
if (!config.skipTranscriptSync) push(h, "SubagentStop", [cmd(config.subagentStopScriptPath, 5)]);
|
|
2036
2119
|
push(h, "SessionEnd", [cmd(config.stopSummaryScriptPath, 3)]);
|
|
2037
2120
|
if (!config.skipTranscriptSync) push(h, "Stop", [cmd(config.transcriptSyncScriptPath, 3)]);
|
|
2038
2121
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
@@ -2054,30 +2137,31 @@ function uninstallCodexHooks(hooksJsonPath) {
|
|
|
2054
2137
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
2055
2138
|
return true;
|
|
2056
2139
|
}
|
|
2057
|
-
var SYNKRO_MARKER3, M_BASH, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2140
|
+
var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2058
2141
|
var init_codexHookConfig = __esm({
|
|
2059
2142
|
"cli/installer/codexHookConfig.ts"() {
|
|
2060
2143
|
"use strict";
|
|
2061
2144
|
init_platform();
|
|
2062
2145
|
SYNKRO_MARKER3 = "__synkro_managed__";
|
|
2063
2146
|
M_BASH = "Bash";
|
|
2064
|
-
|
|
2147
|
+
CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
|
|
2148
|
+
M_EDIT = CODEX_EDIT_MATCHER;
|
|
2065
2149
|
M_AGENT = "Agent";
|
|
2066
2150
|
M_MCP = "mcp__.*";
|
|
2067
|
-
M_ACTIVATE = "mcp__synkro-guardrails__activate_standard";
|
|
2068
|
-
ALL_EVENTS2 = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop"];
|
|
2151
|
+
M_ACTIVATE = "mcp__synkro[-_]guardrails__activate_standard";
|
|
2152
|
+
ALL_EVENTS2 = ["PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "SubagentStart", "SubagentStop", "Stop"];
|
|
2069
2153
|
}
|
|
2070
2154
|
});
|
|
2071
2155
|
|
|
2072
2156
|
// cli/installer/mcpConfig.ts
|
|
2073
|
-
import { existsSync as existsSync13, readFileSync as
|
|
2074
|
-
import { homedir as
|
|
2075
|
-
import { dirname as dirname4, join as
|
|
2157
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
2158
|
+
import { homedir as homedir12 } from "os";
|
|
2159
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2076
2160
|
import { randomBytes } from "crypto";
|
|
2077
2161
|
function readClaudeJson() {
|
|
2078
2162
|
if (!existsSync13(CC_CONFIG_PATH)) return {};
|
|
2079
2163
|
try {
|
|
2080
|
-
const raw =
|
|
2164
|
+
const raw = readFileSync12(CC_CONFIG_PATH, "utf-8");
|
|
2081
2165
|
return JSON.parse(raw);
|
|
2082
2166
|
} catch (err) {
|
|
2083
2167
|
throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
|
|
@@ -2096,7 +2180,7 @@ function installMcpConfig(opts) {
|
|
|
2096
2180
|
if (entry?.[SYNKRO_MARKER4] === true) delete config.mcpServers[name];
|
|
2097
2181
|
}
|
|
2098
2182
|
if (opts.local) {
|
|
2099
|
-
const proxyScript =
|
|
2183
|
+
const proxyScript = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2100
2184
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
2101
2185
|
type: "stdio",
|
|
2102
2186
|
command: "bun",
|
|
@@ -2135,7 +2219,7 @@ function uninstallMcpConfig() {
|
|
|
2135
2219
|
function readCursorMcpJson() {
|
|
2136
2220
|
if (!existsSync13(CURSOR_MCP_PATH)) return {};
|
|
2137
2221
|
try {
|
|
2138
|
-
const raw =
|
|
2222
|
+
const raw = readFileSync12(CURSOR_MCP_PATH, "utf-8");
|
|
2139
2223
|
return JSON.parse(raw);
|
|
2140
2224
|
} catch (err) {
|
|
2141
2225
|
throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${err.message}`);
|
|
@@ -2156,10 +2240,10 @@ function installCursorMcpConfig(opts) {
|
|
|
2156
2240
|
if (opts.local) {
|
|
2157
2241
|
const port = process.env.SYNKRO_MCP_PORT || "18931";
|
|
2158
2242
|
const url2 = `http://127.0.0.1:${port}/`;
|
|
2159
|
-
const jwtPath =
|
|
2243
|
+
const jwtPath = join10(homedir12(), ".synkro", ".mcp-jwt");
|
|
2160
2244
|
let jwt2 = "";
|
|
2161
2245
|
try {
|
|
2162
|
-
jwt2 =
|
|
2246
|
+
jwt2 = readFileSync12(jwtPath, "utf-8").trim();
|
|
2163
2247
|
} catch {
|
|
2164
2248
|
}
|
|
2165
2249
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
@@ -2201,7 +2285,7 @@ function codexConfigPath(path) {
|
|
|
2201
2285
|
function readCodexToml(path) {
|
|
2202
2286
|
if (!existsSync13(path)) return "";
|
|
2203
2287
|
try {
|
|
2204
|
-
return
|
|
2288
|
+
return readFileSync12(path, "utf-8");
|
|
2205
2289
|
} catch (err) {
|
|
2206
2290
|
throw new Error(`Failed to read ${path}: ${err.message}`);
|
|
2207
2291
|
}
|
|
@@ -2290,7 +2374,7 @@ function resolveBunBin2() {
|
|
|
2290
2374
|
function readDesktopJson() {
|
|
2291
2375
|
if (!existsSync13(CLAUDE_DESKTOP_CONFIG_PATH)) return {};
|
|
2292
2376
|
try {
|
|
2293
|
-
return JSON.parse(
|
|
2377
|
+
return JSON.parse(readFileSync12(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
|
|
2294
2378
|
} catch (err) {
|
|
2295
2379
|
throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${err.message}`);
|
|
2296
2380
|
}
|
|
@@ -2347,31 +2431,31 @@ var init_mcpConfig = __esm({
|
|
|
2347
2431
|
init_platform();
|
|
2348
2432
|
SYNKRO_MARKER4 = "__synkro_managed__";
|
|
2349
2433
|
SYNKRO_SERVER_NAME = "synkro-guardrails";
|
|
2350
|
-
CC_CONFIG_PATH =
|
|
2351
|
-
CURSOR_MCP_PATH =
|
|
2352
|
-
CODEX_CONFIG_PATH =
|
|
2434
|
+
CC_CONFIG_PATH = join10(homedir12(), ".claude.json");
|
|
2435
|
+
CURSOR_MCP_PATH = join10(homedir12(), ".cursor", "mcp.json");
|
|
2436
|
+
CODEX_CONFIG_PATH = join10(process.env.CODEX_HOME || join10(homedir12(), ".codex"), "config.toml");
|
|
2353
2437
|
CODEX_MCP_BEGIN = "# >>> synkro managed MCP: synkro-guardrails";
|
|
2354
2438
|
CODEX_MCP_END = "# <<< synkro managed MCP: synkro-guardrails";
|
|
2355
2439
|
CODEX_MCP_SECTION = 'mcp_servers."synkro-guardrails"';
|
|
2356
|
-
CLAUDE_DESKTOP_CONFIG_PATH =
|
|
2357
|
-
|
|
2440
|
+
CLAUDE_DESKTOP_CONFIG_PATH = join10(
|
|
2441
|
+
homedir12(),
|
|
2358
2442
|
"Library",
|
|
2359
2443
|
"Application Support",
|
|
2360
2444
|
"Claude",
|
|
2361
2445
|
"claude_desktop_config.json"
|
|
2362
2446
|
);
|
|
2363
|
-
MCP_STDIO_PROXY_PATH =
|
|
2447
|
+
MCP_STDIO_PROXY_PATH = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2364
2448
|
}
|
|
2365
2449
|
});
|
|
2366
2450
|
|
|
2367
2451
|
// cli/installer/synkroCommand.ts
|
|
2368
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as
|
|
2369
|
-
import { homedir as
|
|
2370
|
-
import { join as
|
|
2452
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
2453
|
+
import { homedir as homedir13 } from "os";
|
|
2454
|
+
import { join as join11 } from "path";
|
|
2371
2455
|
function installSynkroCommand() {
|
|
2372
2456
|
try {
|
|
2373
2457
|
if (existsSync14(COMMAND_PATH)) {
|
|
2374
|
-
const current =
|
|
2458
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2375
2459
|
if (!current.includes(MARKER)) return null;
|
|
2376
2460
|
}
|
|
2377
2461
|
mkdirSync8(COMMANDS_DIR, { recursive: true });
|
|
@@ -2384,7 +2468,7 @@ function installSynkroCommand() {
|
|
|
2384
2468
|
function uninstallSynkroCommand() {
|
|
2385
2469
|
try {
|
|
2386
2470
|
if (!existsSync14(COMMAND_PATH)) return false;
|
|
2387
|
-
const current =
|
|
2471
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2388
2472
|
if (!current.includes(MARKER)) return false;
|
|
2389
2473
|
unlinkSync4(COMMAND_PATH);
|
|
2390
2474
|
return true;
|
|
@@ -2397,8 +2481,8 @@ var init_synkroCommand = __esm({
|
|
|
2397
2481
|
"cli/installer/synkroCommand.ts"() {
|
|
2398
2482
|
"use strict";
|
|
2399
2483
|
MARKER = "__synkro_managed__";
|
|
2400
|
-
COMMANDS_DIR =
|
|
2401
|
-
COMMAND_PATH =
|
|
2484
|
+
COMMANDS_DIR = join11(homedir13(), ".claude", "commands");
|
|
2485
|
+
COMMAND_PATH = join11(COMMANDS_DIR, "synkro.md");
|
|
2402
2486
|
COMMAND_BODY = `<!-- ${MARKER} -->
|
|
2403
2487
|
---
|
|
2404
2488
|
description: Set up Synkro \u2014 create a starter rule set, review suggestions, and watch a live block
|
|
@@ -2456,14 +2540,14 @@ var init_skillParser = __esm({
|
|
|
2456
2540
|
function stubHook(surface, optsLiteral) {
|
|
2457
2541
|
return "#!/usr/bin/env bun\nimport { runStub } from './_synkro-stub-common.ts';\nrunStub(" + JSON.stringify(surface) + ", " + optsLiteral + ");\n";
|
|
2458
2542
|
}
|
|
2459
|
-
var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
|
|
2543
|
+
var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_EDIT_FOLLOWUP_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_SUBAGENT_START_TS, STUB_SUBAGENT_STOP_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
|
|
2460
2544
|
var init_hookScriptsTs = __esm({
|
|
2461
2545
|
"cli/installer/hookScriptsTs.ts"() {
|
|
2462
2546
|
"use strict";
|
|
2463
|
-
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2547
|
+
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2464
2548
|
import { execSync } from 'node:child_process';
|
|
2465
2549
|
import { homedir } from 'node:os';
|
|
2466
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
2550
|
+
import { basename, join, resolve, relative, isAbsolute } from 'node:path';
|
|
2467
2551
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2468
2552
|
|
|
2469
2553
|
const HOME = homedir();
|
|
@@ -2622,13 +2706,17 @@ async function cloudMcpGate(payload: any, harness: string): Promise<string> {
|
|
|
2622
2706
|
try {
|
|
2623
2707
|
const toolName = String(payload.tool_name || payload.tool || '');
|
|
2624
2708
|
const parts = toolName.split('__');
|
|
2625
|
-
const
|
|
2709
|
+
const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
2710
|
+
const server = (parts.length >= 3 && parts[0] === 'mcp')
|
|
2711
|
+
? parts[1]
|
|
2712
|
+
: String(payload.server_name || payload.serverName || payload.server || payload.mcp_server
|
|
2713
|
+
|| input.server_name || input.serverName || input.server || input.mcp_server || '');
|
|
2626
2714
|
if (!server) return failOpen(harness);
|
|
2627
2715
|
const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\/+$/, '');
|
|
2628
2716
|
const resp = await fetch(base + '/api/mcp/gate', {
|
|
2629
2717
|
method: 'POST',
|
|
2630
2718
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },
|
|
2631
|
-
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),
|
|
2719
|
+
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') || toolName, harness: harness }),
|
|
2632
2720
|
signal: AbortSignal.timeout(5000),
|
|
2633
2721
|
});
|
|
2634
2722
|
if (!resp.ok) return failOpen(harness);
|
|
@@ -2725,48 +2813,293 @@ function gatherBaseContent(fp: string, ti: any): string | undefined {
|
|
|
2725
2813
|
return '\n'.repeat(linesBefore) + full.slice(start, end);
|
|
2726
2814
|
}
|
|
2727
2815
|
|
|
2816
|
+
function gatherPreEditContentFromPost(fp: string, toolName: string, ti: any, operation: string): string | undefined {
|
|
2817
|
+
if (operation === 'add') return '';
|
|
2818
|
+
if (operation === 'delete') {
|
|
2819
|
+
const patch = String(ti?.patch || '');
|
|
2820
|
+
const removed: string[] = [];
|
|
2821
|
+
for (const line of patch.split('\n')) {
|
|
2822
|
+
if (line.startsWith('*** ') || line.startsWith('@@')) continue;
|
|
2823
|
+
if (line.startsWith('-')) removed.push(line.slice(1));
|
|
2824
|
+
}
|
|
2825
|
+
return removed.length ? removed.join('\n') : undefined;
|
|
2826
|
+
}
|
|
2827
|
+
let full = '';
|
|
2828
|
+
try { full = readFileSync(fp, 'utf-8'); } catch { return undefined; }
|
|
2829
|
+
if (toolName === 'Edit') {
|
|
2830
|
+
const oldString = String(ti?.old_string || '');
|
|
2831
|
+
const newString = String(ti?.new_string || '');
|
|
2832
|
+
if (newString && full.includes(newString)) return full.replace(newString, oldString);
|
|
2833
|
+
return undefined;
|
|
2834
|
+
}
|
|
2835
|
+
if (toolName === 'MultiEdit' && Array.isArray(ti?.edits)) {
|
|
2836
|
+
let before = full;
|
|
2837
|
+
for (const edit of [...ti.edits].reverse()) {
|
|
2838
|
+
const oldString = String(edit?.old_string || '');
|
|
2839
|
+
const newString = String(edit?.new_string || '');
|
|
2840
|
+
if (!newString || !before.includes(newString)) return undefined;
|
|
2841
|
+
before = before.replace(newString, oldString);
|
|
2842
|
+
}
|
|
2843
|
+
return before;
|
|
2844
|
+
}
|
|
2845
|
+
return full;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2728
2848
|
// Codex edits arrive as the apply_patch tool with the patch text in
|
|
2729
2849
|
// tool_input.command (lines: "*** Update/Add/Delete File: PATH", then hunks where
|
|
2730
2850
|
// "+" adds, "-" removes, " " is context). The edit hooks expect the CC shape
|
|
2731
|
-
// {file_path, old_string, new_string}; translate
|
|
2732
|
-
//
|
|
2733
|
-
// more than one file or hunk cannot be reconstructed faithfully as one Edit, so
|
|
2734
|
-
// mark it for a clear deny-and-retry response instead of grading partial content.
|
|
2851
|
+
// {file_path, old_string, new_string}; translate every file and hunk into an
|
|
2852
|
+
// explicit edit batch so one apply_patch call never drops evidence.
|
|
2735
2853
|
export function normalizeCodexApplyPatch(payload: any): void {
|
|
2736
2854
|
try {
|
|
2737
|
-
if (
|
|
2855
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(String((payload && payload.tool_name) || ''))) return;
|
|
2738
2856
|
const ti = payload.tool_input;
|
|
2739
2857
|
const patch = (ti && typeof ti === 'object')
|
|
2740
2858
|
? String(ti.command || ti.patch || ti.input || '')
|
|
2741
2859
|
: (typeof ti === 'string' ? ti : '');
|
|
2742
2860
|
if (!patch) return;
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2861
|
+
type Hunk = { oldLines: string[]; newLines: string[] };
|
|
2862
|
+
type PatchFile = {
|
|
2863
|
+
operation: 'Add' | 'Update' | 'Delete';
|
|
2864
|
+
path: string;
|
|
2865
|
+
moveTarget: string;
|
|
2866
|
+
hunks: Hunk[];
|
|
2867
|
+
fragment: string[];
|
|
2868
|
+
};
|
|
2869
|
+
const files: PatchFile[] = [];
|
|
2870
|
+
let current: PatchFile | null = null;
|
|
2871
|
+
let hunk: Hunk | null = null;
|
|
2872
|
+
const flushHunk = () => {
|
|
2873
|
+
if (current && hunk && (hunk.oldLines.length || hunk.newLines.length)) current.hunks.push(hunk);
|
|
2874
|
+
hunk = null;
|
|
2875
|
+
};
|
|
2876
|
+
const flushFile = () => {
|
|
2877
|
+
flushHunk();
|
|
2878
|
+
if (current) files.push(current);
|
|
2879
|
+
current = null;
|
|
2880
|
+
};
|
|
2747
2881
|
for (const ln of patch.split('\n')) {
|
|
2748
2882
|
const mFile = ln.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+?)\s*$/);
|
|
2749
|
-
if (mFile) {
|
|
2750
|
-
|
|
2883
|
+
if (mFile) {
|
|
2884
|
+
flushFile();
|
|
2885
|
+
const operation = ln.match(/^\*\*\*\s+(Update|Add|Delete)\s+File:/)?.[1] as 'Add' | 'Update' | 'Delete';
|
|
2886
|
+
current = {
|
|
2887
|
+
operation,
|
|
2888
|
+
path: mFile[1].trim(),
|
|
2889
|
+
moveTarget: '',
|
|
2890
|
+
hunks: [],
|
|
2891
|
+
fragment: [ln],
|
|
2892
|
+
};
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
if (!current) continue;
|
|
2896
|
+
current.fragment.push(ln);
|
|
2897
|
+
const mMove = ln.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
|
|
2898
|
+
if (mMove) { current.moveTarget = mMove[1].trim(); continue; }
|
|
2899
|
+
if (ln.indexOf('@@') === 0) {
|
|
2900
|
+
flushHunk();
|
|
2901
|
+
hunk = { oldLines: [], newLines: [] };
|
|
2902
|
+
const anchor = ln.slice(2).trim();
|
|
2903
|
+
if (anchor) { hunk.oldLines.push(anchor); hunk.newLines.push(anchor); }
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2751
2906
|
if (ln.indexOf('*** ') === 0) continue;
|
|
2907
|
+
hunk = hunk || { oldLines: [], newLines: [] };
|
|
2752
2908
|
const c = ln.charAt(0);
|
|
2753
|
-
if (c === '+') newLines.push(ln.slice(1));
|
|
2754
|
-
else if (c === '-') oldLines.push(ln.slice(1));
|
|
2755
|
-
else if (c === ' ') { oldLines.push(ln.slice(1)); newLines.push(ln.slice(1)); }
|
|
2909
|
+
if (c === '+') hunk.newLines.push(ln.slice(1));
|
|
2910
|
+
else if (c === '-') hunk.oldLines.push(ln.slice(1));
|
|
2911
|
+
else if (c === ' ') { hunk.oldLines.push(ln.slice(1)); hunk.newLines.push(ln.slice(1)); }
|
|
2756
2912
|
}
|
|
2757
|
-
|
|
2913
|
+
flushFile();
|
|
2914
|
+
if (!files.length) {
|
|
2758
2915
|
payload.__synkro_codex_patch_error =
|
|
2759
|
-
'Synkro
|
|
2916
|
+
'Synkro could not recover a file edit from this apply_patch payload.';
|
|
2760
2917
|
return;
|
|
2761
2918
|
}
|
|
2762
|
-
if (!files[0]) return;
|
|
2763
2919
|
const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2920
|
+
const normalized = files.map((file) => {
|
|
2921
|
+
const filePath = !isAbsolute(file.path) && cwd ? resolve(cwd, file.path) : file.path;
|
|
2922
|
+
const patchFragment = file.fragment.join('\n');
|
|
2923
|
+
if (file.operation === 'Add') {
|
|
2924
|
+
return {
|
|
2925
|
+
tool_name: 'Write',
|
|
2926
|
+
tool_input: {
|
|
2927
|
+
file_path: filePath,
|
|
2928
|
+
content: file.hunks.flatMap((item) => item.newLines).join('\n'),
|
|
2929
|
+
patch: patchFragment,
|
|
2930
|
+
},
|
|
2931
|
+
__synkro_codex_operation: 'add',
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
if (file.operation === 'Delete') {
|
|
2935
|
+
return {
|
|
2936
|
+
tool_name: 'Write',
|
|
2937
|
+
tool_input: { file_path: filePath, content: '', patch: patchFragment },
|
|
2938
|
+
__synkro_codex_operation: 'delete',
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
const edits = file.hunks.map((item) => ({
|
|
2942
|
+
old_string: item.oldLines.join('\n'),
|
|
2943
|
+
new_string: item.newLines.join('\n'),
|
|
2944
|
+
}));
|
|
2945
|
+
return {
|
|
2946
|
+
tool_name: edits.length > 1 ? 'MultiEdit' : 'Edit',
|
|
2947
|
+
tool_input: edits.length > 1
|
|
2948
|
+
? { file_path: filePath, edits, patch: patchFragment }
|
|
2949
|
+
: { file_path: filePath, ...(edits[0] || { old_string: '', new_string: '' }), patch: patchFragment },
|
|
2950
|
+
__synkro_codex_operation: file.moveTarget ? 'rename' : 'modify',
|
|
2951
|
+
...(file.moveTarget ? { __synkro_codex_move_target: file.moveTarget } : {}),
|
|
2952
|
+
};
|
|
2953
|
+
});
|
|
2954
|
+
payload.__synkro_codex_edits = normalized;
|
|
2955
|
+
Object.assign(payload, normalized[0]);
|
|
2767
2956
|
} catch { /* leave payload untouched on any parse error */ }
|
|
2768
2957
|
}
|
|
2769
2958
|
|
|
2959
|
+
type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
|
|
2960
|
+
|
|
2961
|
+
function completedCodexToolRecord(entry: any): any {
|
|
2962
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
2963
|
+
if (entry.type === 'response_item' && entry.payload && typeof entry.payload === 'object') {
|
|
2964
|
+
return entry.payload;
|
|
2965
|
+
}
|
|
2966
|
+
return entry.payload && typeof entry.payload === 'object' ? entry.payload : entry;
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function completedCodexToolInput(record: any): any {
|
|
2970
|
+
const raw = record?.input ?? record?.arguments ?? record?.command;
|
|
2971
|
+
if (raw && typeof raw === 'object') return raw;
|
|
2972
|
+
if (typeof raw !== 'string') return {};
|
|
2973
|
+
try {
|
|
2974
|
+
const parsed = JSON.parse(raw);
|
|
2975
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
2976
|
+
if (typeof parsed === 'string') return { command: parsed };
|
|
2977
|
+
} catch {}
|
|
2978
|
+
return { command: raw };
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
function containedCodexEditPath(repoRoot: string, filePath: string): string {
|
|
2982
|
+
if (!repoRoot || !filePath) return '';
|
|
2983
|
+
try {
|
|
2984
|
+
const lexicalRoot = resolve(repoRoot);
|
|
2985
|
+
const realRoot = realpathSync(lexicalRoot);
|
|
2986
|
+
const lexical = isAbsolute(filePath) ? resolve(filePath) : resolve(lexicalRoot, filePath);
|
|
2987
|
+
const isContained = (rel: string): boolean =>
|
|
2988
|
+
!!rel && rel !== '..' && !rel.startsWith('..' + '/') && !isAbsolute(rel);
|
|
2989
|
+
// Accept an absolute path expressed through either the lexical repo alias
|
|
2990
|
+
// (/var on macOS, a symlinked checkout) or its physical path, but always
|
|
2991
|
+
// continue from the physical root so new and existing files share one
|
|
2992
|
+
// canonical provenance path.
|
|
2993
|
+
let rel = relative(lexicalRoot, lexical);
|
|
2994
|
+
if (!isContained(rel)) {
|
|
2995
|
+
rel = relative(realRoot, lexical);
|
|
2996
|
+
if (!isContained(rel)) return '';
|
|
2997
|
+
}
|
|
2998
|
+
let candidate = resolve(realRoot, rel);
|
|
2999
|
+
// realpathSync requires the leaf to exist. Walk to the nearest existing
|
|
3000
|
+
// ancestor, resolve symlinks there, then append the missing suffix. This
|
|
3001
|
+
// both canonicalizes new files and rejects a new leaf beneath an in-repo
|
|
3002
|
+
// symlink that actually escapes the repository.
|
|
3003
|
+
const missing: string[] = [];
|
|
3004
|
+
let ancestor = candidate;
|
|
3005
|
+
while (!existsSync(ancestor)) {
|
|
3006
|
+
const parent = resolve(ancestor, '..');
|
|
3007
|
+
if (parent === ancestor) return '';
|
|
3008
|
+
missing.unshift(basename(ancestor));
|
|
3009
|
+
ancestor = parent;
|
|
3010
|
+
}
|
|
3011
|
+
candidate = resolve(realpathSync(ancestor), ...missing);
|
|
3012
|
+
const realRel = relative(realRoot, candidate);
|
|
3013
|
+
if (!realRel || realRel.startsWith('..') || isAbsolute(realRel)) return '';
|
|
3014
|
+
return existsSync(candidate) ? realpathSync(candidate) : candidate;
|
|
3015
|
+
} catch {
|
|
3016
|
+
return '';
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* Recover completed Codex apply_patch calls from a bounded transcript tail.
|
|
3022
|
+
* A tool call is evidence only after a matching tool output exists. Calls are
|
|
3023
|
+
* expanded one event per file, restricted to the repo, and keyed by the stable
|
|
3024
|
+
* Codex call id so replay is idempotent server-side.
|
|
3025
|
+
*/
|
|
3026
|
+
export function extractCompletedCodexPatchEdits(
|
|
3027
|
+
transcript: string,
|
|
3028
|
+
repoRoot: string,
|
|
3029
|
+
): RecoveredCodexEdit[] {
|
|
3030
|
+
if (!transcript || !repoRoot) return [];
|
|
3031
|
+
const calls = new Map<string, { record: any; timestamp: string }>();
|
|
3032
|
+
const completed = new Set<string>();
|
|
3033
|
+
for (const line of transcript.split('\n')) {
|
|
3034
|
+
if (!line.trim()) continue;
|
|
3035
|
+
let entry: any;
|
|
3036
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
3037
|
+
const record = completedCodexToolRecord(entry);
|
|
3038
|
+
const type = String(record?.type || '');
|
|
3039
|
+
const callId = String(record?.call_id || record?.tool_call_id || record?.id || '');
|
|
3040
|
+
if (!callId) continue;
|
|
3041
|
+
if (type === 'custom_tool_call_output' || type === 'function_call_output') {
|
|
3042
|
+
completed.add(callId);
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
if (type !== 'custom_tool_call' && type !== 'function_call') continue;
|
|
3046
|
+
const name = String(record?.name || record?.tool_name || '');
|
|
3047
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(name)) continue;
|
|
3048
|
+
calls.set(callId, {
|
|
3049
|
+
record,
|
|
3050
|
+
timestamp: String(entry?.timestamp || record?.timestamp || ''),
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
const results: RecoveredCodexEdit[] = [];
|
|
3055
|
+
const seen = new Set<string>();
|
|
3056
|
+
const candidates = [...calls.entries()].filter(([callId]) => completed.has(callId)).slice(-100);
|
|
3057
|
+
for (const [callId, call] of candidates) {
|
|
3058
|
+
const toolInput = completedCodexToolInput(call.record);
|
|
3059
|
+
const patch = String(toolInput?.command || toolInput?.patch || toolInput?.input || '');
|
|
3060
|
+
if (!patch || patch.length > 100000) continue;
|
|
3061
|
+
const raw: any = {
|
|
3062
|
+
cwd: repoRoot,
|
|
3063
|
+
tool_name: String(call.record?.name || call.record?.tool_name || 'apply_patch'),
|
|
3064
|
+
tool_input: toolInput,
|
|
3065
|
+
tool_call_id: callId,
|
|
3066
|
+
_ts: call.timestamp || undefined,
|
|
3067
|
+
};
|
|
3068
|
+
normalizeCodexApplyPatch(raw);
|
|
3069
|
+
const edits = Array.isArray(raw.__synkro_codex_edits) ? raw.__synkro_codex_edits : [];
|
|
3070
|
+
for (const edit of edits) {
|
|
3071
|
+
if (results.length >= 50) return results;
|
|
3072
|
+
const fp = containedCodexEditPath(repoRoot, filePathFromToolInput(edit?.tool_input));
|
|
3073
|
+
if (!fp) continue;
|
|
3074
|
+
const dedupeKey = callId + '\u0000' + fp;
|
|
3075
|
+
if (seen.has(dedupeKey)) continue;
|
|
3076
|
+
seen.add(dedupeKey);
|
|
3077
|
+
const payload: any = {
|
|
3078
|
+
cwd: repoRoot,
|
|
3079
|
+
tool_name: edit.tool_name,
|
|
3080
|
+
tool_input: { ...(edit.tool_input || {}), file_path: fp },
|
|
3081
|
+
tool_call_id: callId,
|
|
3082
|
+
_ts: call.timestamp || undefined,
|
|
3083
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3084
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3085
|
+
__synkro_edit_source: 'transcript',
|
|
3086
|
+
};
|
|
3087
|
+
const baseContent = gatherPreEditContentFromPost(
|
|
3088
|
+
fp,
|
|
3089
|
+
String(payload.tool_name || ''),
|
|
3090
|
+
payload.tool_input,
|
|
3091
|
+
String(payload.__synkro_codex_operation || ''),
|
|
3092
|
+
);
|
|
3093
|
+
if (baseContent === undefined) continue;
|
|
3094
|
+
results.push({
|
|
3095
|
+
payload,
|
|
3096
|
+
baseContent,
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
return results;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2770
3103
|
interface StubOpts {
|
|
2771
3104
|
needsFile?: boolean;
|
|
2772
3105
|
needsTranscript?: boolean;
|
|
@@ -2776,6 +3109,115 @@ interface StubOpts {
|
|
|
2776
3109
|
needsPlan?: boolean;
|
|
2777
3110
|
telemetry?: boolean;
|
|
2778
3111
|
harness?: string;
|
|
3112
|
+
postEdit?: boolean;
|
|
3113
|
+
/** Codex SubagentStart/SubagentStop report the parent as session_id and
|
|
3114
|
+
* the child as agent_id. Remap them to the ordinary child-session envelope. */
|
|
3115
|
+
subagent?: boolean;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
function isContainedFile(root: string, candidate: string): boolean {
|
|
3119
|
+
try {
|
|
3120
|
+
const rel = relative(root, candidate);
|
|
3121
|
+
return !!rel && !rel.startsWith('..') && !isAbsolute(rel) && statSync(candidate).isFile();
|
|
3122
|
+
} catch { return false; }
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
function safeCodexSessionId(value: string): boolean {
|
|
3126
|
+
if (!value || value.length > 200) return false;
|
|
3127
|
+
for (const ch of value) {
|
|
3128
|
+
const code = ch.charCodeAt(0);
|
|
3129
|
+
const alphaNumeric = (code >= 48 && code <= 57)
|
|
3130
|
+
|| (code >= 65 && code <= 90)
|
|
3131
|
+
|| (code >= 97 && code <= 122);
|
|
3132
|
+
if (!alphaNumeric && ch !== '-' && ch !== '_') return false;
|
|
3133
|
+
}
|
|
3134
|
+
return true;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
function readJsonlHeader(path: string): string {
|
|
3138
|
+
let fd = -1;
|
|
3139
|
+
try {
|
|
3140
|
+
fd = openSync(path, 'r');
|
|
3141
|
+
const buf = Buffer.alloc(16384);
|
|
3142
|
+
const count = readSync(fd, buf, 0, buf.length, 0);
|
|
3143
|
+
const head = buf.subarray(0, count).toString('utf-8');
|
|
3144
|
+
const newline = head.indexOf('\n');
|
|
3145
|
+
return newline >= 0 ? head.slice(0, newline) : head;
|
|
3146
|
+
} catch { return ''; }
|
|
3147
|
+
finally { if (fd >= 0) { try { closeSync(fd); } catch {} } }
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
/**
|
|
3151
|
+
* Codex hook payloads identify the rollout by session_id but do not always send
|
|
3152
|
+
* transcript_path. Resolve that ID only within CODEX_HOME/sessions (or the
|
|
3153
|
+
* default ~/.codex/sessions), then cache the exact contained file so subsequent
|
|
3154
|
+
* tool hooks do not recursively scan the rollout tree.
|
|
3155
|
+
*/
|
|
3156
|
+
export function resolveHookTranscriptPath(payload: any, harness: string, sessionId: string): string {
|
|
3157
|
+
const explicit = [
|
|
3158
|
+
payload?.transcript_path,
|
|
3159
|
+
payload?.rollout_path,
|
|
3160
|
+
payload?.session_path,
|
|
3161
|
+
].find((value) => typeof value === 'string' && value);
|
|
3162
|
+
if (typeof explicit === 'string') return explicit;
|
|
3163
|
+
if (harness !== 'codex' || !safeCodexSessionId(sessionId)) return '';
|
|
3164
|
+
|
|
3165
|
+
const homeRoots = [
|
|
3166
|
+
process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : '',
|
|
3167
|
+
resolve(join(HOME, '.codex')),
|
|
3168
|
+
].filter((value, index, all) => !!value && all.indexOf(value) === index);
|
|
3169
|
+
const sessionRoots = homeRoots.map((root) => join(root, 'sessions')).filter((root) => existsSync(root));
|
|
3170
|
+
if (!sessionRoots.length) return '';
|
|
3171
|
+
|
|
3172
|
+
const cachePath = join(
|
|
3173
|
+
HOME,
|
|
3174
|
+
'.synkro',
|
|
3175
|
+
'codex-transcript-' + createHash('sha256').update(sessionId).digest('hex').slice(0, 16),
|
|
3176
|
+
);
|
|
3177
|
+
try {
|
|
3178
|
+
const cached = resolve(readFileSync(cachePath, 'utf-8').trim());
|
|
3179
|
+
if (sessionRoots.some((root) => isContainedFile(root, cached))) return cached;
|
|
3180
|
+
} catch {}
|
|
3181
|
+
|
|
3182
|
+
const candidates: string[] = [];
|
|
3183
|
+
for (const root of sessionRoots) {
|
|
3184
|
+
let entries: string[] = [];
|
|
3185
|
+
try { entries = readdirSync(root, { recursive: true, encoding: 'utf-8' }) as string[]; } catch {}
|
|
3186
|
+
for (const entry of entries) {
|
|
3187
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
3188
|
+
const candidate = resolve(join(root, entry));
|
|
3189
|
+
if (isContainedFile(root, candidate)) candidates.push(candidate);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
let found = candidates.find((candidate) => candidate.includes(sessionId)) || '';
|
|
3194
|
+
if (!found) {
|
|
3195
|
+
const recent = candidates
|
|
3196
|
+
.map((candidate) => {
|
|
3197
|
+
try { return { candidate, mtime: statSync(candidate).mtimeMs }; }
|
|
3198
|
+
catch { return { candidate, mtime: 0 }; }
|
|
3199
|
+
})
|
|
3200
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
3201
|
+
.slice(0, 100);
|
|
3202
|
+
for (const item of recent) {
|
|
3203
|
+
try {
|
|
3204
|
+
const first = JSON.parse(readJsonlHeader(item.candidate));
|
|
3205
|
+
const candidateId = String(first?.payload?.session_id || first?.payload?.id || '');
|
|
3206
|
+
if (first?.type === 'session_meta' && candidateId === sessionId) {
|
|
3207
|
+
found = item.candidate;
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3210
|
+
} catch {}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
if (found) {
|
|
3215
|
+
try {
|
|
3216
|
+
mkdirSync(join(HOME, '.synkro'), { recursive: true });
|
|
3217
|
+
writeFileSync(cachePath, found, { encoding: 'utf-8', mode: 0o600 });
|
|
3218
|
+
} catch {}
|
|
3219
|
+
}
|
|
3220
|
+
return found;
|
|
2779
3221
|
}
|
|
2780
3222
|
|
|
2781
3223
|
export async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {
|
|
@@ -2792,6 +3234,17 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2792
3234
|
// Codex: rewrite apply_patch tool_input into the CC edit shape before any
|
|
2793
3235
|
// surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
|
|
2794
3236
|
if (harness === 'codex') normalizeCodexApplyPatch(payload);
|
|
3237
|
+
if (harness === 'codex' && opts.subagent) {
|
|
3238
|
+
const parentSessionId = String(payload?.session_id || '');
|
|
3239
|
+
const childSessionId = String(payload?.agent_id || '');
|
|
3240
|
+
if (parentSessionId && childSessionId) {
|
|
3241
|
+
payload.parent_session_id = parentSessionId;
|
|
3242
|
+
payload.session_id = childSessionId;
|
|
3243
|
+
if (!payload.transcript_path && payload.agent_transcript_path) {
|
|
3244
|
+
payload.transcript_path = payload.agent_transcript_path;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
2795
3248
|
telemPayload = payload;
|
|
2796
3249
|
telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';
|
|
2797
3250
|
telemSessionId = String(payload?.session_id || payload?.conversation_id || '');
|
|
@@ -2862,14 +3315,40 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2862
3315
|
const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };
|
|
2863
3316
|
|
|
2864
3317
|
if (opts.needsFile) {
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3318
|
+
const normalizedEdits = harness === 'codex' && Array.isArray(payload.__synkro_codex_edits)
|
|
3319
|
+
? payload.__synkro_codex_edits
|
|
3320
|
+
: [{ tool_name: payload.tool_name, tool_input: payload.tool_input }];
|
|
3321
|
+
const editBatch = normalizedEdits.map((edit: any) => {
|
|
3322
|
+
const childPayload = {
|
|
3323
|
+
...payload,
|
|
3324
|
+
tool_name: edit.tool_name,
|
|
3325
|
+
tool_input: edit.tool_input,
|
|
3326
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3327
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3328
|
+
};
|
|
3329
|
+
delete childPayload.__synkro_codex_edits;
|
|
3330
|
+
const fp = filePathFromToolInput(childPayload.tool_input || {});
|
|
3331
|
+
const canRead = fp && (existsSync(fp) || (opts.postEdit && childPayload.__synkro_codex_operation === 'add'));
|
|
3332
|
+
const bc = canRead
|
|
3333
|
+
? (opts.postEdit
|
|
3334
|
+
? gatherPreEditContentFromPost(
|
|
3335
|
+
fp,
|
|
3336
|
+
String(childPayload.tool_name || ''),
|
|
3337
|
+
childPayload.tool_input || {},
|
|
3338
|
+
String(childPayload.__synkro_codex_operation || ''),
|
|
3339
|
+
)
|
|
3340
|
+
: gatherBaseContent(fp, childPayload.tool_input || {}))
|
|
3341
|
+
: undefined;
|
|
3342
|
+
return { payload: childPayload, ...(bc !== undefined ? { baseContent: bc } : {}) };
|
|
3343
|
+
});
|
|
3344
|
+
if (editBatch.length) {
|
|
3345
|
+
envelope.editBatch = editBatch;
|
|
3346
|
+
envelope.payload = editBatch[0].payload;
|
|
3347
|
+
if (editBatch[0].baseContent !== undefined) envelope.baseContent = editBatch[0].baseContent;
|
|
2869
3348
|
}
|
|
2870
3349
|
}
|
|
2871
3350
|
if (opts.needsTranscript) {
|
|
2872
|
-
const tp =
|
|
3351
|
+
const tp = resolveHookTranscriptPath(payload, harness, sessionId);
|
|
2873
3352
|
if (tp && existsSync(tp)) {
|
|
2874
3353
|
try {
|
|
2875
3354
|
const t = readFileSync(tp, 'utf-8');
|
|
@@ -2881,6 +3360,10 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2881
3360
|
// by ts, so we no longer depend on the whole file for absolute message indices.
|
|
2882
3361
|
const cap = opts.fullTranscript ? 400000 : 200000;
|
|
2883
3362
|
envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);
|
|
3363
|
+
if (harness === 'codex') {
|
|
3364
|
+
const recovered = extractCompletedCodexPatchEdits(envelope.transcript, root || cwd);
|
|
3365
|
+
if (recovered.length) envelope.transcriptEdits = recovered;
|
|
3366
|
+
}
|
|
2884
3367
|
} catch {}
|
|
2885
3368
|
}
|
|
2886
3369
|
}
|
|
@@ -2896,7 +3379,8 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2896
3379
|
// prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from
|
|
2897
3380
|
// being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)
|
|
2898
3381
|
// and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.
|
|
2899
|
-
const timeoutMs = surface === '
|
|
3382
|
+
const timeoutMs = surface === 'cwe-precheck' ? 48000
|
|
3383
|
+
: surface === 'bash-followup' ? 32000
|
|
2900
3384
|
: surface === 'prompt-submit' ? 3500
|
|
2901
3385
|
: (opts.telemetry ? 6000 : 28000);
|
|
2902
3386
|
// Cloud has no local container to reach. Post the SAME envelope to the org's grader
|
|
@@ -3061,8 +3545,16 @@ function toolInputSummary(toolName: string, toolInput: any, includeCommand: bool
|
|
|
3061
3545
|
// cc_model is derived by the caller (bounded tail read) and passed in; this stays
|
|
3062
3546
|
// allocation-only so the dormant hot path never touches the transcript.
|
|
3063
3547
|
function telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {
|
|
3548
|
+
const normalizedHarness = String(harness || '').trim().toLowerCase().replace(/[\\s-]+/g, '_');
|
|
3549
|
+
const agentKind = normalizedHarness === 'cursor'
|
|
3550
|
+
? 'cursor'
|
|
3551
|
+
: normalizedHarness === 'codex'
|
|
3552
|
+
? 'codex'
|
|
3553
|
+
: normalizedHarness === 'cc' || normalizedHarness === 'claude_code'
|
|
3554
|
+
? 'claude_code'
|
|
3555
|
+
: undefined;
|
|
3064
3556
|
return {
|
|
3065
|
-
agent_kind:
|
|
3557
|
+
agent_kind: agentKind,
|
|
3066
3558
|
cc_session_id: sessionId || undefined,
|
|
3067
3559
|
cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,
|
|
3068
3560
|
cc_model: ccModel || undefined,
|
|
@@ -3164,6 +3656,7 @@ function emitStubTelemetry(
|
|
|
3164
3656
|
}
|
|
3165
3657
|
`;
|
|
3166
3658
|
STUB_EDIT_PRECHECK_TS = stubHook("edit-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3659
|
+
STUB_EDIT_FOLLOWUP_TS = stubHook("edit-followup", "{ needsFile: true, needsTranscript: true, postEdit: true }");
|
|
3167
3660
|
STUB_CWE_PRECHECK_TS = stubHook("cwe-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3168
3661
|
STUB_CVE_PRECHECK_TS = stubHook("cve-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3169
3662
|
STUB_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true }");
|
|
@@ -3175,6 +3668,8 @@ function emitStubTelemetry(
|
|
|
3175
3668
|
STUB_STOP_SUMMARY_TS = stubHook("stop-summary", "{ needsTranscript: true, fullTranscript: true }");
|
|
3176
3669
|
STUB_SESSION_START_TS = stubHook("session-start", "{ telemetry: true }");
|
|
3177
3670
|
STUB_TRANSCRIPT_SYNC_TS = stubHook("transcript-sync", "{ needsTranscript: true, fullTranscript: true, telemetry: true }");
|
|
3671
|
+
STUB_SUBAGENT_START_TS = stubHook("subagent-start", "{ telemetry: true, subagent: true }");
|
|
3672
|
+
STUB_SUBAGENT_STOP_TS = stubHook("subagent-stop", "{ needsTranscript: true, fullTranscript: true, telemetry: true, subagent: true }");
|
|
3178
3673
|
STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
|
|
3179
3674
|
STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
|
|
3180
3675
|
STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
|
|
@@ -3307,7 +3802,7 @@ try {
|
|
|
3307
3802
|
const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
|
|
3308
3803
|
method: 'POST',
|
|
3309
3804
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
3310
|
-
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x',
|
|
3805
|
+
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
|
|
3311
3806
|
signal: AbortSignal.timeout(1000),
|
|
3312
3807
|
});
|
|
3313
3808
|
if (resp.ok) {
|
|
@@ -3346,8 +3841,14 @@ const PORT = process.env.SYNKRO_MCP_PORT || '18931';
|
|
|
3346
3841
|
function mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }
|
|
3347
3842
|
const chunks: Buffer[] = [];
|
|
3348
3843
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3844
|
+
let originalToolInput: Record<string, unknown> = {};
|
|
3845
|
+
let hookEventName = 'PreToolUse';
|
|
3349
3846
|
try {
|
|
3350
3847
|
const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
|
|
3848
|
+
hookEventName = String(payload.hook_event_name || payload.hookEventName || 'PreToolUse');
|
|
3849
|
+
if (payload.tool_input && typeof payload.tool_input === 'object' && !Array.isArray(payload.tool_input)) {
|
|
3850
|
+
originalToolInput = payload.tool_input;
|
|
3851
|
+
}
|
|
3351
3852
|
await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {
|
|
3352
3853
|
method: 'POST',
|
|
3353
3854
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
@@ -3355,7 +3856,29 @@ try {
|
|
|
3355
3856
|
signal: AbortSignal.timeout(5000),
|
|
3356
3857
|
});
|
|
3357
3858
|
} catch {}
|
|
3358
|
-
|
|
3859
|
+
// PreToolUse enforcement and Codex's native approval are separate lifecycle
|
|
3860
|
+
// stages. The policy gate already allowed this exact activate_standard call;
|
|
3861
|
+
// approve its subsequent PermissionRequest so headless approval_policy=never
|
|
3862
|
+
// can execute it. This script is registered only for the activation matcher,
|
|
3863
|
+
// never as a blanket MCP approval hook.
|
|
3864
|
+
if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionRequest') {
|
|
3865
|
+
process.stdout.write(JSON.stringify({
|
|
3866
|
+
hookSpecificOutput: {
|
|
3867
|
+
hookEventName: 'PermissionRequest',
|
|
3868
|
+
decision: { behavior: 'allow' },
|
|
3869
|
+
},
|
|
3870
|
+
}) + '\\n');
|
|
3871
|
+
} else if (process.env.SYNKRO_HOOK_FORMAT === 'codex') {
|
|
3872
|
+
process.stdout.write(JSON.stringify({
|
|
3873
|
+
hookSpecificOutput: {
|
|
3874
|
+
hookEventName: 'PreToolUse',
|
|
3875
|
+
permissionDecision: 'allow',
|
|
3876
|
+
updatedInput: originalToolInput,
|
|
3877
|
+
},
|
|
3878
|
+
}) + '\\n');
|
|
3879
|
+
} else {
|
|
3880
|
+
process.stdout.write('{}\\n');
|
|
3881
|
+
}
|
|
3359
3882
|
`;
|
|
3360
3883
|
STUB_CURSOR_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true, harness: 'cursor' }");
|
|
3361
3884
|
STUB_CURSOR_SKILL_JUDGE_TS = stubHook("skill-judge", "{ needsFile: true, needsTranscript: true, harness: 'cursor' }");
|
|
@@ -3382,9 +3905,9 @@ __export(stub_exports, {
|
|
|
3382
3905
|
saveCredentials: () => saveCredentials
|
|
3383
3906
|
});
|
|
3384
3907
|
import { createServer } from "http";
|
|
3385
|
-
import { writeFileSync as writeFileSync11, readFileSync as
|
|
3386
|
-
import { homedir as
|
|
3387
|
-
import { join as
|
|
3908
|
+
import { writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
3909
|
+
import { homedir as homedir14, platform as platform2 } from "os";
|
|
3910
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
3388
3911
|
import { execFile } from "child_process";
|
|
3389
3912
|
import jwt from "jsonwebtoken";
|
|
3390
3913
|
function openBrowser(url) {
|
|
@@ -3423,7 +3946,7 @@ function loadCredentials() {
|
|
|
3423
3946
|
return null;
|
|
3424
3947
|
}
|
|
3425
3948
|
try {
|
|
3426
|
-
const content =
|
|
3949
|
+
const content = readFileSync14(AUTH_FILE, "utf8");
|
|
3427
3950
|
return JSON.parse(content);
|
|
3428
3951
|
} catch (error) {
|
|
3429
3952
|
return null;
|
|
@@ -3711,7 +4234,7 @@ var init_stub = __esm({
|
|
|
3711
4234
|
PORT = 8100;
|
|
3712
4235
|
RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
|
|
3713
4236
|
SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
|
|
3714
|
-
AUTH_FILE = process.env.SYNKRO_AUTH_FILE ||
|
|
4237
|
+
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join12(homedir14(), ".synkro", "credentials.json");
|
|
3715
4238
|
RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
3716
4239
|
SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
|
|
3717
4240
|
ERROR_HTML = `
|
|
@@ -3948,9 +4471,9 @@ __export(claudeDesktopTap_exports, {
|
|
|
3948
4471
|
runClaudeDesktopTap: () => runClaudeDesktopTap
|
|
3949
4472
|
});
|
|
3950
4473
|
import { spawn as spawn2 } from "child_process";
|
|
3951
|
-
import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as
|
|
3952
|
-
import { join as
|
|
3953
|
-
import { homedir as
|
|
4474
|
+
import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
|
|
4475
|
+
import { join as join13 } from "path";
|
|
4476
|
+
import { homedir as homedir15 } from "os";
|
|
3954
4477
|
function claudeDesktopInstalled() {
|
|
3955
4478
|
return process.platform === "darwin" && existsSync17("/Applications/Claude.app/Contents/MacOS/Claude");
|
|
3956
4479
|
}
|
|
@@ -4066,7 +4589,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4066
4589
|
}
|
|
4067
4590
|
let token = "";
|
|
4068
4591
|
try {
|
|
4069
|
-
token =
|
|
4592
|
+
token = readFileSync15(JWT_PATH, "utf-8").trim();
|
|
4070
4593
|
} catch {
|
|
4071
4594
|
}
|
|
4072
4595
|
if (!token) {
|
|
@@ -4087,13 +4610,13 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4087
4610
|
} catch (err) {
|
|
4088
4611
|
console.log(` \u26A0 Could not register Claude Desktop MCP: ${err.message}`);
|
|
4089
4612
|
}
|
|
4090
|
-
const cdRoot =
|
|
4613
|
+
const cdRoot = join13(homedir15(), ".synkro", "cd-sessions");
|
|
4091
4614
|
mkdirSync10(cdRoot, { recursive: true });
|
|
4092
|
-
const sessionDir = mkdtempSync(
|
|
4093
|
-
writeFileSync12(
|
|
4094
|
-
writeFileSync12(
|
|
4095
|
-
writeFileSync12(
|
|
4096
|
-
const runnerPath =
|
|
4615
|
+
const sessionDir = mkdtempSync(join13(cdRoot, "synkro-cd-"));
|
|
4616
|
+
writeFileSync12(join13(sessionDir, "tap.py"), ADDON_PY, "utf-8");
|
|
4617
|
+
writeFileSync12(join13(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
|
|
4618
|
+
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
4619
|
+
const runnerPath = join13(sessionDir, "run.sh");
|
|
4097
4620
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
4098
4621
|
await new Promise((resolve6) => {
|
|
4099
4622
|
const child = spawn2("bash", [runnerPath], {
|
|
@@ -4127,7 +4650,7 @@ var init_claudeDesktopTap = __esm({
|
|
|
4127
4650
|
TURN_VERDICTS_URL = "http://127.0.0.1:18931/api/local/turn-verdicts";
|
|
4128
4651
|
TURN_VERDICT_URL = "http://127.0.0.1:18931/api/local/turn-verdict";
|
|
4129
4652
|
MCP_EVENT_URL = "http://127.0.0.1:18931/api/local/mcp/event";
|
|
4130
|
-
JWT_PATH =
|
|
4653
|
+
JWT_PATH = join13(homedir15(), ".synkro", ".mcp-jwt");
|
|
4131
4654
|
ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib
|
|
4132
4655
|
from mitmproxy import http
|
|
4133
4656
|
|
|
@@ -4264,6 +4787,7 @@ def _emit_mcp_event(server, tool, ev_type, eid, convo_id=""):
|
|
|
4264
4787
|
"tool_name": (str(tool)[:120] if tool else None),
|
|
4265
4788
|
"decision": ("allowed" if ev_type == "tool_call" else None),
|
|
4266
4789
|
"repo": "claude-desktop",
|
|
4790
|
+
"harness": "claude_desktop",
|
|
4267
4791
|
}).encode("utf-8")
|
|
4268
4792
|
_post_json(MCP_EVENT_URL, payload, 5)
|
|
4269
4793
|
except Exception:
|
|
@@ -5243,9 +5767,9 @@ __export(macKeychain_exports, {
|
|
|
5243
5767
|
writeCursorApiKey: () => writeCursorApiKey,
|
|
5244
5768
|
writeRefreshAgent: () => writeRefreshAgent
|
|
5245
5769
|
});
|
|
5246
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as
|
|
5247
|
-
import { homedir as
|
|
5248
|
-
import { join as
|
|
5770
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync16 } from "fs";
|
|
5771
|
+
import { homedir as homedir16, platform as platform3 } from "os";
|
|
5772
|
+
import { join as join14 } from "path";
|
|
5249
5773
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5250
5774
|
function needsKeychainBridge() {
|
|
5251
5775
|
return platform3() === "darwin";
|
|
@@ -5268,7 +5792,7 @@ function exportKeychainCreds() {
|
|
|
5268
5792
|
let changed = true;
|
|
5269
5793
|
try {
|
|
5270
5794
|
if (existsSync18(CLAUDE_CREDS_FILE)) {
|
|
5271
|
-
changed =
|
|
5795
|
+
changed = readFileSync16(CLAUDE_CREDS_FILE, "utf-8") !== blob;
|
|
5272
5796
|
}
|
|
5273
5797
|
} catch {
|
|
5274
5798
|
}
|
|
@@ -5278,7 +5802,7 @@ function exportKeychainCreds() {
|
|
|
5278
5802
|
}
|
|
5279
5803
|
function cursorApiKeyConfigured() {
|
|
5280
5804
|
try {
|
|
5281
|
-
return existsSync18(CURSOR_API_KEY_FILE) &&
|
|
5805
|
+
return existsSync18(CURSOR_API_KEY_FILE) && readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
|
|
5282
5806
|
} catch {
|
|
5283
5807
|
return false;
|
|
5284
5808
|
}
|
|
@@ -5294,7 +5818,7 @@ function writeCursorApiKey(key) {
|
|
|
5294
5818
|
async function validateCursorApiKey() {
|
|
5295
5819
|
let key;
|
|
5296
5820
|
try {
|
|
5297
|
-
key =
|
|
5821
|
+
key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
|
|
5298
5822
|
} catch {
|
|
5299
5823
|
return null;
|
|
5300
5824
|
}
|
|
@@ -5315,7 +5839,7 @@ async function validateCursorApiKey() {
|
|
|
5315
5839
|
function credsAreStale() {
|
|
5316
5840
|
if (!existsSync18(CLAUDE_CREDS_FILE)) return true;
|
|
5317
5841
|
try {
|
|
5318
|
-
const raw =
|
|
5842
|
+
const raw = readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5319
5843
|
const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0;
|
|
5320
5844
|
if (!exp) return true;
|
|
5321
5845
|
return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1e3;
|
|
@@ -5330,7 +5854,7 @@ function writeRefreshAgent(synkroBinPath) {
|
|
|
5330
5854
|
if (platform3() !== "darwin") {
|
|
5331
5855
|
throw new KeychainExportError("writeRefreshAgent is darwin-only");
|
|
5332
5856
|
}
|
|
5333
|
-
mkdirSync11(
|
|
5857
|
+
mkdirSync11(join14(homedir16(), "Library", "LaunchAgents"), { recursive: true });
|
|
5334
5858
|
mkdirSync11(SYNKRO_DIR6, { recursive: true });
|
|
5335
5859
|
const script = `#!/bin/bash
|
|
5336
5860
|
# Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.
|
|
@@ -5427,7 +5951,7 @@ function refreshCreds() {
|
|
|
5427
5951
|
}
|
|
5428
5952
|
function readExportedCreds() {
|
|
5429
5953
|
try {
|
|
5430
|
-
return
|
|
5954
|
+
return readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5431
5955
|
} catch {
|
|
5432
5956
|
return null;
|
|
5433
5957
|
}
|
|
@@ -5436,16 +5960,16 @@ var SYNKRO_DIR6, CLAUDE_CREDS_DIR, CLAUDE_CREDS_FILE, CURSOR_CREDS_DIR, CURSOR_A
|
|
|
5436
5960
|
var init_macKeychain = __esm({
|
|
5437
5961
|
"cli/local-cc/macKeychain.ts"() {
|
|
5438
5962
|
"use strict";
|
|
5439
|
-
SYNKRO_DIR6 =
|
|
5440
|
-
CLAUDE_CREDS_DIR =
|
|
5441
|
-
CLAUDE_CREDS_FILE =
|
|
5442
|
-
CURSOR_CREDS_DIR =
|
|
5443
|
-
CURSOR_API_KEY_FILE =
|
|
5963
|
+
SYNKRO_DIR6 = join14(homedir16(), ".synkro");
|
|
5964
|
+
CLAUDE_CREDS_DIR = join14(SYNKRO_DIR6, "claude-creds");
|
|
5965
|
+
CLAUDE_CREDS_FILE = join14(CLAUDE_CREDS_DIR, ".credentials.json");
|
|
5966
|
+
CURSOR_CREDS_DIR = join14(SYNKRO_DIR6, "cursor-creds");
|
|
5967
|
+
CURSOR_API_KEY_FILE = join14(CURSOR_CREDS_DIR, "api-key");
|
|
5444
5968
|
KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
5445
5969
|
LAUNCHD_LABEL = "com.synkro.cli.claude-creds-refresh";
|
|
5446
|
-
LAUNCHD_PLIST =
|
|
5447
|
-
REFRESH_SCRIPT =
|
|
5448
|
-
REFRESH_LOG =
|
|
5970
|
+
LAUNCHD_PLIST = join14(homedir16(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
5971
|
+
REFRESH_SCRIPT = join14(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
|
|
5972
|
+
REFRESH_LOG = join14(SYNKRO_DIR6, "claude-creds-refresh.log");
|
|
5449
5973
|
REFRESH_EXPIRY_BUFFER_SECONDS = 120;
|
|
5450
5974
|
MIN_REFRESH_INTERVAL_SECONDS = 30;
|
|
5451
5975
|
MAX_REFRESH_INTERVAL_SECONDS = 5 * 60;
|
|
@@ -5464,8 +5988,11 @@ var init_macKeychain = __esm({
|
|
|
5464
5988
|
var dockerInstall_exports = {};
|
|
5465
5989
|
__export(dockerInstall_exports, {
|
|
5466
5990
|
DockerInstallError: () => DockerInstallError,
|
|
5991
|
+
HOST_PGLITE_PORT: () => HOST_PGLITE_PORT,
|
|
5992
|
+
PGLITE_PASSWORD_PATH: () => PGLITE_PASSWORD_PATH,
|
|
5467
5993
|
SYNKRO_DIR: () => SYNKRO_DIR7,
|
|
5468
5994
|
assertDockerAvailable: () => assertDockerAvailable,
|
|
5995
|
+
createPgliteScramVerifier: () => createPgliteScramVerifier,
|
|
5469
5996
|
dockerInstall: () => dockerInstall,
|
|
5470
5997
|
dockerRemove: () => dockerRemove,
|
|
5471
5998
|
dockerSafeRestart: () => dockerSafeRestart,
|
|
@@ -5474,20 +6001,80 @@ __export(dockerInstall_exports, {
|
|
|
5474
6001
|
dockerStatus: () => dockerStatus,
|
|
5475
6002
|
dockerStop: () => dockerStop,
|
|
5476
6003
|
dockerUpdate: () => dockerUpdate,
|
|
6004
|
+
ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
|
|
5477
6005
|
imageTag: () => imageTag,
|
|
5478
6006
|
normalizeProvider: () => normalizeProvider,
|
|
5479
6007
|
poolLabel: () => poolLabel,
|
|
5480
6008
|
readContainerConfig: () => readContainerConfig,
|
|
6009
|
+
resolveConductorProvider: () => resolveConductorProvider,
|
|
6010
|
+
resolveContainerName: () => resolveContainerName,
|
|
5481
6011
|
resolveGraderPool: () => resolveGraderPool,
|
|
5482
6012
|
resolveWorkerConfig: () => resolveWorkerConfig,
|
|
5483
6013
|
splitWorkers: () => splitWorkers,
|
|
5484
6014
|
waitForContainerReady: () => waitForContainerReady,
|
|
5485
6015
|
waitForWorkersReady: () => waitForWorkersReady
|
|
5486
6016
|
});
|
|
5487
|
-
import {
|
|
5488
|
-
|
|
5489
|
-
|
|
6017
|
+
import {
|
|
6018
|
+
chmodSync as chmodSync3,
|
|
6019
|
+
copyFileSync,
|
|
6020
|
+
existsSync as existsSync19,
|
|
6021
|
+
mkdirSync as mkdirSync12,
|
|
6022
|
+
readFileSync as readFileSync17,
|
|
6023
|
+
readdirSync as readdirSync2,
|
|
6024
|
+
renameSync as renameSync7,
|
|
6025
|
+
writeFileSync as writeFileSync14
|
|
6026
|
+
} from "fs";
|
|
6027
|
+
import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "crypto";
|
|
6028
|
+
import { homedir as homedir17 } from "os";
|
|
6029
|
+
import { join as join15 } from "path";
|
|
5490
6030
|
import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
|
|
6031
|
+
function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
|
|
6032
|
+
const value = String(raw || "").trim();
|
|
6033
|
+
if (!value) return "synkro-server";
|
|
6034
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) {
|
|
6035
|
+
throw new Error("SYNKRO_CONTAINER_NAME must be a valid Docker container name (1-128 letters, digits, _, ., or -)");
|
|
6036
|
+
}
|
|
6037
|
+
return value;
|
|
6038
|
+
}
|
|
6039
|
+
function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
|
|
6040
|
+
const iterations = 4096;
|
|
6041
|
+
const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
|
|
6042
|
+
const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
|
|
6043
|
+
const storedKey = createHash2("sha256").update(clientKey).digest();
|
|
6044
|
+
const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
|
|
6045
|
+
return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
|
|
6046
|
+
}
|
|
6047
|
+
function ensurePgliteProxyCredentials() {
|
|
6048
|
+
const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
|
|
6049
|
+
const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
|
|
6050
|
+
if (hasPassword && hasUserlist) {
|
|
6051
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6052
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
const password = randomBytes2(24).toString("base64url");
|
|
6056
|
+
const verifier = createPgliteScramVerifier(password);
|
|
6057
|
+
const suffix = `${process.pid}.${Date.now()}.tmp`;
|
|
6058
|
+
const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
|
|
6059
|
+
const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
|
|
6060
|
+
writeFileSync14(passwordTmp, `${password}
|
|
6061
|
+
`, { mode: 384 });
|
|
6062
|
+
writeFileSync14(userlistTmp, `"synkro" "${verifier}"
|
|
6063
|
+
`, { mode: 384 });
|
|
6064
|
+
renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
|
|
6065
|
+
renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
|
|
6066
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6067
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6068
|
+
}
|
|
6069
|
+
function resolveConductorProvider(pool, counts) {
|
|
6070
|
+
if (pool !== "auto") return pool;
|
|
6071
|
+
const ranked = [
|
|
6072
|
+
["claude_code", counts.claudeWorkers],
|
|
6073
|
+
["cursor", counts.cursorWorkers],
|
|
6074
|
+
["codex", counts.codexWorkers]
|
|
6075
|
+
];
|
|
6076
|
+
return ranked.reduce((best, candidate) => candidate[1] > best[1] ? candidate : best, ranked[0])[0];
|
|
6077
|
+
}
|
|
5491
6078
|
function splitWorkers(total, providers) {
|
|
5492
6079
|
const t = Math.max(0, Math.floor(total));
|
|
5493
6080
|
const selected = ["claude_code", "cursor", "codex"].filter((provider) => providers.includes(provider));
|
|
@@ -5587,9 +6174,9 @@ function readSynkroFileConfig() {
|
|
|
5587
6174
|
try {
|
|
5588
6175
|
const root = execSync3("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5589
6176
|
if (!root) return { pool: "auto", warnings: [] };
|
|
5590
|
-
const fp =
|
|
6177
|
+
const fp = join15(root, "synkro.toml");
|
|
5591
6178
|
if (!existsSync19(fp)) return { pool: "auto", warnings: [] };
|
|
5592
|
-
const parsed = parseSynkroToml(
|
|
6179
|
+
const parsed = parseSynkroToml(readFileSync17(fp, "utf-8"));
|
|
5593
6180
|
return resolveGraderPool(parsed);
|
|
5594
6181
|
} catch {
|
|
5595
6182
|
}
|
|
@@ -5634,7 +6221,10 @@ function resolveWorkerConfig(rest) {
|
|
|
5634
6221
|
const cw = sc.claudeWorkers || 0;
|
|
5635
6222
|
const curw = sc.cursorWorkers || 0;
|
|
5636
6223
|
const codw = sc.codexWorkers || 0;
|
|
5637
|
-
if (cw + curw + codw > 0)
|
|
6224
|
+
if (cw + curw + codw > 0) {
|
|
6225
|
+
const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
|
|
6226
|
+
return { ...counts2, conductorProvider: resolveConductorProvider(sc.pool, counts2), explicit };
|
|
6227
|
+
}
|
|
5638
6228
|
}
|
|
5639
6229
|
if (sc.pool === "cursor") {
|
|
5640
6230
|
provs = ["cursor"];
|
|
@@ -5647,7 +6237,9 @@ function resolveWorkerConfig(rest) {
|
|
|
5647
6237
|
if (provs.length === 0) provs = ["claude_code"];
|
|
5648
6238
|
}
|
|
5649
6239
|
}
|
|
5650
|
-
|
|
6240
|
+
const counts = splitWorkers(workers, provs);
|
|
6241
|
+
const pool = provs.length === 1 ? provs[0] : "auto";
|
|
6242
|
+
return { ...counts, conductorProvider: resolveConductorProvider(pool, counts), explicit };
|
|
5651
6243
|
}
|
|
5652
6244
|
function imageTag() {
|
|
5653
6245
|
const registry = process.env.SYNKRO_IMAGE_REGISTRY || "";
|
|
@@ -5667,7 +6259,7 @@ function assertDockerAvailable() {
|
|
|
5667
6259
|
}
|
|
5668
6260
|
function claudeCredsHostDir() {
|
|
5669
6261
|
if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;
|
|
5670
|
-
return
|
|
6262
|
+
return join15(homedir17(), ".claude");
|
|
5671
6263
|
}
|
|
5672
6264
|
function resolveSynkroBin() {
|
|
5673
6265
|
const which2 = spawnSync3("which", ["synkro"], { encoding: "utf-8", timeout: 5e3 });
|
|
@@ -5721,16 +6313,22 @@ async function dockerInstall(opts = {}) {
|
|
|
5721
6313
|
const cursorWorkers = opts.cursorWorkers ?? 0;
|
|
5722
6314
|
const codexWorkers = opts.codexWorkers ?? 0;
|
|
5723
6315
|
const totalWorkers = claudeWorkers + cursorWorkers + codexWorkers;
|
|
5724
|
-
const
|
|
5725
|
-
|
|
6316
|
+
const workerCounts = { claudeWorkers, cursorWorkers, codexWorkers };
|
|
6317
|
+
const conductorProvider = opts.conductorProvider ?? resolveConductorProvider("auto", workerCounts);
|
|
6318
|
+
const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
|
|
6319
|
+
const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
|
|
6320
|
+
const usesCodex = codexWorkers > 0 || conductorProvider === "codex";
|
|
6321
|
+
const codexHomeDir = opts.codexHomeDir ?? join15(SYNKRO_DIR7, "codex-local-session");
|
|
6322
|
+
if (usesCodex && !existsSync19(join15(codexHomeDir, "auth.json"))) {
|
|
5726
6323
|
throw new DockerInstallError(
|
|
5727
6324
|
"Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
|
|
5728
6325
|
);
|
|
5729
6326
|
}
|
|
5730
6327
|
mkdirSync12(PGDATA_PATH, { recursive: true });
|
|
5731
6328
|
mkdirSync12(BACKUP_DIR, { recursive: true });
|
|
6329
|
+
ensurePgliteProxyCredentials();
|
|
5732
6330
|
mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
|
|
5733
|
-
const hostClaudeJson =
|
|
6331
|
+
const hostClaudeJson = join15(homedir17(), ".claude.json");
|
|
5734
6332
|
if (existsSync19(hostClaudeJson)) {
|
|
5735
6333
|
copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
|
|
5736
6334
|
}
|
|
@@ -5743,14 +6341,14 @@ async function dockerInstall(opts = {}) {
|
|
|
5743
6341
|
if (needsKeychainBridge()) {
|
|
5744
6342
|
const claudeCredsPath = exportKeychainCreds();
|
|
5745
6343
|
if (!claudeCredsPath) {
|
|
5746
|
-
if (
|
|
6344
|
+
if (usesClaude) {
|
|
5747
6345
|
throw new DockerInstallError(
|
|
5748
6346
|
"Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container."
|
|
5749
6347
|
);
|
|
5750
6348
|
}
|
|
5751
6349
|
console.warn(" \u26A0 Claude Code keychain entry not found \u2014 live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).");
|
|
5752
6350
|
}
|
|
5753
|
-
if (
|
|
6351
|
+
if (usesCursor && !cursorApiKeyConfigured()) {
|
|
5754
6352
|
console.warn(" \u26A0 No Cursor API key found \u2014 Cursor grader workers will be idle.");
|
|
5755
6353
|
console.warn(" Generate a key at cursor.com \u2192 Settings \u2192 API Keys, then:");
|
|
5756
6354
|
console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);
|
|
@@ -5763,7 +6361,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5763
6361
|
console.warn(` Plist written to ${plist} \u2014 load manually with launchctl bootstrap when ready.`);
|
|
5764
6362
|
}
|
|
5765
6363
|
} else {
|
|
5766
|
-
mkdirSync12(
|
|
6364
|
+
mkdirSync12(join15(homedir17(), ".claude"), { recursive: true });
|
|
5767
6365
|
}
|
|
5768
6366
|
const imageExistsLocally = () => spawnSync3("docker", ["image", "inspect", image], { stdio: "ignore", timeout: 3e4 }).status === 0;
|
|
5769
6367
|
const skipPull = process.env.SYNKRO_SKIP_PULL === "1" || process.env.SYNKRO_SKIP_PULL === "true";
|
|
@@ -5802,7 +6400,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5802
6400
|
"-p",
|
|
5803
6401
|
`127.0.0.1:${HOST_CWE_PORT}:8930`,
|
|
5804
6402
|
"-p",
|
|
5805
|
-
`127.0.0.1:${HOST_PGLITE_PORT}:
|
|
6403
|
+
`127.0.0.1:${HOST_PGLITE_PORT}:5434`,
|
|
5806
6404
|
"-v",
|
|
5807
6405
|
`${PGDATA_PATH}:/data/pgdata`,
|
|
5808
6406
|
"-v",
|
|
@@ -5816,13 +6414,13 @@ async function dockerInstall(opts = {}) {
|
|
|
5816
6414
|
"-v",
|
|
5817
6415
|
`${credsDir}:/home/synkro/.claude:rw`,
|
|
5818
6416
|
"-v",
|
|
5819
|
-
`${
|
|
6417
|
+
`${join15(homedir17(), ".claude")}:/data/claude-host:ro`,
|
|
5820
6418
|
"-v",
|
|
5821
6419
|
`${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
|
|
5822
6420
|
// Cursor creds — mounted RW so the in-container refresher can rotate the
|
|
5823
6421
|
// access token in place. Only mounted when the install includes Cursor.
|
|
5824
|
-
...
|
|
5825
|
-
...
|
|
6422
|
+
...usesCursor ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
|
|
6423
|
+
...usesCodex ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
|
|
5826
6424
|
"-e",
|
|
5827
6425
|
`WORKERS_PER_POOL=${totalWorkers}`,
|
|
5828
6426
|
"-e",
|
|
@@ -5831,9 +6429,13 @@ async function dockerInstall(opts = {}) {
|
|
|
5831
6429
|
`CURSOR_WORKERS=${cursorWorkers}`,
|
|
5832
6430
|
"-e",
|
|
5833
6431
|
`CODEX_WORKERS=${codexWorkers}`,
|
|
6432
|
+
"-e",
|
|
6433
|
+
`SYNKRO_CONDUCTOR_PROVIDER=${conductorProvider}`,
|
|
5834
6434
|
// Pass through the batch-size lever if the operator set it. Defaults
|
|
5835
6435
|
// inside the container to 5; clamped to [1, 20] by synkro-server.ts.
|
|
5836
6436
|
...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
|
|
6437
|
+
// Full verifier prompt/response tracing is explicit opt-in because it contains source code.
|
|
6438
|
+
...process.env.SYNKRO_VERIFY_TRACE === "1" ? ["-e", "SYNKRO_VERIFY_TRACE=1"] : [],
|
|
5837
6439
|
// Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.
|
|
5838
6440
|
...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
|
|
5839
6441
|
// Fix-poll kill switch. Default ON in the image; a benchmark/headless run
|
|
@@ -5865,7 +6467,14 @@ async function dockerInstall(opts = {}) {
|
|
|
5865
6467
|
if (run.status !== 0) {
|
|
5866
6468
|
throw new DockerInstallError(`docker run failed (image ${image})`);
|
|
5867
6469
|
}
|
|
5868
|
-
return {
|
|
6470
|
+
return {
|
|
6471
|
+
image,
|
|
6472
|
+
hostMcpPort: HOST_MCP_PORT,
|
|
6473
|
+
hostGraderPort: HOST_GRADER_PORT,
|
|
6474
|
+
hostCwePort: HOST_CWE_PORT,
|
|
6475
|
+
hostPglitePort: HOST_PGLITE_PORT,
|
|
6476
|
+
pglitePasswordPath: PGLITE_PASSWORD_PATH
|
|
6477
|
+
};
|
|
5869
6478
|
}
|
|
5870
6479
|
async function waitForContainerReady(timeoutMs = 6e4) {
|
|
5871
6480
|
const start = Date.now();
|
|
@@ -5920,7 +6529,7 @@ function dockerStatus() {
|
|
|
5920
6529
|
return {
|
|
5921
6530
|
running: true,
|
|
5922
6531
|
image: imageTag(),
|
|
5923
|
-
healthz: `http://127.0.0.1:${HOST_MCP_PORT}
|
|
6532
|
+
healthz: `http://127.0.0.1:${HOST_MCP_PORT}/health`
|
|
5924
6533
|
};
|
|
5925
6534
|
}
|
|
5926
6535
|
function readContainerConfig() {
|
|
@@ -5949,6 +6558,7 @@ function readContainerConfig() {
|
|
|
5949
6558
|
claudeWorkers: num(get("CLAUDE_WORKERS")),
|
|
5950
6559
|
cursorWorkers: num(get("CURSOR_WORKERS")),
|
|
5951
6560
|
codexWorkers: num(get("CODEX_WORKERS")),
|
|
6561
|
+
conductorProvider: normalizeProvider(get("SYNKRO_CONDUCTOR_PROVIDER") || "") || void 0,
|
|
5952
6562
|
connectedRepo: get("SYNKRO_CONNECTED_REPO") || void 0
|
|
5953
6563
|
};
|
|
5954
6564
|
}
|
|
@@ -6066,23 +6676,34 @@ function checkPgdata() {
|
|
|
6066
6676
|
if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
|
|
6067
6677
|
return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
|
|
6068
6678
|
}
|
|
6069
|
-
var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
6679
|
+
var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, PGLITE_PASSWORD_PATH, PGLITE_USERLIST_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, defaultImageVersion, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
6070
6680
|
var init_dockerInstall = __esm({
|
|
6071
6681
|
"cli/local-cc/dockerInstall.ts"() {
|
|
6072
6682
|
"use strict";
|
|
6073
6683
|
init_agentDetect();
|
|
6074
6684
|
init_macKeychain();
|
|
6075
|
-
SYNKRO_DIR7 =
|
|
6076
|
-
MCP_JWT_PATH =
|
|
6077
|
-
PGDATA_PATH =
|
|
6078
|
-
|
|
6079
|
-
|
|
6685
|
+
SYNKRO_DIR7 = join15(homedir17(), ".synkro");
|
|
6686
|
+
MCP_JWT_PATH = join15(SYNKRO_DIR7, ".mcp-jwt");
|
|
6687
|
+
PGDATA_PATH = join15(SYNKRO_DIR7, "pgdata");
|
|
6688
|
+
PGLITE_PASSWORD_PATH = join15(SYNKRO_DIR7, ".pglite-password");
|
|
6689
|
+
PGLITE_USERLIST_PATH = join15(SYNKRO_DIR7, ".pglite-userlist");
|
|
6690
|
+
CLAUDE_HOST_STATE_DIR = join15(SYNKRO_DIR7, "claude-host-state");
|
|
6691
|
+
CLAUDE_HOST_STATE_FILE = join15(CLAUDE_HOST_STATE_DIR, ".claude.json");
|
|
6080
6692
|
HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
6081
6693
|
HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
|
|
6082
6694
|
HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
|
|
6083
6695
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
6084
|
-
CONTAINER_NAME =
|
|
6085
|
-
|
|
6696
|
+
CONTAINER_NAME = resolveContainerName();
|
|
6697
|
+
defaultImageVersion = () => {
|
|
6698
|
+
if (true) return "1.7.89";
|
|
6699
|
+
try {
|
|
6700
|
+
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
6701
|
+
if (pkg.version) return pkg.version;
|
|
6702
|
+
} catch {
|
|
6703
|
+
}
|
|
6704
|
+
return process.env.npm_package_version || "0.0.0-dev";
|
|
6705
|
+
};
|
|
6706
|
+
DEFAULT_IMAGE = `ghcr.io/synkro-sh/synkro-server:${defaultImageVersion()}`;
|
|
6086
6707
|
DockerInstallError = class extends Error {
|
|
6087
6708
|
constructor(message, cause) {
|
|
6088
6709
|
super(message);
|
|
@@ -6091,17 +6712,17 @@ var init_dockerInstall = __esm({
|
|
|
6091
6712
|
}
|
|
6092
6713
|
cause;
|
|
6093
6714
|
};
|
|
6094
|
-
BACKUP_DIR =
|
|
6715
|
+
BACKUP_DIR = join15(SYNKRO_DIR7, "pgdata-backups");
|
|
6095
6716
|
}
|
|
6096
6717
|
});
|
|
6097
6718
|
|
|
6098
6719
|
// cli/local-cc/setupToken.ts
|
|
6099
6720
|
import { spawn as nodeSpawn } from "child_process";
|
|
6100
|
-
import { readFileSync as
|
|
6101
|
-
import { homedir as
|
|
6102
|
-
import { join as
|
|
6721
|
+
import { readFileSync as readFileSync18, unlinkSync as unlinkSync6 } from "fs";
|
|
6722
|
+
import { homedir as homedir18, platform as platform4 } from "os";
|
|
6723
|
+
import { join as join16 } from "path";
|
|
6103
6724
|
function captureClaudeSetupToken() {
|
|
6104
|
-
const tmpFile =
|
|
6725
|
+
const tmpFile = join16(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
|
|
6105
6726
|
const isMac = platform4() === "darwin";
|
|
6106
6727
|
const bin = "script";
|
|
6107
6728
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
@@ -6115,7 +6736,7 @@ function captureClaudeSetupToken() {
|
|
|
6115
6736
|
proc.on("close", (code) => {
|
|
6116
6737
|
let raw = "";
|
|
6117
6738
|
try {
|
|
6118
|
-
raw =
|
|
6739
|
+
raw = readFileSync18(tmpFile, "utf-8");
|
|
6119
6740
|
} catch (e) {
|
|
6120
6741
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
6121
6742
|
return;
|
|
@@ -6181,15 +6802,15 @@ var SYNKRO_DIR8;
|
|
|
6181
6802
|
var init_setupToken = __esm({
|
|
6182
6803
|
"cli/local-cc/setupToken.ts"() {
|
|
6183
6804
|
"use strict";
|
|
6184
|
-
SYNKRO_DIR8 =
|
|
6805
|
+
SYNKRO_DIR8 = join16(homedir18(), ".synkro");
|
|
6185
6806
|
}
|
|
6186
6807
|
});
|
|
6187
6808
|
|
|
6188
6809
|
// cli/local-cc/codexCloudSetup.ts
|
|
6189
6810
|
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
6190
|
-
import { readFileSync as
|
|
6191
|
-
import { homedir as
|
|
6192
|
-
import { join as
|
|
6811
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
|
|
6812
|
+
import { homedir as homedir19 } from "os";
|
|
6813
|
+
import { join as join17 } from "path";
|
|
6193
6814
|
function findCodexBinary() {
|
|
6194
6815
|
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
6195
6816
|
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
@@ -6198,7 +6819,7 @@ function findCodexBinary() {
|
|
|
6198
6819
|
}
|
|
6199
6820
|
function runCodexLogin(codexBin, codexHome) {
|
|
6200
6821
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
6201
|
-
|
|
6822
|
+
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
6202
6823
|
return new Promise((resolve6, reject) => {
|
|
6203
6824
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
6204
6825
|
stdio: "inherit",
|
|
@@ -6230,13 +6851,13 @@ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
|
6230
6851
|
} catch (e) {
|
|
6231
6852
|
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
6232
6853
|
}
|
|
6233
|
-
const authPath =
|
|
6854
|
+
const authPath = join17(CODEX_CLOUD_HOME, "auth.json");
|
|
6234
6855
|
if (!existsSync20(authPath)) {
|
|
6235
6856
|
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
6236
6857
|
}
|
|
6237
6858
|
let auth;
|
|
6238
6859
|
try {
|
|
6239
|
-
auth = JSON.parse(
|
|
6860
|
+
auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6240
6861
|
} catch (e) {
|
|
6241
6862
|
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
6242
6863
|
}
|
|
@@ -6270,7 +6891,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6270
6891
|
if (!codexBin) {
|
|
6271
6892
|
return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
|
|
6272
6893
|
}
|
|
6273
|
-
const authPath =
|
|
6894
|
+
const authPath = join17(CODEX_LOCAL_HOME, "auth.json");
|
|
6274
6895
|
if (!existsSync20(authPath)) {
|
|
6275
6896
|
onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
|
|
6276
6897
|
try {
|
|
@@ -6280,7 +6901,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6280
6901
|
}
|
|
6281
6902
|
}
|
|
6282
6903
|
try {
|
|
6283
|
-
const auth = JSON.parse(
|
|
6904
|
+
const auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6284
6905
|
if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
|
|
6285
6906
|
} catch (e) {
|
|
6286
6907
|
return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
|
|
@@ -6292,9 +6913,9 @@ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
|
|
|
6292
6913
|
var init_codexCloudSetup = __esm({
|
|
6293
6914
|
"cli/local-cc/codexCloudSetup.ts"() {
|
|
6294
6915
|
"use strict";
|
|
6295
|
-
SYNKRO_DIR9 =
|
|
6296
|
-
CODEX_CLOUD_HOME =
|
|
6297
|
-
CODEX_LOCAL_HOME =
|
|
6916
|
+
SYNKRO_DIR9 = join17(homedir19(), ".synkro");
|
|
6917
|
+
CODEX_CLOUD_HOME = join17(SYNKRO_DIR9, "codex-cloud-session");
|
|
6918
|
+
CODEX_LOCAL_HOME = join17(SYNKRO_DIR9, "codex-local-session");
|
|
6298
6919
|
}
|
|
6299
6920
|
});
|
|
6300
6921
|
|
|
@@ -6316,21 +6937,21 @@ __export(ptyShim_exports, {
|
|
|
6316
6937
|
import {
|
|
6317
6938
|
existsSync as existsSync21,
|
|
6318
6939
|
mkdirSync as mkdirSync14,
|
|
6319
|
-
writeFileSync as
|
|
6320
|
-
chmodSync as
|
|
6321
|
-
readFileSync as
|
|
6940
|
+
writeFileSync as writeFileSync16,
|
|
6941
|
+
chmodSync as chmodSync4,
|
|
6942
|
+
readFileSync as readFileSync20,
|
|
6322
6943
|
rmSync as rmSync2,
|
|
6323
6944
|
realpathSync,
|
|
6324
6945
|
symlinkSync,
|
|
6325
6946
|
lstatSync,
|
|
6326
6947
|
readdirSync as readdirSync3
|
|
6327
6948
|
} from "fs";
|
|
6328
|
-
import { homedir as
|
|
6329
|
-
import { join as
|
|
6949
|
+
import { homedir as homedir20 } from "os";
|
|
6950
|
+
import { join as join18 } from "path";
|
|
6330
6951
|
import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
|
|
6331
6952
|
function rcFiles() {
|
|
6332
|
-
const h =
|
|
6333
|
-
return [
|
|
6953
|
+
const h = homedir20();
|
|
6954
|
+
return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
|
|
6334
6955
|
}
|
|
6335
6956
|
function resolveRealClaude() {
|
|
6336
6957
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
@@ -6342,7 +6963,7 @@ function resolveRealClaude() {
|
|
|
6342
6963
|
return p;
|
|
6343
6964
|
}
|
|
6344
6965
|
}
|
|
6345
|
-
for (const c of [
|
|
6966
|
+
for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
|
|
6346
6967
|
if (existsSync21(c)) {
|
|
6347
6968
|
try {
|
|
6348
6969
|
return realpathSync(c);
|
|
@@ -6357,7 +6978,7 @@ function findClaudeLink() {
|
|
|
6357
6978
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
6358
6979
|
let linkPath = (r.stdout || "").trim();
|
|
6359
6980
|
if (!linkPath) {
|
|
6360
|
-
const c =
|
|
6981
|
+
const c = join18(homedir20(), ".local", "bin", "claude");
|
|
6361
6982
|
if (existsSync21(c)) linkPath = c;
|
|
6362
6983
|
else return null;
|
|
6363
6984
|
}
|
|
@@ -6371,7 +6992,7 @@ function findClaudeLink() {
|
|
|
6371
6992
|
}
|
|
6372
6993
|
function isOurShim(path) {
|
|
6373
6994
|
try {
|
|
6374
|
-
return
|
|
6995
|
+
return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
|
|
6375
6996
|
} catch {
|
|
6376
6997
|
return false;
|
|
6377
6998
|
}
|
|
@@ -6384,7 +7005,7 @@ function shadowClaude() {
|
|
|
6384
7005
|
}
|
|
6385
7006
|
const { linkPath, realTarget } = found;
|
|
6386
7007
|
if (isOurShim(linkPath)) {
|
|
6387
|
-
console.log(` \xB7 ${linkPath.replace(
|
|
7008
|
+
console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
|
|
6388
7009
|
return;
|
|
6389
7010
|
}
|
|
6390
7011
|
let wasSymlink = false;
|
|
@@ -6394,14 +7015,14 @@ function shadowClaude() {
|
|
|
6394
7015
|
}
|
|
6395
7016
|
const state = { linkPath, realTarget, wasSymlink };
|
|
6396
7017
|
try {
|
|
6397
|
-
|
|
7018
|
+
writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
|
|
6398
7019
|
} catch {
|
|
6399
7020
|
}
|
|
6400
7021
|
try {
|
|
6401
7022
|
rmSync2(linkPath, { force: true });
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
console.log(` \u2713 shadowed ${linkPath.replace(
|
|
7023
|
+
writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
|
|
7024
|
+
chmodSync4(linkPath, 493);
|
|
7025
|
+
console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
|
|
6405
7026
|
} catch (e) {
|
|
6406
7027
|
console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
|
|
6407
7028
|
}
|
|
@@ -6409,7 +7030,7 @@ function shadowClaude() {
|
|
|
6409
7030
|
function unshadowClaude() {
|
|
6410
7031
|
let state;
|
|
6411
7032
|
try {
|
|
6412
|
-
state = JSON.parse(
|
|
7033
|
+
state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
|
|
6413
7034
|
} catch {
|
|
6414
7035
|
return;
|
|
6415
7036
|
}
|
|
@@ -6418,7 +7039,7 @@ function unshadowClaude() {
|
|
|
6418
7039
|
if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
|
|
6419
7040
|
rmSync2(state.linkPath, { force: true });
|
|
6420
7041
|
symlinkSync(state.realTarget, state.linkPath);
|
|
6421
|
-
console.log(`\u2713 restored ${state.linkPath.replace(
|
|
7042
|
+
console.log(`\u2713 restored ${state.linkPath.replace(homedir20(), "~")} \u2192 ${state.realTarget.replace(homedir20(), "~")}`);
|
|
6422
7043
|
} catch (e) {
|
|
6423
7044
|
console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
|
|
6424
7045
|
}
|
|
@@ -6429,13 +7050,13 @@ function addPathBlock() {
|
|
|
6429
7050
|
if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
|
|
6430
7051
|
let body = "";
|
|
6431
7052
|
try {
|
|
6432
|
-
body =
|
|
7053
|
+
body = readFileSync20(rc, "utf-8");
|
|
6433
7054
|
} catch {
|
|
6434
7055
|
}
|
|
6435
7056
|
const cleaned = stripBlock(body);
|
|
6436
7057
|
const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
|
|
6437
7058
|
try {
|
|
6438
|
-
|
|
7059
|
+
writeFileSync16(rc, next, "utf-8");
|
|
6439
7060
|
touched.push(rc);
|
|
6440
7061
|
} catch {
|
|
6441
7062
|
}
|
|
@@ -6454,12 +7075,12 @@ function installPtyShim() {
|
|
|
6454
7075
|
mkdirSync14(SHIM_BIN_DIR, { recursive: true });
|
|
6455
7076
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6456
7077
|
const real = resolveRealClaude();
|
|
6457
|
-
|
|
6458
|
-
|
|
7078
|
+
writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
|
|
7079
|
+
chmodSync4(SHIM_PATH, 493);
|
|
6459
7080
|
const touched = addPathBlock();
|
|
6460
7081
|
console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
|
|
6461
7082
|
shadowClaude();
|
|
6462
|
-
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(
|
|
7083
|
+
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
|
|
6463
7084
|
}
|
|
6464
7085
|
function uninstallPtyShim() {
|
|
6465
7086
|
try {
|
|
@@ -6477,10 +7098,10 @@ function uninstallPtyShim() {
|
|
|
6477
7098
|
for (const rc of rcFiles()) {
|
|
6478
7099
|
if (!existsSync21(rc)) continue;
|
|
6479
7100
|
try {
|
|
6480
|
-
const body =
|
|
7101
|
+
const body = readFileSync20(rc, "utf-8");
|
|
6481
7102
|
if (body.includes(RC_BEGIN)) {
|
|
6482
|
-
|
|
6483
|
-
cleaned.push(rc.replace(
|
|
7103
|
+
writeFileSync16(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
7104
|
+
cleaned.push(rc.replace(homedir20(), "~"));
|
|
6484
7105
|
}
|
|
6485
7106
|
} catch {
|
|
6486
7107
|
}
|
|
@@ -6512,7 +7133,7 @@ function listSessions(opts = {}) {
|
|
|
6512
7133
|
const out = [];
|
|
6513
7134
|
for (const f of files) {
|
|
6514
7135
|
try {
|
|
6515
|
-
const r = JSON.parse(
|
|
7136
|
+
const r = JSON.parse(readFileSync20(join18(SESSIONS_DIR, f), "utf-8"));
|
|
6516
7137
|
if (!r || !r.session_id) continue;
|
|
6517
7138
|
if (liveOnly) {
|
|
6518
7139
|
const s = r.tmux_session;
|
|
@@ -6541,7 +7162,7 @@ function resolveTargetSession(override) {
|
|
|
6541
7162
|
const own = currentTmuxSession();
|
|
6542
7163
|
const fromFile = (() => {
|
|
6543
7164
|
try {
|
|
6544
|
-
return
|
|
7165
|
+
return readFileSync20(ACTIVE_SESSION_FILE, "utf-8").trim();
|
|
6545
7166
|
} catch {
|
|
6546
7167
|
return "";
|
|
6547
7168
|
}
|
|
@@ -6567,7 +7188,7 @@ function injectModel(model, sessionOverride) {
|
|
|
6567
7188
|
sendKeys("Escape");
|
|
6568
7189
|
sendKeys("-l", `/model ${model}`);
|
|
6569
7190
|
sendKeys("Enter");
|
|
6570
|
-
const pidFile =
|
|
7191
|
+
const pidFile = join18(PTY_STATE_DIR, `poll-${session}.pid`);
|
|
6571
7192
|
try {
|
|
6572
7193
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6573
7194
|
} catch {
|
|
@@ -6581,13 +7202,13 @@ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, S
|
|
|
6581
7202
|
var init_ptyShim = __esm({
|
|
6582
7203
|
"cli/local-cc/ptyShim.ts"() {
|
|
6583
7204
|
"use strict";
|
|
6584
|
-
SYNKRO_DIR10 =
|
|
6585
|
-
SHIM_BIN_DIR =
|
|
6586
|
-
SHIM_PATH =
|
|
6587
|
-
PTY_STATE_DIR =
|
|
6588
|
-
ACTIVE_SESSION_FILE =
|
|
6589
|
-
SHADOW_STATE_FILE =
|
|
6590
|
-
SESSIONS_DIR =
|
|
7205
|
+
SYNKRO_DIR10 = join18(homedir20(), ".synkro");
|
|
7206
|
+
SHIM_BIN_DIR = join18(SYNKRO_DIR10, "bin");
|
|
7207
|
+
SHIM_PATH = join18(SHIM_BIN_DIR, "claude");
|
|
7208
|
+
PTY_STATE_DIR = join18(SYNKRO_DIR10, "pty");
|
|
7209
|
+
ACTIVE_SESSION_FILE = join18(PTY_STATE_DIR, "active");
|
|
7210
|
+
SHADOW_STATE_FILE = join18(PTY_STATE_DIR, "shadow.json");
|
|
7211
|
+
SESSIONS_DIR = join18(PTY_STATE_DIR, "sessions");
|
|
6591
7212
|
SHIM_SESSION_PREFIX = "synkro-cc-";
|
|
6592
7213
|
RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
|
|
6593
7214
|
RC_END = "# <<< synkro pty shim <<<";
|
|
@@ -6645,6 +7266,202 @@ exit $RC
|
|
|
6645
7266
|
}
|
|
6646
7267
|
});
|
|
6647
7268
|
|
|
7269
|
+
// cli/installer/graderSmoke.ts
|
|
7270
|
+
function isPrimerAckVerdict(value) {
|
|
7271
|
+
return /<category>\s*primer_ack\s*<\/category>/i.test(value) || /<reason>\s*(?:batch )?primer received\s*<\/reason>/i.test(value);
|
|
7272
|
+
}
|
|
7273
|
+
function isValidRiskyEditSmokeVerdict(value) {
|
|
7274
|
+
return /<synkro-verdict(?:\s[^>]*)?>[\s\S]*<\/synkro-verdict>/i.test(value) && /<ok>\s*false\s*<\/ok>/i.test(value) && !isPrimerAckVerdict(value);
|
|
7275
|
+
}
|
|
7276
|
+
var init_graderSmoke = __esm({
|
|
7277
|
+
"cli/installer/graderSmoke.ts"() {
|
|
7278
|
+
"use strict";
|
|
7279
|
+
}
|
|
7280
|
+
});
|
|
7281
|
+
|
|
7282
|
+
// cli/scanning/codexTranscriptUsage.ts
|
|
7283
|
+
function rawCounters(value) {
|
|
7284
|
+
return {
|
|
7285
|
+
input: finiteCounter(value?.input_tokens),
|
|
7286
|
+
output: finiteCounter(value?.output_tokens),
|
|
7287
|
+
cacheCreation: finiteCounter(value?.cache_write_input_tokens),
|
|
7288
|
+
cacheRead: finiteCounter(value?.cached_input_tokens)
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
7291
|
+
function counterDelta(current, previous) {
|
|
7292
|
+
return current >= previous ? current - previous : current;
|
|
7293
|
+
}
|
|
7294
|
+
function normalizedUsage(raw) {
|
|
7295
|
+
return {
|
|
7296
|
+
// Codex input_tokens includes cached/cache-write details. Store only the
|
|
7297
|
+
// uncached remainder in input_tokens so totals and pricing never count the
|
|
7298
|
+
// same prompt tokens twice.
|
|
7299
|
+
input_tokens: Math.max(0, raw.input - raw.cacheCreation - raw.cacheRead),
|
|
7300
|
+
output_tokens: raw.output,
|
|
7301
|
+
cache_creation_input_tokens: raw.cacheCreation,
|
|
7302
|
+
cache_read_input_tokens: raw.cacheRead
|
|
7303
|
+
};
|
|
7304
|
+
}
|
|
7305
|
+
function addUsage(a, b) {
|
|
7306
|
+
return {
|
|
7307
|
+
input_tokens: (a?.input_tokens || 0) + b.input_tokens,
|
|
7308
|
+
output_tokens: (a?.output_tokens || 0) + b.output_tokens,
|
|
7309
|
+
cache_creation_input_tokens: (a?.cache_creation_input_tokens || 0) + b.cache_creation_input_tokens,
|
|
7310
|
+
cache_read_input_tokens: (a?.cache_read_input_tokens || 0) + b.cache_read_input_tokens
|
|
7311
|
+
};
|
|
7312
|
+
}
|
|
7313
|
+
function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
7314
|
+
if (!transcript) return null;
|
|
7315
|
+
const lines = transcript.split("\n");
|
|
7316
|
+
const hasCanonicalAssistant = lines.some((line) => {
|
|
7317
|
+
try {
|
|
7318
|
+
const entry = JSON.parse(line);
|
|
7319
|
+
return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
|
|
7320
|
+
} catch {
|
|
7321
|
+
return false;
|
|
7322
|
+
}
|
|
7323
|
+
});
|
|
7324
|
+
const turnsByLine = /* @__PURE__ */ new Map();
|
|
7325
|
+
let model = "";
|
|
7326
|
+
let lastAssistantLine = -1;
|
|
7327
|
+
let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
|
|
7328
|
+
let latest = null;
|
|
7329
|
+
let sawSnapshot = false;
|
|
7330
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7331
|
+
const line = lines[i].trim();
|
|
7332
|
+
if (!line) continue;
|
|
7333
|
+
let entry;
|
|
7334
|
+
try {
|
|
7335
|
+
entry = JSON.parse(line);
|
|
7336
|
+
} catch {
|
|
7337
|
+
continue;
|
|
7338
|
+
}
|
|
7339
|
+
if (entry?.type === "turn_context" && typeof entry?.payload?.model === "string") {
|
|
7340
|
+
model = entry.payload.model;
|
|
7341
|
+
continue;
|
|
7342
|
+
}
|
|
7343
|
+
if (!hasCanonicalAssistant && entry?.type === "response_item" && entry?.payload?.type === "message" && entry?.payload?.role === "assistant") {
|
|
7344
|
+
lastAssistantLine = i;
|
|
7345
|
+
continue;
|
|
7346
|
+
}
|
|
7347
|
+
if (entry?.type !== "event_msg") continue;
|
|
7348
|
+
if (entry?.payload?.type === "agent_message") {
|
|
7349
|
+
lastAssistantLine = i;
|
|
7350
|
+
continue;
|
|
7351
|
+
}
|
|
7352
|
+
if (entry?.payload?.type !== "token_count") continue;
|
|
7353
|
+
const eventModel = entry?.payload?.model ?? entry?.model;
|
|
7354
|
+
if (typeof eventModel === "string" && eventModel) model = eventModel;
|
|
7355
|
+
const total = entry?.payload?.info?.total_token_usage;
|
|
7356
|
+
if (!total || typeof total !== "object") continue;
|
|
7357
|
+
const current = rawCounters(total);
|
|
7358
|
+
latest = current;
|
|
7359
|
+
if (options.partial && !sawSnapshot) {
|
|
7360
|
+
previous = current;
|
|
7361
|
+
sawSnapshot = true;
|
|
7362
|
+
continue;
|
|
7363
|
+
}
|
|
7364
|
+
sawSnapshot = true;
|
|
7365
|
+
const delta = normalizedUsage({
|
|
7366
|
+
input: counterDelta(current.input, previous.input),
|
|
7367
|
+
output: counterDelta(current.output, previous.output),
|
|
7368
|
+
cacheCreation: counterDelta(current.cacheCreation, previous.cacheCreation),
|
|
7369
|
+
cacheRead: counterDelta(current.cacheRead, previous.cacheRead)
|
|
7370
|
+
});
|
|
7371
|
+
previous = current;
|
|
7372
|
+
if (lastAssistantLine >= 0) {
|
|
7373
|
+
const prior = turnsByLine.get(lastAssistantLine);
|
|
7374
|
+
turnsByLine.set(lastAssistantLine, {
|
|
7375
|
+
lineIndex: lastAssistantLine,
|
|
7376
|
+
model,
|
|
7377
|
+
usage: addUsage(prior?.usage, delta)
|
|
7378
|
+
});
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
if (model) {
|
|
7382
|
+
for (const turn of turnsByLine.values()) {
|
|
7383
|
+
if (!turn.model) turn.model = model;
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
if (!latest) return null;
|
|
7387
|
+
return { model, total: normalizedUsage(latest), turnsByLine };
|
|
7388
|
+
}
|
|
7389
|
+
var finiteCounter;
|
|
7390
|
+
var init_codexTranscriptUsage = __esm({
|
|
7391
|
+
"cli/scanning/codexTranscriptUsage.ts"() {
|
|
7392
|
+
"use strict";
|
|
7393
|
+
finiteCounter = (value) => {
|
|
7394
|
+
const n = Number(value);
|
|
7395
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
7396
|
+
};
|
|
7397
|
+
}
|
|
7398
|
+
});
|
|
7399
|
+
|
|
7400
|
+
// cli/scanning/codexTranscriptMessages.ts
|
|
7401
|
+
import { createHash as createHash3 } from "crypto";
|
|
7402
|
+
function textContent(content) {
|
|
7403
|
+
if (typeof content === "string") return content.trim();
|
|
7404
|
+
if (!Array.isArray(content)) return "";
|
|
7405
|
+
return content.map((block) => {
|
|
7406
|
+
if (typeof block === "string") return block;
|
|
7407
|
+
return block?.type === "text" || block?.type === "input_text" || block?.type === "output_text" ? String(block.text || "") : "";
|
|
7408
|
+
}).join(" ").trim();
|
|
7409
|
+
}
|
|
7410
|
+
function isCodexConversationNoise(content) {
|
|
7411
|
+
const value = content.trimStart();
|
|
7412
|
+
return value.startsWith("[synkro:") || value.startsWith("claude-code: {") || value.startsWith("RAW claude-code agent:") || value.startsWith("Guard: (R") || value.startsWith("<developer") || value.startsWith("<system") || value.startsWith("<permissions instructions>") || value.startsWith("<app-context>") || value.startsWith("<environment_context>") || value.startsWith("<recommended_plugins>");
|
|
7413
|
+
}
|
|
7414
|
+
function parseCodexConversationMessages(transcript) {
|
|
7415
|
+
if (!transcript) return [];
|
|
7416
|
+
const entries = [];
|
|
7417
|
+
for (const [lineIndex, line] of transcript.split("\n").entries()) {
|
|
7418
|
+
const value = line.trim();
|
|
7419
|
+
if (!value) continue;
|
|
7420
|
+
try {
|
|
7421
|
+
entries.push({ lineIndex, entry: JSON.parse(value) });
|
|
7422
|
+
} catch {
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7425
|
+
const hasCanonicalMessages = entries.some(({ entry }) => entry?.type === "event_msg" && (entry?.payload?.type === "user_message" || entry?.payload?.type === "agent_message"));
|
|
7426
|
+
const messages = [];
|
|
7427
|
+
for (const { lineIndex, entry } of entries) {
|
|
7428
|
+
let role = null;
|
|
7429
|
+
let content;
|
|
7430
|
+
let itemId = "";
|
|
7431
|
+
const eventType = entry?.type === "event_msg" ? entry?.payload?.type : "";
|
|
7432
|
+
if (eventType === "user_message" || eventType === "agent_message") {
|
|
7433
|
+
role = eventType === "user_message" ? "user" : "assistant";
|
|
7434
|
+
content = entry?.payload?.message;
|
|
7435
|
+
} else if (!hasCanonicalMessages && entry?.type === "response_item" && entry?.payload?.type === "message") {
|
|
7436
|
+
const responseRole = entry?.payload?.role;
|
|
7437
|
+
if (responseRole !== "user" && responseRole !== "assistant") continue;
|
|
7438
|
+
role = responseRole;
|
|
7439
|
+
content = entry?.payload?.content;
|
|
7440
|
+
itemId = typeof entry?.payload?.id === "string" ? entry.payload.id : "";
|
|
7441
|
+
} else {
|
|
7442
|
+
continue;
|
|
7443
|
+
}
|
|
7444
|
+
if (!role) continue;
|
|
7445
|
+
const text = textContent(content);
|
|
7446
|
+
if (!text || isCodexConversationNoise(text)) continue;
|
|
7447
|
+
const timestamp = typeof entry?.timestamp === "string" && entry.timestamp ? entry.timestamp : void 0;
|
|
7448
|
+
const uuid = typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : itemId || createHash3("sha256").update((timestamp || "") + "\0" + role + "\0" + text).digest("hex").slice(0, 32);
|
|
7449
|
+
messages.push({
|
|
7450
|
+
lineIndex,
|
|
7451
|
+
role,
|
|
7452
|
+
content: text.slice(0, 8e3),
|
|
7453
|
+
uuid,
|
|
7454
|
+
...timestamp ? { timestamp } : {}
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7457
|
+
return messages;
|
|
7458
|
+
}
|
|
7459
|
+
var init_codexTranscriptMessages = __esm({
|
|
7460
|
+
"cli/scanning/codexTranscriptMessages.ts"() {
|
|
7461
|
+
"use strict";
|
|
7462
|
+
}
|
|
7463
|
+
});
|
|
7464
|
+
|
|
6648
7465
|
// cli/commands/install.ts
|
|
6649
7466
|
var install_exports = {};
|
|
6650
7467
|
__export(install_exports, {
|
|
@@ -6660,12 +7477,12 @@ __export(install_exports, {
|
|
|
6660
7477
|
syncSkillFiles: () => syncSkillFiles,
|
|
6661
7478
|
writeHookScripts: () => writeHookScripts
|
|
6662
7479
|
});
|
|
6663
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as
|
|
6664
|
-
import { homedir as
|
|
6665
|
-
import { join as
|
|
7480
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
|
|
7481
|
+
import { homedir as homedir21 } from "os";
|
|
7482
|
+
import { join as join19, isAbsolute, resolve as resolve4 } from "path";
|
|
6666
7483
|
import { execSync as execSync4, spawn as spawn4 } from "child_process";
|
|
6667
7484
|
import { createInterface as createInterface2 } from "readline";
|
|
6668
|
-
import { createHash as
|
|
7485
|
+
import { createHash as createHash4 } from "crypto";
|
|
6669
7486
|
function resolvePersistedHookMode() {
|
|
6670
7487
|
return "stub";
|
|
6671
7488
|
}
|
|
@@ -6793,34 +7610,37 @@ function ensureSynkroDir() {
|
|
|
6793
7610
|
mkdirSync15(HOOKS_DIR, { recursive: true });
|
|
6794
7611
|
mkdirSync15(BIN_DIR, { recursive: true });
|
|
6795
7612
|
mkdirSync15(OFFSETS_DIR, { recursive: true });
|
|
6796
|
-
mkdirSync15(
|
|
7613
|
+
mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
|
|
6797
7614
|
}
|
|
6798
7615
|
function writeHookScripts() {
|
|
6799
|
-
const installExtractCorePath =
|
|
6800
|
-
const bashScriptPath =
|
|
6801
|
-
const skillJudgeScriptPath =
|
|
6802
|
-
const cursorSkillJudgePath =
|
|
6803
|
-
const bashFollowupScriptPath =
|
|
6804
|
-
const editPrecheckScriptPath =
|
|
6805
|
-
const
|
|
6806
|
-
const
|
|
6807
|
-
const
|
|
6808
|
-
const
|
|
6809
|
-
const
|
|
6810
|
-
const
|
|
6811
|
-
const
|
|
6812
|
-
const
|
|
6813
|
-
const
|
|
6814
|
-
const
|
|
6815
|
-
const
|
|
6816
|
-
const
|
|
6817
|
-
const
|
|
6818
|
-
const
|
|
6819
|
-
const
|
|
6820
|
-
const
|
|
6821
|
-
const
|
|
6822
|
-
const
|
|
6823
|
-
const
|
|
7616
|
+
const installExtractCorePath = join19(HOOKS_DIR, "installExtractCore.ts");
|
|
7617
|
+
const bashScriptPath = join19(HOOKS_DIR, "cc-bash-judge.ts");
|
|
7618
|
+
const skillJudgeScriptPath = join19(HOOKS_DIR, "cc-skill-judge.ts");
|
|
7619
|
+
const cursorSkillJudgePath = join19(HOOKS_DIR, "cursor-skill-judge.ts");
|
|
7620
|
+
const bashFollowupScriptPath = join19(HOOKS_DIR, "cc-bash-followup.ts");
|
|
7621
|
+
const editPrecheckScriptPath = join19(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
7622
|
+
const editFollowupScriptPath = join19(HOOKS_DIR, "cc-edit-followup.ts");
|
|
7623
|
+
const cwePrecheckScriptPath = join19(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
7624
|
+
const cvePrecheckScriptPath = join19(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
7625
|
+
const planJudgeScriptPath = join19(HOOKS_DIR, "cc-plan-judge.ts");
|
|
7626
|
+
const agentJudgeScriptPath = join19(HOOKS_DIR, "cc-agent-judge.ts");
|
|
7627
|
+
const stopSummaryScriptPath = join19(HOOKS_DIR, "cc-stop-summary.ts");
|
|
7628
|
+
const sessionStartScriptPath = join19(HOOKS_DIR, "cc-session-start.ts");
|
|
7629
|
+
const transcriptSyncScriptPath = join19(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
7630
|
+
const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
|
|
7631
|
+
const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
|
|
7632
|
+
const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
7633
|
+
const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
|
|
7634
|
+
const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
|
|
7635
|
+
const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
|
|
7636
|
+
const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
|
|
7637
|
+
const cursorBashJudgePath = join19(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
7638
|
+
const cursorEditCapturePath = join19(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
7639
|
+
const cursorAgentCapturePath = join19(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
7640
|
+
const mcpStdioProxyPath = join19(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
7641
|
+
const taskActivateIntentScriptPath = join19(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
7642
|
+
const mcpGateScriptPath = join19(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
7643
|
+
const stubCommonPath = join19(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
6824
7644
|
const stubFiles = [
|
|
6825
7645
|
[stubCommonPath, STUB_COMMON_TS],
|
|
6826
7646
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -6828,6 +7648,7 @@ function writeHookScripts() {
|
|
|
6828
7648
|
[cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],
|
|
6829
7649
|
[bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],
|
|
6830
7650
|
[editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],
|
|
7651
|
+
[editFollowupScriptPath, STUB_EDIT_FOLLOWUP_TS],
|
|
6831
7652
|
[cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],
|
|
6832
7653
|
[cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],
|
|
6833
7654
|
[planJudgeScriptPath, STUB_PLAN_JUDGE_TS],
|
|
@@ -6835,6 +7656,8 @@ function writeHookScripts() {
|
|
|
6835
7656
|
[stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],
|
|
6836
7657
|
[sessionStartScriptPath, STUB_SESSION_START_TS],
|
|
6837
7658
|
[transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],
|
|
7659
|
+
[subagentStartScriptPath, STUB_SUBAGENT_START_TS],
|
|
7660
|
+
[subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
|
|
6838
7661
|
[userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
|
|
6839
7662
|
[promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
|
|
6840
7663
|
[installScanScriptPath, STUB_INSTALL_SCAN_TS],
|
|
@@ -6845,14 +7668,14 @@ function writeHookScripts() {
|
|
|
6845
7668
|
[cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
|
|
6846
7669
|
];
|
|
6847
7670
|
for (const [p, content] of stubFiles) {
|
|
6848
|
-
|
|
6849
|
-
|
|
7671
|
+
writeFileSync17(p, content, "utf-8");
|
|
7672
|
+
chmodSync5(p, 493);
|
|
6850
7673
|
}
|
|
6851
|
-
|
|
6852
|
-
|
|
7674
|
+
writeFileSync17(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
7675
|
+
chmodSync5(mcpStdioProxyPath, 493);
|
|
6853
7676
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
6854
7677
|
try {
|
|
6855
|
-
unlinkSync7(
|
|
7678
|
+
unlinkSync7(join19(HOOKS_DIR, stale));
|
|
6856
7679
|
} catch {
|
|
6857
7680
|
}
|
|
6858
7681
|
}
|
|
@@ -6862,6 +7685,7 @@ function writeHookScripts() {
|
|
|
6862
7685
|
cursorSkillJudgeScript: cursorSkillJudgePath,
|
|
6863
7686
|
bashFollowupScript: bashFollowupScriptPath,
|
|
6864
7687
|
editPrecheckScript: editPrecheckScriptPath,
|
|
7688
|
+
editFollowupScript: editFollowupScriptPath,
|
|
6865
7689
|
cwePrecheckScript: cwePrecheckScriptPath,
|
|
6866
7690
|
cvePrecheckScript: cvePrecheckScriptPath,
|
|
6867
7691
|
planJudgeScript: planJudgeScriptPath,
|
|
@@ -6869,6 +7693,8 @@ function writeHookScripts() {
|
|
|
6869
7693
|
stopSummaryScript: stopSummaryScriptPath,
|
|
6870
7694
|
sessionStartScript: sessionStartScriptPath,
|
|
6871
7695
|
transcriptSyncScript: transcriptSyncScriptPath,
|
|
7696
|
+
subagentStartScript: subagentStartScriptPath,
|
|
7697
|
+
subagentStopScript: subagentStopScriptPath,
|
|
6872
7698
|
userPromptSubmitScript: userPromptSubmitScriptPath,
|
|
6873
7699
|
promptRouteScript: promptRouteScriptPath,
|
|
6874
7700
|
installScanScript: installScanScriptPath,
|
|
@@ -6892,7 +7718,7 @@ function resolveSynkroBundle() {
|
|
|
6892
7718
|
return null;
|
|
6893
7719
|
}
|
|
6894
7720
|
function writeConfigEnv(opts) {
|
|
6895
|
-
const credsPath =
|
|
7721
|
+
const credsPath = join19(SYNKRO_DIR11, "credentials.json");
|
|
6896
7722
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
6897
7723
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
6898
7724
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -6908,7 +7734,7 @@ function writeConfigEnv(opts) {
|
|
|
6908
7734
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
6909
7735
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
6910
7736
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
6911
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.
|
|
7737
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.89")}`
|
|
6912
7738
|
];
|
|
6913
7739
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
6914
7740
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -6928,12 +7754,12 @@ function writeConfigEnv(opts) {
|
|
|
6928
7754
|
lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
6929
7755
|
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
6930
7756
|
lines.push("");
|
|
6931
|
-
|
|
6932
|
-
|
|
7757
|
+
writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
|
|
7758
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
6933
7759
|
}
|
|
6934
7760
|
function persistedTranscriptConsent(source) {
|
|
6935
7761
|
try {
|
|
6936
|
-
const env =
|
|
7762
|
+
const env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
6937
7763
|
const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
|
|
6938
7764
|
if (specific) return specific[1] === "yes";
|
|
6939
7765
|
if (source !== "CODEX") {
|
|
@@ -6956,7 +7782,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6956
7782
|
assertGatewayAllowed(gatewayUrl);
|
|
6957
7783
|
let stored = "";
|
|
6958
7784
|
try {
|
|
6959
|
-
stored =
|
|
7785
|
+
stored = readFileSync21(CLOUD_JWT_PATH, "utf-8").trim();
|
|
6960
7786
|
} catch {
|
|
6961
7787
|
}
|
|
6962
7788
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -6972,7 +7798,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6972
7798
|
throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
|
|
6973
7799
|
}
|
|
6974
7800
|
const { token } = await resp.json();
|
|
6975
|
-
|
|
7801
|
+
writeFileSync17(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
|
|
6976
7802
|
return token;
|
|
6977
7803
|
}
|
|
6978
7804
|
async function provisionCloudContainer(opts) {
|
|
@@ -6997,9 +7823,13 @@ async function provisionCloudContainer(opts) {
|
|
|
6997
7823
|
({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
|
|
6998
7824
|
}
|
|
6999
7825
|
if (claudeWorkers + cursorWorkers + codexWorkers === 0) codexWorkers = totalWorkers;
|
|
7000
|
-
const selectedKind =
|
|
7826
|
+
const selectedKind = resolveConductorProvider(sf?.grader.pool || "auto", {
|
|
7827
|
+
claudeWorkers,
|
|
7828
|
+
cursorWorkers,
|
|
7829
|
+
codexWorkers
|
|
7830
|
+
});
|
|
7001
7831
|
let setupToken = "";
|
|
7002
|
-
if (claudeWorkers > 0) {
|
|
7832
|
+
if (claudeWorkers > 0 || selectedKind === "claude_code") {
|
|
7003
7833
|
try {
|
|
7004
7834
|
console.log(" Authorize your Claude account for the hosted Claude worker \u2014");
|
|
7005
7835
|
console.log(" a browser window will open for approval...\n");
|
|
@@ -7050,9 +7880,9 @@ async function provisionCloudContainer(opts) {
|
|
|
7050
7880
|
if (opts.hasCursor) harness.push("cursor");
|
|
7051
7881
|
if (opts.hasCodex) harness.push("codex");
|
|
7052
7882
|
let cursorApiKey = "";
|
|
7053
|
-
if (cursorWorkers > 0) {
|
|
7883
|
+
if (cursorWorkers > 0 || selectedKind === "cursor") {
|
|
7054
7884
|
try {
|
|
7055
|
-
cursorApiKey =
|
|
7885
|
+
cursorApiKey = readFileSync21(join19(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
|
|
7056
7886
|
} catch {
|
|
7057
7887
|
}
|
|
7058
7888
|
}
|
|
@@ -7062,7 +7892,7 @@ async function provisionCloudContainer(opts) {
|
|
|
7062
7892
|
} catch (e) {
|
|
7063
7893
|
console.warn(` (cloud token unavailable, using session token: ${e.message})`);
|
|
7064
7894
|
}
|
|
7065
|
-
if (codexWorkers > 0) {
|
|
7895
|
+
if (codexWorkers > 0 || selectedKind === "codex") {
|
|
7066
7896
|
const codex = await setupCodexCloud(opts.gatewayUrl, opts.jwt, (message) => console.log(` ${message}`));
|
|
7067
7897
|
if (!codex.ok) {
|
|
7068
7898
|
console.error(` \u2717 ${codex.error || "Codex cloud authorization failed"}`);
|
|
@@ -7081,6 +7911,7 @@ async function provisionCloudContainer(opts) {
|
|
|
7081
7911
|
claude_workers: claudeWorkers,
|
|
7082
7912
|
cursor_workers: cursorWorkers,
|
|
7083
7913
|
codex_workers: codexWorkers,
|
|
7914
|
+
conductor_provider: selectedKind,
|
|
7084
7915
|
cursor_api_key: cursorApiKey,
|
|
7085
7916
|
// never logged; gateway stores it as the org secret
|
|
7086
7917
|
connected_repo: repo,
|
|
@@ -7206,11 +8037,12 @@ async function verifyCloudGrader(jwt2, requestedKind) {
|
|
|
7206
8037
|
body = JSON.parse(raw);
|
|
7207
8038
|
} catch {
|
|
7208
8039
|
}
|
|
7209
|
-
if (r.ok && typeof body.result === "string" && body.result
|
|
8040
|
+
if (r.ok && typeof body.result === "string" && isValidRiskyEditSmokeVerdict(body.result)) {
|
|
7210
8041
|
console.log(" \u2713 smoke grade passed \u2014 cloud grading is live\n");
|
|
7211
8042
|
return true;
|
|
7212
8043
|
}
|
|
7213
|
-
const
|
|
8044
|
+
const invalidVerdict = r.ok && typeof body.result === "string" ? `invalid smoke verdict: ${body.result}` : "";
|
|
8045
|
+
const detail = (body.error || invalidVerdict || raw || "").trim().slice(0, 300) || "(empty response)";
|
|
7214
8046
|
console.warn(` \u2717 smoke grade FAILED (HTTP ${r.status}): ${detail}`);
|
|
7215
8047
|
if (body.error_code) {
|
|
7216
8048
|
console.warn(` \u25B8 ${body.error_code}: ${body.hint || ""}`);
|
|
@@ -7236,7 +8068,7 @@ async function verifyCloudGrader(jwt2, requestedKind) {
|
|
|
7236
8068
|
function readPersistedDeployLocation() {
|
|
7237
8069
|
try {
|
|
7238
8070
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7239
|
-
const m =
|
|
8071
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
7240
8072
|
if (m?.[1] === "cloud") return "cloud";
|
|
7241
8073
|
}
|
|
7242
8074
|
} catch {
|
|
@@ -7245,7 +8077,7 @@ function readPersistedDeployLocation() {
|
|
|
7245
8077
|
}
|
|
7246
8078
|
function updateConfigEnvLocation(location) {
|
|
7247
8079
|
if (!existsSync22(CONFIG_PATH4)) return;
|
|
7248
|
-
let env =
|
|
8080
|
+
let env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
7249
8081
|
const set = (k, v) => {
|
|
7250
8082
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
7251
8083
|
const line = `${k}='${v}'`;
|
|
@@ -7253,14 +8085,14 @@ function updateConfigEnvLocation(location) {
|
|
|
7253
8085
|
};
|
|
7254
8086
|
set("SYNKRO_DEPLOY_LOCATION", location);
|
|
7255
8087
|
set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
|
|
7256
|
-
|
|
7257
|
-
|
|
8088
|
+
writeFileSync17(CONFIG_PATH4, env, "utf-8");
|
|
8089
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
7258
8090
|
}
|
|
7259
8091
|
async function applyMcpConfig(opts) {
|
|
7260
8092
|
if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
|
|
7261
8093
|
let mcpJwt2 = "";
|
|
7262
8094
|
try {
|
|
7263
|
-
mcpJwt2 =
|
|
8095
|
+
mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7264
8096
|
} catch {
|
|
7265
8097
|
}
|
|
7266
8098
|
if (!mcpJwt2) {
|
|
@@ -7391,7 +8223,7 @@ function resolveDeploymentMode() {
|
|
|
7391
8223
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
7392
8224
|
try {
|
|
7393
8225
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7394
|
-
const m =
|
|
8226
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
7395
8227
|
const val = m?.[1]?.toLowerCase();
|
|
7396
8228
|
if (val === "bare-host" || val === "docker") return val;
|
|
7397
8229
|
}
|
|
@@ -7418,16 +8250,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7418
8250
|
meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
7419
8251
|
} catch {
|
|
7420
8252
|
}
|
|
7421
|
-
const claudeDir =
|
|
8253
|
+
const claudeDir = join19(homedir21(), ".claude");
|
|
7422
8254
|
try {
|
|
7423
|
-
const settings = JSON.parse(
|
|
8255
|
+
const settings = JSON.parse(readFileSync21(join19(claudeDir, "settings.json"), "utf-8"));
|
|
7424
8256
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
7425
8257
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
7426
8258
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
7427
8259
|
} catch {
|
|
7428
8260
|
}
|
|
7429
8261
|
try {
|
|
7430
|
-
const mcpCache = JSON.parse(
|
|
8262
|
+
const mcpCache = JSON.parse(readFileSync21(join19(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
7431
8263
|
const mcpNames = Object.keys(mcpCache);
|
|
7432
8264
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
7433
8265
|
} catch {
|
|
@@ -7439,10 +8271,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7439
8271
|
} catch {
|
|
7440
8272
|
}
|
|
7441
8273
|
try {
|
|
7442
|
-
const sessionsDir =
|
|
8274
|
+
const sessionsDir = join19(claudeDir, "sessions");
|
|
7443
8275
|
const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
7444
8276
|
for (const f of files) {
|
|
7445
|
-
const s = JSON.parse(
|
|
8277
|
+
const s = JSON.parse(readFileSync21(join19(sessionsDir, f), "utf-8"));
|
|
7446
8278
|
if (s.version) {
|
|
7447
8279
|
meta.cc_version = meta.cc_version || s.version;
|
|
7448
8280
|
break;
|
|
@@ -7644,7 +8476,7 @@ async function installCommand(opts = {}) {
|
|
|
7644
8476
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
7645
8477
|
emit("install", {
|
|
7646
8478
|
phase: "started",
|
|
7647
|
-
cli_version_to: "1.7.
|
|
8479
|
+
cli_version_to: "1.7.89",
|
|
7648
8480
|
agents_detected: agents.map((a) => a.kind),
|
|
7649
8481
|
with_github: false,
|
|
7650
8482
|
with_local_cc: false,
|
|
@@ -7656,9 +8488,9 @@ async function installCommand(opts = {}) {
|
|
|
7656
8488
|
const scripts = writeHookScripts();
|
|
7657
8489
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
7658
8490
|
for (const mode of ["edit", "bash"]) {
|
|
7659
|
-
const pidFile =
|
|
8491
|
+
const pidFile = join19(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
|
|
7660
8492
|
try {
|
|
7661
|
-
const pid = parseInt(
|
|
8493
|
+
const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
|
|
7662
8494
|
if (pid > 0) {
|
|
7663
8495
|
process.kill(pid, "SIGTERM");
|
|
7664
8496
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -7721,12 +8553,15 @@ async function installCommand(opts = {}) {
|
|
|
7721
8553
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
7722
8554
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
7723
8555
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
8556
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
7724
8557
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
7725
8558
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
7726
8559
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
7727
8560
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
7728
8561
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
7729
8562
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
8563
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
8564
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
7730
8565
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
7731
8566
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
7732
8567
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -7776,7 +8611,7 @@ async function installCommand(opts = {}) {
|
|
|
7776
8611
|
if (mintResp.ok) {
|
|
7777
8612
|
const minted = await mintResp.json();
|
|
7778
8613
|
mcpJwt2 = minted.token;
|
|
7779
|
-
|
|
8614
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
|
|
7780
8615
|
} else {
|
|
7781
8616
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
7782
8617
|
}
|
|
@@ -7804,7 +8639,7 @@ async function installCommand(opts = {}) {
|
|
|
7804
8639
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7805
8640
|
}
|
|
7806
8641
|
const minted = await mintResp.json();
|
|
7807
|
-
|
|
8642
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7808
8643
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7809
8644
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7810
8645
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7826,7 +8661,7 @@ async function installCommand(opts = {}) {
|
|
|
7826
8661
|
if (hasCursor && !opts.noMcp) {
|
|
7827
8662
|
try {
|
|
7828
8663
|
if (useLocalMcp) {
|
|
7829
|
-
const jwtPath =
|
|
8664
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7830
8665
|
if (!existsSync22(jwtPath)) {
|
|
7831
8666
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
7832
8667
|
method: "POST",
|
|
@@ -7835,7 +8670,7 @@ async function installCommand(opts = {}) {
|
|
|
7835
8670
|
});
|
|
7836
8671
|
if (mintResp.ok) {
|
|
7837
8672
|
const minted = await mintResp.json();
|
|
7838
|
-
|
|
8673
|
+
writeFileSync17(jwtPath, minted.token + "\n", { mode: 384 });
|
|
7839
8674
|
}
|
|
7840
8675
|
}
|
|
7841
8676
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
|
|
@@ -7855,7 +8690,7 @@ async function installCommand(opts = {}) {
|
|
|
7855
8690
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7856
8691
|
}
|
|
7857
8692
|
const minted = await mintResp.json();
|
|
7858
|
-
|
|
8693
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7859
8694
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7860
8695
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7861
8696
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7868,10 +8703,10 @@ async function installCommand(opts = {}) {
|
|
|
7868
8703
|
}
|
|
7869
8704
|
if (hasCodex && !opts.noMcp) {
|
|
7870
8705
|
try {
|
|
7871
|
-
const jwtPath =
|
|
8706
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7872
8707
|
let mcpJwt2 = "";
|
|
7873
8708
|
try {
|
|
7874
|
-
mcpJwt2 =
|
|
8709
|
+
mcpJwt2 = readFileSync21(jwtPath, "utf-8").trim();
|
|
7875
8710
|
} catch {
|
|
7876
8711
|
}
|
|
7877
8712
|
if (!mcpJwt2) {
|
|
@@ -7889,7 +8724,7 @@ async function installCommand(opts = {}) {
|
|
|
7889
8724
|
}
|
|
7890
8725
|
const minted = await mintResp.json();
|
|
7891
8726
|
mcpJwt2 = minted.token;
|
|
7892
|
-
|
|
8727
|
+
writeFileSync17(jwtPath, mcpJwt2 + "\n", { mode: 384 });
|
|
7893
8728
|
}
|
|
7894
8729
|
const mcp = installCodexMcpConfig({
|
|
7895
8730
|
gatewayUrl,
|
|
@@ -7985,8 +8820,13 @@ async function installCommand(opts = {}) {
|
|
|
7985
8820
|
({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
|
|
7986
8821
|
if (synkroFilePool !== "auto") console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);
|
|
7987
8822
|
}
|
|
8823
|
+
const conductorProvider = resolveConductorProvider(sf?.grader.pool || "auto", {
|
|
8824
|
+
claudeWorkers,
|
|
8825
|
+
cursorWorkers,
|
|
8826
|
+
codexWorkers
|
|
8827
|
+
});
|
|
7988
8828
|
let codexHomeDir;
|
|
7989
|
-
if (codexWorkers > 0) {
|
|
8829
|
+
if (codexWorkers > 0 || conductorProvider === "codex") {
|
|
7990
8830
|
const codexSetup = await setupCodexLocal((message) => console.log(` ${message}`));
|
|
7991
8831
|
if (!codexSetup.ok || !codexSetup.home) {
|
|
7992
8832
|
console.error(` \u2717 ${codexSetup.error || "Codex grader authorization failed"}`);
|
|
@@ -7996,14 +8836,15 @@ async function installCommand(opts = {}) {
|
|
|
7996
8836
|
}
|
|
7997
8837
|
console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
|
|
7998
8838
|
const connectedRepo = detectGitRepo2() || void 0;
|
|
7999
|
-
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, codexHomeDir, connectedRepo });
|
|
8839
|
+
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort, pglitePasswordPath } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
|
|
8000
8840
|
console.log(` \u2713 pulled ${image}`);
|
|
8001
|
-
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}
|
|
8841
|
+
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
|
|
8842
|
+
console.log(` PGLite: postgresql://synkro@127.0.0.1:${hostPglitePort}/postgres (password: ${pglitePasswordPath})`);
|
|
8002
8843
|
console.log(" waiting for container to be ready...");
|
|
8003
8844
|
const ready = await waitForContainerReady(6e4);
|
|
8004
8845
|
if (ready) {
|
|
8005
8846
|
console.log(" \u2713 container ready");
|
|
8006
|
-
const mcpJwt2 =
|
|
8847
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8007
8848
|
try {
|
|
8008
8849
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
8009
8850
|
method: "POST",
|
|
@@ -8046,7 +8887,7 @@ async function installCommand(opts = {}) {
|
|
|
8046
8887
|
try {
|
|
8047
8888
|
let mcpToken = "";
|
|
8048
8889
|
try {
|
|
8049
|
-
mcpToken =
|
|
8890
|
+
mcpToken = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8050
8891
|
} catch {
|
|
8051
8892
|
}
|
|
8052
8893
|
if (mcpToken) {
|
|
@@ -8214,8 +9055,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8214
9055
|
try {
|
|
8215
9056
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8216
9057
|
if (!root) return;
|
|
8217
|
-
if (root ===
|
|
8218
|
-
const fp =
|
|
9058
|
+
if (root === homedir21()) return;
|
|
9059
|
+
const fp = join19(root, "synkro.toml");
|
|
8219
9060
|
let hasFile = false;
|
|
8220
9061
|
try {
|
|
8221
9062
|
hasFile = statSync2(fp).isFile();
|
|
@@ -8247,7 +9088,7 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8247
9088
|
"cve = true",
|
|
8248
9089
|
""
|
|
8249
9090
|
].join("\n");
|
|
8250
|
-
|
|
9091
|
+
writeFileSync17(fp, toml, "utf-8");
|
|
8251
9092
|
console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
|
|
8252
9093
|
} catch {
|
|
8253
9094
|
}
|
|
@@ -8255,12 +9096,12 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8255
9096
|
function updateSynkroTomlLocation(location) {
|
|
8256
9097
|
try {
|
|
8257
9098
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8258
|
-
if (!root || root ===
|
|
8259
|
-
const fp =
|
|
9099
|
+
if (!root || root === homedir21()) return;
|
|
9100
|
+
const fp = join19(root, "synkro.toml");
|
|
8260
9101
|
let txt = "";
|
|
8261
9102
|
try {
|
|
8262
9103
|
if (!statSync2(fp).isFile()) return;
|
|
8263
|
-
txt =
|
|
9104
|
+
txt = readFileSync21(fp, "utf-8");
|
|
8264
9105
|
} catch {
|
|
8265
9106
|
return;
|
|
8266
9107
|
}
|
|
@@ -8268,7 +9109,7 @@ function updateSynkroTomlLocation(location) {
|
|
|
8268
9109
|
if (!re.test(txt)) return;
|
|
8269
9110
|
const next = txt.replace(re, `$1"${location}"`);
|
|
8270
9111
|
if (next !== txt) {
|
|
8271
|
-
|
|
9112
|
+
writeFileSync17(fp, next, "utf-8");
|
|
8272
9113
|
console.log(` synkro.toml: [grader] location = "${location}"`);
|
|
8273
9114
|
}
|
|
8274
9115
|
} catch {
|
|
@@ -8278,9 +9119,9 @@ function readFullSynkroFile() {
|
|
|
8278
9119
|
try {
|
|
8279
9120
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8280
9121
|
if (!root) return null;
|
|
8281
|
-
const fp =
|
|
9122
|
+
const fp = join19(root, "synkro.toml");
|
|
8282
9123
|
if (!existsSync22(fp)) return null;
|
|
8283
|
-
const parsed = parseSynkroToml2(
|
|
9124
|
+
const parsed = parseSynkroToml2(readFileSync21(fp, "utf-8"));
|
|
8284
9125
|
const valid = ["claude-code", "cursor", "codex"];
|
|
8285
9126
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
8286
9127
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -8320,7 +9161,7 @@ function reconcileHarness() {
|
|
|
8320
9161
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
8321
9162
|
const scripts = writeHookScripts();
|
|
8322
9163
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
8323
|
-
const ccSettings =
|
|
9164
|
+
const ccSettings = join19(homedir21(), ".claude", "settings.json");
|
|
8324
9165
|
if (wantCC) {
|
|
8325
9166
|
installCCHooks(ccSettings, {
|
|
8326
9167
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -8343,7 +9184,7 @@ function reconcileHarness() {
|
|
|
8343
9184
|
});
|
|
8344
9185
|
console.log(" \u2713 Claude Code hooks registered");
|
|
8345
9186
|
try {
|
|
8346
|
-
const mcpJwt2 =
|
|
9187
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8347
9188
|
if (mcpJwt2) {
|
|
8348
9189
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
|
|
8349
9190
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -8355,7 +9196,7 @@ function reconcileHarness() {
|
|
|
8355
9196
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
8356
9197
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
8357
9198
|
}
|
|
8358
|
-
const cursorHooks =
|
|
9199
|
+
const cursorHooks = join19(homedir21(), ".cursor", "hooks.json");
|
|
8359
9200
|
if (wantCursor) {
|
|
8360
9201
|
installCursorHooks(cursorHooks, {
|
|
8361
9202
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -8386,19 +9227,22 @@ function reconcileHarness() {
|
|
|
8386
9227
|
if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
|
|
8387
9228
|
if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
|
|
8388
9229
|
}
|
|
8389
|
-
const codexHooks =
|
|
9230
|
+
const codexHooks = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "hooks.json");
|
|
8390
9231
|
if (wantCodex) {
|
|
8391
9232
|
installCodexHooks(codexHooks, {
|
|
8392
9233
|
bashJudgeScriptPath: scripts.bashScript,
|
|
8393
9234
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
8394
9235
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
8395
9236
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
9237
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
8396
9238
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
8397
9239
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
8398
9240
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
8399
9241
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
8400
9242
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
8401
9243
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
9244
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
9245
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
8402
9246
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
8403
9247
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
8404
9248
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -8421,7 +9265,10 @@ function reconcileHarness() {
|
|
|
8421
9265
|
const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
|
|
8422
9266
|
const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
|
|
8423
9267
|
const codw = Math.max(0, Math.floor(sf.workers.codex || 0));
|
|
8424
|
-
if (cw + curw + codw > 0)
|
|
9268
|
+
if (cw + curw + codw > 0) {
|
|
9269
|
+
const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
|
|
9270
|
+
return { ...counts2, conductorProvider: resolveConductorProvider(sf.grader.pool, counts2) };
|
|
9271
|
+
}
|
|
8425
9272
|
}
|
|
8426
9273
|
const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
|
|
8427
9274
|
const providers = [];
|
|
@@ -8437,7 +9284,8 @@ function reconcileHarness() {
|
|
|
8437
9284
|
if (wantCodex) providers.push("codex");
|
|
8438
9285
|
}
|
|
8439
9286
|
if (providers.length === 0) providers.push("claude_code");
|
|
8440
|
-
|
|
9287
|
+
const counts = splitWorkers(total, providers);
|
|
9288
|
+
return { ...counts, conductorProvider: resolveConductorProvider(sf.grader.pool, counts) };
|
|
8441
9289
|
}
|
|
8442
9290
|
async function ingestSkillTasks(tasks, mcpPort) {
|
|
8443
9291
|
const summary = { ingested: 0, rules: 0, rejected: [] };
|
|
@@ -8474,7 +9322,7 @@ async function syncSkillFiles() {
|
|
|
8474
9322
|
if (resolved.length === 0) return;
|
|
8475
9323
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
8476
9324
|
const tasks = resolved.map((fp) => {
|
|
8477
|
-
const content =
|
|
9325
|
+
const content = readFileSync21(fp, "utf-8");
|
|
8478
9326
|
const source = `skill:${fp.split("/").pop()}`;
|
|
8479
9327
|
if (!content.trim()) {
|
|
8480
9328
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -8499,11 +9347,11 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8499
9347
|
} catch {
|
|
8500
9348
|
}
|
|
8501
9349
|
};
|
|
8502
|
-
add(
|
|
8503
|
-
add(
|
|
9350
|
+
add(join19(homedir21(), ".claude", "skills"));
|
|
9351
|
+
add(join19(homedir21(), ".agents", "skills"));
|
|
8504
9352
|
if (repoRoot2) {
|
|
8505
|
-
add(
|
|
8506
|
-
add(
|
|
9353
|
+
add(join19(repoRoot2, ".claude", "skills"));
|
|
9354
|
+
add(join19(repoRoot2, ".agents", "skills"));
|
|
8507
9355
|
}
|
|
8508
9356
|
const out = [];
|
|
8509
9357
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8514,12 +9362,12 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8514
9362
|
try {
|
|
8515
9363
|
const st = statSync2(file);
|
|
8516
9364
|
if (!st.isFile() || st.size > 2e5) return;
|
|
8517
|
-
content =
|
|
9365
|
+
content = readFileSync21(file, "utf-8");
|
|
8518
9366
|
} catch {
|
|
8519
9367
|
return;
|
|
8520
9368
|
}
|
|
8521
9369
|
if (!content.trim()) return;
|
|
8522
|
-
const hash =
|
|
9370
|
+
const hash = createHash4("sha256").update(content).digest("hex");
|
|
8523
9371
|
if (seen.has(hash) || excludeHashes.has(hash)) return;
|
|
8524
9372
|
seen.add(hash);
|
|
8525
9373
|
const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));
|
|
@@ -8534,7 +9382,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8534
9382
|
}
|
|
8535
9383
|
for (const entry of entries) {
|
|
8536
9384
|
if (entry.startsWith(".")) continue;
|
|
8537
|
-
const full =
|
|
9385
|
+
const full = join19(root, entry);
|
|
8538
9386
|
let st;
|
|
8539
9387
|
try {
|
|
8540
9388
|
st = statSync2(full);
|
|
@@ -8542,7 +9390,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8542
9390
|
continue;
|
|
8543
9391
|
}
|
|
8544
9392
|
if (st.isDirectory()) {
|
|
8545
|
-
const skillMd =
|
|
9393
|
+
const skillMd = join19(full, "SKILL.md");
|
|
8546
9394
|
if (existsSync22(skillMd)) consider(skillMd, entry);
|
|
8547
9395
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
8548
9396
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
@@ -8552,7 +9400,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8552
9400
|
return out;
|
|
8553
9401
|
}
|
|
8554
9402
|
function discoverySetHash(found) {
|
|
8555
|
-
return
|
|
9403
|
+
return createHash4("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
|
|
8556
9404
|
}
|
|
8557
9405
|
async function promptSkillDiscovery(found) {
|
|
8558
9406
|
if (!process.stdin.isTTY || found.length === 0) return [];
|
|
@@ -8592,7 +9440,7 @@ async function discoverAndIngestSkills() {
|
|
|
8592
9440
|
if (sf?.skills?.length) {
|
|
8593
9441
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
8594
9442
|
try {
|
|
8595
|
-
excludeHashes.add(
|
|
9443
|
+
excludeHashes.add(createHash4("sha256").update(readFileSync21(fp, "utf-8")).digest("hex"));
|
|
8596
9444
|
} catch {
|
|
8597
9445
|
}
|
|
8598
9446
|
}
|
|
@@ -8623,13 +9471,13 @@ async function discoverAndIngestSkills() {
|
|
|
8623
9471
|
const setHash = discoverySetHash(selectable);
|
|
8624
9472
|
let prev = "";
|
|
8625
9473
|
try {
|
|
8626
|
-
prev =
|
|
9474
|
+
prev = readFileSync21(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
8627
9475
|
} catch {
|
|
8628
9476
|
}
|
|
8629
9477
|
if (prev === setHash) return;
|
|
8630
9478
|
const picks = await promptSkillDiscovery(found);
|
|
8631
9479
|
try {
|
|
8632
|
-
|
|
9480
|
+
writeFileSync17(SKILLS_DISCOVERED_PATH, setHash);
|
|
8633
9481
|
} catch {
|
|
8634
9482
|
}
|
|
8635
9483
|
if (picks.length === 0) {
|
|
@@ -8668,9 +9516,9 @@ function ensureReachabilityGitHook() {
|
|
|
8668
9516
|
const root = run("git rev-parse --show-toplevel");
|
|
8669
9517
|
if (!root) return null;
|
|
8670
9518
|
let hooksDir = run("git config --get core.hooksPath");
|
|
8671
|
-
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir :
|
|
9519
|
+
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join19(root, hooksDir) : join19(root, ".git", "hooks");
|
|
8672
9520
|
if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
|
|
8673
|
-
const hookPath =
|
|
9521
|
+
const hookPath = join19(hooksDir, "post-commit");
|
|
8674
9522
|
const resolvedBin = resolveSynkroBinPath();
|
|
8675
9523
|
const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
|
|
8676
9524
|
const START = "# >>> synkro reachability (managed) >>>";
|
|
@@ -8684,23 +9532,23 @@ function ensureReachabilityGitHook() {
|
|
|
8684
9532
|
].join("\n");
|
|
8685
9533
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8686
9534
|
if (!existsSync22(hookPath)) {
|
|
8687
|
-
|
|
9535
|
+
writeFileSync17(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
|
|
8688
9536
|
return "installed";
|
|
8689
9537
|
}
|
|
8690
|
-
let cur =
|
|
9538
|
+
let cur = readFileSync21(hookPath, "utf-8");
|
|
8691
9539
|
if (cur.includes(START)) {
|
|
8692
9540
|
cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
|
|
8693
|
-
|
|
9541
|
+
writeFileSync17(hookPath, cur);
|
|
8694
9542
|
try {
|
|
8695
|
-
|
|
9543
|
+
chmodSync5(hookPath, 493);
|
|
8696
9544
|
} catch {
|
|
8697
9545
|
}
|
|
8698
9546
|
return "updated";
|
|
8699
9547
|
}
|
|
8700
9548
|
const sep = cur.endsWith("\n") ? "" : "\n";
|
|
8701
|
-
|
|
9549
|
+
writeFileSync17(hookPath, cur + sep + "\n" + block + "\n");
|
|
8702
9550
|
try {
|
|
8703
|
-
|
|
9551
|
+
chmodSync5(hookPath, 493);
|
|
8704
9552
|
} catch {
|
|
8705
9553
|
}
|
|
8706
9554
|
return "updated";
|
|
@@ -8726,7 +9574,7 @@ function detectGitRepo2() {
|
|
|
8726
9574
|
function getClaudeProjectsFolder() {
|
|
8727
9575
|
const cwd = process.cwd();
|
|
8728
9576
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
8729
|
-
const projectsDir =
|
|
9577
|
+
const projectsDir = join19(homedir21(), ".claude", "projects", sanitized);
|
|
8730
9578
|
return existsSync22(projectsDir) ? projectsDir : null;
|
|
8731
9579
|
}
|
|
8732
9580
|
function extractSessionInsights(projectsDir) {
|
|
@@ -8734,9 +9582,9 @@ function extractSessionInsights(projectsDir) {
|
|
|
8734
9582
|
const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
8735
9583
|
for (const file of files) {
|
|
8736
9584
|
const sessionId = file.replace(".jsonl", "");
|
|
8737
|
-
const filePath =
|
|
9585
|
+
const filePath = join19(projectsDir, file);
|
|
8738
9586
|
try {
|
|
8739
|
-
const content =
|
|
9587
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8740
9588
|
const lines = content.split("\n").filter(Boolean);
|
|
8741
9589
|
for (let i = 0; i < lines.length; i++) {
|
|
8742
9590
|
try {
|
|
@@ -8812,7 +9660,7 @@ function extractTextContent(content) {
|
|
|
8812
9660
|
return "";
|
|
8813
9661
|
}
|
|
8814
9662
|
function getCodexTranscriptFiles(repo) {
|
|
8815
|
-
const sessionsDir =
|
|
9663
|
+
const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
|
|
8816
9664
|
if (!existsSync22(sessionsDir)) return [];
|
|
8817
9665
|
let relative = [];
|
|
8818
9666
|
try {
|
|
@@ -8820,9 +9668,9 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8820
9668
|
} catch {
|
|
8821
9669
|
return [];
|
|
8822
9670
|
}
|
|
8823
|
-
return relative.filter((p) => p.endsWith(".jsonl")).map((p) =>
|
|
9671
|
+
return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
|
|
8824
9672
|
try {
|
|
8825
|
-
const first =
|
|
9673
|
+
const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
|
|
8826
9674
|
const meta = JSON.parse(first);
|
|
8827
9675
|
const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
|
|
8828
9676
|
const root = resolve4(repo);
|
|
@@ -8832,38 +9680,45 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8832
9680
|
}
|
|
8833
9681
|
});
|
|
8834
9682
|
}
|
|
9683
|
+
function isJsonSyntaxError(error) {
|
|
9684
|
+
return error instanceof SyntaxError;
|
|
9685
|
+
}
|
|
8835
9686
|
function parseCodexTranscriptFile(filePath) {
|
|
8836
|
-
const
|
|
9687
|
+
const transcript = readFileSync21(filePath, "utf-8");
|
|
9688
|
+
const lines = transcript.split("\n");
|
|
9689
|
+
const transcriptUsage = parseCodexTranscriptUsage(transcript);
|
|
8837
9690
|
let sessionId = "";
|
|
8838
9691
|
let model = "";
|
|
8839
|
-
const
|
|
8840
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9692
|
+
for (const line of lines) {
|
|
8841
9693
|
try {
|
|
8842
|
-
const entry = JSON.parse(
|
|
9694
|
+
const entry = JSON.parse(line);
|
|
8843
9695
|
if (entry.type === "session_meta") {
|
|
8844
9696
|
sessionId = String(entry.payload?.session_id || entry.payload?.id || sessionId);
|
|
8845
|
-
|
|
8846
|
-
}
|
|
8847
|
-
if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
9697
|
+
} else if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
8848
9698
|
model = entry.payload.model;
|
|
8849
|
-
continue;
|
|
8850
9699
|
}
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
if (eventType !== "user_message" && eventType !== "agent_message") continue;
|
|
8854
|
-
const text = String(entry.payload?.message || "").slice(0, 8e3);
|
|
8855
|
-
if (!text) continue;
|
|
8856
|
-
const type = eventType === "user_message" ? "user" : "assistant";
|
|
8857
|
-
messages.push({
|
|
8858
|
-
message_index: i,
|
|
8859
|
-
type,
|
|
8860
|
-
content: text,
|
|
8861
|
-
...type === "assistant" && model ? { model } : {}
|
|
8862
|
-
});
|
|
8863
|
-
} catch {
|
|
9700
|
+
} catch (error) {
|
|
9701
|
+
if (!isJsonSyntaxError(error)) throw error;
|
|
8864
9702
|
}
|
|
8865
9703
|
}
|
|
8866
|
-
|
|
9704
|
+
const messages = parseCodexConversationMessages(transcript).map((message) => {
|
|
9705
|
+
const turnUsage = message.role === "assistant" ? transcriptUsage?.turnsByLine.get(message.lineIndex) : void 0;
|
|
9706
|
+
return {
|
|
9707
|
+
message_index: message.lineIndex,
|
|
9708
|
+
uuid: message.uuid,
|
|
9709
|
+
type: message.role,
|
|
9710
|
+
content: message.content,
|
|
9711
|
+
...message.role === "assistant" && (turnUsage?.model || model) ? { model: turnUsage?.model || model } : {},
|
|
9712
|
+
...turnUsage ? { usage: turnUsage.usage } : {},
|
|
9713
|
+
...message.timestamp ? { timestamp: message.timestamp } : {}
|
|
9714
|
+
};
|
|
9715
|
+
});
|
|
9716
|
+
return {
|
|
9717
|
+
sessionId,
|
|
9718
|
+
messages,
|
|
9719
|
+
model: transcriptUsage?.model || model,
|
|
9720
|
+
usage: transcriptUsage?.total
|
|
9721
|
+
};
|
|
8867
9722
|
}
|
|
8868
9723
|
async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
8869
9724
|
const files = getCodexTranscriptFiles(repo);
|
|
@@ -8880,7 +9735,15 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8880
9735
|
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
|
|
8881
9736
|
method: "POST",
|
|
8882
9737
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
|
|
8883
|
-
body: JSON.stringify({
|
|
9738
|
+
body: JSON.stringify({
|
|
9739
|
+
session_id: parsed.sessionId,
|
|
9740
|
+
repo,
|
|
9741
|
+
messages,
|
|
9742
|
+
session_usage: parsed.usage,
|
|
9743
|
+
model: parsed.model,
|
|
9744
|
+
harness: "codex",
|
|
9745
|
+
usage_cumulative: true
|
|
9746
|
+
}),
|
|
8884
9747
|
signal: AbortSignal.timeout(15e3)
|
|
8885
9748
|
});
|
|
8886
9749
|
if (resp.ok) {
|
|
@@ -8888,7 +9751,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8888
9751
|
totalSessions++;
|
|
8889
9752
|
totalMessages += result.ingested ?? messages.length;
|
|
8890
9753
|
}
|
|
8891
|
-
|
|
9754
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
8892
9755
|
} catch {
|
|
8893
9756
|
}
|
|
8894
9757
|
if ((i + 1) % 10 === 0 || i === files.length - 1) {
|
|
@@ -8902,14 +9765,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
8902
9765
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8903
9766
|
}
|
|
8904
9767
|
function getCursorTranscriptsDir() {
|
|
8905
|
-
const dir =
|
|
9768
|
+
const dir = join19(homedir21(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
8906
9769
|
return existsSync22(dir) ? dir : null;
|
|
8907
9770
|
}
|
|
8908
9771
|
function isSafeConvId(id) {
|
|
8909
9772
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
8910
9773
|
}
|
|
8911
9774
|
function parseCursorTranscriptFile(filePath) {
|
|
8912
|
-
const content =
|
|
9775
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8913
9776
|
const lines = content.split("\n").filter(Boolean);
|
|
8914
9777
|
const messages = [];
|
|
8915
9778
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8941,7 +9804,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8941
9804
|
for (let i = 0; i < convDirs.length; i++) {
|
|
8942
9805
|
const convId = convDirs[i];
|
|
8943
9806
|
if (!isSafeConvId(convId)) continue;
|
|
8944
|
-
const filePath =
|
|
9807
|
+
const filePath = join19(dir, convId, `${convId}.jsonl`);
|
|
8945
9808
|
if (!existsSync22(filePath)) continue;
|
|
8946
9809
|
try {
|
|
8947
9810
|
const all = parseCursorTranscriptFile(filePath);
|
|
@@ -8964,8 +9827,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8964
9827
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
8965
9828
|
}
|
|
8966
9829
|
try {
|
|
8967
|
-
const lc =
|
|
8968
|
-
|
|
9830
|
+
const lc = readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
9831
|
+
writeFileSync17(join19(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
8969
9832
|
} catch {
|
|
8970
9833
|
}
|
|
8971
9834
|
}
|
|
@@ -8973,7 +9836,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8973
9836
|
return { sessions: totalSessions, messages: totalMessages };
|
|
8974
9837
|
}
|
|
8975
9838
|
function parseTranscriptFile(filePath) {
|
|
8976
|
-
const content =
|
|
9839
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8977
9840
|
const lines = content.split("\n").filter(Boolean);
|
|
8978
9841
|
const messages = [];
|
|
8979
9842
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9021,7 +9884,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9021
9884
|
for (let i = 0; i < files.length; i++) {
|
|
9022
9885
|
const file = files[i];
|
|
9023
9886
|
const sessionId = file.replace(".jsonl", "");
|
|
9024
|
-
const filePath =
|
|
9887
|
+
const filePath = join19(projectsDir, file);
|
|
9025
9888
|
try {
|
|
9026
9889
|
const allMessages = parseTranscriptFile(filePath);
|
|
9027
9890
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -9043,9 +9906,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9043
9906
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
9044
9907
|
}
|
|
9045
9908
|
try {
|
|
9046
|
-
const content =
|
|
9909
|
+
const content = readFileSync21(join19(projectsDir, file), "utf-8");
|
|
9047
9910
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9048
|
-
|
|
9911
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9049
9912
|
} catch {
|
|
9050
9913
|
}
|
|
9051
9914
|
}
|
|
@@ -9066,7 +9929,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9066
9929
|
const sessions = [];
|
|
9067
9930
|
for (const file of batch) {
|
|
9068
9931
|
const sessionId = file.replace(".jsonl", "");
|
|
9069
|
-
const filePath =
|
|
9932
|
+
const filePath = join19(projectsDir, file);
|
|
9070
9933
|
try {
|
|
9071
9934
|
const allMessages = parseTranscriptFile(filePath);
|
|
9072
9935
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -9095,11 +9958,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9095
9958
|
}
|
|
9096
9959
|
for (const file of batch) {
|
|
9097
9960
|
const sessionId = file.replace(".jsonl", "");
|
|
9098
|
-
const filePath =
|
|
9961
|
+
const filePath = join19(projectsDir, file);
|
|
9099
9962
|
try {
|
|
9100
|
-
const content =
|
|
9963
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
9101
9964
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9102
|
-
|
|
9965
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9103
9966
|
} catch {
|
|
9104
9967
|
}
|
|
9105
9968
|
}
|
|
@@ -9117,7 +9980,16 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9117
9980
|
try {
|
|
9118
9981
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9119
9982
|
const messages = parsed.messages.length > 500 ? parsed.messages.slice(-500) : parsed.messages;
|
|
9120
|
-
if (parsed.sessionId && messages.length > 0)
|
|
9983
|
+
if (parsed.sessionId && messages.length > 0) {
|
|
9984
|
+
sessions.push({
|
|
9985
|
+
cc_session_id: parsed.sessionId,
|
|
9986
|
+
messages,
|
|
9987
|
+
model: parsed.model,
|
|
9988
|
+
session_usage: parsed.usage,
|
|
9989
|
+
harness: "codex",
|
|
9990
|
+
usage_cumulative: true
|
|
9991
|
+
});
|
|
9992
|
+
}
|
|
9121
9993
|
} catch {
|
|
9122
9994
|
}
|
|
9123
9995
|
}
|
|
@@ -9139,7 +10011,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9139
10011
|
try {
|
|
9140
10012
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9141
10013
|
if (parsed.sessionId) {
|
|
9142
|
-
|
|
10014
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
9143
10015
|
}
|
|
9144
10016
|
} catch {
|
|
9145
10017
|
}
|
|
@@ -9169,10 +10041,13 @@ var init_install = __esm({
|
|
|
9169
10041
|
init_setupToken();
|
|
9170
10042
|
init_codexCloudSetup();
|
|
9171
10043
|
init_ptyShim();
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
10044
|
+
init_graderSmoke();
|
|
10045
|
+
init_codexTranscriptUsage();
|
|
10046
|
+
init_codexTranscriptMessages();
|
|
10047
|
+
SYNKRO_DIR11 = join19(homedir21(), ".synkro");
|
|
10048
|
+
HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
|
|
10049
|
+
BIN_DIR = join19(SYNKRO_DIR11, "bin");
|
|
10050
|
+
CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
|
|
9176
10051
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
9177
10052
|
import { readFileSync } from 'node:fs';
|
|
9178
10053
|
import { homedir } from 'node:os';
|
|
@@ -9283,23 +10158,23 @@ rl.on('line', async (line) => {
|
|
|
9283
10158
|
}
|
|
9284
10159
|
});
|
|
9285
10160
|
`;
|
|
9286
|
-
OFFSETS_DIR =
|
|
9287
|
-
CLOUD_JWT_PATH =
|
|
9288
|
-
SKILLS_DISCOVERED_PATH =
|
|
10161
|
+
OFFSETS_DIR = join19(SYNKRO_DIR11, ".transcript-offsets");
|
|
10162
|
+
CLOUD_JWT_PATH = join19(SYNKRO_DIR11, ".cloud-jwt");
|
|
10163
|
+
SKILLS_DISCOVERED_PATH = join19(SYNKRO_DIR11, ".skills-discovered");
|
|
9289
10164
|
}
|
|
9290
10165
|
});
|
|
9291
10166
|
|
|
9292
10167
|
// cli/local-cc/install.ts
|
|
9293
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as
|
|
9294
|
-
import { join as
|
|
9295
|
-
import { homedir as
|
|
10168
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18, readFileSync as readFileSync22, chmodSync as chmodSync6, copyFileSync as copyFileSync2, renameSync as renameSync8, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
|
|
10169
|
+
import { join as join20 } from "path";
|
|
10170
|
+
import { homedir as homedir22 } from "os";
|
|
9296
10171
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
9297
10172
|
function writePluginFiles() {
|
|
9298
10173
|
for (const c of CHANNELS) {
|
|
9299
10174
|
mkdirSync16(c.sessionDir, { recursive: true });
|
|
9300
10175
|
mkdirSync16(c.pluginSettingsDir, { recursive: true });
|
|
9301
|
-
|
|
9302
|
-
|
|
10176
|
+
writeFileSync18(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
10177
|
+
writeFileSync18(
|
|
9303
10178
|
c.pluginSettingsPath,
|
|
9304
10179
|
JSON.stringify({
|
|
9305
10180
|
fastMode: true,
|
|
@@ -9314,8 +10189,8 @@ function writePluginFiles() {
|
|
|
9314
10189
|
}, null, 2) + "\n",
|
|
9315
10190
|
"utf-8"
|
|
9316
10191
|
);
|
|
9317
|
-
|
|
9318
|
-
|
|
10192
|
+
writeFileSync18(c.runScriptPath, c.runScriptSource, "utf-8");
|
|
10193
|
+
chmodSync6(c.runScriptPath, 493);
|
|
9319
10194
|
}
|
|
9320
10195
|
}
|
|
9321
10196
|
function runBunInstall() {
|
|
@@ -9336,7 +10211,7 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9336
10211
|
if (!existsSync23(CLAUDE_JSON_PATH)) {
|
|
9337
10212
|
return;
|
|
9338
10213
|
}
|
|
9339
|
-
const originalText =
|
|
10214
|
+
const originalText = readFileSync22(CLAUDE_JSON_PATH, "utf-8");
|
|
9340
10215
|
let parsed;
|
|
9341
10216
|
try {
|
|
9342
10217
|
parsed = JSON.parse(originalText);
|
|
@@ -9368,14 +10243,14 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9368
10243
|
copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
9369
10244
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
9370
10245
|
try {
|
|
9371
|
-
|
|
10246
|
+
writeFileSync18(tmpPath, newText, "utf-8");
|
|
9372
10247
|
const fd = openSync2(tmpPath, "r");
|
|
9373
10248
|
try {
|
|
9374
10249
|
fsyncSync(fd);
|
|
9375
10250
|
} finally {
|
|
9376
10251
|
closeSync2(fd);
|
|
9377
10252
|
}
|
|
9378
|
-
|
|
10253
|
+
renameSync8(tmpPath, CLAUDE_JSON_PATH);
|
|
9379
10254
|
} catch (err) {
|
|
9380
10255
|
try {
|
|
9381
10256
|
unlinkSync8(tmpPath);
|
|
@@ -9401,7 +10276,7 @@ function writeProjectMcpJson() {
|
|
|
9401
10276
|
}
|
|
9402
10277
|
}
|
|
9403
10278
|
};
|
|
9404
|
-
|
|
10279
|
+
writeFileSync18(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
9405
10280
|
}
|
|
9406
10281
|
}
|
|
9407
10282
|
function patchClaudeJson() {
|
|
@@ -9478,42 +10353,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
9478
10353
|
var init_install2 = __esm({
|
|
9479
10354
|
"cli/local-cc/install.ts"() {
|
|
9480
10355
|
"use strict";
|
|
9481
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
9482
|
-
SESSION_DIR =
|
|
9483
|
-
PLUGIN_PATH =
|
|
9484
|
-
PLUGIN_PKG_PATH =
|
|
9485
|
-
PLUGIN_SETTINGS_DIR =
|
|
9486
|
-
PLUGIN_SETTINGS_PATH =
|
|
9487
|
-
PROJECT_MCP_PATH =
|
|
9488
|
-
CLAUDE_JSON_PATH =
|
|
9489
|
-
RUN_SCRIPT_PATH =
|
|
10356
|
+
CLAUDE_JSON_BACKUP_PATH = join20(homedir22(), ".claude.json.synkro-bak");
|
|
10357
|
+
SESSION_DIR = join20(homedir22(), ".synkro", "cc_sessions");
|
|
10358
|
+
PLUGIN_PATH = join20(SESSION_DIR, "synkro-channel.ts");
|
|
10359
|
+
PLUGIN_PKG_PATH = join20(SESSION_DIR, "package.json");
|
|
10360
|
+
PLUGIN_SETTINGS_DIR = join20(SESSION_DIR, ".claude");
|
|
10361
|
+
PLUGIN_SETTINGS_PATH = join20(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
10362
|
+
PROJECT_MCP_PATH = join20(SESSION_DIR, ".mcp.json");
|
|
10363
|
+
CLAUDE_JSON_PATH = join20(homedir22(), ".claude.json");
|
|
10364
|
+
RUN_SCRIPT_PATH = join20(SESSION_DIR, "run-claude.sh");
|
|
9490
10365
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
9491
10366
|
CHANNEL_1_PORT = 8941;
|
|
9492
|
-
SESSION_DIR_2 =
|
|
9493
|
-
PLUGIN_PATH_2 =
|
|
9494
|
-
PLUGIN_PKG_PATH_2 =
|
|
9495
|
-
PLUGIN_SETTINGS_DIR_2 =
|
|
9496
|
-
PLUGIN_SETTINGS_PATH_2 =
|
|
9497
|
-
PROJECT_MCP_PATH_2 =
|
|
9498
|
-
RUN_SCRIPT_PATH_2 =
|
|
10367
|
+
SESSION_DIR_2 = join20(homedir22(), ".synkro", "cc_sessions_2");
|
|
10368
|
+
PLUGIN_PATH_2 = join20(SESSION_DIR_2, "synkro-channel.ts");
|
|
10369
|
+
PLUGIN_PKG_PATH_2 = join20(SESSION_DIR_2, "package.json");
|
|
10370
|
+
PLUGIN_SETTINGS_DIR_2 = join20(SESSION_DIR_2, ".claude");
|
|
10371
|
+
PLUGIN_SETTINGS_PATH_2 = join20(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
10372
|
+
PROJECT_MCP_PATH_2 = join20(SESSION_DIR_2, ".mcp.json");
|
|
10373
|
+
RUN_SCRIPT_PATH_2 = join20(SESSION_DIR_2, "run-claude.sh");
|
|
9499
10374
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
9500
10375
|
CHANNEL_2_PORT = 8951;
|
|
9501
|
-
SESSION_DIR_3 =
|
|
9502
|
-
PLUGIN_PATH_3 =
|
|
9503
|
-
PLUGIN_PKG_PATH_3 =
|
|
9504
|
-
PLUGIN_SETTINGS_DIR_3 =
|
|
9505
|
-
PLUGIN_SETTINGS_PATH_3 =
|
|
9506
|
-
PROJECT_MCP_PATH_3 =
|
|
9507
|
-
RUN_SCRIPT_PATH_3 =
|
|
10376
|
+
SESSION_DIR_3 = join20(homedir22(), ".synkro", "cc_sessions_3");
|
|
10377
|
+
PLUGIN_PATH_3 = join20(SESSION_DIR_3, "synkro-channel.ts");
|
|
10378
|
+
PLUGIN_PKG_PATH_3 = join20(SESSION_DIR_3, "package.json");
|
|
10379
|
+
PLUGIN_SETTINGS_DIR_3 = join20(SESSION_DIR_3, ".claude");
|
|
10380
|
+
PLUGIN_SETTINGS_PATH_3 = join20(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
10381
|
+
PROJECT_MCP_PATH_3 = join20(SESSION_DIR_3, ".mcp.json");
|
|
10382
|
+
RUN_SCRIPT_PATH_3 = join20(SESSION_DIR_3, "run-claude.sh");
|
|
9508
10383
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
9509
10384
|
CHANNEL_3_PORT = 8942;
|
|
9510
|
-
SESSION_DIR_4 =
|
|
9511
|
-
PLUGIN_PATH_4 =
|
|
9512
|
-
PLUGIN_PKG_PATH_4 =
|
|
9513
|
-
PLUGIN_SETTINGS_DIR_4 =
|
|
9514
|
-
PLUGIN_SETTINGS_PATH_4 =
|
|
9515
|
-
PROJECT_MCP_PATH_4 =
|
|
9516
|
-
RUN_SCRIPT_PATH_4 =
|
|
10385
|
+
SESSION_DIR_4 = join20(homedir22(), ".synkro", "cc_sessions_4");
|
|
10386
|
+
PLUGIN_PATH_4 = join20(SESSION_DIR_4, "synkro-channel.ts");
|
|
10387
|
+
PLUGIN_PKG_PATH_4 = join20(SESSION_DIR_4, "package.json");
|
|
10388
|
+
PLUGIN_SETTINGS_DIR_4 = join20(SESSION_DIR_4, ".claude");
|
|
10389
|
+
PLUGIN_SETTINGS_PATH_4 = join20(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
10390
|
+
PROJECT_MCP_PATH_4 = join20(SESSION_DIR_4, ".mcp.json");
|
|
10391
|
+
RUN_SCRIPT_PATH_4 = join20(SESSION_DIR_4, "run-claude.sh");
|
|
9517
10392
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
9518
10393
|
CHANNEL_4_PORT = 8952;
|
|
9519
10394
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -9788,8 +10663,8 @@ __export(disconnect_exports, {
|
|
|
9788
10663
|
disconnectCommand: () => disconnectCommand
|
|
9789
10664
|
});
|
|
9790
10665
|
import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
|
|
9791
|
-
import { homedir as
|
|
9792
|
-
import { join as
|
|
10666
|
+
import { homedir as homedir23 } from "os";
|
|
10667
|
+
import { join as join21 } from "path";
|
|
9793
10668
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
9794
10669
|
import { createInterface as createInterface3 } from "readline";
|
|
9795
10670
|
async function tearDownLocalCC() {
|
|
@@ -9904,13 +10779,13 @@ async function disconnectCommand(args2 = [], opts = {}) {
|
|
|
9904
10779
|
console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
|
|
9905
10780
|
} else {
|
|
9906
10781
|
const keep = /* @__PURE__ */ new Set([
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
10782
|
+
join21(SYNKRO_DIR12, "pgdata"),
|
|
10783
|
+
join21(SYNKRO_DIR12, "pgdata-backups"),
|
|
10784
|
+
join21(SYNKRO_DIR12, ".transcript-offsets")
|
|
9910
10785
|
]);
|
|
9911
10786
|
const preserved = [];
|
|
9912
10787
|
for (const entry of readdirSync5(SYNKRO_DIR12)) {
|
|
9913
|
-
const full =
|
|
10788
|
+
const full = join21(SYNKRO_DIR12, entry);
|
|
9914
10789
|
if (keep.has(full)) {
|
|
9915
10790
|
preserved.push(entry);
|
|
9916
10791
|
continue;
|
|
@@ -9948,14 +10823,14 @@ var init_disconnect = __esm({
|
|
|
9948
10823
|
init_dockerInstall();
|
|
9949
10824
|
init_macKeychain();
|
|
9950
10825
|
init_telemetry();
|
|
9951
|
-
SYNKRO_DIR12 =
|
|
10826
|
+
SYNKRO_DIR12 = join21(homedir23(), ".synkro");
|
|
9952
10827
|
}
|
|
9953
10828
|
});
|
|
9954
10829
|
|
|
9955
10830
|
// cli/local-cc/turnLog.ts
|
|
9956
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as
|
|
9957
|
-
import { dirname as dirname7, join as
|
|
9958
|
-
import { homedir as
|
|
10831
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync23, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
|
|
10832
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
10833
|
+
import { homedir as homedir24 } from "os";
|
|
9959
10834
|
function truncate(s, max = PREVIEW_MAX) {
|
|
9960
10835
|
if (s.length <= max) return s;
|
|
9961
10836
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -9995,7 +10870,7 @@ function readRecentTurns(n = 20) {
|
|
|
9995
10870
|
try {
|
|
9996
10871
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
9997
10872
|
if (size === 0) return [];
|
|
9998
|
-
const text =
|
|
10873
|
+
const text = readFileSync23(TURN_LOG_PATH, "utf-8");
|
|
9999
10874
|
const lines = text.split("\n").filter(Boolean);
|
|
10000
10875
|
const lastN = lines.slice(-n).reverse();
|
|
10001
10876
|
return lastN.map((line) => {
|
|
@@ -10074,7 +10949,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
10074
10949
|
var init_turnLog = __esm({
|
|
10075
10950
|
"cli/local-cc/turnLog.ts"() {
|
|
10076
10951
|
"use strict";
|
|
10077
|
-
TURN_LOG_PATH =
|
|
10952
|
+
TURN_LOG_PATH = join22(homedir24(), ".synkro", "cc_sessions", "turns.log");
|
|
10078
10953
|
PREVIEW_MAX = 400;
|
|
10079
10954
|
}
|
|
10080
10955
|
});
|
|
@@ -10228,8 +11103,8 @@ __export(scanPr_exports, {
|
|
|
10228
11103
|
scanPrCommand: () => scanPrCommand
|
|
10229
11104
|
});
|
|
10230
11105
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
10231
|
-
import { readFileSync as
|
|
10232
|
-
import { join as
|
|
11106
|
+
import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
|
|
11107
|
+
import { join as join23 } from "path";
|
|
10233
11108
|
function parseMatchSpec(condition) {
|
|
10234
11109
|
if (!condition.startsWith("match_spec:")) return null;
|
|
10235
11110
|
try {
|
|
@@ -10708,10 +11583,10 @@ function shouldFail(findings, threshold) {
|
|
|
10708
11583
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
10709
11584
|
}
|
|
10710
11585
|
function readRepoDeps() {
|
|
10711
|
-
const pkgPath =
|
|
11586
|
+
const pkgPath = join23(process.cwd(), "package.json");
|
|
10712
11587
|
if (!existsSync26(pkgPath)) return {};
|
|
10713
11588
|
try {
|
|
10714
|
-
const pkg = JSON.parse(
|
|
11589
|
+
const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
|
|
10715
11590
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
10716
11591
|
} catch {
|
|
10717
11592
|
return {};
|
|
@@ -10955,15 +11830,15 @@ var routeDecide_exports = {};
|
|
|
10955
11830
|
__export(routeDecide_exports, {
|
|
10956
11831
|
routeDecide: () => routeDecide
|
|
10957
11832
|
});
|
|
10958
|
-
import { readFileSync as
|
|
10959
|
-
import { homedir as
|
|
10960
|
-
import { join as
|
|
11833
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
11834
|
+
import { homedir as homedir25 } from "os";
|
|
11835
|
+
import { join as join24 } from "path";
|
|
10961
11836
|
function safeSid(sid) {
|
|
10962
11837
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
10963
11838
|
}
|
|
10964
11839
|
function loadMcpJwt() {
|
|
10965
11840
|
try {
|
|
10966
|
-
return
|
|
11841
|
+
return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
|
|
10967
11842
|
} catch {
|
|
10968
11843
|
return "";
|
|
10969
11844
|
}
|
|
@@ -10973,7 +11848,7 @@ async function routeDecide(sessionId) {
|
|
|
10973
11848
|
const sid = safeSid(sessionId);
|
|
10974
11849
|
let prompt = "";
|
|
10975
11850
|
try {
|
|
10976
|
-
const rec = JSON.parse(
|
|
11851
|
+
const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
|
|
10977
11852
|
prompt = String(rec.last_prompt || "").trim();
|
|
10978
11853
|
} catch {
|
|
10979
11854
|
return;
|
|
@@ -10984,7 +11859,7 @@ async function routeDecide(sessionId) {
|
|
|
10984
11859
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
|
|
10985
11860
|
method: "POST",
|
|
10986
11861
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
|
|
10987
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
11862
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
10988
11863
|
// First call may spin up the route lane (a haiku worker boot), so allow headroom.
|
|
10989
11864
|
signal: AbortSignal.timeout(12e4)
|
|
10990
11865
|
});
|
|
@@ -10996,19 +11871,19 @@ async function routeDecide(sessionId) {
|
|
|
10996
11871
|
return;
|
|
10997
11872
|
}
|
|
10998
11873
|
if (!model || !VALID_MODELS.has(model)) return;
|
|
10999
|
-
const lastFile =
|
|
11874
|
+
const lastFile = join24(PTY_DIR, "route-last-" + sid);
|
|
11000
11875
|
let last = "";
|
|
11001
11876
|
try {
|
|
11002
|
-
last =
|
|
11877
|
+
last = readFileSync25(lastFile, "utf-8").trim();
|
|
11003
11878
|
} catch {
|
|
11004
11879
|
}
|
|
11005
11880
|
if (model === last) return;
|
|
11006
11881
|
try {
|
|
11007
|
-
|
|
11882
|
+
writeFileSync19(lastFile, model);
|
|
11008
11883
|
} catch {
|
|
11009
11884
|
}
|
|
11010
11885
|
try {
|
|
11011
|
-
|
|
11886
|
+
writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
|
|
11012
11887
|
} catch {
|
|
11013
11888
|
}
|
|
11014
11889
|
}
|
|
@@ -11016,9 +11891,9 @@ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
|
|
|
11016
11891
|
var init_routeDecide = __esm({
|
|
11017
11892
|
"cli/local-cc/routeDecide.ts"() {
|
|
11018
11893
|
"use strict";
|
|
11019
|
-
SYNKRO_DIR13 =
|
|
11020
|
-
PTY_DIR =
|
|
11021
|
-
SESSIONS_DIR2 =
|
|
11894
|
+
SYNKRO_DIR13 = join24(homedir25(), ".synkro");
|
|
11895
|
+
PTY_DIR = join24(SYNKRO_DIR13, "pty");
|
|
11896
|
+
SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
|
|
11022
11897
|
VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11023
11898
|
GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11024
11899
|
}
|
|
@@ -11029,9 +11904,9 @@ var routeOrchestrate_exports = {};
|
|
|
11029
11904
|
__export(routeOrchestrate_exports, {
|
|
11030
11905
|
routeAndResubmit: () => routeAndResubmit
|
|
11031
11906
|
});
|
|
11032
|
-
import { readFileSync as
|
|
11033
|
-
import { homedir as
|
|
11034
|
-
import { join as
|
|
11907
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
11908
|
+
import { homedir as homedir26 } from "os";
|
|
11909
|
+
import { join as join25 } from "path";
|
|
11035
11910
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
11036
11911
|
function safeSid2(sid) {
|
|
11037
11912
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -11041,7 +11916,7 @@ function safeSession(s) {
|
|
|
11041
11916
|
}
|
|
11042
11917
|
function loadMcpJwt2() {
|
|
11043
11918
|
try {
|
|
11044
|
-
return
|
|
11919
|
+
return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
11045
11920
|
} catch {
|
|
11046
11921
|
return "";
|
|
11047
11922
|
}
|
|
@@ -11051,7 +11926,7 @@ async function classifyTask(prompt) {
|
|
|
11051
11926
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
|
|
11052
11927
|
method: "POST",
|
|
11053
11928
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
|
|
11054
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
11929
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
11055
11930
|
signal: AbortSignal.timeout(6e4)
|
|
11056
11931
|
});
|
|
11057
11932
|
if (!resp.ok) return null;
|
|
@@ -11067,7 +11942,7 @@ async function classifyTask(prompt) {
|
|
|
11067
11942
|
}
|
|
11068
11943
|
function lastRoutedModel(sid) {
|
|
11069
11944
|
try {
|
|
11070
|
-
const v =
|
|
11945
|
+
const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
|
|
11071
11946
|
return VALID_MODELS2.has(v) ? v : "";
|
|
11072
11947
|
} catch {
|
|
11073
11948
|
return "";
|
|
@@ -11077,12 +11952,12 @@ function resolveSession(sid, tmuxSession) {
|
|
|
11077
11952
|
const candidates = [];
|
|
11078
11953
|
if (tmuxSession) candidates.push(tmuxSession);
|
|
11079
11954
|
try {
|
|
11080
|
-
const rec = JSON.parse(
|
|
11955
|
+
const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
|
|
11081
11956
|
if (rec.tmux_session) candidates.push(rec.tmux_session);
|
|
11082
11957
|
} catch {
|
|
11083
11958
|
}
|
|
11084
11959
|
try {
|
|
11085
|
-
candidates.push(
|
|
11960
|
+
candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
|
|
11086
11961
|
} catch {
|
|
11087
11962
|
}
|
|
11088
11963
|
for (const c of candidates) if (c && safeSession(c)) return c;
|
|
@@ -11117,12 +11992,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
|
|
|
11117
11992
|
}
|
|
11118
11993
|
await wait(500);
|
|
11119
11994
|
try {
|
|
11120
|
-
|
|
11995
|
+
writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
|
|
11121
11996
|
} catch {
|
|
11122
11997
|
}
|
|
11123
11998
|
}
|
|
11124
11999
|
try {
|
|
11125
|
-
|
|
12000
|
+
writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
|
|
11126
12001
|
} catch {
|
|
11127
12002
|
}
|
|
11128
12003
|
sk("C-u");
|
|
@@ -11136,10 +12011,10 @@ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2,
|
|
|
11136
12011
|
var init_routeOrchestrate = __esm({
|
|
11137
12012
|
"cli/local-cc/routeOrchestrate.ts"() {
|
|
11138
12013
|
"use strict";
|
|
11139
|
-
SYNKRO_DIR14 =
|
|
11140
|
-
PTY_DIR2 =
|
|
11141
|
-
SESSIONS_DIR3 =
|
|
11142
|
-
ACTIVE_SESSION_FILE2 =
|
|
12014
|
+
SYNKRO_DIR14 = join25(homedir26(), ".synkro");
|
|
12015
|
+
PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
|
|
12016
|
+
SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
|
|
12017
|
+
ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
|
|
11143
12018
|
VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11144
12019
|
GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11145
12020
|
wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -11151,14 +12026,14 @@ var routingToggle_exports = {};
|
|
|
11151
12026
|
__export(routingToggle_exports, {
|
|
11152
12027
|
routingCommand: () => routingCommand
|
|
11153
12028
|
});
|
|
11154
|
-
import { writeFileSync as
|
|
11155
|
-
import { homedir as
|
|
11156
|
-
import { join as
|
|
12029
|
+
import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
|
|
12030
|
+
import { homedir as homedir27 } from "os";
|
|
12031
|
+
import { join as join26 } from "path";
|
|
11157
12032
|
function safeSid3(sid) {
|
|
11158
12033
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
11159
12034
|
}
|
|
11160
12035
|
function markerFor(session) {
|
|
11161
|
-
return session ?
|
|
12036
|
+
return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
|
|
11162
12037
|
}
|
|
11163
12038
|
function routingCommand(args2) {
|
|
11164
12039
|
const sub = (args2[0] || "status").trim();
|
|
@@ -11167,7 +12042,7 @@ function routingCommand(args2) {
|
|
|
11167
12042
|
if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
|
|
11168
12043
|
if (sub === "on") {
|
|
11169
12044
|
try {
|
|
11170
|
-
|
|
12045
|
+
writeFileSync21(markerFor(session), "1");
|
|
11171
12046
|
} catch (e) {
|
|
11172
12047
|
console.error("routing on failed:", String(e));
|
|
11173
12048
|
return;
|
|
@@ -11190,7 +12065,7 @@ function routingCommand(args2) {
|
|
|
11190
12065
|
for (const f of safeList()) {
|
|
11191
12066
|
if (f === "routing-on" || f.startsWith("routing-on-")) {
|
|
11192
12067
|
try {
|
|
11193
|
-
unlinkSync9(
|
|
12068
|
+
unlinkSync9(join26(PTY_DIR3, f));
|
|
11194
12069
|
removed++;
|
|
11195
12070
|
} catch {
|
|
11196
12071
|
}
|
|
@@ -11222,14 +12097,14 @@ var PTY_DIR3;
|
|
|
11222
12097
|
var init_routingToggle = __esm({
|
|
11223
12098
|
"cli/local-cc/routingToggle.ts"() {
|
|
11224
12099
|
"use strict";
|
|
11225
|
-
PTY_DIR3 =
|
|
12100
|
+
PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
|
|
11226
12101
|
}
|
|
11227
12102
|
});
|
|
11228
12103
|
|
|
11229
12104
|
// cli/local-cc/pueue.ts
|
|
11230
12105
|
import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
|
|
11231
|
-
import { homedir as
|
|
11232
|
-
import { join as
|
|
12106
|
+
import { homedir as homedir28 } from "os";
|
|
12107
|
+
import { join as join27 } from "path";
|
|
11233
12108
|
import { connect as connect2 } from "net";
|
|
11234
12109
|
function pueueAvailable() {
|
|
11235
12110
|
const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
@@ -11295,7 +12170,7 @@ function startTask(opts = {}) {
|
|
|
11295
12170
|
spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
11296
12171
|
existing = findTask(ch);
|
|
11297
12172
|
}
|
|
11298
|
-
const runScript =
|
|
12173
|
+
const runScript = join27(cwd, "run-claude.sh");
|
|
11299
12174
|
const args2 = [
|
|
11300
12175
|
"add",
|
|
11301
12176
|
"--label",
|
|
@@ -11425,12 +12300,12 @@ var init_pueue = __esm({
|
|
|
11425
12300
|
"use strict";
|
|
11426
12301
|
TASK_LABEL = "synkro-local-cc";
|
|
11427
12302
|
TMUX_SESSION = "synkro-local-cc";
|
|
11428
|
-
SESSION_DIR2 =
|
|
12303
|
+
SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
|
|
11429
12304
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
11430
12305
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
11431
|
-
SESSION_DIR_22 =
|
|
11432
|
-
SESSION_DIR_32 =
|
|
11433
|
-
SESSION_DIR_42 =
|
|
12306
|
+
SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
|
|
12307
|
+
SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
|
|
12308
|
+
SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
|
|
11434
12309
|
PueueError = class extends Error {
|
|
11435
12310
|
constructor(message, cause) {
|
|
11436
12311
|
super(message);
|
|
@@ -11445,13 +12320,13 @@ var init_pueue = __esm({
|
|
|
11445
12320
|
});
|
|
11446
12321
|
|
|
11447
12322
|
// cli/local-cc/settings.ts
|
|
11448
|
-
import { existsSync as existsSync28, readFileSync as
|
|
11449
|
-
import { homedir as
|
|
11450
|
-
import { join as
|
|
12323
|
+
import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
|
|
12324
|
+
import { homedir as homedir29 } from "os";
|
|
12325
|
+
import { join as join28 } from "path";
|
|
11451
12326
|
function isLocalCCEnabled() {
|
|
11452
12327
|
if (!existsSync28(CONFIG_PATH5)) return false;
|
|
11453
12328
|
try {
|
|
11454
|
-
const content =
|
|
12329
|
+
const content = readFileSync27(CONFIG_PATH5, "utf-8");
|
|
11455
12330
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
11456
12331
|
return match?.[1] === "yes";
|
|
11457
12332
|
} catch {
|
|
@@ -11462,7 +12337,7 @@ var CONFIG_PATH5;
|
|
|
11462
12337
|
var init_settings = __esm({
|
|
11463
12338
|
"cli/local-cc/settings.ts"() {
|
|
11464
12339
|
"use strict";
|
|
11465
|
-
CONFIG_PATH5 =
|
|
12340
|
+
CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
|
|
11466
12341
|
}
|
|
11467
12342
|
});
|
|
11468
12343
|
|
|
@@ -11472,10 +12347,10 @@ __export(localCc_exports, {
|
|
|
11472
12347
|
localCcCommand: () => localCcCommand
|
|
11473
12348
|
});
|
|
11474
12349
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
11475
|
-
import { homedir as
|
|
11476
|
-
import { join as
|
|
12350
|
+
import { homedir as homedir30 } from "os";
|
|
12351
|
+
import { join as join29 } from "path";
|
|
11477
12352
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
11478
|
-
import { existsSync as existsSync29, readFileSync as
|
|
12353
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
11479
12354
|
function deploymentMode() {
|
|
11480
12355
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
11481
12356
|
if (env === "docker") return "docker";
|
|
@@ -11582,14 +12457,14 @@ TROUBLESHOOTING
|
|
|
11582
12457
|
}
|
|
11583
12458
|
function readGatewayUrl() {
|
|
11584
12459
|
if (existsSync29(CONFIG_PATH6)) {
|
|
11585
|
-
const m =
|
|
12460
|
+
const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
11586
12461
|
if (m) return m[1];
|
|
11587
12462
|
}
|
|
11588
12463
|
return "https://api.synkro.sh";
|
|
11589
12464
|
}
|
|
11590
12465
|
function updateLocalInferenceFlag(enabled) {
|
|
11591
12466
|
if (!existsSync29(CONFIG_PATH6)) return;
|
|
11592
|
-
let content =
|
|
12467
|
+
let content = readFileSync28(CONFIG_PATH6, "utf-8");
|
|
11593
12468
|
const flag = enabled ? "yes" : "no";
|
|
11594
12469
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
11595
12470
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -11598,7 +12473,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
11598
12473
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
11599
12474
|
`;
|
|
11600
12475
|
}
|
|
11601
|
-
|
|
12476
|
+
writeFileSync22(CONFIG_PATH6, content, "utf-8");
|
|
11602
12477
|
}
|
|
11603
12478
|
async function setServerGradingProvider(provider) {
|
|
11604
12479
|
await ensureValidToken();
|
|
@@ -11627,7 +12502,7 @@ async function cmdStatus() {
|
|
|
11627
12502
|
} else {
|
|
11628
12503
|
console.log(`synkro-server container: running (${status.image})`);
|
|
11629
12504
|
try {
|
|
11630
|
-
const r = await fetch(
|
|
12505
|
+
const r = await fetch(status.healthz, { signal: AbortSignal.timeout(3e3) });
|
|
11631
12506
|
console.log(`Health probe: ${r.ok ? "ok" : `HTTP ${r.status}`}`);
|
|
11632
12507
|
} catch (err) {
|
|
11633
12508
|
console.log(`Health probe: ${err.message}`);
|
|
@@ -11719,9 +12594,9 @@ async function warmChannels(ready1, ready2) {
|
|
|
11719
12594
|
async function cmdStart(rest = []) {
|
|
11720
12595
|
if (inDockerMode()) {
|
|
11721
12596
|
if (rest.length > 0) {
|
|
11722
|
-
const { claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest);
|
|
12597
|
+
const { claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest);
|
|
11723
12598
|
console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)...`);
|
|
11724
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
|
|
12599
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
|
|
11725
12600
|
const ready3 = await waitForContainerReady(6e4);
|
|
11726
12601
|
console.log(ready3 ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
|
|
11727
12602
|
return;
|
|
@@ -11768,18 +12643,19 @@ async function cmdRestart(rest = []) {
|
|
|
11768
12643
|
let claudeWorkers;
|
|
11769
12644
|
let cursorWorkers;
|
|
11770
12645
|
let codexWorkers;
|
|
12646
|
+
let conductorProvider;
|
|
11771
12647
|
if (explicit) {
|
|
11772
|
-
({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
|
|
12648
|
+
({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
|
|
11773
12649
|
} else {
|
|
11774
12650
|
const reconciled = reconcileHarness();
|
|
11775
12651
|
if (reconciled) {
|
|
11776
|
-
({ claudeWorkers, cursorWorkers, codexWorkers } = reconciled);
|
|
12652
|
+
({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = reconciled);
|
|
11777
12653
|
} else {
|
|
11778
|
-
({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
|
|
12654
|
+
({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
|
|
11779
12655
|
}
|
|
11780
12656
|
}
|
|
11781
12657
|
console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex, pulling latest image)...`);
|
|
11782
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
|
|
12658
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
|
|
11783
12659
|
const ready = await waitForContainerReady(6e4);
|
|
11784
12660
|
console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
|
|
11785
12661
|
if (ready) {
|
|
@@ -12031,8 +12907,8 @@ var init_localCc = __esm({
|
|
|
12031
12907
|
init_install();
|
|
12032
12908
|
init_client2();
|
|
12033
12909
|
init_stub();
|
|
12034
|
-
SYNKRO_CONFIG_PATH =
|
|
12035
|
-
CONFIG_PATH6 =
|
|
12910
|
+
SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
|
|
12911
|
+
CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
|
|
12036
12912
|
}
|
|
12037
12913
|
});
|
|
12038
12914
|
|
|
@@ -12041,14 +12917,14 @@ var import_exports = {};
|
|
|
12041
12917
|
__export(import_exports, {
|
|
12042
12918
|
importCommand: () => importCommand
|
|
12043
12919
|
});
|
|
12044
|
-
import { existsSync as existsSync30, readFileSync as
|
|
12045
|
-
import { homedir as
|
|
12046
|
-
import { join as
|
|
12920
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
|
|
12921
|
+
import { homedir as homedir31 } from "os";
|
|
12922
|
+
import { join as join30 } from "path";
|
|
12047
12923
|
import { execSync as execSync6 } from "child_process";
|
|
12048
12924
|
import { createInterface as createInterface4 } from "readline";
|
|
12049
12925
|
function readMcpJwt() {
|
|
12050
12926
|
try {
|
|
12051
|
-
return
|
|
12927
|
+
return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
12052
12928
|
} catch {
|
|
12053
12929
|
return "";
|
|
12054
12930
|
}
|
|
@@ -12056,7 +12932,7 @@ function readMcpJwt() {
|
|
|
12056
12932
|
function readConfigEnv2() {
|
|
12057
12933
|
const out = {};
|
|
12058
12934
|
try {
|
|
12059
|
-
for (const line of
|
|
12935
|
+
for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
|
|
12060
12936
|
const t = line.trim();
|
|
12061
12937
|
if (!t || t.startsWith("#")) continue;
|
|
12062
12938
|
const eq = t.indexOf("=");
|
|
@@ -12068,7 +12944,7 @@ function readConfigEnv2() {
|
|
|
12068
12944
|
}
|
|
12069
12945
|
function projectsFolder() {
|
|
12070
12946
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
12071
|
-
const dir =
|
|
12947
|
+
const dir = join30(homedir31(), ".claude", "projects", sanitized);
|
|
12072
12948
|
return existsSync30(dir) ? dir : null;
|
|
12073
12949
|
}
|
|
12074
12950
|
function repoName() {
|
|
@@ -12108,7 +12984,7 @@ function extractToolResultText(content, e) {
|
|
|
12108
12984
|
return t;
|
|
12109
12985
|
}
|
|
12110
12986
|
function parseSession(filePath, sessionId) {
|
|
12111
|
-
const lines =
|
|
12987
|
+
const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
|
|
12112
12988
|
const messages = [];
|
|
12113
12989
|
const actions = [];
|
|
12114
12990
|
let step = 0;
|
|
@@ -12188,7 +13064,7 @@ async function importCommand() {
|
|
|
12188
13064
|
return;
|
|
12189
13065
|
}
|
|
12190
13066
|
}
|
|
12191
|
-
const sessions = files.map((f) => parseSession(
|
|
13067
|
+
const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
12192
13068
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
12193
13069
|
let ok = 0, fail = 0;
|
|
12194
13070
|
if (isCloud) {
|
|
@@ -12263,7 +13139,7 @@ var init_import = __esm({
|
|
|
12263
13139
|
"cli/commands/import.ts"() {
|
|
12264
13140
|
"use strict";
|
|
12265
13141
|
init_stub();
|
|
12266
|
-
CONFIG_PATH7 =
|
|
13142
|
+
CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
|
|
12267
13143
|
}
|
|
12268
13144
|
});
|
|
12269
13145
|
|
|
@@ -12305,10 +13181,10 @@ var init_packVerify = __esm({
|
|
|
12305
13181
|
});
|
|
12306
13182
|
|
|
12307
13183
|
// cli/installer/lockfile.ts
|
|
12308
|
-
import { existsSync as existsSync31, readFileSync as
|
|
12309
|
-
import { join as
|
|
13184
|
+
import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
|
|
13185
|
+
import { join as join31 } from "path";
|
|
12310
13186
|
function lockPath(repoRoot2) {
|
|
12311
|
-
return
|
|
13187
|
+
return join31(repoRoot2, LOCK_FILE);
|
|
12312
13188
|
}
|
|
12313
13189
|
function writeLockfile(repoRoot2, entries) {
|
|
12314
13190
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -12326,7 +13202,7 @@ function writeLockfile(repoRoot2, entries) {
|
|
|
12326
13202
|
""
|
|
12327
13203
|
])
|
|
12328
13204
|
].join("\n");
|
|
12329
|
-
|
|
13205
|
+
writeFileSync23(lockPath(repoRoot2), body, "utf-8");
|
|
12330
13206
|
}
|
|
12331
13207
|
var LOCK_FILE;
|
|
12332
13208
|
var init_lockfile = __esm({
|
|
@@ -12341,9 +13217,9 @@ var sync_exports = {};
|
|
|
12341
13217
|
__export(sync_exports, {
|
|
12342
13218
|
syncCommand: () => syncCommand
|
|
12343
13219
|
});
|
|
12344
|
-
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as
|
|
12345
|
-
import { homedir as
|
|
12346
|
-
import { join as
|
|
13220
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13221
|
+
import { homedir as homedir32 } from "os";
|
|
13222
|
+
import { join as join32 } from "path";
|
|
12347
13223
|
function cacheKey(ref, version) {
|
|
12348
13224
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
12349
13225
|
}
|
|
@@ -12374,7 +13250,7 @@ async function syncCommand(_args = []) {
|
|
|
12374
13250
|
}
|
|
12375
13251
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
12376
13252
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
12377
|
-
const cacheDir =
|
|
13253
|
+
const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
|
|
12378
13254
|
if (!cloud) mkdirSync18(cacheDir, { recursive: true });
|
|
12379
13255
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
12380
13256
|
const lock = [];
|
|
@@ -12403,7 +13279,7 @@ async function syncCommand(_args = []) {
|
|
|
12403
13279
|
if (!cloud) {
|
|
12404
13280
|
const fname = cacheKey(ref, data.version);
|
|
12405
13281
|
keptCacheFiles.add(fname);
|
|
12406
|
-
|
|
13282
|
+
writeFileSync24(join32(cacheDir, fname), JSON.stringify({
|
|
12407
13283
|
ref,
|
|
12408
13284
|
version: data.version,
|
|
12409
13285
|
digest: data.digest,
|
|
@@ -12419,7 +13295,7 @@ async function syncCommand(_args = []) {
|
|
|
12419
13295
|
for (const f of readdirSync8(cacheDir)) {
|
|
12420
13296
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
12421
13297
|
try {
|
|
12422
|
-
rmSync4(
|
|
13298
|
+
rmSync4(join32(cacheDir, f));
|
|
12423
13299
|
} catch {
|
|
12424
13300
|
}
|
|
12425
13301
|
}
|
|
@@ -12448,13 +13324,13 @@ var whoami_exports = {};
|
|
|
12448
13324
|
__export(whoami_exports, {
|
|
12449
13325
|
whoamiCommand: () => whoamiCommand
|
|
12450
13326
|
});
|
|
12451
|
-
import { readFileSync as
|
|
12452
|
-
import { join as
|
|
12453
|
-
import { homedir as
|
|
13327
|
+
import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
|
|
13328
|
+
import { join as join33 } from "path";
|
|
13329
|
+
import { homedir as homedir33 } from "os";
|
|
12454
13330
|
function readConfigEnv3() {
|
|
12455
13331
|
if (!existsSync33(CONFIG_PATH8)) return {};
|
|
12456
13332
|
const out = {};
|
|
12457
|
-
for (const line of
|
|
13333
|
+
for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
|
|
12458
13334
|
const t = line.trim();
|
|
12459
13335
|
if (!t || t.startsWith("#")) continue;
|
|
12460
13336
|
const eq = t.indexOf("=");
|
|
@@ -12465,7 +13341,7 @@ function readConfigEnv3() {
|
|
|
12465
13341
|
function jwtStatus() {
|
|
12466
13342
|
try {
|
|
12467
13343
|
if (!existsSync33(JWT_PATH2)) return { status: "none" };
|
|
12468
|
-
const jwt2 =
|
|
13344
|
+
const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
|
|
12469
13345
|
if (!jwt2) return { status: "none" };
|
|
12470
13346
|
const payload = jwt2.split(".")[1];
|
|
12471
13347
|
if (!payload) return { status: "valid" };
|
|
@@ -12527,9 +13403,9 @@ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
|
|
|
12527
13403
|
var init_whoami = __esm({
|
|
12528
13404
|
"cli/commands/whoami.ts"() {
|
|
12529
13405
|
"use strict";
|
|
12530
|
-
SYNKRO_DIR15 =
|
|
12531
|
-
CONFIG_PATH8 =
|
|
12532
|
-
JWT_PATH2 =
|
|
13406
|
+
SYNKRO_DIR15 = join33(homedir33(), ".synkro");
|
|
13407
|
+
CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
|
|
13408
|
+
JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
|
|
12533
13409
|
GRADING_LABEL = {
|
|
12534
13410
|
local: "on-device worker pool",
|
|
12535
13411
|
cloud: "Synkro Cloud worker pool",
|
|
@@ -12574,12 +13450,12 @@ __export(linear_exports, {
|
|
|
12574
13450
|
formatLinks: () => formatLinks,
|
|
12575
13451
|
linearCommand: () => linearCommand
|
|
12576
13452
|
});
|
|
12577
|
-
import { readFileSync as
|
|
12578
|
-
import { homedir as
|
|
12579
|
-
import { join as
|
|
13453
|
+
import { readFileSync as readFileSync32 } from "fs";
|
|
13454
|
+
import { homedir as homedir34 } from "os";
|
|
13455
|
+
import { join as join34 } from "path";
|
|
12580
13456
|
function mcpJwt() {
|
|
12581
13457
|
try {
|
|
12582
|
-
return
|
|
13458
|
+
return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
|
|
12583
13459
|
} catch {
|
|
12584
13460
|
return "";
|
|
12585
13461
|
}
|
|
@@ -12618,7 +13494,7 @@ var SYNKRO_DIR16, PORT2, BASE;
|
|
|
12618
13494
|
var init_linear = __esm({
|
|
12619
13495
|
"cli/commands/linear.ts"() {
|
|
12620
13496
|
"use strict";
|
|
12621
|
-
SYNKRO_DIR16 =
|
|
13497
|
+
SYNKRO_DIR16 = join34(homedir34(), ".synkro");
|
|
12622
13498
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12623
13499
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
12624
13500
|
}
|
|
@@ -12626,7 +13502,7 @@ var init_linear = __esm({
|
|
|
12626
13502
|
|
|
12627
13503
|
// cli/scanning/cveReachability.ts
|
|
12628
13504
|
import { parse } from "@babel/parser";
|
|
12629
|
-
import { readFileSync as
|
|
13505
|
+
import { readFileSync as readFileSync33 } from "fs";
|
|
12630
13506
|
function walk(node, visit) {
|
|
12631
13507
|
if (!node || typeof node.type !== "string") return;
|
|
12632
13508
|
visit(node);
|
|
@@ -12768,9 +13644,9 @@ var init_cveReachability = __esm({
|
|
|
12768
13644
|
|
|
12769
13645
|
// cli/reachability/reachabilityScan.ts
|
|
12770
13646
|
import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
|
|
12771
|
-
import { readFileSync as
|
|
12772
|
-
import { join as
|
|
12773
|
-
import { homedir as
|
|
13647
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
|
|
13648
|
+
import { join as join35 } from "path";
|
|
13649
|
+
import { homedir as homedir35 } from "os";
|
|
12774
13650
|
import { createRequire } from "module";
|
|
12775
13651
|
function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
12776
13652
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -12787,7 +13663,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12787
13663
|
}
|
|
12788
13664
|
for (const e of ents) {
|
|
12789
13665
|
if (files.length >= maxFiles) break;
|
|
12790
|
-
const full =
|
|
13666
|
+
const full = join35(dir, e.name);
|
|
12791
13667
|
if (e.isDirectory()) {
|
|
12792
13668
|
if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
12793
13669
|
continue;
|
|
@@ -12795,7 +13671,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12795
13671
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
12796
13672
|
const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
|
|
12797
13673
|
try {
|
|
12798
|
-
const content =
|
|
13674
|
+
const content = readFileSync34(full, "utf8");
|
|
12799
13675
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
12800
13676
|
} catch {
|
|
12801
13677
|
}
|
|
@@ -12814,12 +13690,12 @@ function cleanVersion(spec) {
|
|
|
12814
13690
|
function gatherManifestVersions(repoRoot2) {
|
|
12815
13691
|
const out = {};
|
|
12816
13692
|
const dirs = [repoRoot2];
|
|
12817
|
-
const pkgsDir =
|
|
13693
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12818
13694
|
if (existsSync34(pkgsDir)) {
|
|
12819
13695
|
try {
|
|
12820
13696
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12821
|
-
const pd =
|
|
12822
|
-
if (existsSync34(
|
|
13697
|
+
const pd = join35(pkgsDir, d);
|
|
13698
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12823
13699
|
}
|
|
12824
13700
|
} catch {
|
|
12825
13701
|
}
|
|
@@ -12828,7 +13704,7 @@ function gatherManifestVersions(repoRoot2) {
|
|
|
12828
13704
|
for (const dir of dirs) {
|
|
12829
13705
|
let pkg;
|
|
12830
13706
|
try {
|
|
12831
|
-
pkg = JSON.parse(
|
|
13707
|
+
pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12832
13708
|
} catch {
|
|
12833
13709
|
continue;
|
|
12834
13710
|
}
|
|
@@ -12848,28 +13724,28 @@ function findJelly(repoRoot2) {
|
|
|
12848
13724
|
try {
|
|
12849
13725
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
12850
13726
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
12851
|
-
const pkg = JSON.parse(
|
|
13727
|
+
const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
|
|
12852
13728
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
12853
13729
|
if (bin) {
|
|
12854
|
-
const p =
|
|
13730
|
+
const p = join35(dir, bin);
|
|
12855
13731
|
if (existsSync34(p)) return p;
|
|
12856
13732
|
}
|
|
12857
13733
|
} catch {
|
|
12858
13734
|
}
|
|
12859
13735
|
for (const base of [repoRoot2, process.cwd()]) {
|
|
12860
|
-
const b =
|
|
13736
|
+
const b = join35(base, "node_modules", ".bin", "jelly");
|
|
12861
13737
|
if (existsSync34(b)) return b;
|
|
12862
13738
|
}
|
|
12863
13739
|
return null;
|
|
12864
13740
|
}
|
|
12865
13741
|
function findEntries(repoRoot2) {
|
|
12866
13742
|
const dirs = [repoRoot2];
|
|
12867
|
-
const pkgsDir =
|
|
13743
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12868
13744
|
if (existsSync34(pkgsDir)) {
|
|
12869
13745
|
try {
|
|
12870
13746
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12871
|
-
const pd =
|
|
12872
|
-
if (existsSync34(
|
|
13747
|
+
const pd = join35(pkgsDir, d);
|
|
13748
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12873
13749
|
}
|
|
12874
13750
|
} catch {
|
|
12875
13751
|
}
|
|
@@ -12877,11 +13753,11 @@ function findEntries(repoRoot2) {
|
|
|
12877
13753
|
const entries = [];
|
|
12878
13754
|
for (const dir of dirs) {
|
|
12879
13755
|
try {
|
|
12880
|
-
const pkg = JSON.parse(
|
|
13756
|
+
const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12881
13757
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
12882
13758
|
for (const c of cands) {
|
|
12883
13759
|
if (typeof c !== "string") continue;
|
|
12884
|
-
const f =
|
|
13760
|
+
const f = join35(dir, c);
|
|
12885
13761
|
if (existsSync34(f)) {
|
|
12886
13762
|
entries.push(f);
|
|
12887
13763
|
break;
|
|
@@ -12917,7 +13793,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
12917
13793
|
const commit = currentCommit(repoRoot2);
|
|
12918
13794
|
if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
|
|
12919
13795
|
try {
|
|
12920
|
-
const prev = JSON.parse(
|
|
13796
|
+
const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
|
|
12921
13797
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
12922
13798
|
} catch {
|
|
12923
13799
|
}
|
|
@@ -13006,7 +13882,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
13006
13882
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
13007
13883
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
|
|
13008
13884
|
try {
|
|
13009
|
-
|
|
13885
|
+
writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
13010
13886
|
} catch (e) {
|
|
13011
13887
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
13012
13888
|
}
|
|
@@ -13018,7 +13894,7 @@ var init_reachabilityScan = __esm({
|
|
|
13018
13894
|
"use strict";
|
|
13019
13895
|
init_cveReachability();
|
|
13020
13896
|
require2 = createRequire(import.meta.url);
|
|
13021
|
-
REACHABILITY_PATH =
|
|
13897
|
+
REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
|
|
13022
13898
|
}
|
|
13023
13899
|
});
|
|
13024
13900
|
|
|
@@ -13027,15 +13903,15 @@ var reachabilityScan_exports = {};
|
|
|
13027
13903
|
__export(reachabilityScan_exports, {
|
|
13028
13904
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
13029
13905
|
});
|
|
13030
|
-
import { readFileSync as
|
|
13031
|
-
import { join as
|
|
13032
|
-
import { homedir as
|
|
13906
|
+
import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
|
|
13907
|
+
import { join as join36 } from "path";
|
|
13908
|
+
import { homedir as homedir36 } from "os";
|
|
13033
13909
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
13034
13910
|
function readConfigEnv4() {
|
|
13035
|
-
const p =
|
|
13911
|
+
const p = join36(SYNKRO_DIR17, "config.env");
|
|
13036
13912
|
if (!existsSync35(p)) return {};
|
|
13037
13913
|
const out = {};
|
|
13038
|
-
for (const line of
|
|
13914
|
+
for (const line of readFileSync35(p, "utf-8").split("\n")) {
|
|
13039
13915
|
const t = line.trim();
|
|
13040
13916
|
if (!t || t.startsWith("#")) continue;
|
|
13041
13917
|
const eq = t.indexOf("=");
|
|
@@ -13067,11 +13943,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
13067
13943
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
13068
13944
|
let jwt2 = "";
|
|
13069
13945
|
try {
|
|
13070
|
-
jwt2 =
|
|
13946
|
+
jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
|
|
13071
13947
|
} catch {
|
|
13072
13948
|
}
|
|
13073
13949
|
if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
|
|
13074
|
-
const body =
|
|
13950
|
+
const body = readFileSync35(REACHABILITY_PATH, "utf-8");
|
|
13075
13951
|
try {
|
|
13076
13952
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
13077
13953
|
method: "POST",
|
|
@@ -13103,7 +13979,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
13103
13979
|
"cli/commands/reachabilityScan.ts"() {
|
|
13104
13980
|
"use strict";
|
|
13105
13981
|
init_reachabilityScan();
|
|
13106
|
-
SYNKRO_DIR17 =
|
|
13982
|
+
SYNKRO_DIR17 = join36(homedir36(), ".synkro");
|
|
13107
13983
|
}
|
|
13108
13984
|
});
|
|
13109
13985
|
|
|
@@ -13134,10 +14010,10 @@ async function startCommand(rest = []) {
|
|
|
13134
14010
|
if (cfg.explicit) {
|
|
13135
14011
|
console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
|
|
13136
14012
|
`);
|
|
13137
|
-
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13138
|
-
const ready = await waitForContainerReady(
|
|
14013
|
+
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
14014
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13139
14015
|
if (!ready) {
|
|
13140
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14016
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13141
14017
|
process.exit(1);
|
|
13142
14018
|
}
|
|
13143
14019
|
console.log("\nServer is running.");
|
|
@@ -13162,13 +14038,14 @@ async function updateCommand() {
|
|
|
13162
14038
|
const claudeWorkers = cfg.claudeWorkers ?? 8;
|
|
13163
14039
|
const cursorWorkers = cfg.cursorWorkers ?? 0;
|
|
13164
14040
|
const codexWorkers = cfg.codexWorkers ?? 0;
|
|
14041
|
+
const conductorProvider = cfg.conductorProvider;
|
|
13165
14042
|
console.log("Synkro: updating to the latest container image");
|
|
13166
14043
|
console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
|
|
13167
14044
|
`);
|
|
13168
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13169
|
-
const ready = await waitForContainerReady(
|
|
14045
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
14046
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13170
14047
|
if (!ready) {
|
|
13171
|
-
console.error("\n\u26A0 container did not pass its health check within
|
|
14048
|
+
console.error("\n\u26A0 container did not pass its health check within 10m \u2014 check: docker logs synkro-server");
|
|
13172
14049
|
process.exit(1);
|
|
13173
14050
|
}
|
|
13174
14051
|
try {
|
|
@@ -13190,20 +14067,22 @@ async function restartCommand(rest = []) {
|
|
|
13190
14067
|
let claudeWorkers = cfg.claudeWorkers;
|
|
13191
14068
|
let cursorWorkers = cfg.cursorWorkers;
|
|
13192
14069
|
let codexWorkers = cfg.codexWorkers;
|
|
14070
|
+
let conductorProvider = cfg.conductorProvider;
|
|
13193
14071
|
if (!cfg.explicit) {
|
|
13194
14072
|
const reconciled = reconcileHarness();
|
|
13195
14073
|
if (reconciled) {
|
|
13196
14074
|
claudeWorkers = reconciled.claudeWorkers;
|
|
13197
14075
|
cursorWorkers = reconciled.cursorWorkers;
|
|
13198
14076
|
codexWorkers = reconciled.codexWorkers;
|
|
14077
|
+
conductorProvider = reconciled.conductorProvider;
|
|
13199
14078
|
}
|
|
13200
14079
|
}
|
|
13201
14080
|
console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
|
|
13202
14081
|
`);
|
|
13203
|
-
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
|
|
13204
|
-
const ready = await waitForContainerReady(
|
|
14082
|
+
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
14083
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13205
14084
|
if (!ready) {
|
|
13206
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14085
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13207
14086
|
process.exit(1);
|
|
13208
14087
|
}
|
|
13209
14088
|
console.log("\nServer restarted successfully.");
|
|
@@ -13215,11 +14094,13 @@ async function restartCommand(rest = []) {
|
|
|
13215
14094
|
console.warn("\u26A0 workers did not register within 30s \u2014 skill sync skipped");
|
|
13216
14095
|
}
|
|
13217
14096
|
}
|
|
14097
|
+
var LOCAL_CONTAINER_READY_TIMEOUT_MS;
|
|
13218
14098
|
var init_lifecycle = __esm({
|
|
13219
14099
|
"cli/commands/lifecycle.ts"() {
|
|
13220
14100
|
"use strict";
|
|
13221
14101
|
init_dockerInstall();
|
|
13222
14102
|
init_install();
|
|
14103
|
+
LOCAL_CONTAINER_READY_TIMEOUT_MS = 6e5;
|
|
13223
14104
|
}
|
|
13224
14105
|
});
|
|
13225
14106
|
|
|
@@ -13228,13 +14109,13 @@ var config_exports = {};
|
|
|
13228
14109
|
__export(config_exports, {
|
|
13229
14110
|
configCommand: () => configCommand
|
|
13230
14111
|
});
|
|
13231
|
-
import { readFileSync as
|
|
13232
|
-
import { join as
|
|
13233
|
-
import { homedir as
|
|
14112
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
|
|
14113
|
+
import { join as join37 } from "path";
|
|
14114
|
+
import { homedir as homedir37 } from "os";
|
|
13234
14115
|
function readConfigEnv5() {
|
|
13235
14116
|
if (!existsSync36(CONFIG_PATH9)) return {};
|
|
13236
14117
|
const out = {};
|
|
13237
|
-
for (const line of
|
|
14118
|
+
for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
13238
14119
|
const t = line.trim();
|
|
13239
14120
|
if (!t || t.startsWith("#")) continue;
|
|
13240
14121
|
const eq = t.indexOf("=");
|
|
@@ -13247,7 +14128,7 @@ function updateConfigValue(key, value) {
|
|
|
13247
14128
|
console.error("No config found. Run `synkro install` first.");
|
|
13248
14129
|
process.exit(1);
|
|
13249
14130
|
}
|
|
13250
|
-
const lines =
|
|
14131
|
+
const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
|
|
13251
14132
|
const pattern = new RegExp(`^${key}=`);
|
|
13252
14133
|
let found = false;
|
|
13253
14134
|
const updated = lines.map((line) => {
|
|
@@ -13258,7 +14139,7 @@ function updateConfigValue(key, value) {
|
|
|
13258
14139
|
return line;
|
|
13259
14140
|
});
|
|
13260
14141
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
13261
|
-
|
|
14142
|
+
writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
13262
14143
|
}
|
|
13263
14144
|
function resolveInferenceMode(cfg) {
|
|
13264
14145
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -13416,8 +14297,8 @@ var init_config = __esm({
|
|
|
13416
14297
|
"use strict";
|
|
13417
14298
|
init_stub();
|
|
13418
14299
|
init_optout();
|
|
13419
|
-
SYNKRO_DIR18 =
|
|
13420
|
-
CONFIG_PATH9 =
|
|
14300
|
+
SYNKRO_DIR18 = join37(homedir37(), ".synkro");
|
|
14301
|
+
CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
|
|
13421
14302
|
}
|
|
13422
14303
|
});
|
|
13423
14304
|
|
|
@@ -13606,14 +14487,14 @@ Usage:
|
|
|
13606
14487
|
});
|
|
13607
14488
|
|
|
13608
14489
|
// cli/bootstrap.js
|
|
13609
|
-
import { readFileSync as
|
|
14490
|
+
import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
|
|
13610
14491
|
import { resolve as resolve5 } from "path";
|
|
13611
14492
|
var envCandidates = [
|
|
13612
14493
|
resolve5(process.env.HOME ?? "", ".synkro", "config.env")
|
|
13613
14494
|
];
|
|
13614
14495
|
for (const envPath of envCandidates) {
|
|
13615
14496
|
if (!existsSync37(envPath)) continue;
|
|
13616
|
-
const envContent =
|
|
14497
|
+
const envContent = readFileSync37(envPath, "utf-8");
|
|
13617
14498
|
for (const line of envContent.split("\n")) {
|
|
13618
14499
|
const trimmed = line.trim();
|
|
13619
14500
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -13630,7 +14511,7 @@ var subArgs = args.slice(1);
|
|
|
13630
14511
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
13631
14512
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
13632
14513
|
function printVersion() {
|
|
13633
|
-
console.log("1.7.
|
|
14514
|
+
console.log("1.7.89");
|
|
13634
14515
|
}
|
|
13635
14516
|
function printHelp2() {
|
|
13636
14517
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|