@synkro-sh/cli 1.2.3 → 1.2.5

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
@@ -135,7 +135,8 @@ function installCCHooks(settingsPath, config) {
135
135
  hooks: [
136
136
  {
137
137
  type: "command",
138
- command: config.editCaptureScriptPath
138
+ command: config.editCaptureScriptPath,
139
+ timeout: 20
139
140
  }
140
141
  ],
141
142
  [SYNKRO_MARKER]: true
@@ -299,7 +300,7 @@ var init_hookScripts = __esm({
299
300
  # Synkro PreToolUse Bash judge hook
300
301
  # Reads CC's hook payload from stdin, judges via Synkro gateway, returns verdict.
301
302
  # Auth: reads access_token from ~/.synkro/credentials.json, sends Authorization: Bearer.
302
- set -e
303
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
303
304
 
304
305
  synkro_log() { echo "[synkro] $1" >&2; }
305
306
 
@@ -563,24 +564,20 @@ CATEGORY=$(echo "$VERDICT" | jq -r '.category // "destructive_command"' 2>/dev/n
563
564
  # need to interrupt the user \u2014 surfacing them creates alert fatigue and
564
565
  # trains the user to click-through on warnings that turn out to be benign.
565
566
 
567
+ ALT_SUFFIX=""
568
+ if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
569
+ ALT_SUFFIX=" Suggested: \${ALTERNATIVE}"
570
+ fi
571
+
566
572
  case "$SEVERITY" in
567
573
  critical)
568
- synkro_log "bashGuard $CMD_SHORT \u2192 BLOCKED ($CATEGORY): $REASONING"
569
- SYS_MSG="[synkro] bashGuard \u2192 critical: \${REASONING}"
570
- if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
571
- SYS_MSG="\${SYS_MSG}\\n[synkro] suggested \u2192 \${ALTERNATIVE}"
572
- fi
573
- ADDITIONAL_CTX="Synkro safety judge flagged this command (severity: critical, category: \${CATEGORY}). Reasoning: \${REASONING}."
574
- if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
575
- ADDITIONAL_CTX="\${ADDITIONAL_CTX} Suggested alternative: \${ALTERNATIVE}."
576
- fi
577
- PERMISSION_REASON="[synkro] BLOCKED \u2014 \${REASONING}. Severity: critical. Override only if you are certain."
574
+ synkro_log "bashGuard $CMD_SHORT \u2192 BLOCKED ($CATEGORY)"
575
+ PERMISSION_REASON="[synkro] BLOCKED \u2014 \${REASONING}\${ALT_SUFFIX}"
576
+ ADDITIONAL_CTX="Synkro safety judge (severity: critical, category: \${CATEGORY}).\${ALT_SUFFIX}"
578
577
  jq -n \\
579
- --arg sys_msg "$SYS_MSG" \\
580
578
  --arg ctx "$ADDITIONAL_CTX" \\
581
579
  --arg reason "$PERMISSION_REASON" \\
582
580
  '{
583
- systemMessage: $sys_msg,
584
581
  hookSpecificOutput: {
585
582
  hookEventName: "PreToolUse",
586
583
  permissionDecision: "deny",
@@ -590,28 +587,15 @@ case "$SEVERITY" in
590
587
  }'
591
588
  ;;
592
589
  high)
593
- synkro_log "bashGuard $CMD_SHORT \u2192 FLAGGED ($CATEGORY): $REASONING"
594
- SYS_MSG="[synkro] bashGuard \u2192 high: \${REASONING}"
595
- if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
596
- SYS_MSG="\${SYS_MSG}\\n[synkro] suggested \u2192 \${ALTERNATIVE}"
597
- fi
598
- ADDITIONAL_CTX="Synkro safety judge flagged this command (severity: high, category: \${CATEGORY}). Reasoning: \${REASONING}."
599
- if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
600
- ADDITIONAL_CTX="\${ADDITIONAL_CTX} Suggested alternative: \${ALTERNATIVE}."
601
- fi
602
- PERMISSION_REASON="[synkro] high: \${REASONING}"
603
- if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
604
- PERMISSION_REASON="\${PERMISSION_REASON} Alternative: \${ALTERNATIVE}"
605
- fi
606
- # Headless? Upgrade ask \u2192 deny so we fail-closed.
590
+ synkro_log "bashGuard $CMD_SHORT \u2192 FLAGGED ($CATEGORY)"
591
+ PERMISSION_REASON="[synkro] \${REASONING}\${ALT_SUFFIX}"
592
+ ADDITIONAL_CTX="Synkro safety judge (severity: high, category: \${CATEGORY}).\${ALT_SUFFIX}"
607
593
  if [ "$IS_HEADLESS" = "1" ]; then DECISION="deny"; else DECISION="ask"; fi
608
594
  jq -n \\
609
- --arg sys_msg "$SYS_MSG" \\
610
595
  --arg ctx "$ADDITIONAL_CTX" \\
611
596
  --arg reason "$PERMISSION_REASON" \\
612
597
  --arg decision "$DECISION" \\
613
598
  '{
614
- systemMessage: $sys_msg,
615
599
  hookSpecificOutput: {
616
600
  hookEventName: "PreToolUse",
617
601
  permissionDecision: $decision,
@@ -621,9 +605,15 @@ case "$SEVERITY" in
621
605
  }'
622
606
  ;;
623
607
  *)
624
- # low / medium / anything else \u2192 allow
625
- synkro_log "bashGuard $CMD_SHORT \u2192 pass"
626
- jq -n --arg m "[synkro] bashGuard \u2192 pass" '{systemMessage: $m}'
608
+ synkro_log "bashGuard $CMD_SHORT \u2192 pass ($CATEGORY): $REASONING"
609
+ case "$CATEGORY" in
610
+ trivial_utility)
611
+ jq -n --arg m "[synkro] bashGuard \u2192 pass" '{systemMessage: $m}' ;;
612
+ judge_unavailable|judge_error|parse_error)
613
+ jq -n --arg m "[synkro] bashGuard \u2192 pass (grader unavailable)" '{systemMessage: $m}' ;;
614
+ *)
615
+ jq -n --arg m "[synkro] bashGuard \u2192 pass: \${REASONING}" '{systemMessage: $m}' ;;
616
+ esac
627
617
  ;;
628
618
  esac
629
619
 
@@ -642,7 +632,7 @@ exit 0
642
632
  # Synkro's COGS = ~$0.00001 per edit (one Gemini embedding call).
643
633
  #
644
634
  # Always exits 0 with valid JSON. Fails open on any error.
645
- set -e
635
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
646
636
 
647
637
  synkro_log() { echo "[synkro] $1" >&2; }
648
638
 
@@ -1014,7 +1004,7 @@ exit 0
1014
1004
  # edit lands (cross-file shapes, real disk state, post-edit semantic effects).
1015
1005
  #
1016
1006
  # Always exits 0 with valid JSON \u2014 never breaks CC's flow.
1017
- set -e
1007
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
1018
1008
 
1019
1009
  synkro_log() { echo "[synkro] $1" >&2; }
1020
1010
 
@@ -1033,7 +1023,7 @@ if [ ! -f "$CREDS_PATH" ]; then
1033
1023
  echo '{}'
1034
1024
  exit 0
1035
1025
  fi
1036
- JWT=$(jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null)
1026
+ JWT=$(jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null || true)
1037
1027
  if [ -z "$JWT" ]; then
1038
1028
  echo '{}'
1039
1029
  exit 0
@@ -1080,7 +1070,7 @@ if [ -z "$FILE_CONTENT" ]; then
1080
1070
  exit 0
1081
1071
  fi
1082
1072
 
1083
- DIFF_FIELD=$(echo "$TOOL_INPUT" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null)
1073
+ DIFF_FIELD=$(echo "$TOOL_INPUT" | jq -c '{old_string, new_string, edits} | with_entries(select(.value != null))' 2>/dev/null || echo "null")
1084
1074
  if [ -z "$DIFF_FIELD" ] || [ "$DIFF_FIELD" = "null" ] || [ "$DIFF_FIELD" = "{}" ]; then
1085
1075
  DIFF_FIELD="null"
1086
1076
  fi
@@ -1101,7 +1091,12 @@ BODY=$(jq -n \\
1101
1091
  tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),
1102
1092
  cwd: (if ($cwd | length) > 0 then $cwd else null end),
1103
1093
  repo: (if ($repo | length) > 0 then $repo else null end)
1104
- }')
1094
+ }' 2>/dev/null || true)
1095
+
1096
+ if [ -z "$BODY" ] || ! echo "$BODY" | jq -e 'type == "object"' >/dev/null 2>&1; then
1097
+ jq -n --arg m "[synkro] editScan $BASENAME \u2192 error (body construction failed)" '{systemMessage: $m}'
1098
+ exit 0
1099
+ fi
1105
1100
 
1106
1101
  refresh_jwt() {
1107
1102
  local refresh_token
@@ -1128,31 +1123,32 @@ refresh_jwt() {
1128
1123
  return 0
1129
1124
  }
1130
1125
 
1131
- # Fire-and-forget correction-followup. PostToolUse means the edit landed \u2192
1132
- # flip any pending precheck-correction for this tool_use_id to 'allow'.
1126
+ # Fire-and-forget correction-followup (backgrounded \u2014 must not block grading).
1133
1127
  if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
1134
- FOLLOWUP_BODY=$(jq -n \\
1135
- --arg sid "$SESSION_ID" \\
1136
- --arg tid "$TOOL_USE_ID" \\
1137
- '{session_id: $sid, tool_use_id: $tid, decision: "allow"}')
1138
- curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup" \\
1139
- -H "Content-Type: application/json" \\
1140
- -H "Authorization: Bearer $JWT" \\
1141
- -d "$FOLLOWUP_BODY" \\
1142
- --max-time 2 \\
1143
- >/dev/null 2>&1 || true
1128
+ (
1129
+ FOLLOWUP_BODY=$(jq -n \\
1130
+ --arg sid "$SESSION_ID" \\
1131
+ --arg tid "$TOOL_USE_ID" \\
1132
+ '{session_id: $sid, tool_use_id: $tid, decision: "allow"}')
1133
+ curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup" \\
1134
+ -H "Content-Type: application/json" \\
1135
+ -H "Authorization: Bearer $JWT" \\
1136
+ -d "$FOLLOWUP_BODY" \\
1137
+ --max-time 2 \\
1138
+ >/dev/null 2>&1
1139
+ ) &
1144
1140
  fi
1145
1141
 
1146
1142
  # Resolve tier (cached 60 min) \u2014 same logic as the other hooks.
1147
1143
  TIER_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}"
1148
1144
  SYNKRO_INFERENCE_TIER=""
1149
1145
  if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
1150
- SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
1146
+ SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null || true)
1151
1147
  fi
1152
1148
  if [ -z "$SYNKRO_INFERENCE_TIER" ]; then
1153
1149
  ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
1154
1150
  if [ -n "$ME_RESP" ]; then
1155
- SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
1151
+ SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null || true)
1156
1152
  [ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
1157
1153
  fi
1158
1154
  fi
@@ -1195,7 +1191,7 @@ else
1195
1191
  -H "Content-Type: application/json" \\
1196
1192
  -H "Authorization: Bearer $JWT" \\
1197
1193
  -d "$BODY" \\
1198
- --max-time 5 2>/dev/null || echo "")
1194
+ --max-time 12 2>/dev/null || echo "")
1199
1195
 
1200
1196
  if echo "$RESP" | grep -qE '"detail":"Token has expired|"detail":"Invalid or expired token'; then
1201
1197
  if refresh_jwt; then
@@ -1203,7 +1199,7 @@ else
1203
1199
  -H "Content-Type: application/json" \\
1204
1200
  -H "Authorization: Bearer $JWT" \\
1205
1201
  -d "$BODY" \\
1206
- --max-time 5 2>/dev/null || echo "")
1202
+ --max-time 12 2>/dev/null || echo "")
1207
1203
  fi
1208
1204
  fi
1209
1205
  fi
@@ -1244,7 +1240,7 @@ exit 0
1244
1240
  # Synkro Stop hook \u2014 emits "[synkro] stop \u2192 N findings: X auto-fixed, Y open"
1245
1241
  # as a final summary line when the CC session ends. Reads guard_checks rows
1246
1242
  # for the session via /api/v1/cli/session-summary.
1247
- set -e
1243
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
1248
1244
 
1249
1245
  CONFIG_FILE="$HOME/.synkro/config.env"
1250
1246
  if [ -f "$CONFIG_FILE" ]; then
@@ -1309,7 +1305,7 @@ exit 0
1309
1305
  # Synkro SessionStart hook \u2014 when the user opens a fresh CC session in a repo
1310
1306
  # we have findings on, surface "[synkro] session start \u2192 N open finding(s) in
1311
1307
  # this repo" so they have context. Silent when there's nothing to report.
1312
- set -e
1308
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
1313
1309
 
1314
1310
  CONFIG_FILE="$HOME/.synkro/config.env"
1315
1311
  if [ -f "$CONFIG_FILE" ]; then
@@ -1370,7 +1366,7 @@ exit 0
1370
1366
  # Synkro PostToolUse Bash hook \u2014 minimal correction-followup fire.
1371
1367
  # No grading happens here; verdict already came from PreToolUse. This is just
1372
1368
  # the "user approved + agent ran it" capture.
1373
- set -e
1369
+ # No set -e: hook must ALWAYS produce JSON output. Silent death = CC timeout.
1374
1370
 
1375
1371
  CONFIG_FILE="$HOME/.synkro/config.env"
1376
1372
  if [ -f "$CONFIG_FILE" ]; then
@@ -2225,7 +2221,7 @@ function writeConfigEnv(opts) {
2225
2221
  `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
2226
2222
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
2227
2223
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
2228
- `SYNKRO_VERSION=${shellQuoteSingle("1.2.3")}`
2224
+ `SYNKRO_VERSION=${shellQuoteSingle("1.2.5")}`
2229
2225
  ];
2230
2226
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
2231
2227
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);