@synkro-sh/cli 1.6.79 → 1.6.81

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 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';
@@ -3019,6 +3019,18 @@ export async function syncConversationTranscript(
3019
3019
  };
3020
3020
  });
3021
3021
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
3022
+ // Per-turn usage + model so the session view can show input/output/cache
3023
+ // tokens and cost line-by-line (not just the session aggregate).
3024
+ if (msgObj && typeof msgObj.model === 'string') msg.model = msgObj.model;
3025
+ const u = msgObj && (msgObj.usage as Record<string, unknown> | undefined);
3026
+ if (u && typeof u === 'object') {
3027
+ msg.usage = {
3028
+ input_tokens: Number(u.input_tokens) || 0,
3029
+ output_tokens: Number(u.output_tokens) || 0,
3030
+ cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,
3031
+ cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0,
3032
+ };
3033
+ }
3022
3034
  if (msgObj && typeof msgObj === 'object') {
3023
3035
  msg.model = msgObj.model || null;
3024
3036
  const u = msgObj.usage as Record<string, unknown> | undefined;
@@ -3374,47 +3386,76 @@ export function aggregateUsage(
3374
3386
  ): { model: string; totals: Record<string, number> } {
3375
3387
  const result = { model: opts?.modelFallback || '', totals: { in: 0, out: 0, cw: 0, cr: 0 } };
3376
3388
  if (!transcriptPath || !existsSync(transcriptPath)) return result;
3377
- try {
3378
- const raw = readFileSync(transcriptPath, 'utf-8');
3379
- const entries: Record<string, unknown>[] = [];
3380
- for (const line of raw.split('\\n')) {
3381
- if (!line.trim()) continue;
3382
- try { entries.push(JSON.parse(line) as Record<string, unknown>); } catch {}
3383
- }
3384
-
3385
- let sawExplicit = false;
3386
- for (const entry of entries) {
3387
- const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;
3388
- const msg = entry.message as Record<string, unknown> | undefined;
3389
- if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, tokenCount)) sawExplicit = true;
3390
- if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, msg?.usage as Record<string, unknown>)) sawExplicit = true;
3391
- if (addUsageBlock({ model: '', totals: { in: 0, out: 0, cw: 0, cr: 0 } }, entry.usage as Record<string, unknown>)) sawExplicit = true;
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
- }
3389
+ // CWE-22: only ever read the agent's own transcript, which lives under HOME
3390
+ // (~/.claude, ~/.cursor). Never follow an arbitrary external path.
3391
+ if (!isPathUnder(transcriptPath, HOME)) return result;
3392
+
3393
+ // INCREMENTAL DELTA: returns ONLY the usage in transcript bytes appended since
3394
+ // the last call (a byte offset is tracked per transcript in a sidecar). All three
3395
+ // usage hooks (Claude UserPromptSubmit + Stop, Cursor transcript-sync) share that
3396
+ // offset, so their deltas never overlap; the DB then INCREMENTS by each delta, so
3397
+ // usage climbs per message in both harnesses. We never re-read the whole
3398
+ // (100s-of-MB) transcript \u2014 the old full read OOM'd bun and dropped the tick.
3399
+ const key = (transcriptPath.split('/').pop() || 'session').replace(/[^A-Za-z0-9._-]/g, '_');
3400
+ const stateFile = join(SESSIONS_DIR, key + '.usage.json');
3401
+ let offset = 0, sawExplicit = false, model = '';
3402
+ try {
3403
+ const s = JSON.parse(readFileSync(stateFile, 'utf-8'));
3404
+ if (s && typeof s.offset === 'number') { offset = s.offset; sawExplicit = !!s.sawExplicit; model = s.model || ''; }
3417
3405
  } catch {}
3406
+
3407
+ let fd = -1;
3408
+ try {
3409
+ const size = statSync(transcriptPath).size;
3410
+ if (size < offset) { offset = 0; sawExplicit = false; } // rotated/truncated
3411
+ // Never read more than 16MB in one shot; first sight of a giant existing
3412
+ // transcript jumps to the tail rather than OOM on the whole history.
3413
+ const MAX_READ = 16 * 1024 * 1024;
3414
+ let start = offset, capped = false;
3415
+ if (size - start > MAX_READ) { start = size - MAX_READ; capped = true; }
3416
+ const len = size - start;
3417
+ if (len > 0) {
3418
+ fd = openSync(transcriptPath, 'r');
3419
+ const buf = Buffer.alloc(len);
3420
+ readSync(fd, buf, 0, len, start);
3421
+ const lines = buf.toString('utf-8').split('\\n');
3422
+ if (capped && lines.length) lines.shift(); // drop the partial first line when we jumped mid-file
3423
+ for (const line of lines) {
3424
+ if (!line.trim()) continue;
3425
+ let entry: Record<string, unknown>;
3426
+ try { entry = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
3427
+ const msg = entry.message as Record<string, unknown> | undefined;
3428
+ const modelInfo = entry.modelInfo as Record<string, unknown> | undefined;
3429
+ const m = (typeof modelInfo?.modelName === 'string' && modelInfo.modelName)
3430
+ || (typeof modelInfo?.model === 'string' && modelInfo.model)
3431
+ || (typeof entry.model === 'string' && entry.model)
3432
+ || msgModel(entry);
3433
+ if (m) { model = m; result.model = m; }
3434
+
3435
+ const tokenCount = (entry.tokenCount ?? entry.token_count) as Record<string, unknown> | undefined;
3436
+ // result.totals is the DELTA for THIS call only (it never carries prior totals).
3437
+ const had = addUsageBlock(result, tokenCount)
3438
+ || addUsageBlock(result, msg?.usage as Record<string, unknown>)
3439
+ || addUsageBlock(result, entry.usage as Record<string, unknown>);
3440
+ if (had) { sawExplicit = true; continue; }
3441
+ if (sawExplicit) continue;
3442
+
3443
+ // Fallback (Cursor / transcripts without usage blocks): estimate from text.
3444
+ const text = usageTextFromTranscriptEntry(entry);
3445
+ if (!text) continue;
3446
+ const est = Math.ceil(text.length / 4);
3447
+ const role = String(entry.role || entry.type || '');
3448
+ if (role === 'user') result.totals.in += est;
3449
+ else if (role === 'assistant') result.totals.out += est;
3450
+ }
3451
+ }
3452
+ // Advance the offset (delta consumed). Ship is best-effort like the other
3453
+ // hooks \u2014 a rare dropped tick loses only that one delta, not the running total.
3454
+ try { mkdirSync(SESSIONS_DIR, { recursive: true }); writeFileSync(stateFile, JSON.stringify({ offset: size, model, sawExplicit })); } catch {}
3455
+ } catch {} finally {
3456
+ if (fd >= 0) { try { closeSync(fd); } catch {} }
3457
+ }
3458
+ if (!result.model) result.model = model || opts?.modelFallback || '';
3418
3459
  return result;
3419
3460
  }
3420
3461
 
@@ -10437,7 +10478,7 @@ async function promptAgentSelection(detected) {
10437
10478
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
10438
10479
  console.log(` ${detected.length + 1}. Both / all (default)`);
10439
10480
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10440
- const ask2 = () => new Promise((resolve4) => {
10481
+ const ask3 = () => new Promise((resolve4) => {
10441
10482
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
10442
10483
  const t = answer.trim().toLowerCase();
10443
10484
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
@@ -10450,10 +10491,10 @@ async function promptAgentSelection(detected) {
10450
10491
  return resolve4([detected[n - 1]]);
10451
10492
  }
10452
10493
  console.log("Invalid choice. Try again.");
10453
- resolve4(ask2());
10494
+ resolve4(ask3());
10454
10495
  });
10455
10496
  });
10456
- return ask2();
10497
+ return ask3();
10457
10498
  }
10458
10499
  async function promptCursorApiKey(opts) {
10459
10500
  const { cursorApiKeyConfigured: cursorApiKeyConfigured2, writeCursorApiKey: writeCursorApiKey2, validateCursorApiKey: validateCursorApiKey2 } = await Promise.resolve().then(() => (init_macKeychain(), macKeychain_exports));
@@ -10505,7 +10546,7 @@ async function promptDeployLocation() {
10505
10546
  async function promptTranscriptSources(wantCC, wantCursor) {
10506
10547
  if (!wantCC && !wantCursor) return { cc: false, cursor: false };
10507
10548
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10508
- const ask2 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));
10549
+ const ask3 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));
10509
10550
  try {
10510
10551
  if (wantCC && wantCursor) {
10511
10552
  console.log("Import and embed past session history? This indexes sessions so Ask Synkro");
@@ -10514,14 +10555,14 @@ async function promptTranscriptSources(wantCC, wantCursor) {
10514
10555
  console.log(" 2. Claude Code only");
10515
10556
  console.log(" 3. Both");
10516
10557
  console.log(" n. Skip");
10517
- const a2 = await ask2("Choose [1/2/3/n] (default: 3): ");
10558
+ const a2 = await ask3("Choose [1/2/3/n] (default: 3): ");
10518
10559
  if (a2 === "n" || a2 === "no") return { cc: false, cursor: false };
10519
10560
  if (a2 === "1") return { cc: false, cursor: true };
10520
10561
  if (a2 === "2") return { cc: true, cursor: false };
10521
10562
  return { cc: true, cursor: true };
10522
10563
  }
10523
10564
  const which2 = wantCursor ? "Cursor" : "Claude Code";
10524
- const a = await ask2(
10565
+ const a = await ask3(
10525
10566
  `Import and embed your ${which2} session history?
10526
10567
  This indexes past sessions so Ask Synkro can answer questions about your
10527
10568
  coding patterns and the dashboard shows full history. (Y/n) `
@@ -10709,7 +10750,7 @@ function writeConfigEnv(opts) {
10709
10750
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10710
10751
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10711
10752
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10712
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.79")}`
10753
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.81")}`
10713
10754
  ];
10714
10755
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10715
10756
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -14104,6 +14145,182 @@ var init_localCc = __esm({
14104
14145
  }
14105
14146
  });
14106
14147
 
14148
+ // cli/commands/import.ts
14149
+ var import_exports = {};
14150
+ __export(import_exports, {
14151
+ importCommand: () => importCommand
14152
+ });
14153
+ import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync5 } from "fs";
14154
+ import { homedir as homedir17 } from "os";
14155
+ import { join as join17 } from "path";
14156
+ import { execSync as execSync7 } from "child_process";
14157
+ import { createInterface as createInterface5 } from "readline";
14158
+ function readConfigEnv() {
14159
+ const out = {};
14160
+ try {
14161
+ for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
14162
+ const t = line.trim();
14163
+ if (!t || t.startsWith("#")) continue;
14164
+ const eq = t.indexOf("=");
14165
+ if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
14166
+ }
14167
+ } catch {
14168
+ }
14169
+ return out;
14170
+ }
14171
+ function projectsFolder() {
14172
+ const sanitized = "-" + process.cwd().replace(/\//g, "-");
14173
+ const dir = join17(homedir17(), ".claude", "projects", sanitized);
14174
+ return existsSync17(dir) ? dir : null;
14175
+ }
14176
+ function repoName() {
14177
+ try {
14178
+ return execSync7("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim().split("/").pop() || "repo";
14179
+ } catch {
14180
+ return process.cwd().split("/").pop() || "repo";
14181
+ }
14182
+ }
14183
+ function extractText(content) {
14184
+ if (typeof content === "string") return content;
14185
+ if (Array.isArray(content)) {
14186
+ return content.map((b) => typeof b === "string" ? b : b?.type === "text" ? b.text || "" : "").filter(Boolean).join("\n");
14187
+ }
14188
+ return "";
14189
+ }
14190
+ function parseSession(filePath, sessionId) {
14191
+ const lines = readFileSync15(filePath, "utf-8").split("\n").filter(Boolean);
14192
+ const messages = [];
14193
+ const actions = [];
14194
+ let step = 0;
14195
+ for (let i = 0; i < lines.length; i++) {
14196
+ let e;
14197
+ try {
14198
+ e = JSON.parse(lines[i]);
14199
+ } catch {
14200
+ continue;
14201
+ }
14202
+ const kind = e.type === "assistant" || e.message?.role === "assistant" ? "assistant" : e.type === "user" || e.message?.role === "user" ? "user" : "";
14203
+ if (!kind) continue;
14204
+ const msgObj = e.message;
14205
+ const text = extractText(msgObj?.content ?? e.content);
14206
+ if (!text.trim()) continue;
14207
+ const msg = {
14208
+ message_index: i,
14209
+ type: kind,
14210
+ content: text.slice(0, 8e3),
14211
+ timestamp: e.timestamp || null
14212
+ };
14213
+ if (kind === "assistant") {
14214
+ if (typeof msgObj?.model === "string") msg.model = msgObj.model;
14215
+ const u = msgObj?.usage;
14216
+ if (u && typeof u === "object") {
14217
+ msg.usage = {
14218
+ input_tokens: Number(u.input_tokens) || 0,
14219
+ output_tokens: Number(u.output_tokens) || 0,
14220
+ cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,
14221
+ cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0
14222
+ };
14223
+ }
14224
+ const blocks = Array.isArray(msgObj?.content) ? msgObj.content : [];
14225
+ const toolCalls = blocks.filter((b) => b?.type === "tool_use").map((b) => ({ name: b.name, input: JSON.stringify(b.input || {}).slice(0, 500), id: b.id }));
14226
+ if (toolCalls.length) {
14227
+ msg.tool_calls = toolCalls;
14228
+ for (const t of toolCalls) actions.push({ step: ++step, tool: t.name, summary: "", file: null, outcome: null });
14229
+ }
14230
+ }
14231
+ messages.push(msg);
14232
+ }
14233
+ return { cc_session_id: sessionId, messages, actions };
14234
+ }
14235
+ function ask2(q) {
14236
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
14237
+ return new Promise((resolve4) => rl.question(q, (a) => {
14238
+ rl.close();
14239
+ resolve4(/^y(es)?$/i.test(a.trim()));
14240
+ }));
14241
+ }
14242
+ async function importCommand() {
14243
+ const config = readConfigEnv();
14244
+ const isCloud = config.SYNKRO_DEPLOY_LOCATION === "cloud" || config.SYNKRO_STORAGE_MODE === "cloud";
14245
+ const repo = repoName();
14246
+ const dir = projectsFolder();
14247
+ if (!dir) {
14248
+ console.log("No Claude Code transcripts found for this repo (~/.claude/projects).");
14249
+ return;
14250
+ }
14251
+ const files = readdirSync5(dir).filter((f) => f.endsWith(".jsonl"));
14252
+ if (!files.length) {
14253
+ console.log("No sessions to import.");
14254
+ return;
14255
+ }
14256
+ console.log(`Found ${files.length} session(s) for "${repo}" (${isCloud ? "cloud" : "local"} mode).`);
14257
+ if (!isCloud) {
14258
+ const ok2 = await ask2(`Import all ${files.length} historical session(s) into your on-device store? [y/N] `);
14259
+ if (!ok2) {
14260
+ console.log("Aborted \u2014 nothing imported.");
14261
+ return;
14262
+ }
14263
+ }
14264
+ const sessions = files.map((f) => parseSession(join17(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
14265
+ const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
14266
+ let ok = 0, fail = 0;
14267
+ if (isCloud) {
14268
+ await ensureValidToken();
14269
+ const token = getAccessToken();
14270
+ if (!token) {
14271
+ console.log("Not authenticated \u2014 run `synkro login` first.");
14272
+ return;
14273
+ }
14274
+ const gateway = config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh";
14275
+ for (let i = 0; i < sessions.length; i += 20) {
14276
+ const batch = sessions.slice(i, i + 20);
14277
+ try {
14278
+ const r = await fetch(`${gateway}/api/v1/cli/sync-transcripts`, {
14279
+ method: "POST",
14280
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
14281
+ body: JSON.stringify({ repo, sessions: batch }),
14282
+ signal: AbortSignal.timeout(3e4)
14283
+ });
14284
+ if (r.ok) ok += batch.length;
14285
+ else {
14286
+ fail += batch.length;
14287
+ console.warn(` batch failed: HTTP ${r.status}`);
14288
+ }
14289
+ } catch (e) {
14290
+ fail += batch.length;
14291
+ console.warn(` batch error: ${e.message}`);
14292
+ }
14293
+ }
14294
+ } else {
14295
+ const port = config.SYNKRO_MCP_PORT || "18931";
14296
+ for (const s of sessions) {
14297
+ try {
14298
+ const r = await fetch(`http://127.0.0.1:${port}/api/ingest`, {
14299
+ method: "POST",
14300
+ headers: { "Content-Type": "application/json" },
14301
+ body: JSON.stringify({ capture_type: "transcript_sync", session_id: s.cc_session_id, repo, messages: s.messages, actions: s.actions }),
14302
+ signal: AbortSignal.timeout(15e3)
14303
+ });
14304
+ if (r.ok) ok++;
14305
+ else fail++;
14306
+ } catch {
14307
+ fail++;
14308
+ }
14309
+ }
14310
+ if (fail && ok === 0) console.log(" (is the local container running? `synkro status`)");
14311
+ }
14312
+ console.log(`
14313
+ \u2713 Imported ${ok} session(s) (${totalMsgs} messages)${fail ? `, ${fail} failed` : ""}.`);
14314
+ }
14315
+ var CONFIG_PATH5;
14316
+ var init_import = __esm({
14317
+ "cli/commands/import.ts"() {
14318
+ "use strict";
14319
+ init_stub();
14320
+ CONFIG_PATH5 = join17(homedir17(), ".synkro", "config.env");
14321
+ }
14322
+ });
14323
+
14107
14324
  // cli/commands/lifecycle.ts
14108
14325
  var lifecycle_exports = {};
14109
14326
  __export(lifecycle_exports, {
@@ -14216,13 +14433,13 @@ var config_exports = {};
14216
14433
  __export(config_exports, {
14217
14434
  configCommand: () => configCommand
14218
14435
  });
14219
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync17 } from "fs";
14220
- import { join as join17 } from "path";
14221
- import { homedir as homedir17 } from "os";
14222
- function readConfigEnv() {
14223
- if (!existsSync17(CONFIG_PATH5)) return {};
14436
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, existsSync as existsSync18 } from "fs";
14437
+ import { join as join18 } from "path";
14438
+ import { homedir as homedir18 } from "os";
14439
+ function readConfigEnv2() {
14440
+ if (!existsSync18(CONFIG_PATH6)) return {};
14224
14441
  const out = {};
14225
- for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
14442
+ for (const line of readFileSync16(CONFIG_PATH6, "utf-8").split("\n")) {
14226
14443
  const t = line.trim();
14227
14444
  if (!t || t.startsWith("#")) continue;
14228
14445
  const eq = t.indexOf("=");
@@ -14231,11 +14448,11 @@ function readConfigEnv() {
14231
14448
  return out;
14232
14449
  }
14233
14450
  function updateConfigValue(key, value) {
14234
- if (!existsSync17(CONFIG_PATH5)) {
14451
+ if (!existsSync18(CONFIG_PATH6)) {
14235
14452
  console.error("No config found. Run `synkro install` first.");
14236
14453
  process.exit(1);
14237
14454
  }
14238
- const lines = readFileSync15(CONFIG_PATH5, "utf-8").split("\n");
14455
+ const lines = readFileSync16(CONFIG_PATH6, "utf-8").split("\n");
14239
14456
  const pattern = new RegExp(`^${key}=`);
14240
14457
  let found = false;
14241
14458
  const updated = lines.map((line) => {
@@ -14246,10 +14463,10 @@ function updateConfigValue(key, value) {
14246
14463
  return line;
14247
14464
  });
14248
14465
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
14249
- writeFileSync11(CONFIG_PATH5, updated.join("\n"), "utf-8");
14466
+ writeFileSync11(CONFIG_PATH6, updated.join("\n"), "utf-8");
14250
14467
  }
14251
14468
  async function reconcileContainer() {
14252
- const cfg = readConfigEnv();
14469
+ const cfg = readConfigEnv2();
14253
14470
  const cloudOnly = (cfg.SYNKRO_GRADING_MODE || "local") === "byok" && (cfg.SYNKRO_STORAGE_MODE || "local") === "cloud";
14254
14471
  try {
14255
14472
  const { dockerInstall: dockerInstall2, dockerSafeStop: dockerSafeStop2, readContainerConfig: readContainerConfig2, assertDockerAvailable: assertDockerAvailable2, waitForContainerReady: waitForContainerReady2 } = await Promise.resolve().then(() => (init_dockerInstall(), dockerInstall_exports));
@@ -14273,7 +14490,7 @@ async function reconcileContainer() {
14273
14490
  }
14274
14491
  async function configCommand(args2) {
14275
14492
  if (args2.length === 0) {
14276
- const config2 = readConfigEnv();
14493
+ const config2 = readConfigEnv2();
14277
14494
  console.log("Synkro config:\n");
14278
14495
  console.log(` grading: ${config2.SYNKRO_GRADING_MODE || "local"}`);
14279
14496
  console.log(` storage: ${config2.SYNKRO_STORAGE_MODE || "local"}`);
@@ -14328,7 +14545,7 @@ To change:`);
14328
14545
  process.exit(1);
14329
14546
  }
14330
14547
  const token = getAccessToken();
14331
- const config = readConfigEnv();
14548
+ const config = readConfigEnv2();
14332
14549
  const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
14333
14550
  try {
14334
14551
  const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
@@ -14351,25 +14568,25 @@ To change:`);
14351
14568
  updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
14352
14569
  console.log(`\u2713 Inference set to '${inferenceValue}'.`);
14353
14570
  }
14354
- var SYNKRO_DIR7, CONFIG_PATH5;
14571
+ var SYNKRO_DIR7, CONFIG_PATH6;
14355
14572
  var init_config = __esm({
14356
14573
  "cli/commands/config.ts"() {
14357
14574
  "use strict";
14358
14575
  init_stub();
14359
- SYNKRO_DIR7 = join17(homedir17(), ".synkro");
14360
- CONFIG_PATH5 = join17(SYNKRO_DIR7, "config.env");
14576
+ SYNKRO_DIR7 = join18(homedir18(), ".synkro");
14577
+ CONFIG_PATH6 = join18(SYNKRO_DIR7, "config.env");
14361
14578
  }
14362
14579
  });
14363
14580
 
14364
14581
  // cli/bootstrap.js
14365
- import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
14582
+ import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
14366
14583
  import { resolve as resolve3 } from "path";
14367
14584
  var envCandidates = [
14368
14585
  resolve3(process.env.HOME ?? "", ".synkro", "config.env")
14369
14586
  ];
14370
14587
  for (const envPath of envCandidates) {
14371
- if (!existsSync18(envPath)) continue;
14372
- const envContent = readFileSync16(envPath, "utf-8");
14588
+ if (!existsSync19(envPath)) continue;
14589
+ const envContent = readFileSync17(envPath, "utf-8");
14373
14590
  for (const line of envContent.split("\n")) {
14374
14591
  const trimmed = line.trim();
14375
14592
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -14384,7 +14601,7 @@ var args = process.argv.slice(2);
14384
14601
  var cmd = args[0] || "";
14385
14602
  var subArgs = args.slice(1);
14386
14603
  function printVersion() {
14387
- console.log("1.6.79");
14604
+ console.log("1.6.81");
14388
14605
  }
14389
14606
  function printHelp2() {
14390
14607
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
@@ -14458,6 +14675,11 @@ async function main() {
14458
14675
  await localCcCommand2(subArgs);
14459
14676
  break;
14460
14677
  }
14678
+ case "import": {
14679
+ const { importCommand: importCommand2 } = await Promise.resolve().then(() => (init_import(), import_exports));
14680
+ await importCommand2();
14681
+ break;
14682
+ }
14461
14683
  case "version":
14462
14684
  case "--version":
14463
14685
  case "-v": {