@synkro-sh/cli 1.6.81 → 1.6.83

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
@@ -1744,6 +1744,26 @@ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeout
1744
1744
  return String(data.result || '');
1745
1745
  }
1746
1746
 
1747
+ // Option B: when a cloud grade fails (esp. a timeout), the REAL reason lives
1748
+ // inside the container, not on the wire \u2014 the client only ever sees "timed out".
1749
+ // Pull the org's per-worker claude/cursor sick reasons + log tails from the
1750
+ // hosted /debug endpoint so the diagnostic records WHY the grade actually died
1751
+ // (e.g. a claude 401 inside the worker). Best-effort + tightly timed; null if
1752
+ // the container is itself unreachable.
1753
+ async function fetchContainerSickReason(jwt: string, timeoutMs = 2500): Promise<string | null> {
1754
+ try {
1755
+ const r = await fetch(CONTAINERS_URL + '/debug', {
1756
+ headers: { Authorization: 'Bearer ' + jwt },
1757
+ signal: AbortSignal.timeout(timeoutMs),
1758
+ });
1759
+ if (!r.ok) return 'debug ' + r.status + ': ' + (await r.text().catch(() => '')).slice(0, 400);
1760
+ const data = await r.json().catch(() => null);
1761
+ return data ? JSON.stringify(data).slice(0, 8000) : null;
1762
+ } catch {
1763
+ return null;
1764
+ }
1765
+ }
1766
+
1747
1767
  // Cloud transcript capture \u2014 the cloud mirror of the local /api/conversation-sync
1748
1768
  // + /api/session-action telemetry. Posts to the API /api/v1/cli/sync-transcripts
1749
1769
  // (the org is derived from the login JWT, same one cloud grading uses). Best-effort:
@@ -2989,35 +3009,38 @@ export async function syncConversationTranscript(
2989
3009
  const entry = JSON.parse(allLines[i]) as Record<string, unknown>;
2990
3010
  const kind = transcriptEntryType(entry);
2991
3011
  if (kind !== 'user' && kind !== 'assistant') continue;
2992
- const text = extractTranscriptEntryText(entry, 8000, sessionId);
2993
- if (!text) continue;
2994
-
2995
3012
  const msgObj = entry.message as Record<string, unknown> | undefined;
2996
3013
  const content = (msgObj && typeof msgObj === 'object' ? msgObj.content : undefined) ?? entry.content;
2997
3014
  const singleBlock = transcriptContentBlock(entry);
3015
+ // Tool calls FIRST \u2014 a tool-only assistant turn (Edit/Bash/Read, no text) is
3016
+ // most of a coding session and must still be captured; skipping on empty text
3017
+ // dropped both the turn and its tool calls.
3018
+ const blocks = (kind === 'assistant')
3019
+ ? (Array.isArray(content) ? content : (singleBlock ? [singleBlock] : []))
3020
+ : [];
3021
+ const toolCalls = blocks
3022
+ .filter((c: unknown) => {
3023
+ const b = c as Record<string, unknown>;
3024
+ return b?.type === 'tool_use' || b?.type === 'tool_call';
3025
+ })
3026
+ .map((c: unknown) => {
3027
+ const b = c as Record<string, unknown>;
3028
+ return {
3029
+ name: b.name,
3030
+ input: JSON.stringify(b.input || b.arguments || {}).slice(0, 500),
3031
+ id: b.id,
3032
+ };
3033
+ });
3034
+ const text = extractTranscriptEntryText(entry, 8000, sessionId);
3035
+ if (!text && toolCalls.length === 0) continue;
3036
+
2998
3037
  const msg: Record<string, unknown> = {
2999
3038
  message_index: i,
3000
3039
  type: kind,
3001
- content: text,
3040
+ content: text || ('\u21B3 ' + toolCalls.map((t: any) => t.name).join(', ')),
3002
3041
  ts: entry.timestamp || null,
3003
3042
  };
3004
3043
  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
3044
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
3022
3045
  // Per-turn usage + model so the session view can show input/output/cache
3023
3046
  // tokens and cost line-by-line (not just the session aggregate).
@@ -3746,35 +3769,99 @@ export function isGraderNotConfigured(errorMessage: string): boolean {
3746
3769
  return lower.includes('no worker') || lower.includes('not configured') || lower.includes('agent_kind') || lower.includes('no pool');
3747
3770
  }
3748
3771
 
3749
- export function graderUnavailableMessage(hook: string, target: string, errorMessage: string, agentKind: AgentKind = 'claude_code'): string {
3772
+ // Categorize a grader failure into a stable machine category + a human "why"
3773
+ // (with a fix hint). Shared by the terminal message and the diagnostic log so
3774
+ // the two never disagree about the reason.
3775
+ export function classifyGraderError(errorMessage: string): { category: string; why: string } {
3776
+ const e = (errorMessage || '').toLowerCase();
3777
+ const snippet = (errorMessage || '').replace(/\\s+/g, ' ').trim().slice(0, 160);
3750
3778
  if (errorMessage === 'SYNKRO_CHANNEL_DOWN') {
3751
- return hook + ' ' + target + ' \u2192 local grader unavailable (container not running), skipped';
3779
+ return { category: 'channel_down', why: 'grader container not running (run \`synkro status\`)' };
3752
3780
  }
3753
3781
  if (isGraderNotConfigured(errorMessage)) {
3754
- const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';
3755
- return hook + ' ' + target + ' \u2192 local grader not configured for ' + agent + '. Add this agent to your synkro.toml file and run \`synkro install\`.';
3782
+ return { category: 'not_configured', why: 'grader not configured for this agent \u2014 add it to synkro.toml and run \`synkro install\`' };
3756
3783
  }
3757
- // In cloud-container mode the grade ran against the hosted ingress, NOT a local
3758
- // worker \u2014 say so, and distinguish a timeout (slow/cold grade) from a true
3759
- // outage so "local grader unavailable" stops masquerading as a dead grader.
3784
+ if (/tim(e|ed)\\s*out|aborted|operation was aborted/.test(e)) {
3785
+ return { category: 'timeout', why: 'grade timed out (45s) \u2014 grader cold/slow, or its Claude token is invalid (re-run \`synkro install\`)' };
3786
+ }
3787
+ if (/\\b401\\b|unauthorized|token expired|invalid token|\\b403\\b|forbidden/.test(e)) {
3788
+ return { category: 'auth', why: 'auth rejected \u2014 session token expired (re-run \`synkro login\`, or \`synkro install\` if the grader Claude token is stale)' };
3789
+ }
3790
+ if (/econnrefused|enotfound|eai_again|fetch failed|socket|econnreset|getaddrinfo|network/.test(e)) {
3791
+ return { category: 'unreachable', why: 'grader unreachable \u2014 network or container down' };
3792
+ }
3793
+ if (/\\b5\\d\\d\\b/.test(e)) {
3794
+ return { category: 'server_error', why: 'grader server error: ' + snippet };
3795
+ }
3796
+ return { category: 'unknown', why: snippet || 'unknown error' };
3797
+ }
3798
+
3799
+ export function graderUnavailableMessage(hook: string, target: string, errorMessage: string, agentKind: AgentKind = 'claude_code'): string {
3800
+ const base = hook + ' ' + target + ' \u2192 ';
3801
+ if (isGraderNotConfigured(errorMessage)) {
3802
+ const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';
3803
+ return base + 'local grader not configured for ' + agent + '. Add this agent to your synkro.toml file and run \`synkro install\`.';
3804
+ }
3805
+ // Always surface WHY (not just "unavailable"), categorized, with a fix hint \u2014
3806
+ // the full raw error + the exact graded prompt are in grader-unavailable.log.
3807
+ const where = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud' ? 'cloud grader' : 'local grader';
3808
+ const { why } = classifyGraderError(errorMessage);
3809
+ return base + where + ' unavailable \u2014 ' + why + '; skipped (full detail: ~/.synkro/grader-unavailable.log)';
3810
+ }
3811
+
3812
+ // Diagnostic record for a skipped grade \u2014 the categorized reason AND the EXACT
3813
+ // prompt that was sent for inference (gradeInput), so you can see WHAT was being
3814
+ // graded when it failed. Routing differs by mode:
3815
+ // \u2022 CLOUD \u2192 ship to Timescale via /api/v1/cli/grader-unavailable (central, so
3816
+ // we can debug customer failures), enriched on timeout/outage with the
3817
+ // container's own claude error (option B). NOT written to the user's machine.
3818
+ // \u2022 LOCAL \u2192 the on-device ~/.synkro/grader-unavailable.log file.
3819
+ // Fire-and-forget in cloud mode: callers don't await \u2014 the pending fetch keeps
3820
+ // the hook process alive until it settles (or the watchdog ends the run).
3821
+ export async function logGraderUnavailable(
3822
+ hook: string, target: string, errorMessage: string,
3823
+ gradeInput?: string, ctx?: { sessionId?: string; repo?: string },
3824
+ ): Promise<void> {
3825
+ const { category, why } = classifyGraderError(errorMessage);
3826
+
3760
3827
  if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {
3761
- const isTimeout = /tim(e|ed)\\s*out|aborted|the operation was aborted/i.test(errorMessage);
3762
- if (isTimeout) {
3763
- return hook + ' ' + target + ' \u2192 cloud grade timed out (45s), skipped';
3828
+ try {
3829
+ const jwt = loadJwt();
3830
+ if (!jwt) return;
3831
+ // Only chase the container's internal reason for failures where it adds
3832
+ // signal (a timeout/outage hides the real claude error); skip it for fast,
3833
+ // already-explanatory failures like auth.
3834
+ let sickReason: string | null = null;
3835
+ if (category === 'timeout' || category === 'unreachable' || category === 'server_error') {
3836
+ sickReason = await fetchContainerSickReason(jwt);
3837
+ }
3838
+ await fetch(GATEWAY_URL + '/api/v1/cli/grader-unavailable', {
3839
+ method: 'POST',
3840
+ headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
3841
+ body: JSON.stringify({
3842
+ hook, target, category, why,
3843
+ error: (errorMessage || '').slice(0, 2000),
3844
+ grade_input: gradeInput ? String(gradeInput).slice(0, 20000) : null,
3845
+ sick_reason: sickReason,
3846
+ session_id: ctx?.sessionId || null,
3847
+ repo: ctx?.repo || null,
3848
+ }),
3849
+ signal: AbortSignal.timeout(3000),
3850
+ }).catch(() => {});
3851
+ } catch {
3852
+ // best-effort \u2014 never let diagnostics cascade into a hook failure
3764
3853
  }
3765
- return hook + ' ' + target + ' \u2192 cloud grader unavailable, skipped';
3854
+ return;
3766
3855
  }
3767
- return hook + ' ' + target + ' \u2192 local grader unavailable, skipped';
3768
- }
3769
3856
 
3770
- export function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {
3857
+ // Local mode: on-device diagnostic log.
3771
3858
  try {
3772
- const entry = {
3773
- ts: new Date().toISOString(),
3774
- hook,
3775
- target,
3776
- error: errorMessage.slice(0, 500),
3859
+ const entry: Record<string, unknown> = {
3860
+ ts: new Date().toISOString(), mode: 'local',
3861
+ hook, target, category, why,
3862
+ error: (errorMessage || '').slice(0, 1000),
3777
3863
  };
3864
+ if (gradeInput) entry.gradeInput = String(gradeInput).slice(0, 20000);
3778
3865
  appendFileSync(UNAVAIL_LOG, JSON.stringify(entry) + '\\n', 'utf-8');
3779
3866
  } catch {
3780
3867
  // best-effort \u2014 never let logging failure cascade into a hook failure
@@ -4034,7 +4121,7 @@ async function main() {
4034
4121
  }
4035
4122
  } catch (err) {
4036
4123
  const errMsg = (err as Error).message || String(err);
4037
- logGraderUnavailable('editGuard', fileShort, errMsg);
4124
+ logGraderUnavailable('editGuard', fileShort, errMsg, buildCombined(proposed), { sessionId, repo: gitRepo });
4038
4125
  outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('editGuard', fileShort, errMsg, graderPool) });
4039
4126
  return;
4040
4127
  }
@@ -4108,7 +4195,7 @@ async function main() {
4108
4195
  gradeResp = await localGrade('edit', graderPrompt, undefined, graderPool);
4109
4196
  } catch (err) {
4110
4197
  const errMsg = (err as Error).message || String(err);
4111
- logGraderUnavailable('editGuard', fileShort, errMsg);
4198
+ logGraderUnavailable('editGuard', fileShort, errMsg, graderPrompt, { sessionId, repo: gitRepo });
4112
4199
  outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('editGuard', fileShort, errMsg, graderPool) });
4113
4200
  return;
4114
4201
  }
@@ -4675,7 +4762,7 @@ async function main() {
4675
4762
  gradeResponses = [resp1, resp2];
4676
4763
  } catch (gradeErr: any) {
4677
4764
  const reason = gradeErr?.message || String(gradeErr);
4678
- logGraderUnavailable('cweGuard', fileShort, reason);
4765
+ logGraderUnavailable('cweGuard', fileShort, reason, buildCwePrompt(cweContent), { sessionId, repo: gitRepo });
4679
4766
  outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });
4680
4767
  return;
4681
4768
  }
@@ -4684,7 +4771,7 @@ async function main() {
4684
4771
  gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent), graderPool)];
4685
4772
  } catch (gradeErr: any) {
4686
4773
  const reason = gradeErr?.message || String(gradeErr);
4687
- logGraderUnavailable('cweGuard', fileShort, reason);
4774
+ logGraderUnavailable('cweGuard', fileShort, reason, buildCwePrompt(cweContent), { sessionId, repo: gitRepo });
4688
4775
  outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });
4689
4776
  return;
4690
4777
  }
@@ -5432,7 +5519,7 @@ async function main() {
5432
5519
  gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);
5433
5520
  } catch (err) {
5434
5521
  const errMsg = (err as Error).message || String(err);
5435
- logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', errMsg);
5522
+ logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', errMsg, graderPrompt, { sessionId, repo: gitRepo });
5436
5523
  if (scanConcern) {
5437
5524
  const ctx = scanBlockContext + ' Synkro flagged this install but the grader is unavailable to check consent — ask the user for explicit consent, then retry.';
5438
5525
  outputJson({
@@ -5650,7 +5737,7 @@ async function main() {
5650
5737
  gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);
5651
5738
  } catch (err) {
5652
5739
  const errMsg = (err as Error).message || String(err);
5653
- logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), errMsg);
5740
+ logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), errMsg, graderPrompt, { sessionId, repo: gitRepo });
5654
5741
  outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('agentGuard', subagentType || 'agent', errMsg, graderPool) });
5655
5742
  return;
5656
5743
  }
@@ -5741,7 +5828,7 @@ import {
5741
5828
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
5742
5829
  parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,
5743
5830
  outputJson, outputEmpty, setupCursorHookSignals, installHookWatchdog, isPlanTool, hookSessionId, GATEWAY_URL,
5744
- filterRules, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,
5831
+ filterRules, graderUnavailableMessage, logGraderUnavailable, resolveTranscriptPath, isCursorInvokingCcHook,
5745
5832
  loadSynkroFile, effectiveGraderPool, synkroFilePresent, noSynkroSkipMessage,
5746
5833
  } from './_synkro-common.ts';
5747
5834
  import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
@@ -5861,7 +5948,9 @@ async function main() {
5861
5948
  try {
5862
5949
  gradeResp = await localGrade('plan', graderPrompt, undefined, graderPool);
5863
5950
  } catch (err) {
5864
- outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('planReview', 'plan', (err as Error).message || String(err), graderPool) });
5951
+ const errMsg = (err as Error).message || String(err);
5952
+ logGraderUnavailable('planReview', 'plan', errMsg, graderPrompt, { sessionId, repo: gitRepo });
5953
+ outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('planReview', 'plan', errMsg, graderPool) });
5865
5954
  return;
5866
5955
  }
5867
5956
 
@@ -6475,7 +6564,7 @@ async function main() {
6475
6564
  try {
6476
6565
  gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS, graderPool);
6477
6566
  } catch (e) {
6478
- logGraderUnavailable('bashGuard', command.slice(0, 200), (e as Error).message || String(e));
6567
+ logGraderUnavailable('bashGuard', command.slice(0, 200), (e as Error).message || String(e), graderPrompt, { sessionId, repo });
6479
6568
  if (scanConcern) {
6480
6569
  // Grader unavailable to run the consent check \u2014 fail closed on a
6481
6570
  // scanner-flagged install (ask-mode so the user can still consent).
@@ -10750,7 +10839,7 @@ function writeConfigEnv(opts) {
10750
10839
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10751
10840
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10752
10841
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10753
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.81")}`
10842
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.83")}`
10754
10843
  ];
10755
10844
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10756
10845
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -14169,11 +14258,17 @@ function readConfigEnv() {
14169
14258
  return out;
14170
14259
  }
14171
14260
  function projectsFolder() {
14172
- const sanitized = "-" + process.cwd().replace(/\//g, "-");
14261
+ const sanitized = process.cwd().replace(/\//g, "-");
14173
14262
  const dir = join17(homedir17(), ".claude", "projects", sanitized);
14174
14263
  return existsSync17(dir) ? dir : null;
14175
14264
  }
14176
14265
  function repoName() {
14266
+ try {
14267
+ const url = execSync7("git config --get remote.origin.url", { encoding: "utf-8" }).trim();
14268
+ const m = url.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
14269
+ if (m) return m[1];
14270
+ } catch {
14271
+ }
14177
14272
  try {
14178
14273
  return execSync7("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim().split("/").pop() || "repo";
14179
14274
  } catch {
@@ -14203,11 +14298,13 @@ function parseSession(filePath, sessionId) {
14203
14298
  if (!kind) continue;
14204
14299
  const msgObj = e.message;
14205
14300
  const text = extractText(msgObj?.content ?? e.content);
14206
- if (!text.trim()) continue;
14301
+ const blocks = kind === "assistant" && Array.isArray(msgObj?.content) ? msgObj.content : [];
14302
+ 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 }));
14303
+ if (!text.trim() && toolCalls.length === 0) continue;
14207
14304
  const msg = {
14208
14305
  message_index: i,
14209
14306
  type: kind,
14210
- content: text.slice(0, 8e3),
14307
+ content: text.trim() ? text.slice(0, 8e3) : "\u21B3 " + toolCalls.map((t) => t.name).join(", "),
14211
14308
  timestamp: e.timestamp || null
14212
14309
  };
14213
14310
  if (kind === "assistant") {
@@ -14221,8 +14318,6 @@ function parseSession(filePath, sessionId) {
14221
14318
  cache_read_input_tokens: Number(u.cache_read_input_tokens) || 0
14222
14319
  };
14223
14320
  }
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
14321
  if (toolCalls.length) {
14227
14322
  msg.tool_calls = toolCalls;
14228
14323
  for (const t of toolCalls) actions.push({ step: ++step, tool: t.name, summary: "", file: null, outcome: null });
@@ -14272,25 +14367,29 @@ async function importCommand() {
14272
14367
  return;
14273
14368
  }
14274
14369
  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);
14370
+ for (const s of sessions) {
14277
14371
  try {
14278
14372
  const r = await fetch(`${gateway}/api/v1/cli/sync-transcripts`, {
14279
14373
  method: "POST",
14280
14374
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
14281
- body: JSON.stringify({ repo, sessions: batch }),
14282
- signal: AbortSignal.timeout(3e4)
14375
+ body: JSON.stringify({ repo, sessions: [s] }),
14376
+ signal: AbortSignal.timeout(6e4)
14283
14377
  });
14284
- if (r.ok) ok += batch.length;
14285
- else {
14286
- fail += batch.length;
14287
- console.warn(` batch failed: HTTP ${r.status}`);
14378
+ if (r.ok) {
14379
+ ok++;
14380
+ process.stdout.write(".");
14381
+ } else {
14382
+ fail++;
14383
+ console.warn(`
14384
+ ${String(s.cc_session_id).slice(0, 8)} failed: HTTP ${r.status}`);
14288
14385
  }
14289
14386
  } catch (e) {
14290
- fail += batch.length;
14291
- console.warn(` batch error: ${e.message}`);
14387
+ fail++;
14388
+ console.warn(`
14389
+ ${String(s.cc_session_id).slice(0, 8)} error: ${e.message}`);
14292
14390
  }
14293
14391
  }
14392
+ process.stdout.write("\n");
14294
14393
  } else {
14295
14394
  const port = config.SYNKRO_MCP_PORT || "18931";
14296
14395
  for (const s of sessions) {
@@ -14601,7 +14700,7 @@ var args = process.argv.slice(2);
14601
14700
  var cmd = args[0] || "";
14602
14701
  var subArgs = args.slice(1);
14603
14702
  function printVersion() {
14604
- console.log("1.6.81");
14703
+ console.log("1.6.83");
14605
14704
  }
14606
14705
  function printHelp2() {
14607
14706
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents