@synkro-sh/cli 1.6.80 → 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
@@ -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;
@@ -10466,7 +10478,7 @@ async function promptAgentSelection(detected) {
10466
10478
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
10467
10479
  console.log(` ${detected.length + 1}. Both / all (default)`);
10468
10480
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10469
- const ask2 = () => new Promise((resolve4) => {
10481
+ const ask3 = () => new Promise((resolve4) => {
10470
10482
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
10471
10483
  const t = answer.trim().toLowerCase();
10472
10484
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
@@ -10479,10 +10491,10 @@ async function promptAgentSelection(detected) {
10479
10491
  return resolve4([detected[n - 1]]);
10480
10492
  }
10481
10493
  console.log("Invalid choice. Try again.");
10482
- resolve4(ask2());
10494
+ resolve4(ask3());
10483
10495
  });
10484
10496
  });
10485
- return ask2();
10497
+ return ask3();
10486
10498
  }
10487
10499
  async function promptCursorApiKey(opts) {
10488
10500
  const { cursorApiKeyConfigured: cursorApiKeyConfigured2, writeCursorApiKey: writeCursorApiKey2, validateCursorApiKey: validateCursorApiKey2 } = await Promise.resolve().then(() => (init_macKeychain(), macKeychain_exports));
@@ -10534,7 +10546,7 @@ async function promptDeployLocation() {
10534
10546
  async function promptTranscriptSources(wantCC, wantCursor) {
10535
10547
  if (!wantCC && !wantCursor) return { cc: false, cursor: false };
10536
10548
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10537
- 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())));
10538
10550
  try {
10539
10551
  if (wantCC && wantCursor) {
10540
10552
  console.log("Import and embed past session history? This indexes sessions so Ask Synkro");
@@ -10543,14 +10555,14 @@ async function promptTranscriptSources(wantCC, wantCursor) {
10543
10555
  console.log(" 2. Claude Code only");
10544
10556
  console.log(" 3. Both");
10545
10557
  console.log(" n. Skip");
10546
- const a2 = await ask2("Choose [1/2/3/n] (default: 3): ");
10558
+ const a2 = await ask3("Choose [1/2/3/n] (default: 3): ");
10547
10559
  if (a2 === "n" || a2 === "no") return { cc: false, cursor: false };
10548
10560
  if (a2 === "1") return { cc: false, cursor: true };
10549
10561
  if (a2 === "2") return { cc: true, cursor: false };
10550
10562
  return { cc: true, cursor: true };
10551
10563
  }
10552
10564
  const which2 = wantCursor ? "Cursor" : "Claude Code";
10553
- const a = await ask2(
10565
+ const a = await ask3(
10554
10566
  `Import and embed your ${which2} session history?
10555
10567
  This indexes past sessions so Ask Synkro can answer questions about your
10556
10568
  coding patterns and the dashboard shows full history. (Y/n) `
@@ -10738,7 +10750,7 @@ function writeConfigEnv(opts) {
10738
10750
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10739
10751
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10740
10752
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10741
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.80")}`
10753
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.81")}`
10742
10754
  ];
10743
10755
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10744
10756
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -14133,6 +14145,182 @@ var init_localCc = __esm({
14133
14145
  }
14134
14146
  });
14135
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
+
14136
14324
  // cli/commands/lifecycle.ts
14137
14325
  var lifecycle_exports = {};
14138
14326
  __export(lifecycle_exports, {
@@ -14245,13 +14433,13 @@ var config_exports = {};
14245
14433
  __export(config_exports, {
14246
14434
  configCommand: () => configCommand
14247
14435
  });
14248
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync17 } from "fs";
14249
- import { join as join17 } from "path";
14250
- import { homedir as homedir17 } from "os";
14251
- function readConfigEnv() {
14252
- 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 {};
14253
14441
  const out = {};
14254
- for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
14442
+ for (const line of readFileSync16(CONFIG_PATH6, "utf-8").split("\n")) {
14255
14443
  const t = line.trim();
14256
14444
  if (!t || t.startsWith("#")) continue;
14257
14445
  const eq = t.indexOf("=");
@@ -14260,11 +14448,11 @@ function readConfigEnv() {
14260
14448
  return out;
14261
14449
  }
14262
14450
  function updateConfigValue(key, value) {
14263
- if (!existsSync17(CONFIG_PATH5)) {
14451
+ if (!existsSync18(CONFIG_PATH6)) {
14264
14452
  console.error("No config found. Run `synkro install` first.");
14265
14453
  process.exit(1);
14266
14454
  }
14267
- const lines = readFileSync15(CONFIG_PATH5, "utf-8").split("\n");
14455
+ const lines = readFileSync16(CONFIG_PATH6, "utf-8").split("\n");
14268
14456
  const pattern = new RegExp(`^${key}=`);
14269
14457
  let found = false;
14270
14458
  const updated = lines.map((line) => {
@@ -14275,10 +14463,10 @@ function updateConfigValue(key, value) {
14275
14463
  return line;
14276
14464
  });
14277
14465
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
14278
- writeFileSync11(CONFIG_PATH5, updated.join("\n"), "utf-8");
14466
+ writeFileSync11(CONFIG_PATH6, updated.join("\n"), "utf-8");
14279
14467
  }
14280
14468
  async function reconcileContainer() {
14281
- const cfg = readConfigEnv();
14469
+ const cfg = readConfigEnv2();
14282
14470
  const cloudOnly = (cfg.SYNKRO_GRADING_MODE || "local") === "byok" && (cfg.SYNKRO_STORAGE_MODE || "local") === "cloud";
14283
14471
  try {
14284
14472
  const { dockerInstall: dockerInstall2, dockerSafeStop: dockerSafeStop2, readContainerConfig: readContainerConfig2, assertDockerAvailable: assertDockerAvailable2, waitForContainerReady: waitForContainerReady2 } = await Promise.resolve().then(() => (init_dockerInstall(), dockerInstall_exports));
@@ -14302,7 +14490,7 @@ async function reconcileContainer() {
14302
14490
  }
14303
14491
  async function configCommand(args2) {
14304
14492
  if (args2.length === 0) {
14305
- const config2 = readConfigEnv();
14493
+ const config2 = readConfigEnv2();
14306
14494
  console.log("Synkro config:\n");
14307
14495
  console.log(` grading: ${config2.SYNKRO_GRADING_MODE || "local"}`);
14308
14496
  console.log(` storage: ${config2.SYNKRO_STORAGE_MODE || "local"}`);
@@ -14357,7 +14545,7 @@ To change:`);
14357
14545
  process.exit(1);
14358
14546
  }
14359
14547
  const token = getAccessToken();
14360
- const config = readConfigEnv();
14548
+ const config = readConfigEnv2();
14361
14549
  const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
14362
14550
  try {
14363
14551
  const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
@@ -14380,25 +14568,25 @@ To change:`);
14380
14568
  updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
14381
14569
  console.log(`\u2713 Inference set to '${inferenceValue}'.`);
14382
14570
  }
14383
- var SYNKRO_DIR7, CONFIG_PATH5;
14571
+ var SYNKRO_DIR7, CONFIG_PATH6;
14384
14572
  var init_config = __esm({
14385
14573
  "cli/commands/config.ts"() {
14386
14574
  "use strict";
14387
14575
  init_stub();
14388
- SYNKRO_DIR7 = join17(homedir17(), ".synkro");
14389
- CONFIG_PATH5 = join17(SYNKRO_DIR7, "config.env");
14576
+ SYNKRO_DIR7 = join18(homedir18(), ".synkro");
14577
+ CONFIG_PATH6 = join18(SYNKRO_DIR7, "config.env");
14390
14578
  }
14391
14579
  });
14392
14580
 
14393
14581
  // cli/bootstrap.js
14394
- import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
14582
+ import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
14395
14583
  import { resolve as resolve3 } from "path";
14396
14584
  var envCandidates = [
14397
14585
  resolve3(process.env.HOME ?? "", ".synkro", "config.env")
14398
14586
  ];
14399
14587
  for (const envPath of envCandidates) {
14400
- if (!existsSync18(envPath)) continue;
14401
- const envContent = readFileSync16(envPath, "utf-8");
14588
+ if (!existsSync19(envPath)) continue;
14589
+ const envContent = readFileSync17(envPath, "utf-8");
14402
14590
  for (const line of envContent.split("\n")) {
14403
14591
  const trimmed = line.trim();
14404
14592
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -14413,7 +14601,7 @@ var args = process.argv.slice(2);
14413
14601
  var cmd = args[0] || "";
14414
14602
  var subArgs = args.slice(1);
14415
14603
  function printVersion() {
14416
- console.log("1.6.80");
14604
+ console.log("1.6.81");
14417
14605
  }
14418
14606
  function printHelp2() {
14419
14607
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
@@ -14487,6 +14675,11 @@ async function main() {
14487
14675
  await localCcCommand2(subArgs);
14488
14676
  break;
14489
14677
  }
14678
+ case "import": {
14679
+ const { importCommand: importCommand2 } = await Promise.resolve().then(() => (init_import(), import_exports));
14680
+ await importCommand2();
14681
+ break;
14682
+ }
14490
14683
  case "version":
14491
14684
  case "--version":
14492
14685
  case "-v": {