@synkro-sh/cli 1.6.80 → 1.6.82

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
@@ -2989,36 +2989,51 @@ export async function syncConversationTranscript(
2989
2989
  const entry = JSON.parse(allLines[i]) as Record<string, unknown>;
2990
2990
  const kind = transcriptEntryType(entry);
2991
2991
  if (kind !== 'user' && kind !== 'assistant') continue;
2992
- const text = extractTranscriptEntryText(entry, 8000, sessionId);
2993
- if (!text) continue;
2994
-
2995
2992
  const msgObj = entry.message as Record<string, unknown> | undefined;
2996
2993
  const content = (msgObj && typeof msgObj === 'object' ? msgObj.content : undefined) ?? entry.content;
2997
2994
  const singleBlock = transcriptContentBlock(entry);
2995
+ // Tool calls FIRST \u2014 a tool-only assistant turn (Edit/Bash/Read, no text) is
2996
+ // most of a coding session and must still be captured; skipping on empty text
2997
+ // dropped both the turn and its tool calls.
2998
+ const blocks = (kind === 'assistant')
2999
+ ? (Array.isArray(content) ? content : (singleBlock ? [singleBlock] : []))
3000
+ : [];
3001
+ const toolCalls = blocks
3002
+ .filter((c: unknown) => {
3003
+ const b = c as Record<string, unknown>;
3004
+ return b?.type === 'tool_use' || b?.type === 'tool_call';
3005
+ })
3006
+ .map((c: unknown) => {
3007
+ const b = c as Record<string, unknown>;
3008
+ return {
3009
+ name: b.name,
3010
+ input: JSON.stringify(b.input || b.arguments || {}).slice(0, 500),
3011
+ id: b.id,
3012
+ };
3013
+ });
3014
+ const text = extractTranscriptEntryText(entry, 8000, sessionId);
3015
+ if (!text && toolCalls.length === 0) continue;
3016
+
2998
3017
  const msg: Record<string, unknown> = {
2999
3018
  message_index: i,
3000
3019
  type: kind,
3001
- content: text,
3020
+ content: text || ('\u21B3 ' + toolCalls.map((t: any) => t.name).join(', ')),
3002
3021
  ts: entry.timestamp || null,
3003
3022
  };
3004
3023
  if (kind === 'assistant') {
3005
- const blocks = Array.isArray(content)
3006
- ? content
3007
- : (singleBlock ? [singleBlock] : []);
3008
- const toolCalls = blocks
3009
- .filter((c: unknown) => {
3010
- const b = c as Record<string, unknown>;
3011
- return b?.type === 'tool_use' || b?.type === 'tool_call';
3012
- })
3013
- .map((c: unknown) => {
3014
- const b = c as Record<string, unknown>;
3015
- return {
3016
- name: b.name,
3017
- input: JSON.stringify(b.input || b.arguments || {}).slice(0, 500),
3018
- id: b.id,
3019
- };
3020
- });
3021
3024
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
3025
+ // Per-turn usage + model so the session view can show input/output/cache
3026
+ // tokens and cost line-by-line (not just the session aggregate).
3027
+ if (msgObj && typeof msgObj.model === 'string') msg.model = msgObj.model;
3028
+ const u = msgObj && (msgObj.usage as Record<string, unknown> | undefined);
3029
+ if (u && typeof u === 'object') {
3030
+ msg.usage = {
3031
+ input_tokens: Number(u.input_tokens) || 0,
3032
+ output_tokens: Number(u.output_tokens) || 0,
3033
+ cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,
3034
+ cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0,
3035
+ };
3036
+ }
3022
3037
  if (msgObj && typeof msgObj === 'object') {
3023
3038
  msg.model = msgObj.model || null;
3024
3039
  const u = msgObj.usage as Record<string, unknown> | undefined;
@@ -10466,7 +10481,7 @@ async function promptAgentSelection(detected) {
10466
10481
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
10467
10482
  console.log(` ${detected.length + 1}. Both / all (default)`);
10468
10483
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10469
- const ask2 = () => new Promise((resolve4) => {
10484
+ const ask3 = () => new Promise((resolve4) => {
10470
10485
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
10471
10486
  const t = answer.trim().toLowerCase();
10472
10487
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
@@ -10479,10 +10494,10 @@ async function promptAgentSelection(detected) {
10479
10494
  return resolve4([detected[n - 1]]);
10480
10495
  }
10481
10496
  console.log("Invalid choice. Try again.");
10482
- resolve4(ask2());
10497
+ resolve4(ask3());
10483
10498
  });
10484
10499
  });
10485
- return ask2();
10500
+ return ask3();
10486
10501
  }
10487
10502
  async function promptCursorApiKey(opts) {
10488
10503
  const { cursorApiKeyConfigured: cursorApiKeyConfigured2, writeCursorApiKey: writeCursorApiKey2, validateCursorApiKey: validateCursorApiKey2 } = await Promise.resolve().then(() => (init_macKeychain(), macKeychain_exports));
@@ -10534,7 +10549,7 @@ async function promptDeployLocation() {
10534
10549
  async function promptTranscriptSources(wantCC, wantCursor) {
10535
10550
  if (!wantCC && !wantCursor) return { cc: false, cursor: false };
10536
10551
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
10537
- const ask2 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));
10552
+ const ask3 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));
10538
10553
  try {
10539
10554
  if (wantCC && wantCursor) {
10540
10555
  console.log("Import and embed past session history? This indexes sessions so Ask Synkro");
@@ -10543,14 +10558,14 @@ async function promptTranscriptSources(wantCC, wantCursor) {
10543
10558
  console.log(" 2. Claude Code only");
10544
10559
  console.log(" 3. Both");
10545
10560
  console.log(" n. Skip");
10546
- const a2 = await ask2("Choose [1/2/3/n] (default: 3): ");
10561
+ const a2 = await ask3("Choose [1/2/3/n] (default: 3): ");
10547
10562
  if (a2 === "n" || a2 === "no") return { cc: false, cursor: false };
10548
10563
  if (a2 === "1") return { cc: false, cursor: true };
10549
10564
  if (a2 === "2") return { cc: true, cursor: false };
10550
10565
  return { cc: true, cursor: true };
10551
10566
  }
10552
10567
  const which2 = wantCursor ? "Cursor" : "Claude Code";
10553
- const a = await ask2(
10568
+ const a = await ask3(
10554
10569
  `Import and embed your ${which2} session history?
10555
10570
  This indexes past sessions so Ask Synkro can answer questions about your
10556
10571
  coding patterns and the dashboard shows full history. (Y/n) `
@@ -10738,7 +10753,7 @@ function writeConfigEnv(opts) {
10738
10753
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10739
10754
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10740
10755
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10741
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.80")}`
10756
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.82")}`
10742
10757
  ];
10743
10758
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10744
10759
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -14133,6 +14148,192 @@ var init_localCc = __esm({
14133
14148
  }
14134
14149
  });
14135
14150
 
14151
+ // cli/commands/import.ts
14152
+ var import_exports = {};
14153
+ __export(import_exports, {
14154
+ importCommand: () => importCommand
14155
+ });
14156
+ import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync5 } from "fs";
14157
+ import { homedir as homedir17 } from "os";
14158
+ import { join as join17 } from "path";
14159
+ import { execSync as execSync7 } from "child_process";
14160
+ import { createInterface as createInterface5 } from "readline";
14161
+ function readConfigEnv() {
14162
+ const out = {};
14163
+ try {
14164
+ for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
14165
+ const t = line.trim();
14166
+ if (!t || t.startsWith("#")) continue;
14167
+ const eq = t.indexOf("=");
14168
+ if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
14169
+ }
14170
+ } catch {
14171
+ }
14172
+ return out;
14173
+ }
14174
+ function projectsFolder() {
14175
+ const sanitized = process.cwd().replace(/\//g, "-");
14176
+ const dir = join17(homedir17(), ".claude", "projects", sanitized);
14177
+ return existsSync17(dir) ? dir : null;
14178
+ }
14179
+ function repoName() {
14180
+ try {
14181
+ const url = execSync7("git config --get remote.origin.url", { encoding: "utf-8" }).trim();
14182
+ const m = url.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
14183
+ if (m) return m[1];
14184
+ } catch {
14185
+ }
14186
+ try {
14187
+ return execSync7("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim().split("/").pop() || "repo";
14188
+ } catch {
14189
+ return process.cwd().split("/").pop() || "repo";
14190
+ }
14191
+ }
14192
+ function extractText(content) {
14193
+ if (typeof content === "string") return content;
14194
+ if (Array.isArray(content)) {
14195
+ return content.map((b) => typeof b === "string" ? b : b?.type === "text" ? b.text || "" : "").filter(Boolean).join("\n");
14196
+ }
14197
+ return "";
14198
+ }
14199
+ function parseSession(filePath, sessionId) {
14200
+ const lines = readFileSync15(filePath, "utf-8").split("\n").filter(Boolean);
14201
+ const messages = [];
14202
+ const actions = [];
14203
+ let step = 0;
14204
+ for (let i = 0; i < lines.length; i++) {
14205
+ let e;
14206
+ try {
14207
+ e = JSON.parse(lines[i]);
14208
+ } catch {
14209
+ continue;
14210
+ }
14211
+ const kind = e.type === "assistant" || e.message?.role === "assistant" ? "assistant" : e.type === "user" || e.message?.role === "user" ? "user" : "";
14212
+ if (!kind) continue;
14213
+ const msgObj = e.message;
14214
+ const text = extractText(msgObj?.content ?? e.content);
14215
+ const blocks = kind === "assistant" && Array.isArray(msgObj?.content) ? msgObj.content : [];
14216
+ 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 }));
14217
+ if (!text.trim() && toolCalls.length === 0) continue;
14218
+ const msg = {
14219
+ message_index: i,
14220
+ type: kind,
14221
+ content: text.trim() ? text.slice(0, 8e3) : "\u21B3 " + toolCalls.map((t) => t.name).join(", "),
14222
+ timestamp: e.timestamp || null
14223
+ };
14224
+ if (kind === "assistant") {
14225
+ if (typeof msgObj?.model === "string") msg.model = msgObj.model;
14226
+ const u = msgObj?.usage;
14227
+ if (u && typeof u === "object") {
14228
+ msg.usage = {
14229
+ input_tokens: Number(u.input_tokens) || 0,
14230
+ output_tokens: Number(u.output_tokens) || 0,
14231
+ cache_creation_input_tokens: Number(u.cache_creation_input_tokens) || 0,
14232
+ cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0
14233
+ };
14234
+ }
14235
+ if (toolCalls.length) {
14236
+ msg.tool_calls = toolCalls;
14237
+ for (const t of toolCalls) actions.push({ step: ++step, tool: t.name, summary: "", file: null, outcome: null });
14238
+ }
14239
+ }
14240
+ messages.push(msg);
14241
+ }
14242
+ return { cc_session_id: sessionId, messages, actions };
14243
+ }
14244
+ function ask2(q) {
14245
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
14246
+ return new Promise((resolve4) => rl.question(q, (a) => {
14247
+ rl.close();
14248
+ resolve4(/^y(es)?$/i.test(a.trim()));
14249
+ }));
14250
+ }
14251
+ async function importCommand() {
14252
+ const config = readConfigEnv();
14253
+ const isCloud = config.SYNKRO_DEPLOY_LOCATION === "cloud" || config.SYNKRO_STORAGE_MODE === "cloud";
14254
+ const repo = repoName();
14255
+ const dir = projectsFolder();
14256
+ if (!dir) {
14257
+ console.log("No Claude Code transcripts found for this repo (~/.claude/projects).");
14258
+ return;
14259
+ }
14260
+ const files = readdirSync5(dir).filter((f) => f.endsWith(".jsonl"));
14261
+ if (!files.length) {
14262
+ console.log("No sessions to import.");
14263
+ return;
14264
+ }
14265
+ console.log(`Found ${files.length} session(s) for "${repo}" (${isCloud ? "cloud" : "local"} mode).`);
14266
+ if (!isCloud) {
14267
+ const ok2 = await ask2(`Import all ${files.length} historical session(s) into your on-device store? [y/N] `);
14268
+ if (!ok2) {
14269
+ console.log("Aborted \u2014 nothing imported.");
14270
+ return;
14271
+ }
14272
+ }
14273
+ const sessions = files.map((f) => parseSession(join17(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
14274
+ const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
14275
+ let ok = 0, fail = 0;
14276
+ if (isCloud) {
14277
+ await ensureValidToken();
14278
+ const token = getAccessToken();
14279
+ if (!token) {
14280
+ console.log("Not authenticated \u2014 run `synkro login` first.");
14281
+ return;
14282
+ }
14283
+ const gateway = config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh";
14284
+ for (const s of sessions) {
14285
+ try {
14286
+ const r = await fetch(`${gateway}/api/v1/cli/sync-transcripts`, {
14287
+ method: "POST",
14288
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
14289
+ body: JSON.stringify({ repo, sessions: [s] }),
14290
+ signal: AbortSignal.timeout(6e4)
14291
+ });
14292
+ if (r.ok) {
14293
+ ok++;
14294
+ process.stdout.write(".");
14295
+ } else {
14296
+ fail++;
14297
+ console.warn(`
14298
+ ${String(s.cc_session_id).slice(0, 8)} failed: HTTP ${r.status}`);
14299
+ }
14300
+ } catch (e) {
14301
+ fail++;
14302
+ console.warn(`
14303
+ ${String(s.cc_session_id).slice(0, 8)} error: ${e.message}`);
14304
+ }
14305
+ }
14306
+ process.stdout.write("\n");
14307
+ } else {
14308
+ const port = config.SYNKRO_MCP_PORT || "18931";
14309
+ for (const s of sessions) {
14310
+ try {
14311
+ const r = await fetch(`http://127.0.0.1:${port}/api/ingest`, {
14312
+ method: "POST",
14313
+ headers: { "Content-Type": "application/json" },
14314
+ body: JSON.stringify({ capture_type: "transcript_sync", session_id: s.cc_session_id, repo, messages: s.messages, actions: s.actions }),
14315
+ signal: AbortSignal.timeout(15e3)
14316
+ });
14317
+ if (r.ok) ok++;
14318
+ else fail++;
14319
+ } catch {
14320
+ fail++;
14321
+ }
14322
+ }
14323
+ if (fail && ok === 0) console.log(" (is the local container running? `synkro status`)");
14324
+ }
14325
+ console.log(`
14326
+ \u2713 Imported ${ok} session(s) (${totalMsgs} messages)${fail ? `, ${fail} failed` : ""}.`);
14327
+ }
14328
+ var CONFIG_PATH5;
14329
+ var init_import = __esm({
14330
+ "cli/commands/import.ts"() {
14331
+ "use strict";
14332
+ init_stub();
14333
+ CONFIG_PATH5 = join17(homedir17(), ".synkro", "config.env");
14334
+ }
14335
+ });
14336
+
14136
14337
  // cli/commands/lifecycle.ts
14137
14338
  var lifecycle_exports = {};
14138
14339
  __export(lifecycle_exports, {
@@ -14245,13 +14446,13 @@ var config_exports = {};
14245
14446
  __export(config_exports, {
14246
14447
  configCommand: () => configCommand
14247
14448
  });
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 {};
14449
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, existsSync as existsSync18 } from "fs";
14450
+ import { join as join18 } from "path";
14451
+ import { homedir as homedir18 } from "os";
14452
+ function readConfigEnv2() {
14453
+ if (!existsSync18(CONFIG_PATH6)) return {};
14253
14454
  const out = {};
14254
- for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
14455
+ for (const line of readFileSync16(CONFIG_PATH6, "utf-8").split("\n")) {
14255
14456
  const t = line.trim();
14256
14457
  if (!t || t.startsWith("#")) continue;
14257
14458
  const eq = t.indexOf("=");
@@ -14260,11 +14461,11 @@ function readConfigEnv() {
14260
14461
  return out;
14261
14462
  }
14262
14463
  function updateConfigValue(key, value) {
14263
- if (!existsSync17(CONFIG_PATH5)) {
14464
+ if (!existsSync18(CONFIG_PATH6)) {
14264
14465
  console.error("No config found. Run `synkro install` first.");
14265
14466
  process.exit(1);
14266
14467
  }
14267
- const lines = readFileSync15(CONFIG_PATH5, "utf-8").split("\n");
14468
+ const lines = readFileSync16(CONFIG_PATH6, "utf-8").split("\n");
14268
14469
  const pattern = new RegExp(`^${key}=`);
14269
14470
  let found = false;
14270
14471
  const updated = lines.map((line) => {
@@ -14275,10 +14476,10 @@ function updateConfigValue(key, value) {
14275
14476
  return line;
14276
14477
  });
14277
14478
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
14278
- writeFileSync11(CONFIG_PATH5, updated.join("\n"), "utf-8");
14479
+ writeFileSync11(CONFIG_PATH6, updated.join("\n"), "utf-8");
14279
14480
  }
14280
14481
  async function reconcileContainer() {
14281
- const cfg = readConfigEnv();
14482
+ const cfg = readConfigEnv2();
14282
14483
  const cloudOnly = (cfg.SYNKRO_GRADING_MODE || "local") === "byok" && (cfg.SYNKRO_STORAGE_MODE || "local") === "cloud";
14283
14484
  try {
14284
14485
  const { dockerInstall: dockerInstall2, dockerSafeStop: dockerSafeStop2, readContainerConfig: readContainerConfig2, assertDockerAvailable: assertDockerAvailable2, waitForContainerReady: waitForContainerReady2 } = await Promise.resolve().then(() => (init_dockerInstall(), dockerInstall_exports));
@@ -14302,7 +14503,7 @@ async function reconcileContainer() {
14302
14503
  }
14303
14504
  async function configCommand(args2) {
14304
14505
  if (args2.length === 0) {
14305
- const config2 = readConfigEnv();
14506
+ const config2 = readConfigEnv2();
14306
14507
  console.log("Synkro config:\n");
14307
14508
  console.log(` grading: ${config2.SYNKRO_GRADING_MODE || "local"}`);
14308
14509
  console.log(` storage: ${config2.SYNKRO_STORAGE_MODE || "local"}`);
@@ -14357,7 +14558,7 @@ To change:`);
14357
14558
  process.exit(1);
14358
14559
  }
14359
14560
  const token = getAccessToken();
14360
- const config = readConfigEnv();
14561
+ const config = readConfigEnv2();
14361
14562
  const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
14362
14563
  try {
14363
14564
  const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
@@ -14380,25 +14581,25 @@ To change:`);
14380
14581
  updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
14381
14582
  console.log(`\u2713 Inference set to '${inferenceValue}'.`);
14382
14583
  }
14383
- var SYNKRO_DIR7, CONFIG_PATH5;
14584
+ var SYNKRO_DIR7, CONFIG_PATH6;
14384
14585
  var init_config = __esm({
14385
14586
  "cli/commands/config.ts"() {
14386
14587
  "use strict";
14387
14588
  init_stub();
14388
- SYNKRO_DIR7 = join17(homedir17(), ".synkro");
14389
- CONFIG_PATH5 = join17(SYNKRO_DIR7, "config.env");
14589
+ SYNKRO_DIR7 = join18(homedir18(), ".synkro");
14590
+ CONFIG_PATH6 = join18(SYNKRO_DIR7, "config.env");
14390
14591
  }
14391
14592
  });
14392
14593
 
14393
14594
  // cli/bootstrap.js
14394
- import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
14595
+ import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
14395
14596
  import { resolve as resolve3 } from "path";
14396
14597
  var envCandidates = [
14397
14598
  resolve3(process.env.HOME ?? "", ".synkro", "config.env")
14398
14599
  ];
14399
14600
  for (const envPath of envCandidates) {
14400
- if (!existsSync18(envPath)) continue;
14401
- const envContent = readFileSync16(envPath, "utf-8");
14601
+ if (!existsSync19(envPath)) continue;
14602
+ const envContent = readFileSync17(envPath, "utf-8");
14402
14603
  for (const line of envContent.split("\n")) {
14403
14604
  const trimmed = line.trim();
14404
14605
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -14413,7 +14614,7 @@ var args = process.argv.slice(2);
14413
14614
  var cmd = args[0] || "";
14414
14615
  var subArgs = args.slice(1);
14415
14616
  function printVersion() {
14416
- console.log("1.6.80");
14617
+ console.log("1.6.82");
14417
14618
  }
14418
14619
  function printHelp2() {
14419
14620
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
@@ -14487,6 +14688,11 @@ async function main() {
14487
14688
  await localCcCommand2(subArgs);
14488
14689
  break;
14489
14690
  }
14691
+ case "import": {
14692
+ const { importCommand: importCommand2 } = await Promise.resolve().then(() => (init_import(), import_exports));
14693
+ await importCommand2();
14694
+ break;
14695
+ }
14490
14696
  case "version":
14491
14697
  case "--version":
14492
14698
  case "-v": {