@synkro-sh/cli 1.7.88 → 1.7.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.js +1421 -596
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -147,7 +147,7 @@ function getIdentity() {
|
|
|
147
147
|
if (cached2) return cached2;
|
|
148
148
|
let cliVersion = "0.0.0";
|
|
149
149
|
try {
|
|
150
|
-
cliVersion = "1.7.
|
|
150
|
+
cliVersion = "1.7.89";
|
|
151
151
|
} catch {
|
|
152
152
|
}
|
|
153
153
|
const creds = loadCredentialsIdentity();
|
|
@@ -283,6 +283,100 @@ var init_emit = __esm({
|
|
|
283
283
|
}
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
// cli/telemetry/httpSql.ts
|
|
287
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
288
|
+
import { homedir as homedir4 } from "os";
|
|
289
|
+
import { join as join4 } from "path";
|
|
290
|
+
function quoteIdentifier(value) {
|
|
291
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(value)) throw new Error("invalid SQL identifier");
|
|
292
|
+
return `"${value}"`;
|
|
293
|
+
}
|
|
294
|
+
function shiftPlaceholders(text, offset) {
|
|
295
|
+
return text.replace(/\$(\d+)/g, (_, raw) => `$${Number(raw) + offset}`);
|
|
296
|
+
}
|
|
297
|
+
function bulkFragment(rows, columns) {
|
|
298
|
+
if (!rows.length || !columns.length) throw new Error("empty SQL helper");
|
|
299
|
+
const params = [];
|
|
300
|
+
const tuples = rows.map((row) => {
|
|
301
|
+
const placeholders = columns.map((column) => {
|
|
302
|
+
let value = row[column];
|
|
303
|
+
let cast = "";
|
|
304
|
+
if (column === "context" && typeof value === "object" && value !== null) {
|
|
305
|
+
value = JSON.stringify(value);
|
|
306
|
+
cast = "::jsonb";
|
|
307
|
+
}
|
|
308
|
+
params.push(value ?? null);
|
|
309
|
+
return `$${params.length}${cast}`;
|
|
310
|
+
});
|
|
311
|
+
return `(${placeholders.join(", ")})`;
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
[FRAGMENT]: true,
|
|
315
|
+
text: `(${columns.map(quoteIdentifier).join(", ")}) VALUES ${tuples.join(", ")}`,
|
|
316
|
+
params
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function createTelemetryHttpSql(options = {}) {
|
|
320
|
+
const port = options.port ?? Number.parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
321
|
+
const endpoint = `http://127.0.0.1:${port}/api/local/telemetry/query`;
|
|
322
|
+
const token = options.token ?? readFileSync3(join4(homedir4(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
323
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
324
|
+
const query = async (text, params = []) => {
|
|
325
|
+
const response = await fetchImpl(endpoint, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: {
|
|
328
|
+
Authorization: `Bearer ${token}`,
|
|
329
|
+
"Content-Type": "application/json"
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({ text, params }),
|
|
332
|
+
signal: AbortSignal.timeout(3e4)
|
|
333
|
+
});
|
|
334
|
+
const payload = await response.json().catch(() => ({}));
|
|
335
|
+
if (!response.ok) throw new Error(payload.error || `local telemetry query failed (${response.status})`);
|
|
336
|
+
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
337
|
+
Object.defineProperty(rows, "count", {
|
|
338
|
+
value: Number(payload.count ?? rows.length),
|
|
339
|
+
enumerable: false,
|
|
340
|
+
configurable: true
|
|
341
|
+
});
|
|
342
|
+
return rows;
|
|
343
|
+
};
|
|
344
|
+
const sql = function(first, ...values) {
|
|
345
|
+
if (Array.isArray(first) && "raw" in first) {
|
|
346
|
+
const strings = first;
|
|
347
|
+
let text = strings[0];
|
|
348
|
+
const params = [];
|
|
349
|
+
for (let i = 0; i < values.length; i++) {
|
|
350
|
+
const value = values[i];
|
|
351
|
+
if (value && typeof value === "object" && value[FRAGMENT]) {
|
|
352
|
+
text += shiftPlaceholders(value.text, params.length);
|
|
353
|
+
params.push(...value.params);
|
|
354
|
+
} else {
|
|
355
|
+
params.push(value);
|
|
356
|
+
text += `$${params.length}`;
|
|
357
|
+
}
|
|
358
|
+
text += strings[i + 1];
|
|
359
|
+
}
|
|
360
|
+
return query(text, params);
|
|
361
|
+
}
|
|
362
|
+
if (Array.isArray(first)) {
|
|
363
|
+
return bulkFragment(first, values);
|
|
364
|
+
}
|
|
365
|
+
throw new Error("unsupported SQL invocation");
|
|
366
|
+
};
|
|
367
|
+
sql.unsafe = query;
|
|
368
|
+
sql.end = async () => {
|
|
369
|
+
};
|
|
370
|
+
return sql;
|
|
371
|
+
}
|
|
372
|
+
var FRAGMENT;
|
|
373
|
+
var init_httpSql = __esm({
|
|
374
|
+
"cli/telemetry/httpSql.ts"() {
|
|
375
|
+
"use strict";
|
|
376
|
+
FRAGMENT = /* @__PURE__ */ Symbol("synkro-http-sql-fragment");
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
286
380
|
// cli/telemetry/db.ts
|
|
287
381
|
var db_exports = {};
|
|
288
382
|
__export(db_exports, {
|
|
@@ -290,24 +384,11 @@ __export(db_exports, {
|
|
|
290
384
|
connectDb: () => connectDb,
|
|
291
385
|
resetDbForTests: () => resetDbForTests
|
|
292
386
|
});
|
|
293
|
-
import postgres from "postgres";
|
|
294
387
|
async function connectDb() {
|
|
295
388
|
if (cached3) return cached3;
|
|
296
389
|
if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;
|
|
297
390
|
try {
|
|
298
|
-
const sql =
|
|
299
|
-
host: PGLITE_HOST,
|
|
300
|
-
port: PGLITE_PORT,
|
|
301
|
-
username: PGLITE_USER,
|
|
302
|
-
database: PGLITE_DB,
|
|
303
|
-
// max:1 mirrors synkro-server — PGlite is single-threaded; multiple
|
|
304
|
-
// sockets just queue at the multiplexer.
|
|
305
|
-
max: 1,
|
|
306
|
-
connect_timeout: 2,
|
|
307
|
-
idle_timeout: 30,
|
|
308
|
-
onnotice: () => {
|
|
309
|
-
}
|
|
310
|
-
});
|
|
391
|
+
const sql = createTelemetryHttpSql();
|
|
311
392
|
await sql`SELECT 1`;
|
|
312
393
|
cached3 = sql;
|
|
313
394
|
initFailedAt = 0;
|
|
@@ -317,7 +398,7 @@ async function connectDb() {
|
|
|
317
398
|
initFailedAt = Date.now();
|
|
318
399
|
if (process.env.SYNKRO_TELEMETRY_DEBUG === "1") {
|
|
319
400
|
const msg = err instanceof Error ? err.message : String(err);
|
|
320
|
-
process.stderr.write(`[synkro] telemetry
|
|
401
|
+
process.stderr.write(`[synkro] local telemetry API unreachable: ${msg}
|
|
321
402
|
`);
|
|
322
403
|
}
|
|
323
404
|
return null;
|
|
@@ -354,17 +435,11 @@ function resetDbForTests() {
|
|
|
354
435
|
initFailedAt = 0;
|
|
355
436
|
migrated = false;
|
|
356
437
|
}
|
|
357
|
-
var
|
|
438
|
+
var SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
|
|
358
439
|
var init_db = __esm({
|
|
359
440
|
"cli/telemetry/db.ts"() {
|
|
360
441
|
"use strict";
|
|
361
|
-
|
|
362
|
-
PGLITE_PORT = parseInt(
|
|
363
|
-
process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || "15433",
|
|
364
|
-
10
|
|
365
|
-
);
|
|
366
|
-
PGLITE_USER = process.env.SYNKRO_PGLITE_USER || "postgres";
|
|
367
|
-
PGLITE_DB = process.env.SYNKRO_PGLITE_DB || "postgres";
|
|
442
|
+
init_httpSql();
|
|
368
443
|
SCHEMA_VERSION = 1;
|
|
369
444
|
cached3 = null;
|
|
370
445
|
migrated = false;
|
|
@@ -438,9 +513,9 @@ var init_db = __esm({
|
|
|
438
513
|
});
|
|
439
514
|
|
|
440
515
|
// cli/telemetry/drain.ts
|
|
441
|
-
import { openSync, closeSync, fstatSync, readFileSync as
|
|
442
|
-
import { homedir as
|
|
443
|
-
import { join as
|
|
516
|
+
import { openSync, closeSync, fstatSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
|
|
517
|
+
import { homedir as homedir5 } from "os";
|
|
518
|
+
import { join as join5 } from "path";
|
|
444
519
|
function ensureDir3() {
|
|
445
520
|
if (!existsSync4(SYNKRO_DIR3)) mkdirSync3(SYNKRO_DIR3, { recursive: true, mode: 448 });
|
|
446
521
|
}
|
|
@@ -494,7 +569,7 @@ function parseQueueFile(path) {
|
|
|
494
569
|
if (!existsSync4(path)) return { rows: [], malformed: 0 };
|
|
495
570
|
let raw;
|
|
496
571
|
try {
|
|
497
|
-
raw =
|
|
572
|
+
raw = readFileSync4(path, "utf-8");
|
|
498
573
|
} catch {
|
|
499
574
|
return { rows: [], malformed: 0 };
|
|
500
575
|
}
|
|
@@ -534,7 +609,7 @@ function recoverProcessing() {
|
|
|
534
609
|
ensureDir3();
|
|
535
610
|
let body;
|
|
536
611
|
try {
|
|
537
|
-
body =
|
|
612
|
+
body = readFileSync4(PROCESSING_PATH, "utf-8");
|
|
538
613
|
} catch {
|
|
539
614
|
return;
|
|
540
615
|
}
|
|
@@ -663,39 +738,41 @@ async function insertEvents(sql, rows, mirroredIds) {
|
|
|
663
738
|
return inserted;
|
|
664
739
|
}
|
|
665
740
|
async function drainToPglite() {
|
|
666
|
-
const
|
|
667
|
-
if (
|
|
741
|
+
const queued = readQueue().rows;
|
|
742
|
+
if (queued.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
|
|
743
|
+
const rows = queued.length > MIRROR_MAX_PER_DRAIN ? queued.slice(-MIRROR_MAX_PER_DRAIN) : queued;
|
|
668
744
|
const sql = await connectDb();
|
|
669
|
-
if (!sql) return { ok: false, ingested: 0, pending:
|
|
745
|
+
if (!sql) return { ok: false, ingested: 0, pending: queued.length, reason: "db_unavailable" };
|
|
670
746
|
try {
|
|
671
747
|
const ingested = await insertEvents(sql, rows);
|
|
672
748
|
patchMetaCache({ last_drained_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
673
|
-
return { ok: true, ingested, pending:
|
|
749
|
+
return { ok: true, ingested, pending: queued.length };
|
|
674
750
|
} catch (err) {
|
|
675
751
|
const msg = err instanceof Error ? err.message : String(err);
|
|
676
|
-
return { ok: false, ingested: 0, pending:
|
|
752
|
+
return { ok: false, ingested: 0, pending: queued.length, reason: "error", error: msg };
|
|
677
753
|
}
|
|
678
754
|
}
|
|
679
|
-
var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, STALE_LOCK_MS, MAX_QUEUE_ROWS;
|
|
755
|
+
var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, MIRROR_MAX_PER_DRAIN, STALE_LOCK_MS, MAX_QUEUE_ROWS;
|
|
680
756
|
var init_drain = __esm({
|
|
681
757
|
"cli/telemetry/drain.ts"() {
|
|
682
758
|
"use strict";
|
|
683
759
|
init_db();
|
|
684
760
|
init_emit();
|
|
685
761
|
init_metaCache();
|
|
686
|
-
SYNKRO_DIR3 =
|
|
687
|
-
QUEUE_LOCK =
|
|
762
|
+
SYNKRO_DIR3 = join5(homedir5(), ".synkro");
|
|
763
|
+
QUEUE_LOCK = join5(SYNKRO_DIR3, ".telemetry-queue.lock");
|
|
688
764
|
PROCESSING_PATH = `${PENDING_PATH}.processing`;
|
|
689
765
|
CHUNK = 500;
|
|
766
|
+
MIRROR_MAX_PER_DRAIN = 5e3;
|
|
690
767
|
STALE_LOCK_MS = 6e4;
|
|
691
768
|
MAX_QUEUE_ROWS = 5e4;
|
|
692
769
|
}
|
|
693
770
|
});
|
|
694
771
|
|
|
695
772
|
// cli/telemetry/optout.ts
|
|
696
|
-
import { chmodSync, existsSync as existsSync5, readFileSync as
|
|
697
|
-
import { homedir as
|
|
698
|
-
import { join as
|
|
773
|
+
import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
774
|
+
import { homedir as homedir6 } from "os";
|
|
775
|
+
import { join as join6 } from "path";
|
|
699
776
|
function sanitize(raw, maxLen = 256) {
|
|
700
777
|
return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
|
|
701
778
|
}
|
|
@@ -708,7 +785,7 @@ function writeConfigEnvFlag(key, value) {
|
|
|
708
785
|
let content = "";
|
|
709
786
|
if (existsSync5(CONFIG_PATH2)) {
|
|
710
787
|
try {
|
|
711
|
-
content =
|
|
788
|
+
content = readFileSync5(CONFIG_PATH2, "utf-8");
|
|
712
789
|
} catch {
|
|
713
790
|
content = "";
|
|
714
791
|
}
|
|
@@ -778,8 +855,8 @@ var init_optout = __esm({
|
|
|
778
855
|
"use strict";
|
|
779
856
|
init_emit();
|
|
780
857
|
init_metaCache();
|
|
781
|
-
SYNKRO_DIR4 =
|
|
782
|
-
CONFIG_PATH2 =
|
|
858
|
+
SYNKRO_DIR4 = join6(homedir6(), ".synkro");
|
|
859
|
+
CONFIG_PATH2 = join6(SYNKRO_DIR4, "config.env");
|
|
783
860
|
KEY_ENABLED = "SYNKRO_TELEMETRY_ENABLED";
|
|
784
861
|
KEY_REMOTE = "SYNKRO_TELEMETRY_REMOTE_FLUSH";
|
|
785
862
|
}
|
|
@@ -828,15 +905,15 @@ __export(flush_exports, {
|
|
|
828
905
|
flushDetached: () => flushDetached
|
|
829
906
|
});
|
|
830
907
|
import { spawn } from "child_process";
|
|
831
|
-
import { existsSync as existsSync6, readFileSync as
|
|
832
|
-
import { homedir as
|
|
833
|
-
import { join as
|
|
908
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
909
|
+
import { homedir as homedir7 } from "os";
|
|
910
|
+
import { join as join7 } from "path";
|
|
834
911
|
function readConfigEnv() {
|
|
835
912
|
if (!existsSync6(CONFIG_PATH3)) return {};
|
|
836
913
|
const out = {};
|
|
837
914
|
let raw;
|
|
838
915
|
try {
|
|
839
|
-
raw =
|
|
916
|
+
raw = readFileSync6(CONFIG_PATH3, "utf-8");
|
|
840
917
|
} catch {
|
|
841
918
|
return {};
|
|
842
919
|
}
|
|
@@ -877,14 +954,14 @@ function resolveGatewayUrl() {
|
|
|
877
954
|
function loadJwt() {
|
|
878
955
|
if ((process.env.SYNKRO_STORAGE_MODE || "") === "cloud") {
|
|
879
956
|
try {
|
|
880
|
-
const mcp =
|
|
957
|
+
const mcp = readFileSync6(join7(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
|
|
881
958
|
if (mcp) return mcp;
|
|
882
959
|
} catch {
|
|
883
960
|
}
|
|
884
961
|
}
|
|
885
962
|
try {
|
|
886
963
|
if (!existsSync6(CREDS_PATH2)) return null;
|
|
887
|
-
const creds = JSON.parse(
|
|
964
|
+
const creds = JSON.parse(readFileSync6(CREDS_PATH2, "utf-8"));
|
|
888
965
|
return creds.access_token || null;
|
|
889
966
|
} catch {
|
|
890
967
|
return null;
|
|
@@ -1039,20 +1116,20 @@ var init_flush = __esm({
|
|
|
1039
1116
|
init_optout();
|
|
1040
1117
|
init_metaCache();
|
|
1041
1118
|
init_wire();
|
|
1042
|
-
SYNKRO_DIR5 =
|
|
1043
|
-
CONFIG_PATH3 =
|
|
1044
|
-
CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH ||
|
|
1119
|
+
SYNKRO_DIR5 = join7(homedir7(), ".synkro");
|
|
1120
|
+
CONFIG_PATH3 = join7(SYNKRO_DIR5, "config.env");
|
|
1121
|
+
CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join7(SYNKRO_DIR5, "credentials.json");
|
|
1045
1122
|
THROTTLE_MS = 6e4;
|
|
1046
1123
|
DEFAULT_GATEWAY = "https://api.synkro.sh";
|
|
1047
1124
|
}
|
|
1048
1125
|
});
|
|
1049
1126
|
|
|
1050
1127
|
// cli/telemetry/stats.ts
|
|
1051
|
-
import { existsSync as existsSync7, readFileSync as
|
|
1128
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync4 } from "fs";
|
|
1052
1129
|
function countPending() {
|
|
1053
1130
|
if (!existsSync7(PENDING_PATH)) return 0;
|
|
1054
1131
|
try {
|
|
1055
|
-
const raw =
|
|
1132
|
+
const raw = readFileSync7(PENDING_PATH, "utf-8");
|
|
1056
1133
|
if (!raw) return 0;
|
|
1057
1134
|
let n = 0;
|
|
1058
1135
|
for (const line of raw.split("\n")) if (line.trim()) n++;
|
|
@@ -1174,13 +1251,13 @@ var init_stats = __esm({
|
|
|
1174
1251
|
});
|
|
1175
1252
|
|
|
1176
1253
|
// cli/telemetry/purge.ts
|
|
1177
|
-
import { existsSync as existsSync8, readFileSync as
|
|
1254
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
1178
1255
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1179
1256
|
function unlinkPendingCount() {
|
|
1180
1257
|
if (!existsSync8(PENDING_PATH)) return 0;
|
|
1181
1258
|
let n = 0;
|
|
1182
1259
|
try {
|
|
1183
|
-
const raw =
|
|
1260
|
+
const raw = readFileSync8(PENDING_PATH, "utf-8");
|
|
1184
1261
|
for (const line of raw.split("\n")) if (line.trim()) n++;
|
|
1185
1262
|
} catch {
|
|
1186
1263
|
}
|
|
@@ -1345,8 +1422,8 @@ var init_telemetry = __esm({
|
|
|
1345
1422
|
|
|
1346
1423
|
// cli/installer/agentDetect.ts
|
|
1347
1424
|
import { existsSync as existsSync9 } from "fs";
|
|
1348
|
-
import { homedir as
|
|
1349
|
-
import { join as
|
|
1425
|
+
import { homedir as homedir8 } from "os";
|
|
1426
|
+
import { join as join8 } from "path";
|
|
1350
1427
|
import { execFileSync as execFileSync2, execSync } from "child_process";
|
|
1351
1428
|
function which(cmd3) {
|
|
1352
1429
|
try {
|
|
@@ -1367,21 +1444,21 @@ function getVersion(cmd3) {
|
|
|
1367
1444
|
}
|
|
1368
1445
|
function detectAgents() {
|
|
1369
1446
|
const agents = [];
|
|
1370
|
-
const home =
|
|
1447
|
+
const home = homedir8();
|
|
1371
1448
|
const claudeBinary = which("claude");
|
|
1372
|
-
const claudeConfigDir =
|
|
1449
|
+
const claudeConfigDir = join8(home, ".claude");
|
|
1373
1450
|
if (claudeBinary) {
|
|
1374
1451
|
agents.push({
|
|
1375
1452
|
kind: "claude_code",
|
|
1376
1453
|
name: "Claude Code",
|
|
1377
1454
|
binaryPath: claudeBinary,
|
|
1378
1455
|
configDir: claudeConfigDir,
|
|
1379
|
-
settingsPath:
|
|
1456
|
+
settingsPath: join8(claudeConfigDir, "settings.json"),
|
|
1380
1457
|
version: getVersion("claude")
|
|
1381
1458
|
});
|
|
1382
1459
|
}
|
|
1383
1460
|
const cursorBinary = which("cursor");
|
|
1384
|
-
const cursorConfigDir =
|
|
1461
|
+
const cursorConfigDir = join8(home, ".cursor");
|
|
1385
1462
|
const cursorApp = process.platform === "darwin" && existsSync9("/Applications/Cursor.app");
|
|
1386
1463
|
if (cursorBinary || cursorApp || existsSync9(cursorConfigDir)) {
|
|
1387
1464
|
let version;
|
|
@@ -1399,19 +1476,19 @@ function detectAgents() {
|
|
|
1399
1476
|
name: "Cursor",
|
|
1400
1477
|
binaryPath: cursorBinary,
|
|
1401
1478
|
configDir: cursorConfigDir,
|
|
1402
|
-
settingsPath:
|
|
1479
|
+
settingsPath: join8(cursorConfigDir, "hooks.json"),
|
|
1403
1480
|
version
|
|
1404
1481
|
});
|
|
1405
1482
|
}
|
|
1406
1483
|
const codexBinary = which("codex");
|
|
1407
|
-
const codexConfigDir = process.env.CODEX_HOME ||
|
|
1484
|
+
const codexConfigDir = process.env.CODEX_HOME || join8(home, ".codex");
|
|
1408
1485
|
if (codexBinary || existsSync9(codexConfigDir)) {
|
|
1409
1486
|
agents.push({
|
|
1410
1487
|
kind: "codex",
|
|
1411
1488
|
name: "Codex",
|
|
1412
1489
|
binaryPath: codexBinary,
|
|
1413
1490
|
configDir: codexConfigDir,
|
|
1414
|
-
settingsPath:
|
|
1491
|
+
settingsPath: join8(codexConfigDir, "hooks.json"),
|
|
1415
1492
|
version: codexBinary ? getVersion("codex") : void 0
|
|
1416
1493
|
});
|
|
1417
1494
|
}
|
|
@@ -1425,16 +1502,16 @@ var init_agentDetect = __esm({
|
|
|
1425
1502
|
|
|
1426
1503
|
// cli/installer/platform.ts
|
|
1427
1504
|
import { existsSync as existsSync10 } from "fs";
|
|
1428
|
-
import { homedir as
|
|
1429
|
-
import { join as
|
|
1505
|
+
import { homedir as homedir9 } from "os";
|
|
1506
|
+
import { join as join9 } from "path";
|
|
1430
1507
|
import { spawnSync } from "child_process";
|
|
1431
1508
|
function resolveBunBin() {
|
|
1432
1509
|
const finder = IS_WINDOWS ? "where" : "which";
|
|
1433
1510
|
const r = spawnSync(finder, ["bun"], { encoding: "utf-8", timeout: 5e3 });
|
|
1434
1511
|
const resolved = (r.stdout || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean);
|
|
1435
1512
|
if (resolved) return resolved;
|
|
1436
|
-
const home = process.env.USERPROFILE ||
|
|
1437
|
-
const candidates = IS_WINDOWS ? [
|
|
1513
|
+
const home = process.env.USERPROFILE || homedir9();
|
|
1514
|
+
const candidates = IS_WINDOWS ? [join9(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join9(home, ".bun", "bin", "bun")];
|
|
1438
1515
|
for (const p of candidates) if (existsSync10(p)) return p;
|
|
1439
1516
|
return IS_WINDOWS ? "bun.exe" : "bun";
|
|
1440
1517
|
}
|
|
@@ -1458,12 +1535,12 @@ var init_platform = __esm({
|
|
|
1458
1535
|
});
|
|
1459
1536
|
|
|
1460
1537
|
// cli/installer/ccHookConfig.ts
|
|
1461
|
-
import { existsSync as existsSync11, readFileSync as
|
|
1538
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1462
1539
|
import { dirname } from "path";
|
|
1463
1540
|
function readSettings(path) {
|
|
1464
1541
|
if (!existsSync11(path)) return {};
|
|
1465
1542
|
try {
|
|
1466
|
-
const raw =
|
|
1543
|
+
const raw = readFileSync9(path, "utf-8");
|
|
1467
1544
|
return JSON.parse(raw);
|
|
1468
1545
|
} catch (err) {
|
|
1469
1546
|
throw new Error(`Failed to parse ${path}: ${err.message}`);
|
|
@@ -1609,7 +1686,7 @@ function installCCHooks(settingsPath, config) {
|
|
|
1609
1686
|
}
|
|
1610
1687
|
if (config.taskActivateIntentScriptPath) {
|
|
1611
1688
|
settings.hooks.PreToolUse.push({
|
|
1612
|
-
matcher: "mcp__synkro-guardrails__activate_standard",
|
|
1689
|
+
matcher: "mcp__synkro[-_]guardrails__activate_standard",
|
|
1613
1690
|
hooks: [
|
|
1614
1691
|
{
|
|
1615
1692
|
type: "command",
|
|
@@ -1723,9 +1800,9 @@ var init_ccHookConfig = __esm({
|
|
|
1723
1800
|
});
|
|
1724
1801
|
|
|
1725
1802
|
// cli/installer/cursorHookConfig.ts
|
|
1726
|
-
import { readFileSync as
|
|
1803
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
|
|
1727
1804
|
import { dirname as dirname2, resolve, normalize } from "path";
|
|
1728
|
-
import { homedir as
|
|
1805
|
+
import { homedir as homedir10 } from "os";
|
|
1729
1806
|
function shellQuote(s) {
|
|
1730
1807
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1731
1808
|
}
|
|
@@ -1745,7 +1822,7 @@ function validateHooksPath(path) {
|
|
|
1745
1822
|
function readHooksFile(rawPath) {
|
|
1746
1823
|
const safePath = validateHooksPath(rawPath);
|
|
1747
1824
|
try {
|
|
1748
|
-
const raw =
|
|
1825
|
+
const raw = readFileSync10(safePath, "utf-8");
|
|
1749
1826
|
return JSON.parse(raw);
|
|
1750
1827
|
} catch (err) {
|
|
1751
1828
|
if (err?.code === "ENOENT") return { version: 1, hooks: {} };
|
|
@@ -1927,8 +2004,8 @@ var init_cursorHookConfig = __esm({
|
|
|
1927
2004
|
init_platform();
|
|
1928
2005
|
SYNKRO_MARKER2 = "__synkro_managed__";
|
|
1929
2006
|
ALLOWED_PARENT_DIRS = [
|
|
1930
|
-
resolve(
|
|
1931
|
-
resolve(
|
|
2007
|
+
resolve(homedir10(), ".cursor"),
|
|
2008
|
+
resolve(homedir10(), ".config", "cursor")
|
|
1932
2009
|
];
|
|
1933
2010
|
ALL_EVENTS = [
|
|
1934
2011
|
"sessionStart",
|
|
@@ -1948,9 +2025,9 @@ var init_cursorHookConfig = __esm({
|
|
|
1948
2025
|
});
|
|
1949
2026
|
|
|
1950
2027
|
// cli/installer/codexHookConfig.ts
|
|
1951
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2028
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
1952
2029
|
import { dirname as dirname3, resolve as resolve2, normalize as normalize2 } from "path";
|
|
1953
|
-
import { homedir as
|
|
2030
|
+
import { homedir as homedir11 } from "os";
|
|
1954
2031
|
function shellQuote2(s) {
|
|
1955
2032
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1956
2033
|
}
|
|
@@ -1967,8 +2044,8 @@ function codexCmd(scriptPath) {
|
|
|
1967
2044
|
function validateHooksPath2(path) {
|
|
1968
2045
|
const resolved = resolve2(normalize2(path));
|
|
1969
2046
|
const allowedParentDirs = [
|
|
1970
|
-
resolve2(
|
|
1971
|
-
resolve2(
|
|
2047
|
+
resolve2(homedir11(), ".codex"),
|
|
2048
|
+
resolve2(homedir11(), ".config", "codex"),
|
|
1972
2049
|
...process.env.CODEX_HOME ? [resolve2(process.env.CODEX_HOME)] : []
|
|
1973
2050
|
];
|
|
1974
2051
|
if (!allowedParentDirs.some((dir) => resolved.startsWith(dir + "/") || resolved === dir)) {
|
|
@@ -1980,7 +2057,7 @@ function readHooksFile2(rawPath) {
|
|
|
1980
2057
|
const safePath = validateHooksPath2(rawPath);
|
|
1981
2058
|
if (!existsSync12(safePath)) return { hooks: {} };
|
|
1982
2059
|
try {
|
|
1983
|
-
return JSON.parse(
|
|
2060
|
+
return JSON.parse(readFileSync11(safePath, "utf-8"));
|
|
1984
2061
|
} catch (err) {
|
|
1985
2062
|
throw new Error(`Failed to parse ${safePath}: ${err.message}`);
|
|
1986
2063
|
}
|
|
@@ -2026,13 +2103,19 @@ function installCodexHooks(hooksJsonPath, config) {
|
|
|
2026
2103
|
cmd(config.cvePrecheckScriptPath, 15),
|
|
2027
2104
|
...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
|
|
2028
2105
|
], M_EDIT);
|
|
2106
|
+
push(h, "PostToolUse", [
|
|
2107
|
+
cmd(config.editFollowupScriptPath, 30, "Recording edit")
|
|
2108
|
+
], M_EDIT);
|
|
2029
2109
|
push(h, "PreToolUse", [cmd(config.agentJudgeScriptPath, 50)], M_AGENT);
|
|
2030
2110
|
if (config.mcpGateScriptPath) push(h, "PreToolUse", [cmd(config.mcpGateScriptPath, 50)], M_MCP);
|
|
2031
2111
|
if (config.taskActivateIntentScriptPath) push(h, "PreToolUse", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
|
|
2112
|
+
if (config.taskActivateIntentScriptPath) push(h, "PermissionRequest", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
|
|
2032
2113
|
push(h, "PostToolUse", [cmd(config.bashFollowupScriptPath, 10)], M_BASH);
|
|
2033
2114
|
push(h, "UserPromptSubmit", [cmd(config.userPromptSubmitScriptPath, 5)]);
|
|
2034
2115
|
if (config.promptRouteScriptPath) push(h, "UserPromptSubmit", [cmd(config.promptRouteScriptPath, 5)]);
|
|
2035
2116
|
push(h, "SessionStart", [cmd(config.sessionStartScriptPath, 5)]);
|
|
2117
|
+
push(h, "SubagentStart", [cmd(config.subagentStartScriptPath, 5)]);
|
|
2118
|
+
if (!config.skipTranscriptSync) push(h, "SubagentStop", [cmd(config.subagentStopScriptPath, 5)]);
|
|
2036
2119
|
push(h, "SessionEnd", [cmd(config.stopSummaryScriptPath, 3)]);
|
|
2037
2120
|
if (!config.skipTranscriptSync) push(h, "Stop", [cmd(config.transcriptSyncScriptPath, 3)]);
|
|
2038
2121
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
@@ -2054,30 +2137,31 @@ function uninstallCodexHooks(hooksJsonPath) {
|
|
|
2054
2137
|
writeHooksFileAtomic2(hooksJsonPath, file);
|
|
2055
2138
|
return true;
|
|
2056
2139
|
}
|
|
2057
|
-
var SYNKRO_MARKER3, M_BASH, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2140
|
+
var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
|
|
2058
2141
|
var init_codexHookConfig = __esm({
|
|
2059
2142
|
"cli/installer/codexHookConfig.ts"() {
|
|
2060
2143
|
"use strict";
|
|
2061
2144
|
init_platform();
|
|
2062
2145
|
SYNKRO_MARKER3 = "__synkro_managed__";
|
|
2063
2146
|
M_BASH = "Bash";
|
|
2064
|
-
|
|
2147
|
+
CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
|
|
2148
|
+
M_EDIT = CODEX_EDIT_MATCHER;
|
|
2065
2149
|
M_AGENT = "Agent";
|
|
2066
2150
|
M_MCP = "mcp__.*";
|
|
2067
|
-
M_ACTIVATE = "mcp__synkro-guardrails__activate_standard";
|
|
2068
|
-
ALL_EVENTS2 = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop"];
|
|
2151
|
+
M_ACTIVATE = "mcp__synkro[-_]guardrails__activate_standard";
|
|
2152
|
+
ALL_EVENTS2 = ["PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "SubagentStart", "SubagentStop", "Stop"];
|
|
2069
2153
|
}
|
|
2070
2154
|
});
|
|
2071
2155
|
|
|
2072
2156
|
// cli/installer/mcpConfig.ts
|
|
2073
|
-
import { existsSync as existsSync13, readFileSync as
|
|
2074
|
-
import { homedir as
|
|
2075
|
-
import { dirname as dirname4, join as
|
|
2157
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
2158
|
+
import { homedir as homedir12 } from "os";
|
|
2159
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2076
2160
|
import { randomBytes } from "crypto";
|
|
2077
2161
|
function readClaudeJson() {
|
|
2078
2162
|
if (!existsSync13(CC_CONFIG_PATH)) return {};
|
|
2079
2163
|
try {
|
|
2080
|
-
const raw =
|
|
2164
|
+
const raw = readFileSync12(CC_CONFIG_PATH, "utf-8");
|
|
2081
2165
|
return JSON.parse(raw);
|
|
2082
2166
|
} catch (err) {
|
|
2083
2167
|
throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
|
|
@@ -2096,7 +2180,7 @@ function installMcpConfig(opts) {
|
|
|
2096
2180
|
if (entry?.[SYNKRO_MARKER4] === true) delete config.mcpServers[name];
|
|
2097
2181
|
}
|
|
2098
2182
|
if (opts.local) {
|
|
2099
|
-
const proxyScript =
|
|
2183
|
+
const proxyScript = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2100
2184
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
2101
2185
|
type: "stdio",
|
|
2102
2186
|
command: "bun",
|
|
@@ -2135,7 +2219,7 @@ function uninstallMcpConfig() {
|
|
|
2135
2219
|
function readCursorMcpJson() {
|
|
2136
2220
|
if (!existsSync13(CURSOR_MCP_PATH)) return {};
|
|
2137
2221
|
try {
|
|
2138
|
-
const raw =
|
|
2222
|
+
const raw = readFileSync12(CURSOR_MCP_PATH, "utf-8");
|
|
2139
2223
|
return JSON.parse(raw);
|
|
2140
2224
|
} catch (err) {
|
|
2141
2225
|
throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${err.message}`);
|
|
@@ -2156,10 +2240,10 @@ function installCursorMcpConfig(opts) {
|
|
|
2156
2240
|
if (opts.local) {
|
|
2157
2241
|
const port = process.env.SYNKRO_MCP_PORT || "18931";
|
|
2158
2242
|
const url2 = `http://127.0.0.1:${port}/`;
|
|
2159
|
-
const jwtPath =
|
|
2243
|
+
const jwtPath = join10(homedir12(), ".synkro", ".mcp-jwt");
|
|
2160
2244
|
let jwt2 = "";
|
|
2161
2245
|
try {
|
|
2162
|
-
jwt2 =
|
|
2246
|
+
jwt2 = readFileSync12(jwtPath, "utf-8").trim();
|
|
2163
2247
|
} catch {
|
|
2164
2248
|
}
|
|
2165
2249
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
@@ -2201,7 +2285,7 @@ function codexConfigPath(path) {
|
|
|
2201
2285
|
function readCodexToml(path) {
|
|
2202
2286
|
if (!existsSync13(path)) return "";
|
|
2203
2287
|
try {
|
|
2204
|
-
return
|
|
2288
|
+
return readFileSync12(path, "utf-8");
|
|
2205
2289
|
} catch (err) {
|
|
2206
2290
|
throw new Error(`Failed to read ${path}: ${err.message}`);
|
|
2207
2291
|
}
|
|
@@ -2290,7 +2374,7 @@ function resolveBunBin2() {
|
|
|
2290
2374
|
function readDesktopJson() {
|
|
2291
2375
|
if (!existsSync13(CLAUDE_DESKTOP_CONFIG_PATH)) return {};
|
|
2292
2376
|
try {
|
|
2293
|
-
return JSON.parse(
|
|
2377
|
+
return JSON.parse(readFileSync12(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
|
|
2294
2378
|
} catch (err) {
|
|
2295
2379
|
throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${err.message}`);
|
|
2296
2380
|
}
|
|
@@ -2347,31 +2431,31 @@ var init_mcpConfig = __esm({
|
|
|
2347
2431
|
init_platform();
|
|
2348
2432
|
SYNKRO_MARKER4 = "__synkro_managed__";
|
|
2349
2433
|
SYNKRO_SERVER_NAME = "synkro-guardrails";
|
|
2350
|
-
CC_CONFIG_PATH =
|
|
2351
|
-
CURSOR_MCP_PATH =
|
|
2352
|
-
CODEX_CONFIG_PATH =
|
|
2434
|
+
CC_CONFIG_PATH = join10(homedir12(), ".claude.json");
|
|
2435
|
+
CURSOR_MCP_PATH = join10(homedir12(), ".cursor", "mcp.json");
|
|
2436
|
+
CODEX_CONFIG_PATH = join10(process.env.CODEX_HOME || join10(homedir12(), ".codex"), "config.toml");
|
|
2353
2437
|
CODEX_MCP_BEGIN = "# >>> synkro managed MCP: synkro-guardrails";
|
|
2354
2438
|
CODEX_MCP_END = "# <<< synkro managed MCP: synkro-guardrails";
|
|
2355
2439
|
CODEX_MCP_SECTION = 'mcp_servers."synkro-guardrails"';
|
|
2356
|
-
CLAUDE_DESKTOP_CONFIG_PATH =
|
|
2357
|
-
|
|
2440
|
+
CLAUDE_DESKTOP_CONFIG_PATH = join10(
|
|
2441
|
+
homedir12(),
|
|
2358
2442
|
"Library",
|
|
2359
2443
|
"Application Support",
|
|
2360
2444
|
"Claude",
|
|
2361
2445
|
"claude_desktop_config.json"
|
|
2362
2446
|
);
|
|
2363
|
-
MCP_STDIO_PROXY_PATH =
|
|
2447
|
+
MCP_STDIO_PROXY_PATH = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
|
|
2364
2448
|
}
|
|
2365
2449
|
});
|
|
2366
2450
|
|
|
2367
2451
|
// cli/installer/synkroCommand.ts
|
|
2368
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as
|
|
2369
|
-
import { homedir as
|
|
2370
|
-
import { join as
|
|
2452
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
2453
|
+
import { homedir as homedir13 } from "os";
|
|
2454
|
+
import { join as join11 } from "path";
|
|
2371
2455
|
function installSynkroCommand() {
|
|
2372
2456
|
try {
|
|
2373
2457
|
if (existsSync14(COMMAND_PATH)) {
|
|
2374
|
-
const current =
|
|
2458
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2375
2459
|
if (!current.includes(MARKER)) return null;
|
|
2376
2460
|
}
|
|
2377
2461
|
mkdirSync8(COMMANDS_DIR, { recursive: true });
|
|
@@ -2384,7 +2468,7 @@ function installSynkroCommand() {
|
|
|
2384
2468
|
function uninstallSynkroCommand() {
|
|
2385
2469
|
try {
|
|
2386
2470
|
if (!existsSync14(COMMAND_PATH)) return false;
|
|
2387
|
-
const current =
|
|
2471
|
+
const current = readFileSync13(COMMAND_PATH, "utf-8");
|
|
2388
2472
|
if (!current.includes(MARKER)) return false;
|
|
2389
2473
|
unlinkSync4(COMMAND_PATH);
|
|
2390
2474
|
return true;
|
|
@@ -2397,8 +2481,8 @@ var init_synkroCommand = __esm({
|
|
|
2397
2481
|
"cli/installer/synkroCommand.ts"() {
|
|
2398
2482
|
"use strict";
|
|
2399
2483
|
MARKER = "__synkro_managed__";
|
|
2400
|
-
COMMANDS_DIR =
|
|
2401
|
-
COMMAND_PATH =
|
|
2484
|
+
COMMANDS_DIR = join11(homedir13(), ".claude", "commands");
|
|
2485
|
+
COMMAND_PATH = join11(COMMANDS_DIR, "synkro.md");
|
|
2402
2486
|
COMMAND_BODY = `<!-- ${MARKER} -->
|
|
2403
2487
|
---
|
|
2404
2488
|
description: Set up Synkro \u2014 create a starter rule set, review suggestions, and watch a live block
|
|
@@ -2456,14 +2540,14 @@ var init_skillParser = __esm({
|
|
|
2456
2540
|
function stubHook(surface, optsLiteral) {
|
|
2457
2541
|
return "#!/usr/bin/env bun\nimport { runStub } from './_synkro-stub-common.ts';\nrunStub(" + JSON.stringify(surface) + ", " + optsLiteral + ");\n";
|
|
2458
2542
|
}
|
|
2459
|
-
var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
|
|
2543
|
+
var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_EDIT_FOLLOWUP_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_SUBAGENT_START_TS, STUB_SUBAGENT_STOP_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
|
|
2460
2544
|
var init_hookScriptsTs = __esm({
|
|
2461
2545
|
"cli/installer/hookScriptsTs.ts"() {
|
|
2462
2546
|
"use strict";
|
|
2463
|
-
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2547
|
+
STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2464
2548
|
import { execSync } from 'node:child_process';
|
|
2465
2549
|
import { homedir } from 'node:os';
|
|
2466
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
2550
|
+
import { basename, join, resolve, relative, isAbsolute } from 'node:path';
|
|
2467
2551
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2468
2552
|
|
|
2469
2553
|
const HOME = homedir();
|
|
@@ -2622,13 +2706,17 @@ async function cloudMcpGate(payload: any, harness: string): Promise<string> {
|
|
|
2622
2706
|
try {
|
|
2623
2707
|
const toolName = String(payload.tool_name || payload.tool || '');
|
|
2624
2708
|
const parts = toolName.split('__');
|
|
2625
|
-
const
|
|
2709
|
+
const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
|
|
2710
|
+
const server = (parts.length >= 3 && parts[0] === 'mcp')
|
|
2711
|
+
? parts[1]
|
|
2712
|
+
: String(payload.server_name || payload.serverName || payload.server || payload.mcp_server
|
|
2713
|
+
|| input.server_name || input.serverName || input.server || input.mcp_server || '');
|
|
2626
2714
|
if (!server) return failOpen(harness);
|
|
2627
2715
|
const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\/+$/, '');
|
|
2628
2716
|
const resp = await fetch(base + '/api/mcp/gate', {
|
|
2629
2717
|
method: 'POST',
|
|
2630
2718
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },
|
|
2631
|
-
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),
|
|
2719
|
+
body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') || toolName, harness: harness }),
|
|
2632
2720
|
signal: AbortSignal.timeout(5000),
|
|
2633
2721
|
});
|
|
2634
2722
|
if (!resp.ok) return failOpen(harness);
|
|
@@ -2725,48 +2813,293 @@ function gatherBaseContent(fp: string, ti: any): string | undefined {
|
|
|
2725
2813
|
return '\n'.repeat(linesBefore) + full.slice(start, end);
|
|
2726
2814
|
}
|
|
2727
2815
|
|
|
2816
|
+
function gatherPreEditContentFromPost(fp: string, toolName: string, ti: any, operation: string): string | undefined {
|
|
2817
|
+
if (operation === 'add') return '';
|
|
2818
|
+
if (operation === 'delete') {
|
|
2819
|
+
const patch = String(ti?.patch || '');
|
|
2820
|
+
const removed: string[] = [];
|
|
2821
|
+
for (const line of patch.split('\n')) {
|
|
2822
|
+
if (line.startsWith('*** ') || line.startsWith('@@')) continue;
|
|
2823
|
+
if (line.startsWith('-')) removed.push(line.slice(1));
|
|
2824
|
+
}
|
|
2825
|
+
return removed.length ? removed.join('\n') : undefined;
|
|
2826
|
+
}
|
|
2827
|
+
let full = '';
|
|
2828
|
+
try { full = readFileSync(fp, 'utf-8'); } catch { return undefined; }
|
|
2829
|
+
if (toolName === 'Edit') {
|
|
2830
|
+
const oldString = String(ti?.old_string || '');
|
|
2831
|
+
const newString = String(ti?.new_string || '');
|
|
2832
|
+
if (newString && full.includes(newString)) return full.replace(newString, oldString);
|
|
2833
|
+
return undefined;
|
|
2834
|
+
}
|
|
2835
|
+
if (toolName === 'MultiEdit' && Array.isArray(ti?.edits)) {
|
|
2836
|
+
let before = full;
|
|
2837
|
+
for (const edit of [...ti.edits].reverse()) {
|
|
2838
|
+
const oldString = String(edit?.old_string || '');
|
|
2839
|
+
const newString = String(edit?.new_string || '');
|
|
2840
|
+
if (!newString || !before.includes(newString)) return undefined;
|
|
2841
|
+
before = before.replace(newString, oldString);
|
|
2842
|
+
}
|
|
2843
|
+
return before;
|
|
2844
|
+
}
|
|
2845
|
+
return full;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2728
2848
|
// Codex edits arrive as the apply_patch tool with the patch text in
|
|
2729
2849
|
// tool_input.command (lines: "*** Update/Add/Delete File: PATH", then hunks where
|
|
2730
2850
|
// "+" adds, "-" removes, " " is context). The edit hooks expect the CC shape
|
|
2731
|
-
// {file_path, old_string, new_string}; translate
|
|
2732
|
-
//
|
|
2733
|
-
// more than one file or hunk cannot be reconstructed faithfully as one Edit, so
|
|
2734
|
-
// mark it for a clear deny-and-retry response instead of grading partial content.
|
|
2851
|
+
// {file_path, old_string, new_string}; translate every file and hunk into an
|
|
2852
|
+
// explicit edit batch so one apply_patch call never drops evidence.
|
|
2735
2853
|
export function normalizeCodexApplyPatch(payload: any): void {
|
|
2736
2854
|
try {
|
|
2737
|
-
if (
|
|
2855
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(String((payload && payload.tool_name) || ''))) return;
|
|
2738
2856
|
const ti = payload.tool_input;
|
|
2739
2857
|
const patch = (ti && typeof ti === 'object')
|
|
2740
2858
|
? String(ti.command || ti.patch || ti.input || '')
|
|
2741
2859
|
: (typeof ti === 'string' ? ti : '');
|
|
2742
2860
|
if (!patch) return;
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2861
|
+
type Hunk = { oldLines: string[]; newLines: string[] };
|
|
2862
|
+
type PatchFile = {
|
|
2863
|
+
operation: 'Add' | 'Update' | 'Delete';
|
|
2864
|
+
path: string;
|
|
2865
|
+
moveTarget: string;
|
|
2866
|
+
hunks: Hunk[];
|
|
2867
|
+
fragment: string[];
|
|
2868
|
+
};
|
|
2869
|
+
const files: PatchFile[] = [];
|
|
2870
|
+
let current: PatchFile | null = null;
|
|
2871
|
+
let hunk: Hunk | null = null;
|
|
2872
|
+
const flushHunk = () => {
|
|
2873
|
+
if (current && hunk && (hunk.oldLines.length || hunk.newLines.length)) current.hunks.push(hunk);
|
|
2874
|
+
hunk = null;
|
|
2875
|
+
};
|
|
2876
|
+
const flushFile = () => {
|
|
2877
|
+
flushHunk();
|
|
2878
|
+
if (current) files.push(current);
|
|
2879
|
+
current = null;
|
|
2880
|
+
};
|
|
2747
2881
|
for (const ln of patch.split('\n')) {
|
|
2748
2882
|
const mFile = ln.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+?)\s*$/);
|
|
2749
|
-
if (mFile) {
|
|
2750
|
-
|
|
2883
|
+
if (mFile) {
|
|
2884
|
+
flushFile();
|
|
2885
|
+
const operation = ln.match(/^\*\*\*\s+(Update|Add|Delete)\s+File:/)?.[1] as 'Add' | 'Update' | 'Delete';
|
|
2886
|
+
current = {
|
|
2887
|
+
operation,
|
|
2888
|
+
path: mFile[1].trim(),
|
|
2889
|
+
moveTarget: '',
|
|
2890
|
+
hunks: [],
|
|
2891
|
+
fragment: [ln],
|
|
2892
|
+
};
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
if (!current) continue;
|
|
2896
|
+
current.fragment.push(ln);
|
|
2897
|
+
const mMove = ln.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
|
|
2898
|
+
if (mMove) { current.moveTarget = mMove[1].trim(); continue; }
|
|
2899
|
+
if (ln.indexOf('@@') === 0) {
|
|
2900
|
+
flushHunk();
|
|
2901
|
+
hunk = { oldLines: [], newLines: [] };
|
|
2902
|
+
const anchor = ln.slice(2).trim();
|
|
2903
|
+
if (anchor) { hunk.oldLines.push(anchor); hunk.newLines.push(anchor); }
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2751
2906
|
if (ln.indexOf('*** ') === 0) continue;
|
|
2907
|
+
hunk = hunk || { oldLines: [], newLines: [] };
|
|
2752
2908
|
const c = ln.charAt(0);
|
|
2753
|
-
if (c === '+') newLines.push(ln.slice(1));
|
|
2754
|
-
else if (c === '-') oldLines.push(ln.slice(1));
|
|
2755
|
-
else if (c === ' ') { oldLines.push(ln.slice(1)); newLines.push(ln.slice(1)); }
|
|
2909
|
+
if (c === '+') hunk.newLines.push(ln.slice(1));
|
|
2910
|
+
else if (c === '-') hunk.oldLines.push(ln.slice(1));
|
|
2911
|
+
else if (c === ' ') { hunk.oldLines.push(ln.slice(1)); hunk.newLines.push(ln.slice(1)); }
|
|
2756
2912
|
}
|
|
2757
|
-
|
|
2913
|
+
flushFile();
|
|
2914
|
+
if (!files.length) {
|
|
2758
2915
|
payload.__synkro_codex_patch_error =
|
|
2759
|
-
'Synkro
|
|
2916
|
+
'Synkro could not recover a file edit from this apply_patch payload.';
|
|
2760
2917
|
return;
|
|
2761
2918
|
}
|
|
2762
|
-
if (!files[0]) return;
|
|
2763
2919
|
const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2920
|
+
const normalized = files.map((file) => {
|
|
2921
|
+
const filePath = !isAbsolute(file.path) && cwd ? resolve(cwd, file.path) : file.path;
|
|
2922
|
+
const patchFragment = file.fragment.join('\n');
|
|
2923
|
+
if (file.operation === 'Add') {
|
|
2924
|
+
return {
|
|
2925
|
+
tool_name: 'Write',
|
|
2926
|
+
tool_input: {
|
|
2927
|
+
file_path: filePath,
|
|
2928
|
+
content: file.hunks.flatMap((item) => item.newLines).join('\n'),
|
|
2929
|
+
patch: patchFragment,
|
|
2930
|
+
},
|
|
2931
|
+
__synkro_codex_operation: 'add',
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
if (file.operation === 'Delete') {
|
|
2935
|
+
return {
|
|
2936
|
+
tool_name: 'Write',
|
|
2937
|
+
tool_input: { file_path: filePath, content: '', patch: patchFragment },
|
|
2938
|
+
__synkro_codex_operation: 'delete',
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
const edits = file.hunks.map((item) => ({
|
|
2942
|
+
old_string: item.oldLines.join('\n'),
|
|
2943
|
+
new_string: item.newLines.join('\n'),
|
|
2944
|
+
}));
|
|
2945
|
+
return {
|
|
2946
|
+
tool_name: edits.length > 1 ? 'MultiEdit' : 'Edit',
|
|
2947
|
+
tool_input: edits.length > 1
|
|
2948
|
+
? { file_path: filePath, edits, patch: patchFragment }
|
|
2949
|
+
: { file_path: filePath, ...(edits[0] || { old_string: '', new_string: '' }), patch: patchFragment },
|
|
2950
|
+
__synkro_codex_operation: file.moveTarget ? 'rename' : 'modify',
|
|
2951
|
+
...(file.moveTarget ? { __synkro_codex_move_target: file.moveTarget } : {}),
|
|
2952
|
+
};
|
|
2953
|
+
});
|
|
2954
|
+
payload.__synkro_codex_edits = normalized;
|
|
2955
|
+
Object.assign(payload, normalized[0]);
|
|
2767
2956
|
} catch { /* leave payload untouched on any parse error */ }
|
|
2768
2957
|
}
|
|
2769
2958
|
|
|
2959
|
+
type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
|
|
2960
|
+
|
|
2961
|
+
function completedCodexToolRecord(entry: any): any {
|
|
2962
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
2963
|
+
if (entry.type === 'response_item' && entry.payload && typeof entry.payload === 'object') {
|
|
2964
|
+
return entry.payload;
|
|
2965
|
+
}
|
|
2966
|
+
return entry.payload && typeof entry.payload === 'object' ? entry.payload : entry;
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function completedCodexToolInput(record: any): any {
|
|
2970
|
+
const raw = record?.input ?? record?.arguments ?? record?.command;
|
|
2971
|
+
if (raw && typeof raw === 'object') return raw;
|
|
2972
|
+
if (typeof raw !== 'string') return {};
|
|
2973
|
+
try {
|
|
2974
|
+
const parsed = JSON.parse(raw);
|
|
2975
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
2976
|
+
if (typeof parsed === 'string') return { command: parsed };
|
|
2977
|
+
} catch {}
|
|
2978
|
+
return { command: raw };
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
function containedCodexEditPath(repoRoot: string, filePath: string): string {
|
|
2982
|
+
if (!repoRoot || !filePath) return '';
|
|
2983
|
+
try {
|
|
2984
|
+
const lexicalRoot = resolve(repoRoot);
|
|
2985
|
+
const realRoot = realpathSync(lexicalRoot);
|
|
2986
|
+
const lexical = isAbsolute(filePath) ? resolve(filePath) : resolve(lexicalRoot, filePath);
|
|
2987
|
+
const isContained = (rel: string): boolean =>
|
|
2988
|
+
!!rel && rel !== '..' && !rel.startsWith('..' + '/') && !isAbsolute(rel);
|
|
2989
|
+
// Accept an absolute path expressed through either the lexical repo alias
|
|
2990
|
+
// (/var on macOS, a symlinked checkout) or its physical path, but always
|
|
2991
|
+
// continue from the physical root so new and existing files share one
|
|
2992
|
+
// canonical provenance path.
|
|
2993
|
+
let rel = relative(lexicalRoot, lexical);
|
|
2994
|
+
if (!isContained(rel)) {
|
|
2995
|
+
rel = relative(realRoot, lexical);
|
|
2996
|
+
if (!isContained(rel)) return '';
|
|
2997
|
+
}
|
|
2998
|
+
let candidate = resolve(realRoot, rel);
|
|
2999
|
+
// realpathSync requires the leaf to exist. Walk to the nearest existing
|
|
3000
|
+
// ancestor, resolve symlinks there, then append the missing suffix. This
|
|
3001
|
+
// both canonicalizes new files and rejects a new leaf beneath an in-repo
|
|
3002
|
+
// symlink that actually escapes the repository.
|
|
3003
|
+
const missing: string[] = [];
|
|
3004
|
+
let ancestor = candidate;
|
|
3005
|
+
while (!existsSync(ancestor)) {
|
|
3006
|
+
const parent = resolve(ancestor, '..');
|
|
3007
|
+
if (parent === ancestor) return '';
|
|
3008
|
+
missing.unshift(basename(ancestor));
|
|
3009
|
+
ancestor = parent;
|
|
3010
|
+
}
|
|
3011
|
+
candidate = resolve(realpathSync(ancestor), ...missing);
|
|
3012
|
+
const realRel = relative(realRoot, candidate);
|
|
3013
|
+
if (!realRel || realRel.startsWith('..') || isAbsolute(realRel)) return '';
|
|
3014
|
+
return existsSync(candidate) ? realpathSync(candidate) : candidate;
|
|
3015
|
+
} catch {
|
|
3016
|
+
return '';
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* Recover completed Codex apply_patch calls from a bounded transcript tail.
|
|
3022
|
+
* A tool call is evidence only after a matching tool output exists. Calls are
|
|
3023
|
+
* expanded one event per file, restricted to the repo, and keyed by the stable
|
|
3024
|
+
* Codex call id so replay is idempotent server-side.
|
|
3025
|
+
*/
|
|
3026
|
+
export function extractCompletedCodexPatchEdits(
|
|
3027
|
+
transcript: string,
|
|
3028
|
+
repoRoot: string,
|
|
3029
|
+
): RecoveredCodexEdit[] {
|
|
3030
|
+
if (!transcript || !repoRoot) return [];
|
|
3031
|
+
const calls = new Map<string, { record: any; timestamp: string }>();
|
|
3032
|
+
const completed = new Set<string>();
|
|
3033
|
+
for (const line of transcript.split('\n')) {
|
|
3034
|
+
if (!line.trim()) continue;
|
|
3035
|
+
let entry: any;
|
|
3036
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
3037
|
+
const record = completedCodexToolRecord(entry);
|
|
3038
|
+
const type = String(record?.type || '');
|
|
3039
|
+
const callId = String(record?.call_id || record?.tool_call_id || record?.id || '');
|
|
3040
|
+
if (!callId) continue;
|
|
3041
|
+
if (type === 'custom_tool_call_output' || type === 'function_call_output') {
|
|
3042
|
+
completed.add(callId);
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
if (type !== 'custom_tool_call' && type !== 'function_call') continue;
|
|
3046
|
+
const name = String(record?.name || record?.tool_name || '');
|
|
3047
|
+
if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(name)) continue;
|
|
3048
|
+
calls.set(callId, {
|
|
3049
|
+
record,
|
|
3050
|
+
timestamp: String(entry?.timestamp || record?.timestamp || ''),
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
const results: RecoveredCodexEdit[] = [];
|
|
3055
|
+
const seen = new Set<string>();
|
|
3056
|
+
const candidates = [...calls.entries()].filter(([callId]) => completed.has(callId)).slice(-100);
|
|
3057
|
+
for (const [callId, call] of candidates) {
|
|
3058
|
+
const toolInput = completedCodexToolInput(call.record);
|
|
3059
|
+
const patch = String(toolInput?.command || toolInput?.patch || toolInput?.input || '');
|
|
3060
|
+
if (!patch || patch.length > 100000) continue;
|
|
3061
|
+
const raw: any = {
|
|
3062
|
+
cwd: repoRoot,
|
|
3063
|
+
tool_name: String(call.record?.name || call.record?.tool_name || 'apply_patch'),
|
|
3064
|
+
tool_input: toolInput,
|
|
3065
|
+
tool_call_id: callId,
|
|
3066
|
+
_ts: call.timestamp || undefined,
|
|
3067
|
+
};
|
|
3068
|
+
normalizeCodexApplyPatch(raw);
|
|
3069
|
+
const edits = Array.isArray(raw.__synkro_codex_edits) ? raw.__synkro_codex_edits : [];
|
|
3070
|
+
for (const edit of edits) {
|
|
3071
|
+
if (results.length >= 50) return results;
|
|
3072
|
+
const fp = containedCodexEditPath(repoRoot, filePathFromToolInput(edit?.tool_input));
|
|
3073
|
+
if (!fp) continue;
|
|
3074
|
+
const dedupeKey = callId + '\u0000' + fp;
|
|
3075
|
+
if (seen.has(dedupeKey)) continue;
|
|
3076
|
+
seen.add(dedupeKey);
|
|
3077
|
+
const payload: any = {
|
|
3078
|
+
cwd: repoRoot,
|
|
3079
|
+
tool_name: edit.tool_name,
|
|
3080
|
+
tool_input: { ...(edit.tool_input || {}), file_path: fp },
|
|
3081
|
+
tool_call_id: callId,
|
|
3082
|
+
_ts: call.timestamp || undefined,
|
|
3083
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3084
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3085
|
+
__synkro_edit_source: 'transcript',
|
|
3086
|
+
};
|
|
3087
|
+
const baseContent = gatherPreEditContentFromPost(
|
|
3088
|
+
fp,
|
|
3089
|
+
String(payload.tool_name || ''),
|
|
3090
|
+
payload.tool_input,
|
|
3091
|
+
String(payload.__synkro_codex_operation || ''),
|
|
3092
|
+
);
|
|
3093
|
+
if (baseContent === undefined) continue;
|
|
3094
|
+
results.push({
|
|
3095
|
+
payload,
|
|
3096
|
+
baseContent,
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
return results;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2770
3103
|
interface StubOpts {
|
|
2771
3104
|
needsFile?: boolean;
|
|
2772
3105
|
needsTranscript?: boolean;
|
|
@@ -2776,6 +3109,115 @@ interface StubOpts {
|
|
|
2776
3109
|
needsPlan?: boolean;
|
|
2777
3110
|
telemetry?: boolean;
|
|
2778
3111
|
harness?: string;
|
|
3112
|
+
postEdit?: boolean;
|
|
3113
|
+
/** Codex SubagentStart/SubagentStop report the parent as session_id and
|
|
3114
|
+
* the child as agent_id. Remap them to the ordinary child-session envelope. */
|
|
3115
|
+
subagent?: boolean;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
function isContainedFile(root: string, candidate: string): boolean {
|
|
3119
|
+
try {
|
|
3120
|
+
const rel = relative(root, candidate);
|
|
3121
|
+
return !!rel && !rel.startsWith('..') && !isAbsolute(rel) && statSync(candidate).isFile();
|
|
3122
|
+
} catch { return false; }
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
function safeCodexSessionId(value: string): boolean {
|
|
3126
|
+
if (!value || value.length > 200) return false;
|
|
3127
|
+
for (const ch of value) {
|
|
3128
|
+
const code = ch.charCodeAt(0);
|
|
3129
|
+
const alphaNumeric = (code >= 48 && code <= 57)
|
|
3130
|
+
|| (code >= 65 && code <= 90)
|
|
3131
|
+
|| (code >= 97 && code <= 122);
|
|
3132
|
+
if (!alphaNumeric && ch !== '-' && ch !== '_') return false;
|
|
3133
|
+
}
|
|
3134
|
+
return true;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
function readJsonlHeader(path: string): string {
|
|
3138
|
+
let fd = -1;
|
|
3139
|
+
try {
|
|
3140
|
+
fd = openSync(path, 'r');
|
|
3141
|
+
const buf = Buffer.alloc(16384);
|
|
3142
|
+
const count = readSync(fd, buf, 0, buf.length, 0);
|
|
3143
|
+
const head = buf.subarray(0, count).toString('utf-8');
|
|
3144
|
+
const newline = head.indexOf('\n');
|
|
3145
|
+
return newline >= 0 ? head.slice(0, newline) : head;
|
|
3146
|
+
} catch { return ''; }
|
|
3147
|
+
finally { if (fd >= 0) { try { closeSync(fd); } catch {} } }
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
/**
|
|
3151
|
+
* Codex hook payloads identify the rollout by session_id but do not always send
|
|
3152
|
+
* transcript_path. Resolve that ID only within CODEX_HOME/sessions (or the
|
|
3153
|
+
* default ~/.codex/sessions), then cache the exact contained file so subsequent
|
|
3154
|
+
* tool hooks do not recursively scan the rollout tree.
|
|
3155
|
+
*/
|
|
3156
|
+
export function resolveHookTranscriptPath(payload: any, harness: string, sessionId: string): string {
|
|
3157
|
+
const explicit = [
|
|
3158
|
+
payload?.transcript_path,
|
|
3159
|
+
payload?.rollout_path,
|
|
3160
|
+
payload?.session_path,
|
|
3161
|
+
].find((value) => typeof value === 'string' && value);
|
|
3162
|
+
if (typeof explicit === 'string') return explicit;
|
|
3163
|
+
if (harness !== 'codex' || !safeCodexSessionId(sessionId)) return '';
|
|
3164
|
+
|
|
3165
|
+
const homeRoots = [
|
|
3166
|
+
process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : '',
|
|
3167
|
+
resolve(join(HOME, '.codex')),
|
|
3168
|
+
].filter((value, index, all) => !!value && all.indexOf(value) === index);
|
|
3169
|
+
const sessionRoots = homeRoots.map((root) => join(root, 'sessions')).filter((root) => existsSync(root));
|
|
3170
|
+
if (!sessionRoots.length) return '';
|
|
3171
|
+
|
|
3172
|
+
const cachePath = join(
|
|
3173
|
+
HOME,
|
|
3174
|
+
'.synkro',
|
|
3175
|
+
'codex-transcript-' + createHash('sha256').update(sessionId).digest('hex').slice(0, 16),
|
|
3176
|
+
);
|
|
3177
|
+
try {
|
|
3178
|
+
const cached = resolve(readFileSync(cachePath, 'utf-8').trim());
|
|
3179
|
+
if (sessionRoots.some((root) => isContainedFile(root, cached))) return cached;
|
|
3180
|
+
} catch {}
|
|
3181
|
+
|
|
3182
|
+
const candidates: string[] = [];
|
|
3183
|
+
for (const root of sessionRoots) {
|
|
3184
|
+
let entries: string[] = [];
|
|
3185
|
+
try { entries = readdirSync(root, { recursive: true, encoding: 'utf-8' }) as string[]; } catch {}
|
|
3186
|
+
for (const entry of entries) {
|
|
3187
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
3188
|
+
const candidate = resolve(join(root, entry));
|
|
3189
|
+
if (isContainedFile(root, candidate)) candidates.push(candidate);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
let found = candidates.find((candidate) => candidate.includes(sessionId)) || '';
|
|
3194
|
+
if (!found) {
|
|
3195
|
+
const recent = candidates
|
|
3196
|
+
.map((candidate) => {
|
|
3197
|
+
try { return { candidate, mtime: statSync(candidate).mtimeMs }; }
|
|
3198
|
+
catch { return { candidate, mtime: 0 }; }
|
|
3199
|
+
})
|
|
3200
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
3201
|
+
.slice(0, 100);
|
|
3202
|
+
for (const item of recent) {
|
|
3203
|
+
try {
|
|
3204
|
+
const first = JSON.parse(readJsonlHeader(item.candidate));
|
|
3205
|
+
const candidateId = String(first?.payload?.session_id || first?.payload?.id || '');
|
|
3206
|
+
if (first?.type === 'session_meta' && candidateId === sessionId) {
|
|
3207
|
+
found = item.candidate;
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3210
|
+
} catch {}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
if (found) {
|
|
3215
|
+
try {
|
|
3216
|
+
mkdirSync(join(HOME, '.synkro'), { recursive: true });
|
|
3217
|
+
writeFileSync(cachePath, found, { encoding: 'utf-8', mode: 0o600 });
|
|
3218
|
+
} catch {}
|
|
3219
|
+
}
|
|
3220
|
+
return found;
|
|
2779
3221
|
}
|
|
2780
3222
|
|
|
2781
3223
|
export async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {
|
|
@@ -2792,6 +3234,17 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2792
3234
|
// Codex: rewrite apply_patch tool_input into the CC edit shape before any
|
|
2793
3235
|
// surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
|
|
2794
3236
|
if (harness === 'codex') normalizeCodexApplyPatch(payload);
|
|
3237
|
+
if (harness === 'codex' && opts.subagent) {
|
|
3238
|
+
const parentSessionId = String(payload?.session_id || '');
|
|
3239
|
+
const childSessionId = String(payload?.agent_id || '');
|
|
3240
|
+
if (parentSessionId && childSessionId) {
|
|
3241
|
+
payload.parent_session_id = parentSessionId;
|
|
3242
|
+
payload.session_id = childSessionId;
|
|
3243
|
+
if (!payload.transcript_path && payload.agent_transcript_path) {
|
|
3244
|
+
payload.transcript_path = payload.agent_transcript_path;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
2795
3248
|
telemPayload = payload;
|
|
2796
3249
|
telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';
|
|
2797
3250
|
telemSessionId = String(payload?.session_id || payload?.conversation_id || '');
|
|
@@ -2862,14 +3315,40 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2862
3315
|
const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };
|
|
2863
3316
|
|
|
2864
3317
|
if (opts.needsFile) {
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3318
|
+
const normalizedEdits = harness === 'codex' && Array.isArray(payload.__synkro_codex_edits)
|
|
3319
|
+
? payload.__synkro_codex_edits
|
|
3320
|
+
: [{ tool_name: payload.tool_name, tool_input: payload.tool_input }];
|
|
3321
|
+
const editBatch = normalizedEdits.map((edit: any) => {
|
|
3322
|
+
const childPayload = {
|
|
3323
|
+
...payload,
|
|
3324
|
+
tool_name: edit.tool_name,
|
|
3325
|
+
tool_input: edit.tool_input,
|
|
3326
|
+
__synkro_codex_operation: edit.__synkro_codex_operation,
|
|
3327
|
+
__synkro_codex_move_target: edit.__synkro_codex_move_target,
|
|
3328
|
+
};
|
|
3329
|
+
delete childPayload.__synkro_codex_edits;
|
|
3330
|
+
const fp = filePathFromToolInput(childPayload.tool_input || {});
|
|
3331
|
+
const canRead = fp && (existsSync(fp) || (opts.postEdit && childPayload.__synkro_codex_operation === 'add'));
|
|
3332
|
+
const bc = canRead
|
|
3333
|
+
? (opts.postEdit
|
|
3334
|
+
? gatherPreEditContentFromPost(
|
|
3335
|
+
fp,
|
|
3336
|
+
String(childPayload.tool_name || ''),
|
|
3337
|
+
childPayload.tool_input || {},
|
|
3338
|
+
String(childPayload.__synkro_codex_operation || ''),
|
|
3339
|
+
)
|
|
3340
|
+
: gatherBaseContent(fp, childPayload.tool_input || {}))
|
|
3341
|
+
: undefined;
|
|
3342
|
+
return { payload: childPayload, ...(bc !== undefined ? { baseContent: bc } : {}) };
|
|
3343
|
+
});
|
|
3344
|
+
if (editBatch.length) {
|
|
3345
|
+
envelope.editBatch = editBatch;
|
|
3346
|
+
envelope.payload = editBatch[0].payload;
|
|
3347
|
+
if (editBatch[0].baseContent !== undefined) envelope.baseContent = editBatch[0].baseContent;
|
|
2869
3348
|
}
|
|
2870
3349
|
}
|
|
2871
3350
|
if (opts.needsTranscript) {
|
|
2872
|
-
const tp =
|
|
3351
|
+
const tp = resolveHookTranscriptPath(payload, harness, sessionId);
|
|
2873
3352
|
if (tp && existsSync(tp)) {
|
|
2874
3353
|
try {
|
|
2875
3354
|
const t = readFileSync(tp, 'utf-8');
|
|
@@ -2881,6 +3360,10 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2881
3360
|
// by ts, so we no longer depend on the whole file for absolute message indices.
|
|
2882
3361
|
const cap = opts.fullTranscript ? 400000 : 200000;
|
|
2883
3362
|
envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);
|
|
3363
|
+
if (harness === 'codex') {
|
|
3364
|
+
const recovered = extractCompletedCodexPatchEdits(envelope.transcript, root || cwd);
|
|
3365
|
+
if (recovered.length) envelope.transcriptEdits = recovered;
|
|
3366
|
+
}
|
|
2884
3367
|
} catch {}
|
|
2885
3368
|
}
|
|
2886
3369
|
}
|
|
@@ -2896,7 +3379,8 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
|
|
|
2896
3379
|
// prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from
|
|
2897
3380
|
// being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)
|
|
2898
3381
|
// and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.
|
|
2899
|
-
const timeoutMs = surface === '
|
|
3382
|
+
const timeoutMs = surface === 'cwe-precheck' ? 48000
|
|
3383
|
+
: surface === 'bash-followup' ? 32000
|
|
2900
3384
|
: surface === 'prompt-submit' ? 3500
|
|
2901
3385
|
: (opts.telemetry ? 6000 : 28000);
|
|
2902
3386
|
// Cloud has no local container to reach. Post the SAME envelope to the org's grader
|
|
@@ -3061,8 +3545,16 @@ function toolInputSummary(toolName: string, toolInput: any, includeCommand: bool
|
|
|
3061
3545
|
// cc_model is derived by the caller (bounded tail read) and passed in; this stays
|
|
3062
3546
|
// allocation-only so the dormant hot path never touches the transcript.
|
|
3063
3547
|
function telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {
|
|
3548
|
+
const normalizedHarness = String(harness || '').trim().toLowerCase().replace(/[\\s-]+/g, '_');
|
|
3549
|
+
const agentKind = normalizedHarness === 'cursor'
|
|
3550
|
+
? 'cursor'
|
|
3551
|
+
: normalizedHarness === 'codex'
|
|
3552
|
+
? 'codex'
|
|
3553
|
+
: normalizedHarness === 'cc' || normalizedHarness === 'claude_code'
|
|
3554
|
+
? 'claude_code'
|
|
3555
|
+
: undefined;
|
|
3064
3556
|
return {
|
|
3065
|
-
agent_kind:
|
|
3557
|
+
agent_kind: agentKind,
|
|
3066
3558
|
cc_session_id: sessionId || undefined,
|
|
3067
3559
|
cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,
|
|
3068
3560
|
cc_model: ccModel || undefined,
|
|
@@ -3164,6 +3656,7 @@ function emitStubTelemetry(
|
|
|
3164
3656
|
}
|
|
3165
3657
|
`;
|
|
3166
3658
|
STUB_EDIT_PRECHECK_TS = stubHook("edit-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3659
|
+
STUB_EDIT_FOLLOWUP_TS = stubHook("edit-followup", "{ needsFile: true, needsTranscript: true, postEdit: true }");
|
|
3167
3660
|
STUB_CWE_PRECHECK_TS = stubHook("cwe-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3168
3661
|
STUB_CVE_PRECHECK_TS = stubHook("cve-precheck", "{ needsFile: true, needsTranscript: true }");
|
|
3169
3662
|
STUB_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true }");
|
|
@@ -3175,6 +3668,8 @@ function emitStubTelemetry(
|
|
|
3175
3668
|
STUB_STOP_SUMMARY_TS = stubHook("stop-summary", "{ needsTranscript: true, fullTranscript: true }");
|
|
3176
3669
|
STUB_SESSION_START_TS = stubHook("session-start", "{ telemetry: true }");
|
|
3177
3670
|
STUB_TRANSCRIPT_SYNC_TS = stubHook("transcript-sync", "{ needsTranscript: true, fullTranscript: true, telemetry: true }");
|
|
3671
|
+
STUB_SUBAGENT_START_TS = stubHook("subagent-start", "{ telemetry: true, subagent: true }");
|
|
3672
|
+
STUB_SUBAGENT_STOP_TS = stubHook("subagent-stop", "{ needsTranscript: true, fullTranscript: true, telemetry: true, subagent: true }");
|
|
3178
3673
|
STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
|
|
3179
3674
|
STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
|
|
3180
3675
|
STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
|
|
@@ -3307,7 +3802,7 @@ try {
|
|
|
3307
3802
|
const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
|
|
3308
3803
|
method: 'POST',
|
|
3309
3804
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
3310
|
-
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x',
|
|
3805
|
+
body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
|
|
3311
3806
|
signal: AbortSignal.timeout(1000),
|
|
3312
3807
|
});
|
|
3313
3808
|
if (resp.ok) {
|
|
@@ -3346,8 +3841,14 @@ const PORT = process.env.SYNKRO_MCP_PORT || '18931';
|
|
|
3346
3841
|
function mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }
|
|
3347
3842
|
const chunks: Buffer[] = [];
|
|
3348
3843
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3844
|
+
let originalToolInput: Record<string, unknown> = {};
|
|
3845
|
+
let hookEventName = 'PreToolUse';
|
|
3349
3846
|
try {
|
|
3350
3847
|
const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
|
|
3848
|
+
hookEventName = String(payload.hook_event_name || payload.hookEventName || 'PreToolUse');
|
|
3849
|
+
if (payload.tool_input && typeof payload.tool_input === 'object' && !Array.isArray(payload.tool_input)) {
|
|
3850
|
+
originalToolInput = payload.tool_input;
|
|
3851
|
+
}
|
|
3351
3852
|
await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {
|
|
3352
3853
|
method: 'POST',
|
|
3353
3854
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
|
|
@@ -3355,7 +3856,29 @@ try {
|
|
|
3355
3856
|
signal: AbortSignal.timeout(5000),
|
|
3356
3857
|
});
|
|
3357
3858
|
} catch {}
|
|
3358
|
-
|
|
3859
|
+
// PreToolUse enforcement and Codex's native approval are separate lifecycle
|
|
3860
|
+
// stages. The policy gate already allowed this exact activate_standard call;
|
|
3861
|
+
// approve its subsequent PermissionRequest so headless approval_policy=never
|
|
3862
|
+
// can execute it. This script is registered only for the activation matcher,
|
|
3863
|
+
// never as a blanket MCP approval hook.
|
|
3864
|
+
if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionRequest') {
|
|
3865
|
+
process.stdout.write(JSON.stringify({
|
|
3866
|
+
hookSpecificOutput: {
|
|
3867
|
+
hookEventName: 'PermissionRequest',
|
|
3868
|
+
decision: { behavior: 'allow' },
|
|
3869
|
+
},
|
|
3870
|
+
}) + '\\n');
|
|
3871
|
+
} else if (process.env.SYNKRO_HOOK_FORMAT === 'codex') {
|
|
3872
|
+
process.stdout.write(JSON.stringify({
|
|
3873
|
+
hookSpecificOutput: {
|
|
3874
|
+
hookEventName: 'PreToolUse',
|
|
3875
|
+
permissionDecision: 'allow',
|
|
3876
|
+
updatedInput: originalToolInput,
|
|
3877
|
+
},
|
|
3878
|
+
}) + '\\n');
|
|
3879
|
+
} else {
|
|
3880
|
+
process.stdout.write('{}\\n');
|
|
3881
|
+
}
|
|
3359
3882
|
`;
|
|
3360
3883
|
STUB_CURSOR_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true, harness: 'cursor' }");
|
|
3361
3884
|
STUB_CURSOR_SKILL_JUDGE_TS = stubHook("skill-judge", "{ needsFile: true, needsTranscript: true, harness: 'cursor' }");
|
|
@@ -3382,9 +3905,9 @@ __export(stub_exports, {
|
|
|
3382
3905
|
saveCredentials: () => saveCredentials
|
|
3383
3906
|
});
|
|
3384
3907
|
import { createServer } from "http";
|
|
3385
|
-
import { writeFileSync as writeFileSync11, readFileSync as
|
|
3386
|
-
import { homedir as
|
|
3387
|
-
import { join as
|
|
3908
|
+
import { writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
3909
|
+
import { homedir as homedir14, platform as platform2 } from "os";
|
|
3910
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
3388
3911
|
import { execFile } from "child_process";
|
|
3389
3912
|
import jwt from "jsonwebtoken";
|
|
3390
3913
|
function openBrowser(url) {
|
|
@@ -3423,7 +3946,7 @@ function loadCredentials() {
|
|
|
3423
3946
|
return null;
|
|
3424
3947
|
}
|
|
3425
3948
|
try {
|
|
3426
|
-
const content =
|
|
3949
|
+
const content = readFileSync14(AUTH_FILE, "utf8");
|
|
3427
3950
|
return JSON.parse(content);
|
|
3428
3951
|
} catch (error) {
|
|
3429
3952
|
return null;
|
|
@@ -3711,7 +4234,7 @@ var init_stub = __esm({
|
|
|
3711
4234
|
PORT = 8100;
|
|
3712
4235
|
RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
|
|
3713
4236
|
SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
|
|
3714
|
-
AUTH_FILE = process.env.SYNKRO_AUTH_FILE ||
|
|
4237
|
+
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join12(homedir14(), ".synkro", "credentials.json");
|
|
3715
4238
|
RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
3716
4239
|
SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
|
|
3717
4240
|
ERROR_HTML = `
|
|
@@ -3948,9 +4471,9 @@ __export(claudeDesktopTap_exports, {
|
|
|
3948
4471
|
runClaudeDesktopTap: () => runClaudeDesktopTap
|
|
3949
4472
|
});
|
|
3950
4473
|
import { spawn as spawn2 } from "child_process";
|
|
3951
|
-
import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as
|
|
3952
|
-
import { join as
|
|
3953
|
-
import { homedir as
|
|
4474
|
+
import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
|
|
4475
|
+
import { join as join13 } from "path";
|
|
4476
|
+
import { homedir as homedir15 } from "os";
|
|
3954
4477
|
function claudeDesktopInstalled() {
|
|
3955
4478
|
return process.platform === "darwin" && existsSync17("/Applications/Claude.app/Contents/MacOS/Claude");
|
|
3956
4479
|
}
|
|
@@ -4066,7 +4589,7 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4066
4589
|
}
|
|
4067
4590
|
let token = "";
|
|
4068
4591
|
try {
|
|
4069
|
-
token =
|
|
4592
|
+
token = readFileSync15(JWT_PATH, "utf-8").trim();
|
|
4070
4593
|
} catch {
|
|
4071
4594
|
}
|
|
4072
4595
|
if (!token) {
|
|
@@ -4087,13 +4610,13 @@ async function runClaudeDesktopTap(opts = {}) {
|
|
|
4087
4610
|
} catch (err) {
|
|
4088
4611
|
console.log(` \u26A0 Could not register Claude Desktop MCP: ${err.message}`);
|
|
4089
4612
|
}
|
|
4090
|
-
const cdRoot =
|
|
4613
|
+
const cdRoot = join13(homedir15(), ".synkro", "cd-sessions");
|
|
4091
4614
|
mkdirSync10(cdRoot, { recursive: true });
|
|
4092
|
-
const sessionDir = mkdtempSync(
|
|
4093
|
-
writeFileSync12(
|
|
4094
|
-
writeFileSync12(
|
|
4095
|
-
writeFileSync12(
|
|
4096
|
-
const runnerPath =
|
|
4615
|
+
const sessionDir = mkdtempSync(join13(cdRoot, "synkro-cd-"));
|
|
4616
|
+
writeFileSync12(join13(sessionDir, "tap.py"), ADDON_PY, "utf-8");
|
|
4617
|
+
writeFileSync12(join13(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
|
|
4618
|
+
writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
|
|
4619
|
+
const runnerPath = join13(sessionDir, "run.sh");
|
|
4097
4620
|
writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
|
|
4098
4621
|
await new Promise((resolve6) => {
|
|
4099
4622
|
const child = spawn2("bash", [runnerPath], {
|
|
@@ -4127,7 +4650,7 @@ var init_claudeDesktopTap = __esm({
|
|
|
4127
4650
|
TURN_VERDICTS_URL = "http://127.0.0.1:18931/api/local/turn-verdicts";
|
|
4128
4651
|
TURN_VERDICT_URL = "http://127.0.0.1:18931/api/local/turn-verdict";
|
|
4129
4652
|
MCP_EVENT_URL = "http://127.0.0.1:18931/api/local/mcp/event";
|
|
4130
|
-
JWT_PATH =
|
|
4653
|
+
JWT_PATH = join13(homedir15(), ".synkro", ".mcp-jwt");
|
|
4131
4654
|
ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib
|
|
4132
4655
|
from mitmproxy import http
|
|
4133
4656
|
|
|
@@ -4264,6 +4787,7 @@ def _emit_mcp_event(server, tool, ev_type, eid, convo_id=""):
|
|
|
4264
4787
|
"tool_name": (str(tool)[:120] if tool else None),
|
|
4265
4788
|
"decision": ("allowed" if ev_type == "tool_call" else None),
|
|
4266
4789
|
"repo": "claude-desktop",
|
|
4790
|
+
"harness": "claude_desktop",
|
|
4267
4791
|
}).encode("utf-8")
|
|
4268
4792
|
_post_json(MCP_EVENT_URL, payload, 5)
|
|
4269
4793
|
except Exception:
|
|
@@ -5243,9 +5767,9 @@ __export(macKeychain_exports, {
|
|
|
5243
5767
|
writeCursorApiKey: () => writeCursorApiKey,
|
|
5244
5768
|
writeRefreshAgent: () => writeRefreshAgent
|
|
5245
5769
|
});
|
|
5246
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as
|
|
5247
|
-
import { homedir as
|
|
5248
|
-
import { join as
|
|
5770
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync16 } from "fs";
|
|
5771
|
+
import { homedir as homedir16, platform as platform3 } from "os";
|
|
5772
|
+
import { join as join14 } from "path";
|
|
5249
5773
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5250
5774
|
function needsKeychainBridge() {
|
|
5251
5775
|
return platform3() === "darwin";
|
|
@@ -5268,7 +5792,7 @@ function exportKeychainCreds() {
|
|
|
5268
5792
|
let changed = true;
|
|
5269
5793
|
try {
|
|
5270
5794
|
if (existsSync18(CLAUDE_CREDS_FILE)) {
|
|
5271
|
-
changed =
|
|
5795
|
+
changed = readFileSync16(CLAUDE_CREDS_FILE, "utf-8") !== blob;
|
|
5272
5796
|
}
|
|
5273
5797
|
} catch {
|
|
5274
5798
|
}
|
|
@@ -5278,7 +5802,7 @@ function exportKeychainCreds() {
|
|
|
5278
5802
|
}
|
|
5279
5803
|
function cursorApiKeyConfigured() {
|
|
5280
5804
|
try {
|
|
5281
|
-
return existsSync18(CURSOR_API_KEY_FILE) &&
|
|
5805
|
+
return existsSync18(CURSOR_API_KEY_FILE) && readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
|
|
5282
5806
|
} catch {
|
|
5283
5807
|
return false;
|
|
5284
5808
|
}
|
|
@@ -5294,7 +5818,7 @@ function writeCursorApiKey(key) {
|
|
|
5294
5818
|
async function validateCursorApiKey() {
|
|
5295
5819
|
let key;
|
|
5296
5820
|
try {
|
|
5297
|
-
key =
|
|
5821
|
+
key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
|
|
5298
5822
|
} catch {
|
|
5299
5823
|
return null;
|
|
5300
5824
|
}
|
|
@@ -5315,7 +5839,7 @@ async function validateCursorApiKey() {
|
|
|
5315
5839
|
function credsAreStale() {
|
|
5316
5840
|
if (!existsSync18(CLAUDE_CREDS_FILE)) return true;
|
|
5317
5841
|
try {
|
|
5318
|
-
const raw =
|
|
5842
|
+
const raw = readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5319
5843
|
const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0;
|
|
5320
5844
|
if (!exp) return true;
|
|
5321
5845
|
return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1e3;
|
|
@@ -5330,7 +5854,7 @@ function writeRefreshAgent(synkroBinPath) {
|
|
|
5330
5854
|
if (platform3() !== "darwin") {
|
|
5331
5855
|
throw new KeychainExportError("writeRefreshAgent is darwin-only");
|
|
5332
5856
|
}
|
|
5333
|
-
mkdirSync11(
|
|
5857
|
+
mkdirSync11(join14(homedir16(), "Library", "LaunchAgents"), { recursive: true });
|
|
5334
5858
|
mkdirSync11(SYNKRO_DIR6, { recursive: true });
|
|
5335
5859
|
const script = `#!/bin/bash
|
|
5336
5860
|
# Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.
|
|
@@ -5427,7 +5951,7 @@ function refreshCreds() {
|
|
|
5427
5951
|
}
|
|
5428
5952
|
function readExportedCreds() {
|
|
5429
5953
|
try {
|
|
5430
|
-
return
|
|
5954
|
+
return readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
|
|
5431
5955
|
} catch {
|
|
5432
5956
|
return null;
|
|
5433
5957
|
}
|
|
@@ -5436,16 +5960,16 @@ var SYNKRO_DIR6, CLAUDE_CREDS_DIR, CLAUDE_CREDS_FILE, CURSOR_CREDS_DIR, CURSOR_A
|
|
|
5436
5960
|
var init_macKeychain = __esm({
|
|
5437
5961
|
"cli/local-cc/macKeychain.ts"() {
|
|
5438
5962
|
"use strict";
|
|
5439
|
-
SYNKRO_DIR6 =
|
|
5440
|
-
CLAUDE_CREDS_DIR =
|
|
5441
|
-
CLAUDE_CREDS_FILE =
|
|
5442
|
-
CURSOR_CREDS_DIR =
|
|
5443
|
-
CURSOR_API_KEY_FILE =
|
|
5963
|
+
SYNKRO_DIR6 = join14(homedir16(), ".synkro");
|
|
5964
|
+
CLAUDE_CREDS_DIR = join14(SYNKRO_DIR6, "claude-creds");
|
|
5965
|
+
CLAUDE_CREDS_FILE = join14(CLAUDE_CREDS_DIR, ".credentials.json");
|
|
5966
|
+
CURSOR_CREDS_DIR = join14(SYNKRO_DIR6, "cursor-creds");
|
|
5967
|
+
CURSOR_API_KEY_FILE = join14(CURSOR_CREDS_DIR, "api-key");
|
|
5444
5968
|
KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
5445
5969
|
LAUNCHD_LABEL = "com.synkro.cli.claude-creds-refresh";
|
|
5446
|
-
LAUNCHD_PLIST =
|
|
5447
|
-
REFRESH_SCRIPT =
|
|
5448
|
-
REFRESH_LOG =
|
|
5970
|
+
LAUNCHD_PLIST = join14(homedir16(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
5971
|
+
REFRESH_SCRIPT = join14(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
|
|
5972
|
+
REFRESH_LOG = join14(SYNKRO_DIR6, "claude-creds-refresh.log");
|
|
5449
5973
|
REFRESH_EXPIRY_BUFFER_SECONDS = 120;
|
|
5450
5974
|
MIN_REFRESH_INTERVAL_SECONDS = 30;
|
|
5451
5975
|
MAX_REFRESH_INTERVAL_SECONDS = 5 * 60;
|
|
@@ -5464,8 +5988,11 @@ var init_macKeychain = __esm({
|
|
|
5464
5988
|
var dockerInstall_exports = {};
|
|
5465
5989
|
__export(dockerInstall_exports, {
|
|
5466
5990
|
DockerInstallError: () => DockerInstallError,
|
|
5991
|
+
HOST_PGLITE_PORT: () => HOST_PGLITE_PORT,
|
|
5992
|
+
PGLITE_PASSWORD_PATH: () => PGLITE_PASSWORD_PATH,
|
|
5467
5993
|
SYNKRO_DIR: () => SYNKRO_DIR7,
|
|
5468
5994
|
assertDockerAvailable: () => assertDockerAvailable,
|
|
5995
|
+
createPgliteScramVerifier: () => createPgliteScramVerifier,
|
|
5469
5996
|
dockerInstall: () => dockerInstall,
|
|
5470
5997
|
dockerRemove: () => dockerRemove,
|
|
5471
5998
|
dockerSafeRestart: () => dockerSafeRestart,
|
|
@@ -5474,21 +6001,71 @@ __export(dockerInstall_exports, {
|
|
|
5474
6001
|
dockerStatus: () => dockerStatus,
|
|
5475
6002
|
dockerStop: () => dockerStop,
|
|
5476
6003
|
dockerUpdate: () => dockerUpdate,
|
|
6004
|
+
ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
|
|
5477
6005
|
imageTag: () => imageTag,
|
|
5478
6006
|
normalizeProvider: () => normalizeProvider,
|
|
5479
6007
|
poolLabel: () => poolLabel,
|
|
5480
6008
|
readContainerConfig: () => readContainerConfig,
|
|
5481
6009
|
resolveConductorProvider: () => resolveConductorProvider,
|
|
6010
|
+
resolveContainerName: () => resolveContainerName,
|
|
5482
6011
|
resolveGraderPool: () => resolveGraderPool,
|
|
5483
6012
|
resolveWorkerConfig: () => resolveWorkerConfig,
|
|
5484
6013
|
splitWorkers: () => splitWorkers,
|
|
5485
6014
|
waitForContainerReady: () => waitForContainerReady,
|
|
5486
6015
|
waitForWorkersReady: () => waitForWorkersReady
|
|
5487
6016
|
});
|
|
5488
|
-
import {
|
|
5489
|
-
|
|
5490
|
-
|
|
6017
|
+
import {
|
|
6018
|
+
chmodSync as chmodSync3,
|
|
6019
|
+
copyFileSync,
|
|
6020
|
+
existsSync as existsSync19,
|
|
6021
|
+
mkdirSync as mkdirSync12,
|
|
6022
|
+
readFileSync as readFileSync17,
|
|
6023
|
+
readdirSync as readdirSync2,
|
|
6024
|
+
renameSync as renameSync7,
|
|
6025
|
+
writeFileSync as writeFileSync14
|
|
6026
|
+
} from "fs";
|
|
6027
|
+
import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "crypto";
|
|
6028
|
+
import { homedir as homedir17 } from "os";
|
|
6029
|
+
import { join as join15 } from "path";
|
|
5491
6030
|
import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
|
|
6031
|
+
function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
|
|
6032
|
+
const value = String(raw || "").trim();
|
|
6033
|
+
if (!value) return "synkro-server";
|
|
6034
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) {
|
|
6035
|
+
throw new Error("SYNKRO_CONTAINER_NAME must be a valid Docker container name (1-128 letters, digits, _, ., or -)");
|
|
6036
|
+
}
|
|
6037
|
+
return value;
|
|
6038
|
+
}
|
|
6039
|
+
function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
|
|
6040
|
+
const iterations = 4096;
|
|
6041
|
+
const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
|
|
6042
|
+
const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
|
|
6043
|
+
const storedKey = createHash2("sha256").update(clientKey).digest();
|
|
6044
|
+
const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
|
|
6045
|
+
return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
|
|
6046
|
+
}
|
|
6047
|
+
function ensurePgliteProxyCredentials() {
|
|
6048
|
+
const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
|
|
6049
|
+
const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
|
|
6050
|
+
if (hasPassword && hasUserlist) {
|
|
6051
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6052
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
const password = randomBytes2(24).toString("base64url");
|
|
6056
|
+
const verifier = createPgliteScramVerifier(password);
|
|
6057
|
+
const suffix = `${process.pid}.${Date.now()}.tmp`;
|
|
6058
|
+
const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
|
|
6059
|
+
const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
|
|
6060
|
+
writeFileSync14(passwordTmp, `${password}
|
|
6061
|
+
`, { mode: 384 });
|
|
6062
|
+
writeFileSync14(userlistTmp, `"synkro" "${verifier}"
|
|
6063
|
+
`, { mode: 384 });
|
|
6064
|
+
renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
|
|
6065
|
+
renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
|
|
6066
|
+
chmodSync3(PGLITE_PASSWORD_PATH, 384);
|
|
6067
|
+
chmodSync3(PGLITE_USERLIST_PATH, 384);
|
|
6068
|
+
}
|
|
5492
6069
|
function resolveConductorProvider(pool, counts) {
|
|
5493
6070
|
if (pool !== "auto") return pool;
|
|
5494
6071
|
const ranked = [
|
|
@@ -5597,9 +6174,9 @@ function readSynkroFileConfig() {
|
|
|
5597
6174
|
try {
|
|
5598
6175
|
const root = execSync3("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5599
6176
|
if (!root) return { pool: "auto", warnings: [] };
|
|
5600
|
-
const fp =
|
|
6177
|
+
const fp = join15(root, "synkro.toml");
|
|
5601
6178
|
if (!existsSync19(fp)) return { pool: "auto", warnings: [] };
|
|
5602
|
-
const parsed = parseSynkroToml(
|
|
6179
|
+
const parsed = parseSynkroToml(readFileSync17(fp, "utf-8"));
|
|
5603
6180
|
return resolveGraderPool(parsed);
|
|
5604
6181
|
} catch {
|
|
5605
6182
|
}
|
|
@@ -5682,7 +6259,7 @@ function assertDockerAvailable() {
|
|
|
5682
6259
|
}
|
|
5683
6260
|
function claudeCredsHostDir() {
|
|
5684
6261
|
if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;
|
|
5685
|
-
return
|
|
6262
|
+
return join15(homedir17(), ".claude");
|
|
5686
6263
|
}
|
|
5687
6264
|
function resolveSynkroBin() {
|
|
5688
6265
|
const which2 = spawnSync3("which", ["synkro"], { encoding: "utf-8", timeout: 5e3 });
|
|
@@ -5741,16 +6318,17 @@ async function dockerInstall(opts = {}) {
|
|
|
5741
6318
|
const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
|
|
5742
6319
|
const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
|
|
5743
6320
|
const usesCodex = codexWorkers > 0 || conductorProvider === "codex";
|
|
5744
|
-
const codexHomeDir = opts.codexHomeDir ??
|
|
5745
|
-
if (usesCodex && !existsSync19(
|
|
6321
|
+
const codexHomeDir = opts.codexHomeDir ?? join15(SYNKRO_DIR7, "codex-local-session");
|
|
6322
|
+
if (usesCodex && !existsSync19(join15(codexHomeDir, "auth.json"))) {
|
|
5746
6323
|
throw new DockerInstallError(
|
|
5747
6324
|
"Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
|
|
5748
6325
|
);
|
|
5749
6326
|
}
|
|
5750
6327
|
mkdirSync12(PGDATA_PATH, { recursive: true });
|
|
5751
6328
|
mkdirSync12(BACKUP_DIR, { recursive: true });
|
|
6329
|
+
ensurePgliteProxyCredentials();
|
|
5752
6330
|
mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
|
|
5753
|
-
const hostClaudeJson =
|
|
6331
|
+
const hostClaudeJson = join15(homedir17(), ".claude.json");
|
|
5754
6332
|
if (existsSync19(hostClaudeJson)) {
|
|
5755
6333
|
copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
|
|
5756
6334
|
}
|
|
@@ -5783,7 +6361,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5783
6361
|
console.warn(` Plist written to ${plist} \u2014 load manually with launchctl bootstrap when ready.`);
|
|
5784
6362
|
}
|
|
5785
6363
|
} else {
|
|
5786
|
-
mkdirSync12(
|
|
6364
|
+
mkdirSync12(join15(homedir17(), ".claude"), { recursive: true });
|
|
5787
6365
|
}
|
|
5788
6366
|
const imageExistsLocally = () => spawnSync3("docker", ["image", "inspect", image], { stdio: "ignore", timeout: 3e4 }).status === 0;
|
|
5789
6367
|
const skipPull = process.env.SYNKRO_SKIP_PULL === "1" || process.env.SYNKRO_SKIP_PULL === "true";
|
|
@@ -5822,7 +6400,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5822
6400
|
"-p",
|
|
5823
6401
|
`127.0.0.1:${HOST_CWE_PORT}:8930`,
|
|
5824
6402
|
"-p",
|
|
5825
|
-
`127.0.0.1:${HOST_PGLITE_PORT}:
|
|
6403
|
+
`127.0.0.1:${HOST_PGLITE_PORT}:5434`,
|
|
5826
6404
|
"-v",
|
|
5827
6405
|
`${PGDATA_PATH}:/data/pgdata`,
|
|
5828
6406
|
"-v",
|
|
@@ -5836,7 +6414,7 @@ async function dockerInstall(opts = {}) {
|
|
|
5836
6414
|
"-v",
|
|
5837
6415
|
`${credsDir}:/home/synkro/.claude:rw`,
|
|
5838
6416
|
"-v",
|
|
5839
|
-
`${
|
|
6417
|
+
`${join15(homedir17(), ".claude")}:/data/claude-host:ro`,
|
|
5840
6418
|
"-v",
|
|
5841
6419
|
`${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
|
|
5842
6420
|
// Cursor creds — mounted RW so the in-container refresher can rotate the
|
|
@@ -5856,6 +6434,8 @@ async function dockerInstall(opts = {}) {
|
|
|
5856
6434
|
// Pass through the batch-size lever if the operator set it. Defaults
|
|
5857
6435
|
// inside the container to 5; clamped to [1, 20] by synkro-server.ts.
|
|
5858
6436
|
...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
|
|
6437
|
+
// Full verifier prompt/response tracing is explicit opt-in because it contains source code.
|
|
6438
|
+
...process.env.SYNKRO_VERIFY_TRACE === "1" ? ["-e", "SYNKRO_VERIFY_TRACE=1"] : [],
|
|
5859
6439
|
// Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.
|
|
5860
6440
|
...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
|
|
5861
6441
|
// Fix-poll kill switch. Default ON in the image; a benchmark/headless run
|
|
@@ -5887,7 +6467,14 @@ async function dockerInstall(opts = {}) {
|
|
|
5887
6467
|
if (run.status !== 0) {
|
|
5888
6468
|
throw new DockerInstallError(`docker run failed (image ${image})`);
|
|
5889
6469
|
}
|
|
5890
|
-
return {
|
|
6470
|
+
return {
|
|
6471
|
+
image,
|
|
6472
|
+
hostMcpPort: HOST_MCP_PORT,
|
|
6473
|
+
hostGraderPort: HOST_GRADER_PORT,
|
|
6474
|
+
hostCwePort: HOST_CWE_PORT,
|
|
6475
|
+
hostPglitePort: HOST_PGLITE_PORT,
|
|
6476
|
+
pglitePasswordPath: PGLITE_PASSWORD_PATH
|
|
6477
|
+
};
|
|
5891
6478
|
}
|
|
5892
6479
|
async function waitForContainerReady(timeoutMs = 6e4) {
|
|
5893
6480
|
const start = Date.now();
|
|
@@ -5942,7 +6529,7 @@ function dockerStatus() {
|
|
|
5942
6529
|
return {
|
|
5943
6530
|
running: true,
|
|
5944
6531
|
image: imageTag(),
|
|
5945
|
-
healthz: `http://127.0.0.1:${HOST_MCP_PORT}
|
|
6532
|
+
healthz: `http://127.0.0.1:${HOST_MCP_PORT}/health`
|
|
5946
6533
|
};
|
|
5947
6534
|
}
|
|
5948
6535
|
function readContainerConfig() {
|
|
@@ -6089,23 +6676,34 @@ function checkPgdata() {
|
|
|
6089
6676
|
if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
|
|
6090
6677
|
return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
|
|
6091
6678
|
}
|
|
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;
|
|
6679
|
+
var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, PGLITE_PASSWORD_PATH, PGLITE_USERLIST_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, defaultImageVersion, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
6093
6680
|
var init_dockerInstall = __esm({
|
|
6094
6681
|
"cli/local-cc/dockerInstall.ts"() {
|
|
6095
6682
|
"use strict";
|
|
6096
6683
|
init_agentDetect();
|
|
6097
6684
|
init_macKeychain();
|
|
6098
|
-
SYNKRO_DIR7 =
|
|
6099
|
-
MCP_JWT_PATH =
|
|
6100
|
-
PGDATA_PATH =
|
|
6101
|
-
|
|
6102
|
-
|
|
6685
|
+
SYNKRO_DIR7 = join15(homedir17(), ".synkro");
|
|
6686
|
+
MCP_JWT_PATH = join15(SYNKRO_DIR7, ".mcp-jwt");
|
|
6687
|
+
PGDATA_PATH = join15(SYNKRO_DIR7, "pgdata");
|
|
6688
|
+
PGLITE_PASSWORD_PATH = join15(SYNKRO_DIR7, ".pglite-password");
|
|
6689
|
+
PGLITE_USERLIST_PATH = join15(SYNKRO_DIR7, ".pglite-userlist");
|
|
6690
|
+
CLAUDE_HOST_STATE_DIR = join15(SYNKRO_DIR7, "claude-host-state");
|
|
6691
|
+
CLAUDE_HOST_STATE_FILE = join15(CLAUDE_HOST_STATE_DIR, ".claude.json");
|
|
6103
6692
|
HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
6104
6693
|
HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
|
|
6105
6694
|
HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
|
|
6106
6695
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
6107
|
-
CONTAINER_NAME =
|
|
6108
|
-
|
|
6696
|
+
CONTAINER_NAME = resolveContainerName();
|
|
6697
|
+
defaultImageVersion = () => {
|
|
6698
|
+
if (true) return "1.7.89";
|
|
6699
|
+
try {
|
|
6700
|
+
const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
|
|
6701
|
+
if (pkg.version) return pkg.version;
|
|
6702
|
+
} catch {
|
|
6703
|
+
}
|
|
6704
|
+
return process.env.npm_package_version || "0.0.0-dev";
|
|
6705
|
+
};
|
|
6706
|
+
DEFAULT_IMAGE = `ghcr.io/synkro-sh/synkro-server:${defaultImageVersion()}`;
|
|
6109
6707
|
DockerInstallError = class extends Error {
|
|
6110
6708
|
constructor(message, cause) {
|
|
6111
6709
|
super(message);
|
|
@@ -6114,17 +6712,17 @@ var init_dockerInstall = __esm({
|
|
|
6114
6712
|
}
|
|
6115
6713
|
cause;
|
|
6116
6714
|
};
|
|
6117
|
-
BACKUP_DIR =
|
|
6715
|
+
BACKUP_DIR = join15(SYNKRO_DIR7, "pgdata-backups");
|
|
6118
6716
|
}
|
|
6119
6717
|
});
|
|
6120
6718
|
|
|
6121
6719
|
// cli/local-cc/setupToken.ts
|
|
6122
6720
|
import { spawn as nodeSpawn } from "child_process";
|
|
6123
|
-
import { readFileSync as
|
|
6124
|
-
import { homedir as
|
|
6125
|
-
import { join as
|
|
6721
|
+
import { readFileSync as readFileSync18, unlinkSync as unlinkSync6 } from "fs";
|
|
6722
|
+
import { homedir as homedir18, platform as platform4 } from "os";
|
|
6723
|
+
import { join as join16 } from "path";
|
|
6126
6724
|
function captureClaudeSetupToken() {
|
|
6127
|
-
const tmpFile =
|
|
6725
|
+
const tmpFile = join16(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
|
|
6128
6726
|
const isMac = platform4() === "darwin";
|
|
6129
6727
|
const bin = "script";
|
|
6130
6728
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
@@ -6138,7 +6736,7 @@ function captureClaudeSetupToken() {
|
|
|
6138
6736
|
proc.on("close", (code) => {
|
|
6139
6737
|
let raw = "";
|
|
6140
6738
|
try {
|
|
6141
|
-
raw =
|
|
6739
|
+
raw = readFileSync18(tmpFile, "utf-8");
|
|
6142
6740
|
} catch (e) {
|
|
6143
6741
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
6144
6742
|
return;
|
|
@@ -6204,15 +6802,15 @@ var SYNKRO_DIR8;
|
|
|
6204
6802
|
var init_setupToken = __esm({
|
|
6205
6803
|
"cli/local-cc/setupToken.ts"() {
|
|
6206
6804
|
"use strict";
|
|
6207
|
-
SYNKRO_DIR8 =
|
|
6805
|
+
SYNKRO_DIR8 = join16(homedir18(), ".synkro");
|
|
6208
6806
|
}
|
|
6209
6807
|
});
|
|
6210
6808
|
|
|
6211
6809
|
// cli/local-cc/codexCloudSetup.ts
|
|
6212
6810
|
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
6213
|
-
import { readFileSync as
|
|
6214
|
-
import { homedir as
|
|
6215
|
-
import { join as
|
|
6811
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
|
|
6812
|
+
import { homedir as homedir19 } from "os";
|
|
6813
|
+
import { join as join17 } from "path";
|
|
6216
6814
|
function findCodexBinary() {
|
|
6217
6815
|
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
6218
6816
|
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
@@ -6221,7 +6819,7 @@ function findCodexBinary() {
|
|
|
6221
6819
|
}
|
|
6222
6820
|
function runCodexLogin(codexBin, codexHome) {
|
|
6223
6821
|
mkdirSync13(codexHome, { recursive: true, mode: 448 });
|
|
6224
|
-
|
|
6822
|
+
writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
|
|
6225
6823
|
return new Promise((resolve6, reject) => {
|
|
6226
6824
|
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
6227
6825
|
stdio: "inherit",
|
|
@@ -6253,13 +6851,13 @@ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
|
6253
6851
|
} catch (e) {
|
|
6254
6852
|
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
6255
6853
|
}
|
|
6256
|
-
const authPath =
|
|
6854
|
+
const authPath = join17(CODEX_CLOUD_HOME, "auth.json");
|
|
6257
6855
|
if (!existsSync20(authPath)) {
|
|
6258
6856
|
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
6259
6857
|
}
|
|
6260
6858
|
let auth;
|
|
6261
6859
|
try {
|
|
6262
|
-
auth = JSON.parse(
|
|
6860
|
+
auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6263
6861
|
} catch (e) {
|
|
6264
6862
|
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
6265
6863
|
}
|
|
@@ -6293,7 +6891,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6293
6891
|
if (!codexBin) {
|
|
6294
6892
|
return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
|
|
6295
6893
|
}
|
|
6296
|
-
const authPath =
|
|
6894
|
+
const authPath = join17(CODEX_LOCAL_HOME, "auth.json");
|
|
6297
6895
|
if (!existsSync20(authPath)) {
|
|
6298
6896
|
onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
|
|
6299
6897
|
try {
|
|
@@ -6303,7 +6901,7 @@ async function setupCodexLocal(onStatus) {
|
|
|
6303
6901
|
}
|
|
6304
6902
|
}
|
|
6305
6903
|
try {
|
|
6306
|
-
const auth = JSON.parse(
|
|
6904
|
+
const auth = JSON.parse(readFileSync19(authPath, "utf-8"));
|
|
6307
6905
|
if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
|
|
6308
6906
|
} catch (e) {
|
|
6309
6907
|
return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
|
|
@@ -6315,9 +6913,9 @@ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
|
|
|
6315
6913
|
var init_codexCloudSetup = __esm({
|
|
6316
6914
|
"cli/local-cc/codexCloudSetup.ts"() {
|
|
6317
6915
|
"use strict";
|
|
6318
|
-
SYNKRO_DIR9 =
|
|
6319
|
-
CODEX_CLOUD_HOME =
|
|
6320
|
-
CODEX_LOCAL_HOME =
|
|
6916
|
+
SYNKRO_DIR9 = join17(homedir19(), ".synkro");
|
|
6917
|
+
CODEX_CLOUD_HOME = join17(SYNKRO_DIR9, "codex-cloud-session");
|
|
6918
|
+
CODEX_LOCAL_HOME = join17(SYNKRO_DIR9, "codex-local-session");
|
|
6321
6919
|
}
|
|
6322
6920
|
});
|
|
6323
6921
|
|
|
@@ -6339,21 +6937,21 @@ __export(ptyShim_exports, {
|
|
|
6339
6937
|
import {
|
|
6340
6938
|
existsSync as existsSync21,
|
|
6341
6939
|
mkdirSync as mkdirSync14,
|
|
6342
|
-
writeFileSync as
|
|
6343
|
-
chmodSync as
|
|
6344
|
-
readFileSync as
|
|
6940
|
+
writeFileSync as writeFileSync16,
|
|
6941
|
+
chmodSync as chmodSync4,
|
|
6942
|
+
readFileSync as readFileSync20,
|
|
6345
6943
|
rmSync as rmSync2,
|
|
6346
6944
|
realpathSync,
|
|
6347
6945
|
symlinkSync,
|
|
6348
6946
|
lstatSync,
|
|
6349
6947
|
readdirSync as readdirSync3
|
|
6350
6948
|
} from "fs";
|
|
6351
|
-
import { homedir as
|
|
6352
|
-
import { join as
|
|
6949
|
+
import { homedir as homedir20 } from "os";
|
|
6950
|
+
import { join as join18 } from "path";
|
|
6353
6951
|
import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
|
|
6354
6952
|
function rcFiles() {
|
|
6355
|
-
const h =
|
|
6356
|
-
return [
|
|
6953
|
+
const h = homedir20();
|
|
6954
|
+
return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
|
|
6357
6955
|
}
|
|
6358
6956
|
function resolveRealClaude() {
|
|
6359
6957
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
@@ -6365,7 +6963,7 @@ function resolveRealClaude() {
|
|
|
6365
6963
|
return p;
|
|
6366
6964
|
}
|
|
6367
6965
|
}
|
|
6368
|
-
for (const c of [
|
|
6966
|
+
for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
|
|
6369
6967
|
if (existsSync21(c)) {
|
|
6370
6968
|
try {
|
|
6371
6969
|
return realpathSync(c);
|
|
@@ -6380,7 +6978,7 @@ function findClaudeLink() {
|
|
|
6380
6978
|
const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
|
|
6381
6979
|
let linkPath = (r.stdout || "").trim();
|
|
6382
6980
|
if (!linkPath) {
|
|
6383
|
-
const c =
|
|
6981
|
+
const c = join18(homedir20(), ".local", "bin", "claude");
|
|
6384
6982
|
if (existsSync21(c)) linkPath = c;
|
|
6385
6983
|
else return null;
|
|
6386
6984
|
}
|
|
@@ -6394,7 +6992,7 @@ function findClaudeLink() {
|
|
|
6394
6992
|
}
|
|
6395
6993
|
function isOurShim(path) {
|
|
6396
6994
|
try {
|
|
6397
|
-
return
|
|
6995
|
+
return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
|
|
6398
6996
|
} catch {
|
|
6399
6997
|
return false;
|
|
6400
6998
|
}
|
|
@@ -6407,7 +7005,7 @@ function shadowClaude() {
|
|
|
6407
7005
|
}
|
|
6408
7006
|
const { linkPath, realTarget } = found;
|
|
6409
7007
|
if (isOurShim(linkPath)) {
|
|
6410
|
-
console.log(` \xB7 ${linkPath.replace(
|
|
7008
|
+
console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
|
|
6411
7009
|
return;
|
|
6412
7010
|
}
|
|
6413
7011
|
let wasSymlink = false;
|
|
@@ -6417,14 +7015,14 @@ function shadowClaude() {
|
|
|
6417
7015
|
}
|
|
6418
7016
|
const state = { linkPath, realTarget, wasSymlink };
|
|
6419
7017
|
try {
|
|
6420
|
-
|
|
7018
|
+
writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
|
|
6421
7019
|
} catch {
|
|
6422
7020
|
}
|
|
6423
7021
|
try {
|
|
6424
7022
|
rmSync2(linkPath, { force: true });
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
console.log(` \u2713 shadowed ${linkPath.replace(
|
|
7023
|
+
writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
|
|
7024
|
+
chmodSync4(linkPath, 493);
|
|
7025
|
+
console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
|
|
6428
7026
|
} catch (e) {
|
|
6429
7027
|
console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
|
|
6430
7028
|
}
|
|
@@ -6432,7 +7030,7 @@ function shadowClaude() {
|
|
|
6432
7030
|
function unshadowClaude() {
|
|
6433
7031
|
let state;
|
|
6434
7032
|
try {
|
|
6435
|
-
state = JSON.parse(
|
|
7033
|
+
state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
|
|
6436
7034
|
} catch {
|
|
6437
7035
|
return;
|
|
6438
7036
|
}
|
|
@@ -6441,7 +7039,7 @@ function unshadowClaude() {
|
|
|
6441
7039
|
if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
|
|
6442
7040
|
rmSync2(state.linkPath, { force: true });
|
|
6443
7041
|
symlinkSync(state.realTarget, state.linkPath);
|
|
6444
|
-
console.log(`\u2713 restored ${state.linkPath.replace(
|
|
7042
|
+
console.log(`\u2713 restored ${state.linkPath.replace(homedir20(), "~")} \u2192 ${state.realTarget.replace(homedir20(), "~")}`);
|
|
6445
7043
|
} catch (e) {
|
|
6446
7044
|
console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
|
|
6447
7045
|
}
|
|
@@ -6452,13 +7050,13 @@ function addPathBlock() {
|
|
|
6452
7050
|
if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
|
|
6453
7051
|
let body = "";
|
|
6454
7052
|
try {
|
|
6455
|
-
body =
|
|
7053
|
+
body = readFileSync20(rc, "utf-8");
|
|
6456
7054
|
} catch {
|
|
6457
7055
|
}
|
|
6458
7056
|
const cleaned = stripBlock(body);
|
|
6459
7057
|
const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
|
|
6460
7058
|
try {
|
|
6461
|
-
|
|
7059
|
+
writeFileSync16(rc, next, "utf-8");
|
|
6462
7060
|
touched.push(rc);
|
|
6463
7061
|
} catch {
|
|
6464
7062
|
}
|
|
@@ -6477,12 +7075,12 @@ function installPtyShim() {
|
|
|
6477
7075
|
mkdirSync14(SHIM_BIN_DIR, { recursive: true });
|
|
6478
7076
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6479
7077
|
const real = resolveRealClaude();
|
|
6480
|
-
|
|
6481
|
-
|
|
7078
|
+
writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
|
|
7079
|
+
chmodSync4(SHIM_PATH, 493);
|
|
6482
7080
|
const touched = addPathBlock();
|
|
6483
7081
|
console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
|
|
6484
7082
|
shadowClaude();
|
|
6485
|
-
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(
|
|
7083
|
+
if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
|
|
6486
7084
|
}
|
|
6487
7085
|
function uninstallPtyShim() {
|
|
6488
7086
|
try {
|
|
@@ -6500,10 +7098,10 @@ function uninstallPtyShim() {
|
|
|
6500
7098
|
for (const rc of rcFiles()) {
|
|
6501
7099
|
if (!existsSync21(rc)) continue;
|
|
6502
7100
|
try {
|
|
6503
|
-
const body =
|
|
7101
|
+
const body = readFileSync20(rc, "utf-8");
|
|
6504
7102
|
if (body.includes(RC_BEGIN)) {
|
|
6505
|
-
|
|
6506
|
-
cleaned.push(rc.replace(
|
|
7103
|
+
writeFileSync16(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
7104
|
+
cleaned.push(rc.replace(homedir20(), "~"));
|
|
6507
7105
|
}
|
|
6508
7106
|
} catch {
|
|
6509
7107
|
}
|
|
@@ -6535,7 +7133,7 @@ function listSessions(opts = {}) {
|
|
|
6535
7133
|
const out = [];
|
|
6536
7134
|
for (const f of files) {
|
|
6537
7135
|
try {
|
|
6538
|
-
const r = JSON.parse(
|
|
7136
|
+
const r = JSON.parse(readFileSync20(join18(SESSIONS_DIR, f), "utf-8"));
|
|
6539
7137
|
if (!r || !r.session_id) continue;
|
|
6540
7138
|
if (liveOnly) {
|
|
6541
7139
|
const s = r.tmux_session;
|
|
@@ -6564,7 +7162,7 @@ function resolveTargetSession(override) {
|
|
|
6564
7162
|
const own = currentTmuxSession();
|
|
6565
7163
|
const fromFile = (() => {
|
|
6566
7164
|
try {
|
|
6567
|
-
return
|
|
7165
|
+
return readFileSync20(ACTIVE_SESSION_FILE, "utf-8").trim();
|
|
6568
7166
|
} catch {
|
|
6569
7167
|
return "";
|
|
6570
7168
|
}
|
|
@@ -6590,7 +7188,7 @@ function injectModel(model, sessionOverride) {
|
|
|
6590
7188
|
sendKeys("Escape");
|
|
6591
7189
|
sendKeys("-l", `/model ${model}`);
|
|
6592
7190
|
sendKeys("Enter");
|
|
6593
|
-
const pidFile =
|
|
7191
|
+
const pidFile = join18(PTY_STATE_DIR, `poll-${session}.pid`);
|
|
6594
7192
|
try {
|
|
6595
7193
|
mkdirSync14(PTY_STATE_DIR, { recursive: true });
|
|
6596
7194
|
} catch {
|
|
@@ -6604,13 +7202,13 @@ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, S
|
|
|
6604
7202
|
var init_ptyShim = __esm({
|
|
6605
7203
|
"cli/local-cc/ptyShim.ts"() {
|
|
6606
7204
|
"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 =
|
|
7205
|
+
SYNKRO_DIR10 = join18(homedir20(), ".synkro");
|
|
7206
|
+
SHIM_BIN_DIR = join18(SYNKRO_DIR10, "bin");
|
|
7207
|
+
SHIM_PATH = join18(SHIM_BIN_DIR, "claude");
|
|
7208
|
+
PTY_STATE_DIR = join18(SYNKRO_DIR10, "pty");
|
|
7209
|
+
ACTIVE_SESSION_FILE = join18(PTY_STATE_DIR, "active");
|
|
7210
|
+
SHADOW_STATE_FILE = join18(PTY_STATE_DIR, "shadow.json");
|
|
7211
|
+
SESSIONS_DIR = join18(PTY_STATE_DIR, "sessions");
|
|
6614
7212
|
SHIM_SESSION_PREFIX = "synkro-cc-";
|
|
6615
7213
|
RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
|
|
6616
7214
|
RC_END = "# <<< synkro pty shim <<<";
|
|
@@ -6681,6 +7279,189 @@ var init_graderSmoke = __esm({
|
|
|
6681
7279
|
}
|
|
6682
7280
|
});
|
|
6683
7281
|
|
|
7282
|
+
// cli/scanning/codexTranscriptUsage.ts
|
|
7283
|
+
function rawCounters(value) {
|
|
7284
|
+
return {
|
|
7285
|
+
input: finiteCounter(value?.input_tokens),
|
|
7286
|
+
output: finiteCounter(value?.output_tokens),
|
|
7287
|
+
cacheCreation: finiteCounter(value?.cache_write_input_tokens),
|
|
7288
|
+
cacheRead: finiteCounter(value?.cached_input_tokens)
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
7291
|
+
function counterDelta(current, previous) {
|
|
7292
|
+
return current >= previous ? current - previous : current;
|
|
7293
|
+
}
|
|
7294
|
+
function normalizedUsage(raw) {
|
|
7295
|
+
return {
|
|
7296
|
+
// Codex input_tokens includes cached/cache-write details. Store only the
|
|
7297
|
+
// uncached remainder in input_tokens so totals and pricing never count the
|
|
7298
|
+
// same prompt tokens twice.
|
|
7299
|
+
input_tokens: Math.max(0, raw.input - raw.cacheCreation - raw.cacheRead),
|
|
7300
|
+
output_tokens: raw.output,
|
|
7301
|
+
cache_creation_input_tokens: raw.cacheCreation,
|
|
7302
|
+
cache_read_input_tokens: raw.cacheRead
|
|
7303
|
+
};
|
|
7304
|
+
}
|
|
7305
|
+
function addUsage(a, b) {
|
|
7306
|
+
return {
|
|
7307
|
+
input_tokens: (a?.input_tokens || 0) + b.input_tokens,
|
|
7308
|
+
output_tokens: (a?.output_tokens || 0) + b.output_tokens,
|
|
7309
|
+
cache_creation_input_tokens: (a?.cache_creation_input_tokens || 0) + b.cache_creation_input_tokens,
|
|
7310
|
+
cache_read_input_tokens: (a?.cache_read_input_tokens || 0) + b.cache_read_input_tokens
|
|
7311
|
+
};
|
|
7312
|
+
}
|
|
7313
|
+
function parseCodexTranscriptUsage(transcript, options = {}) {
|
|
7314
|
+
if (!transcript) return null;
|
|
7315
|
+
const lines = transcript.split("\n");
|
|
7316
|
+
const hasCanonicalAssistant = lines.some((line) => {
|
|
7317
|
+
try {
|
|
7318
|
+
const entry = JSON.parse(line);
|
|
7319
|
+
return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
|
|
7320
|
+
} catch {
|
|
7321
|
+
return false;
|
|
7322
|
+
}
|
|
7323
|
+
});
|
|
7324
|
+
const turnsByLine = /* @__PURE__ */ new Map();
|
|
7325
|
+
let model = "";
|
|
7326
|
+
let lastAssistantLine = -1;
|
|
7327
|
+
let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
|
|
7328
|
+
let latest = null;
|
|
7329
|
+
let sawSnapshot = false;
|
|
7330
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7331
|
+
const line = lines[i].trim();
|
|
7332
|
+
if (!line) continue;
|
|
7333
|
+
let entry;
|
|
7334
|
+
try {
|
|
7335
|
+
entry = JSON.parse(line);
|
|
7336
|
+
} catch {
|
|
7337
|
+
continue;
|
|
7338
|
+
}
|
|
7339
|
+
if (entry?.type === "turn_context" && typeof entry?.payload?.model === "string") {
|
|
7340
|
+
model = entry.payload.model;
|
|
7341
|
+
continue;
|
|
7342
|
+
}
|
|
7343
|
+
if (!hasCanonicalAssistant && entry?.type === "response_item" && entry?.payload?.type === "message" && entry?.payload?.role === "assistant") {
|
|
7344
|
+
lastAssistantLine = i;
|
|
7345
|
+
continue;
|
|
7346
|
+
}
|
|
7347
|
+
if (entry?.type !== "event_msg") continue;
|
|
7348
|
+
if (entry?.payload?.type === "agent_message") {
|
|
7349
|
+
lastAssistantLine = i;
|
|
7350
|
+
continue;
|
|
7351
|
+
}
|
|
7352
|
+
if (entry?.payload?.type !== "token_count") continue;
|
|
7353
|
+
const eventModel = entry?.payload?.model ?? entry?.model;
|
|
7354
|
+
if (typeof eventModel === "string" && eventModel) model = eventModel;
|
|
7355
|
+
const total = entry?.payload?.info?.total_token_usage;
|
|
7356
|
+
if (!total || typeof total !== "object") continue;
|
|
7357
|
+
const current = rawCounters(total);
|
|
7358
|
+
latest = current;
|
|
7359
|
+
if (options.partial && !sawSnapshot) {
|
|
7360
|
+
previous = current;
|
|
7361
|
+
sawSnapshot = true;
|
|
7362
|
+
continue;
|
|
7363
|
+
}
|
|
7364
|
+
sawSnapshot = true;
|
|
7365
|
+
const delta = normalizedUsage({
|
|
7366
|
+
input: counterDelta(current.input, previous.input),
|
|
7367
|
+
output: counterDelta(current.output, previous.output),
|
|
7368
|
+
cacheCreation: counterDelta(current.cacheCreation, previous.cacheCreation),
|
|
7369
|
+
cacheRead: counterDelta(current.cacheRead, previous.cacheRead)
|
|
7370
|
+
});
|
|
7371
|
+
previous = current;
|
|
7372
|
+
if (lastAssistantLine >= 0) {
|
|
7373
|
+
const prior = turnsByLine.get(lastAssistantLine);
|
|
7374
|
+
turnsByLine.set(lastAssistantLine, {
|
|
7375
|
+
lineIndex: lastAssistantLine,
|
|
7376
|
+
model,
|
|
7377
|
+
usage: addUsage(prior?.usage, delta)
|
|
7378
|
+
});
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
if (model) {
|
|
7382
|
+
for (const turn of turnsByLine.values()) {
|
|
7383
|
+
if (!turn.model) turn.model = model;
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
if (!latest) return null;
|
|
7387
|
+
return { model, total: normalizedUsage(latest), turnsByLine };
|
|
7388
|
+
}
|
|
7389
|
+
var finiteCounter;
|
|
7390
|
+
var init_codexTranscriptUsage = __esm({
|
|
7391
|
+
"cli/scanning/codexTranscriptUsage.ts"() {
|
|
7392
|
+
"use strict";
|
|
7393
|
+
finiteCounter = (value) => {
|
|
7394
|
+
const n = Number(value);
|
|
7395
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
7396
|
+
};
|
|
7397
|
+
}
|
|
7398
|
+
});
|
|
7399
|
+
|
|
7400
|
+
// cli/scanning/codexTranscriptMessages.ts
|
|
7401
|
+
import { createHash as createHash3 } from "crypto";
|
|
7402
|
+
function textContent(content) {
|
|
7403
|
+
if (typeof content === "string") return content.trim();
|
|
7404
|
+
if (!Array.isArray(content)) return "";
|
|
7405
|
+
return content.map((block) => {
|
|
7406
|
+
if (typeof block === "string") return block;
|
|
7407
|
+
return block?.type === "text" || block?.type === "input_text" || block?.type === "output_text" ? String(block.text || "") : "";
|
|
7408
|
+
}).join(" ").trim();
|
|
7409
|
+
}
|
|
7410
|
+
function isCodexConversationNoise(content) {
|
|
7411
|
+
const value = content.trimStart();
|
|
7412
|
+
return value.startsWith("[synkro:") || value.startsWith("claude-code: {") || value.startsWith("RAW claude-code agent:") || value.startsWith("Guard: (R") || value.startsWith("<developer") || value.startsWith("<system") || value.startsWith("<permissions instructions>") || value.startsWith("<app-context>") || value.startsWith("<environment_context>") || value.startsWith("<recommended_plugins>");
|
|
7413
|
+
}
|
|
7414
|
+
function parseCodexConversationMessages(transcript) {
|
|
7415
|
+
if (!transcript) return [];
|
|
7416
|
+
const entries = [];
|
|
7417
|
+
for (const [lineIndex, line] of transcript.split("\n").entries()) {
|
|
7418
|
+
const value = line.trim();
|
|
7419
|
+
if (!value) continue;
|
|
7420
|
+
try {
|
|
7421
|
+
entries.push({ lineIndex, entry: JSON.parse(value) });
|
|
7422
|
+
} catch {
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7425
|
+
const hasCanonicalMessages = entries.some(({ entry }) => entry?.type === "event_msg" && (entry?.payload?.type === "user_message" || entry?.payload?.type === "agent_message"));
|
|
7426
|
+
const messages = [];
|
|
7427
|
+
for (const { lineIndex, entry } of entries) {
|
|
7428
|
+
let role = null;
|
|
7429
|
+
let content;
|
|
7430
|
+
let itemId = "";
|
|
7431
|
+
const eventType = entry?.type === "event_msg" ? entry?.payload?.type : "";
|
|
7432
|
+
if (eventType === "user_message" || eventType === "agent_message") {
|
|
7433
|
+
role = eventType === "user_message" ? "user" : "assistant";
|
|
7434
|
+
content = entry?.payload?.message;
|
|
7435
|
+
} else if (!hasCanonicalMessages && entry?.type === "response_item" && entry?.payload?.type === "message") {
|
|
7436
|
+
const responseRole = entry?.payload?.role;
|
|
7437
|
+
if (responseRole !== "user" && responseRole !== "assistant") continue;
|
|
7438
|
+
role = responseRole;
|
|
7439
|
+
content = entry?.payload?.content;
|
|
7440
|
+
itemId = typeof entry?.payload?.id === "string" ? entry.payload.id : "";
|
|
7441
|
+
} else {
|
|
7442
|
+
continue;
|
|
7443
|
+
}
|
|
7444
|
+
if (!role) continue;
|
|
7445
|
+
const text = textContent(content);
|
|
7446
|
+
if (!text || isCodexConversationNoise(text)) continue;
|
|
7447
|
+
const timestamp = typeof entry?.timestamp === "string" && entry.timestamp ? entry.timestamp : void 0;
|
|
7448
|
+
const uuid = typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : itemId || createHash3("sha256").update((timestamp || "") + "\0" + role + "\0" + text).digest("hex").slice(0, 32);
|
|
7449
|
+
messages.push({
|
|
7450
|
+
lineIndex,
|
|
7451
|
+
role,
|
|
7452
|
+
content: text.slice(0, 8e3),
|
|
7453
|
+
uuid,
|
|
7454
|
+
...timestamp ? { timestamp } : {}
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7457
|
+
return messages;
|
|
7458
|
+
}
|
|
7459
|
+
var init_codexTranscriptMessages = __esm({
|
|
7460
|
+
"cli/scanning/codexTranscriptMessages.ts"() {
|
|
7461
|
+
"use strict";
|
|
7462
|
+
}
|
|
7463
|
+
});
|
|
7464
|
+
|
|
6684
7465
|
// cli/commands/install.ts
|
|
6685
7466
|
var install_exports = {};
|
|
6686
7467
|
__export(install_exports, {
|
|
@@ -6696,12 +7477,12 @@ __export(install_exports, {
|
|
|
6696
7477
|
syncSkillFiles: () => syncSkillFiles,
|
|
6697
7478
|
writeHookScripts: () => writeHookScripts
|
|
6698
7479
|
});
|
|
6699
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as
|
|
6700
|
-
import { homedir as
|
|
6701
|
-
import { join as
|
|
7480
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
|
|
7481
|
+
import { homedir as homedir21 } from "os";
|
|
7482
|
+
import { join as join19, isAbsolute, resolve as resolve4 } from "path";
|
|
6702
7483
|
import { execSync as execSync4, spawn as spawn4 } from "child_process";
|
|
6703
7484
|
import { createInterface as createInterface2 } from "readline";
|
|
6704
|
-
import { createHash as
|
|
7485
|
+
import { createHash as createHash4 } from "crypto";
|
|
6705
7486
|
function resolvePersistedHookMode() {
|
|
6706
7487
|
return "stub";
|
|
6707
7488
|
}
|
|
@@ -6829,34 +7610,37 @@ function ensureSynkroDir() {
|
|
|
6829
7610
|
mkdirSync15(HOOKS_DIR, { recursive: true });
|
|
6830
7611
|
mkdirSync15(BIN_DIR, { recursive: true });
|
|
6831
7612
|
mkdirSync15(OFFSETS_DIR, { recursive: true });
|
|
6832
|
-
mkdirSync15(
|
|
7613
|
+
mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
|
|
6833
7614
|
}
|
|
6834
7615
|
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
|
|
7616
|
+
const installExtractCorePath = join19(HOOKS_DIR, "installExtractCore.ts");
|
|
7617
|
+
const bashScriptPath = join19(HOOKS_DIR, "cc-bash-judge.ts");
|
|
7618
|
+
const skillJudgeScriptPath = join19(HOOKS_DIR, "cc-skill-judge.ts");
|
|
7619
|
+
const cursorSkillJudgePath = join19(HOOKS_DIR, "cursor-skill-judge.ts");
|
|
7620
|
+
const bashFollowupScriptPath = join19(HOOKS_DIR, "cc-bash-followup.ts");
|
|
7621
|
+
const editPrecheckScriptPath = join19(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
7622
|
+
const editFollowupScriptPath = join19(HOOKS_DIR, "cc-edit-followup.ts");
|
|
7623
|
+
const cwePrecheckScriptPath = join19(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
7624
|
+
const cvePrecheckScriptPath = join19(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
7625
|
+
const planJudgeScriptPath = join19(HOOKS_DIR, "cc-plan-judge.ts");
|
|
7626
|
+
const agentJudgeScriptPath = join19(HOOKS_DIR, "cc-agent-judge.ts");
|
|
7627
|
+
const stopSummaryScriptPath = join19(HOOKS_DIR, "cc-stop-summary.ts");
|
|
7628
|
+
const sessionStartScriptPath = join19(HOOKS_DIR, "cc-session-start.ts");
|
|
7629
|
+
const transcriptSyncScriptPath = join19(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
7630
|
+
const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
|
|
7631
|
+
const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
|
|
7632
|
+
const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
7633
|
+
const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
|
|
7634
|
+
const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
|
|
7635
|
+
const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
|
|
7636
|
+
const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
|
|
7637
|
+
const cursorBashJudgePath = join19(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
7638
|
+
const cursorEditCapturePath = join19(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
7639
|
+
const cursorAgentCapturePath = join19(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
7640
|
+
const mcpStdioProxyPath = join19(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
7641
|
+
const taskActivateIntentScriptPath = join19(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
7642
|
+
const mcpGateScriptPath = join19(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
7643
|
+
const stubCommonPath = join19(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
6860
7644
|
const stubFiles = [
|
|
6861
7645
|
[stubCommonPath, STUB_COMMON_TS],
|
|
6862
7646
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -6864,6 +7648,7 @@ function writeHookScripts() {
|
|
|
6864
7648
|
[cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],
|
|
6865
7649
|
[bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],
|
|
6866
7650
|
[editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],
|
|
7651
|
+
[editFollowupScriptPath, STUB_EDIT_FOLLOWUP_TS],
|
|
6867
7652
|
[cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],
|
|
6868
7653
|
[cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],
|
|
6869
7654
|
[planJudgeScriptPath, STUB_PLAN_JUDGE_TS],
|
|
@@ -6871,6 +7656,8 @@ function writeHookScripts() {
|
|
|
6871
7656
|
[stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],
|
|
6872
7657
|
[sessionStartScriptPath, STUB_SESSION_START_TS],
|
|
6873
7658
|
[transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],
|
|
7659
|
+
[subagentStartScriptPath, STUB_SUBAGENT_START_TS],
|
|
7660
|
+
[subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
|
|
6874
7661
|
[userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
|
|
6875
7662
|
[promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
|
|
6876
7663
|
[installScanScriptPath, STUB_INSTALL_SCAN_TS],
|
|
@@ -6881,14 +7668,14 @@ function writeHookScripts() {
|
|
|
6881
7668
|
[cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
|
|
6882
7669
|
];
|
|
6883
7670
|
for (const [p, content] of stubFiles) {
|
|
6884
|
-
|
|
6885
|
-
|
|
7671
|
+
writeFileSync17(p, content, "utf-8");
|
|
7672
|
+
chmodSync5(p, 493);
|
|
6886
7673
|
}
|
|
6887
|
-
|
|
6888
|
-
|
|
7674
|
+
writeFileSync17(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
7675
|
+
chmodSync5(mcpStdioProxyPath, 493);
|
|
6889
7676
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
6890
7677
|
try {
|
|
6891
|
-
unlinkSync7(
|
|
7678
|
+
unlinkSync7(join19(HOOKS_DIR, stale));
|
|
6892
7679
|
} catch {
|
|
6893
7680
|
}
|
|
6894
7681
|
}
|
|
@@ -6898,6 +7685,7 @@ function writeHookScripts() {
|
|
|
6898
7685
|
cursorSkillJudgeScript: cursorSkillJudgePath,
|
|
6899
7686
|
bashFollowupScript: bashFollowupScriptPath,
|
|
6900
7687
|
editPrecheckScript: editPrecheckScriptPath,
|
|
7688
|
+
editFollowupScript: editFollowupScriptPath,
|
|
6901
7689
|
cwePrecheckScript: cwePrecheckScriptPath,
|
|
6902
7690
|
cvePrecheckScript: cvePrecheckScriptPath,
|
|
6903
7691
|
planJudgeScript: planJudgeScriptPath,
|
|
@@ -6905,6 +7693,8 @@ function writeHookScripts() {
|
|
|
6905
7693
|
stopSummaryScript: stopSummaryScriptPath,
|
|
6906
7694
|
sessionStartScript: sessionStartScriptPath,
|
|
6907
7695
|
transcriptSyncScript: transcriptSyncScriptPath,
|
|
7696
|
+
subagentStartScript: subagentStartScriptPath,
|
|
7697
|
+
subagentStopScript: subagentStopScriptPath,
|
|
6908
7698
|
userPromptSubmitScript: userPromptSubmitScriptPath,
|
|
6909
7699
|
promptRouteScript: promptRouteScriptPath,
|
|
6910
7700
|
installScanScript: installScanScriptPath,
|
|
@@ -6928,7 +7718,7 @@ function resolveSynkroBundle() {
|
|
|
6928
7718
|
return null;
|
|
6929
7719
|
}
|
|
6930
7720
|
function writeConfigEnv(opts) {
|
|
6931
|
-
const credsPath =
|
|
7721
|
+
const credsPath = join19(SYNKRO_DIR11, "credentials.json");
|
|
6932
7722
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
6933
7723
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
6934
7724
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -6944,7 +7734,7 @@ function writeConfigEnv(opts) {
|
|
|
6944
7734
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
|
|
6945
7735
|
`SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
|
|
6946
7736
|
`SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
|
|
6947
|
-
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.
|
|
7737
|
+
`SYNKRO_VERSION=${shellQuoteSingle2("1.7.89")}`
|
|
6948
7738
|
];
|
|
6949
7739
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
|
|
6950
7740
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
|
|
@@ -6964,12 +7754,12 @@ function writeConfigEnv(opts) {
|
|
|
6964
7754
|
lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
|
|
6965
7755
|
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
6966
7756
|
lines.push("");
|
|
6967
|
-
|
|
6968
|
-
|
|
7757
|
+
writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
|
|
7758
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
6969
7759
|
}
|
|
6970
7760
|
function persistedTranscriptConsent(source) {
|
|
6971
7761
|
try {
|
|
6972
|
-
const env =
|
|
7762
|
+
const env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
6973
7763
|
const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
|
|
6974
7764
|
if (specific) return specific[1] === "yes";
|
|
6975
7765
|
if (source !== "CODEX") {
|
|
@@ -6992,7 +7782,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
6992
7782
|
assertGatewayAllowed(gatewayUrl);
|
|
6993
7783
|
let stored = "";
|
|
6994
7784
|
try {
|
|
6995
|
-
stored =
|
|
7785
|
+
stored = readFileSync21(CLOUD_JWT_PATH, "utf-8").trim();
|
|
6996
7786
|
} catch {
|
|
6997
7787
|
}
|
|
6998
7788
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -7008,7 +7798,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
7008
7798
|
throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
|
|
7009
7799
|
}
|
|
7010
7800
|
const { token } = await resp.json();
|
|
7011
|
-
|
|
7801
|
+
writeFileSync17(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
|
|
7012
7802
|
return token;
|
|
7013
7803
|
}
|
|
7014
7804
|
async function provisionCloudContainer(opts) {
|
|
@@ -7092,7 +7882,7 @@ async function provisionCloudContainer(opts) {
|
|
|
7092
7882
|
let cursorApiKey = "";
|
|
7093
7883
|
if (cursorWorkers > 0 || selectedKind === "cursor") {
|
|
7094
7884
|
try {
|
|
7095
|
-
cursorApiKey =
|
|
7885
|
+
cursorApiKey = readFileSync21(join19(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
|
|
7096
7886
|
} catch {
|
|
7097
7887
|
}
|
|
7098
7888
|
}
|
|
@@ -7278,7 +8068,7 @@ async function verifyCloudGrader(jwt2, requestedKind) {
|
|
|
7278
8068
|
function readPersistedDeployLocation() {
|
|
7279
8069
|
try {
|
|
7280
8070
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7281
|
-
const m =
|
|
8071
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
7282
8072
|
if (m?.[1] === "cloud") return "cloud";
|
|
7283
8073
|
}
|
|
7284
8074
|
} catch {
|
|
@@ -7287,7 +8077,7 @@ function readPersistedDeployLocation() {
|
|
|
7287
8077
|
}
|
|
7288
8078
|
function updateConfigEnvLocation(location) {
|
|
7289
8079
|
if (!existsSync22(CONFIG_PATH4)) return;
|
|
7290
|
-
let env =
|
|
8080
|
+
let env = readFileSync21(CONFIG_PATH4, "utf-8");
|
|
7291
8081
|
const set = (k, v) => {
|
|
7292
8082
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
7293
8083
|
const line = `${k}='${v}'`;
|
|
@@ -7295,14 +8085,14 @@ function updateConfigEnvLocation(location) {
|
|
|
7295
8085
|
};
|
|
7296
8086
|
set("SYNKRO_DEPLOY_LOCATION", location);
|
|
7297
8087
|
set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
|
|
7298
|
-
|
|
7299
|
-
|
|
8088
|
+
writeFileSync17(CONFIG_PATH4, env, "utf-8");
|
|
8089
|
+
chmodSync5(CONFIG_PATH4, 384);
|
|
7300
8090
|
}
|
|
7301
8091
|
async function applyMcpConfig(opts) {
|
|
7302
8092
|
if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
|
|
7303
8093
|
let mcpJwt2 = "";
|
|
7304
8094
|
try {
|
|
7305
|
-
mcpJwt2 =
|
|
8095
|
+
mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
7306
8096
|
} catch {
|
|
7307
8097
|
}
|
|
7308
8098
|
if (!mcpJwt2) {
|
|
@@ -7433,7 +8223,7 @@ function resolveDeploymentMode() {
|
|
|
7433
8223
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
7434
8224
|
try {
|
|
7435
8225
|
if (existsSync22(CONFIG_PATH4)) {
|
|
7436
|
-
const m =
|
|
8226
|
+
const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
7437
8227
|
const val = m?.[1]?.toLowerCase();
|
|
7438
8228
|
if (val === "bare-host" || val === "docker") return val;
|
|
7439
8229
|
}
|
|
@@ -7460,16 +8250,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7460
8250
|
meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
7461
8251
|
} catch {
|
|
7462
8252
|
}
|
|
7463
|
-
const claudeDir =
|
|
8253
|
+
const claudeDir = join19(homedir21(), ".claude");
|
|
7464
8254
|
try {
|
|
7465
|
-
const settings = JSON.parse(
|
|
8255
|
+
const settings = JSON.parse(readFileSync21(join19(claudeDir, "settings.json"), "utf-8"));
|
|
7466
8256
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
7467
8257
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
7468
8258
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
7469
8259
|
} catch {
|
|
7470
8260
|
}
|
|
7471
8261
|
try {
|
|
7472
|
-
const mcpCache = JSON.parse(
|
|
8262
|
+
const mcpCache = JSON.parse(readFileSync21(join19(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
7473
8263
|
const mcpNames = Object.keys(mcpCache);
|
|
7474
8264
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
7475
8265
|
} catch {
|
|
@@ -7481,10 +8271,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
7481
8271
|
} catch {
|
|
7482
8272
|
}
|
|
7483
8273
|
try {
|
|
7484
|
-
const sessionsDir =
|
|
8274
|
+
const sessionsDir = join19(claudeDir, "sessions");
|
|
7485
8275
|
const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
7486
8276
|
for (const f of files) {
|
|
7487
|
-
const s = JSON.parse(
|
|
8277
|
+
const s = JSON.parse(readFileSync21(join19(sessionsDir, f), "utf-8"));
|
|
7488
8278
|
if (s.version) {
|
|
7489
8279
|
meta.cc_version = meta.cc_version || s.version;
|
|
7490
8280
|
break;
|
|
@@ -7686,7 +8476,7 @@ async function installCommand(opts = {}) {
|
|
|
7686
8476
|
await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
|
|
7687
8477
|
emit("install", {
|
|
7688
8478
|
phase: "started",
|
|
7689
|
-
cli_version_to: "1.7.
|
|
8479
|
+
cli_version_to: "1.7.89",
|
|
7690
8480
|
agents_detected: agents.map((a) => a.kind),
|
|
7691
8481
|
with_github: false,
|
|
7692
8482
|
with_local_cc: false,
|
|
@@ -7698,9 +8488,9 @@ async function installCommand(opts = {}) {
|
|
|
7698
8488
|
const scripts = writeHookScripts();
|
|
7699
8489
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
7700
8490
|
for (const mode of ["edit", "bash"]) {
|
|
7701
|
-
const pidFile =
|
|
8491
|
+
const pidFile = join19(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
|
|
7702
8492
|
try {
|
|
7703
|
-
const pid = parseInt(
|
|
8493
|
+
const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
|
|
7704
8494
|
if (pid > 0) {
|
|
7705
8495
|
process.kill(pid, "SIGTERM");
|
|
7706
8496
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -7763,12 +8553,15 @@ async function installCommand(opts = {}) {
|
|
|
7763
8553
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
7764
8554
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
7765
8555
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
8556
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
7766
8557
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
7767
8558
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
7768
8559
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
7769
8560
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
7770
8561
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
7771
8562
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
8563
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
8564
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
7772
8565
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
7773
8566
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
7774
8567
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -7818,7 +8611,7 @@ async function installCommand(opts = {}) {
|
|
|
7818
8611
|
if (mintResp.ok) {
|
|
7819
8612
|
const minted = await mintResp.json();
|
|
7820
8613
|
mcpJwt2 = minted.token;
|
|
7821
|
-
|
|
8614
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
|
|
7822
8615
|
} else {
|
|
7823
8616
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
7824
8617
|
}
|
|
@@ -7846,7 +8639,7 @@ async function installCommand(opts = {}) {
|
|
|
7846
8639
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7847
8640
|
}
|
|
7848
8641
|
const minted = await mintResp.json();
|
|
7849
|
-
|
|
8642
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7850
8643
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7851
8644
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7852
8645
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7868,7 +8661,7 @@ async function installCommand(opts = {}) {
|
|
|
7868
8661
|
if (hasCursor && !opts.noMcp) {
|
|
7869
8662
|
try {
|
|
7870
8663
|
if (useLocalMcp) {
|
|
7871
|
-
const jwtPath =
|
|
8664
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7872
8665
|
if (!existsSync22(jwtPath)) {
|
|
7873
8666
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
7874
8667
|
method: "POST",
|
|
@@ -7877,7 +8670,7 @@ async function installCommand(opts = {}) {
|
|
|
7877
8670
|
});
|
|
7878
8671
|
if (mintResp.ok) {
|
|
7879
8672
|
const minted = await mintResp.json();
|
|
7880
|
-
|
|
8673
|
+
writeFileSync17(jwtPath, minted.token + "\n", { mode: 384 });
|
|
7881
8674
|
}
|
|
7882
8675
|
}
|
|
7883
8676
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
|
|
@@ -7897,7 +8690,7 @@ async function installCommand(opts = {}) {
|
|
|
7897
8690
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
7898
8691
|
}
|
|
7899
8692
|
const minted = await mintResp.json();
|
|
7900
|
-
|
|
8693
|
+
writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
7901
8694
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
7902
8695
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
7903
8696
|
console.log(` url: ${mcp.url}`);
|
|
@@ -7910,10 +8703,10 @@ async function installCommand(opts = {}) {
|
|
|
7910
8703
|
}
|
|
7911
8704
|
if (hasCodex && !opts.noMcp) {
|
|
7912
8705
|
try {
|
|
7913
|
-
const jwtPath =
|
|
8706
|
+
const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
|
|
7914
8707
|
let mcpJwt2 = "";
|
|
7915
8708
|
try {
|
|
7916
|
-
mcpJwt2 =
|
|
8709
|
+
mcpJwt2 = readFileSync21(jwtPath, "utf-8").trim();
|
|
7917
8710
|
} catch {
|
|
7918
8711
|
}
|
|
7919
8712
|
if (!mcpJwt2) {
|
|
@@ -7931,7 +8724,7 @@ async function installCommand(opts = {}) {
|
|
|
7931
8724
|
}
|
|
7932
8725
|
const minted = await mintResp.json();
|
|
7933
8726
|
mcpJwt2 = minted.token;
|
|
7934
|
-
|
|
8727
|
+
writeFileSync17(jwtPath, mcpJwt2 + "\n", { mode: 384 });
|
|
7935
8728
|
}
|
|
7936
8729
|
const mcp = installCodexMcpConfig({
|
|
7937
8730
|
gatewayUrl,
|
|
@@ -8043,14 +8836,15 @@ async function installCommand(opts = {}) {
|
|
|
8043
8836
|
}
|
|
8044
8837
|
console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
|
|
8045
8838
|
const connectedRepo = detectGitRepo2() || void 0;
|
|
8046
|
-
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
|
|
8839
|
+
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort, pglitePasswordPath } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
|
|
8047
8840
|
console.log(` \u2713 pulled ${image}`);
|
|
8048
|
-
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}
|
|
8841
|
+
console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
|
|
8842
|
+
console.log(` PGLite: postgresql://synkro@127.0.0.1:${hostPglitePort}/postgres (password: ${pglitePasswordPath})`);
|
|
8049
8843
|
console.log(" waiting for container to be ready...");
|
|
8050
8844
|
const ready = await waitForContainerReady(6e4);
|
|
8051
8845
|
if (ready) {
|
|
8052
8846
|
console.log(" \u2713 container ready");
|
|
8053
|
-
const mcpJwt2 =
|
|
8847
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8054
8848
|
try {
|
|
8055
8849
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
8056
8850
|
method: "POST",
|
|
@@ -8093,7 +8887,7 @@ async function installCommand(opts = {}) {
|
|
|
8093
8887
|
try {
|
|
8094
8888
|
let mcpToken = "";
|
|
8095
8889
|
try {
|
|
8096
|
-
mcpToken =
|
|
8890
|
+
mcpToken = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8097
8891
|
} catch {
|
|
8098
8892
|
}
|
|
8099
8893
|
if (mcpToken) {
|
|
@@ -8261,8 +9055,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8261
9055
|
try {
|
|
8262
9056
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8263
9057
|
if (!root) return;
|
|
8264
|
-
if (root ===
|
|
8265
|
-
const fp =
|
|
9058
|
+
if (root === homedir21()) return;
|
|
9059
|
+
const fp = join19(root, "synkro.toml");
|
|
8266
9060
|
let hasFile = false;
|
|
8267
9061
|
try {
|
|
8268
9062
|
hasFile = statSync2(fp).isFile();
|
|
@@ -8294,7 +9088,7 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8294
9088
|
"cve = true",
|
|
8295
9089
|
""
|
|
8296
9090
|
].join("\n");
|
|
8297
|
-
|
|
9091
|
+
writeFileSync17(fp, toml, "utf-8");
|
|
8298
9092
|
console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
|
|
8299
9093
|
} catch {
|
|
8300
9094
|
}
|
|
@@ -8302,12 +9096,12 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
8302
9096
|
function updateSynkroTomlLocation(location) {
|
|
8303
9097
|
try {
|
|
8304
9098
|
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 =
|
|
9099
|
+
if (!root || root === homedir21()) return;
|
|
9100
|
+
const fp = join19(root, "synkro.toml");
|
|
8307
9101
|
let txt = "";
|
|
8308
9102
|
try {
|
|
8309
9103
|
if (!statSync2(fp).isFile()) return;
|
|
8310
|
-
txt =
|
|
9104
|
+
txt = readFileSync21(fp, "utf-8");
|
|
8311
9105
|
} catch {
|
|
8312
9106
|
return;
|
|
8313
9107
|
}
|
|
@@ -8315,7 +9109,7 @@ function updateSynkroTomlLocation(location) {
|
|
|
8315
9109
|
if (!re.test(txt)) return;
|
|
8316
9110
|
const next = txt.replace(re, `$1"${location}"`);
|
|
8317
9111
|
if (next !== txt) {
|
|
8318
|
-
|
|
9112
|
+
writeFileSync17(fp, next, "utf-8");
|
|
8319
9113
|
console.log(` synkro.toml: [grader] location = "${location}"`);
|
|
8320
9114
|
}
|
|
8321
9115
|
} catch {
|
|
@@ -8325,9 +9119,9 @@ function readFullSynkroFile() {
|
|
|
8325
9119
|
try {
|
|
8326
9120
|
const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8327
9121
|
if (!root) return null;
|
|
8328
|
-
const fp =
|
|
9122
|
+
const fp = join19(root, "synkro.toml");
|
|
8329
9123
|
if (!existsSync22(fp)) return null;
|
|
8330
|
-
const parsed = parseSynkroToml2(
|
|
9124
|
+
const parsed = parseSynkroToml2(readFileSync21(fp, "utf-8"));
|
|
8331
9125
|
const valid = ["claude-code", "cursor", "codex"];
|
|
8332
9126
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
8333
9127
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -8367,7 +9161,7 @@ function reconcileHarness() {
|
|
|
8367
9161
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
8368
9162
|
const scripts = writeHookScripts();
|
|
8369
9163
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
8370
|
-
const ccSettings =
|
|
9164
|
+
const ccSettings = join19(homedir21(), ".claude", "settings.json");
|
|
8371
9165
|
if (wantCC) {
|
|
8372
9166
|
installCCHooks(ccSettings, {
|
|
8373
9167
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -8390,7 +9184,7 @@ function reconcileHarness() {
|
|
|
8390
9184
|
});
|
|
8391
9185
|
console.log(" \u2713 Claude Code hooks registered");
|
|
8392
9186
|
try {
|
|
8393
|
-
const mcpJwt2 =
|
|
9187
|
+
const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
|
|
8394
9188
|
if (mcpJwt2) {
|
|
8395
9189
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
|
|
8396
9190
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -8402,7 +9196,7 @@ function reconcileHarness() {
|
|
|
8402
9196
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
8403
9197
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
8404
9198
|
}
|
|
8405
|
-
const cursorHooks =
|
|
9199
|
+
const cursorHooks = join19(homedir21(), ".cursor", "hooks.json");
|
|
8406
9200
|
if (wantCursor) {
|
|
8407
9201
|
installCursorHooks(cursorHooks, {
|
|
8408
9202
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -8433,19 +9227,22 @@ function reconcileHarness() {
|
|
|
8433
9227
|
if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
|
|
8434
9228
|
if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
|
|
8435
9229
|
}
|
|
8436
|
-
const codexHooks =
|
|
9230
|
+
const codexHooks = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "hooks.json");
|
|
8437
9231
|
if (wantCodex) {
|
|
8438
9232
|
installCodexHooks(codexHooks, {
|
|
8439
9233
|
bashJudgeScriptPath: scripts.bashScript,
|
|
8440
9234
|
skillJudgeScriptPath: scripts.skillJudgeScript,
|
|
8441
9235
|
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
8442
9236
|
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
9237
|
+
editFollowupScriptPath: scripts.editFollowupScript,
|
|
8443
9238
|
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
8444
9239
|
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
8445
9240
|
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
8446
9241
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
8447
9242
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
8448
9243
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
9244
|
+
subagentStartScriptPath: scripts.subagentStartScript,
|
|
9245
|
+
subagentStopScriptPath: scripts.subagentStopScript,
|
|
8449
9246
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
8450
9247
|
promptRouteScriptPath: scripts.promptRouteScript,
|
|
8451
9248
|
installScanScriptPath: scripts.installScanScript,
|
|
@@ -8525,7 +9322,7 @@ async function syncSkillFiles() {
|
|
|
8525
9322
|
if (resolved.length === 0) return;
|
|
8526
9323
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
8527
9324
|
const tasks = resolved.map((fp) => {
|
|
8528
|
-
const content =
|
|
9325
|
+
const content = readFileSync21(fp, "utf-8");
|
|
8529
9326
|
const source = `skill:${fp.split("/").pop()}`;
|
|
8530
9327
|
if (!content.trim()) {
|
|
8531
9328
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -8550,11 +9347,11 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8550
9347
|
} catch {
|
|
8551
9348
|
}
|
|
8552
9349
|
};
|
|
8553
|
-
add(
|
|
8554
|
-
add(
|
|
9350
|
+
add(join19(homedir21(), ".claude", "skills"));
|
|
9351
|
+
add(join19(homedir21(), ".agents", "skills"));
|
|
8555
9352
|
if (repoRoot2) {
|
|
8556
|
-
add(
|
|
8557
|
-
add(
|
|
9353
|
+
add(join19(repoRoot2, ".claude", "skills"));
|
|
9354
|
+
add(join19(repoRoot2, ".agents", "skills"));
|
|
8558
9355
|
}
|
|
8559
9356
|
const out = [];
|
|
8560
9357
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8565,12 +9362,12 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8565
9362
|
try {
|
|
8566
9363
|
const st = statSync2(file);
|
|
8567
9364
|
if (!st.isFile() || st.size > 2e5) return;
|
|
8568
|
-
content =
|
|
9365
|
+
content = readFileSync21(file, "utf-8");
|
|
8569
9366
|
} catch {
|
|
8570
9367
|
return;
|
|
8571
9368
|
}
|
|
8572
9369
|
if (!content.trim()) return;
|
|
8573
|
-
const hash =
|
|
9370
|
+
const hash = createHash4("sha256").update(content).digest("hex");
|
|
8574
9371
|
if (seen.has(hash) || excludeHashes.has(hash)) return;
|
|
8575
9372
|
seen.add(hash);
|
|
8576
9373
|
const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));
|
|
@@ -8585,7 +9382,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8585
9382
|
}
|
|
8586
9383
|
for (const entry of entries) {
|
|
8587
9384
|
if (entry.startsWith(".")) continue;
|
|
8588
|
-
const full =
|
|
9385
|
+
const full = join19(root, entry);
|
|
8589
9386
|
let st;
|
|
8590
9387
|
try {
|
|
8591
9388
|
st = statSync2(full);
|
|
@@ -8593,7 +9390,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8593
9390
|
continue;
|
|
8594
9391
|
}
|
|
8595
9392
|
if (st.isDirectory()) {
|
|
8596
|
-
const skillMd =
|
|
9393
|
+
const skillMd = join19(full, "SKILL.md");
|
|
8597
9394
|
if (existsSync22(skillMd)) consider(skillMd, entry);
|
|
8598
9395
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
8599
9396
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
@@ -8603,7 +9400,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
|
|
|
8603
9400
|
return out;
|
|
8604
9401
|
}
|
|
8605
9402
|
function discoverySetHash(found) {
|
|
8606
|
-
return
|
|
9403
|
+
return createHash4("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
|
|
8607
9404
|
}
|
|
8608
9405
|
async function promptSkillDiscovery(found) {
|
|
8609
9406
|
if (!process.stdin.isTTY || found.length === 0) return [];
|
|
@@ -8643,7 +9440,7 @@ async function discoverAndIngestSkills() {
|
|
|
8643
9440
|
if (sf?.skills?.length) {
|
|
8644
9441
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
8645
9442
|
try {
|
|
8646
|
-
excludeHashes.add(
|
|
9443
|
+
excludeHashes.add(createHash4("sha256").update(readFileSync21(fp, "utf-8")).digest("hex"));
|
|
8647
9444
|
} catch {
|
|
8648
9445
|
}
|
|
8649
9446
|
}
|
|
@@ -8674,13 +9471,13 @@ async function discoverAndIngestSkills() {
|
|
|
8674
9471
|
const setHash = discoverySetHash(selectable);
|
|
8675
9472
|
let prev = "";
|
|
8676
9473
|
try {
|
|
8677
|
-
prev =
|
|
9474
|
+
prev = readFileSync21(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
8678
9475
|
} catch {
|
|
8679
9476
|
}
|
|
8680
9477
|
if (prev === setHash) return;
|
|
8681
9478
|
const picks = await promptSkillDiscovery(found);
|
|
8682
9479
|
try {
|
|
8683
|
-
|
|
9480
|
+
writeFileSync17(SKILLS_DISCOVERED_PATH, setHash);
|
|
8684
9481
|
} catch {
|
|
8685
9482
|
}
|
|
8686
9483
|
if (picks.length === 0) {
|
|
@@ -8719,9 +9516,9 @@ function ensureReachabilityGitHook() {
|
|
|
8719
9516
|
const root = run("git rev-parse --show-toplevel");
|
|
8720
9517
|
if (!root) return null;
|
|
8721
9518
|
let hooksDir = run("git config --get core.hooksPath");
|
|
8722
|
-
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir :
|
|
9519
|
+
hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join19(root, hooksDir) : join19(root, ".git", "hooks");
|
|
8723
9520
|
if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
|
|
8724
|
-
const hookPath =
|
|
9521
|
+
const hookPath = join19(hooksDir, "post-commit");
|
|
8725
9522
|
const resolvedBin = resolveSynkroBinPath();
|
|
8726
9523
|
const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
|
|
8727
9524
|
const START = "# >>> synkro reachability (managed) >>>";
|
|
@@ -8735,23 +9532,23 @@ function ensureReachabilityGitHook() {
|
|
|
8735
9532
|
].join("\n");
|
|
8736
9533
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8737
9534
|
if (!existsSync22(hookPath)) {
|
|
8738
|
-
|
|
9535
|
+
writeFileSync17(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
|
|
8739
9536
|
return "installed";
|
|
8740
9537
|
}
|
|
8741
|
-
let cur =
|
|
9538
|
+
let cur = readFileSync21(hookPath, "utf-8");
|
|
8742
9539
|
if (cur.includes(START)) {
|
|
8743
9540
|
cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
|
|
8744
|
-
|
|
9541
|
+
writeFileSync17(hookPath, cur);
|
|
8745
9542
|
try {
|
|
8746
|
-
|
|
9543
|
+
chmodSync5(hookPath, 493);
|
|
8747
9544
|
} catch {
|
|
8748
9545
|
}
|
|
8749
9546
|
return "updated";
|
|
8750
9547
|
}
|
|
8751
9548
|
const sep = cur.endsWith("\n") ? "" : "\n";
|
|
8752
|
-
|
|
9549
|
+
writeFileSync17(hookPath, cur + sep + "\n" + block + "\n");
|
|
8753
9550
|
try {
|
|
8754
|
-
|
|
9551
|
+
chmodSync5(hookPath, 493);
|
|
8755
9552
|
} catch {
|
|
8756
9553
|
}
|
|
8757
9554
|
return "updated";
|
|
@@ -8777,7 +9574,7 @@ function detectGitRepo2() {
|
|
|
8777
9574
|
function getClaudeProjectsFolder() {
|
|
8778
9575
|
const cwd = process.cwd();
|
|
8779
9576
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
8780
|
-
const projectsDir =
|
|
9577
|
+
const projectsDir = join19(homedir21(), ".claude", "projects", sanitized);
|
|
8781
9578
|
return existsSync22(projectsDir) ? projectsDir : null;
|
|
8782
9579
|
}
|
|
8783
9580
|
function extractSessionInsights(projectsDir) {
|
|
@@ -8785,9 +9582,9 @@ function extractSessionInsights(projectsDir) {
|
|
|
8785
9582
|
const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
8786
9583
|
for (const file of files) {
|
|
8787
9584
|
const sessionId = file.replace(".jsonl", "");
|
|
8788
|
-
const filePath =
|
|
9585
|
+
const filePath = join19(projectsDir, file);
|
|
8789
9586
|
try {
|
|
8790
|
-
const content =
|
|
9587
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8791
9588
|
const lines = content.split("\n").filter(Boolean);
|
|
8792
9589
|
for (let i = 0; i < lines.length; i++) {
|
|
8793
9590
|
try {
|
|
@@ -8863,7 +9660,7 @@ function extractTextContent(content) {
|
|
|
8863
9660
|
return "";
|
|
8864
9661
|
}
|
|
8865
9662
|
function getCodexTranscriptFiles(repo) {
|
|
8866
|
-
const sessionsDir =
|
|
9663
|
+
const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
|
|
8867
9664
|
if (!existsSync22(sessionsDir)) return [];
|
|
8868
9665
|
let relative = [];
|
|
8869
9666
|
try {
|
|
@@ -8871,9 +9668,9 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8871
9668
|
} catch {
|
|
8872
9669
|
return [];
|
|
8873
9670
|
}
|
|
8874
|
-
return relative.filter((p) => p.endsWith(".jsonl")).map((p) =>
|
|
9671
|
+
return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
|
|
8875
9672
|
try {
|
|
8876
|
-
const first =
|
|
9673
|
+
const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
|
|
8877
9674
|
const meta = JSON.parse(first);
|
|
8878
9675
|
const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
|
|
8879
9676
|
const root = resolve4(repo);
|
|
@@ -8883,38 +9680,45 @@ function getCodexTranscriptFiles(repo) {
|
|
|
8883
9680
|
}
|
|
8884
9681
|
});
|
|
8885
9682
|
}
|
|
9683
|
+
function isJsonSyntaxError(error) {
|
|
9684
|
+
return error instanceof SyntaxError;
|
|
9685
|
+
}
|
|
8886
9686
|
function parseCodexTranscriptFile(filePath) {
|
|
8887
|
-
const
|
|
9687
|
+
const transcript = readFileSync21(filePath, "utf-8");
|
|
9688
|
+
const lines = transcript.split("\n");
|
|
9689
|
+
const transcriptUsage = parseCodexTranscriptUsage(transcript);
|
|
8888
9690
|
let sessionId = "";
|
|
8889
9691
|
let model = "";
|
|
8890
|
-
const
|
|
8891
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9692
|
+
for (const line of lines) {
|
|
8892
9693
|
try {
|
|
8893
|
-
const entry = JSON.parse(
|
|
9694
|
+
const entry = JSON.parse(line);
|
|
8894
9695
|
if (entry.type === "session_meta") {
|
|
8895
9696
|
sessionId = String(entry.payload?.session_id || entry.payload?.id || sessionId);
|
|
8896
|
-
|
|
8897
|
-
}
|
|
8898
|
-
if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
9697
|
+
} else if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
|
|
8899
9698
|
model = entry.payload.model;
|
|
8900
|
-
continue;
|
|
8901
9699
|
}
|
|
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 {
|
|
9700
|
+
} catch (error) {
|
|
9701
|
+
if (!isJsonSyntaxError(error)) throw error;
|
|
8915
9702
|
}
|
|
8916
9703
|
}
|
|
8917
|
-
|
|
9704
|
+
const messages = parseCodexConversationMessages(transcript).map((message) => {
|
|
9705
|
+
const turnUsage = message.role === "assistant" ? transcriptUsage?.turnsByLine.get(message.lineIndex) : void 0;
|
|
9706
|
+
return {
|
|
9707
|
+
message_index: message.lineIndex,
|
|
9708
|
+
uuid: message.uuid,
|
|
9709
|
+
type: message.role,
|
|
9710
|
+
content: message.content,
|
|
9711
|
+
...message.role === "assistant" && (turnUsage?.model || model) ? { model: turnUsage?.model || model } : {},
|
|
9712
|
+
...turnUsage ? { usage: turnUsage.usage } : {},
|
|
9713
|
+
...message.timestamp ? { timestamp: message.timestamp } : {}
|
|
9714
|
+
};
|
|
9715
|
+
});
|
|
9716
|
+
return {
|
|
9717
|
+
sessionId,
|
|
9718
|
+
messages,
|
|
9719
|
+
model: transcriptUsage?.model || model,
|
|
9720
|
+
usage: transcriptUsage?.total
|
|
9721
|
+
};
|
|
8918
9722
|
}
|
|
8919
9723
|
async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
8920
9724
|
const files = getCodexTranscriptFiles(repo);
|
|
@@ -8931,7 +9735,15 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8931
9735
|
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
|
|
8932
9736
|
method: "POST",
|
|
8933
9737
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
|
|
8934
|
-
body: JSON.stringify({
|
|
9738
|
+
body: JSON.stringify({
|
|
9739
|
+
session_id: parsed.sessionId,
|
|
9740
|
+
repo,
|
|
9741
|
+
messages,
|
|
9742
|
+
session_usage: parsed.usage,
|
|
9743
|
+
model: parsed.model,
|
|
9744
|
+
harness: "codex",
|
|
9745
|
+
usage_cumulative: true
|
|
9746
|
+
}),
|
|
8935
9747
|
signal: AbortSignal.timeout(15e3)
|
|
8936
9748
|
});
|
|
8937
9749
|
if (resp.ok) {
|
|
@@ -8939,7 +9751,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8939
9751
|
totalSessions++;
|
|
8940
9752
|
totalMessages += result.ingested ?? messages.length;
|
|
8941
9753
|
}
|
|
8942
|
-
|
|
9754
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
8943
9755
|
} catch {
|
|
8944
9756
|
}
|
|
8945
9757
|
if ((i + 1) % 10 === 0 || i === files.length - 1) {
|
|
@@ -8953,14 +9765,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
8953
9765
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8954
9766
|
}
|
|
8955
9767
|
function getCursorTranscriptsDir() {
|
|
8956
|
-
const dir =
|
|
9768
|
+
const dir = join19(homedir21(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
8957
9769
|
return existsSync22(dir) ? dir : null;
|
|
8958
9770
|
}
|
|
8959
9771
|
function isSafeConvId(id) {
|
|
8960
9772
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
8961
9773
|
}
|
|
8962
9774
|
function parseCursorTranscriptFile(filePath) {
|
|
8963
|
-
const content =
|
|
9775
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
8964
9776
|
const lines = content.split("\n").filter(Boolean);
|
|
8965
9777
|
const messages = [];
|
|
8966
9778
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8992,7 +9804,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8992
9804
|
for (let i = 0; i < convDirs.length; i++) {
|
|
8993
9805
|
const convId = convDirs[i];
|
|
8994
9806
|
if (!isSafeConvId(convId)) continue;
|
|
8995
|
-
const filePath =
|
|
9807
|
+
const filePath = join19(dir, convId, `${convId}.jsonl`);
|
|
8996
9808
|
if (!existsSync22(filePath)) continue;
|
|
8997
9809
|
try {
|
|
8998
9810
|
const all = parseCursorTranscriptFile(filePath);
|
|
@@ -9015,8 +9827,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9015
9827
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
9016
9828
|
}
|
|
9017
9829
|
try {
|
|
9018
|
-
const lc =
|
|
9019
|
-
|
|
9830
|
+
const lc = readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
9831
|
+
writeFileSync17(join19(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
9020
9832
|
} catch {
|
|
9021
9833
|
}
|
|
9022
9834
|
}
|
|
@@ -9024,7 +9836,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9024
9836
|
return { sessions: totalSessions, messages: totalMessages };
|
|
9025
9837
|
}
|
|
9026
9838
|
function parseTranscriptFile(filePath) {
|
|
9027
|
-
const content =
|
|
9839
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
9028
9840
|
const lines = content.split("\n").filter(Boolean);
|
|
9029
9841
|
const messages = [];
|
|
9030
9842
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9072,7 +9884,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9072
9884
|
for (let i = 0; i < files.length; i++) {
|
|
9073
9885
|
const file = files[i];
|
|
9074
9886
|
const sessionId = file.replace(".jsonl", "");
|
|
9075
|
-
const filePath =
|
|
9887
|
+
const filePath = join19(projectsDir, file);
|
|
9076
9888
|
try {
|
|
9077
9889
|
const allMessages = parseTranscriptFile(filePath);
|
|
9078
9890
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -9094,9 +9906,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
9094
9906
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
9095
9907
|
}
|
|
9096
9908
|
try {
|
|
9097
|
-
const content =
|
|
9909
|
+
const content = readFileSync21(join19(projectsDir, file), "utf-8");
|
|
9098
9910
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9099
|
-
|
|
9911
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9100
9912
|
} catch {
|
|
9101
9913
|
}
|
|
9102
9914
|
}
|
|
@@ -9117,7 +9929,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9117
9929
|
const sessions = [];
|
|
9118
9930
|
for (const file of batch) {
|
|
9119
9931
|
const sessionId = file.replace(".jsonl", "");
|
|
9120
|
-
const filePath =
|
|
9932
|
+
const filePath = join19(projectsDir, file);
|
|
9121
9933
|
try {
|
|
9122
9934
|
const allMessages = parseTranscriptFile(filePath);
|
|
9123
9935
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -9146,11 +9958,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9146
9958
|
}
|
|
9147
9959
|
for (const file of batch) {
|
|
9148
9960
|
const sessionId = file.replace(".jsonl", "");
|
|
9149
|
-
const filePath =
|
|
9961
|
+
const filePath = join19(projectsDir, file);
|
|
9150
9962
|
try {
|
|
9151
|
-
const content =
|
|
9963
|
+
const content = readFileSync21(filePath, "utf-8");
|
|
9152
9964
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
9153
|
-
|
|
9965
|
+
writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
9154
9966
|
} catch {
|
|
9155
9967
|
}
|
|
9156
9968
|
}
|
|
@@ -9168,7 +9980,16 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9168
9980
|
try {
|
|
9169
9981
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9170
9982
|
const messages = parsed.messages.length > 500 ? parsed.messages.slice(-500) : parsed.messages;
|
|
9171
|
-
if (parsed.sessionId && messages.length > 0)
|
|
9983
|
+
if (parsed.sessionId && messages.length > 0) {
|
|
9984
|
+
sessions.push({
|
|
9985
|
+
cc_session_id: parsed.sessionId,
|
|
9986
|
+
messages,
|
|
9987
|
+
model: parsed.model,
|
|
9988
|
+
session_usage: parsed.usage,
|
|
9989
|
+
harness: "codex",
|
|
9990
|
+
usage_cumulative: true
|
|
9991
|
+
});
|
|
9992
|
+
}
|
|
9172
9993
|
} catch {
|
|
9173
9994
|
}
|
|
9174
9995
|
}
|
|
@@ -9190,7 +10011,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
9190
10011
|
try {
|
|
9191
10012
|
const parsed = parseCodexTranscriptFile(filePath);
|
|
9192
10013
|
if (parsed.sessionId) {
|
|
9193
|
-
|
|
10014
|
+
writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
|
|
9194
10015
|
}
|
|
9195
10016
|
} catch {
|
|
9196
10017
|
}
|
|
@@ -9221,10 +10042,12 @@ var init_install = __esm({
|
|
|
9221
10042
|
init_codexCloudSetup();
|
|
9222
10043
|
init_ptyShim();
|
|
9223
10044
|
init_graderSmoke();
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
10045
|
+
init_codexTranscriptUsage();
|
|
10046
|
+
init_codexTranscriptMessages();
|
|
10047
|
+
SYNKRO_DIR11 = join19(homedir21(), ".synkro");
|
|
10048
|
+
HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
|
|
10049
|
+
BIN_DIR = join19(SYNKRO_DIR11, "bin");
|
|
10050
|
+
CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
|
|
9228
10051
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
9229
10052
|
import { readFileSync } from 'node:fs';
|
|
9230
10053
|
import { homedir } from 'node:os';
|
|
@@ -9335,23 +10158,23 @@ rl.on('line', async (line) => {
|
|
|
9335
10158
|
}
|
|
9336
10159
|
});
|
|
9337
10160
|
`;
|
|
9338
|
-
OFFSETS_DIR =
|
|
9339
|
-
CLOUD_JWT_PATH =
|
|
9340
|
-
SKILLS_DISCOVERED_PATH =
|
|
10161
|
+
OFFSETS_DIR = join19(SYNKRO_DIR11, ".transcript-offsets");
|
|
10162
|
+
CLOUD_JWT_PATH = join19(SYNKRO_DIR11, ".cloud-jwt");
|
|
10163
|
+
SKILLS_DISCOVERED_PATH = join19(SYNKRO_DIR11, ".skills-discovered");
|
|
9341
10164
|
}
|
|
9342
10165
|
});
|
|
9343
10166
|
|
|
9344
10167
|
// cli/local-cc/install.ts
|
|
9345
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as
|
|
9346
|
-
import { join as
|
|
9347
|
-
import { homedir as
|
|
10168
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18, readFileSync as readFileSync22, chmodSync as chmodSync6, copyFileSync as copyFileSync2, renameSync as renameSync8, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
|
|
10169
|
+
import { join as join20 } from "path";
|
|
10170
|
+
import { homedir as homedir22 } from "os";
|
|
9348
10171
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
9349
10172
|
function writePluginFiles() {
|
|
9350
10173
|
for (const c of CHANNELS) {
|
|
9351
10174
|
mkdirSync16(c.sessionDir, { recursive: true });
|
|
9352
10175
|
mkdirSync16(c.pluginSettingsDir, { recursive: true });
|
|
9353
|
-
|
|
9354
|
-
|
|
10176
|
+
writeFileSync18(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
10177
|
+
writeFileSync18(
|
|
9355
10178
|
c.pluginSettingsPath,
|
|
9356
10179
|
JSON.stringify({
|
|
9357
10180
|
fastMode: true,
|
|
@@ -9366,8 +10189,8 @@ function writePluginFiles() {
|
|
|
9366
10189
|
}, null, 2) + "\n",
|
|
9367
10190
|
"utf-8"
|
|
9368
10191
|
);
|
|
9369
|
-
|
|
9370
|
-
|
|
10192
|
+
writeFileSync18(c.runScriptPath, c.runScriptSource, "utf-8");
|
|
10193
|
+
chmodSync6(c.runScriptPath, 493);
|
|
9371
10194
|
}
|
|
9372
10195
|
}
|
|
9373
10196
|
function runBunInstall() {
|
|
@@ -9388,7 +10211,7 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9388
10211
|
if (!existsSync23(CLAUDE_JSON_PATH)) {
|
|
9389
10212
|
return;
|
|
9390
10213
|
}
|
|
9391
|
-
const originalText =
|
|
10214
|
+
const originalText = readFileSync22(CLAUDE_JSON_PATH, "utf-8");
|
|
9392
10215
|
let parsed;
|
|
9393
10216
|
try {
|
|
9394
10217
|
parsed = JSON.parse(originalText);
|
|
@@ -9420,14 +10243,14 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
9420
10243
|
copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
9421
10244
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
9422
10245
|
try {
|
|
9423
|
-
|
|
10246
|
+
writeFileSync18(tmpPath, newText, "utf-8");
|
|
9424
10247
|
const fd = openSync2(tmpPath, "r");
|
|
9425
10248
|
try {
|
|
9426
10249
|
fsyncSync(fd);
|
|
9427
10250
|
} finally {
|
|
9428
10251
|
closeSync2(fd);
|
|
9429
10252
|
}
|
|
9430
|
-
|
|
10253
|
+
renameSync8(tmpPath, CLAUDE_JSON_PATH);
|
|
9431
10254
|
} catch (err) {
|
|
9432
10255
|
try {
|
|
9433
10256
|
unlinkSync8(tmpPath);
|
|
@@ -9453,7 +10276,7 @@ function writeProjectMcpJson() {
|
|
|
9453
10276
|
}
|
|
9454
10277
|
}
|
|
9455
10278
|
};
|
|
9456
|
-
|
|
10279
|
+
writeFileSync18(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
9457
10280
|
}
|
|
9458
10281
|
}
|
|
9459
10282
|
function patchClaudeJson() {
|
|
@@ -9530,42 +10353,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
9530
10353
|
var init_install2 = __esm({
|
|
9531
10354
|
"cli/local-cc/install.ts"() {
|
|
9532
10355
|
"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 =
|
|
10356
|
+
CLAUDE_JSON_BACKUP_PATH = join20(homedir22(), ".claude.json.synkro-bak");
|
|
10357
|
+
SESSION_DIR = join20(homedir22(), ".synkro", "cc_sessions");
|
|
10358
|
+
PLUGIN_PATH = join20(SESSION_DIR, "synkro-channel.ts");
|
|
10359
|
+
PLUGIN_PKG_PATH = join20(SESSION_DIR, "package.json");
|
|
10360
|
+
PLUGIN_SETTINGS_DIR = join20(SESSION_DIR, ".claude");
|
|
10361
|
+
PLUGIN_SETTINGS_PATH = join20(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
10362
|
+
PROJECT_MCP_PATH = join20(SESSION_DIR, ".mcp.json");
|
|
10363
|
+
CLAUDE_JSON_PATH = join20(homedir22(), ".claude.json");
|
|
10364
|
+
RUN_SCRIPT_PATH = join20(SESSION_DIR, "run-claude.sh");
|
|
9542
10365
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
9543
10366
|
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 =
|
|
10367
|
+
SESSION_DIR_2 = join20(homedir22(), ".synkro", "cc_sessions_2");
|
|
10368
|
+
PLUGIN_PATH_2 = join20(SESSION_DIR_2, "synkro-channel.ts");
|
|
10369
|
+
PLUGIN_PKG_PATH_2 = join20(SESSION_DIR_2, "package.json");
|
|
10370
|
+
PLUGIN_SETTINGS_DIR_2 = join20(SESSION_DIR_2, ".claude");
|
|
10371
|
+
PLUGIN_SETTINGS_PATH_2 = join20(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
10372
|
+
PROJECT_MCP_PATH_2 = join20(SESSION_DIR_2, ".mcp.json");
|
|
10373
|
+
RUN_SCRIPT_PATH_2 = join20(SESSION_DIR_2, "run-claude.sh");
|
|
9551
10374
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
9552
10375
|
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 =
|
|
10376
|
+
SESSION_DIR_3 = join20(homedir22(), ".synkro", "cc_sessions_3");
|
|
10377
|
+
PLUGIN_PATH_3 = join20(SESSION_DIR_3, "synkro-channel.ts");
|
|
10378
|
+
PLUGIN_PKG_PATH_3 = join20(SESSION_DIR_3, "package.json");
|
|
10379
|
+
PLUGIN_SETTINGS_DIR_3 = join20(SESSION_DIR_3, ".claude");
|
|
10380
|
+
PLUGIN_SETTINGS_PATH_3 = join20(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
10381
|
+
PROJECT_MCP_PATH_3 = join20(SESSION_DIR_3, ".mcp.json");
|
|
10382
|
+
RUN_SCRIPT_PATH_3 = join20(SESSION_DIR_3, "run-claude.sh");
|
|
9560
10383
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
9561
10384
|
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 =
|
|
10385
|
+
SESSION_DIR_4 = join20(homedir22(), ".synkro", "cc_sessions_4");
|
|
10386
|
+
PLUGIN_PATH_4 = join20(SESSION_DIR_4, "synkro-channel.ts");
|
|
10387
|
+
PLUGIN_PKG_PATH_4 = join20(SESSION_DIR_4, "package.json");
|
|
10388
|
+
PLUGIN_SETTINGS_DIR_4 = join20(SESSION_DIR_4, ".claude");
|
|
10389
|
+
PLUGIN_SETTINGS_PATH_4 = join20(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
10390
|
+
PROJECT_MCP_PATH_4 = join20(SESSION_DIR_4, ".mcp.json");
|
|
10391
|
+
RUN_SCRIPT_PATH_4 = join20(SESSION_DIR_4, "run-claude.sh");
|
|
9569
10392
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
9570
10393
|
CHANNEL_4_PORT = 8952;
|
|
9571
10394
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -9840,8 +10663,8 @@ __export(disconnect_exports, {
|
|
|
9840
10663
|
disconnectCommand: () => disconnectCommand
|
|
9841
10664
|
});
|
|
9842
10665
|
import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
|
|
9843
|
-
import { homedir as
|
|
9844
|
-
import { join as
|
|
10666
|
+
import { homedir as homedir23 } from "os";
|
|
10667
|
+
import { join as join21 } from "path";
|
|
9845
10668
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
9846
10669
|
import { createInterface as createInterface3 } from "readline";
|
|
9847
10670
|
async function tearDownLocalCC() {
|
|
@@ -9956,13 +10779,13 @@ async function disconnectCommand(args2 = [], opts = {}) {
|
|
|
9956
10779
|
console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
|
|
9957
10780
|
} else {
|
|
9958
10781
|
const keep = /* @__PURE__ */ new Set([
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
10782
|
+
join21(SYNKRO_DIR12, "pgdata"),
|
|
10783
|
+
join21(SYNKRO_DIR12, "pgdata-backups"),
|
|
10784
|
+
join21(SYNKRO_DIR12, ".transcript-offsets")
|
|
9962
10785
|
]);
|
|
9963
10786
|
const preserved = [];
|
|
9964
10787
|
for (const entry of readdirSync5(SYNKRO_DIR12)) {
|
|
9965
|
-
const full =
|
|
10788
|
+
const full = join21(SYNKRO_DIR12, entry);
|
|
9966
10789
|
if (keep.has(full)) {
|
|
9967
10790
|
preserved.push(entry);
|
|
9968
10791
|
continue;
|
|
@@ -10000,14 +10823,14 @@ var init_disconnect = __esm({
|
|
|
10000
10823
|
init_dockerInstall();
|
|
10001
10824
|
init_macKeychain();
|
|
10002
10825
|
init_telemetry();
|
|
10003
|
-
SYNKRO_DIR12 =
|
|
10826
|
+
SYNKRO_DIR12 = join21(homedir23(), ".synkro");
|
|
10004
10827
|
}
|
|
10005
10828
|
});
|
|
10006
10829
|
|
|
10007
10830
|
// 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
|
|
10831
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync23, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
|
|
10832
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
10833
|
+
import { homedir as homedir24 } from "os";
|
|
10011
10834
|
function truncate(s, max = PREVIEW_MAX) {
|
|
10012
10835
|
if (s.length <= max) return s;
|
|
10013
10836
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -10047,7 +10870,7 @@ function readRecentTurns(n = 20) {
|
|
|
10047
10870
|
try {
|
|
10048
10871
|
const size = statSync3(TURN_LOG_PATH).size;
|
|
10049
10872
|
if (size === 0) return [];
|
|
10050
|
-
const text =
|
|
10873
|
+
const text = readFileSync23(TURN_LOG_PATH, "utf-8");
|
|
10051
10874
|
const lines = text.split("\n").filter(Boolean);
|
|
10052
10875
|
const lastN = lines.slice(-n).reverse();
|
|
10053
10876
|
return lastN.map((line) => {
|
|
@@ -10126,7 +10949,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
10126
10949
|
var init_turnLog = __esm({
|
|
10127
10950
|
"cli/local-cc/turnLog.ts"() {
|
|
10128
10951
|
"use strict";
|
|
10129
|
-
TURN_LOG_PATH =
|
|
10952
|
+
TURN_LOG_PATH = join22(homedir24(), ".synkro", "cc_sessions", "turns.log");
|
|
10130
10953
|
PREVIEW_MAX = 400;
|
|
10131
10954
|
}
|
|
10132
10955
|
});
|
|
@@ -10280,8 +11103,8 @@ __export(scanPr_exports, {
|
|
|
10280
11103
|
scanPrCommand: () => scanPrCommand
|
|
10281
11104
|
});
|
|
10282
11105
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
10283
|
-
import { readFileSync as
|
|
10284
|
-
import { join as
|
|
11106
|
+
import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
|
|
11107
|
+
import { join as join23 } from "path";
|
|
10285
11108
|
function parseMatchSpec(condition) {
|
|
10286
11109
|
if (!condition.startsWith("match_spec:")) return null;
|
|
10287
11110
|
try {
|
|
@@ -10760,10 +11583,10 @@ function shouldFail(findings, threshold) {
|
|
|
10760
11583
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
10761
11584
|
}
|
|
10762
11585
|
function readRepoDeps() {
|
|
10763
|
-
const pkgPath =
|
|
11586
|
+
const pkgPath = join23(process.cwd(), "package.json");
|
|
10764
11587
|
if (!existsSync26(pkgPath)) return {};
|
|
10765
11588
|
try {
|
|
10766
|
-
const pkg = JSON.parse(
|
|
11589
|
+
const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
|
|
10767
11590
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
10768
11591
|
} catch {
|
|
10769
11592
|
return {};
|
|
@@ -11007,15 +11830,15 @@ var routeDecide_exports = {};
|
|
|
11007
11830
|
__export(routeDecide_exports, {
|
|
11008
11831
|
routeDecide: () => routeDecide
|
|
11009
11832
|
});
|
|
11010
|
-
import { readFileSync as
|
|
11011
|
-
import { homedir as
|
|
11012
|
-
import { join as
|
|
11833
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
|
|
11834
|
+
import { homedir as homedir25 } from "os";
|
|
11835
|
+
import { join as join24 } from "path";
|
|
11013
11836
|
function safeSid(sid) {
|
|
11014
11837
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
11015
11838
|
}
|
|
11016
11839
|
function loadMcpJwt() {
|
|
11017
11840
|
try {
|
|
11018
|
-
return
|
|
11841
|
+
return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
|
|
11019
11842
|
} catch {
|
|
11020
11843
|
return "";
|
|
11021
11844
|
}
|
|
@@ -11025,7 +11848,7 @@ async function routeDecide(sessionId) {
|
|
|
11025
11848
|
const sid = safeSid(sessionId);
|
|
11026
11849
|
let prompt = "";
|
|
11027
11850
|
try {
|
|
11028
|
-
const rec = JSON.parse(
|
|
11851
|
+
const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
|
|
11029
11852
|
prompt = String(rec.last_prompt || "").trim();
|
|
11030
11853
|
} catch {
|
|
11031
11854
|
return;
|
|
@@ -11036,7 +11859,7 @@ async function routeDecide(sessionId) {
|
|
|
11036
11859
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
|
|
11037
11860
|
method: "POST",
|
|
11038
11861
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
|
|
11039
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
11862
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
11040
11863
|
// First call may spin up the route lane (a haiku worker boot), so allow headroom.
|
|
11041
11864
|
signal: AbortSignal.timeout(12e4)
|
|
11042
11865
|
});
|
|
@@ -11048,19 +11871,19 @@ async function routeDecide(sessionId) {
|
|
|
11048
11871
|
return;
|
|
11049
11872
|
}
|
|
11050
11873
|
if (!model || !VALID_MODELS.has(model)) return;
|
|
11051
|
-
const lastFile =
|
|
11874
|
+
const lastFile = join24(PTY_DIR, "route-last-" + sid);
|
|
11052
11875
|
let last = "";
|
|
11053
11876
|
try {
|
|
11054
|
-
last =
|
|
11877
|
+
last = readFileSync25(lastFile, "utf-8").trim();
|
|
11055
11878
|
} catch {
|
|
11056
11879
|
}
|
|
11057
11880
|
if (model === last) return;
|
|
11058
11881
|
try {
|
|
11059
|
-
|
|
11882
|
+
writeFileSync19(lastFile, model);
|
|
11060
11883
|
} catch {
|
|
11061
11884
|
}
|
|
11062
11885
|
try {
|
|
11063
|
-
|
|
11886
|
+
writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
|
|
11064
11887
|
} catch {
|
|
11065
11888
|
}
|
|
11066
11889
|
}
|
|
@@ -11068,9 +11891,9 @@ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
|
|
|
11068
11891
|
var init_routeDecide = __esm({
|
|
11069
11892
|
"cli/local-cc/routeDecide.ts"() {
|
|
11070
11893
|
"use strict";
|
|
11071
|
-
SYNKRO_DIR13 =
|
|
11072
|
-
PTY_DIR =
|
|
11073
|
-
SESSIONS_DIR2 =
|
|
11894
|
+
SYNKRO_DIR13 = join24(homedir25(), ".synkro");
|
|
11895
|
+
PTY_DIR = join24(SYNKRO_DIR13, "pty");
|
|
11896
|
+
SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
|
|
11074
11897
|
VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11075
11898
|
GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11076
11899
|
}
|
|
@@ -11081,9 +11904,9 @@ var routeOrchestrate_exports = {};
|
|
|
11081
11904
|
__export(routeOrchestrate_exports, {
|
|
11082
11905
|
routeAndResubmit: () => routeAndResubmit
|
|
11083
11906
|
});
|
|
11084
|
-
import { readFileSync as
|
|
11085
|
-
import { homedir as
|
|
11086
|
-
import { join as
|
|
11907
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
11908
|
+
import { homedir as homedir26 } from "os";
|
|
11909
|
+
import { join as join25 } from "path";
|
|
11087
11910
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
11088
11911
|
function safeSid2(sid) {
|
|
11089
11912
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -11093,7 +11916,7 @@ function safeSession(s) {
|
|
|
11093
11916
|
}
|
|
11094
11917
|
function loadMcpJwt2() {
|
|
11095
11918
|
try {
|
|
11096
|
-
return
|
|
11919
|
+
return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
|
|
11097
11920
|
} catch {
|
|
11098
11921
|
return "";
|
|
11099
11922
|
}
|
|
@@ -11103,7 +11926,7 @@ async function classifyTask(prompt) {
|
|
|
11103
11926
|
const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
|
|
11104
11927
|
method: "POST",
|
|
11105
11928
|
headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
|
|
11106
|
-
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt,
|
|
11929
|
+
body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
|
|
11107
11930
|
signal: AbortSignal.timeout(6e4)
|
|
11108
11931
|
});
|
|
11109
11932
|
if (!resp.ok) return null;
|
|
@@ -11119,7 +11942,7 @@ async function classifyTask(prompt) {
|
|
|
11119
11942
|
}
|
|
11120
11943
|
function lastRoutedModel(sid) {
|
|
11121
11944
|
try {
|
|
11122
|
-
const v =
|
|
11945
|
+
const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
|
|
11123
11946
|
return VALID_MODELS2.has(v) ? v : "";
|
|
11124
11947
|
} catch {
|
|
11125
11948
|
return "";
|
|
@@ -11129,12 +11952,12 @@ function resolveSession(sid, tmuxSession) {
|
|
|
11129
11952
|
const candidates = [];
|
|
11130
11953
|
if (tmuxSession) candidates.push(tmuxSession);
|
|
11131
11954
|
try {
|
|
11132
|
-
const rec = JSON.parse(
|
|
11955
|
+
const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
|
|
11133
11956
|
if (rec.tmux_session) candidates.push(rec.tmux_session);
|
|
11134
11957
|
} catch {
|
|
11135
11958
|
}
|
|
11136
11959
|
try {
|
|
11137
|
-
candidates.push(
|
|
11960
|
+
candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
|
|
11138
11961
|
} catch {
|
|
11139
11962
|
}
|
|
11140
11963
|
for (const c of candidates) if (c && safeSession(c)) return c;
|
|
@@ -11169,12 +11992,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
|
|
|
11169
11992
|
}
|
|
11170
11993
|
await wait(500);
|
|
11171
11994
|
try {
|
|
11172
|
-
|
|
11995
|
+
writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
|
|
11173
11996
|
} catch {
|
|
11174
11997
|
}
|
|
11175
11998
|
}
|
|
11176
11999
|
try {
|
|
11177
|
-
|
|
12000
|
+
writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
|
|
11178
12001
|
} catch {
|
|
11179
12002
|
}
|
|
11180
12003
|
sk("C-u");
|
|
@@ -11188,10 +12011,10 @@ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2,
|
|
|
11188
12011
|
var init_routeOrchestrate = __esm({
|
|
11189
12012
|
"cli/local-cc/routeOrchestrate.ts"() {
|
|
11190
12013
|
"use strict";
|
|
11191
|
-
SYNKRO_DIR14 =
|
|
11192
|
-
PTY_DIR2 =
|
|
11193
|
-
SESSIONS_DIR3 =
|
|
11194
|
-
ACTIVE_SESSION_FILE2 =
|
|
12014
|
+
SYNKRO_DIR14 = join25(homedir26(), ".synkro");
|
|
12015
|
+
PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
|
|
12016
|
+
SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
|
|
12017
|
+
ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
|
|
11195
12018
|
VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
|
|
11196
12019
|
GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
|
|
11197
12020
|
wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -11203,14 +12026,14 @@ var routingToggle_exports = {};
|
|
|
11203
12026
|
__export(routingToggle_exports, {
|
|
11204
12027
|
routingCommand: () => routingCommand
|
|
11205
12028
|
});
|
|
11206
|
-
import { writeFileSync as
|
|
11207
|
-
import { homedir as
|
|
11208
|
-
import { join as
|
|
12029
|
+
import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
|
|
12030
|
+
import { homedir as homedir27 } from "os";
|
|
12031
|
+
import { join as join26 } from "path";
|
|
11209
12032
|
function safeSid3(sid) {
|
|
11210
12033
|
return sid.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
11211
12034
|
}
|
|
11212
12035
|
function markerFor(session) {
|
|
11213
|
-
return session ?
|
|
12036
|
+
return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
|
|
11214
12037
|
}
|
|
11215
12038
|
function routingCommand(args2) {
|
|
11216
12039
|
const sub = (args2[0] || "status").trim();
|
|
@@ -11219,7 +12042,7 @@ function routingCommand(args2) {
|
|
|
11219
12042
|
if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
|
|
11220
12043
|
if (sub === "on") {
|
|
11221
12044
|
try {
|
|
11222
|
-
|
|
12045
|
+
writeFileSync21(markerFor(session), "1");
|
|
11223
12046
|
} catch (e) {
|
|
11224
12047
|
console.error("routing on failed:", String(e));
|
|
11225
12048
|
return;
|
|
@@ -11242,7 +12065,7 @@ function routingCommand(args2) {
|
|
|
11242
12065
|
for (const f of safeList()) {
|
|
11243
12066
|
if (f === "routing-on" || f.startsWith("routing-on-")) {
|
|
11244
12067
|
try {
|
|
11245
|
-
unlinkSync9(
|
|
12068
|
+
unlinkSync9(join26(PTY_DIR3, f));
|
|
11246
12069
|
removed++;
|
|
11247
12070
|
} catch {
|
|
11248
12071
|
}
|
|
@@ -11274,14 +12097,14 @@ var PTY_DIR3;
|
|
|
11274
12097
|
var init_routingToggle = __esm({
|
|
11275
12098
|
"cli/local-cc/routingToggle.ts"() {
|
|
11276
12099
|
"use strict";
|
|
11277
|
-
PTY_DIR3 =
|
|
12100
|
+
PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
|
|
11278
12101
|
}
|
|
11279
12102
|
});
|
|
11280
12103
|
|
|
11281
12104
|
// cli/local-cc/pueue.ts
|
|
11282
12105
|
import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
|
|
11283
|
-
import { homedir as
|
|
11284
|
-
import { join as
|
|
12106
|
+
import { homedir as homedir28 } from "os";
|
|
12107
|
+
import { join as join27 } from "path";
|
|
11285
12108
|
import { connect as connect2 } from "net";
|
|
11286
12109
|
function pueueAvailable() {
|
|
11287
12110
|
const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
|
|
@@ -11347,7 +12170,7 @@ function startTask(opts = {}) {
|
|
|
11347
12170
|
spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
11348
12171
|
existing = findTask(ch);
|
|
11349
12172
|
}
|
|
11350
|
-
const runScript =
|
|
12173
|
+
const runScript = join27(cwd, "run-claude.sh");
|
|
11351
12174
|
const args2 = [
|
|
11352
12175
|
"add",
|
|
11353
12176
|
"--label",
|
|
@@ -11477,12 +12300,12 @@ var init_pueue = __esm({
|
|
|
11477
12300
|
"use strict";
|
|
11478
12301
|
TASK_LABEL = "synkro-local-cc";
|
|
11479
12302
|
TMUX_SESSION = "synkro-local-cc";
|
|
11480
|
-
SESSION_DIR2 =
|
|
12303
|
+
SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
|
|
11481
12304
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
11482
12305
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
11483
|
-
SESSION_DIR_22 =
|
|
11484
|
-
SESSION_DIR_32 =
|
|
11485
|
-
SESSION_DIR_42 =
|
|
12306
|
+
SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
|
|
12307
|
+
SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
|
|
12308
|
+
SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
|
|
11486
12309
|
PueueError = class extends Error {
|
|
11487
12310
|
constructor(message, cause) {
|
|
11488
12311
|
super(message);
|
|
@@ -11497,13 +12320,13 @@ var init_pueue = __esm({
|
|
|
11497
12320
|
});
|
|
11498
12321
|
|
|
11499
12322
|
// cli/local-cc/settings.ts
|
|
11500
|
-
import { existsSync as existsSync28, readFileSync as
|
|
11501
|
-
import { homedir as
|
|
11502
|
-
import { join as
|
|
12323
|
+
import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
|
|
12324
|
+
import { homedir as homedir29 } from "os";
|
|
12325
|
+
import { join as join28 } from "path";
|
|
11503
12326
|
function isLocalCCEnabled() {
|
|
11504
12327
|
if (!existsSync28(CONFIG_PATH5)) return false;
|
|
11505
12328
|
try {
|
|
11506
|
-
const content =
|
|
12329
|
+
const content = readFileSync27(CONFIG_PATH5, "utf-8");
|
|
11507
12330
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
11508
12331
|
return match?.[1] === "yes";
|
|
11509
12332
|
} catch {
|
|
@@ -11514,7 +12337,7 @@ var CONFIG_PATH5;
|
|
|
11514
12337
|
var init_settings = __esm({
|
|
11515
12338
|
"cli/local-cc/settings.ts"() {
|
|
11516
12339
|
"use strict";
|
|
11517
|
-
CONFIG_PATH5 =
|
|
12340
|
+
CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
|
|
11518
12341
|
}
|
|
11519
12342
|
});
|
|
11520
12343
|
|
|
@@ -11524,10 +12347,10 @@ __export(localCc_exports, {
|
|
|
11524
12347
|
localCcCommand: () => localCcCommand
|
|
11525
12348
|
});
|
|
11526
12349
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
11527
|
-
import { homedir as
|
|
11528
|
-
import { join as
|
|
12350
|
+
import { homedir as homedir30 } from "os";
|
|
12351
|
+
import { join as join29 } from "path";
|
|
11529
12352
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
11530
|
-
import { existsSync as existsSync29, readFileSync as
|
|
12353
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
11531
12354
|
function deploymentMode() {
|
|
11532
12355
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
11533
12356
|
if (env === "docker") return "docker";
|
|
@@ -11634,14 +12457,14 @@ TROUBLESHOOTING
|
|
|
11634
12457
|
}
|
|
11635
12458
|
function readGatewayUrl() {
|
|
11636
12459
|
if (existsSync29(CONFIG_PATH6)) {
|
|
11637
|
-
const m =
|
|
12460
|
+
const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
11638
12461
|
if (m) return m[1];
|
|
11639
12462
|
}
|
|
11640
12463
|
return "https://api.synkro.sh";
|
|
11641
12464
|
}
|
|
11642
12465
|
function updateLocalInferenceFlag(enabled) {
|
|
11643
12466
|
if (!existsSync29(CONFIG_PATH6)) return;
|
|
11644
|
-
let content =
|
|
12467
|
+
let content = readFileSync28(CONFIG_PATH6, "utf-8");
|
|
11645
12468
|
const flag = enabled ? "yes" : "no";
|
|
11646
12469
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
11647
12470
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -11650,7 +12473,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
11650
12473
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
11651
12474
|
`;
|
|
11652
12475
|
}
|
|
11653
|
-
|
|
12476
|
+
writeFileSync22(CONFIG_PATH6, content, "utf-8");
|
|
11654
12477
|
}
|
|
11655
12478
|
async function setServerGradingProvider(provider) {
|
|
11656
12479
|
await ensureValidToken();
|
|
@@ -11679,7 +12502,7 @@ async function cmdStatus() {
|
|
|
11679
12502
|
} else {
|
|
11680
12503
|
console.log(`synkro-server container: running (${status.image})`);
|
|
11681
12504
|
try {
|
|
11682
|
-
const r = await fetch(
|
|
12505
|
+
const r = await fetch(status.healthz, { signal: AbortSignal.timeout(3e3) });
|
|
11683
12506
|
console.log(`Health probe: ${r.ok ? "ok" : `HTTP ${r.status}`}`);
|
|
11684
12507
|
} catch (err) {
|
|
11685
12508
|
console.log(`Health probe: ${err.message}`);
|
|
@@ -12084,8 +12907,8 @@ var init_localCc = __esm({
|
|
|
12084
12907
|
init_install();
|
|
12085
12908
|
init_client2();
|
|
12086
12909
|
init_stub();
|
|
12087
|
-
SYNKRO_CONFIG_PATH =
|
|
12088
|
-
CONFIG_PATH6 =
|
|
12910
|
+
SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
|
|
12911
|
+
CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
|
|
12089
12912
|
}
|
|
12090
12913
|
});
|
|
12091
12914
|
|
|
@@ -12094,14 +12917,14 @@ var import_exports = {};
|
|
|
12094
12917
|
__export(import_exports, {
|
|
12095
12918
|
importCommand: () => importCommand
|
|
12096
12919
|
});
|
|
12097
|
-
import { existsSync as existsSync30, readFileSync as
|
|
12098
|
-
import { homedir as
|
|
12099
|
-
import { join as
|
|
12920
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
|
|
12921
|
+
import { homedir as homedir31 } from "os";
|
|
12922
|
+
import { join as join30 } from "path";
|
|
12100
12923
|
import { execSync as execSync6 } from "child_process";
|
|
12101
12924
|
import { createInterface as createInterface4 } from "readline";
|
|
12102
12925
|
function readMcpJwt() {
|
|
12103
12926
|
try {
|
|
12104
|
-
return
|
|
12927
|
+
return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
|
|
12105
12928
|
} catch {
|
|
12106
12929
|
return "";
|
|
12107
12930
|
}
|
|
@@ -12109,7 +12932,7 @@ function readMcpJwt() {
|
|
|
12109
12932
|
function readConfigEnv2() {
|
|
12110
12933
|
const out = {};
|
|
12111
12934
|
try {
|
|
12112
|
-
for (const line of
|
|
12935
|
+
for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
|
|
12113
12936
|
const t = line.trim();
|
|
12114
12937
|
if (!t || t.startsWith("#")) continue;
|
|
12115
12938
|
const eq = t.indexOf("=");
|
|
@@ -12121,7 +12944,7 @@ function readConfigEnv2() {
|
|
|
12121
12944
|
}
|
|
12122
12945
|
function projectsFolder() {
|
|
12123
12946
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
12124
|
-
const dir =
|
|
12947
|
+
const dir = join30(homedir31(), ".claude", "projects", sanitized);
|
|
12125
12948
|
return existsSync30(dir) ? dir : null;
|
|
12126
12949
|
}
|
|
12127
12950
|
function repoName() {
|
|
@@ -12161,7 +12984,7 @@ function extractToolResultText(content, e) {
|
|
|
12161
12984
|
return t;
|
|
12162
12985
|
}
|
|
12163
12986
|
function parseSession(filePath, sessionId) {
|
|
12164
|
-
const lines =
|
|
12987
|
+
const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
|
|
12165
12988
|
const messages = [];
|
|
12166
12989
|
const actions = [];
|
|
12167
12990
|
let step = 0;
|
|
@@ -12241,7 +13064,7 @@ async function importCommand() {
|
|
|
12241
13064
|
return;
|
|
12242
13065
|
}
|
|
12243
13066
|
}
|
|
12244
|
-
const sessions = files.map((f) => parseSession(
|
|
13067
|
+
const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
12245
13068
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
12246
13069
|
let ok = 0, fail = 0;
|
|
12247
13070
|
if (isCloud) {
|
|
@@ -12316,7 +13139,7 @@ var init_import = __esm({
|
|
|
12316
13139
|
"cli/commands/import.ts"() {
|
|
12317
13140
|
"use strict";
|
|
12318
13141
|
init_stub();
|
|
12319
|
-
CONFIG_PATH7 =
|
|
13142
|
+
CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
|
|
12320
13143
|
}
|
|
12321
13144
|
});
|
|
12322
13145
|
|
|
@@ -12358,10 +13181,10 @@ var init_packVerify = __esm({
|
|
|
12358
13181
|
});
|
|
12359
13182
|
|
|
12360
13183
|
// cli/installer/lockfile.ts
|
|
12361
|
-
import { existsSync as existsSync31, readFileSync as
|
|
12362
|
-
import { join as
|
|
13184
|
+
import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
|
|
13185
|
+
import { join as join31 } from "path";
|
|
12363
13186
|
function lockPath(repoRoot2) {
|
|
12364
|
-
return
|
|
13187
|
+
return join31(repoRoot2, LOCK_FILE);
|
|
12365
13188
|
}
|
|
12366
13189
|
function writeLockfile(repoRoot2, entries) {
|
|
12367
13190
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -12379,7 +13202,7 @@ function writeLockfile(repoRoot2, entries) {
|
|
|
12379
13202
|
""
|
|
12380
13203
|
])
|
|
12381
13204
|
].join("\n");
|
|
12382
|
-
|
|
13205
|
+
writeFileSync23(lockPath(repoRoot2), body, "utf-8");
|
|
12383
13206
|
}
|
|
12384
13207
|
var LOCK_FILE;
|
|
12385
13208
|
var init_lockfile = __esm({
|
|
@@ -12394,9 +13217,9 @@ var sync_exports = {};
|
|
|
12394
13217
|
__export(sync_exports, {
|
|
12395
13218
|
syncCommand: () => syncCommand
|
|
12396
13219
|
});
|
|
12397
|
-
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as
|
|
12398
|
-
import { homedir as
|
|
12399
|
-
import { join as
|
|
13220
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13221
|
+
import { homedir as homedir32 } from "os";
|
|
13222
|
+
import { join as join32 } from "path";
|
|
12400
13223
|
function cacheKey(ref, version) {
|
|
12401
13224
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
12402
13225
|
}
|
|
@@ -12427,7 +13250,7 @@ async function syncCommand(_args = []) {
|
|
|
12427
13250
|
}
|
|
12428
13251
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
12429
13252
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
12430
|
-
const cacheDir =
|
|
13253
|
+
const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
|
|
12431
13254
|
if (!cloud) mkdirSync18(cacheDir, { recursive: true });
|
|
12432
13255
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
12433
13256
|
const lock = [];
|
|
@@ -12456,7 +13279,7 @@ async function syncCommand(_args = []) {
|
|
|
12456
13279
|
if (!cloud) {
|
|
12457
13280
|
const fname = cacheKey(ref, data.version);
|
|
12458
13281
|
keptCacheFiles.add(fname);
|
|
12459
|
-
|
|
13282
|
+
writeFileSync24(join32(cacheDir, fname), JSON.stringify({
|
|
12460
13283
|
ref,
|
|
12461
13284
|
version: data.version,
|
|
12462
13285
|
digest: data.digest,
|
|
@@ -12472,7 +13295,7 @@ async function syncCommand(_args = []) {
|
|
|
12472
13295
|
for (const f of readdirSync8(cacheDir)) {
|
|
12473
13296
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
12474
13297
|
try {
|
|
12475
|
-
rmSync4(
|
|
13298
|
+
rmSync4(join32(cacheDir, f));
|
|
12476
13299
|
} catch {
|
|
12477
13300
|
}
|
|
12478
13301
|
}
|
|
@@ -12501,13 +13324,13 @@ var whoami_exports = {};
|
|
|
12501
13324
|
__export(whoami_exports, {
|
|
12502
13325
|
whoamiCommand: () => whoamiCommand
|
|
12503
13326
|
});
|
|
12504
|
-
import { readFileSync as
|
|
12505
|
-
import { join as
|
|
12506
|
-
import { homedir as
|
|
13327
|
+
import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
|
|
13328
|
+
import { join as join33 } from "path";
|
|
13329
|
+
import { homedir as homedir33 } from "os";
|
|
12507
13330
|
function readConfigEnv3() {
|
|
12508
13331
|
if (!existsSync33(CONFIG_PATH8)) return {};
|
|
12509
13332
|
const out = {};
|
|
12510
|
-
for (const line of
|
|
13333
|
+
for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
|
|
12511
13334
|
const t = line.trim();
|
|
12512
13335
|
if (!t || t.startsWith("#")) continue;
|
|
12513
13336
|
const eq = t.indexOf("=");
|
|
@@ -12518,7 +13341,7 @@ function readConfigEnv3() {
|
|
|
12518
13341
|
function jwtStatus() {
|
|
12519
13342
|
try {
|
|
12520
13343
|
if (!existsSync33(JWT_PATH2)) return { status: "none" };
|
|
12521
|
-
const jwt2 =
|
|
13344
|
+
const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
|
|
12522
13345
|
if (!jwt2) return { status: "none" };
|
|
12523
13346
|
const payload = jwt2.split(".")[1];
|
|
12524
13347
|
if (!payload) return { status: "valid" };
|
|
@@ -12580,9 +13403,9 @@ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
|
|
|
12580
13403
|
var init_whoami = __esm({
|
|
12581
13404
|
"cli/commands/whoami.ts"() {
|
|
12582
13405
|
"use strict";
|
|
12583
|
-
SYNKRO_DIR15 =
|
|
12584
|
-
CONFIG_PATH8 =
|
|
12585
|
-
JWT_PATH2 =
|
|
13406
|
+
SYNKRO_DIR15 = join33(homedir33(), ".synkro");
|
|
13407
|
+
CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
|
|
13408
|
+
JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
|
|
12586
13409
|
GRADING_LABEL = {
|
|
12587
13410
|
local: "on-device worker pool",
|
|
12588
13411
|
cloud: "Synkro Cloud worker pool",
|
|
@@ -12627,12 +13450,12 @@ __export(linear_exports, {
|
|
|
12627
13450
|
formatLinks: () => formatLinks,
|
|
12628
13451
|
linearCommand: () => linearCommand
|
|
12629
13452
|
});
|
|
12630
|
-
import { readFileSync as
|
|
12631
|
-
import { homedir as
|
|
12632
|
-
import { join as
|
|
13453
|
+
import { readFileSync as readFileSync32 } from "fs";
|
|
13454
|
+
import { homedir as homedir34 } from "os";
|
|
13455
|
+
import { join as join34 } from "path";
|
|
12633
13456
|
function mcpJwt() {
|
|
12634
13457
|
try {
|
|
12635
|
-
return
|
|
13458
|
+
return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
|
|
12636
13459
|
} catch {
|
|
12637
13460
|
return "";
|
|
12638
13461
|
}
|
|
@@ -12671,7 +13494,7 @@ var SYNKRO_DIR16, PORT2, BASE;
|
|
|
12671
13494
|
var init_linear = __esm({
|
|
12672
13495
|
"cli/commands/linear.ts"() {
|
|
12673
13496
|
"use strict";
|
|
12674
|
-
SYNKRO_DIR16 =
|
|
13497
|
+
SYNKRO_DIR16 = join34(homedir34(), ".synkro");
|
|
12675
13498
|
PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12676
13499
|
BASE = `http://127.0.0.1:${PORT2}`;
|
|
12677
13500
|
}
|
|
@@ -12679,7 +13502,7 @@ var init_linear = __esm({
|
|
|
12679
13502
|
|
|
12680
13503
|
// cli/scanning/cveReachability.ts
|
|
12681
13504
|
import { parse } from "@babel/parser";
|
|
12682
|
-
import { readFileSync as
|
|
13505
|
+
import { readFileSync as readFileSync33 } from "fs";
|
|
12683
13506
|
function walk(node, visit) {
|
|
12684
13507
|
if (!node || typeof node.type !== "string") return;
|
|
12685
13508
|
visit(node);
|
|
@@ -12821,9 +13644,9 @@ var init_cveReachability = __esm({
|
|
|
12821
13644
|
|
|
12822
13645
|
// cli/reachability/reachabilityScan.ts
|
|
12823
13646
|
import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
|
|
12824
|
-
import { readFileSync as
|
|
12825
|
-
import { join as
|
|
12826
|
-
import { homedir as
|
|
13647
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
|
|
13648
|
+
import { join as join35 } from "path";
|
|
13649
|
+
import { homedir as homedir35 } from "os";
|
|
12827
13650
|
import { createRequire } from "module";
|
|
12828
13651
|
function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
12829
13652
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
|
|
@@ -12840,7 +13663,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12840
13663
|
}
|
|
12841
13664
|
for (const e of ents) {
|
|
12842
13665
|
if (files.length >= maxFiles) break;
|
|
12843
|
-
const full =
|
|
13666
|
+
const full = join35(dir, e.name);
|
|
12844
13667
|
if (e.isDirectory()) {
|
|
12845
13668
|
if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
|
|
12846
13669
|
continue;
|
|
@@ -12848,7 +13671,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
|
|
|
12848
13671
|
if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
|
|
12849
13672
|
const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
|
|
12850
13673
|
try {
|
|
12851
|
-
const content =
|
|
13674
|
+
const content = readFileSync34(full, "utf8");
|
|
12852
13675
|
if (content.length <= maxBytes) files.push({ path: rel, content });
|
|
12853
13676
|
} catch {
|
|
12854
13677
|
}
|
|
@@ -12867,12 +13690,12 @@ function cleanVersion(spec) {
|
|
|
12867
13690
|
function gatherManifestVersions(repoRoot2) {
|
|
12868
13691
|
const out = {};
|
|
12869
13692
|
const dirs = [repoRoot2];
|
|
12870
|
-
const pkgsDir =
|
|
13693
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12871
13694
|
if (existsSync34(pkgsDir)) {
|
|
12872
13695
|
try {
|
|
12873
13696
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12874
|
-
const pd =
|
|
12875
|
-
if (existsSync34(
|
|
13697
|
+
const pd = join35(pkgsDir, d);
|
|
13698
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12876
13699
|
}
|
|
12877
13700
|
} catch {
|
|
12878
13701
|
}
|
|
@@ -12881,7 +13704,7 @@ function gatherManifestVersions(repoRoot2) {
|
|
|
12881
13704
|
for (const dir of dirs) {
|
|
12882
13705
|
let pkg;
|
|
12883
13706
|
try {
|
|
12884
|
-
pkg = JSON.parse(
|
|
13707
|
+
pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12885
13708
|
} catch {
|
|
12886
13709
|
continue;
|
|
12887
13710
|
}
|
|
@@ -12901,28 +13724,28 @@ function findJelly(repoRoot2) {
|
|
|
12901
13724
|
try {
|
|
12902
13725
|
const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
|
|
12903
13726
|
const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
|
|
12904
|
-
const pkg = JSON.parse(
|
|
13727
|
+
const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
|
|
12905
13728
|
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
|
|
12906
13729
|
if (bin) {
|
|
12907
|
-
const p =
|
|
13730
|
+
const p = join35(dir, bin);
|
|
12908
13731
|
if (existsSync34(p)) return p;
|
|
12909
13732
|
}
|
|
12910
13733
|
} catch {
|
|
12911
13734
|
}
|
|
12912
13735
|
for (const base of [repoRoot2, process.cwd()]) {
|
|
12913
|
-
const b =
|
|
13736
|
+
const b = join35(base, "node_modules", ".bin", "jelly");
|
|
12914
13737
|
if (existsSync34(b)) return b;
|
|
12915
13738
|
}
|
|
12916
13739
|
return null;
|
|
12917
13740
|
}
|
|
12918
13741
|
function findEntries(repoRoot2) {
|
|
12919
13742
|
const dirs = [repoRoot2];
|
|
12920
|
-
const pkgsDir =
|
|
13743
|
+
const pkgsDir = join35(repoRoot2, "packages");
|
|
12921
13744
|
if (existsSync34(pkgsDir)) {
|
|
12922
13745
|
try {
|
|
12923
13746
|
for (const d of readdirSync9(pkgsDir)) {
|
|
12924
|
-
const pd =
|
|
12925
|
-
if (existsSync34(
|
|
13747
|
+
const pd = join35(pkgsDir, d);
|
|
13748
|
+
if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
|
|
12926
13749
|
}
|
|
12927
13750
|
} catch {
|
|
12928
13751
|
}
|
|
@@ -12930,11 +13753,11 @@ function findEntries(repoRoot2) {
|
|
|
12930
13753
|
const entries = [];
|
|
12931
13754
|
for (const dir of dirs) {
|
|
12932
13755
|
try {
|
|
12933
|
-
const pkg = JSON.parse(
|
|
13756
|
+
const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
|
|
12934
13757
|
const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
|
|
12935
13758
|
for (const c of cands) {
|
|
12936
13759
|
if (typeof c !== "string") continue;
|
|
12937
|
-
const f =
|
|
13760
|
+
const f = join35(dir, c);
|
|
12938
13761
|
if (existsSync34(f)) {
|
|
12939
13762
|
entries.push(f);
|
|
12940
13763
|
break;
|
|
@@ -12970,7 +13793,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
12970
13793
|
const commit = currentCommit(repoRoot2);
|
|
12971
13794
|
if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
|
|
12972
13795
|
try {
|
|
12973
|
-
const prev = JSON.parse(
|
|
13796
|
+
const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
|
|
12974
13797
|
if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
|
|
12975
13798
|
} catch {
|
|
12976
13799
|
}
|
|
@@ -13059,7 +13882,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
|
|
|
13059
13882
|
if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
|
|
13060
13883
|
const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
|
|
13061
13884
|
try {
|
|
13062
|
-
|
|
13885
|
+
writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
|
|
13063
13886
|
} catch (e) {
|
|
13064
13887
|
return { ok: false, reason: "write failed: " + String(e.message || e) };
|
|
13065
13888
|
}
|
|
@@ -13071,7 +13894,7 @@ var init_reachabilityScan = __esm({
|
|
|
13071
13894
|
"use strict";
|
|
13072
13895
|
init_cveReachability();
|
|
13073
13896
|
require2 = createRequire(import.meta.url);
|
|
13074
|
-
REACHABILITY_PATH =
|
|
13897
|
+
REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
|
|
13075
13898
|
}
|
|
13076
13899
|
});
|
|
13077
13900
|
|
|
@@ -13080,15 +13903,15 @@ var reachabilityScan_exports = {};
|
|
|
13080
13903
|
__export(reachabilityScan_exports, {
|
|
13081
13904
|
reachabilityScanCommand: () => reachabilityScanCommand
|
|
13082
13905
|
});
|
|
13083
|
-
import { readFileSync as
|
|
13084
|
-
import { join as
|
|
13085
|
-
import { homedir as
|
|
13906
|
+
import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
|
|
13907
|
+
import { join as join36 } from "path";
|
|
13908
|
+
import { homedir as homedir36 } from "os";
|
|
13086
13909
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
13087
13910
|
function readConfigEnv4() {
|
|
13088
|
-
const p =
|
|
13911
|
+
const p = join36(SYNKRO_DIR17, "config.env");
|
|
13089
13912
|
if (!existsSync35(p)) return {};
|
|
13090
13913
|
const out = {};
|
|
13091
|
-
for (const line of
|
|
13914
|
+
for (const line of readFileSync35(p, "utf-8").split("\n")) {
|
|
13092
13915
|
const t = line.trim();
|
|
13093
13916
|
if (!t || t.startsWith("#")) continue;
|
|
13094
13917
|
const eq = t.indexOf("=");
|
|
@@ -13120,11 +13943,11 @@ async function pushToCloud(cfg, repo) {
|
|
|
13120
13943
|
while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
|
|
13121
13944
|
let jwt2 = "";
|
|
13122
13945
|
try {
|
|
13123
|
-
jwt2 =
|
|
13946
|
+
jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
|
|
13124
13947
|
} catch {
|
|
13125
13948
|
}
|
|
13126
13949
|
if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
|
|
13127
|
-
const body =
|
|
13950
|
+
const body = readFileSync35(REACHABILITY_PATH, "utf-8");
|
|
13128
13951
|
try {
|
|
13129
13952
|
const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
|
|
13130
13953
|
method: "POST",
|
|
@@ -13156,7 +13979,7 @@ var init_reachabilityScan2 = __esm({
|
|
|
13156
13979
|
"cli/commands/reachabilityScan.ts"() {
|
|
13157
13980
|
"use strict";
|
|
13158
13981
|
init_reachabilityScan();
|
|
13159
|
-
SYNKRO_DIR17 =
|
|
13982
|
+
SYNKRO_DIR17 = join36(homedir36(), ".synkro");
|
|
13160
13983
|
}
|
|
13161
13984
|
});
|
|
13162
13985
|
|
|
@@ -13188,9 +14011,9 @@ async function startCommand(rest = []) {
|
|
|
13188
14011
|
console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
|
|
13189
14012
|
`);
|
|
13190
14013
|
await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13191
|
-
const ready = await waitForContainerReady(
|
|
14014
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13192
14015
|
if (!ready) {
|
|
13193
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14016
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13194
14017
|
process.exit(1);
|
|
13195
14018
|
}
|
|
13196
14019
|
console.log("\nServer is running.");
|
|
@@ -13220,9 +14043,9 @@ async function updateCommand() {
|
|
|
13220
14043
|
console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
|
|
13221
14044
|
`);
|
|
13222
14045
|
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13223
|
-
const ready = await waitForContainerReady(
|
|
14046
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13224
14047
|
if (!ready) {
|
|
13225
|
-
console.error("\n\u26A0 container did not pass its health check within
|
|
14048
|
+
console.error("\n\u26A0 container did not pass its health check within 10m \u2014 check: docker logs synkro-server");
|
|
13226
14049
|
process.exit(1);
|
|
13227
14050
|
}
|
|
13228
14051
|
try {
|
|
@@ -13257,9 +14080,9 @@ async function restartCommand(rest = []) {
|
|
|
13257
14080
|
console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
|
|
13258
14081
|
`);
|
|
13259
14082
|
await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
|
|
13260
|
-
const ready = await waitForContainerReady(
|
|
14083
|
+
const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
|
|
13261
14084
|
if (!ready) {
|
|
13262
|
-
console.error("\n\u26A0 container did not pass /healthz within
|
|
14085
|
+
console.error("\n\u26A0 container did not pass /healthz within 10m");
|
|
13263
14086
|
process.exit(1);
|
|
13264
14087
|
}
|
|
13265
14088
|
console.log("\nServer restarted successfully.");
|
|
@@ -13271,11 +14094,13 @@ async function restartCommand(rest = []) {
|
|
|
13271
14094
|
console.warn("\u26A0 workers did not register within 30s \u2014 skill sync skipped");
|
|
13272
14095
|
}
|
|
13273
14096
|
}
|
|
14097
|
+
var LOCAL_CONTAINER_READY_TIMEOUT_MS;
|
|
13274
14098
|
var init_lifecycle = __esm({
|
|
13275
14099
|
"cli/commands/lifecycle.ts"() {
|
|
13276
14100
|
"use strict";
|
|
13277
14101
|
init_dockerInstall();
|
|
13278
14102
|
init_install();
|
|
14103
|
+
LOCAL_CONTAINER_READY_TIMEOUT_MS = 6e5;
|
|
13279
14104
|
}
|
|
13280
14105
|
});
|
|
13281
14106
|
|
|
@@ -13284,13 +14109,13 @@ var config_exports = {};
|
|
|
13284
14109
|
__export(config_exports, {
|
|
13285
14110
|
configCommand: () => configCommand
|
|
13286
14111
|
});
|
|
13287
|
-
import { readFileSync as
|
|
13288
|
-
import { join as
|
|
13289
|
-
import { homedir as
|
|
14112
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
|
|
14113
|
+
import { join as join37 } from "path";
|
|
14114
|
+
import { homedir as homedir37 } from "os";
|
|
13290
14115
|
function readConfigEnv5() {
|
|
13291
14116
|
if (!existsSync36(CONFIG_PATH9)) return {};
|
|
13292
14117
|
const out = {};
|
|
13293
|
-
for (const line of
|
|
14118
|
+
for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
|
|
13294
14119
|
const t = line.trim();
|
|
13295
14120
|
if (!t || t.startsWith("#")) continue;
|
|
13296
14121
|
const eq = t.indexOf("=");
|
|
@@ -13303,7 +14128,7 @@ function updateConfigValue(key, value) {
|
|
|
13303
14128
|
console.error("No config found. Run `synkro install` first.");
|
|
13304
14129
|
process.exit(1);
|
|
13305
14130
|
}
|
|
13306
|
-
const lines =
|
|
14131
|
+
const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
|
|
13307
14132
|
const pattern = new RegExp(`^${key}=`);
|
|
13308
14133
|
let found = false;
|
|
13309
14134
|
const updated = lines.map((line) => {
|
|
@@ -13314,7 +14139,7 @@ function updateConfigValue(key, value) {
|
|
|
13314
14139
|
return line;
|
|
13315
14140
|
});
|
|
13316
14141
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
13317
|
-
|
|
14142
|
+
writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
|
|
13318
14143
|
}
|
|
13319
14144
|
function resolveInferenceMode(cfg) {
|
|
13320
14145
|
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
@@ -13472,8 +14297,8 @@ var init_config = __esm({
|
|
|
13472
14297
|
"use strict";
|
|
13473
14298
|
init_stub();
|
|
13474
14299
|
init_optout();
|
|
13475
|
-
SYNKRO_DIR18 =
|
|
13476
|
-
CONFIG_PATH9 =
|
|
14300
|
+
SYNKRO_DIR18 = join37(homedir37(), ".synkro");
|
|
14301
|
+
CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
|
|
13477
14302
|
}
|
|
13478
14303
|
});
|
|
13479
14304
|
|
|
@@ -13662,14 +14487,14 @@ Usage:
|
|
|
13662
14487
|
});
|
|
13663
14488
|
|
|
13664
14489
|
// cli/bootstrap.js
|
|
13665
|
-
import { readFileSync as
|
|
14490
|
+
import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
|
|
13666
14491
|
import { resolve as resolve5 } from "path";
|
|
13667
14492
|
var envCandidates = [
|
|
13668
14493
|
resolve5(process.env.HOME ?? "", ".synkro", "config.env")
|
|
13669
14494
|
];
|
|
13670
14495
|
for (const envPath of envCandidates) {
|
|
13671
14496
|
if (!existsSync37(envPath)) continue;
|
|
13672
|
-
const envContent =
|
|
14497
|
+
const envContent = readFileSync37(envPath, "utf-8");
|
|
13673
14498
|
for (const line of envContent.split("\n")) {
|
|
13674
14499
|
const trimmed = line.trim();
|
|
13675
14500
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -13686,7 +14511,7 @@ var subArgs = args.slice(1);
|
|
|
13686
14511
|
var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
|
|
13687
14512
|
var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
|
|
13688
14513
|
function printVersion() {
|
|
13689
|
-
console.log("1.7.
|
|
14514
|
+
console.log("1.7.89");
|
|
13690
14515
|
}
|
|
13691
14516
|
function printHelp2() {
|
|
13692
14517
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|