@synkro-sh/cli 1.6.79 → 1.6.80
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 +72 -43
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -913,7 +913,7 @@ var init_hookScriptsTs = __esm({
|
|
|
913
913
|
"use strict";
|
|
914
914
|
SYNKRO_COMMON_TS = `
|
|
915
915
|
// Shared Synkro hook utilities \u2014 imported by all hook scripts.
|
|
916
|
-
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, renameSync, openSync, closeSync, unlinkSync, readdirSync, statSync, createReadStream } from 'node:fs';
|
|
916
|
+
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, renameSync, openSync, closeSync, readSync, unlinkSync, readdirSync, statSync, createReadStream } from 'node:fs';
|
|
917
917
|
import { createInterface } from 'node:readline';
|
|
918
918
|
import { join, dirname, basename, extname, resolve as resolvePath } from 'node:path';
|
|
919
919
|
import { homedir } from 'node:os';
|
|
@@ -3374,47 +3374,76 @@ export function aggregateUsage(
|
|
|
3374
3374
|
): { model: string; totals: Record<string, number> } {
|
|
3375
3375
|
const result = { model: opts?.modelFallback || '', totals: { in: 0, out: 0, cw: 0, cr: 0 } };
|
|
3376
3376
|
if (!transcriptPath || !existsSync(transcriptPath)) return result;
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
}
|
|
3393
|
-
|
|
3394
|
-
for (const entry of entries) {
|
|
3395
|
-
const role = String(entry.role || entry.type || '');
|
|
3396
|
-
const modelInfo = entry.modelInfo as Record<string, unknown> | undefined;
|
|
3397
|
-
const model = (typeof modelInfo?.modelName === 'string' && modelInfo.modelName)
|
|
3398
|
-
|| (typeof modelInfo?.model === 'string' && modelInfo.model)
|
|
3399
|
-
|| (typeof entry.model === 'string' && entry.model)
|
|
3400
|
-
|| msgModel(entry);
|
|
3401
|
-
if (model) result.model = model;
|
|
3402
|
-
|
|
3403
|
-
const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;
|
|
3404
|
-
const msg = entry.message as Record<string, unknown> | undefined;
|
|
3405
|
-
const hadLine = addUsageBlock(result, tokenCount)
|
|
3406
|
-
|| addUsageBlock(result, msg?.usage as Record<string, unknown>)
|
|
3407
|
-
|| addUsageBlock(result, entry.usage as Record<string, unknown>);
|
|
3408
|
-
|
|
3409
|
-
if (hadLine || sawExplicit) continue;
|
|
3410
|
-
|
|
3411
|
-
const text = usageTextFromTranscriptEntry(entry);
|
|
3412
|
-
if (!text) continue;
|
|
3413
|
-
const est = Math.ceil(text.length / 4);
|
|
3414
|
-
if (role === 'user' || entry.type === 'user') result.totals.in += est;
|
|
3415
|
-
else if (role === 'assistant' || entry.type === 'assistant') result.totals.out += est;
|
|
3416
|
-
}
|
|
3377
|
+
// CWE-22: only ever read the agent's own transcript, which lives under HOME
|
|
3378
|
+
// (~/.claude, ~/.cursor). Never follow an arbitrary external path.
|
|
3379
|
+
if (!isPathUnder(transcriptPath, HOME)) return result;
|
|
3380
|
+
|
|
3381
|
+
// INCREMENTAL DELTA: returns ONLY the usage in transcript bytes appended since
|
|
3382
|
+
// the last call (a byte offset is tracked per transcript in a sidecar). All three
|
|
3383
|
+
// usage hooks (Claude UserPromptSubmit + Stop, Cursor transcript-sync) share that
|
|
3384
|
+
// offset, so their deltas never overlap; the DB then INCREMENTS by each delta, so
|
|
3385
|
+
// usage climbs per message in both harnesses. We never re-read the whole
|
|
3386
|
+
// (100s-of-MB) transcript \u2014 the old full read OOM'd bun and dropped the tick.
|
|
3387
|
+
const key = (transcriptPath.split('/').pop() || 'session').replace(/[^A-Za-z0-9._-]/g, '_');
|
|
3388
|
+
const stateFile = join(SESSIONS_DIR, key + '.usage.json');
|
|
3389
|
+
let offset = 0, sawExplicit = false, model = '';
|
|
3390
|
+
try {
|
|
3391
|
+
const s = JSON.parse(readFileSync(stateFile, 'utf-8'));
|
|
3392
|
+
if (s && typeof s.offset === 'number') { offset = s.offset; sawExplicit = !!s.sawExplicit; model = s.model || ''; }
|
|
3417
3393
|
} catch {}
|
|
3394
|
+
|
|
3395
|
+
let fd = -1;
|
|
3396
|
+
try {
|
|
3397
|
+
const size = statSync(transcriptPath).size;
|
|
3398
|
+
if (size < offset) { offset = 0; sawExplicit = false; } // rotated/truncated
|
|
3399
|
+
// Never read more than 16MB in one shot; first sight of a giant existing
|
|
3400
|
+
// transcript jumps to the tail rather than OOM on the whole history.
|
|
3401
|
+
const MAX_READ = 16 * 1024 * 1024;
|
|
3402
|
+
let start = offset, capped = false;
|
|
3403
|
+
if (size - start > MAX_READ) { start = size - MAX_READ; capped = true; }
|
|
3404
|
+
const len = size - start;
|
|
3405
|
+
if (len > 0) {
|
|
3406
|
+
fd = openSync(transcriptPath, 'r');
|
|
3407
|
+
const buf = Buffer.alloc(len);
|
|
3408
|
+
readSync(fd, buf, 0, len, start);
|
|
3409
|
+
const lines = buf.toString('utf-8').split('\\n');
|
|
3410
|
+
if (capped && lines.length) lines.shift(); // drop the partial first line when we jumped mid-file
|
|
3411
|
+
for (const line of lines) {
|
|
3412
|
+
if (!line.trim()) continue;
|
|
3413
|
+
let entry: Record<string, unknown>;
|
|
3414
|
+
try { entry = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
|
|
3415
|
+
const msg = entry.message as Record<string, unknown> | undefined;
|
|
3416
|
+
const modelInfo = entry.modelInfo as Record<string, unknown> | undefined;
|
|
3417
|
+
const m = (typeof modelInfo?.modelName === 'string' && modelInfo.modelName)
|
|
3418
|
+
|| (typeof modelInfo?.model === 'string' && modelInfo.model)
|
|
3419
|
+
|| (typeof entry.model === 'string' && entry.model)
|
|
3420
|
+
|| msgModel(entry);
|
|
3421
|
+
if (m) { model = m; result.model = m; }
|
|
3422
|
+
|
|
3423
|
+
const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;
|
|
3424
|
+
// result.totals is the DELTA for THIS call only (it never carries prior totals).
|
|
3425
|
+
const had = addUsageBlock(result, tokenCount)
|
|
3426
|
+
|| addUsageBlock(result, msg?.usage as Record<string, unknown>)
|
|
3427
|
+
|| addUsageBlock(result, entry.usage as Record<string, unknown>);
|
|
3428
|
+
if (had) { sawExplicit = true; continue; }
|
|
3429
|
+
if (sawExplicit) continue;
|
|
3430
|
+
|
|
3431
|
+
// Fallback (Cursor / transcripts without usage blocks): estimate from text.
|
|
3432
|
+
const text = usageTextFromTranscriptEntry(entry);
|
|
3433
|
+
if (!text) continue;
|
|
3434
|
+
const est = Math.ceil(text.length / 4);
|
|
3435
|
+
const role = String(entry.role || entry.type || '');
|
|
3436
|
+
if (role === 'user') result.totals.in += est;
|
|
3437
|
+
else if (role === 'assistant') result.totals.out += est;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
// Advance the offset (delta consumed). Ship is best-effort like the other
|
|
3441
|
+
// hooks \u2014 a rare dropped tick loses only that one delta, not the running total.
|
|
3442
|
+
try { mkdirSync(SESSIONS_DIR, { recursive: true }); writeFileSync(stateFile, JSON.stringify({ offset: size, model, sawExplicit })); } catch {}
|
|
3443
|
+
} catch {} finally {
|
|
3444
|
+
if (fd >= 0) { try { closeSync(fd); } catch {} }
|
|
3445
|
+
}
|
|
3446
|
+
if (!result.model) result.model = model || opts?.modelFallback || '';
|
|
3418
3447
|
return result;
|
|
3419
3448
|
}
|
|
3420
3449
|
|
|
@@ -10709,7 +10738,7 @@ function writeConfigEnv(opts) {
|
|
|
10709
10738
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10710
10739
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10711
10740
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10712
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10741
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.80")}`
|
|
10713
10742
|
];
|
|
10714
10743
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10715
10744
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -14384,7 +14413,7 @@ var args = process.argv.slice(2);
|
|
|
14384
14413
|
var cmd = args[0] || "";
|
|
14385
14414
|
var subArgs = args.slice(1);
|
|
14386
14415
|
function printVersion() {
|
|
14387
|
-
console.log("1.6.
|
|
14416
|
+
console.log("1.6.80");
|
|
14388
14417
|
}
|
|
14389
14418
|
function printHelp2() {
|
|
14390
14419
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|