@synkro-sh/cli 1.6.30 → 1.6.31

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
@@ -2351,12 +2351,10 @@ export async function pushConversationMessage(
2351
2351
  type: role,
2352
2352
  content: text,
2353
2353
  ts: new Date().toISOString(),
2354
+ message_index: seq,
2354
2355
  patch_redacted: opts.patchRedacted ?? true,
2355
2356
  }],
2356
2357
  };
2357
- if (!opts.patchRedacted) {
2358
- (body.messages as Record<string, unknown>[])[0].message_index = seq;
2359
- }
2360
2358
  const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {
2361
2359
  method: 'POST',
2362
2360
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },
@@ -2493,10 +2491,13 @@ export async function syncConversationTranscript(
2493
2491
  } catch {}
2494
2492
  }
2495
2493
 
2496
- writeFileSync(offsetFile, String(totalLines), 'utf-8');
2497
- if (messages.length === 0) return { ingested: 0, messages: [] };
2494
+ if (messages.length === 0) {
2495
+ writeFileSync(offsetFile, String(totalLines), 'utf-8');
2496
+ return { ingested: 0, messages: [] };
2497
+ }
2498
2498
 
2499
- const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
2499
+ const rawPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);
2500
+ const mcpPort = (rawPort > 0 && rawPort < 65536) ? rawPort : 18931;
2500
2501
  let mcpToken = '';
2501
2502
  try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}
2502
2503
  if (!mcpToken) return { ingested: 0, messages };
@@ -2509,6 +2510,7 @@ export async function syncConversationTranscript(
2509
2510
  signal: AbortSignal.timeout(5000),
2510
2511
  });
2511
2512
  if (resp.ok) {
2513
+ writeFileSync(offsetFile, String(totalLines), 'utf-8');
2512
2514
  const data = await resp.json() as { ingested?: number };
2513
2515
  return { ingested: data.ingested ?? messages.length, messages };
2514
2516
  }
@@ -3042,7 +3044,17 @@ export function hookSessionId(payload: Record<string, unknown>): string {
3042
3044
  }
3043
3045
 
3044
3046
  export function isCursorHookFormat(): boolean {
3045
- return process.env.SYNKRO_HOOK_FORMAT === 'cursor';
3047
+ return process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor');
3048
+ }
3049
+
3050
+ // Cursor reads CC hooks from ~/.claude/settings.json and fires them alongside
3051
+ // its own ~/.cursor/hooks.json entries. When that happens, agentKind is
3052
+ // 'claude_code' but the payload model is non-Claude (e.g. gpt-5.5).
3053
+ // Return true so the CC hook can bail out and let Cursor's hooks handle it.
3054
+ export function isCursorInvokingCcHook(agentKind: string, model: string): boolean {
3055
+ if (agentKind === 'cursor') return false;
3056
+ if (!model || model === 'unknown' || model === '') return false;
3057
+ return !model.startsWith('claude-');
3046
3058
  }
3047
3059
 
3048
3060
  let cursorHookExited = false;
@@ -3142,13 +3154,13 @@ import {
3142
3154
  appendSessionAction, readSessionLog, compressSessionLog, log,
3143
3155
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
3144
3156
  logGraderUnavailable, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,
3145
- captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath,
3157
+ captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath, isCursorInvokingCcHook,
3146
3158
  type HookConfig, type Rule,
3147
3159
  } from './_synkro-common.ts';
3148
3160
  import { existsSync, readFileSync } from 'node:fs';
3149
3161
  import { basename, join } from 'node:path';
3150
3162
 
3151
- const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
3163
+ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';
3152
3164
 
3153
3165
  async function main() {
3154
3166
  setupCursorHookSignals();
@@ -3235,6 +3247,8 @@ async function main() {
3235
3247
  ? cursorModelFromPayload(payload)
3236
3248
  : (transcript.ccModel || String(payload.model ?? payload.model_id ?? ''));
3237
3249
 
3250
+ if (isCursorInvokingCcHook(agentKind, captureModel)) { outputEmpty(); return; }
3251
+
3238
3252
  // Model detection: prefer transcript (CC), fall back to payload (Cursor)
3239
3253
  if (!transcript.ccModel) {
3240
3254
  transcript.ccModel = captureModel;
@@ -3392,12 +3406,12 @@ import {
3392
3406
  localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
3393
3407
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, isShellTool, isCursorHookFormat,
3394
3408
  extractShellCodeWrites, hookSessionId, filePathFromToolInput, emitBlockScanFindings, dispatchFinding, dispatchCapture, GATEWAY_URL,
3395
- logGraderUnavailable, resolveTranscriptPath,
3409
+ logGraderUnavailable, resolveTranscriptPath, isCursorInvokingCcHook,
3396
3410
  } from './_synkro-common.ts';
3397
3411
  import { basename, extname, resolve, join, dirname } from 'node:path';
3398
3412
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
3399
3413
 
3400
- const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
3414
+ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';
3401
3415
 
3402
3416
  function detectModel(payload: Record<string, unknown>): string {
3403
3417
  const raw = String(payload.model ?? payload.model_id ?? '');
@@ -3543,6 +3557,8 @@ async function main() {
3543
3557
  const shellCommand = typeof payload.command === 'string' ? payload.command.trim() : '';
3544
3558
  const ccModel = detectModel(payload);
3545
3559
 
3560
+ if (isCursorInvokingCcHook(agentKind, ccModel)) { outputEmpty(); return; }
3561
+
3546
3562
  const targets: CweScanTarget[] = [];
3547
3563
 
3548
3564
  if (isCursorHookFormat() && (shellCommand || isShellTool(toolName))) {
@@ -3946,6 +3962,7 @@ import {
3946
3962
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
3947
3963
  reconstructContent, readStdin, findNearestDeps, filePathFromToolInput, log,
3948
3964
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, dispatchCapture, dispatchScanResult, extractTranscript, emitBlockScanFindings, resolveTranscriptPath, GATEWAY_URL,
3965
+ isCursorHookFormat,
3949
3966
  } from './_synkro-common.ts';
3950
3967
  import { basename } from 'node:path';
3951
3968
  import { readFileSync } from 'node:fs';
@@ -3976,6 +3993,9 @@ async function main() {
3976
3993
  return;
3977
3994
  }
3978
3995
 
3996
+ const _m = String(payload.model ?? payload.model_id ?? '');
3997
+ if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }
3998
+
3979
3999
  const toolInput = payload.tool_input || {};
3980
4000
  const sessionId = hookSessionId(payload);
3981
4001
  const workspaceRoots = Array.isArray(payload.workspace_roots) ? payload.workspace_roots as string[] : [];
@@ -4238,6 +4258,9 @@ async function main() {
4238
4258
  if (!input.trim()) { outputEmpty(); return; }
4239
4259
 
4240
4260
  const payload = JSON.parse(input);
4261
+ const _m = String(payload.model ?? payload.model_id ?? '');
4262
+ if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }
4263
+
4241
4264
  const toolInput = payload.tool_input || {};
4242
4265
  const command = typeof payload.command === 'string' ? payload.command : (toolInput.command || '');
4243
4266
  if (!command) { outputEmpty(); return; }
@@ -4340,7 +4363,7 @@ import {
4340
4363
  extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
4341
4364
  outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
4342
4365
  logGraderUnavailable, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,
4343
- hashCommand, resolveTranscriptPath,
4366
+ hashCommand, resolveTranscriptPath, isCursorHookFormat,
4344
4367
  type HookConfig, type Rule,
4345
4368
  } from './_synkro-common.ts';
4346
4369
  import { createHash } from 'node:crypto';
@@ -4391,6 +4414,9 @@ async function main() {
4391
4414
  return;
4392
4415
  }
4393
4416
 
4417
+ const _m = String(payload.model ?? payload.model_id ?? '');
4418
+ if (!isCursorHookFormat() && _m && !_m.startsWith('claude-')) { outputEmpty(); return; }
4419
+
4394
4420
  const toolInput = payload.tool_input || {};
4395
4421
  const sessionId = hookSessionId(payload);
4396
4422
  const toolUseId = payload.tool_use_id || '';
@@ -4630,11 +4656,11 @@ import {
4630
4656
  parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
4631
4657
  extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
4632
4658
  outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,
4633
- logGraderUnavailable, filterRules, normalizeMode, resolveTranscriptPath,
4659
+ logGraderUnavailable, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,
4634
4660
  type HookConfig, type Rule,
4635
4661
  } from './_synkro-common.ts';
4636
4662
 
4637
- const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
4663
+ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';
4638
4664
 
4639
4665
  async function main() {
4640
4666
  setupCursorHookSignals();
@@ -4649,6 +4675,9 @@ async function main() {
4649
4675
  return;
4650
4676
  }
4651
4677
 
4678
+ const _m = String(payload.model ?? payload.model_id ?? '');
4679
+ if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }
4680
+
4652
4681
  const toolInput = payload.tool_input || {};
4653
4682
  const sessionId = hookSessionId(payload);
4654
4683
  const toolUseId = payload.tool_use_id || '';
@@ -4809,13 +4838,13 @@ import {
4809
4838
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
4810
4839
  parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,
4811
4840
  outputJson, outputEmpty, setupCursorHookSignals, isPlanTool, hookSessionId, GATEWAY_URL,
4812
- filterRules, resolveTranscriptPath,
4841
+ filterRules, resolveTranscriptPath, isCursorInvokingCcHook,
4813
4842
  } from './_synkro-common.ts';
4814
4843
  import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
4815
4844
  import { join } from 'node:path';
4816
4845
  import { homedir } from 'node:os';
4817
4846
 
4818
- const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
4847
+ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.includes('--cursor')) ? 'cursor' : 'claude_code';
4819
4848
 
4820
4849
  function findLatestPlanInDir(plansDir: string): string | null {
4821
4850
  if (!existsSync(plansDir)) return null;
@@ -4867,6 +4896,9 @@ async function main() {
4867
4896
  const toolName = payload.tool_name || '';
4868
4897
  if (!isPlanTool(toolName)) { outputEmpty(); return; }
4869
4898
 
4899
+ const _m = String(payload.model ?? payload.model_id ?? '');
4900
+ if (isCursorInvokingCcHook(agentKind, _m)) { outputEmpty(); return; }
4901
+
4870
4902
  const planFile = findLatestPlan();
4871
4903
  if (!planFile) { outputEmpty(); return; }
4872
4904
  const plan = readFileSync(planFile, 'utf-8');
@@ -7944,7 +7976,7 @@ function writeConfigEnv(opts) {
7944
7976
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
7945
7977
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
7946
7978
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
7947
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.30")}`
7979
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.31")}`
7948
7980
  ];
7949
7981
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
7950
7982
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -10715,7 +10747,7 @@ var args = process.argv.slice(2);
10715
10747
  var cmd = args[0] || "";
10716
10748
  var subArgs = args.slice(1);
10717
10749
  function printVersion() {
10718
- console.log("1.6.30");
10750
+ console.log("1.6.31");
10719
10751
  }
10720
10752
  function printHelp2() {
10721
10753
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents