@synkro-sh/cli 1.7.88 → 1.7.90
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 +1523 -598
- 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.90";
|
|
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,15 +2103,25 @@ 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
|
+
push(h, "Stop", [
|
|
2121
|
+
cmd(config.cweStopScriptPath, 50, "Checking completed edits for weaknesses"),
|
|
2122
|
+
cmd(config.cveStopScriptPath, 50, "Checking completed edits for vulnerabilities"),
|
|
2123
|
+
...!config.skipTranscriptSync ? [cmd(config.transcriptSyncScriptPath, 3)] : []
|
|
2124
|
+
]);
|
|
2038
2125
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
2039
2126
|
}
|
|
2040
2127
|
function uninstallCodexHooks(hooksJsonPath) {
|
|
@@ -2054,30 +2141,31 @@ function uninstallCodexHooks(hooksJsonPath) {
|
|
|
2054
2141
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
2055
2142
|
return true;
|
|
2056
2143
|
}
|
|
2057
|
-
var SYNKRO_MARKER3, M_BASH, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2144
|
+
var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2058
2145
|
var init_codexHookConfig = __esm({
|
|
2059
2146
|
"cli/installer/codexHookConfig.ts"() {
|
|
2060
2147
|
"use strict";
|
|
2061
2148
|
init_platform();
|
|
2062
2149
|
SYNKRO_MARKER3 = "__synkro_managed__";
|
|
2063
2150
|
M_BASH = "Bash";
|
|
2064
|
-
|
|
2151
|
+
CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
|
|
2152
|
+
M_EDIT = CODEX_EDIT_MATCHER;
|
|
2065
2153
|
M_AGENT = "Agent";
|
|
2066
2154
|
M_MCP = "mcp__.*";
|
|
2067
|
-
M_ACTIVATE = "mcp__synkro-guardrails__activate_standard";
|
|
2068
|
-
ALL_EVENTS2 = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop"];
|
|
2155
|
+
M_ACTIVATE = "mcp__synkro[-_]guardrails__activate_standard";
|
|
2156
|
+
ALL_EVENTS2 = ["PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "SubagentStart", "SubagentStop", "Stop"];
|
|
2069
2157
|
}
|
|
2070
2158
|
});
|
|
2071
2159
|
|
|
2072
2160
|
// cli/installer/mcpConfig.ts
|
|
2073
|
-
import { existsSync as existsSync13, readFileSync as
|
|
2074
|
-
import { homedir as
|
|
2075
|
-
import { dirname as dirname4, join as
|
|
2161
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
2162
|
+
import { homedir as homedir12 } from "os";
|
|
2163
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2076
2164
|
import { randomBytes } from "crypto";
|
|
2077
2165
|
function readClaudeJson() {
|
|
2078
2166
|
if (!existsSync13(CC_CONFIG_PATH)) return {};
|
|
2079
2167
|
try {
|
|
2080
|
-
const raw =
|
|
2168
|
+
const raw = readFileSync12(CC_CONFIG_PATH, "utf-8");
|
|
2081
2169
|
return JSON.parse(raw);
|
|
2082
2170
|
} catch (err) {
|
|
2083
2171
|
throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
|
|
@@ -2096,7 +2184,7 @@ function installMcpConfig(opts) {
|
|
|
2096
2184
|
if (entry?.[SYNKRO_MARKER4] === true) delete config.mcpServers[name];
|
|
2097
2185
|
}
|
|
2098
2186
|
if (opts.local) {
|
|
2099
|
-
const proxyScript =
|
|
2187
|
+
const proxyScript = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2100
2188
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
2101
2189
|
type: "stdio",
|
|
2102
2190
|
command: "bun",
|
|
@@ -2135,7 +2223,7 @@ function uninstallMcpConfig() {
|
|
|
2135
2223
|
function readCursorMcpJson() {
|
|
2136
2224
|
if (!existsSync13(CURSOR_MCP_PATH)) return {};
|
|
2137
2225
|
try {
|
|
2138
|
-
const raw =
|
|
2226
|
+
const raw = readFileSync12(CURSOR_MCP_PATH, "utf-8");
|
|
2139
2227
|
return JSON.parse(raw);
|
|
2140
2228
|
} catch (err) {
|
|
2141
2229
|
throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${err.message}`);
|
|
@@ -2156,10 +2244,10 @@ function installCursorMcpConfig(opts) {
|
|
|
2156
2244
|
if (opts.local) {
|
|
2157
2245
|
const port = process.env.SYNKRO_MCP_PORT || "18931";
|
|
2158
2246
|
const url2 = `http://127.0.0.1:${port}/`;
|
|
2159
|
-
const jwtPath =
|
|
2247
|
+
const jwtPath = join10(homedir12(), ".synkro", ".mcp-jwt");
|
|
2160
2248
|
let jwt2 = "";
|
|
2161
2249
|
try {
|
|
2162
|
-
jwt2 =
|
|
2250
|
+
jwt2 = readFileSync12(jwtPath, "utf-8").trim();
|
|
2163
2251
|
} catch {
|
|
2164
2252
|
}
|
|
2165
2253
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
@@ -2201,7 +2289,7 @@ function codexConfigPath(path) {
|
|
|
2201
2289
|
function readCodexToml(path) {
|
|
2202
2290
|
if (!existsSync13(path)) return "";
|
|
2203
2291
|
try {
|
|
2204
|
-
return
|
|
2292
|
+
return readFileSync12(path, "utf-8");
|
|
2205
2293
|
} catch (err) {
|
|
2206
2294
|
throw new Error(`Failed to read ${path}: ${err.message}`);
|
|
2207
2295
|
}
|
|
@@ -2290,7 +2378,7 @@ function resolveBunBin2() {
|
|
|
2290
2378
|
function readDesktopJson() {
|
|
2291
2379
|
if (!existsSync13(CLAUDE_DESKTOP_CONFIG_PATH)) return {};
|
|
2292
2380
|
try {
|
|
2293
|
-
return JSON.parse(
|
|
2381
|
+
return JSON.parse(readFileSync12(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
|
|
2294
2382
|
} catch (err) {
|
|
2295
2383
|
throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${err.message}`);
|
|
2296
2384
|
}
|
|
@@ -2347,31 +2435,31 @@ var init_mcpConfig = __esm({
|
|
|
2347
2435
|
init_platform();
|
|
2348
2436
|
SYNKRO_MARKER4 = "__synkro_managed__";
|
|
2349
2437
|
SYNKRO_SERVER_NAME = "synkro-guardrails";
|
|
2350
|
-
CC_CONFIG_PATH =
|
|
2351
|
-
CURSOR_MCP_PATH =
|
|
2352
|
-
CODEX_CONFIG_PATH =
|
|
2438
|
+
CC_CONFIG_PATH = join10(homedir12(), ".claude.json");
|
|
2439
|
+
CURSOR_MCP_PATH = join10(homedir12(), ".cursor", "mcp.json");
|
|
2440
|
+
CODEX_CONFIG_PATH = join10(process.env.CODEX_HOME || join10(homedir12(), ".codex"), "config.toml");
|
|
2353
2441
|
CODEX_MCP_BEGIN = "# >>> synkro managed MCP: synkro-guardrails";
|
|
2354
2442
|
CODEX_MCP_END = "# <<< synkro managed MCP: synkro-guardrails";
|
|
2355
2443
|
CODEX_MCP_SECTION = 'mcp_servers."synkro-guardrails"';
|
|
2356
|
-
CLAUDE_DESKTOP_CONFIG_PATH =
|
|
2357
|
-
|
|
2444
|
+
CLAUDE_DESKTOP_CONFIG_PATH = join10(
|
|
2445
|
+
homedir12(),
|
|
2358
2446
|
"Library",
|
|
2359
2447
|
"Application Support",
|
|
2360
2448
|
"Claude",
|
|
2361
2449
|
"claude_desktop_config.json"
|
|
2362
2450
|
);
|
|
2363
|
-
MCP_STDIO_PROXY_PATH =
|
|
2451
|
+
MCP_STDIO_PROXY_PATH = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2364
2452
|
}
|
|
2365
2453
|
});
|
|
2366
2454
|
|
|
2367
2455
|
// cli/installer/synkroCommand.ts
|
|
2368
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as
|
|
2369
|
-
import { homedir as
|
|
2370
|
-
import { join as
|
|
2456
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
2457
|
+
import { homedir as homedir13 } from "os";
|
|
2458
|
+
import { join as join11 } from "path";
|
|
2371
2459
|
function installSynkroCommand() {
|
|
2372
2460
|
try {
|
|
2373
2461
|
if (existsSync14(COMMAND_PATH)) {
|
|
2374
|
-
const current =
|
|
2462
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2375
2463
|
if (!current.includes(MARKER)) return null;
|
|
2376
2464
|
}
|
|
2377
2465
|
mkdirSync8(COMMANDS_DIR, { recursive: true });
|
|
@@ -2384,7 +2472,7 @@ function installSynkroCommand() {
|
|
|
2384
2472
|
function uninstallSynkroCommand() {
|
|
2385
2473
|
try {
|
|
2386
2474
|
if (!existsSync14(COMMAND_PATH)) return false;
|
|
2387
|
-
const current =
|
|
2475
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2388
2476
|
if (!current.includes(MARKER)) return false;
|
|
2389
2477
|
unlinkSync4(COMMAND_PATH);
|
|
2390
2478
|
return true;
|
|
@@ -2397,8 +2485,8 @@ var init_synkroCommand = __esm({
|
|
|
2397
2485
|
"cli/installer/synkroCommand.ts"() {
|
|
2398
2486
|
"use strict";
|
|
2399
2487
|
MARKER = "__synkro_managed__";
|
|
2400
|
-
COMMANDS_DIR =
|
|
2401
|
-
COMMAND_PATH =
|
|
2488
|
+
COMMANDS_DIR = join11(homedir13(), ".claude", "commands");
|
|
2489
|
+
COMMAND_PATH = join11(COMMANDS_DIR, "synkro.md");
|
|
2402
2490
|
COMMAND_BODY = `<!-- ${MARKER} -->
|
|
2403
2491
|
---
|
|
2404
2492
|
description: Set up Synkro \u2014 create a starter rule set, review suggestions, and watch a live block
|
|
@@ -2456,14 +2544,14 @@ var init_skillParser = __esm({
|
|
|
2456
2544
|
function stubHook(surface, optsLiteral) {
|
|
2457
2545
|
return "#!/usr/bin/env bun\nimport { runStub } from './_synkro-stub-common.ts';\nrunStub(" + JSON.stringify(surface) + ", " + optsLiteral + ");\n";
|
|
2458
2546
|
}
|
|
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;
|
|
2547
|
+
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_CODEX_CWE_STOP_TS, STUB_CODEX_CVE_STOP_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
2548
|
var init_hookScriptsTs = __esm({
|
|
2461
2549
|
"cli/installer/hookScriptsTs.ts"() {
|
|
2462
2550
|
"use strict";
|
|
2463
|
-
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2551
|
+
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2464
2552
|
import { execSync } from 'node:child_process';
|
|
2465
2553
|
import { homedir } from 'node:os';
|
|
2466
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
2554
|
+
import { basename, join, resolve, relative, isAbsolute } from 'node:path';
|
|
2467
2555
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2468
2556
|
|
|
2469
2557
|
const HOME = homedir();
|
|
@@ -2622,13 +2710,17 @@ async function cloudMcpGate(payload: any, harness: string): Promise<string> {
|
|
|
2622
2710
|
try {
|
|
2623
2711
|
const toolName = String(payload.tool_name || payload.tool || '');
|
|
2624
2712
|
const parts = toolName.split('__');
|
|
2625
|
-
const
|
|
2713
|
+
const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
2714
|
+
const server = (parts.length >= 3 && parts[0] === 'mcp')
|
|
2715
|
+
? parts[1]
|
|
2716
|
+
: String(payload.server_name || payload.serverName || payload.server || payload.mcp_server
|
|
2717
|
+
|| input.server_name || input.serverName || input.server || input.mcp_server || '');
|
|
2626
2718
|
if (!server) return failOpen(harness);
|
|
2627
2719
|
const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\/+$/, '');
|
|
2628
2720
|
const resp = await fetch(base + '/api/mcp/gate', {
|
|
2629
2721
|
method: 'POST',
|
|
2630
2722
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },
|
|
2631
|
-
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),
|
|
2723
|
+
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') || toolName, harness: harness }),
|
|
2632
2724
|
signal: AbortSignal.timeout(5000),
|
|
2633
2725
|
});
|
|
2634
2726
|
if (!resp.ok) return failOpen(harness);
|
|
@@ -2664,6 +2756,28 @@ function failOpen(harness: string): string {
|
|
|
2664
2756
|
return harness === 'cursor' ? '{"permission":"allow"}' : '{}';
|
|
2665
2757
|
}
|
|
2666
2758
|
|
|
2759
|
+
/** Convert an existing security PreToolUse verdict into Codex's Stop contract. */
|
|
2760
|
+
export function codexStopResponse(responseText: string): string {
|
|
2761
|
+
try {
|
|
2762
|
+
const parsed = JSON.parse(responseText || '{}');
|
|
2763
|
+
const specific = parsed?.hookSpecificOutput;
|
|
2764
|
+
if (specific?.permissionDecision !== 'deny') return '{}';
|
|
2765
|
+
const reason = String(
|
|
2766
|
+
specific.permissionDecisionReason
|
|
2767
|
+
|| specific.additionalContext
|
|
2768
|
+
|| parsed.systemMessage
|
|
2769
|
+
|| 'A completed edit contains a CWE weakness. Fix it before stopping.',
|
|
2770
|
+
);
|
|
2771
|
+
return JSON.stringify({
|
|
2772
|
+
decision: 'block',
|
|
2773
|
+
reason,
|
|
2774
|
+
...(parsed.systemMessage ? { systemMessage: parsed.systemMessage } : {}),
|
|
2775
|
+
});
|
|
2776
|
+
} catch {
|
|
2777
|
+
return '{}';
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2667
2781
|
function out(s: string): void { try { process.stdout.write(s + '\n'); } catch {} }
|
|
2668
2782
|
|
|
2669
2783
|
function gitRoot(cwd: string): string {
|
|
@@ -2725,48 +2839,327 @@ function gatherBaseContent(fp: string, ti: any): string | undefined {
|
|
|
2725
2839
|
return '\n'.repeat(linesBefore) + full.slice(start, end);
|
|
2726
2840
|
}
|
|
2727
2841
|
|
|
2842
|
+
function gatherPreEditContentFromPost(fp: string, toolName: string, ti: any, operation: string): string | undefined {
|
|
2843
|
+
if (operation === 'add') return '';
|
|
2844
|
+
if (operation === 'delete') {
|
|
2845
|
+
const patch = String(ti?.patch || '');
|
|
2846
|
+
const removed: string[] = [];
|
|
2847
|
+
for (const line of patch.split('\n')) {
|
|
2848
|
+
if (line.startsWith('*** ') || line.startsWith('@@')) continue;
|
|
2849
|
+
if (line.startsWith('-')) removed.push(line.slice(1));
|
|
2850
|
+
}
|
|
2851
|
+
return removed.length ? removed.join('\n') : undefined;
|
|
2852
|
+
}
|
|
2853
|
+
let full = '';
|
|
2854
|
+
try { full = readFileSync(fp, 'utf-8'); } catch { return undefined; }
|
|
2855
|
+
if (toolName === 'Edit') {
|
|
2856
|
+
const oldString = String(ti?.old_string || '');
|
|
2857
|
+
const newString = String(ti?.new_string || '');
|
|
2858
|
+
if (newString && full.includes(newString)) return full.replace(newString, oldString);
|
|
2859
|
+
return undefined;
|
|
2860
|
+
}
|
|
2861
|
+
if (toolName === 'MultiEdit' && Array.isArray(ti?.edits)) {
|
|
2862
|
+
let before = full;
|
|
2863
|
+
for (const edit of [...ti.edits].reverse()) {
|
|
2864
|
+
const oldString = String(edit?.old_string || '');
|
|
2865
|
+
const newString = String(edit?.new_string || '');
|
|
2866
|
+
if (!newString || !before.includes(newString)) return undefined;
|
|
2867
|
+
before = before.replace(newString, oldString);
|
|
2868
|
+
}
|
|
2869
|
+
return before;
|
|
2870
|
+
}
|
|
2871
|
+
return full;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2728
2874
|
// Codex edits arrive as the apply_patch tool with the patch text in
|
|
2729
2875
|
// tool_input.command (lines: "*** Update/Add/Delete File: PATH", then hunks where
|
|
2730
2876
|
// "+" 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.
|
|
2877
|
+
// {file_path, old_string, new_string}; translate every file and hunk into an
|
|
2878
|
+
// explicit edit batch so one apply_patch call never drops evidence.
|
|
2735
2879
|
export function normalizeCodexApplyPatch(payload: any): void {
|
|
2736
2880
|
try {
|
|
2737
|
-
if (
|
|
2881
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(String((payload && payload.tool_name) || ''))) return;
|
|
2738
2882
|
const ti = payload.tool_input;
|
|
2739
2883
|
const patch = (ti && typeof ti === 'object')
|
|
2740
2884
|
? String(ti.command || ti.patch || ti.input || '')
|
|
2741
2885
|
: (typeof ti === 'string' ? ti : '');
|
|
2742
2886
|
if (!patch) return;
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2887
|
+
type Hunk = { oldLines: string[]; newLines: string[] };
|
|
2888
|
+
type PatchFile = {
|
|
2889
|
+
operation: 'Add' | 'Update' | 'Delete';
|
|
2890
|
+
path: string;
|
|
2891
|
+
moveTarget: string;
|
|
2892
|
+
hunks: Hunk[];
|
|
2893
|
+
fragment: string[];
|
|
2894
|
+
};
|
|
2895
|
+
const files: PatchFile[] = [];
|
|
2896
|
+
let current: PatchFile | null = null;
|
|
2897
|
+
let hunk: Hunk | null = null;
|
|
2898
|
+
const flushHunk = () => {
|
|
2899
|
+
if (current && hunk && (hunk.oldLines.length || hunk.newLines.length)) current.hunks.push(hunk);
|
|
2900
|
+
hunk = null;
|
|
2901
|
+
};
|
|
2902
|
+
const flushFile = () => {
|
|
2903
|
+
flushHunk();
|
|
2904
|
+
if (current) files.push(current);
|
|
2905
|
+
current = null;
|
|
2906
|
+
};
|
|
2747
2907
|
for (const ln of patch.split('\n')) {
|
|
2748
2908
|
const mFile = ln.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+?)\s*$/);
|
|
2749
|
-
if (mFile) {
|
|
2750
|
-
|
|
2909
|
+
if (mFile) {
|
|
2910
|
+
flushFile();
|
|
2911
|
+
const operation = ln.match(/^\*\*\*\s+(Update|Add|Delete)\s+File:/)?.[1] as 'Add' | 'Update' | 'Delete';
|
|
2912
|
+
current = {
|
|
2913
|
+
operation,
|
|
2914
|
+
path: mFile[1].trim(),
|
|
2915
|
+
moveTarget: '',
|
|
2916
|
+
hunks: [],
|
|
2917
|
+
fragment: [ln],
|
|
2918
|
+
};
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
if (!current) continue;
|
|
2922
|
+
current.fragment.push(ln);
|
|
2923
|
+
const mMove = ln.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
|
|
2924
|
+
if (mMove) { current.moveTarget = mMove[1].trim(); continue; }
|
|
2925
|
+
if (ln.indexOf('@@') === 0) {
|
|
2926
|
+
flushHunk();
|
|
2927
|
+
hunk = { oldLines: [], newLines: [] };
|
|
2928
|
+
const anchor = ln.slice(2).trim();
|
|
2929
|
+
if (anchor) { hunk.oldLines.push(anchor); hunk.newLines.push(anchor); }
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2751
2932
|
if (ln.indexOf('*** ') === 0) continue;
|
|
2933
|
+
hunk = hunk || { oldLines: [], newLines: [] };
|
|
2752
2934
|
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)); }
|
|
2935
|
+
if (c === '+') hunk.newLines.push(ln.slice(1));
|
|
2936
|
+
else if (c === '-') hunk.oldLines.push(ln.slice(1));
|
|
2937
|
+
else if (c === ' ') { hunk.oldLines.push(ln.slice(1)); hunk.newLines.push(ln.slice(1)); }
|
|
2756
2938
|
}
|
|
2757
|
-
|
|
2939
|
+
flushFile();
|
|
2940
|
+
if (!files.length) {
|
|
2758
2941
|
payload.__synkro_codex_patch_error =
|
|
2759
|
-
'Synkro
|
|
2942
|
+
'Synkro could not recover a file edit from this apply_patch payload.';
|
|
2760
2943
|
return;
|
|
2761
2944
|
}
|
|
2762
|
-
if (!files[0]) return;
|
|
2763
2945
|
const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2946
|
+
const normalized = files.map((file) => {
|
|
2947
|
+
const filePath = !isAbsolute(file.path) && cwd ? resolve(cwd, file.path) : file.path;
|
|
2948
|
+
const patchFragment = file.fragment.join('\n');
|
|
2949
|
+
if (file.operation === 'Add') {
|
|
2950
|
+
return {
|
|
2951
|
+
tool_name: 'Write',
|
|
2952
|
+
tool_input: {
|
|
2953
|
+
file_path: filePath,
|
|
2954
|
+
content: file.hunks.flatMap((item) => item.newLines).join('\n'),
|
|
2955
|
+
patch: patchFragment,
|
|
2956
|
+
},
|
|
2957
|
+
__synkro_codex_operation: 'add',
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
if (file.operation === 'Delete') {
|
|
2961
|
+
return {
|
|
2962
|
+
tool_name: 'Write',
|
|
2963
|
+
tool_input: { file_path: filePath, content: '', patch: patchFragment },
|
|
2964
|
+
__synkro_codex_operation: 'delete',
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
const edits = file.hunks.map((item) => ({
|
|
2968
|
+
old_string: item.oldLines.join('\n'),
|
|
2969
|
+
new_string: item.newLines.join('\n'),
|
|
2970
|
+
}));
|
|
2971
|
+
return {
|
|
2972
|
+
tool_name: edits.length > 1 ? 'MultiEdit' : 'Edit',
|
|
2973
|
+
tool_input: edits.length > 1
|
|
2974
|
+
? { file_path: filePath, edits, patch: patchFragment }
|
|
2975
|
+
: { file_path: filePath, ...(edits[0] || { old_string: '', new_string: '' }), patch: patchFragment },
|
|
2976
|
+
__synkro_codex_operation: file.moveTarget ? 'rename' : 'modify',
|
|
2977
|
+
...(file.moveTarget ? { __synkro_codex_move_target: file.moveTarget } : {}),
|
|
2978
|
+
};
|
|
2979
|
+
});
|
|
2980
|
+
payload.__synkro_codex_edits = normalized;
|
|
2981
|
+
Object.assign(payload, normalized[0]);
|
|
2767
2982
|
} catch { /* leave payload untouched on any parse error */ }
|
|
2768
2983
|
}
|
|
2769
2984
|
|
|
2985
|
+
type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
|
|
2986
|
+
|
|
2987
|
+
function completedCodexToolRecord(entry: any): any {
|
|
2988
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
2989
|
+
if (entry.type === 'response_item' && entry.payload && typeof entry.payload === 'object') {
|
|
2990
|
+
return entry.payload;
|
|
2991
|
+
}
|
|
2992
|
+
return entry.payload && typeof entry.payload === 'object' ? entry.payload : entry;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
function completedCodexToolInput(record: any): any {
|
|
2996
|
+
const raw = record?.input ?? record?.arguments ?? record?.command;
|
|
2997
|
+
if (raw && typeof raw === 'object') return raw;
|
|
2998
|
+
if (typeof raw !== 'string') return {};
|
|
2999
|
+
try {
|
|
3000
|
+
const parsed = JSON.parse(raw);
|
|
3001
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
3002
|
+
if (typeof parsed === 'string') return { command: parsed };
|
|
3003
|
+
} catch {}
|
|
3004
|
+
return { command: raw };
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
function containedCodexEditPath(repoRoot: string, filePath: string): string {
|
|
3008
|
+
if (!repoRoot || !filePath) return '';
|
|
3009
|
+
try {
|
|
3010
|
+
const lexicalRoot = resolve(repoRoot);
|
|
3011
|
+
const realRoot = realpathSync(lexicalRoot);
|
|
3012
|
+
const lexical = isAbsolute(filePath) ? resolve(filePath) : resolve(lexicalRoot, filePath);
|
|
3013
|
+
const isContained = (rel: string): boolean =>
|
|
3014
|
+
!!rel && rel !== '..' && !rel.startsWith('..' + '/') && !isAbsolute(rel);
|
|
3015
|
+
// Accept an absolute path expressed through either the lexical repo alias
|
|
3016
|
+
// (/var on macOS, a symlinked checkout) or its physical path, but always
|
|
3017
|
+
// continue from the physical root so new and existing files share one
|
|
3018
|
+
// canonical provenance path.
|
|
3019
|
+
let rel = relative(lexicalRoot, lexical);
|
|
3020
|
+
if (!isContained(rel)) {
|
|
3021
|
+
rel = relative(realRoot, lexical);
|
|
3022
|
+
if (!isContained(rel)) return '';
|
|
3023
|
+
}
|
|
3024
|
+
let candidate = resolve(realRoot, rel);
|
|
3025
|
+
// realpathSync requires the leaf to exist. Walk to the nearest existing
|
|
3026
|
+
// ancestor, resolve symlinks there, then append the missing suffix. This
|
|
3027
|
+
// both canonicalizes new files and rejects a new leaf beneath an in-repo
|
|
3028
|
+
// symlink that actually escapes the repository.
|
|
3029
|
+
const missing: string[] = [];
|
|
3030
|
+
let ancestor = candidate;
|
|
3031
|
+
while (!existsSync(ancestor)) {
|
|
3032
|
+
const parent = resolve(ancestor, '..');
|
|
3033
|
+
if (parent === ancestor) return '';
|
|
3034
|
+
missing.unshift(basename(ancestor));
|
|
3035
|
+
ancestor = parent;
|
|
3036
|
+
}
|
|
3037
|
+
candidate = resolve(realpathSync(ancestor), ...missing);
|
|
3038
|
+
const realRel = relative(realRoot, candidate);
|
|
3039
|
+
if (!realRel || realRel.startsWith('..') || isAbsolute(realRel)) return '';
|
|
3040
|
+
return existsSync(candidate) ? realpathSync(candidate) : candidate;
|
|
3041
|
+
} catch {
|
|
3042
|
+
return '';
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
/**
|
|
3047
|
+
* Recover completed Codex apply_patch calls from a bounded transcript tail.
|
|
3048
|
+
* A tool call is evidence only after a matching tool output exists. Calls are
|
|
3049
|
+
* expanded one event per file, restricted to the repo, and keyed by the stable
|
|
3050
|
+
* Codex call id so replay is idempotent server-side.
|
|
3051
|
+
*/
|
|
3052
|
+
export function extractCompletedCodexPatchEdits(
|
|
3053
|
+
transcript: string,
|
|
3054
|
+
repoRoot: string,
|
|
3055
|
+
): RecoveredCodexEdit[] {
|
|
3056
|
+
if (!transcript || !repoRoot) return [];
|
|
3057
|
+
const calls = new Map<string, { record: any; timestamp: string }>();
|
|
3058
|
+
const completed = new Set<string>();
|
|
3059
|
+
for (const line of transcript.split('\n')) {
|
|
3060
|
+
if (!line.trim()) continue;
|
|
3061
|
+
let entry: any;
|
|
3062
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
3063
|
+
const record = completedCodexToolRecord(entry);
|
|
3064
|
+
const type = String(record?.type || '');
|
|
3065
|
+
const callId = String(record?.call_id || record?.tool_call_id || record?.id || '');
|
|
3066
|
+
if (!callId) continue;
|
|
3067
|
+
if (type === 'custom_tool_call_output' || type === 'function_call_output') {
|
|
3068
|
+
completed.add(callId);
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
3071
|
+
if (type !== 'custom_tool_call' && type !== 'function_call') continue;
|
|
3072
|
+
const name = String(record?.name || record?.tool_name || '');
|
|
3073
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(name)) continue;
|
|
3074
|
+
calls.set(callId, {
|
|
3075
|
+
record,
|
|
3076
|
+
timestamp: String(entry?.timestamp || record?.timestamp || ''),
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
const results: RecoveredCodexEdit[] = [];
|
|
3081
|
+
const seen = new Set<string>();
|
|
3082
|
+
const candidates = [...calls.entries()].filter(([callId]) => completed.has(callId)).slice(-100);
|
|
3083
|
+
for (const [callId, call] of candidates) {
|
|
3084
|
+
const toolInput = completedCodexToolInput(call.record);
|
|
3085
|
+
const patch = String(toolInput?.command || toolInput?.patch || toolInput?.input || '');
|
|
3086
|
+
if (!patch || patch.length > 100000) continue;
|
|
3087
|
+
const raw: any = {
|
|
3088
|
+
cwd: repoRoot,
|
|
3089
|
+
tool_name: String(call.record?.name || call.record?.tool_name || 'apply_patch'),
|
|
3090
|
+
tool_input: toolInput,
|
|
3091
|
+
tool_call_id: callId,
|
|
3092
|
+
_ts: call.timestamp || undefined,
|
|
3093
|
+
};
|
|
3094
|
+
normalizeCodexApplyPatch(raw);
|
|
3095
|
+
const edits = Array.isArray(raw.__synkro_codex_edits) ? raw.__synkro_codex_edits : [];
|
|
3096
|
+
for (const edit of edits) {
|
|
3097
|
+
if (results.length >= 50) return results;
|
|
3098
|
+
const fp = containedCodexEditPath(repoRoot, filePathFromToolInput(edit?.tool_input));
|
|
3099
|
+
if (!fp) continue;
|
|
3100
|
+
const dedupeKey = callId + '\u0000' + fp;
|
|
3101
|
+
if (seen.has(dedupeKey)) continue;
|
|
3102
|
+
seen.add(dedupeKey);
|
|
3103
|
+
const payload: any = {
|
|
3104
|
+
cwd: repoRoot,
|
|
3105
|
+
tool_name: edit.tool_name,
|
|
3106
|
+
tool_input: { ...(edit.tool_input || {}), file_path: fp },
|
|
3107
|
+
tool_call_id: callId,
|
|
3108
|
+
_ts: call.timestamp || undefined,
|
|
3109
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3110
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3111
|
+
__synkro_edit_source: 'transcript',
|
|
3112
|
+
};
|
|
3113
|
+
const baseContent = gatherPreEditContentFromPost(
|
|
3114
|
+
fp,
|
|
3115
|
+
String(payload.tool_name || ''),
|
|
3116
|
+
payload.tool_input,
|
|
3117
|
+
String(payload.__synkro_codex_operation || ''),
|
|
3118
|
+
);
|
|
3119
|
+
if (baseContent === undefined) continue;
|
|
3120
|
+
results.push({
|
|
3121
|
+
payload,
|
|
3122
|
+
baseContent,
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
return results;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
/**
|
|
3130
|
+
* Recover only the latest completed apply_patch for each file in the current
|
|
3131
|
+
* Codex turn. Stop hooks may fire repeatedly while an agent self-corrects, so
|
|
3132
|
+
* scanning the latest on-disk state avoids re-blocking an earlier vulnerable
|
|
3133
|
+
* patch after a later patch fixed it.
|
|
3134
|
+
*/
|
|
3135
|
+
export function extractCompletedCodexTurnPatchEdits(
|
|
3136
|
+
transcript: string,
|
|
3137
|
+
repoRoot: string,
|
|
3138
|
+
): RecoveredCodexEdit[] {
|
|
3139
|
+
if (!transcript || !repoRoot) return [];
|
|
3140
|
+
const lines = transcript.split('\n');
|
|
3141
|
+
let latestUserLine = -1;
|
|
3142
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3143
|
+
if (!lines[index].trim()) continue;
|
|
3144
|
+
let entry: any;
|
|
3145
|
+
try { entry = JSON.parse(lines[index]); } catch { continue; }
|
|
3146
|
+
const canonicalUser = entry?.type === 'event_msg'
|
|
3147
|
+
&& entry?.payload?.type === 'user_message';
|
|
3148
|
+
const compatibilityUser = entry?.type === 'response_item'
|
|
3149
|
+
&& entry?.payload?.type === 'message'
|
|
3150
|
+
&& entry?.payload?.role === 'user';
|
|
3151
|
+
if (canonicalUser || compatibilityUser) latestUserLine = index;
|
|
3152
|
+
}
|
|
3153
|
+
const turnTranscript = lines.slice(latestUserLine + 1).join('\n');
|
|
3154
|
+
const recovered = extractCompletedCodexPatchEdits(turnTranscript, repoRoot);
|
|
3155
|
+
const latestByFile = new Map<string, RecoveredCodexEdit>();
|
|
3156
|
+
for (const item of recovered) {
|
|
3157
|
+
const filePath = filePathFromToolInput(item.payload?.tool_input);
|
|
3158
|
+
if (filePath) latestByFile.set(filePath, item);
|
|
3159
|
+
}
|
|
3160
|
+
return [...latestByFile.values()];
|
|
3161
|
+
}
|
|
3162
|
+
|
|
2770
3163
|
interface StubOpts {
|
|
2771
3164
|
needsFile?: boolean;
|
|
2772
3165
|
needsTranscript?: boolean;
|
|
@@ -2776,6 +3169,119 @@ interface StubOpts {
|
|
|
2776
3169
|
needsPlan?: boolean;
|
|
2777
3170
|
telemetry?: boolean;
|
|
2778
3171
|
harness?: string;
|
|
3172
|
+
postEdit?: boolean;
|
|
3173
|
+
/** Recover only this Codex turn's latest completed patch per file. */
|
|
3174
|
+
codexTurnEdits?: boolean;
|
|
3175
|
+
/** Translate a security PreToolUse deny into Codex's Stop block response. */
|
|
3176
|
+
stopContract?: boolean;
|
|
3177
|
+
/** Codex SubagentStart/SubagentStop report the parent as session_id and
|
|
3178
|
+
* the child as agent_id. Remap them to the ordinary child-session envelope. */
|
|
3179
|
+
subagent?: boolean;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
function isContainedFile(root: string, candidate: string): boolean {
|
|
3183
|
+
try {
|
|
3184
|
+
const rel = relative(root, candidate);
|
|
3185
|
+
return !!rel && !rel.startsWith('..') && !isAbsolute(rel) && statSync(candidate).isFile();
|
|
3186
|
+
} catch { return false; }
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
function safeCodexSessionId(value: string): boolean {
|
|
3190
|
+
if (!value || value.length > 200) return false;
|
|
3191
|
+
for (const ch of value) {
|
|
3192
|
+
const code = ch.charCodeAt(0);
|
|
3193
|
+
const alphaNumeric = (code >= 48 && code <= 57)
|
|
3194
|
+
|| (code >= 65 && code <= 90)
|
|
3195
|
+
|| (code >= 97 && code <= 122);
|
|
3196
|
+
if (!alphaNumeric && ch !== '-' && ch !== '_') return false;
|
|
3197
|
+
}
|
|
3198
|
+
return true;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
function readJsonlHeader(path: string): string {
|
|
3202
|
+
let fd = -1;
|
|
3203
|
+
try {
|
|
3204
|
+
fd = openSync(path, 'r');
|
|
3205
|
+
const buf = Buffer.alloc(16384);
|
|
3206
|
+
const count = readSync(fd, buf, 0, buf.length, 0);
|
|
3207
|
+
const head = buf.subarray(0, count).toString('utf-8');
|
|
3208
|
+
const newline = head.indexOf('\n');
|
|
3209
|
+
return newline >= 0 ? head.slice(0, newline) : head;
|
|
3210
|
+
} catch { return ''; }
|
|
3211
|
+
finally { if (fd >= 0) { try { closeSync(fd); } catch {} } }
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
/**
|
|
3215
|
+
* Codex hook payloads identify the rollout by session_id but do not always send
|
|
3216
|
+
* transcript_path. Resolve that ID only within CODEX_HOME/sessions (or the
|
|
3217
|
+
* default ~/.codex/sessions), then cache the exact contained file so subsequent
|
|
3218
|
+
* tool hooks do not recursively scan the rollout tree.
|
|
3219
|
+
*/
|
|
3220
|
+
export function resolveHookTranscriptPath(payload: any, harness: string, sessionId: string): string {
|
|
3221
|
+
const explicit = [
|
|
3222
|
+
payload?.transcript_path,
|
|
3223
|
+
payload?.rollout_path,
|
|
3224
|
+
payload?.session_path,
|
|
3225
|
+
].find((value) => typeof value === 'string' && value);
|
|
3226
|
+
if (typeof explicit === 'string') return explicit;
|
|
3227
|
+
if (harness !== 'codex' || !safeCodexSessionId(sessionId)) return '';
|
|
3228
|
+
|
|
3229
|
+
const homeRoots = [
|
|
3230
|
+
process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : '',
|
|
3231
|
+
resolve(join(HOME, '.codex')),
|
|
3232
|
+
].filter((value, index, all) => !!value && all.indexOf(value) === index);
|
|
3233
|
+
const sessionRoots = homeRoots.map((root) => join(root, 'sessions')).filter((root) => existsSync(root));
|
|
3234
|
+
if (!sessionRoots.length) return '';
|
|
3235
|
+
|
|
3236
|
+
const cachePath = join(
|
|
3237
|
+
HOME,
|
|
3238
|
+
'.synkro',
|
|
3239
|
+
'codex-transcript-' + createHash('sha256').update(sessionId).digest('hex').slice(0, 16),
|
|
3240
|
+
);
|
|
3241
|
+
try {
|
|
3242
|
+
const cached = resolve(readFileSync(cachePath, 'utf-8').trim());
|
|
3243
|
+
if (sessionRoots.some((root) => isContainedFile(root, cached))) return cached;
|
|
3244
|
+
} catch {}
|
|
3245
|
+
|
|
3246
|
+
const candidates: string[] = [];
|
|
3247
|
+
for (const root of sessionRoots) {
|
|
3248
|
+
let entries: string[] = [];
|
|
3249
|
+
try { entries = readdirSync(root, { recursive: true, encoding: 'utf-8' }) as string[]; } catch {}
|
|
3250
|
+
for (const entry of entries) {
|
|
3251
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
3252
|
+
const candidate = resolve(join(root, entry));
|
|
3253
|
+
if (isContainedFile(root, candidate)) candidates.push(candidate);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
let found = candidates.find((candidate) => candidate.includes(sessionId)) || '';
|
|
3258
|
+
if (!found) {
|
|
3259
|
+
const recent = candidates
|
|
3260
|
+
.map((candidate) => {
|
|
3261
|
+
try { return { candidate, mtime: statSync(candidate).mtimeMs }; }
|
|
3262
|
+
catch { return { candidate, mtime: 0 }; }
|
|
3263
|
+
})
|
|
3264
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
3265
|
+
.slice(0, 100);
|
|
3266
|
+
for (const item of recent) {
|
|
3267
|
+
try {
|
|
3268
|
+
const first = JSON.parse(readJsonlHeader(item.candidate));
|
|
3269
|
+
const candidateId = String(first?.payload?.session_id || first?.payload?.id || '');
|
|
3270
|
+
if (first?.type === 'session_meta' && candidateId === sessionId) {
|
|
3271
|
+
found = item.candidate;
|
|
3272
|
+
break;
|
|
3273
|
+
}
|
|
3274
|
+
} catch {}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
if (found) {
|
|
3279
|
+
try {
|
|
3280
|
+
mkdirSync(join(HOME, '.synkro'), { recursive: true });
|
|
3281
|
+
writeFileSync(cachePath, found, { encoding: 'utf-8', mode: 0o600 });
|
|
3282
|
+
} catch {}
|
|
3283
|
+
}
|
|
3284
|
+
return found;
|
|
2779
3285
|
}
|
|
2780
3286
|
|
|
2781
3287
|
export async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {
|
|
@@ -2792,6 +3298,17 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2792
3298
|
// Codex: rewrite apply_patch tool_input into the CC edit shape before any
|
|
2793
3299
|
// surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
|
|
2794
3300
|
if (harness === 'codex') normalizeCodexApplyPatch(payload);
|
|
3301
|
+
if (harness === 'codex' && opts.subagent) {
|
|
3302
|
+
const parentSessionId = String(payload?.session_id || '');
|
|
3303
|
+
const childSessionId = String(payload?.agent_id || '');
|
|
3304
|
+
if (parentSessionId && childSessionId) {
|
|
3305
|
+
payload.parent_session_id = parentSessionId;
|
|
3306
|
+
payload.session_id = childSessionId;
|
|
3307
|
+
if (!payload.transcript_path && payload.agent_transcript_path) {
|
|
3308
|
+
payload.transcript_path = payload.agent_transcript_path;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
2795
3312
|
telemPayload = payload;
|
|
2796
3313
|
telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';
|
|
2797
3314
|
telemSessionId = String(payload?.session_id || payload?.conversation_id || '');
|
|
@@ -2862,14 +3379,40 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2862
3379
|
const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };
|
|
2863
3380
|
|
|
2864
3381
|
if (opts.needsFile) {
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3382
|
+
const normalizedEdits = harness === 'codex' && Array.isArray(payload.__synkro_codex_edits)
|
|
3383
|
+
? payload.__synkro_codex_edits
|
|
3384
|
+
: [{ tool_name: payload.tool_name, tool_input: payload.tool_input }];
|
|
3385
|
+
const editBatch = normalizedEdits.map((edit: any) => {
|
|
3386
|
+
const childPayload = {
|
|
3387
|
+
...payload,
|
|
3388
|
+
tool_name: edit.tool_name,
|
|
3389
|
+
tool_input: edit.tool_input,
|
|
3390
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3391
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3392
|
+
};
|
|
3393
|
+
delete childPayload.__synkro_codex_edits;
|
|
3394
|
+
const fp = filePathFromToolInput(childPayload.tool_input || {});
|
|
3395
|
+
const canRead = fp && (existsSync(fp) || (opts.postEdit && childPayload.__synkro_codex_operation === 'add'));
|
|
3396
|
+
const bc = canRead
|
|
3397
|
+
? (opts.postEdit
|
|
3398
|
+
? gatherPreEditContentFromPost(
|
|
3399
|
+
fp,
|
|
3400
|
+
String(childPayload.tool_name || ''),
|
|
3401
|
+
childPayload.tool_input || {},
|
|
3402
|
+
String(childPayload.__synkro_codex_operation || ''),
|
|
3403
|
+
)
|
|
3404
|
+
: gatherBaseContent(fp, childPayload.tool_input || {}))
|
|
3405
|
+
: undefined;
|
|
3406
|
+
return { payload: childPayload, ...(bc !== undefined ? { baseContent: bc } : {}) };
|
|
3407
|
+
});
|
|
3408
|
+
if (editBatch.length) {
|
|
3409
|
+
envelope.editBatch = editBatch;
|
|
3410
|
+
envelope.payload = editBatch[0].payload;
|
|
3411
|
+
if (editBatch[0].baseContent !== undefined) envelope.baseContent = editBatch[0].baseContent;
|
|
2869
3412
|
}
|
|
2870
3413
|
}
|
|
2871
3414
|
if (opts.needsTranscript) {
|
|
2872
|
-
const tp =
|
|
3415
|
+
const tp = resolveHookTranscriptPath(payload, harness, sessionId);
|
|
2873
3416
|
if (tp && existsSync(tp)) {
|
|
2874
3417
|
try {
|
|
2875
3418
|
const t = readFileSync(tp, 'utf-8');
|
|
@@ -2881,9 +3424,34 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2881
3424
|
// by ts, so we no longer depend on the whole file for absolute message indices.
|
|
2882
3425
|
const cap = opts.fullTranscript ? 400000 : 200000;
|
|
2883
3426
|
envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);
|
|
3427
|
+
if (harness === 'codex') {
|
|
3428
|
+
const recovered = opts.codexTurnEdits
|
|
3429
|
+
? extractCompletedCodexTurnPatchEdits(envelope.transcript, root || cwd)
|
|
3430
|
+
: extractCompletedCodexPatchEdits(envelope.transcript, root || cwd);
|
|
3431
|
+
if (recovered.length) {
|
|
3432
|
+
envelope.transcriptEdits = recovered;
|
|
3433
|
+
if (opts.codexTurnEdits) {
|
|
3434
|
+
envelope.editBatch = recovered.map((item) => ({
|
|
3435
|
+
...item,
|
|
3436
|
+
payload: {
|
|
3437
|
+
...item.payload,
|
|
3438
|
+
__synkro_post_edit_enforcement: true,
|
|
3439
|
+
},
|
|
3440
|
+
}));
|
|
3441
|
+
envelope.payload = envelope.editBatch[0].payload;
|
|
3442
|
+
envelope.baseContent = envelope.editBatch[0].baseContent;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
2884
3446
|
} catch {}
|
|
2885
3447
|
}
|
|
2886
3448
|
}
|
|
3449
|
+
// No completed apply_patch in this turn means there is nothing for the
|
|
3450
|
+
// fallback to scan. Stay silent and avoid touching the server/database.
|
|
3451
|
+
if (opts.codexTurnEdits && (!Array.isArray(envelope.editBatch) || envelope.editBatch.length === 0)) {
|
|
3452
|
+
out(failOpen(harness));
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
2887
3455
|
if (opts.needsPlan) {
|
|
2888
3456
|
const ti = payload.tool_input || {};
|
|
2889
3457
|
envelope.planText = String(ti.plan || ti.content || payload.plan || '');
|
|
@@ -2896,7 +3464,8 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2896
3464
|
// prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from
|
|
2897
3465
|
// being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)
|
|
2898
3466
|
// and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.
|
|
2899
|
-
const timeoutMs = surface === '
|
|
3467
|
+
const timeoutMs = surface === 'cwe-precheck' ? 48000
|
|
3468
|
+
: surface === 'bash-followup' ? 32000
|
|
2900
3469
|
: surface === 'prompt-submit' ? 3500
|
|
2901
3470
|
: (opts.telemetry ? 6000 : 28000);
|
|
2902
3471
|
// Cloud has no local container to reach. Post the SAME envelope to the org's grader
|
|
@@ -2937,7 +3506,10 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2937
3506
|
} catch (e) { /* connection refused / timeout → retry below */ }
|
|
2938
3507
|
if (i < attempts - 1) await new Promise((r) => setTimeout(r, 500 * (i + 1)));
|
|
2939
3508
|
}
|
|
2940
|
-
const
|
|
3509
|
+
const rawResponseText = text || failOpen(harness);
|
|
3510
|
+
const responseText = opts.stopContract && harness === 'codex'
|
|
3511
|
+
? codexStopResponse(rawResponseText)
|
|
3512
|
+
: rawResponseText;
|
|
2941
3513
|
out(responseText);
|
|
2942
3514
|
emitStubTelemetry(surface, harness, telemPayload, responseText, Date.now() - startedAt, telemCwd, telemSessionId);
|
|
2943
3515
|
} catch (err) {
|
|
@@ -3061,8 +3633,16 @@ function toolInputSummary(toolName: string, toolInput: any, includeCommand: bool
|
|
|
3061
3633
|
// cc_model is derived by the caller (bounded tail read) and passed in; this stays
|
|
3062
3634
|
// allocation-only so the dormant hot path never touches the transcript.
|
|
3063
3635
|
function telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {
|
|
3636
|
+
const normalizedHarness = String(harness || '').trim().toLowerCase().replace(/[\\s-]+/g, '_');
|
|
3637
|
+
const agentKind = normalizedHarness === 'cursor'
|
|
3638
|
+
? 'cursor'
|
|
3639
|
+
: normalizedHarness === 'codex'
|
|
3640
|
+
? 'codex'
|
|
3641
|
+
: normalizedHarness === 'cc' || normalizedHarness === 'claude_code'
|
|
3642
|
+
? 'claude_code'
|
|
3643
|
+
: undefined;
|
|
3064
3644
|
return {
|
|
3065
|
-
agent_kind:
|
|
3645
|
+
agent_kind: agentKind,
|
|
3066
3646
|
cc_session_id: sessionId || undefined,
|
|
3067
3647
|
cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,
|
|
3068
3648
|
cc_model: ccModel || undefined,
|
|
@@ -3164,6 +3744,7 @@ function emitStubTelemetry(
|
|
|
3164
3744
|
}
|
|
3165
3745
|
`;
|
|
3166
3746
|
STUB_EDIT_PRECHECK_TS = stubHook("edit-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3747
|
+
STUB_EDIT_FOLLOWUP_TS = stubHook("edit-followup", "{ needsFile: true, needsTranscript: true, postEdit: true }");
|
|
3167
3748
|
STUB_CWE_PRECHECK_TS = stubHook("cwe-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3168
3749
|
STUB_CVE_PRECHECK_TS = stubHook("cve-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3169
3750
|
STUB_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true }");
|
|
@@ -3175,6 +3756,10 @@ function emitStubTelemetry(
|
|
|
3175
3756
|
STUB_STOP_SUMMARY_TS = stubHook("stop-summary", "{ needsTranscript: true, fullTranscript: true }");
|
|
3176
3757
|
STUB_SESSION_START_TS = stubHook("session-start", "{ telemetry: true }");
|
|
3177
3758
|
STUB_TRANSCRIPT_SYNC_TS = stubHook("transcript-sync", "{ needsTranscript: true, fullTranscript: true, telemetry: true }");
|
|
3759
|
+
STUB_CODEX_CWE_STOP_TS = stubHook("cwe-precheck", "{ needsTranscript: true, fullTranscript: true, codexTurnEdits: true, stopContract: true }");
|
|
3760
|
+
STUB_CODEX_CVE_STOP_TS = stubHook("cve-precheck", "{ needsTranscript: true, fullTranscript: true, codexTurnEdits: true, stopContract: true }");
|
|
3761
|
+
STUB_SUBAGENT_START_TS = stubHook("subagent-start", "{ telemetry: true, subagent: true }");
|
|
3762
|
+
STUB_SUBAGENT_STOP_TS = stubHook("subagent-stop", "{ needsTranscript: true, fullTranscript: true, telemetry: true, subagent: true }");
|
|
3178
3763
|
STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
|
|
3179
3764
|
STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
|
|
3180
3765
|
STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
|
|
@@ -3307,7 +3892,7 @@ try {
|
|
|
3307
3892
|
const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
|
|
3308
3893
|
method: 'POST',
|
|
3309
3894
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
3310
|
-
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x',
|
|
3895
|
+
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
|
|
3311
3896
|
signal: AbortSignal.timeout(1000),
|
|
3312
3897
|
});
|
|
3313
3898
|
if (resp.ok) {
|
|
@@ -3346,8 +3931,14 @@ const PORT = process.env.SYNKRO_MCP_PORT || '18931';
|
|
|
3346
3931
|
function mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }
|
|
3347
3932
|
const chunks: Buffer[] = [];
|
|
3348
3933
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3934
|
+
let originalToolInput: Record<string, unknown> = {};
|
|
3935
|
+
let hookEventName = 'PreToolUse';
|
|
3349
3936
|
try {
|
|
3350
3937
|
const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
|
|
3938
|
+
hookEventName = String(payload.hook_event_name || payload.hookEventName || 'PreToolUse');
|
|
3939
|
+
if (payload.tool_input && typeof payload.tool_input === 'object' && !Array.isArray(payload.tool_input)) {
|
|
3940
|
+
originalToolInput = payload.tool_input;
|
|
3941
|
+
}
|
|
3351
3942
|
await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {
|
|
3352
3943
|
method: 'POST',
|
|
3353
3944
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
@@ -3355,7 +3946,29 @@ try {
|
|
|
3355
3946
|
signal: AbortSignal.timeout(5000),
|
|
3356
3947
|
});
|
|
3357
3948
|
} catch {}
|
|
3358
|
-
|
|
3949
|
+
// PreToolUse enforcement and Codex's native approval are separate lifecycle
|
|
3950
|
+
// stages. The policy gate already allowed this exact activate_standard call;
|
|
3951
|
+
// approve its subsequent PermissionRequest so headless approval_policy=never
|
|
3952
|
+
// can execute it. This script is registered only for the activation matcher,
|
|
3953
|
+
// never as a blanket MCP approval hook.
|
|
3954
|
+
if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionRequest') {
|
|
3955
|
+
process.stdout.write(JSON.stringify({
|
|
3956
|
+
hookSpecificOutput: {
|
|
3957
|
+
hookEventName: 'PermissionRequest',
|
|
3958
|
+
decision: { behavior: 'allow' },
|
|
3959
|
+
},
|
|
3960
|
+
}) + '\\n');
|
|
3961
|
+
} else if (process.env.SYNKRO_HOOK_FORMAT === 'codex') {
|
|
3962
|
+
process.stdout.write(JSON.stringify({
|
|
3963
|
+
hookSpecificOutput: {
|
|
3964
|
+
hookEventName: 'PreToolUse',
|
|
3965
|
+
permissionDecision: 'allow',
|
|
3966
|
+
updatedInput: originalToolInput,
|
|
3967
|
+
},
|
|
3968
|
+
}) + '\\n');
|
|
3969
|
+
} else {
|
|
3970
|
+
process.stdout.write('{}\\n');
|
|
3971
|
+
}
|
|
3359
3972
|
`;
|
|
3360
3973
|
STUB_CURSOR_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true, harness: 'cursor' }");
|
|
3361
3974
|
STUB_CURSOR_SKILL_JUDGE_TS = stubHook("skill-judge", "{ needsFile: true, needsTranscript: true, harness: 'cursor' }");
|
|
@@ -3382,9 +3995,9 @@ __export(stub_exports, {
|
|
|
3382
3995
|
saveCredentials: () => saveCredentials
|
|
3383
3996
|
});
|
|
3384
3997
|
import { createServer } from "http";
|
|
3385
|
-
import { writeFileSync as writeFileSync11, readFileSync as
|
|
3386
|
-
import { homedir as
|
|
3387
|
-
import { join as
|
|
3998
|
+
import { writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
3999
|
+
import { homedir as homedir14, platform as platform2 } from "os";
|
|
4000
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
3388
4001
|
import { execFile } from "child_process";
|
|
3389
4002
|
import jwt from "jsonwebtoken";
|
|
3390
4003
|
function openBrowser(url) {
|
|
@@ -3423,7 +4036,7 @@ function loadCredentials() {
|
|
|
3423
4036
|
return null;
|
|
3424
4037
|
}
|
|
3425
4038
|
try {
|
|
3426
|
-
const content =
|
|
4039
|
+
const content = readFileSync14(AUTH_FILE, "utf8");
|
|
3427
4040
|
return JSON.parse(content);
|
|
3428
4041
|
} catch (error) {
|
|
3429
4042
|
return null;
|
|
@@ -3711,7 +4324,7 @@ var init_stub = __esm({
|
|
|
3711
4324
|
PORT = 8100;
|
|
3712
4325
|
RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
|
|
3713
4326
|
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 ||
|
|
4327
|
+
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join12(homedir14(), ".synkro", "credentials.json");
|
|
3715
4328
|
RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
3716
4329
|
SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
|
|
3717
4330
|
ERROR_HTML = `
|
|
@@ -3948,9 +4561,9 @@ __export(claudeDesktopTap_exports, {
|
|
|
3948
4561
|
runClaudeDesktopTap: () => runClaudeDesktopTap
|
|
3949
4562
|
});
|
|
3950
4563
|
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
|
|
4564
|
+
import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
|
|
4565
|
+
import { join as join13 } from "path";
|
|
4566
|
+
import { homedir as homedir15 } from "os";
|
|
3954
4567
|
function claudeDesktopInstalled() {
|
|
3955
4568
|
return process.platform === "darwin" && existsSync17("/Applications/Claude.app/Contents/MacOS/Claude");
|
|
3956
4569
|
}
|
|
@@ -4066,7 +4679,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4066
4679
|
}
|
|
4067
4680
|
let token = "";
|
|
4068
4681
|
try {
|
|
4069
|
-
token =
|
|
4682
|
+
token = readFileSync15(JWT_PATH, "utf-8").trim();
|
|
4070
4683
|
} catch {
|
|
4071
4684
|
}
|
|
4072
4685
|
if (!token) {
|
|
@@ -4087,13 +4700,13 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4087
4700
|
} catch (err) {
|
|
4088
4701
|
console.log(` \u26A0 Could not register Claude Desktop MCP: ${err.message}`);
|
|
4089
4702
|
}
|
|
4090
|
-
const cdRoot =
|
|
4703
|
+
const cdRoot = join13(homedir15(), ".synkro", "cd-sessions");
|
|
4091
4704
|
mkdirSync10(cdRoot, { recursive: true });
|
|
4092
|
-
const sessionDir = mkdtempSync(
|
|
4093
|
-
writeFileSync12(
|
|
4094
|
-
writeFileSync12(
|
|
4095
|
-
writeFileSync12(
|
|
4096
|
-
const runnerPath =
|
|
4705
|
+
const sessionDir = mkdtempSync(join13(cdRoot, "synkro-cd-"));
|
|
4706
|
+
writeFileSync12(join13(sessionDir, "tap.py"), ADDON_PY, "utf-8");
|
|
4707
|
+
writeFileSync12(join13(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
|
|
4708
|
+
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
4709
|
+
const runnerPath = join13(sessionDir, "run.sh");
|
|
4097
4710
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
4098
4711
|
await new Promise((resolve6) => {
|
|
4099
4712
|
const child = spawn2("bash", [runnerPath], {
|
|
@@ -4127,7 +4740,7 @@ var init_claudeDesktopTap = __esm({
|
|
|
4127
4740
|
TURN_VERDICTS_URL = "http://127.0.0.1:18931/api/local/turn-verdicts";
|
|
4128
4741
|
TURN_VERDICT_URL = "http://127.0.0.1:18931/api/local/turn-verdict";
|
|
4129
4742
|
MCP_EVENT_URL = "http://127.0.0.1:18931/api/local/mcp/event";
|
|
4130
|
-
JWT_PATH =
|
|
4743
|
+
JWT_PATH = join13(homedir15(), ".synkro", ".mcp-jwt");
|
|
4131
4744
|
ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib
|
|
4132
4745
|
from mitmproxy import http
|
|
4133
4746
|
|
|
@@ -4264,6 +4877,7 @@ def _emit_mcp_event(server, tool, ev_type, eid, convo_id=""):
|
|
|
4264
4877
|
"tool_name": (str(tool)[:120] if tool else None),
|
|
4265
4878
|
"decision": ("allowed" if ev_type == "tool_call" else None),
|
|
4266
4879
|
"repo": "claude-desktop",
|
|
4880
|
+
"harness": "claude_desktop",
|
|
4267
4881
|
}).encode("utf-8")
|
|
4268
4882
|
_post_json(MCP_EVENT_URL, payload, 5)
|
|
4269
4883
|
except Exception:
|
|
@@ -5243,9 +5857,9 @@ __export(macKeychain_exports, {
|
|
|
5243
5857
|
writeCursorApiKey: () => writeCursorApiKey,
|
|
5244
5858
|
writeRefreshAgent: () => writeRefreshAgent
|
|
5245
5859
|
});
|
|
5246
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as
|
|
5247
|
-
import { homedir as
|
|
5248
|
-
import { join as
|
|
5860
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync16 } from "fs";
|
|
5861
|
+
import { homedir as homedir16, platform as platform3 } from "os";
|
|
5862
|
+
import { join as join14 } from "path";
|
|
5249
5863
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5250
5864
|
function needsKeychainBridge() {
|
|
5251
5865
|
return platform3() === "darwin";
|
|
@@ -5268,7 +5882,7 @@ function exportKeychainCreds() {
|
|
|
5268
5882
|
let changed = true;
|
|
5269
5883
|
try {
|
|
5270
5884
|
if (existsSync18(CLAUDE_CREDS_FILE)) {
|
|
5271
|
-
changed =
|
|
5885
|
+
changed = readFileSync16(CLAUDE_CREDS_FILE, "utf-8") !== blob;
|
|
5272
5886
|
}
|
|
5273
5887
|
} catch {
|
|
5274
5888
|
}
|
|
@@ -5278,7 +5892,7 @@ function exportKeychainCreds() {
|
|
|
5278
5892
|
}
|
|
5279
5893
|
function cursorApiKeyConfigured() {
|
|
5280
5894
|
try {
|
|
5281
|
-
return existsSync18(CURSOR_API_KEY_FILE) &&
|
|
5895
|
+
return existsSync18(CURSOR_API_KEY_FILE) && readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
|
|
5282
5896
|
} catch {
|
|
5283
5897
|
return false;
|
|
5284
5898
|
}
|
|
@@ -5294,7 +5908,7 @@ function writeCursorApiKey(key) {
|
|
|
5294
5908
|
async function validateCursorApiKey() {
|
|
5295
5909
|
let key;
|
|
5296
5910
|
try {
|
|
5297
|
-
key =
|
|
5911
|
+
key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
|
|
5298
5912
|
} catch {
|
|
5299
5913
|
return null;
|
|
5300
5914
|
}
|
|
@@ -5315,7 +5929,7 @@ async function validateCursorApiKey() {
|
|
|
5315
5929
|
function credsAreStale() {
|
|
5316
5930
|
if (!existsSync18(CLAUDE_CREDS_FILE)) return true;
|
|
5317
5931
|
try {
|
|
5318
|
-
const raw =
|
|
5932
|
+
const raw = readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5319
5933
|
const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0;
|
|
5320
5934
|
if (!exp) return true;
|
|
5321
5935
|
return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1e3;
|
|
@@ -5330,7 +5944,7 @@ function writeRefreshAgent(synkroBinPath) {
|
|
|
5330
5944
|
if (platform3() !== "darwin") {
|
|
5331
5945
|
throw new KeychainExportError("writeRefreshAgent is darwin-only");
|
|
5332
5946
|
}
|
|
5333
|
-
mkdirSync11(
|
|
5947
|
+
mkdirSync11(join14(homedir16(), "Library", "LaunchAgents"), { recursive: true });
|
|
5334
5948
|
mkdirSync11(SYNKRO_DIR6, { recursive: true });
|
|
5335
5949
|
const script = `#!/bin/bash
|
|
5336
5950
|
# Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.
|
|
@@ -5427,7 +6041,7 @@ function refreshCreds() {
|
|
|
5427
6041
|
}
|
|
5428
6042
|
function readExportedCreds() {
|
|
5429
6043
|
try {
|
|
5430
|
-
return
|
|
6044
|
+
return readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5431
6045
|
} catch {
|
|
5432
6046
|
return null;
|
|
5433
6047
|
}
|
|
@@ -5436,16 +6050,16 @@ var SYNKRO_DIR6, CLAUDE_CREDS_DIR, CLAUDE_CREDS_FILE, CURSOR_CREDS_DIR, CURSOR_A
|
|
|
5436
6050
|
var init_macKeychain = __esm({
|
|
5437
6051
|
"cli/local-cc/macKeychain.ts"() {
|
|
5438
6052
|
"use strict";
|
|
5439
|
-
SYNKRO_DIR6 =
|
|
5440
|
-
CLAUDE_CREDS_DIR =
|
|
5441
|
-
CLAUDE_CREDS_FILE =
|
|
5442
|
-
CURSOR_CREDS_DIR =
|
|
5443
|
-
CURSOR_API_KEY_FILE =
|
|
6053
|
+
SYNKRO_DIR6 = join14(homedir16(), ".synkro");
|
|
6054
|
+
CLAUDE_CREDS_DIR = join14(SYNKRO_DIR6, "claude-creds");
|
|
6055
|
+
CLAUDE_CREDS_FILE = join14(CLAUDE_CREDS_DIR, ".credentials.json");
|
|
6056
|
+
CURSOR_CREDS_DIR = join14(SYNKRO_DIR6, "cursor-creds");
|
|
6057
|
+
CURSOR_API_KEY_FILE = join14(CURSOR_CREDS_DIR, "api-key");
|
|
5444
6058
|
KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
5445
6059
|
LAUNCHD_LABEL = "com.synkro.cli.claude-creds-refresh";
|
|
5446
|
-
LAUNCHD_PLIST =
|
|
5447
|
-
REFRESH_SCRIPT =
|
|
5448
|
-
REFRESH_LOG =
|
|
6060
|
+
LAUNCHD_PLIST = join14(homedir16(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
6061
|
+
REFRESH_SCRIPT = join14(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
|
|
6062
|
+
REFRESH_LOG = join14(SYNKRO_DIR6, "claude-creds-refresh.log");
|
|
5449
6063
|
REFRESH_EXPIRY_BUFFER_SECONDS = 120;
|
|
5450
6064
|
MIN_REFRESH_INTERVAL_SECONDS = 30;
|
|
5451
6065
|
MAX_REFRESH_INTERVAL_SECONDS = 5 * 60;
|
|
@@ -5464,8 +6078,11 @@ var init_macKeychain = __esm({
|
|
|
5464
6078
|
var dockerInstall_exports = {};
|
|
5465
6079
|
__export(dockerInstall_exports, {
|
|
5466
6080
|
DockerInstallError: () => DockerInstallError,
|
|
6081
|
+
HOST_PGLITE_PORT: () => HOST_PGLITE_PORT,
|
|
6082
|
+
PGLITE_PASSWORD_PATH: () => PGLITE_PASSWORD_PATH,
|
|
5467
6083
|
SYNKRO_DIR: () => SYNKRO_DIR7,
|
|
5468
6084
|
assertDockerAvailable: () => assertDockerAvailable,
|
|
6085
|
+
createPgliteScramVerifier: () => createPgliteScramVerifier,
|
|
5469
6086
|
dockerInstall: () => dockerInstall,
|
|
5470
6087
|
dockerRemove: () => dockerRemove,
|
|
5471
6088
|
dockerSafeRestart: () => dockerSafeRestart,
|
|
@@ -5474,21 +6091,71 @@ __export(dockerInstall_exports, {
|
|
|
5474
6091
|
dockerStatus: () => dockerStatus,
|
|
5475
6092
|
dockerStop: () => dockerStop,
|
|
5476
6093
|
dockerUpdate: () => dockerUpdate,
|
|
6094
|
+
ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
|
|
5477
6095
|
imageTag: () => imageTag,
|
|
5478
6096
|
normalizeProvider: () => normalizeProvider,
|
|
5479
6097
|
poolLabel: () => poolLabel,
|
|
5480
6098
|
readContainerConfig: () => readContainerConfig,
|
|
5481
6099
|
resolveConductorProvider: () => resolveConductorProvider,
|
|
6100
|
+
resolveContainerName: () => resolveContainerName,
|
|
5482
6101
|
resolveGraderPool: () => resolveGraderPool,
|
|
5483
6102
|
resolveWorkerConfig: () => resolveWorkerConfig,
|
|
5484
6103
|
splitWorkers: () => splitWorkers,
|
|
5485
6104
|
waitForContainerReady: () => waitForContainerReady,
|
|
5486
6105
|
waitForWorkersReady: () => waitForWorkersReady
|
|
5487
6106
|
});
|
|
5488
|
-
import {
|
|
5489
|
-
|
|
5490
|
-
|
|
6107
|
+
import {
|
|
6108
|
+
chmodSync as chmodSync3,
|
|
6109
|
+
copyFileSync,
|
|
6110
|
+
existsSync as existsSync19,
|
|
6111
|
+
mkdirSync as mkdirSync12,
|
|
6112
|
+
readFileSync as readFileSync17,
|
|
6113
|
+
readdirSync as readdirSync2,
|
|
6114
|
+
renameSync as renameSync7,
|
|
6115
|
+
writeFileSync as writeFileSync14
|
|
6116
|
+
} from "fs";
|
|
6117
|
+
import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "crypto";
|
|
6118
|
+
import { homedir as homedir17 } from "os";
|
|
6119
|
+
import { join as join15 } from "path";
|
|
5491
6120
|
import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
|
|
6121
|
+
function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
|
|
6122
|
+
const value = String(raw || "").trim();
|
|
6123
|
+
if (!value) return "synkro-server";
|
|
6124
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) {
|
|
6125
|
+
throw new Error("SYNKRO_CONTAINER_NAME must be a valid Docker container name (1-128 letters, digits, _, ., or -)");
|
|
6126
|
+
}
|
|
6127
|
+
return value;
|
|
6128
|
+
}
|
|
6129
|
+
function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
|
|
6130
|
+
const iterations = 4096;
|
|
6131
|
+
const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
|
|
6132
|
+
const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
|
|
6133
|
+
const storedKey = createHash2("sha256").update(clientKey).digest();
|
|
6134
|
+
const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
|
|
6135
|
+
return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
|
|
6136
|
+
}
|
|
6137
|
+
function ensurePgliteProxyCredentials() {
|
|
6138
|
+
const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
|
|
6139
|
+
const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
|
|
6140
|
+
if (hasPassword && hasUserlist) {
|
|
6141
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6142
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
const password = randomBytes2(24).toString("base64url");
|
|
6146
|
+
const verifier = createPgliteScramVerifier(password);
|
|
6147
|
+
const suffix = `${process.pid}.${Date.now()}.tmp`;
|
|
6148
|
+
const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
|
|
6149
|
+
const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
|
|
6150
|
+
writeFileSync14(passwordTmp, `${password}
|
|
6151
|
+
`, { mode: 384 });
|
|
6152
|
+
writeFileSync14(userlistTmp, `"synkro" "${verifier}"
|
|
6153
|
+
`, { mode: 384 });
|
|
6154
|
+
renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
|
|
6155
|
+
renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
|
|
6156
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6157
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6158
|
+
}
|
|
5492
6159
|
function resolveConductorProvider(pool, counts) {
|
|
5493
6160
|
if (pool !== "auto") return pool;
|
|
5494
6161
|
const ranked = [
|
|
@@ -5597,9 +6264,9 @@ function readSynkroFileConfig() {
|
|
|
5597
6264
|
try {
|
|
5598
6265
|
const root = execSync3("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5599
6266
|
if (!root) return { pool: "auto", warnings: [] };
|
|
5600
|
-
const fp =
|
|
6267
|
+
const fp = join15(root, "synkro.toml");
|
|
5601
6268
|
if (!existsSync19(fp)) return { pool: "auto", warnings: [] };
|
|
5602
|
-
const parsed = parseSynkroToml(
|
|
6269
|
+
const parsed = parseSynkroToml(readFileSync17(fp, "utf-8"));
|
|
5603
6270
|
return resolveGraderPool(parsed);
|
|
5604
6271
|
} catch {
|
|
5605
6272
|
}
|
|
@@ -5682,7 +6349,7 @@ function assertDockerAvailable() {
|
|
|
5682
6349
|
}
|
|
5683
6350
|
function claudeCredsHostDir() {
|
|
5684
6351
|
if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;
|
|
5685
|
-
return
|
|
6352
|
+
return join15(homedir17(), ".claude");
|
|
5686
6353
|
}
|
|
5687
6354
|
function resolveSynkroBin() {
|
|
5688
6355
|
const which2 = spawnSync3("which", ["synkro"], { encoding: "utf-8", timeout: 5e3 });
|
|
@@ -5741,16 +6408,17 @@ async function dockerInstall(opts = {}) {
|
|
|
5741
6408
|
const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
|
|
5742
6409
|
const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
|
|
5743
6410
|
const usesCodex = codexWorkers > 0 || conductorProvider === "codex";
|
|
5744
|
-
const codexHomeDir = opts.codexHomeDir ??
|
|
5745
|
-
if (usesCodex && !existsSync19(
|
|
6411
|
+
const codexHomeDir = opts.codexHomeDir ?? join15(SYNKRO_DIR7, "codex-local-session");
|
|
6412
|
+
if (usesCodex && !existsSync19(join15(codexHomeDir, "auth.json"))) {
|
|
5746
6413
|
throw new DockerInstallError(
|
|
5747
6414
|
"Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
|
|
5748
6415
|
);
|
|
5749
6416
|
}
|
|
5750
6417
|
mkdirSync12(PGDATA_PATH, { recursive: true });
|
|
5751
6418
|
mkdirSync12(BACKUP_DIR, { recursive: true });
|
|
6419
|
+
ensurePgliteProxyCredentials();
|
|
5752
6420
|
mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
|
|
5753
|
-
const hostClaudeJson =
|
|
6421
|
+
const hostClaudeJson = join15(homedir17(), ".claude.json");
|
|
5754
6422
|
if (existsSync19(hostClaudeJson)) {
|
|
5755
6423
|
copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
|
|
5756
6424
|
}
|
|
@@ -5783,7 +6451,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5783
6451
|
console.warn(` Plist written to ${plist} \u2014 load manually with launchctl bootstrap when ready.`);
|
|
5784
6452
|
}
|
|
5785
6453
|
} else {
|
|
5786
|
-
mkdirSync12(
|
|
6454
|
+
mkdirSync12(join15(homedir17(), ".claude"), { recursive: true });
|
|
5787
6455
|
}
|
|
5788
6456
|
const imageExistsLocally = () => spawnSync3("docker", ["image", "inspect", image], { stdio: "ignore", timeout: 3e4 }).status === 0;
|
|
5789
6457
|
const skipPull = process.env.SYNKRO_SKIP_PULL === "1" || process.env.SYNKRO_SKIP_PULL === "true";
|
|
@@ -5822,7 +6490,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5822
6490
|
"-p",
|
|
5823
6491
|
`127.0.0.1:${HOST_CWE_PORT}:8930`,
|
|
5824
6492
|
"-p",
|
|
5825
|
-
`127.0.0.1:${HOST_PGLITE_PORT}:
|
|
6493
|
+
`127.0.0.1:${HOST_PGLITE_PORT}:5434`,
|
|
5826
6494
|
"-v",
|
|
5827
6495
|
`${PGDATA_PATH}:/data/pgdata`,
|
|
5828
6496
|
"-v",
|
|
@@ -5836,7 +6504,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5836
6504
|
"-v",
|
|
5837
6505
|
`${credsDir}:/home/synkro/.claude:rw`,
|
|
5838
6506
|
"-v",
|
|
5839
|
-
`${
|
|
6507
|
+
`${join15(homedir17(), ".claude")}:/data/claude-host:ro`,
|
|
5840
6508
|
"-v",
|
|
5841
6509
|
`${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
|
|
5842
6510
|
// Cursor creds — mounted RW so the in-container refresher can rotate the
|
|
@@ -5856,6 +6524,8 @@ async function dockerInstall(opts = {}) {
|
|
|
5856
6524
|
// Pass through the batch-size lever if the operator set it. Defaults
|
|
5857
6525
|
// inside the container to 5; clamped to [1, 20] by synkro-server.ts.
|
|
5858
6526
|
...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
|
|
6527
|
+
// Full verifier prompt/response tracing is explicit opt-in because it contains source code.
|
|
6528
|
+
...process.env.SYNKRO_VERIFY_TRACE === "1" ? ["-e", "SYNKRO_VERIFY_TRACE=1"] : [],
|
|
5859
6529
|
// Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.
|
|
5860
6530
|
...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
|
|
5861
6531
|
// Fix-poll kill switch. Default ON in the image; a benchmark/headless run
|
|
@@ -5887,7 +6557,14 @@ async function dockerInstall(opts = {}) {
|
|
|
5887
6557
|
if (run.status !== 0) {
|
|
5888
6558
|
throw new DockerInstallError(`docker run failed (image ${image})`);
|
|
5889
6559
|
}
|
|
5890
|
-
return {
|
|
6560
|
+
return {
|
|
6561
|
+
image,
|
|
6562
|
+
hostMcpPort: HOST_MCP_PORT,
|
|
6563
|
+
hostGraderPort: HOST_GRADER_PORT,
|
|
6564
|
+
hostCwePort: HOST_CWE_PORT,
|
|
6565
|
+
hostPglitePort: HOST_PGLITE_PORT,
|
|
6566
|
+
pglitePasswordPath: PGLITE_PASSWORD_PATH
|
|
6567
|
+
};
|
|
5891
6568
|
}
|
|
5892
6569
|
async function waitForContainerReady(timeoutMs = 6e4) {
|
|
5893
6570
|
const start = Date.now();
|
|
@@ -5942,7 +6619,7 @@ function dockerStatus() {
|
|
|
5942
6619
|
return {
|
|
5943
6620
|
running: true,
|
|
5944
6621
|
image: imageTag(),
|
|
5945
|
-
healthz: `http://127.0.0.1:${HOST_MCP_PORT}
|
|
6622
|
+
healthz: `http://127.0.0.1:${HOST_MCP_PORT}/health`
|
|
5946
6623
|
};
|
|
5947
6624
|
}
|
|
5948
6625
|
function readContainerConfig() {
|
|
@@ -6089,23 +6766,34 @@ function checkPgdata() {
|
|
|
6089
6766
|
if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
|
|
6090
6767
|
return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
|
|
6091
6768
|
}
|
|
6092
|
-
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;
|
|
6769
|
+
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;
|
|
6093
6770
|
var init_dockerInstall = __esm({
|
|
6094
6771
|
"cli/local-cc/dockerInstall.ts"() {
|
|
6095
6772
|
"use strict";
|
|
6096
6773
|
init_agentDetect();
|
|
6097
6774
|
init_macKeychain();
|
|
6098
|
-
SYNKRO_DIR7 =
|
|
6099
|
-
MCP_JWT_PATH =
|
|
6100
|
-
PGDATA_PATH =
|
|
6101
|
-
|
|
6102
|
-
|
|
6775
|
+
SYNKRO_DIR7 = join15(homedir17(), ".synkro");
|
|
6776
|
+
MCP_JWT_PATH = join15(SYNKRO_DIR7, ".mcp-jwt");
|
|
6777
|
+
PGDATA_PATH = join15(SYNKRO_DIR7, "pgdata");
|
|
6778
|
+
PGLITE_PASSWORD_PATH = join15(SYNKRO_DIR7, ".pglite-password");
|
|
6779
|
+
PGLITE_USERLIST_PATH = join15(SYNKRO_DIR7, ".pglite-userlist");
|
|
6780
|
+
CLAUDE_HOST_STATE_DIR = join15(SYNKRO_DIR7, "claude-host-state");
|
|
6781
|
+
CLAUDE_HOST_STATE_FILE = join15(CLAUDE_HOST_STATE_DIR, ".claude.json");
|
|
6103
6782
|
HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
6104
6783
|
HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
|
|
6105
6784
|
HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
|
|
6106
6785
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
6107
|
-
CONTAINER_NAME =
|
|
6108
|
-
|
|
6786
|
+
CONTAINER_NAME = resolveContainerName();
|
|
6787
|
+
defaultImageVersion = () => {
|
|
6788
|
+
if (true) return "1.7.90";
|
|
6789
|
+
try {
|
|
6790
|
+
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
6791
|
+
if (pkg.version) return pkg.version;
|
|
6792
|
+
} catch {
|
|
6793
|
+
}
|
|
6794
|
+
return process.env.npm_package_version || "0.0.0-dev";
|
|
6795
|
+
};
|
|
6796
|
+
DEFAULT_IMAGE = `ghcr.io/synkro-sh/synkro-server:${defaultImageVersion()}`;
|
|
6109
6797
|
DockerInstallError = class extends Error {
|
|
6110
6798
|
constructor(message, cause) {
|
|
6111
6799
|
super(message);
|
|
@@ -6114,17 +6802,17 @@ var init_dockerInstall = __esm({
|
|
|
6114
6802
|
}
|
|
6115
6803
|
cause;
|
|
6116
6804
|
};
|
|
6117
|
-
BACKUP_DIR =
|
|
6805
|
+
BACKUP_DIR = join15(SYNKRO_DIR7, "pgdata-backups");
|
|
6118
6806
|
}
|
|
6119
6807
|
});
|
|
6120
6808
|
|
|
6121
6809
|
// cli/local-cc/setupToken.ts
|
|
6122
6810
|
import { spawn as nodeSpawn } from "child_process";
|
|
6123
|
-
import { readFileSync as
|
|
6124
|
-
import { homedir as
|
|
6125
|
-
import { join as
|
|
6811
|
+
import { readFileSync as readFileSync18, unlinkSync as unlinkSync6 } from "fs";
|
|
6812
|
+
import { homedir as homedir18, platform as platform4 } from "os";
|
|
6813
|
+
import { join as join16 } from "path";
|
|
6126
6814
|
function captureClaudeSetupToken() {
|
|
6127
|
-
const tmpFile =
|
|
6815
|
+
const tmpFile = join16(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
|
|
6128
6816
|
const isMac = platform4() === "darwin";
|
|
6129
6817
|
const bin = "script";
|
|
6130
6818
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
@@ -6138,7 +6826,7 @@ function captureClaudeSetupToken() {
|
|
|
6138
6826
|
proc.on("close", (code) => {
|
|
6139
6827
|
let raw = "";
|
|
6140
6828
|
try {
|
|
6141
|
-
raw =
|
|
6829
|
+
raw = readFileSync18(tmpFile, "utf-8");
|
|
6142
6830
|
} catch (e) {
|
|
6143
6831
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
6144
6832
|
return;
|
|
@@ -6204,15 +6892,15 @@ var SYNKRO_DIR8;
|
|
|
6204
6892
|
var init_setupToken = __esm({
|
|
6205
6893
|
"cli/local-cc/setupToken.ts"() {
|
|
6206
6894
|
"use strict";
|
|
6207
|
-
SYNKRO_DIR8 =
|
|
6895
|
+
SYNKRO_DIR8 = join16(homedir18(), ".synkro");
|
|
6208
6896
|
}
|
|
6209
6897
|
});
|
|
6210
6898
|
|
|
6211
6899
|
// cli/local-cc/codexCloudSetup.ts
|
|
6212
6900
|
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
6213
|
-
import { readFileSync as
|
|
6214
|
-
import { homedir as
|
|
6215
|
-
import { join as
|
|
6901
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
|
|
6902
|
+
import { homedir as homedir19 } from "os";
|
|
6903
|
+
import { join as join17 } from "path";
|
|
6216
6904
|
function findCodexBinary() {
|
|
6217
6905
|
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
6218
6906
|
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
@@ -6221,7 +6909,7 @@ function findCodexBinary() {
|
|
|
6221
6909
|
}
|
|
6222
6910
|
function runCodexLogin(codexBin, codexHome) {
|
|
6223
6911
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
6224
|
-
|
|
6912
|
+
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
6225
6913
|
return new Promise((resolve6, reject) => {
|
|
6226
6914
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
6227
6915
|
stdio: "inherit",
|
|
@@ -6253,13 +6941,13 @@ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
|
6253
6941
|
} catch (e) {
|
|
6254
6942
|
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
6255
6943
|
}
|
|
6256
|
-
const authPath =
|
|
6944
|
+
const authPath = join17(CODEX_CLOUD_HOME, "auth.json");
|
|
6257
6945
|
if (!existsSync20(authPath)) {
|
|
6258
6946
|
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
6259
6947
|
}
|
|
6260
6948
|
let auth;
|
|
6261
6949
|
try {
|
|
6262
|
-
auth = JSON.parse(
|
|
6950
|
+
auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6263
6951
|
} catch (e) {
|
|
6264
6952
|
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
6265
6953
|
}
|
|
@@ -6293,7 +6981,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6293
6981
|
if (!codexBin) {
|
|
6294
6982
|
return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
|
|
6295
6983
|
}
|
|
6296
|
-
const authPath =
|
|
6984
|
+
const authPath = join17(CODEX_LOCAL_HOME, "auth.json");
|
|
6297
6985
|
if (!existsSync20(authPath)) {
|
|
6298
6986
|
onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
|
|
6299
6987
|
try {
|
|
@@ -6303,7 +6991,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6303
6991
|
}
|
|
6304
6992
|
}
|
|
6305
6993
|
try {
|
|
6306
|
-
const auth = JSON.parse(
|
|
6994
|
+
const auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6307
6995
|
if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
|
|
6308
6996
|
} catch (e) {
|
|
6309
6997
|
return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
|
|
@@ -6315,9 +7003,9 @@ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
|
|
|
6315
7003
|
var init_codexCloudSetup = __esm({
|
|
6316
7004
|
"cli/local-cc/codexCloudSetup.ts"() {
|
|
6317
7005
|
"use strict";
|
|
6318
|
-
SYNKRO_DIR9 =
|
|
6319
|
-
CODEX_CLOUD_HOME =
|
|
6320
|
-
CODEX_LOCAL_HOME =
|
|
7006
|
+
SYNKRO_DIR9 = join17(homedir19(), ".synkro");
|
|
7007
|
+
CODEX_CLOUD_HOME = join17(SYNKRO_DIR9, "codex-cloud-session");
|
|
7008
|
+
CODEX_LOCAL_HOME = join17(SYNKRO_DIR9, "codex-local-session");
|
|
6321
7009
|
}
|
|
6322
7010
|
});
|
|
6323
7011
|
|
|
@@ -6339,21 +7027,21 @@ __export(ptyShim_exports, {
|
|
|
6339
7027
|
import {
|
|
6340
7028
|
existsSync as existsSync21,
|
|
6341
7029
|
mkdirSync as mkdirSync14,
|
|
6342
|
-
writeFileSync as
|
|
6343
|
-
chmodSync as
|
|
6344
|
-
readFileSync as
|
|
7030
|
+
writeFileSync as writeFileSync16,
|
|
7031
|
+
chmodSync as chmodSync4,
|
|
7032
|
+
readFileSync as readFileSync20,
|
|
6345
7033
|
rmSync as rmSync2,
|
|
6346
7034
|
realpathSync,
|
|
6347
7035
|
symlinkSync,
|
|
6348
7036
|
lstatSync,
|
|
6349
7037
|
readdirSync as readdirSync3
|
|
6350
7038
|
} from "fs";
|
|
6351
|
-
import { homedir as
|
|
6352
|
-
import { join as
|
|
7039
|
+
import { homedir as homedir20 } from "os";
|
|
7040
|
+
import { join as join18 } from "path";
|
|
6353
7041
|
import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
|
|
6354
7042
|
function rcFiles() {
|
|
6355
|
-
const h =
|
|
6356
|
-
return [
|
|
7043
|
+
const h = homedir20();
|
|
7044
|
+
return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
|
|
6357
7045
|
}
|
|
6358
7046
|
function resolveRealClaude() {
|
|
6359
7047
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
@@ -6365,7 +7053,7 @@ function resolveRealClaude() {
|
|
|
6365
7053
|
return p;
|
|
6366
7054
|
}
|
|
6367
7055
|
}
|
|
6368
|
-
for (const c of [
|
|
7056
|
+
for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
|
|
6369
7057
|
if (existsSync21(c)) {
|
|
6370
7058
|
try {
|
|
6371
7059
|
return realpathSync(c);
|
|
@@ -6380,7 +7068,7 @@ function findClaudeLink() {
|
|
|
6380
7068
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
6381
7069
|
let linkPath = (r.stdout || "").trim();
|
|
6382
7070
|
if (!linkPath) {
|
|
6383
|
-
const c =
|
|
7071
|
+
const c = join18(homedir20(), ".local", "bin", "claude");
|
|
6384
7072
|
if (existsSync21(c)) linkPath = c;
|
|
6385
7073
|
else return null;
|
|
6386
7074
|
}
|
|
@@ -6394,7 +7082,7 @@ function findClaudeLink() {
|
|
|
6394
7082
|
}
|
|
6395
7083
|
function isOurShim(path) {
|
|
6396
7084
|
try {
|
|
6397
|
-
return
|
|
7085
|
+
return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
|
|
6398
7086
|
} catch {
|
|
6399
7087
|
return false;
|
|
6400
7088
|
}
|
|
@@ -6407,7 +7095,7 @@ function shadowClaude() {
|
|
|
6407
7095
|
}
|
|
6408
7096
|
const { linkPath, realTarget } = found;
|
|
6409
7097
|
if (isOurShim(linkPath)) {
|
|
6410
|
-
console.log(` \xB7 ${linkPath.replace(
|
|
7098
|
+
console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
|
|
6411
7099
|
return;
|
|
6412
7100
|
}
|
|
6413
7101
|
let wasSymlink = false;
|
|
@@ -6417,14 +7105,14 @@ function shadowClaude() {
|
|
|
6417
7105
|
}
|
|
6418
7106
|
const state = { linkPath, realTarget, wasSymlink };
|
|
6419
7107
|
try {
|
|
6420
|
-
|
|
7108
|
+
writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
|
|
6421
7109
|
} catch {
|
|
6422
7110
|
}
|
|
6423
7111
|
try {
|
|
6424
7112
|
rmSync2(linkPath, { force: true });
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
console.log(` \u2713 shadowed ${linkPath.replace(
|
|
7113
|
+
writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
|
|
7114
|
+
chmodSync4(linkPath, 493);
|
|
7115
|
+
console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
|
|
6428
7116
|
} catch (e) {
|
|
6429
7117
|
console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
|
|
6430
7118
|
}
|
|
@@ -6432,7 +7120,7 @@ function shadowClaude() {
|
|
|
6432
7120
|
function unshadowClaude() {
|
|
6433
7121
|
let state;
|
|
6434
7122
|
try {
|
|
6435
|
-
state = JSON.parse(
|
|
7123
|
+
state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
|
|
6436
7124
|
} catch {
|
|
6437
7125
|
return;
|
|
6438
7126
|
}
|
|
@@ -6441,7 +7129,7 @@ function unshadowClaude() {
|
|
|
6441
7129
|
if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
|
|
6442
7130
|
rmSync2(state.linkPath, { force: true });
|
|
6443
7131
|
symlinkSync(state.realTarget, state.linkPath);
|
|
6444
|
-
console.log(`\u2713 restored ${state.linkPath.replace(
|
|
7132
|
+
console.log(`\u2713 restored ${state.linkPath.replace(homedir20(), "~")} \u2192 ${state.realTarget.replace(homedir20(), "~")}`);
|
|
6445
7133
|
} catch (e) {
|
|
6446
7134
|
console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
|
|
6447
7135
|
}
|
|
@@ -6452,13 +7140,13 @@ function addPathBlock() {
|
|
|
6452
7140
|
if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
|
|
6453
7141
|
let body = "";
|
|
6454
7142
|
try {
|
|
6455
|
-
body =
|
|
7143
|
+
body = readFileSync20(rc, "utf-8");
|
|
6456
7144
|
} catch {
|
|
6457
7145
|
}
|
|
6458
7146
|
const cleaned = stripBlock(body);
|
|
6459
7147
|
const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
|
|
6460
7148
|
try {
|
|
6461
|
-
|
|
7149
|
+
writeFileSync16(rc, next, "utf-8");
|
|
6462
7150
|
touched.push(rc);
|
|
6463
7151
|
} catch {
|
|
6464
7152
|
}
|
|
@@ -6477,12 +7165,12 @@ function installPtyShim() {
|
|
|
6477
7165
|
mkdirSync14(SHIM_BIN_DIR, { recursive: true });
|
|
6478
7166
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6479
7167
|
const real = resolveRealClaude();
|
|
6480
|
-
|
|
6481
|
-
|
|
7168
|
+
writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
|
|
7169
|
+
chmodSync4(SHIM_PATH, 493);
|
|
6482
7170
|
const touched = addPathBlock();
|
|
6483
7171
|
console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
|
|
6484
7172
|
shadowClaude();
|
|
6485
|
-
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(
|
|
7173
|
+
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
|
|
6486
7174
|
}
|
|
6487
7175
|
function uninstallPtyShim() {
|
|
6488
7176
|
try {
|
|
@@ -6500,10 +7188,10 @@ function uninstallPtyShim() {
|
|
|
6500
7188
|
for (const rc of rcFiles()) {
|
|
6501
7189
|
if (!existsSync21(rc)) continue;
|
|
6502
7190
|
try {
|
|
6503
|
-
const body =
|
|
7191
|
+
const body = readFileSync20(rc, "utf-8");
|
|
6504
7192
|
if (body.includes(RC_BEGIN)) {
|
|
6505
|
-
|
|
6506
|
-
cleaned.push(rc.replace(
|
|
7193
|
+
writeFileSync16(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
7194
|
+
cleaned.push(rc.replace(homedir20(), "~"));
|
|
6507
7195
|
}
|
|
6508
7196
|
} catch {
|
|
6509
7197
|
}
|
|
@@ -6535,7 +7223,7 @@ function listSessions(opts = {}) {
|
|
|
6535
7223
|
const out = [];
|
|
6536
7224
|
for (const f of files) {
|
|
6537
7225
|
try {
|
|
6538
|
-
const r = JSON.parse(
|
|
7226
|
+
const r = JSON.parse(readFileSync20(join18(SESSIONS_DIR, f), "utf-8"));
|
|
6539
7227
|
if (!r || !r.session_id) continue;
|
|
6540
7228
|
if (liveOnly) {
|
|
6541
7229
|
const s = r.tmux_session;
|
|
@@ -6564,7 +7252,7 @@ function resolveTargetSession(override) {
|
|
|
6564
7252
|
const own = currentTmuxSession();
|
|
6565
7253
|
const fromFile = (() => {
|
|
6566
7254
|
try {
|
|
6567
|
-
return
|
|
7255
|
+
return readFileSync20(ACTIVE_SESSION_FILE, "utf-8").trim();
|
|
6568
7256
|
} catch {
|
|
6569
7257
|
return "";
|
|
6570
7258
|
}
|
|
@@ -6590,7 +7278,7 @@ function injectModel(model, sessionOverride) {
|
|
|
6590
7278
|
sendKeys("Escape");
|
|
6591
7279
|
sendKeys("-l", `/model ${model}`);
|
|
6592
7280
|
sendKeys("Enter");
|
|
6593
|
-
const pidFile =
|
|
7281
|
+
const pidFile = join18(PTY_STATE_DIR, `poll-${session}.pid`);
|
|
6594
7282
|
try {
|
|
6595
7283
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6596
7284
|
} catch {
|
|
@@ -6604,13 +7292,13 @@ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, S
|
|
|
6604
7292
|
var init_ptyShim = __esm({
|
|
6605
7293
|
"cli/local-cc/ptyShim.ts"() {
|
|
6606
7294
|
"use strict";
|
|
6607
|
-
SYNKRO_DIR10 =
|
|
6608
|
-
SHIM_BIN_DIR =
|
|
6609
|
-
SHIM_PATH =
|
|
6610
|
-
PTY_STATE_DIR =
|
|
6611
|
-
ACTIVE_SESSION_FILE =
|
|
6612
|
-
SHADOW_STATE_FILE =
|
|
6613
|
-
SESSIONS_DIR =
|
|
7295
|
+
SYNKRO_DIR10 = join18(homedir20(), ".synkro");
|
|
7296
|
+
SHIM_BIN_DIR = join18(SYNKRO_DIR10, "bin");
|
|
7297
|
+
SHIM_PATH = join18(SHIM_BIN_DIR, "claude");
|
|
7298
|
+
PTY_STATE_DIR = join18(SYNKRO_DIR10, "pty");
|
|
7299
|
+
ACTIVE_SESSION_FILE = join18(PTY_STATE_DIR, "active");
|
|
7300
|
+
SHADOW_STATE_FILE = join18(PTY_STATE_DIR, "shadow.json");
|
|
7301
|
+
SESSIONS_DIR = join18(PTY_STATE_DIR, "sessions");
|
|
6614
7302
|
SHIM_SESSION_PREFIX = "synkro-cc-";
|
|
6615
7303
|
RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
|
|
6616
7304
|
RC_END = "# <<< synkro pty shim <<<";
|
|
@@ -6681,6 +7369,189 @@ var init_graderSmoke = __esm({
|
|
|
6681
7369
|
}
|
|
6682
7370
|
});
|
|
6683
7371
|
|
|
7372
|
+
// cli/scanning/codexTranscriptUsage.ts
|
|
7373
|
+
function rawCounters(value) {
|
|
7374
|
+
return {
|
|
7375
|
+
input: finiteCounter(value?.input_tokens),
|
|
7376
|
+
output: finiteCounter(value?.output_tokens),
|
|
7377
|
+
cacheCreation: finiteCounter(value?.cache_write_input_tokens),
|
|
7378
|
+
cacheRead: finiteCounter(value?.cached_input_tokens)
|
|
7379
|
+
};
|
|
7380
|
+
}
|
|
7381
|
+
function counterDelta(current, previous) {
|
|
7382
|
+
return current >= previous ? current - previous : current;
|
|
7383
|
+
}
|
|
7384
|
+
function normalizedUsage(raw) {
|
|
7385
|
+
return {
|
|
7386
|
+
// Codex input_tokens includes cached/cache-write details. Store only the
|
|
7387
|
+
// uncached remainder in input_tokens so totals and pricing never count the
|
|
7388
|
+
// same prompt tokens twice.
|
|
7389
|
+
input_tokens: Math.max(0, raw.input - raw.cacheCreation - raw.cacheRead),
|
|
7390
|
+
output_tokens: raw.output,
|
|
7391
|
+
cache_creation_input_tokens: raw.cacheCreation,
|
|
7392
|
+
cache_read_input_tokens: raw.cacheRead
|
|
7393
|
+
};
|
|
7394
|
+
}
|
|
7395
|
+
function addUsage(a, b) {
|
|
7396
|
+
return {
|
|
7397
|
+
input_tokens: (a?.input_tokens || 0) + b.input_tokens,
|
|
7398
|
+
output_tokens: (a?.output_tokens || 0) + b.output_tokens,
|
|
7399
|
+
cache_creation_input_tokens: (a?.cache_creation_input_tokens || 0) + b.cache_creation_input_tokens,
|
|
7400
|
+
cache_read_input_tokens: (a?.cache_read_input_tokens || 0) + b.cache_read_input_tokens
|
|
7401
|
+
};
|
|
7402
|
+
}
|
|
7403
|
+
function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
7404
|
+
if (!transcript) return null;
|
|
7405
|
+
const lines = transcript.split("\n");
|
|
7406
|
+
const hasCanonicalAssistant = lines.some((line) => {
|
|
7407
|
+
try {
|
|
7408
|
+
const entry = JSON.parse(line);
|
|
7409
|
+
return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
|
|
7410
|
+
} catch {
|
|
7411
|
+
return false;
|
|
7412
|
+
}
|
|
7413
|
+
});
|
|
7414
|
+
const turnsByLine = /* @__PURE__ */ new Map();
|
|
7415
|
+
let model = "";
|
|
7416
|
+
let lastAssistantLine = -1;
|
|
7417
|
+
let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
|
|
7418
|
+
let latest = null;
|
|
7419
|
+
let sawSnapshot = false;
|
|
7420
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7421
|
+
const line = lines[i].trim();
|
|
7422
|
+
if (!line) continue;
|
|
7423
|
+
let entry;
|
|
7424
|
+
try {
|
|
7425
|
+
entry = JSON.parse(line);
|
|
7426
|
+
} catch {
|
|
7427
|
+
continue;
|
|
7428
|
+
}
|
|
7429
|
+
if (entry?.type === "turn_context" && typeof entry?.payload?.model === "string") {
|
|
7430
|
+
model = entry.payload.model;
|
|
7431
|
+
continue;
|
|
7432
|
+
}
|
|
7433
|
+
if (!hasCanonicalAssistant && entry?.type === "response_item" && entry?.payload?.type === "message" && entry?.payload?.role === "assistant") {
|
|
7434
|
+
lastAssistantLine = i;
|
|
7435
|
+
continue;
|
|
7436
|
+
}
|
|
7437
|
+
if (entry?.type !== "event_msg") continue;
|
|
7438
|
+
if (entry?.payload?.type === "agent_message") {
|
|
7439
|
+
lastAssistantLine = i;
|
|
7440
|
+
continue;
|
|
7441
|
+
}
|
|
7442
|
+
if (entry?.payload?.type !== "token_count") continue;
|
|
7443
|
+
const eventModel = entry?.payload?.model ?? entry?.model;
|
|
7444
|
+
if (typeof eventModel === "string" && eventModel) model = eventModel;
|
|
7445
|
+
const total = entry?.payload?.info?.total_token_usage;
|
|
7446
|
+
if (!total || typeof total !== "object") continue;
|
|
7447
|
+
const current = rawCounters(total);
|
|
7448
|
+
latest = current;
|
|
7449
|
+
if (options.partial && !sawSnapshot) {
|
|
7450
|
+
previous = current;
|
|
7451
|
+
sawSnapshot = true;
|
|
7452
|
+
continue;
|
|
7453
|
+
}
|
|
7454
|
+
sawSnapshot = true;
|
|
7455
|
+
const delta = normalizedUsage({
|
|
7456
|
+
input: counterDelta(current.input, previous.input),
|
|
7457
|
+
output: counterDelta(current.output, previous.output),
|
|
7458
|
+
cacheCreation: counterDelta(current.cacheCreation, previous.cacheCreation),
|
|
7459
|
+
cacheRead: counterDelta(current.cacheRead, previous.cacheRead)
|
|
7460
|
+
});
|
|
7461
|
+
previous = current;
|
|
7462
|
+
if (lastAssistantLine >= 0) {
|
|
7463
|
+
const prior = turnsByLine.get(lastAssistantLine);
|
|
7464
|
+
turnsByLine.set(lastAssistantLine, {
|
|
7465
|
+
lineIndex: lastAssistantLine,
|
|
7466
|
+
model,
|
|
7467
|
+
usage: addUsage(prior?.usage, delta)
|
|
7468
|
+
});
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7471
|
+
if (model) {
|
|
7472
|
+
for (const turn of turnsByLine.values()) {
|
|
7473
|
+
if (!turn.model) turn.model = model;
|
|
7474
|
+
}
|
|
7475
|
+
}
|
|
7476
|
+
if (!latest) return null;
|
|
7477
|
+
return { model, total: normalizedUsage(latest), turnsByLine };
|
|
7478
|
+
}
|
|
7479
|
+
var finiteCounter;
|
|
7480
|
+
var init_codexTranscriptUsage = __esm({
|
|
7481
|
+
"cli/scanning/codexTranscriptUsage.ts"() {
|
|
7482
|
+
"use strict";
|
|
7483
|
+
finiteCounter = (value) => {
|
|
7484
|
+
const n = Number(value);
|
|
7485
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
7486
|
+
};
|
|
7487
|
+
}
|
|
7488
|
+
});
|
|
7489
|
+
|
|
7490
|
+
// cli/scanning/codexTranscriptMessages.ts
|
|
7491
|
+
import { createHash as createHash3 } from "crypto";
|
|
7492
|
+
function textContent(content) {
|
|
7493
|
+
if (typeof content === "string") return content.trim();
|
|
7494
|
+
if (!Array.isArray(content)) return "";
|
|
7495
|
+
return content.map((block) => {
|
|
7496
|
+
if (typeof block === "string") return block;
|
|
7497
|
+
return block?.type === "text" || block?.type === "input_text" || block?.type === "output_text" ? String(block.text || "") : "";
|
|
7498
|
+
}).join(" ").trim();
|
|
7499
|
+
}
|
|
7500
|
+
function isCodexConversationNoise(content) {
|
|
7501
|
+
const value = content.trimStart();
|
|
7502
|
+
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>");
|
|
7503
|
+
}
|
|
7504
|
+
function parseCodexConversationMessages(transcript) {
|
|
7505
|
+
if (!transcript) return [];
|
|
7506
|
+
const entries = [];
|
|
7507
|
+
for (const [lineIndex, line] of transcript.split("\n").entries()) {
|
|
7508
|
+
const value = line.trim();
|
|
7509
|
+
if (!value) continue;
|
|
7510
|
+
try {
|
|
7511
|
+
entries.push({ lineIndex, entry: JSON.parse(value) });
|
|
7512
|
+
} catch {
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
const hasCanonicalMessages = entries.some(({ entry }) => entry?.type === "event_msg" && (entry?.payload?.type === "user_message" || entry?.payload?.type === "agent_message"));
|
|
7516
|
+
const messages = [];
|
|
7517
|
+
for (const { lineIndex, entry } of entries) {
|
|
7518
|
+
let role = null;
|
|
7519
|
+
let content;
|
|
7520
|
+
let itemId = "";
|
|
7521
|
+
const eventType = entry?.type === "event_msg" ? entry?.payload?.type : "";
|
|
7522
|
+
if (eventType === "user_message" || eventType === "agent_message") {
|
|
7523
|
+
role = eventType === "user_message" ? "user" : "assistant";
|
|
7524
|
+
content = entry?.payload?.message;
|
|
7525
|
+
} else if (!hasCanonicalMessages && entry?.type === "response_item" && entry?.payload?.type === "message") {
|
|
7526
|
+
const responseRole = entry?.payload?.role;
|
|
7527
|
+
if (responseRole !== "user" && responseRole !== "assistant") continue;
|
|
7528
|
+
role = responseRole;
|
|
7529
|
+
content = entry?.payload?.content;
|
|
7530
|
+
itemId = typeof entry?.payload?.id === "string" ? entry.payload.id : "";
|
|
7531
|
+
} else {
|
|
7532
|
+
continue;
|
|
7533
|
+
}
|
|
7534
|
+
if (!role) continue;
|
|
7535
|
+
const text = textContent(content);
|
|
7536
|
+
if (!text || isCodexConversationNoise(text)) continue;
|
|
7537
|
+
const timestamp = typeof entry?.timestamp === "string" && entry.timestamp ? entry.timestamp : void 0;
|
|
7538
|
+
const uuid = typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : itemId || createHash3("sha256").update((timestamp || "") + "\0" + role + "\0" + text).digest("hex").slice(0, 32);
|
|
7539
|
+
messages.push({
|
|
7540
|
+
lineIndex,
|
|
7541
|
+
role,
|
|
7542
|
+
content: text.slice(0, 8e3),
|
|
7543
|
+
uuid,
|
|
7544
|
+
...timestamp ? { timestamp } : {}
|
|
7545
|
+
});
|
|
7546
|
+
}
|
|
7547
|
+
return messages;
|
|
7548
|
+
}
|
|
7549
|
+
var init_codexTranscriptMessages = __esm({
|
|
7550
|
+
"cli/scanning/codexTranscriptMessages.ts"() {
|
|
7551
|
+
"use strict";
|
|
7552
|
+
}
|
|
7553
|
+
});
|
|
7554
|
+
|
|
6684
7555
|
// cli/commands/install.ts
|
|
6685
7556
|
var install_exports = {};
|
|
6686
7557
|
__export(install_exports, {
|
|
@@ -6696,12 +7567,12 @@ __export(install_exports, {
|
|
|
6696
7567
|
syncSkillFiles: () => syncSkillFiles,
|
|
6697
7568
|
writeHookScripts: () => writeHookScripts
|
|
6698
7569
|
});
|
|
6699
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as
|
|
6700
|
-
import { homedir as
|
|
6701
|
-
import { join as
|
|
7570
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
|
|
7571
|
+
import { homedir as homedir21 } from "os";
|
|
7572
|
+
import { join as join19, isAbsolute, resolve as resolve4 } from "path";
|
|
6702
7573
|
import { execSync as execSync4, spawn as spawn4 } from "child_process";
|
|
6703
7574
|
import { createInterface as createInterface2 } from "readline";
|
|
6704
|
-
import { createHash as
|
|
7575
|
+
import { createHash as createHash4 } from "crypto";
|
|
6705
7576
|
function resolvePersistedHookMode() {
|
|
6706
7577
|
return "stub";
|
|
6707
7578
|
}
|
|
@@ -6829,34 +7700,39 @@ function ensureSynkroDir() {
|
|
|
6829
7700
|
mkdirSync15(HOOKS_DIR, { recursive: true });
|
|
6830
7701
|
mkdirSync15(BIN_DIR, { recursive: true });
|
|
6831
7702
|
mkdirSync15(OFFSETS_DIR, { recursive: true });
|
|
6832
|
-
mkdirSync15(
|
|
7703
|
+
mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
|
|
6833
7704
|
}
|
|
6834
7705
|
function writeHookScripts() {
|
|
6835
|
-
const installExtractCorePath =
|
|
6836
|
-
const bashScriptPath =
|
|
6837
|
-
const skillJudgeScriptPath =
|
|
6838
|
-
const cursorSkillJudgePath =
|
|
6839
|
-
const bashFollowupScriptPath =
|
|
6840
|
-
const editPrecheckScriptPath =
|
|
6841
|
-
const
|
|
6842
|
-
const
|
|
6843
|
-
const
|
|
6844
|
-
const
|
|
6845
|
-
const
|
|
6846
|
-
const
|
|
6847
|
-
const
|
|
6848
|
-
const
|
|
6849
|
-
const
|
|
6850
|
-
const
|
|
6851
|
-
const
|
|
6852
|
-
const
|
|
6853
|
-
const
|
|
6854
|
-
const
|
|
6855
|
-
const
|
|
6856
|
-
const
|
|
6857
|
-
const
|
|
6858
|
-
const
|
|
6859
|
-
const
|
|
7706
|
+
const installExtractCorePath = join19(HOOKS_DIR, "installExtractCore.ts");
|
|
7707
|
+
const bashScriptPath = join19(HOOKS_DIR, "cc-bash-judge.ts");
|
|
7708
|
+
const skillJudgeScriptPath = join19(HOOKS_DIR, "cc-skill-judge.ts");
|
|
7709
|
+
const cursorSkillJudgePath = join19(HOOKS_DIR, "cursor-skill-judge.ts");
|
|
7710
|
+
const bashFollowupScriptPath = join19(HOOKS_DIR, "cc-bash-followup.ts");
|
|
7711
|
+
const editPrecheckScriptPath = join19(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
7712
|
+
const editFollowupScriptPath = join19(HOOKS_DIR, "cc-edit-followup.ts");
|
|
7713
|
+
const cwePrecheckScriptPath = join19(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
7714
|
+
const cvePrecheckScriptPath = join19(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
7715
|
+
const planJudgeScriptPath = join19(HOOKS_DIR, "cc-plan-judge.ts");
|
|
7716
|
+
const agentJudgeScriptPath = join19(HOOKS_DIR, "cc-agent-judge.ts");
|
|
7717
|
+
const stopSummaryScriptPath = join19(HOOKS_DIR, "cc-stop-summary.ts");
|
|
7718
|
+
const cweStopScriptPath = join19(HOOKS_DIR, "codex-cwe-stop.ts");
|
|
7719
|
+
const cveStopScriptPath = join19(HOOKS_DIR, "codex-cve-stop.ts");
|
|
7720
|
+
const sessionStartScriptPath = join19(HOOKS_DIR, "cc-session-start.ts");
|
|
7721
|
+
const transcriptSyncScriptPath = join19(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
7722
|
+
const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
|
|
7723
|
+
const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
|
|
7724
|
+
const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
7725
|
+
const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
|
|
7726
|
+
const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
|
|
7727
|
+
const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
|
|
7728
|
+
const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
|
|
7729
|
+
const cursorBashJudgePath = join19(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
7730
|
+
const cursorEditCapturePath = join19(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
7731
|
+
const cursorAgentCapturePath = join19(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
7732
|
+
const mcpStdioProxyPath = join19(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
7733
|
+
const taskActivateIntentScriptPath = join19(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
7734
|
+
const mcpGateScriptPath = join19(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
7735
|
+
const stubCommonPath = join19(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
6860
7736
|
const stubFiles = [
|
|
6861
7737
|
[stubCommonPath, STUB_COMMON_TS],
|
|
6862
7738
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -6864,13 +7740,18 @@ function writeHookScripts() {
|
|
|
6864
7740
|
[cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],
|
|
6865
7741
|
[bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],
|
|
6866
7742
|
[editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],
|
|
7743
|
+
[editFollowupScriptPath, STUB_EDIT_FOLLOWUP_TS],
|
|
6867
7744
|
[cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],
|
|
6868
7745
|
[cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],
|
|
6869
7746
|
[planJudgeScriptPath, STUB_PLAN_JUDGE_TS],
|
|
6870
7747
|
[agentJudgeScriptPath, STUB_AGENT_JUDGE_TS],
|
|
6871
7748
|
[stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],
|
|
7749
|
+
[cweStopScriptPath, STUB_CODEX_CWE_STOP_TS],
|
|
7750
|
+
[cveStopScriptPath, STUB_CODEX_CVE_STOP_TS],
|
|
6872
7751
|
[sessionStartScriptPath, STUB_SESSION_START_TS],
|
|
6873
7752
|
[transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],
|
|
7753
|
+
[subagentStartScriptPath, STUB_SUBAGENT_START_TS],
|
|
7754
|
+
[subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
|
|
6874
7755
|
[userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
|
|
6875
7756
|
[promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
|
|
6876
7757
|
[installScanScriptPath, STUB_INSTALL_SCAN_TS],
|
|
@@ -6881,14 +7762,14 @@ function writeHookScripts() {
|
|
|
6881
7762
|
[cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
|
|
6882
7763
|
];
|
|
6883
7764
|
for (const [p, content] of stubFiles) {
|
|
6884
|
-
|
|
6885
|
-
|
|
7765
|
+
writeFileSync17(p, content, "utf-8");
|
|
7766
|
+
chmodSync5(p, 493);
|
|
6886
7767
|
}
|
|
6887
|
-
|
|
6888
|
-
|
|
7768
|
+
writeFileSync17(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
7769
|
+
chmodSync5(mcpStdioProxyPath, 493);
|
|
6889
7770
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
6890
7771
|
try {
|
|
6891
|
-
unlinkSync7(
|
|
7772
|
+
unlinkSync7(join19(HOOKS_DIR, stale));
|
|
6892
7773
|
} catch {
|
|
6893
7774
|
}
|
|
6894
7775
|
}
|
|
@@ -6898,13 +7779,18 @@ function writeHookScripts() {
|
|
|
6898
7779
|
cursorSkillJudgeScript: cursorSkillJudgePath,
|
|
6899
7780
|
bashFollowupScript: bashFollowupScriptPath,
|
|
6900
7781
|
editPrecheckScript: editPrecheckScriptPath,
|
|
7782
|
+
editFollowupScript: editFollowupScriptPath,
|
|
6901
7783
|
cwePrecheckScript: cwePrecheckScriptPath,
|
|
6902
7784
|
cvePrecheckScript: cvePrecheckScriptPath,
|
|
6903
7785
|
planJudgeScript: planJudgeScriptPath,
|
|
6904
7786
|
agentJudgeScript: agentJudgeScriptPath,
|
|
6905
7787
|
stopSummaryScript: stopSummaryScriptPath,
|
|
7788
|
+
cweStopScript: cweStopScriptPath,
|
|
7789
|
+
cveStopScript: cveStopScriptPath,
|
|
6906
7790
|
sessionStartScript: sessionStartScriptPath,
|
|
6907
7791
|
transcriptSyncScript: transcriptSyncScriptPath,
|
|
7792
|
+
subagentStartScript: subagentStartScriptPath,
|
|
7793
|
+
subagentStopScript: subagentStopScriptPath,
|
|
6908
7794
|
userPromptSubmitScript: userPromptSubmitScriptPath,
|
|
6909
7795
|
promptRouteScript: promptRouteScriptPath,
|
|
6910
7796
|
installScanScript: installScanScriptPath,
|
|
@@ -6928,7 +7814,7 @@ function resolveSynkroBundle() {
|
|
|
6928
7814
|
return null;
|
|
6929
7815
|
}
|
|
6930
7816
|
function writeConfigEnv(opts) {
|
|
6931
|
-
const credsPath =
|
|
7817
|
+
const credsPath = join19(SYNKRO_DIR11, "credentials.json");
|
|
6932
7818
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
6933
7819
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
6934
7820
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -6944,7 +7830,7 @@ function writeConfigEnv(opts) {
|
|
|
6944
7830
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
6945
7831
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
6946
7832
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
6947
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.
|
|
7833
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.90")}`
|
|
6948
7834
|
];
|
|
6949
7835
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
6950
7836
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -6964,12 +7850,12 @@ function writeConfigEnv(opts) {
|
|
|
6964
7850
|
lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
6965
7851
|
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
6966
7852
|
lines.push("");
|
|
6967
|
-
|
|
6968
|
-
|
|
7853
|
+
writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
|
|
7854
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
6969
7855
|
}
|
|
6970
7856
|
function persistedTranscriptConsent(source) {
|
|
6971
7857
|
try {
|
|
6972
|
-
const env =
|
|
7858
|
+
const env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
6973
7859
|
const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
|
|
6974
7860
|
if (specific) return specific[1] === "yes";
|
|
6975
7861
|
if (source !== "CODEX") {
|
|
@@ -6992,7 +7878,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6992
7878
|
assertGatewayAllowed(gatewayUrl);
|
|
6993
7879
|
let stored = "";
|
|
6994
7880
|
try {
|
|
6995
|
-
stored =
|
|
7881
|
+
stored = readFileSync21(CLOUD_JWT_PATH, "utf-8").trim();
|
|
6996
7882
|
} catch {
|
|
6997
7883
|
}
|
|
6998
7884
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -7008,7 +7894,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
7008
7894
|
throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
|
|
7009
7895
|
}
|
|
7010
7896
|
const { token } = await resp.json();
|
|
7011
|
-
|
|
7897
|
+
writeFileSync17(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
|
|
7012
7898
|
return token;
|
|
7013
7899
|
}
|
|
7014
7900
|
async function provisionCloudContainer(opts) {
|
|
@@ -7092,7 +7978,7 @@ async function provisionCloudContainer(opts) {
|
|
|
7092
7978
|
let cursorApiKey = "";
|
|
7093
7979
|
if (cursorWorkers > 0 || selectedKind === "cursor") {
|
|
7094
7980
|
try {
|
|
7095
|
-
cursorApiKey =
|
|
7981
|
+
cursorApiKey = readFileSync21(join19(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
|
|
7096
7982
|
} catch {
|
|
7097
7983
|
}
|
|
7098
7984
|
}
|
|
@@ -7278,7 +8164,7 @@ async function verifyCloudGrader(jwt2, requestedKind) {
|
|
|
7278
8164
|
function readPersistedDeployLocation() {
|
|
7279
8165
|
try {
|
|
7280
8166
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7281
|
-
const m =
|
|
8167
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
7282
8168
|
if (m?.[1] === "cloud") return "cloud";
|
|
7283
8169
|
}
|
|
7284
8170
|
} catch {
|
|
@@ -7287,7 +8173,7 @@ function readPersistedDeployLocation() {
|
|
|
7287
8173
|
}
|
|
7288
8174
|
function updateConfigEnvLocation(location) {
|
|
7289
8175
|
if (!existsSync22(CONFIG_PATH4)) return;
|
|
7290
|
-
let env =
|
|
8176
|
+
let env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
7291
8177
|
const set = (k, v) => {
|
|
7292
8178
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
7293
8179
|
const line = `${k}='${v}'`;
|
|
@@ -7295,14 +8181,14 @@ function updateConfigEnvLocation(location) {
|
|
|
7295
8181
|
};
|
|
7296
8182
|
set("SYNKRO_DEPLOY_LOCATION", location);
|
|
7297
8183
|
set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
|
|
7298
|
-
|
|
7299
|
-
|
|
8184
|
+
writeFileSync17(CONFIG_PATH4, env, "utf-8");
|
|
8185
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
7300
8186
|
}
|
|
7301
8187
|
async function applyMcpConfig(opts) {
|
|
7302
8188
|
if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
|
|
7303
8189
|
let mcpJwt2 = "";
|
|
7304
8190
|
try {
|
|
7305
|
-
mcpJwt2 =
|
|
8191
|
+
mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7306
8192
|
} catch {
|
|
7307
8193
|
}
|
|
7308
8194
|
if (!mcpJwt2) {
|
|
@@ -7433,7 +8319,7 @@ function resolveDeploymentMode() {
|
|
|
7433
8319
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
7434
8320
|
try {
|
|
7435
8321
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7436
|
-
const m =
|
|
8322
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
7437
8323
|
const val = m?.[1]?.toLowerCase();
|
|
7438
8324
|
if (val === "bare-host" || val === "docker") return val;
|
|
7439
8325
|
}
|
|
@@ -7460,16 +8346,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7460
8346
|
meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
7461
8347
|
} catch {
|
|
7462
8348
|
}
|
|
7463
|
-
const claudeDir =
|
|
8349
|
+
const claudeDir = join19(homedir21(), ".claude");
|
|
7464
8350
|
try {
|
|
7465
|
-
const settings = JSON.parse(
|
|
8351
|
+
const settings = JSON.parse(readFileSync21(join19(claudeDir, "settings.json"), "utf-8"));
|
|
7466
8352
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
7467
8353
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
7468
8354
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
7469
8355
|
} catch {
|
|
7470
8356
|
}
|
|
7471
8357
|
try {
|
|
7472
|
-
const mcpCache = JSON.parse(
|
|
8358
|
+
const mcpCache = JSON.parse(readFileSync21(join19(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
7473
8359
|
const mcpNames = Object.keys(mcpCache);
|
|
7474
8360
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
7475
8361
|
} catch {
|
|
@@ -7481,10 +8367,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7481
8367
|
} catch {
|
|
7482
8368
|
}
|
|
7483
8369
|
try {
|
|
7484
|
-
const sessionsDir =
|
|
8370
|
+
const sessionsDir = join19(claudeDir, "sessions");
|
|
7485
8371
|
const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
7486
8372
|
for (const f of files) {
|
|
7487
|
-
const s = JSON.parse(
|
|
8373
|
+
const s = JSON.parse(readFileSync21(join19(sessionsDir, f), "utf-8"));
|
|
7488
8374
|
if (s.version) {
|
|
7489
8375
|
meta.cc_version = meta.cc_version || s.version;
|
|
7490
8376
|
break;
|
|
@@ -7686,7 +8572,7 @@ async function installCommand(opts = {}) {
|
|
|
7686
8572
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
7687
8573
|
emit("install", {
|
|
7688
8574
|
phase: "started",
|
|
7689
|
-
cli_version_to: "1.7.
|
|
8575
|
+
cli_version_to: "1.7.90",
|
|
7690
8576
|
agents_detected: agents.map((a) => a.kind),
|
|
7691
8577
|
with_github: false,
|
|
7692
8578
|
with_local_cc: false,
|
|
@@ -7698,9 +8584,9 @@ async function installCommand(opts = {}) {
|
|
|
7698
8584
|
const scripts = writeHookScripts();
|
|
7699
8585
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
7700
8586
|
for (const mode of ["edit", "bash"]) {
|
|
7701
|
-
const pidFile =
|
|
8587
|
+
const pidFile = join19(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
|
|
7702
8588
|
try {
|
|
7703
|
-
const pid = parseInt(
|
|
8589
|
+
const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
|
|
7704
8590
|
if (pid > 0) {
|
|
7705
8591
|
process.kill(pid, "SIGTERM");
|
|
7706
8592
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -7763,12 +8649,17 @@ async function installCommand(opts = {}) {
|
|
|
7763
8649
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
7764
8650
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
7765
8651
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
8652
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
7766
8653
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
7767
8654
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
7768
8655
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
7769
8656
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
8657
|
+
cweStopScriptPath: scripts.cweStopScript,
|
|
8658
|
+
cveStopScriptPath: scripts.cveStopScript,
|
|
7770
8659
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
7771
8660
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
8661
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
8662
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
7772
8663
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
7773
8664
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
7774
8665
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -7818,7 +8709,7 @@ async function installCommand(opts = {}) {
|
|
|
7818
8709
|
if (mintResp.ok) {
|
|
7819
8710
|
const minted = await mintResp.json();
|
|
7820
8711
|
mcpJwt2 = minted.token;
|
|
7821
|
-
|
|
8712
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
|
|
7822
8713
|
} else {
|
|
7823
8714
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
7824
8715
|
}
|
|
@@ -7846,7 +8737,7 @@ async function installCommand(opts = {}) {
|
|
|
7846
8737
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7847
8738
|
}
|
|
7848
8739
|
const minted = await mintResp.json();
|
|
7849
|
-
|
|
8740
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7850
8741
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7851
8742
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7852
8743
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7868,7 +8759,7 @@ async function installCommand(opts = {}) {
|
|
|
7868
8759
|
if (hasCursor && !opts.noMcp) {
|
|
7869
8760
|
try {
|
|
7870
8761
|
if (useLocalMcp) {
|
|
7871
|
-
const jwtPath =
|
|
8762
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7872
8763
|
if (!existsSync22(jwtPath)) {
|
|
7873
8764
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
7874
8765
|
method: "POST",
|
|
@@ -7877,7 +8768,7 @@ async function installCommand(opts = {}) {
|
|
|
7877
8768
|
});
|
|
7878
8769
|
if (mintResp.ok) {
|
|
7879
8770
|
const minted = await mintResp.json();
|
|
7880
|
-
|
|
8771
|
+
writeFileSync17(jwtPath, minted.token + "\n", { mode: 384 });
|
|
7881
8772
|
}
|
|
7882
8773
|
}
|
|
7883
8774
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
|
|
@@ -7897,7 +8788,7 @@ async function installCommand(opts = {}) {
|
|
|
7897
8788
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7898
8789
|
}
|
|
7899
8790
|
const minted = await mintResp.json();
|
|
7900
|
-
|
|
8791
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7901
8792
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7902
8793
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7903
8794
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7910,10 +8801,10 @@ async function installCommand(opts = {}) {
|
|
|
7910
8801
|
}
|
|
7911
8802
|
if (hasCodex && !opts.noMcp) {
|
|
7912
8803
|
try {
|
|
7913
|
-
const jwtPath =
|
|
8804
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7914
8805
|
let mcpJwt2 = "";
|
|
7915
8806
|
try {
|
|
7916
|
-
mcpJwt2 =
|
|
8807
|
+
mcpJwt2 = readFileSync21(jwtPath, "utf-8").trim();
|
|
7917
8808
|
} catch {
|
|
7918
8809
|
}
|
|
7919
8810
|
if (!mcpJwt2) {
|
|
@@ -7931,7 +8822,7 @@ async function installCommand(opts = {}) {
|
|
|
7931
8822
|
}
|
|
7932
8823
|
const minted = await mintResp.json();
|
|
7933
8824
|
mcpJwt2 = minted.token;
|
|
7934
|
-
|
|
8825
|
+
writeFileSync17(jwtPath, mcpJwt2 + "\n", { mode: 384 });
|
|
7935
8826
|
}
|
|
7936
8827
|
const mcp = installCodexMcpConfig({
|
|
7937
8828
|
gatewayUrl,
|
|
@@ -8043,14 +8934,15 @@ async function installCommand(opts = {}) {
|
|
|
8043
8934
|
}
|
|
8044
8935
|
console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
|
|
8045
8936
|
const connectedRepo = detectGitRepo2() || void 0;
|
|
8046
|
-
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
|
|
8937
|
+
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort, pglitePasswordPath } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
|
|
8047
8938
|
console.log(` \u2713 pulled ${image}`);
|
|
8048
|
-
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}
|
|
8939
|
+
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
|
|
8940
|
+
console.log(` PGLite: postgresql://synkro@127.0.0.1:${hostPglitePort}/postgres (password: ${pglitePasswordPath})`);
|
|
8049
8941
|
console.log(" waiting for container to be ready...");
|
|
8050
8942
|
const ready = await waitForContainerReady(6e4);
|
|
8051
8943
|
if (ready) {
|
|
8052
8944
|
console.log(" \u2713 container ready");
|
|
8053
|
-
const mcpJwt2 =
|
|
8945
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8054
8946
|
try {
|
|
8055
8947
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
8056
8948
|
method: "POST",
|
|
@@ -8093,7 +8985,7 @@ async function installCommand(opts = {}) {
|
|
|
8093
8985
|
try {
|
|
8094
8986
|
let mcpToken = "";
|
|
8095
8987
|
try {
|
|
8096
|
-
mcpToken =
|
|
8988
|
+
mcpToken = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8097
8989
|
} catch {
|
|
8098
8990
|
}
|
|
8099
8991
|
if (mcpToken) {
|
|
@@ -8261,8 +9153,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8261
9153
|
try {
|
|
8262
9154
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8263
9155
|
if (!root) return;
|
|
8264
|
-
if (root ===
|
|
8265
|
-
const fp =
|
|
9156
|
+
if (root === homedir21()) return;
|
|
9157
|
+
const fp = join19(root, "synkro.toml");
|
|
8266
9158
|
let hasFile = false;
|
|
8267
9159
|
try {
|
|
8268
9160
|
hasFile = statSync2(fp).isFile();
|
|
@@ -8294,7 +9186,7 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8294
9186
|
"cve = true",
|
|
8295
9187
|
""
|
|
8296
9188
|
].join("\n");
|
|
8297
|
-
|
|
9189
|
+
writeFileSync17(fp, toml, "utf-8");
|
|
8298
9190
|
console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
|
|
8299
9191
|
} catch {
|
|
8300
9192
|
}
|
|
@@ -8302,12 +9194,12 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8302
9194
|
function updateSynkroTomlLocation(location) {
|
|
8303
9195
|
try {
|
|
8304
9196
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8305
|
-
if (!root || root ===
|
|
8306
|
-
const fp =
|
|
9197
|
+
if (!root || root === homedir21()) return;
|
|
9198
|
+
const fp = join19(root, "synkro.toml");
|
|
8307
9199
|
let txt = "";
|
|
8308
9200
|
try {
|
|
8309
9201
|
if (!statSync2(fp).isFile()) return;
|
|
8310
|
-
txt =
|
|
9202
|
+
txt = readFileSync21(fp, "utf-8");
|
|
8311
9203
|
} catch {
|
|
8312
9204
|
return;
|
|
8313
9205
|
}
|
|
@@ -8315,7 +9207,7 @@ function updateSynkroTomlLocation(location) {
|
|
|
8315
9207
|
if (!re.test(txt)) return;
|
|
8316
9208
|
const next = txt.replace(re, `$1"${location}"`);
|
|
8317
9209
|
if (next !== txt) {
|
|
8318
|
-
|
|
9210
|
+
writeFileSync17(fp, next, "utf-8");
|
|
8319
9211
|
console.log(` synkro.toml: [grader] location = "${location}"`);
|
|
8320
9212
|
}
|
|
8321
9213
|
} catch {
|
|
@@ -8325,9 +9217,9 @@ function readFullSynkroFile() {
|
|
|
8325
9217
|
try {
|
|
8326
9218
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8327
9219
|
if (!root) return null;
|
|
8328
|
-
const fp =
|
|
9220
|
+
const fp = join19(root, "synkro.toml");
|
|
8329
9221
|
if (!existsSync22(fp)) return null;
|
|
8330
|
-
const parsed = parseSynkroToml2(
|
|
9222
|
+
const parsed = parseSynkroToml2(readFileSync21(fp, "utf-8"));
|
|
8331
9223
|
const valid = ["claude-code", "cursor", "codex"];
|
|
8332
9224
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
8333
9225
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -8367,7 +9259,7 @@ function reconcileHarness() {
|
|
|
8367
9259
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
8368
9260
|
const scripts = writeHookScripts();
|
|
8369
9261
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
8370
|
-
const ccSettings =
|
|
9262
|
+
const ccSettings = join19(homedir21(), ".claude", "settings.json");
|
|
8371
9263
|
if (wantCC) {
|
|
8372
9264
|
installCCHooks(ccSettings, {
|
|
8373
9265
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -8390,7 +9282,7 @@ function reconcileHarness() {
|
|
|
8390
9282
|
});
|
|
8391
9283
|
console.log(" \u2713 Claude Code hooks registered");
|
|
8392
9284
|
try {
|
|
8393
|
-
const mcpJwt2 =
|
|
9285
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8394
9286
|
if (mcpJwt2) {
|
|
8395
9287
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
|
|
8396
9288
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -8402,7 +9294,7 @@ function reconcileHarness() {
|
|
|
8402
9294
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
8403
9295
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
8404
9296
|
}
|
|
8405
|
-
const cursorHooks =
|
|
9297
|
+
const cursorHooks = join19(homedir21(), ".cursor", "hooks.json");
|
|
8406
9298
|
if (wantCursor) {
|
|
8407
9299
|
installCursorHooks(cursorHooks, {
|
|
8408
9300
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -8433,19 +9325,24 @@ function reconcileHarness() {
|
|
|
8433
9325
|
if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
|
|
8434
9326
|
if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
|
|
8435
9327
|
}
|
|
8436
|
-
const codexHooks =
|
|
9328
|
+
const codexHooks = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "hooks.json");
|
|
8437
9329
|
if (wantCodex) {
|
|
8438
9330
|
installCodexHooks(codexHooks, {
|
|
8439
9331
|
bashJudgeScriptPath: scripts.bashScript,
|
|
8440
9332
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
8441
9333
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
8442
9334
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
9335
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
8443
9336
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
8444
9337
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
8445
9338
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
8446
9339
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
9340
|
+
cweStopScriptPath: scripts.cweStopScript,
|
|
9341
|
+
cveStopScriptPath: scripts.cveStopScript,
|
|
8447
9342
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
8448
9343
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
9344
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
9345
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
8449
9346
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
8450
9347
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
8451
9348
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -8525,7 +9422,7 @@ async function syncSkillFiles() {
|
|
|
8525
9422
|
if (resolved.length === 0) return;
|
|
8526
9423
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
8527
9424
|
const tasks = resolved.map((fp) => {
|
|
8528
|
-
const content =
|
|
9425
|
+
const content = readFileSync21(fp, "utf-8");
|
|
8529
9426
|
const source = `skill:${fp.split("/").pop()}`;
|
|
8530
9427
|
if (!content.trim()) {
|
|
8531
9428
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -8550,11 +9447,11 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8550
9447
|
} catch {
|
|
8551
9448
|
}
|
|
8552
9449
|
};
|
|
8553
|
-
add(
|
|
8554
|
-
add(
|
|
9450
|
+
add(join19(homedir21(), ".claude", "skills"));
|
|
9451
|
+
add(join19(homedir21(), ".agents", "skills"));
|
|
8555
9452
|
if (repoRoot2) {
|
|
8556
|
-
add(
|
|
8557
|
-
add(
|
|
9453
|
+
add(join19(repoRoot2, ".claude", "skills"));
|
|
9454
|
+
add(join19(repoRoot2, ".agents", "skills"));
|
|
8558
9455
|
}
|
|
8559
9456
|
const out = [];
|
|
8560
9457
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8565,12 +9462,12 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8565
9462
|
try {
|
|
8566
9463
|
const st = statSync2(file);
|
|
8567
9464
|
if (!st.isFile() || st.size > 2e5) return;
|
|
8568
|
-
content =
|
|
9465
|
+
content = readFileSync21(file, "utf-8");
|
|
8569
9466
|
} catch {
|
|
8570
9467
|
return;
|
|
8571
9468
|
}
|
|
8572
9469
|
if (!content.trim()) return;
|
|
8573
|
-
const hash =
|
|
9470
|
+
const hash = createHash4("sha256").update(content).digest("hex");
|
|
8574
9471
|
if (seen.has(hash) || excludeHashes.has(hash)) return;
|
|
8575
9472
|
seen.add(hash);
|
|
8576
9473
|
const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));
|
|
@@ -8585,7 +9482,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8585
9482
|
}
|
|
8586
9483
|
for (const entry of entries) {
|
|
8587
9484
|
if (entry.startsWith(".")) continue;
|
|
8588
|
-
const full =
|
|
9485
|
+
const full = join19(root, entry);
|
|
8589
9486
|
let st;
|
|
8590
9487
|
try {
|
|
8591
9488
|
st = statSync2(full);
|
|
@@ -8593,7 +9490,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8593
9490
|
continue;
|
|
8594
9491
|
}
|
|
8595
9492
|
if (st.isDirectory()) {
|
|
8596
|
-
const skillMd =
|
|
9493
|
+
const skillMd = join19(full, "SKILL.md");
|
|
8597
9494
|
if (existsSync22(skillMd)) consider(skillMd, entry);
|
|
8598
9495
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
8599
9496
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
@@ -8603,7 +9500,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8603
9500
|
return out;
|
|
8604
9501
|
}
|
|
8605
9502
|
function discoverySetHash(found) {
|
|
8606
|
-
return
|
|
9503
|
+
return createHash4("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
|
|
8607
9504
|
}
|
|
8608
9505
|
async function promptSkillDiscovery(found) {
|
|
8609
9506
|
if (!process.stdin.isTTY || found.length === 0) return [];
|
|
@@ -8643,7 +9540,7 @@ async function discoverAndIngestSkills() {
|
|
|
8643
9540
|
if (sf?.skills?.length) {
|
|
8644
9541
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
8645
9542
|
try {
|
|
8646
|
-
excludeHashes.add(
|
|
9543
|
+
excludeHashes.add(createHash4("sha256").update(readFileSync21(fp, "utf-8")).digest("hex"));
|
|
8647
9544
|
} catch {
|
|
8648
9545
|
}
|
|
8649
9546
|
}
|
|
@@ -8674,13 +9571,13 @@ async function discoverAndIngestSkills() {
|
|
|
8674
9571
|
const setHash = discoverySetHash(selectable);
|
|
8675
9572
|
let prev = "";
|
|
8676
9573
|
try {
|
|
8677
|
-
prev =
|
|
9574
|
+
prev = readFileSync21(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
8678
9575
|
} catch {
|
|
8679
9576
|
}
|
|
8680
9577
|
if (prev === setHash) return;
|
|
8681
9578
|
const picks = await promptSkillDiscovery(found);
|
|
8682
9579
|
try {
|
|
8683
|
-
|
|
9580
|
+
writeFileSync17(SKILLS_DISCOVERED_PATH, setHash);
|
|
8684
9581
|
} catch {
|
|
8685
9582
|
}
|
|
8686
9583
|
if (picks.length === 0) {
|
|
@@ -8719,9 +9616,9 @@ function ensureReachabilityGitHook() {
|
|
|
8719
9616
|
const root = run("git rev-parse --show-toplevel");
|
|
8720
9617
|
if (!root) return null;
|
|
8721
9618
|
let hooksDir = run("git config --get core.hooksPath");
|
|
8722
|
-
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir :
|
|
9619
|
+
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join19(root, hooksDir) : join19(root, ".git", "hooks");
|
|
8723
9620
|
if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
|
|
8724
|
-
const hookPath =
|
|
9621
|
+
const hookPath = join19(hooksDir, "post-commit");
|
|
8725
9622
|
const resolvedBin = resolveSynkroBinPath();
|
|
8726
9623
|
const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
|
|
8727
9624
|
const START = "# >>> synkro reachability (managed) >>>";
|
|
@@ -8735,23 +9632,23 @@ function ensureReachabilityGitHook() {
|
|
|
8735
9632
|
].join("\n");
|
|
8736
9633
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8737
9634
|
if (!existsSync22(hookPath)) {
|
|
8738
|
-
|
|
9635
|
+
writeFileSync17(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
|
|
8739
9636
|
return "installed";
|
|
8740
9637
|
}
|
|
8741
|
-
let cur =
|
|
9638
|
+
let cur = readFileSync21(hookPath, "utf-8");
|
|
8742
9639
|
if (cur.includes(START)) {
|
|
8743
9640
|
cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
|
|
8744
|
-
|
|
9641
|
+
writeFileSync17(hookPath, cur);
|
|
8745
9642
|
try {
|
|
8746
|
-
|
|
9643
|
+
chmodSync5(hookPath, 493);
|
|
8747
9644
|
} catch {
|
|
8748
9645
|
}
|
|
8749
9646
|
return "updated";
|
|
8750
9647
|
}
|
|
8751
9648
|
const sep = cur.endsWith("\n") ? "" : "\n";
|
|
8752
|
-
|
|
9649
|
+
writeFileSync17(hookPath, cur + sep + "\n" + block + "\n");
|
|
8753
9650
|
try {
|
|
8754
|
-
|
|
9651
|
+
chmodSync5(hookPath, 493);
|
|
8755
9652
|
} catch {
|
|
8756
9653
|
}
|
|
8757
9654
|
return "updated";
|
|
@@ -8777,7 +9674,7 @@ function detectGitRepo2() {
|
|
|
8777
9674
|
function getClaudeProjectsFolder() {
|
|
8778
9675
|
const cwd = process.cwd();
|
|
8779
9676
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
8780
|
-
const projectsDir =
|
|
9677
|
+
const projectsDir = join19(homedir21(), ".claude", "projects", sanitized);
|
|
8781
9678
|
return existsSync22(projectsDir) ? projectsDir : null;
|
|
8782
9679
|
}
|
|
8783
9680
|
function extractSessionInsights(projectsDir) {
|
|
@@ -8785,9 +9682,9 @@ function extractSessionInsights(projectsDir) {
|
|
|
8785
9682
|
const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
8786
9683
|
for (const file of files) {
|
|
8787
9684
|
const sessionId = file.replace(".jsonl", "");
|
|
8788
|
-
const filePath =
|
|
9685
|
+
const filePath = join19(projectsDir, file);
|
|
8789
9686
|
try {
|
|
8790
|
-
const content =
|
|
9687
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8791
9688
|
const lines = content.split("\n").filter(Boolean);
|
|
8792
9689
|
for (let i = 0; i < lines.length; i++) {
|
|
8793
9690
|
try {
|
|
@@ -8863,7 +9760,7 @@ function extractTextContent(content) {
|
|
|
8863
9760
|
return "";
|
|
8864
9761
|
}
|
|
8865
9762
|
function getCodexTranscriptFiles(repo) {
|
|
8866
|
-
const sessionsDir =
|
|
9763
|
+
const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
|
|
8867
9764
|
if (!existsSync22(sessionsDir)) return [];
|
|
8868
9765
|
let relative = [];
|
|
8869
9766
|
try {
|
|
@@ -8871,9 +9768,9 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8871
9768
|
} catch {
|
|
8872
9769
|
return [];
|
|
8873
9770
|
}
|
|
8874
|
-
return relative.filter((p) => p.endsWith(".jsonl")).map((p) =>
|
|
9771
|
+
return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
|
|
8875
9772
|
try {
|
|
8876
|
-
const first =
|
|
9773
|
+
const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
|
|
8877
9774
|
const meta = JSON.parse(first);
|
|
8878
9775
|
const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
|
|
8879
9776
|
const root = resolve4(repo);
|
|
@@ -8883,38 +9780,45 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8883
9780
|
}
|
|
8884
9781
|
});
|
|
8885
9782
|
}
|
|
9783
|
+
function isJsonSyntaxError(error) {
|
|
9784
|
+
return error instanceof SyntaxError;
|
|
9785
|
+
}
|
|
8886
9786
|
function parseCodexTranscriptFile(filePath) {
|
|
8887
|
-
const
|
|
9787
|
+
const transcript = readFileSync21(filePath, "utf-8");
|
|
9788
|
+
const lines = transcript.split("\n");
|
|
9789
|
+
const transcriptUsage = parseCodexTranscriptUsage(transcript);
|
|
8888
9790
|
let sessionId = "";
|
|
8889
9791
|
let model = "";
|
|
8890
|
-
const
|
|
8891
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9792
|
+
for (const line of lines) {
|
|
8892
9793
|
try {
|
|
8893
|
-
const entry = JSON.parse(
|
|
9794
|
+
const entry = JSON.parse(line);
|
|
8894
9795
|
if (entry.type === "session_meta") {
|
|
8895
9796
|
sessionId = String(entry.payload?.session_id || entry.payload?.id || sessionId);
|
|
8896
|
-
|
|
8897
|
-
}
|
|
8898
|
-
if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
9797
|
+
} else if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
8899
9798
|
model = entry.payload.model;
|
|
8900
|
-
continue;
|
|
8901
9799
|
}
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
if (eventType !== "user_message" && eventType !== "agent_message") continue;
|
|
8905
|
-
const text = String(entry.payload?.message || "").slice(0, 8e3);
|
|
8906
|
-
if (!text) continue;
|
|
8907
|
-
const type = eventType === "user_message" ? "user" : "assistant";
|
|
8908
|
-
messages.push({
|
|
8909
|
-
message_index: i,
|
|
8910
|
-
type,
|
|
8911
|
-
content: text,
|
|
8912
|
-
...type === "assistant" && model ? { model } : {}
|
|
8913
|
-
});
|
|
8914
|
-
} catch {
|
|
9800
|
+
} catch (error) {
|
|
9801
|
+
if (!isJsonSyntaxError(error)) throw error;
|
|
8915
9802
|
}
|
|
8916
9803
|
}
|
|
8917
|
-
|
|
9804
|
+
const messages = parseCodexConversationMessages(transcript).map((message) => {
|
|
9805
|
+
const turnUsage = message.role === "assistant" ? transcriptUsage?.turnsByLine.get(message.lineIndex) : void 0;
|
|
9806
|
+
return {
|
|
9807
|
+
message_index: message.lineIndex,
|
|
9808
|
+
uuid: message.uuid,
|
|
9809
|
+
type: message.role,
|
|
9810
|
+
content: message.content,
|
|
9811
|
+
...message.role === "assistant" && (turnUsage?.model || model) ? { model: turnUsage?.model || model } : {},
|
|
9812
|
+
...turnUsage ? { usage: turnUsage.usage } : {},
|
|
9813
|
+
...message.timestamp ? { timestamp: message.timestamp } : {}
|
|
9814
|
+
};
|
|
9815
|
+
});
|
|
9816
|
+
return {
|
|
9817
|
+
sessionId,
|
|
9818
|
+
messages,
|
|
9819
|
+
model: transcriptUsage?.model || model,
|
|
9820
|
+
usage: transcriptUsage?.total
|
|
9821
|
+
};
|
|
8918
9822
|
}
|
|
8919
9823
|
async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
8920
9824
|
const files = getCodexTranscriptFiles(repo);
|
|
@@ -8931,7 +9835,15 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8931
9835
|
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
|
|
8932
9836
|
method: "POST",
|
|
8933
9837
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
|
|
8934
|
-
body: JSON.stringify({
|
|
9838
|
+
body: JSON.stringify({
|
|
9839
|
+
session_id: parsed.sessionId,
|
|
9840
|
+
repo,
|
|
9841
|
+
messages,
|
|
9842
|
+
session_usage: parsed.usage,
|
|
9843
|
+
model: parsed.model,
|
|
9844
|
+
harness: "codex",
|
|
9845
|
+
usage_cumulative: true
|
|
9846
|
+
}),
|
|
8935
9847
|
signal: AbortSignal.timeout(15e3)
|
|
8936
9848
|
});
|
|
8937
9849
|
if (resp.ok) {
|
|
@@ -8939,7 +9851,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8939
9851
|
totalSessions++;
|
|
8940
9852
|
totalMessages += result.ingested ?? messages.length;
|
|
8941
9853
|
}
|
|
8942
|
-
|
|
9854
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
8943
9855
|
} catch {
|
|
8944
9856
|
}
|
|
8945
9857
|
if ((i + 1) % 10 === 0 || i === files.length - 1) {
|
|
@@ -8953,14 +9865,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
8953
9865
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8954
9866
|
}
|
|
8955
9867
|
function getCursorTranscriptsDir() {
|
|
8956
|
-
const dir =
|
|
9868
|
+
const dir = join19(homedir21(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
8957
9869
|
return existsSync22(dir) ? dir : null;
|
|
8958
9870
|
}
|
|
8959
9871
|
function isSafeConvId(id) {
|
|
8960
9872
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
8961
9873
|
}
|
|
8962
9874
|
function parseCursorTranscriptFile(filePath) {
|
|
8963
|
-
const content =
|
|
9875
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8964
9876
|
const lines = content.split("\n").filter(Boolean);
|
|
8965
9877
|
const messages = [];
|
|
8966
9878
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8992,7 +9904,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8992
9904
|
for (let i = 0; i < convDirs.length; i++) {
|
|
8993
9905
|
const convId = convDirs[i];
|
|
8994
9906
|
if (!isSafeConvId(convId)) continue;
|
|
8995
|
-
const filePath =
|
|
9907
|
+
const filePath = join19(dir, convId, `${convId}.jsonl`);
|
|
8996
9908
|
if (!existsSync22(filePath)) continue;
|
|
8997
9909
|
try {
|
|
8998
9910
|
const all = parseCursorTranscriptFile(filePath);
|
|
@@ -9015,8 +9927,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9015
9927
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
9016
9928
|
}
|
|
9017
9929
|
try {
|
|
9018
|
-
const lc =
|
|
9019
|
-
|
|
9930
|
+
const lc = readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
9931
|
+
writeFileSync17(join19(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
9020
9932
|
} catch {
|
|
9021
9933
|
}
|
|
9022
9934
|
}
|
|
@@ -9024,7 +9936,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9024
9936
|
return { sessions: totalSessions, messages: totalMessages };
|
|
9025
9937
|
}
|
|
9026
9938
|
function parseTranscriptFile(filePath) {
|
|
9027
|
-
const content =
|
|
9939
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
9028
9940
|
const lines = content.split("\n").filter(Boolean);
|
|
9029
9941
|
const messages = [];
|
|
9030
9942
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9072,7 +9984,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9072
9984
|
for (let i = 0; i < files.length; i++) {
|
|
9073
9985
|
const file = files[i];
|
|
9074
9986
|
const sessionId = file.replace(".jsonl", "");
|
|
9075
|
-
const filePath =
|
|
9987
|
+
const filePath = join19(projectsDir, file);
|
|
9076
9988
|
try {
|
|
9077
9989
|
const allMessages = parseTranscriptFile(filePath);
|
|
9078
9990
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -9094,9 +10006,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9094
10006
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
9095
10007
|
}
|
|
9096
10008
|
try {
|
|
9097
|
-
const content =
|
|
10009
|
+
const content = readFileSync21(join19(projectsDir, file), "utf-8");
|
|
9098
10010
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9099
|
-
|
|
10011
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9100
10012
|
} catch {
|
|
9101
10013
|
}
|
|
9102
10014
|
}
|
|
@@ -9117,7 +10029,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9117
10029
|
const sessions = [];
|
|
9118
10030
|
for (const file of batch) {
|
|
9119
10031
|
const sessionId = file.replace(".jsonl", "");
|
|
9120
|
-
const filePath =
|
|
10032
|
+
const filePath = join19(projectsDir, file);
|
|
9121
10033
|
try {
|
|
9122
10034
|
const allMessages = parseTranscriptFile(filePath);
|
|
9123
10035
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -9146,11 +10058,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9146
10058
|
}
|
|
9147
10059
|
for (const file of batch) {
|
|
9148
10060
|
const sessionId = file.replace(".jsonl", "");
|
|
9149
|
-
const filePath =
|
|
10061
|
+
const filePath = join19(projectsDir, file);
|
|
9150
10062
|
try {
|
|
9151
|
-
const content =
|
|
10063
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
9152
10064
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9153
|
-
|
|
10065
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9154
10066
|
} catch {
|
|
9155
10067
|
}
|
|
9156
10068
|
}
|
|
@@ -9168,7 +10080,16 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9168
10080
|
try {
|
|
9169
10081
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9170
10082
|
const messages = parsed.messages.length > 500 ? parsed.messages.slice(-500) : parsed.messages;
|
|
9171
|
-
if (parsed.sessionId && messages.length > 0)
|
|
10083
|
+
if (parsed.sessionId && messages.length > 0) {
|
|
10084
|
+
sessions.push({
|
|
10085
|
+
cc_session_id: parsed.sessionId,
|
|
10086
|
+
messages,
|
|
10087
|
+
model: parsed.model,
|
|
10088
|
+
session_usage: parsed.usage,
|
|
10089
|
+
harness: "codex",
|
|
10090
|
+
usage_cumulative: true
|
|
10091
|
+
});
|
|
10092
|
+
}
|
|
9172
10093
|
} catch {
|
|
9173
10094
|
}
|
|
9174
10095
|
}
|
|
@@ -9190,7 +10111,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9190
10111
|
try {
|
|
9191
10112
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9192
10113
|
if (parsed.sessionId) {
|
|
9193
|
-
|
|
10114
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
9194
10115
|
}
|
|
9195
10116
|
} catch {
|
|
9196
10117
|
}
|
|
@@ -9221,10 +10142,12 @@ var init_install = __esm({
|
|
|
9221
10142
|
init_codexCloudSetup();
|
|
9222
10143
|
init_ptyShim();
|
|
9223
10144
|
init_graderSmoke();
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
10145
|
+
init_codexTranscriptUsage();
|
|
10146
|
+
init_codexTranscriptMessages();
|
|
10147
|
+
SYNKRO_DIR11 = join19(homedir21(), ".synkro");
|
|
10148
|
+
HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
|
|
10149
|
+
BIN_DIR = join19(SYNKRO_DIR11, "bin");
|
|
10150
|
+
CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
|
|
9228
10151
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
9229
10152
|
import { readFileSync } from 'node:fs';
|
|
9230
10153
|
import { homedir } from 'node:os';
|
|
@@ -9335,23 +10258,23 @@ rl.on('line', async (line) => {
|
|
|
9335
10258
|
}
|
|
9336
10259
|
});
|
|
9337
10260
|
`;
|
|
9338
|
-
OFFSETS_DIR =
|
|
9339
|
-
CLOUD_JWT_PATH =
|
|
9340
|
-
SKILLS_DISCOVERED_PATH =
|
|
10261
|
+
OFFSETS_DIR = join19(SYNKRO_DIR11, ".transcript-offsets");
|
|
10262
|
+
CLOUD_JWT_PATH = join19(SYNKRO_DIR11, ".cloud-jwt");
|
|
10263
|
+
SKILLS_DISCOVERED_PATH = join19(SYNKRO_DIR11, ".skills-discovered");
|
|
9341
10264
|
}
|
|
9342
10265
|
});
|
|
9343
10266
|
|
|
9344
10267
|
// cli/local-cc/install.ts
|
|
9345
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as
|
|
9346
|
-
import { join as
|
|
9347
|
-
import { homedir as
|
|
10268
|
+
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";
|
|
10269
|
+
import { join as join20 } from "path";
|
|
10270
|
+
import { homedir as homedir22 } from "os";
|
|
9348
10271
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
9349
10272
|
function writePluginFiles() {
|
|
9350
10273
|
for (const c of CHANNELS) {
|
|
9351
10274
|
mkdirSync16(c.sessionDir, { recursive: true });
|
|
9352
10275
|
mkdirSync16(c.pluginSettingsDir, { recursive: true });
|
|
9353
|
-
|
|
9354
|
-
|
|
10276
|
+
writeFileSync18(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
10277
|
+
writeFileSync18(
|
|
9355
10278
|
c.pluginSettingsPath,
|
|
9356
10279
|
JSON.stringify({
|
|
9357
10280
|
fastMode: true,
|
|
@@ -9366,8 +10289,8 @@ function writePluginFiles() {
|
|
|
9366
10289
|
}, null, 2) + "\n",
|
|
9367
10290
|
"utf-8"
|
|
9368
10291
|
);
|
|
9369
|
-
|
|
9370
|
-
|
|
10292
|
+
writeFileSync18(c.runScriptPath, c.runScriptSource, "utf-8");
|
|
10293
|
+
chmodSync6(c.runScriptPath, 493);
|
|
9371
10294
|
}
|
|
9372
10295
|
}
|
|
9373
10296
|
function runBunInstall() {
|
|
@@ -9388,7 +10311,7 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9388
10311
|
if (!existsSync23(CLAUDE_JSON_PATH)) {
|
|
9389
10312
|
return;
|
|
9390
10313
|
}
|
|
9391
|
-
const originalText =
|
|
10314
|
+
const originalText = readFileSync22(CLAUDE_JSON_PATH, "utf-8");
|
|
9392
10315
|
let parsed;
|
|
9393
10316
|
try {
|
|
9394
10317
|
parsed = JSON.parse(originalText);
|
|
@@ -9420,14 +10343,14 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9420
10343
|
copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
9421
10344
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
9422
10345
|
try {
|
|
9423
|
-
|
|
10346
|
+
writeFileSync18(tmpPath, newText, "utf-8");
|
|
9424
10347
|
const fd = openSync2(tmpPath, "r");
|
|
9425
10348
|
try {
|
|
9426
10349
|
fsyncSync(fd);
|
|
9427
10350
|
} finally {
|
|
9428
10351
|
closeSync2(fd);
|
|
9429
10352
|
}
|
|
9430
|
-
|
|
10353
|
+
renameSync8(tmpPath, CLAUDE_JSON_PATH);
|
|
9431
10354
|
} catch (err) {
|
|
9432
10355
|
try {
|
|
9433
10356
|
unlinkSync8(tmpPath);
|
|
@@ -9453,7 +10376,7 @@ function writeProjectMcpJson() {
|
|
|
9453
10376
|
}
|
|
9454
10377
|
}
|
|
9455
10378
|
};
|
|
9456
|
-
|
|
10379
|
+
writeFileSync18(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
9457
10380
|
}
|
|
9458
10381
|
}
|
|
9459
10382
|
function patchClaudeJson() {
|
|
@@ -9530,42 +10453,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
9530
10453
|
var init_install2 = __esm({
|
|
9531
10454
|
"cli/local-cc/install.ts"() {
|
|
9532
10455
|
"use strict";
|
|
9533
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
9534
|
-
SESSION_DIR =
|
|
9535
|
-
PLUGIN_PATH =
|
|
9536
|
-
PLUGIN_PKG_PATH =
|
|
9537
|
-
PLUGIN_SETTINGS_DIR =
|
|
9538
|
-
PLUGIN_SETTINGS_PATH =
|
|
9539
|
-
PROJECT_MCP_PATH =
|
|
9540
|
-
CLAUDE_JSON_PATH =
|
|
9541
|
-
RUN_SCRIPT_PATH =
|
|
10456
|
+
CLAUDE_JSON_BACKUP_PATH = join20(homedir22(), ".claude.json.synkro-bak");
|
|
10457
|
+
SESSION_DIR = join20(homedir22(), ".synkro", "cc_sessions");
|
|
10458
|
+
PLUGIN_PATH = join20(SESSION_DIR, "synkro-channel.ts");
|
|
10459
|
+
PLUGIN_PKG_PATH = join20(SESSION_DIR, "package.json");
|
|
10460
|
+
PLUGIN_SETTINGS_DIR = join20(SESSION_DIR, ".claude");
|
|
10461
|
+
PLUGIN_SETTINGS_PATH = join20(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
10462
|
+
PROJECT_MCP_PATH = join20(SESSION_DIR, ".mcp.json");
|
|
10463
|
+
CLAUDE_JSON_PATH = join20(homedir22(), ".claude.json");
|
|
10464
|
+
RUN_SCRIPT_PATH = join20(SESSION_DIR, "run-claude.sh");
|
|
9542
10465
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
9543
10466
|
CHANNEL_1_PORT = 8941;
|
|
9544
|
-
SESSION_DIR_2 =
|
|
9545
|
-
PLUGIN_PATH_2 =
|
|
9546
|
-
PLUGIN_PKG_PATH_2 =
|
|
9547
|
-
PLUGIN_SETTINGS_DIR_2 =
|
|
9548
|
-
PLUGIN_SETTINGS_PATH_2 =
|
|
9549
|
-
PROJECT_MCP_PATH_2 =
|
|
9550
|
-
RUN_SCRIPT_PATH_2 =
|
|
10467
|
+
SESSION_DIR_2 = join20(homedir22(), ".synkro", "cc_sessions_2");
|
|
10468
|
+
PLUGIN_PATH_2 = join20(SESSION_DIR_2, "synkro-channel.ts");
|
|
10469
|
+
PLUGIN_PKG_PATH_2 = join20(SESSION_DIR_2, "package.json");
|
|
10470
|
+
PLUGIN_SETTINGS_DIR_2 = join20(SESSION_DIR_2, ".claude");
|
|
10471
|
+
PLUGIN_SETTINGS_PATH_2 = join20(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
10472
|
+
PROJECT_MCP_PATH_2 = join20(SESSION_DIR_2, ".mcp.json");
|
|
10473
|
+
RUN_SCRIPT_PATH_2 = join20(SESSION_DIR_2, "run-claude.sh");
|
|
9551
10474
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
9552
10475
|
CHANNEL_2_PORT = 8951;
|
|
9553
|
-
SESSION_DIR_3 =
|
|
9554
|
-
PLUGIN_PATH_3 =
|
|
9555
|
-
PLUGIN_PKG_PATH_3 =
|
|
9556
|
-
PLUGIN_SETTINGS_DIR_3 =
|
|
9557
|
-
PLUGIN_SETTINGS_PATH_3 =
|
|
9558
|
-
PROJECT_MCP_PATH_3 =
|
|
9559
|
-
RUN_SCRIPT_PATH_3 =
|
|
10476
|
+
SESSION_DIR_3 = join20(homedir22(), ".synkro", "cc_sessions_3");
|
|
10477
|
+
PLUGIN_PATH_3 = join20(SESSION_DIR_3, "synkro-channel.ts");
|
|
10478
|
+
PLUGIN_PKG_PATH_3 = join20(SESSION_DIR_3, "package.json");
|
|
10479
|
+
PLUGIN_SETTINGS_DIR_3 = join20(SESSION_DIR_3, ".claude");
|
|
10480
|
+
PLUGIN_SETTINGS_PATH_3 = join20(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
10481
|
+
PROJECT_MCP_PATH_3 = join20(SESSION_DIR_3, ".mcp.json");
|
|
10482
|
+
RUN_SCRIPT_PATH_3 = join20(SESSION_DIR_3, "run-claude.sh");
|
|
9560
10483
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
9561
10484
|
CHANNEL_3_PORT = 8942;
|
|
9562
|
-
SESSION_DIR_4 =
|
|
9563
|
-
PLUGIN_PATH_4 =
|
|
9564
|
-
PLUGIN_PKG_PATH_4 =
|
|
9565
|
-
PLUGIN_SETTINGS_DIR_4 =
|
|
9566
|
-
PLUGIN_SETTINGS_PATH_4 =
|
|
9567
|
-
PROJECT_MCP_PATH_4 =
|
|
9568
|
-
RUN_SCRIPT_PATH_4 =
|
|
10485
|
+
SESSION_DIR_4 = join20(homedir22(), ".synkro", "cc_sessions_4");
|
|
10486
|
+
PLUGIN_PATH_4 = join20(SESSION_DIR_4, "synkro-channel.ts");
|
|
10487
|
+
PLUGIN_PKG_PATH_4 = join20(SESSION_DIR_4, "package.json");
|
|
10488
|
+
PLUGIN_SETTINGS_DIR_4 = join20(SESSION_DIR_4, ".claude");
|
|
10489
|
+
PLUGIN_SETTINGS_PATH_4 = join20(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
10490
|
+
PROJECT_MCP_PATH_4 = join20(SESSION_DIR_4, ".mcp.json");
|
|
10491
|
+
RUN_SCRIPT_PATH_4 = join20(SESSION_DIR_4, "run-claude.sh");
|
|
9569
10492
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
9570
10493
|
CHANNEL_4_PORT = 8952;
|
|
9571
10494
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -9840,8 +10763,8 @@ __export(disconnect_exports, {
|
|
|
9840
10763
|
disconnectCommand: () => disconnectCommand
|
|
9841
10764
|
});
|
|
9842
10765
|
import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
|
|
9843
|
-
import { homedir as
|
|
9844
|
-
import { join as
|
|
10766
|
+
import { homedir as homedir23 } from "os";
|
|
10767
|
+
import { join as join21 } from "path";
|
|
9845
10768
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
9846
10769
|
import { createInterface as createInterface3 } from "readline";
|
|
9847
10770
|
async function tearDownLocalCC() {
|
|
@@ -9956,13 +10879,13 @@ async function disconnectCommand(args2 = [], opts = {}) {
|
|
|
9956
10879
|
console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
|
|
9957
10880
|
} else {
|
|
9958
10881
|
const keep = /* @__PURE__ */ new Set([
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
10882
|
+
join21(SYNKRO_DIR12, "pgdata"),
|
|
10883
|
+
join21(SYNKRO_DIR12, "pgdata-backups"),
|
|
10884
|
+
join21(SYNKRO_DIR12, ".transcript-offsets")
|
|
9962
10885
|
]);
|
|
9963
10886
|
const preserved = [];
|
|
9964
10887
|
for (const entry of readdirSync5(SYNKRO_DIR12)) {
|
|
9965
|
-
const full =
|
|
10888
|
+
const full = join21(SYNKRO_DIR12, entry);
|
|
9966
10889
|
if (keep.has(full)) {
|
|
9967
10890
|
preserved.push(entry);
|
|
9968
10891
|
continue;
|
|
@@ -10000,14 +10923,14 @@ var init_disconnect = __esm({
|
|
|
10000
10923
|
init_dockerInstall();
|
|
10001
10924
|
init_macKeychain();
|
|
10002
10925
|
init_telemetry();
|
|
10003
|
-
SYNKRO_DIR12 =
|
|
10926
|
+
SYNKRO_DIR12 = join21(homedir23(), ".synkro");
|
|
10004
10927
|
}
|
|
10005
10928
|
});
|
|
10006
10929
|
|
|
10007
10930
|
// cli/local-cc/turnLog.ts
|
|
10008
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as
|
|
10009
|
-
import { dirname as dirname7, join as
|
|
10010
|
-
import { homedir as
|
|
10931
|
+
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";
|
|
10932
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
10933
|
+
import { homedir as homedir24 } from "os";
|
|
10011
10934
|
function truncate(s, max = PREVIEW_MAX) {
|
|
10012
10935
|
if (s.length <= max) return s;
|
|
10013
10936
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -10047,7 +10970,7 @@ function readRecentTurns(n = 20) {
|
|
|
10047
10970
|
try {
|
|
10048
10971
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
10049
10972
|
if (size === 0) return [];
|
|
10050
|
-
const text =
|
|
10973
|
+
const text = readFileSync23(TURN_LOG_PATH, "utf-8");
|
|
10051
10974
|
const lines = text.split("\n").filter(Boolean);
|
|
10052
10975
|
const lastN = lines.slice(-n).reverse();
|
|
10053
10976
|
return lastN.map((line) => {
|
|
@@ -10126,7 +11049,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
10126
11049
|
var init_turnLog = __esm({
|
|
10127
11050
|
"cli/local-cc/turnLog.ts"() {
|
|
10128
11051
|
"use strict";
|
|
10129
|
-
TURN_LOG_PATH =
|
|
11052
|
+
TURN_LOG_PATH = join22(homedir24(), ".synkro", "cc_sessions", "turns.log");
|
|
10130
11053
|
PREVIEW_MAX = 400;
|
|
10131
11054
|
}
|
|
10132
11055
|
});
|
|
@@ -10280,8 +11203,8 @@ __export(scanPr_exports, {
|
|
|
10280
11203
|
scanPrCommand: () => scanPrCommand
|
|
10281
11204
|
});
|
|
10282
11205
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
10283
|
-
import { readFileSync as
|
|
10284
|
-
import { join as
|
|
11206
|
+
import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
|
|
11207
|
+
import { join as join23 } from "path";
|
|
10285
11208
|
function parseMatchSpec(condition) {
|
|
10286
11209
|
if (!condition.startsWith("match_spec:")) return null;
|
|
10287
11210
|
try {
|
|
@@ -10760,10 +11683,10 @@ function shouldFail(findings, threshold) {
|
|
|
10760
11683
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
10761
11684
|
}
|
|
10762
11685
|
function readRepoDeps() {
|
|
10763
|
-
const pkgPath =
|
|
11686
|
+
const pkgPath = join23(process.cwd(), "package.json");
|
|
10764
11687
|
if (!existsSync26(pkgPath)) return {};
|
|
10765
11688
|
try {
|
|
10766
|
-
const pkg = JSON.parse(
|
|
11689
|
+
const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
|
|
10767
11690
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
10768
11691
|
} catch {
|
|
10769
11692
|
return {};
|
|
@@ -11007,15 +11930,15 @@ var routeDecide_exports = {};
|
|
|
11007
11930
|
__export(routeDecide_exports, {
|
|
11008
11931
|
routeDecide: () => routeDecide
|
|
11009
11932
|
});
|
|
11010
|
-
import { readFileSync as
|
|
11011
|
-
import { homedir as
|
|
11012
|
-
import { join as
|
|
11933
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
11934
|
+
import { homedir as homedir25 } from "os";
|
|
11935
|
+
import { join as join24 } from "path";
|
|
11013
11936
|
function safeSid(sid) {
|
|
11014
11937
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
11015
11938
|
}
|
|
11016
11939
|
function loadMcpJwt() {
|
|
11017
11940
|
try {
|
|
11018
|
-
return
|
|
11941
|
+
return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
|
|
11019
11942
|
} catch {
|
|
11020
11943
|
return "";
|
|
11021
11944
|
}
|
|
@@ -11025,7 +11948,7 @@ async function routeDecide(sessionId) {
|
|
|
11025
11948
|
const sid = safeSid(sessionId);
|
|
11026
11949
|
let prompt = "";
|
|
11027
11950
|
try {
|
|
11028
|
-
const rec = JSON.parse(
|
|
11951
|
+
const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
|
|
11029
11952
|
prompt = String(rec.last_prompt || "").trim();
|
|
11030
11953
|
} catch {
|
|
11031
11954
|
return;
|
|
@@ -11036,7 +11959,7 @@ async function routeDecide(sessionId) {
|
|
|
11036
11959
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
|
|
11037
11960
|
method: "POST",
|
|
11038
11961
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
|
|
11039
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
11962
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
11040
11963
|
// First call may spin up the route lane (a haiku worker boot), so allow headroom.
|
|
11041
11964
|
signal: AbortSignal.timeout(12e4)
|
|
11042
11965
|
});
|
|
@@ -11048,19 +11971,19 @@ async function routeDecide(sessionId) {
|
|
|
11048
11971
|
return;
|
|
11049
11972
|
}
|
|
11050
11973
|
if (!model || !VALID_MODELS.has(model)) return;
|
|
11051
|
-
const lastFile =
|
|
11974
|
+
const lastFile = join24(PTY_DIR, "route-last-" + sid);
|
|
11052
11975
|
let last = "";
|
|
11053
11976
|
try {
|
|
11054
|
-
last =
|
|
11977
|
+
last = readFileSync25(lastFile, "utf-8").trim();
|
|
11055
11978
|
} catch {
|
|
11056
11979
|
}
|
|
11057
11980
|
if (model === last) return;
|
|
11058
11981
|
try {
|
|
11059
|
-
|
|
11982
|
+
writeFileSync19(lastFile, model);
|
|
11060
11983
|
} catch {
|
|
11061
11984
|
}
|
|
11062
11985
|
try {
|
|
11063
|
-
|
|
11986
|
+
writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
|
|
11064
11987
|
} catch {
|
|
11065
11988
|
}
|
|
11066
11989
|
}
|
|
@@ -11068,9 +11991,9 @@ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
|
|
|
11068
11991
|
var init_routeDecide = __esm({
|
|
11069
11992
|
"cli/local-cc/routeDecide.ts"() {
|
|
11070
11993
|
"use strict";
|
|
11071
|
-
SYNKRO_DIR13 =
|
|
11072
|
-
PTY_DIR =
|
|
11073
|
-
SESSIONS_DIR2 =
|
|
11994
|
+
SYNKRO_DIR13 = join24(homedir25(), ".synkro");
|
|
11995
|
+
PTY_DIR = join24(SYNKRO_DIR13, "pty");
|
|
11996
|
+
SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
|
|
11074
11997
|
VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11075
11998
|
GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11076
11999
|
}
|
|
@@ -11081,9 +12004,9 @@ var routeOrchestrate_exports = {};
|
|
|
11081
12004
|
__export(routeOrchestrate_exports, {
|
|
11082
12005
|
routeAndResubmit: () => routeAndResubmit
|
|
11083
12006
|
});
|
|
11084
|
-
import { readFileSync as
|
|
11085
|
-
import { homedir as
|
|
11086
|
-
import { join as
|
|
12007
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
12008
|
+
import { homedir as homedir26 } from "os";
|
|
12009
|
+
import { join as join25 } from "path";
|
|
11087
12010
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
11088
12011
|
function safeSid2(sid) {
|
|
11089
12012
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -11093,7 +12016,7 @@ function safeSession(s) {
|
|
|
11093
12016
|
}
|
|
11094
12017
|
function loadMcpJwt2() {
|
|
11095
12018
|
try {
|
|
11096
|
-
return
|
|
12019
|
+
return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
11097
12020
|
} catch {
|
|
11098
12021
|
return "";
|
|
11099
12022
|
}
|
|
@@ -11103,7 +12026,7 @@ async function classifyTask(prompt) {
|
|
|
11103
12026
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
|
|
11104
12027
|
method: "POST",
|
|
11105
12028
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
|
|
11106
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
12029
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
11107
12030
|
signal: AbortSignal.timeout(6e4)
|
|
11108
12031
|
});
|
|
11109
12032
|
if (!resp.ok) return null;
|
|
@@ -11119,7 +12042,7 @@ async function classifyTask(prompt) {
|
|
|
11119
12042
|
}
|
|
11120
12043
|
function lastRoutedModel(sid) {
|
|
11121
12044
|
try {
|
|
11122
|
-
const v =
|
|
12045
|
+
const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
|
|
11123
12046
|
return VALID_MODELS2.has(v) ? v : "";
|
|
11124
12047
|
} catch {
|
|
11125
12048
|
return "";
|
|
@@ -11129,12 +12052,12 @@ function resolveSession(sid, tmuxSession) {
|
|
|
11129
12052
|
const candidates = [];
|
|
11130
12053
|
if (tmuxSession) candidates.push(tmuxSession);
|
|
11131
12054
|
try {
|
|
11132
|
-
const rec = JSON.parse(
|
|
12055
|
+
const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
|
|
11133
12056
|
if (rec.tmux_session) candidates.push(rec.tmux_session);
|
|
11134
12057
|
} catch {
|
|
11135
12058
|
}
|
|
11136
12059
|
try {
|
|
11137
|
-
candidates.push(
|
|
12060
|
+
candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
|
|
11138
12061
|
} catch {
|
|
11139
12062
|
}
|
|
11140
12063
|
for (const c of candidates) if (c && safeSession(c)) return c;
|
|
@@ -11169,12 +12092,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
|
|
|
11169
12092
|
}
|
|
11170
12093
|
await wait(500);
|
|
11171
12094
|
try {
|
|
11172
|
-
|
|
12095
|
+
writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
|
|
11173
12096
|
} catch {
|
|
11174
12097
|
}
|
|
11175
12098
|
}
|
|
11176
12099
|
try {
|
|
11177
|
-
|
|
12100
|
+
writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
|
|
11178
12101
|
} catch {
|
|
11179
12102
|
}
|
|
11180
12103
|
sk("C-u");
|
|
@@ -11188,10 +12111,10 @@ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2,
|
|
|
11188
12111
|
var init_routeOrchestrate = __esm({
|
|
11189
12112
|
"cli/local-cc/routeOrchestrate.ts"() {
|
|
11190
12113
|
"use strict";
|
|
11191
|
-
SYNKRO_DIR14 =
|
|
11192
|
-
PTY_DIR2 =
|
|
11193
|
-
SESSIONS_DIR3 =
|
|
11194
|
-
ACTIVE_SESSION_FILE2 =
|
|
12114
|
+
SYNKRO_DIR14 = join25(homedir26(), ".synkro");
|
|
12115
|
+
PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
|
|
12116
|
+
SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
|
|
12117
|
+
ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
|
|
11195
12118
|
VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11196
12119
|
GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11197
12120
|
wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -11203,14 +12126,14 @@ var routingToggle_exports = {};
|
|
|
11203
12126
|
__export(routingToggle_exports, {
|
|
11204
12127
|
routingCommand: () => routingCommand
|
|
11205
12128
|
});
|
|
11206
|
-
import { writeFileSync as
|
|
11207
|
-
import { homedir as
|
|
11208
|
-
import { join as
|
|
12129
|
+
import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
|
|
12130
|
+
import { homedir as homedir27 } from "os";
|
|
12131
|
+
import { join as join26 } from "path";
|
|
11209
12132
|
function safeSid3(sid) {
|
|
11210
12133
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
11211
12134
|
}
|
|
11212
12135
|
function markerFor(session) {
|
|
11213
|
-
return session ?
|
|
12136
|
+
return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
|
|
11214
12137
|
}
|
|
11215
12138
|
function routingCommand(args2) {
|
|
11216
12139
|
const sub = (args2[0] || "status").trim();
|
|
@@ -11219,7 +12142,7 @@ function routingCommand(args2) {
|
|
|
11219
12142
|
if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
|
|
11220
12143
|
if (sub === "on") {
|
|
11221
12144
|
try {
|
|
11222
|
-
|
|
12145
|
+
writeFileSync21(markerFor(session), "1");
|
|
11223
12146
|
} catch (e) {
|
|
11224
12147
|
console.error("routing on failed:", String(e));
|
|
11225
12148
|
return;
|
|
@@ -11242,7 +12165,7 @@ function routingCommand(args2) {
|
|
|
11242
12165
|
for (const f of safeList()) {
|
|
11243
12166
|
if (f === "routing-on" || f.startsWith("routing-on-")) {
|
|
11244
12167
|
try {
|
|
11245
|
-
unlinkSync9(
|
|
12168
|
+
unlinkSync9(join26(PTY_DIR3, f));
|
|
11246
12169
|
removed++;
|
|
11247
12170
|
} catch {
|
|
11248
12171
|
}
|
|
@@ -11274,14 +12197,14 @@ var PTY_DIR3;
|
|
|
11274
12197
|
var init_routingToggle = __esm({
|
|
11275
12198
|
"cli/local-cc/routingToggle.ts"() {
|
|
11276
12199
|
"use strict";
|
|
11277
|
-
PTY_DIR3 =
|
|
12200
|
+
PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
|
|
11278
12201
|
}
|
|
11279
12202
|
});
|
|
11280
12203
|
|
|
11281
12204
|
// cli/local-cc/pueue.ts
|
|
11282
12205
|
import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
|
|
11283
|
-
import { homedir as
|
|
11284
|
-
import { join as
|
|
12206
|
+
import { homedir as homedir28 } from "os";
|
|
12207
|
+
import { join as join27 } from "path";
|
|
11285
12208
|
import { connect as connect2 } from "net";
|
|
11286
12209
|
function pueueAvailable() {
|
|
11287
12210
|
const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
@@ -11347,7 +12270,7 @@ function startTask(opts = {}) {
|
|
|
11347
12270
|
spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
11348
12271
|
existing = findTask(ch);
|
|
11349
12272
|
}
|
|
11350
|
-
const runScript =
|
|
12273
|
+
const runScript = join27(cwd, "run-claude.sh");
|
|
11351
12274
|
const args2 = [
|
|
11352
12275
|
"add",
|
|
11353
12276
|
"--label",
|
|
@@ -11477,12 +12400,12 @@ var init_pueue = __esm({
|
|
|
11477
12400
|
"use strict";
|
|
11478
12401
|
TASK_LABEL = "synkro-local-cc";
|
|
11479
12402
|
TMUX_SESSION = "synkro-local-cc";
|
|
11480
|
-
SESSION_DIR2 =
|
|
12403
|
+
SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
|
|
11481
12404
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
11482
12405
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
11483
|
-
SESSION_DIR_22 =
|
|
11484
|
-
SESSION_DIR_32 =
|
|
11485
|
-
SESSION_DIR_42 =
|
|
12406
|
+
SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
|
|
12407
|
+
SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
|
|
12408
|
+
SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
|
|
11486
12409
|
PueueError = class extends Error {
|
|
11487
12410
|
constructor(message, cause) {
|
|
11488
12411
|
super(message);
|
|
@@ -11497,13 +12420,13 @@ var init_pueue = __esm({
|
|
|
11497
12420
|
});
|
|
11498
12421
|
|
|
11499
12422
|
// cli/local-cc/settings.ts
|
|
11500
|
-
import { existsSync as existsSync28, readFileSync as
|
|
11501
|
-
import { homedir as
|
|
11502
|
-
import { join as
|
|
12423
|
+
import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
|
|
12424
|
+
import { homedir as homedir29 } from "os";
|
|
12425
|
+
import { join as join28 } from "path";
|
|
11503
12426
|
function isLocalCCEnabled() {
|
|
11504
12427
|
if (!existsSync28(CONFIG_PATH5)) return false;
|
|
11505
12428
|
try {
|
|
11506
|
-
const content =
|
|
12429
|
+
const content = readFileSync27(CONFIG_PATH5, "utf-8");
|
|
11507
12430
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
11508
12431
|
return match?.[1] === "yes";
|
|
11509
12432
|
} catch {
|
|
@@ -11514,7 +12437,7 @@ var CONFIG_PATH5;
|
|
|
11514
12437
|
var init_settings = __esm({
|
|
11515
12438
|
"cli/local-cc/settings.ts"() {
|
|
11516
12439
|
"use strict";
|
|
11517
|
-
CONFIG_PATH5 =
|
|
12440
|
+
CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
|
|
11518
12441
|
}
|
|
11519
12442
|
});
|
|
11520
12443
|
|
|
@@ -11524,10 +12447,10 @@ __export(localCc_exports, {
|
|
|
11524
12447
|
localCcCommand: () => localCcCommand
|
|
11525
12448
|
});
|
|
11526
12449
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
11527
|
-
import { homedir as
|
|
11528
|
-
import { join as
|
|
12450
|
+
import { homedir as homedir30 } from "os";
|
|
12451
|
+
import { join as join29 } from "path";
|
|
11529
12452
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
11530
|
-
import { existsSync as existsSync29, readFileSync as
|
|
12453
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
11531
12454
|
function deploymentMode() {
|
|
11532
12455
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
11533
12456
|
if (env === "docker") return "docker";
|
|
@@ -11634,14 +12557,14 @@ TROUBLESHOOTING
|
|
|
11634
12557
|
}
|
|
11635
12558
|
function readGatewayUrl() {
|
|
11636
12559
|
if (existsSync29(CONFIG_PATH6)) {
|
|
11637
|
-
const m =
|
|
12560
|
+
const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
11638
12561
|
if (m) return m[1];
|
|
11639
12562
|
}
|
|
11640
12563
|
return "https://api.synkro.sh";
|
|
11641
12564
|
}
|
|
11642
12565
|
function updateLocalInferenceFlag(enabled) {
|
|
11643
12566
|
if (!existsSync29(CONFIG_PATH6)) return;
|
|
11644
|
-
let content =
|
|
12567
|
+
let content = readFileSync28(CONFIG_PATH6, "utf-8");
|
|
11645
12568
|
const flag = enabled ? "yes" : "no";
|
|
11646
12569
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
11647
12570
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -11650,7 +12573,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
11650
12573
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
11651
12574
|
`;
|
|
11652
12575
|
}
|
|
11653
|
-
|
|
12576
|
+
writeFileSync22(CONFIG_PATH6, content, "utf-8");
|
|
11654
12577
|
}
|
|
11655
12578
|
async function setServerGradingProvider(provider) {
|
|
11656
12579
|
await ensureValidToken();
|
|
@@ -11679,7 +12602,7 @@ async function cmdStatus() {
|
|
|
11679
12602
|
} else {
|
|
11680
12603
|
console.log(`synkro-server container: running (${status.image})`);
|
|
11681
12604
|
try {
|
|
11682
|
-
const r = await fetch(
|
|
12605
|
+
const r = await fetch(status.healthz, { signal: AbortSignal.timeout(3e3) });
|
|
11683
12606
|
console.log(`Health probe: ${r.ok ? "ok" : `HTTP ${r.status}`}`);
|
|
11684
12607
|
} catch (err) {
|
|
11685
12608
|
console.log(`Health probe: ${err.message}`);
|
|
@@ -12084,8 +13007,8 @@ var init_localCc = __esm({
|
|
|
12084
13007
|
init_install();
|
|
12085
13008
|
init_client2();
|
|
12086
13009
|
init_stub();
|
|
12087
|
-
SYNKRO_CONFIG_PATH =
|
|
12088
|
-
CONFIG_PATH6 =
|
|
13010
|
+
SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
|
|
13011
|
+
CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
|
|
12089
13012
|
}
|
|
12090
13013
|
});
|
|
12091
13014
|
|
|
@@ -12094,14 +13017,14 @@ var import_exports = {};
|
|
|
12094
13017
|
__export(import_exports, {
|
|
12095
13018
|
importCommand: () => importCommand
|
|
12096
13019
|
});
|
|
12097
|
-
import { existsSync as existsSync30, readFileSync as
|
|
12098
|
-
import { homedir as
|
|
12099
|
-
import { join as
|
|
13020
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
|
|
13021
|
+
import { homedir as homedir31 } from "os";
|
|
13022
|
+
import { join as join30 } from "path";
|
|
12100
13023
|
import { execSync as execSync6 } from "child_process";
|
|
12101
13024
|
import { createInterface as createInterface4 } from "readline";
|
|
12102
13025
|
function readMcpJwt() {
|
|
12103
13026
|
try {
|
|
12104
|
-
return
|
|
13027
|
+
return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
12105
13028
|
} catch {
|
|
12106
13029
|
return "";
|
|
12107
13030
|
}
|
|
@@ -12109,7 +13032,7 @@ function readMcpJwt() {
|
|
|
12109
13032
|
function readConfigEnv2() {
|
|
12110
13033
|
const out = {};
|
|
12111
13034
|
try {
|
|
12112
|
-
for (const line of
|
|
13035
|
+
for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
|
|
12113
13036
|
const t = line.trim();
|
|
12114
13037
|
if (!t || t.startsWith("#")) continue;
|
|
12115
13038
|
const eq = t.indexOf("=");
|
|
@@ -12121,7 +13044,7 @@ function readConfigEnv2() {
|
|
|
12121
13044
|
}
|
|
12122
13045
|
function projectsFolder() {
|
|
12123
13046
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
12124
|
-
const dir =
|
|
13047
|
+
const dir = join30(homedir31(), ".claude", "projects", sanitized);
|
|
12125
13048
|
return existsSync30(dir) ? dir : null;
|
|
12126
13049
|
}
|
|
12127
13050
|
function repoName() {
|
|
@@ -12161,7 +13084,7 @@ function extractToolResultText(content, e) {
|
|
|
12161
13084
|
return t;
|
|
12162
13085
|
}
|
|
12163
13086
|
function parseSession(filePath, sessionId) {
|
|
12164
|
-
const lines =
|
|
13087
|
+
const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
|
|
12165
13088
|
const messages = [];
|
|
12166
13089
|
const actions = [];
|
|
12167
13090
|
let step = 0;
|
|
@@ -12241,7 +13164,7 @@ async function importCommand() {
|
|
|
12241
13164
|
return;
|
|
12242
13165
|
}
|
|
12243
13166
|
}
|
|
12244
|
-
const sessions = files.map((f) => parseSession(
|
|
13167
|
+
const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
12245
13168
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
12246
13169
|
let ok = 0, fail = 0;
|
|
12247
13170
|
if (isCloud) {
|
|
@@ -12316,7 +13239,7 @@ var init_import = __esm({
|
|
|
12316
13239
|
"cli/commands/import.ts"() {
|
|
12317
13240
|
"use strict";
|
|
12318
13241
|
init_stub();
|
|
12319
|
-
CONFIG_PATH7 =
|
|
13242
|
+
CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
|
|
12320
13243
|
}
|
|
12321
13244
|
});
|
|
12322
13245
|
|
|
@@ -12358,10 +13281,10 @@ var init_packVerify = __esm({
|
|
|
12358
13281
|
});
|
|
12359
13282
|
|
|
12360
13283
|
// cli/installer/lockfile.ts
|
|
12361
|
-
import { existsSync as existsSync31, readFileSync as
|
|
12362
|
-
import { join as
|
|
13284
|
+
import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
|
|
13285
|
+
import { join as join31 } from "path";
|
|
12363
13286
|
function lockPath(repoRoot2) {
|
|
12364
|
-
return
|
|
13287
|
+
return join31(repoRoot2, LOCK_FILE);
|
|
12365
13288
|
}
|
|
12366
13289
|
function writeLockfile(repoRoot2, entries) {
|
|
12367
13290
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -12379,7 +13302,7 @@ function writeLockfile(repoRoot2, entries) {
|
|
|
12379
13302
|
""
|
|
12380
13303
|
])
|
|
12381
13304
|
].join("\n");
|
|
12382
|
-
|
|
13305
|
+
writeFileSync23(lockPath(repoRoot2), body, "utf-8");
|
|
12383
13306
|
}
|
|
12384
13307
|
var LOCK_FILE;
|
|
12385
13308
|
var init_lockfile = __esm({
|
|
@@ -12394,9 +13317,9 @@ var sync_exports = {};
|
|
|
12394
13317
|
__export(sync_exports, {
|
|
12395
13318
|
syncCommand: () => syncCommand
|
|
12396
13319
|
});
|
|
12397
|
-
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as
|
|
12398
|
-
import { homedir as
|
|
12399
|
-
import { join as
|
|
13320
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13321
|
+
import { homedir as homedir32 } from "os";
|
|
13322
|
+
import { join as join32 } from "path";
|
|
12400
13323
|
function cacheKey(ref, version) {
|
|
12401
13324
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
12402
13325
|
}
|
|
@@ -12427,7 +13350,7 @@ async function syncCommand(_args = []) {
|
|
|
12427
13350
|
}
|
|
12428
13351
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
12429
13352
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
12430
|
-
const cacheDir =
|
|
13353
|
+
const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
|
|
12431
13354
|
if (!cloud) mkdirSync18(cacheDir, { recursive: true });
|
|
12432
13355
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
12433
13356
|
const lock = [];
|
|
@@ -12456,7 +13379,7 @@ async function syncCommand(_args = []) {
|
|
|
12456
13379
|
if (!cloud) {
|
|
12457
13380
|
const fname = cacheKey(ref, data.version);
|
|
12458
13381
|
keptCacheFiles.add(fname);
|
|
12459
|
-
|
|
13382
|
+
writeFileSync24(join32(cacheDir, fname), JSON.stringify({
|
|
12460
13383
|
ref,
|
|
12461
13384
|
version: data.version,
|
|
12462
13385
|
digest: data.digest,
|
|
@@ -12472,7 +13395,7 @@ async function syncCommand(_args = []) {
|
|
|
12472
13395
|
for (const f of readdirSync8(cacheDir)) {
|
|
12473
13396
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
12474
13397
|
try {
|
|
12475
|
-
rmSync4(
|
|
13398
|
+
rmSync4(join32(cacheDir, f));
|
|
12476
13399
|
} catch {
|
|
12477
13400
|
}
|
|
12478
13401
|
}
|
|
@@ -12501,13 +13424,13 @@ var whoami_exports = {};
|
|
|
12501
13424
|
__export(whoami_exports, {
|
|
12502
13425
|
whoamiCommand: () => whoamiCommand
|
|
12503
13426
|
});
|
|
12504
|
-
import { readFileSync as
|
|
12505
|
-
import { join as
|
|
12506
|
-
import { homedir as
|
|
13427
|
+
import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
|
|
13428
|
+
import { join as join33 } from "path";
|
|
13429
|
+
import { homedir as homedir33 } from "os";
|
|
12507
13430
|
function readConfigEnv3() {
|
|
12508
13431
|
if (!existsSync33(CONFIG_PATH8)) return {};
|
|
12509
13432
|
const out = {};
|
|
12510
|
-
for (const line of
|
|
13433
|
+
for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
|
|
12511
13434
|
const t = line.trim();
|
|
12512
13435
|
if (!t || t.startsWith("#")) continue;
|
|
12513
13436
|
const eq = t.indexOf("=");
|
|
@@ -12518,7 +13441,7 @@ function readConfigEnv3() {
|
|
|
12518
13441
|
function jwtStatus() {
|
|
12519
13442
|
try {
|
|
12520
13443
|
if (!existsSync33(JWT_PATH2)) return { status: "none" };
|
|
12521
|
-
const jwt2 =
|
|
13444
|
+
const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
|
|
12522
13445
|
if (!jwt2) return { status: "none" };
|
|
12523
13446
|
const payload = jwt2.split(".")[1];
|
|
12524
13447
|
if (!payload) return { status: "valid" };
|
|
@@ -12580,9 +13503,9 @@ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
|
|
|
12580
13503
|
var init_whoami = __esm({
|
|
12581
13504
|
"cli/commands/whoami.ts"() {
|
|
12582
13505
|
"use strict";
|
|
12583
|
-
SYNKRO_DIR15 =
|
|
12584
|
-
CONFIG_PATH8 =
|
|
12585
|
-
JWT_PATH2 =
|
|
13506
|
+
SYNKRO_DIR15 = join33(homedir33(), ".synkro");
|
|
13507
|
+
CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
|
|
13508
|
+
JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
|
|
12586
13509
|
GRADING_LABEL = {
|
|
12587
13510
|
local: "on-device worker pool",
|
|
12588
13511
|
cloud: "Synkro Cloud worker pool",
|
|
@@ -12627,12 +13550,12 @@ __export(linear_exports, {
|
|
|
12627
13550
|
formatLinks: () => formatLinks,
|
|
12628
13551
|
linearCommand: () => linearCommand
|
|
12629
13552
|
});
|
|
12630
|
-
import { readFileSync as
|
|
12631
|
-
import { homedir as
|
|
12632
|
-
import { join as
|
|
13553
|
+
import { readFileSync as readFileSync32 } from "fs";
|
|
13554
|
+
import { homedir as homedir34 } from "os";
|
|
13555
|
+
import { join as join34 } from "path";
|
|
12633
13556
|
function mcpJwt() {
|
|
12634
13557
|
try {
|
|
12635
|
-
return
|
|
13558
|
+
return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
|
|
12636
13559
|
} catch {
|
|
12637
13560
|
return "";
|
|
12638
13561
|
}
|
|
@@ -12671,7 +13594,7 @@ var SYNKRO_DIR16, PORT2, BASE;
|
|
|
12671
13594
|
var init_linear = __esm({
|
|
12672
13595
|
"cli/commands/linear.ts"() {
|
|
12673
13596
|
"use strict";
|
|
12674
|
-
SYNKRO_DIR16 =
|
|
13597
|
+
SYNKRO_DIR16 = join34(homedir34(), ".synkro");
|
|
12675
13598
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12676
13599
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
12677
13600
|
}
|
|
@@ -12679,7 +13602,7 @@ var init_linear = __esm({
|
|
|
12679
13602
|
|
|
12680
13603
|
// cli/scanning/cveReachability.ts
|
|
12681
13604
|
import { parse } from "@babel/parser";
|
|
12682
|
-
import { readFileSync as
|
|
13605
|
+
import { readFileSync as readFileSync33 } from "fs";
|
|
12683
13606
|
function walk(node, visit) {
|
|
12684
13607
|
if (!node || typeof node.type !== "string") return;
|
|
12685
13608
|
visit(node);
|
|
@@ -12821,9 +13744,9 @@ var init_cveReachability = __esm({
|
|
|
12821
13744
|
|
|
12822
13745
|
// cli/reachability/reachabilityScan.ts
|
|
12823
13746
|
import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
|
|
12824
|
-
import { readFileSync as
|
|
12825
|
-
import { join as
|
|
12826
|
-
import { homedir as
|
|
13747
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
|
|
13748
|
+
import { join as join35 } from "path";
|
|
13749
|
+
import { homedir as homedir35 } from "os";
|
|
12827
13750
|
import { createRequire } from "module";
|
|
12828
13751
|
function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
12829
13752
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -12840,7 +13763,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12840
13763
|
}
|
|
12841
13764
|
for (const e of ents) {
|
|
12842
13765
|
if (files.length >= maxFiles) break;
|
|
12843
|
-
const full =
|
|
13766
|
+
const full = join35(dir, e.name);
|
|
12844
13767
|
if (e.isDirectory()) {
|
|
12845
13768
|
if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
12846
13769
|
continue;
|
|
@@ -12848,7 +13771,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12848
13771
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
12849
13772
|
const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
|
|
12850
13773
|
try {
|
|
12851
|
-
const content =
|
|
13774
|
+
const content = readFileSync34(full, "utf8");
|
|
12852
13775
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
12853
13776
|
} catch {
|
|
12854
13777
|
}
|
|
@@ -12867,12 +13790,12 @@ function cleanVersion(spec) {
|
|
|
12867
13790
|
function gatherManifestVersions(repoRoot2) {
|
|
12868
13791
|
const out = {};
|
|
12869
13792
|
const dirs = [repoRoot2];
|
|
12870
|
-
const pkgsDir =
|
|
13793
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12871
13794
|
if (existsSync34(pkgsDir)) {
|
|
12872
13795
|
try {
|
|
12873
13796
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12874
|
-
const pd =
|
|
12875
|
-
if (existsSync34(
|
|
13797
|
+
const pd = join35(pkgsDir, d);
|
|
13798
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12876
13799
|
}
|
|
12877
13800
|
} catch {
|
|
12878
13801
|
}
|
|
@@ -12881,7 +13804,7 @@ function gatherManifestVersions(repoRoot2) {
|
|
|
12881
13804
|
for (const dir of dirs) {
|
|
12882
13805
|
let pkg;
|
|
12883
13806
|
try {
|
|
12884
|
-
pkg = JSON.parse(
|
|
13807
|
+
pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12885
13808
|
} catch {
|
|
12886
13809
|
continue;
|
|
12887
13810
|
}
|
|
@@ -12901,28 +13824,28 @@ function findJelly(repoRoot2) {
|
|
|
12901
13824
|
try {
|
|
12902
13825
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
12903
13826
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
12904
|
-
const pkg = JSON.parse(
|
|
13827
|
+
const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
|
|
12905
13828
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
12906
13829
|
if (bin) {
|
|
12907
|
-
const p =
|
|
13830
|
+
const p = join35(dir, bin);
|
|
12908
13831
|
if (existsSync34(p)) return p;
|
|
12909
13832
|
}
|
|
12910
13833
|
} catch {
|
|
12911
13834
|
}
|
|
12912
13835
|
for (const base of [repoRoot2, process.cwd()]) {
|
|
12913
|
-
const b =
|
|
13836
|
+
const b = join35(base, "node_modules", ".bin", "jelly");
|
|
12914
13837
|
if (existsSync34(b)) return b;
|
|
12915
13838
|
}
|
|
12916
13839
|
return null;
|
|
12917
13840
|
}
|
|
12918
13841
|
function findEntries(repoRoot2) {
|
|
12919
13842
|
const dirs = [repoRoot2];
|
|
12920
|
-
const pkgsDir =
|
|
13843
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12921
13844
|
if (existsSync34(pkgsDir)) {
|
|
12922
13845
|
try {
|
|
12923
13846
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12924
|
-
const pd =
|
|
12925
|
-
if (existsSync34(
|
|
13847
|
+
const pd = join35(pkgsDir, d);
|
|
13848
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12926
13849
|
}
|
|
12927
13850
|
} catch {
|
|
12928
13851
|
}
|
|
@@ -12930,11 +13853,11 @@ function findEntries(repoRoot2) {
|
|
|
12930
13853
|
const entries = [];
|
|
12931
13854
|
for (const dir of dirs) {
|
|
12932
13855
|
try {
|
|
12933
|
-
const pkg = JSON.parse(
|
|
13856
|
+
const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12934
13857
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
12935
13858
|
for (const c of cands) {
|
|
12936
13859
|
if (typeof c !== "string") continue;
|
|
12937
|
-
const f =
|
|
13860
|
+
const f = join35(dir, c);
|
|
12938
13861
|
if (existsSync34(f)) {
|
|
12939
13862
|
entries.push(f);
|
|
12940
13863
|
break;
|
|
@@ -12970,7 +13893,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
12970
13893
|
const commit = currentCommit(repoRoot2);
|
|
12971
13894
|
if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
|
|
12972
13895
|
try {
|
|
12973
|
-
const prev = JSON.parse(
|
|
13896
|
+
const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
|
|
12974
13897
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
12975
13898
|
} catch {
|
|
12976
13899
|
}
|
|
@@ -13059,7 +13982,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
13059
13982
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
13060
13983
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
|
|
13061
13984
|
try {
|
|
13062
|
-
|
|
13985
|
+
writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
13063
13986
|
} catch (e) {
|
|
13064
13987
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
13065
13988
|
}
|
|
@@ -13071,7 +13994,7 @@ var init_reachabilityScan = __esm({
|
|
|
13071
13994
|
"use strict";
|
|
13072
13995
|
init_cveReachability();
|
|
13073
13996
|
require2 = createRequire(import.meta.url);
|
|
13074
|
-
REACHABILITY_PATH =
|
|
13997
|
+
REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
|
|
13075
13998
|
}
|
|
13076
13999
|
});
|
|
13077
14000
|
|
|
@@ -13080,15 +14003,15 @@ var reachabilityScan_exports = {};
|
|
|
13080
14003
|
__export(reachabilityScan_exports, {
|
|
13081
14004
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
13082
14005
|
});
|
|
13083
|
-
import { readFileSync as
|
|
13084
|
-
import { join as
|
|
13085
|
-
import { homedir as
|
|
14006
|
+
import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
|
|
14007
|
+
import { join as join36 } from "path";
|
|
14008
|
+
import { homedir as homedir36 } from "os";
|
|
13086
14009
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
13087
14010
|
function readConfigEnv4() {
|
|
13088
|
-
const p =
|
|
14011
|
+
const p = join36(SYNKRO_DIR17, "config.env");
|
|
13089
14012
|
if (!existsSync35(p)) return {};
|
|
13090
14013
|
const out = {};
|
|
13091
|
-
for (const line of
|
|
14014
|
+
for (const line of readFileSync35(p, "utf-8").split("\n")) {
|
|
13092
14015
|
const t = line.trim();
|
|
13093
14016
|
if (!t || t.startsWith("#")) continue;
|
|
13094
14017
|
const eq = t.indexOf("=");
|
|
@@ -13120,11 +14043,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
13120
14043
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
13121
14044
|
let jwt2 = "";
|
|
13122
14045
|
try {
|
|
13123
|
-
jwt2 =
|
|
14046
|
+
jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
|
|
13124
14047
|
} catch {
|
|
13125
14048
|
}
|
|
13126
14049
|
if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
|
|
13127
|
-
const body =
|
|
14050
|
+
const body = readFileSync35(REACHABILITY_PATH, "utf-8");
|
|
13128
14051
|
try {
|
|
13129
14052
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
13130
14053
|
method: "POST",
|
|
@@ -13156,7 +14079,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
13156
14079
|
"cli/commands/reachabilityScan.ts"() {
|
|
13157
14080
|
"use strict";
|
|
13158
14081
|
init_reachabilityScan();
|
|
13159
|
-
SYNKRO_DIR17 =
|
|
14082
|
+
SYNKRO_DIR17 = join36(homedir36(), ".synkro");
|
|
13160
14083
|
}
|
|
13161
14084
|
});
|
|
13162
14085
|
|
|
@@ -13188,9 +14111,9 @@ async function startCommand(rest = []) {
|
|
|
13188
14111
|
console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
|
|
13189
14112
|
`);
|
|
13190
14113
|
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13191
|
-
const ready = await waitForContainerReady(
|
|
14114
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13192
14115
|
if (!ready) {
|
|
13193
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14116
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13194
14117
|
process.exit(1);
|
|
13195
14118
|
}
|
|
13196
14119
|
console.log("\nServer is running.");
|
|
@@ -13220,9 +14143,9 @@ async function updateCommand() {
|
|
|
13220
14143
|
console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
|
|
13221
14144
|
`);
|
|
13222
14145
|
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13223
|
-
const ready = await waitForContainerReady(
|
|
14146
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13224
14147
|
if (!ready) {
|
|
13225
|
-
console.error("\n\u26A0 container did not pass its health check within
|
|
14148
|
+
console.error("\n\u26A0 container did not pass its health check within 10m \u2014 check: docker logs synkro-server");
|
|
13226
14149
|
process.exit(1);
|
|
13227
14150
|
}
|
|
13228
14151
|
try {
|
|
@@ -13257,9 +14180,9 @@ async function restartCommand(rest = []) {
|
|
|
13257
14180
|
console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
|
|
13258
14181
|
`);
|
|
13259
14182
|
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13260
|
-
const ready = await waitForContainerReady(
|
|
14183
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13261
14184
|
if (!ready) {
|
|
13262
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14185
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13263
14186
|
process.exit(1);
|
|
13264
14187
|
}
|
|
13265
14188
|
console.log("\nServer restarted successfully.");
|
|
@@ -13271,11 +14194,13 @@ async function restartCommand(rest = []) {
|
|
|
13271
14194
|
console.warn("\u26A0 workers did not register within 30s \u2014 skill sync skipped");
|
|
13272
14195
|
}
|
|
13273
14196
|
}
|
|
14197
|
+
var LOCAL_CONTAINER_READY_TIMEOUT_MS;
|
|
13274
14198
|
var init_lifecycle = __esm({
|
|
13275
14199
|
"cli/commands/lifecycle.ts"() {
|
|
13276
14200
|
"use strict";
|
|
13277
14201
|
init_dockerInstall();
|
|
13278
14202
|
init_install();
|
|
14203
|
+
LOCAL_CONTAINER_READY_TIMEOUT_MS = 6e5;
|
|
13279
14204
|
}
|
|
13280
14205
|
});
|
|
13281
14206
|
|
|
@@ -13284,13 +14209,13 @@ var config_exports = {};
|
|
|
13284
14209
|
__export(config_exports, {
|
|
13285
14210
|
configCommand: () => configCommand
|
|
13286
14211
|
});
|
|
13287
|
-
import { readFileSync as
|
|
13288
|
-
import { join as
|
|
13289
|
-
import { homedir as
|
|
14212
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
|
|
14213
|
+
import { join as join37 } from "path";
|
|
14214
|
+
import { homedir as homedir37 } from "os";
|
|
13290
14215
|
function readConfigEnv5() {
|
|
13291
14216
|
if (!existsSync36(CONFIG_PATH9)) return {};
|
|
13292
14217
|
const out = {};
|
|
13293
|
-
for (const line of
|
|
14218
|
+
for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
13294
14219
|
const t = line.trim();
|
|
13295
14220
|
if (!t || t.startsWith("#")) continue;
|
|
13296
14221
|
const eq = t.indexOf("=");
|
|
@@ -13303,7 +14228,7 @@ function updateConfigValue(key, value) {
|
|
|
13303
14228
|
console.error("No config found. Run `synkro install` first.");
|
|
13304
14229
|
process.exit(1);
|
|
13305
14230
|
}
|
|
13306
|
-
const lines =
|
|
14231
|
+
const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
|
|
13307
14232
|
const pattern = new RegExp(`^${key}=`);
|
|
13308
14233
|
let found = false;
|
|
13309
14234
|
const updated = lines.map((line) => {
|
|
@@ -13314,7 +14239,7 @@ function updateConfigValue(key, value) {
|
|
|
13314
14239
|
return line;
|
|
13315
14240
|
});
|
|
13316
14241
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
13317
|
-
|
|
14242
|
+
writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
13318
14243
|
}
|
|
13319
14244
|
function resolveInferenceMode(cfg) {
|
|
13320
14245
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -13472,8 +14397,8 @@ var init_config = __esm({
|
|
|
13472
14397
|
"use strict";
|
|
13473
14398
|
init_stub();
|
|
13474
14399
|
init_optout();
|
|
13475
|
-
SYNKRO_DIR18 =
|
|
13476
|
-
CONFIG_PATH9 =
|
|
14400
|
+
SYNKRO_DIR18 = join37(homedir37(), ".synkro");
|
|
14401
|
+
CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
|
|
13477
14402
|
}
|
|
13478
14403
|
});
|
|
13479
14404
|
|
|
@@ -13662,14 +14587,14 @@ Usage:
|
|
|
13662
14587
|
});
|
|
13663
14588
|
|
|
13664
14589
|
// cli/bootstrap.js
|
|
13665
|
-
import { readFileSync as
|
|
14590
|
+
import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
|
|
13666
14591
|
import { resolve as resolve5 } from "path";
|
|
13667
14592
|
var envCandidates = [
|
|
13668
14593
|
resolve5(process.env.HOME ?? "", ".synkro", "config.env")
|
|
13669
14594
|
];
|
|
13670
14595
|
for (const envPath of envCandidates) {
|
|
13671
14596
|
if (!existsSync37(envPath)) continue;
|
|
13672
|
-
const envContent =
|
|
14597
|
+
const envContent = readFileSync37(envPath, "utf-8");
|
|
13673
14598
|
for (const line of envContent.split("\n")) {
|
|
13674
14599
|
const trimmed = line.trim();
|
|
13675
14600
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -13686,7 +14611,7 @@ var subArgs = args.slice(1);
|
|
|
13686
14611
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
13687
14612
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
13688
14613
|
function printVersion() {
|
|
13689
|
-
console.log("1.7.
|
|
14614
|
+
console.log("1.7.90");
|
|
13690
14615
|
}
|
|
13691
14616
|
function printHelp2() {
|
|
13692
14617
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|