@synkro-sh/cli 1.4.36 → 1.4.38

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
@@ -198,20 +198,18 @@ function installCCHooks(settingsPath, config) {
198
198
  ],
199
199
  [SYNKRO_MARKER]: true
200
200
  });
201
- if (!config.skipTranscriptSync) {
202
- settings.hooks.Stop = settings.hooks.Stop ?? [];
203
- removeSynkroEntries(settings.hooks, "Stop");
204
- settings.hooks.Stop.push({
205
- hooks: [
206
- {
207
- type: "command",
208
- command: config.transcriptSyncScriptPath,
209
- timeout: 3
210
- }
211
- ],
212
- [SYNKRO_MARKER]: true
213
- });
214
- }
201
+ settings.hooks.Stop = settings.hooks.Stop ?? [];
202
+ removeSynkroEntries(settings.hooks, "Stop");
203
+ settings.hooks.Stop.push({
204
+ hooks: [
205
+ {
206
+ type: "command",
207
+ command: config.transcriptSyncScriptPath,
208
+ timeout: 3
209
+ }
210
+ ],
211
+ [SYNKRO_MARKER]: true
212
+ });
215
213
  writeSettingsAtomic(settingsPath, settings);
216
214
  }
217
215
  function uninstallCCHooks(settingsPath) {
@@ -510,16 +508,25 @@ synkro_channel_up() {
510
508
  (exec 3<>/dev/tcp/127.0.0.1/\${SYNKRO_CHANNEL_PORT:-8929}) 2>/dev/null && exec 3<&- 3>&-
511
509
  }
512
510
 
513
- # Fetch hook config. Sets SYNKRO_CAPTURE_DEPTH, SYNKRO_TIER, SYNKRO_RULES.
511
+ # Fetch hook config. Sets SYNKRO_CAPTURE_DEPTH, SYNKRO_TIER, SYNKRO_RULES, SYNKRO_SILENT, SYNKRO_POLICY_NAME.
514
512
  synkro_load_config() {
515
513
  local resp
516
514
  resp=$(curl -sS "\${GATEWAY_URL}/api/v1/hook/config\${1:+?$1}" -H "Authorization: Bearer $JWT" --max-time 4 2>/dev/null || echo "")
517
515
  if [ -z "$resp" ]; then return; fi
518
516
  SYNKRO_CAPTURE_DEPTH=$(echo "$resp" | jq -r '.capture_depth // "local_only"' 2>/dev/null)
519
517
  SYNKRO_TIER=$(echo "$resp" | jq -r '.tier // "standard"' 2>/dev/null)
518
+ SYNKRO_SILENT=$(echo "$resp" | jq -r '.silent_mode // false' 2>/dev/null)
519
+ SYNKRO_POLICY_NAME=$(echo "$resp" | jq -r '.active_policy_name // empty' 2>/dev/null)
520
520
  SYNKRO_RULES=$(echo "$resp" | jq -c '[.rules[]? | select(.hook_stage == "pre" or .hook_stage == "both" or .hook_stage == null) | {rule_id,text,severity,category,mode}]' 2>/dev/null || echo "[]")
521
521
  }
522
522
 
523
+ # Build the tag prefix for system messages: [synkro] or [synkro:PolicyName] or [synkro:silent]
524
+ synkro_tag() {
525
+ if [ "$SYNKRO_SILENT" = "true" ]; then echo "[synkro:silent]"; return; fi
526
+ if [ -n "\${SYNKRO_POLICY_NAME:-}" ]; then echo "[synkro:\${SYNKRO_POLICY_NAME}]"; return; fi
527
+ echo "[synkro]"
528
+ }
529
+
523
530
  # Decide routing: "local" (grade on device) or "cloud" (POST to server)
524
531
  synkro_route() {
525
532
  [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && echo "local" && return
@@ -739,6 +746,20 @@ IS_HEADLESS="\${SYNKRO_HEADLESS:-0}"
739
746
  case "$PERMISSION_MODE" in acceptEdits|bypassPermissions|plan|auto) IS_HEADLESS="1" ;; esac
740
747
 
741
748
  synkro_load_config
749
+ TAG=$(synkro_tag)
750
+
751
+ if [ "$SYNKRO_SILENT" = "true" ]; then
752
+ jq -n --arg m "$TAG bashGuard \u2192 skipped (silent mode)" '{systemMessage: $m}'
753
+ exit 0
754
+ fi
755
+
756
+ # Pre-grade consent check: skip grading if user already approved this command in this session
757
+ CMD_HASH=$(printf '%s' "$COMMAND" | shasum -a 256 | cut -c1-16)
758
+ if [ -n "$SESSION_ID" ] && [ -n "$CMD_HASH" ] && synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
759
+ jq -n --arg m "$TAG bashGuard \u2192 pass (previously approved)" '{systemMessage: $m}'
760
+ exit 0
761
+ fi
762
+
742
763
  ROUTE=$(synkro_route)
743
764
 
744
765
  if [ "$ROUTE" = "local" ]; then
@@ -749,7 +770,7 @@ if [ "$ROUTE" = "local" ]; then
749
770
 
750
771
  CC_RESP=$(synkro_local_grade bash < "$GRADER_FILE" 2>&1)
751
772
  if [ $? -ne 0 ]; then
752
- jq -n --arg m "[synkro:local] bashGuard \u2192 pass: local grader unavailable (run synkro local-cc start)" '{systemMessage: $m}'
773
+ jq -n --arg m "$TAG bashGuard \u2192 pass: local grader unavailable (run synkro local-cc start)" '{systemMessage: $m}'
753
774
  exit 0
754
775
  fi
755
776
  synkro_parse_local_verdict "$CC_RESP"
@@ -761,31 +782,20 @@ if [ "$ROUTE" = "local" ]; then
761
782
  if [ "$LOCAL_OK" = "false" ]; then
762
783
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
763
784
  if [ "$RULE_MODE" = "audit" ]; then
764
- REASON="[synkro:local] bashGuard \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
785
+ REASON="$TAG bashGuard \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
765
786
  jq -n --arg m "$REASON" '{systemMessage: $m}'
766
787
  synkro_dispatch_capture "bash" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
767
788
  "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
768
789
  else
769
- CMD_HASH=$(printf '%s' "$COMMAND" | shasum -a 256 | cut -c1-16)
770
- if [ -n "$SESSION_ID" ] && synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
771
- REASON="[synkro:local] bashGuard \u2192 pass (consented retry): \${LOCAL_REASON:-retrying previously consented command}"
772
- jq -n --arg m "$REASON" '{systemMessage: $m}'
773
- synkro_dispatch_capture "bash" "pass" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
774
- "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
775
- else
776
- if [ -n "$SESSION_ID" ] && synkro_consent_has_consumed "$SESSION_ID" "$CMD_HASH"; then
777
- synkro_consent_clear_consumed "$SESSION_ID" "$CMD_HASH"
778
- fi
779
- if [ "$IS_HEADLESS" = "1" ]; then DEC="deny"; else DEC="ask"; fi
780
- REASON="[synkro:local] bashGuard \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
781
- jq -n --arg dec "$DEC" --arg reason "$REASON" --arg ctx "$REASON" \\
782
- '{systemMessage:$reason,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$dec,permissionDecisionReason:$reason,additionalContext:$ctx}}'
783
- synkro_dispatch_capture "bash" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
784
- "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
785
- fi
790
+ if [ "$IS_HEADLESS" = "1" ]; then DEC="deny"; else DEC="ask"; fi
791
+ REASON="$TAG bashGuard \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
792
+ jq -n --arg dec "$DEC" --arg reason "$REASON" --arg ctx "$REASON" \\
793
+ '{systemMessage:$reason,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$dec,permissionDecisionReason:$reason,additionalContext:$ctx}}'
794
+ synkro_dispatch_capture "bash" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
795
+ "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
786
796
  fi
787
797
  else
788
- jq -n --arg m "[synkro:local] bashGuard \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
798
+ jq -n --arg m "$TAG bashGuard \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
789
799
  synkro_dispatch_capture "bash" "pass" "audit" "\${LOCAL_CAT:-trivial_utility}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
790
800
  "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "\${RECENT_USER_MESSAGES:-[]}"
791
801
  fi
@@ -940,6 +950,13 @@ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
940
950
  fi
941
951
 
942
952
  synkro_load_config
953
+ TAG=$(synkro_tag)
954
+
955
+ if [ "$SYNKRO_SILENT" = "true" ]; then
956
+ jq -n --arg m "$TAG editGuard \u2192 skipped (silent mode)" '{systemMessage: $m}'
957
+ exit 0
958
+ fi
959
+
943
960
  ROUTE=$(synkro_route)
944
961
 
945
962
  if [ "$ROUTE" = "local" ]; then
@@ -950,7 +967,7 @@ if [ "$ROUTE" = "local" ]; then
950
967
 
951
968
  CC_RESP=$(synkro_local_grade edit < "$GRADER_FILE" 2>&1)
952
969
  if [ $? -ne 0 ]; then
953
- jq -n --arg m "[synkro:local] editGuard \u2192 pass: local grader unavailable (run synkro local-cc start)" '{systemMessage: $m}'
970
+ jq -n --arg m "$TAG editGuard \u2192 pass: local grader unavailable (run synkro local-cc start)" '{systemMessage: $m}'
954
971
  exit 0
955
972
  fi
956
973
  synkro_parse_local_verdict "$CC_RESP"
@@ -963,20 +980,20 @@ if [ "$ROUTE" = "local" ]; then
963
980
  if [ "$LOCAL_OK" = "false" ]; then
964
981
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
965
982
  if [ "$RULE_MODE" = "audit" ]; then
966
- REASON="[synkro:local] editGuard $FILE_SHORT \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
983
+ REASON="$TAG editGuard $FILE_SHORT \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
967
984
  jq -n --arg m "$REASON" '{systemMessage: $m}'
968
985
  synkro_dispatch_capture "edit" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
969
986
  "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "[]"
970
987
  else
971
988
  if [ "$IS_HEADLESS" = "1" ]; then DEC="deny"; else DEC="ask"; fi
972
- REASON="[synkro:local] editGuard $FILE_SHORT \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
989
+ REASON="$TAG editGuard $FILE_SHORT \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
973
990
  jq -n --arg dec "$DEC" --arg reason "$REASON" --arg ctx "$REASON" \\
974
991
  '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$dec,permissionDecisionReason:$reason,additionalContext:$ctx}}'
975
992
  synkro_dispatch_capture "edit" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
976
993
  "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "[]"
977
994
  fi
978
995
  else
979
- jq -n --arg m "[synkro:local] editGuard $FILE_SHORT \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
996
+ jq -n --arg m "$TAG editGuard $FILE_SHORT \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
980
997
  synkro_dispatch_capture "edit" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
981
998
  "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
982
999
  fi
@@ -1101,6 +1118,12 @@ while [ "$_PKG_DIR" != "/" ]; do
1101
1118
  done
1102
1119
 
1103
1120
  synkro_load_config
1121
+ TAG=$(synkro_tag)
1122
+
1123
+ if [ "$SYNKRO_SILENT" = "true" ]; then
1124
+ echo '{}'; exit 0
1125
+ fi
1126
+
1104
1127
  ROUTE=$(synkro_route)
1105
1128
 
1106
1129
  if [ "$ROUTE" = "local" ]; then
@@ -1122,18 +1145,18 @@ if [ "$ROUTE" = "local" ]; then
1122
1145
  if [ "$LOCAL_OK" = "false" ]; then
1123
1146
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
1124
1147
  if [ "$RULE_MODE" = "audit" ]; then
1125
- REASON="[synkro:local] editScan $BASENAME \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
1148
+ REASON="$TAG editScan $BASENAME \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
1126
1149
  jq -n --arg m "$REASON" '{systemMessage: $m}'
1127
1150
  synkro_dispatch_capture "edit_scan" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1128
1151
  "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$SCAN_VIOLATED" "[]"
1129
1152
  else
1130
- REASON="[synkro:local] editScan $BASENAME \u2192 block: \${LOCAL_REASON:-policy violation}"
1153
+ REASON="$TAG editScan $BASENAME \u2192 block: \${LOCAL_REASON:-policy violation}"
1131
1154
  jq -n --arg m "$REASON" '{systemMessage: $m, additionalContext: $m}'
1132
1155
  synkro_dispatch_capture "edit_scan" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1133
1156
  "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$SCAN_VIOLATED" "[]"
1134
1157
  fi
1135
1158
  else
1136
- jq -n --arg m "[synkro:local] editScan $BASENAME \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
1159
+ jq -n --arg m "$TAG editScan $BASENAME \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
1137
1160
  synkro_dispatch_capture "edit_scan" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1138
1161
  "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
1139
1162
  fi
@@ -1207,6 +1230,13 @@ PLAN_SHORT=$(printf '%s' "$PLAN" | head -c 80)
1207
1230
  synkro_log "planReview checking: $PLAN_SHORT..."
1208
1231
 
1209
1232
  synkro_load_config
1233
+ TAG=$(synkro_tag)
1234
+
1235
+ if [ "$SYNKRO_SILENT" = "true" ]; then
1236
+ jq -n --arg m "$TAG planReview \u2192 skipped (silent mode)" '{systemMessage: $m}'
1237
+ exit 0
1238
+ fi
1239
+
1210
1240
  ROUTE=$(synkro_route)
1211
1241
 
1212
1242
  if [ "$ROUTE" = "local" ]; then
@@ -1227,12 +1257,12 @@ if [ "$ROUTE" = "local" ]; then
1227
1257
  if [ "$LOCAL_OK" = "false" ]; then
1228
1258
  VCOUNT=$(printf '%s' "$CC_RESP" | grep -c '<violation>' 2>/dev/null || echo "0")
1229
1259
  [ "$VCOUNT" = "0" ] && VCOUNT="1"
1230
- MSG="[synkro:local] planReview \u2192 \${VCOUNT} rule(s) relevant\${LOCAL_RULE_ID:+ (first: $LOCAL_RULE_ID)}: \${LOCAL_REASON:-check org rules during implementation}"
1260
+ MSG="$TAG planReview \u2192 \${VCOUNT} rule(s) relevant\${LOCAL_RULE_ID:+ (first: $LOCAL_RULE_ID)}: \${LOCAL_REASON:-check org rules during implementation}"
1231
1261
  jq -n --arg m "$MSG" '{systemMessage: $m}'
1232
1262
  synkro_dispatch_capture "plan_review" "advisory" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "ExitPlanMode" "$GIT_REPO" "$SESSION_ID" \\
1233
1263
  "$PLAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$PLAN_VIOLATED" "[]"
1234
1264
  else
1235
- jq -n --arg m "[synkro:local] planReview \u2192 clean: \${LOCAL_REASON:-no relevant org rules for this plan}" '{systemMessage: $m}'
1265
+ jq -n --arg m "$TAG planReview \u2192 clean: \${LOCAL_REASON:-no relevant org rules for this plan}" '{systemMessage: $m}'
1236
1266
  synkro_dispatch_capture "plan_review" "clean" "audit" "\${LOCAL_CAT:-general}" "ExitPlanMode" "$GIT_REPO" "$SESSION_ID" \\
1237
1267
  "$PLAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
1238
1268
  fi
@@ -1273,7 +1303,7 @@ fi
1273
1303
  HR=$(echo "$RESP" | jq -c '.hook_response')
1274
1304
  if echo "$HR" | jq -e '.hookSpecificOutput.permissionDecision' >/dev/null 2>&1; then
1275
1305
  REASON=$(echo "$HR" | jq -r '.hookSpecificOutput.permissionDecisionReason // "check org rules"' 2>/dev/null)
1276
- jq -n --arg m "[synkro:cloud] planReview \u2192 advisory: $REASON" '{systemMessage: $m}'
1306
+ jq -n --arg m "$TAG planReview \u2192 advisory: $REASON" '{systemMessage: $m}'
1277
1307
  else
1278
1308
  echo "$HR"
1279
1309
  fi
@@ -1341,10 +1371,12 @@ OPEN=$(echo "$RESP" | jq -r '.open // 0' 2>/dev/null)
1341
1371
 
1342
1372
  if [ "\${EDITS:-0}" = "0" ] || [ -z "$EDITS" ]; then echo '{}'; exit 0; fi
1343
1373
 
1374
+ synkro_load_config
1375
+ TAG=$(synkro_tag)
1344
1376
  if [ "\${FINDINGS:-0}" = "0" ] || [ -z "$FINDINGS" ]; then
1345
- SYS_MSG="[synkro] stop \u2192 0 issues across \${EDITS} edit(s), session complete"
1377
+ SYS_MSG="$TAG stop \u2192 0 issues across \${EDITS} edit(s), session complete"
1346
1378
  else
1347
- SYS_MSG="[synkro] stop \u2192 \${FINDINGS} finding(s): \${AUTO_FIXED} auto-fixed, \${OPEN} open"
1379
+ SYS_MSG="$TAG stop \u2192 \${FINDINGS} finding(s): \${AUTO_FIXED} auto-fixed, \${OPEN} open"
1348
1380
  fi
1349
1381
 
1350
1382
  jq -n --arg m "$SYS_MSG" '{systemMessage: $m}'
@@ -1358,11 +1390,30 @@ JWT=$(synkro_load_jwt)
1358
1390
 
1359
1391
  # Route preamble
1360
1392
  SYNKRO_PORT="\${SYNKRO_CHANNEL_PORT:-8929}"
1393
+ PAYLOAD=$(cat)
1394
+ CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
1395
+ SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
1396
+ GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
1397
+
1398
+ RESP=""
1399
+ if [ -n "$JWT" ]; then
1400
+ RESP=$(curl -sS -G "\${GATEWAY_URL}/api/v1/hook/config" \\
1401
+ --data-urlencode "session_id=\${SESSION_ID:-}" \\
1402
+ --data-urlencode "repo=\${GIT_REPO:-}" \\
1403
+ -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
1404
+ if [ -n "$RESP" ]; then
1405
+ SYNKRO_SILENT=$(echo "$RESP" | jq -r '.silent_mode // false' 2>/dev/null)
1406
+ SYNKRO_POLICY_NAME=$(echo "$RESP" | jq -r '.active_policy_name // empty' 2>/dev/null)
1407
+ fi
1408
+ fi
1409
+
1410
+ TAG=$(synkro_tag)
1411
+
1361
1412
  if (exec 3<>/dev/tcp/127.0.0.1/"$SYNKRO_PORT") 2>/dev/null; then
1362
1413
  exec 3<&- 3>&- 2>/dev/null || true
1363
- ROUTE_LINE="[synkro] inference: local-cc (channel reachable on 127.0.0.1:$SYNKRO_PORT)"
1414
+ ROUTE_LINE="$TAG inference: local-cc (channel reachable on 127.0.0.1:$SYNKRO_PORT)"
1364
1415
  else
1365
- ROUTE_LINE="[synkro] inference: cloud (local-cc channel not reachable)"
1416
+ ROUTE_LINE="$TAG inference: cloud (local-cc channel not reachable)"
1366
1417
  fi
1367
1418
 
1368
1419
  if [ -z "$JWT" ]; then
@@ -1370,16 +1421,6 @@ if [ -z "$JWT" ]; then
1370
1421
  exit 0
1371
1422
  fi
1372
1423
 
1373
- PAYLOAD=$(cat)
1374
- CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
1375
- SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
1376
- GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
1377
-
1378
- RESP=$(curl -sS -G "\${GATEWAY_URL}/api/v1/hook/config" \\
1379
- --data-urlencode "session_id=\${SESSION_ID:-}" \\
1380
- --data-urlencode "repo=\${GIT_REPO:-}" \\
1381
- -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
1382
-
1383
1424
  OPEN=0
1384
1425
  if [ -n "$RESP" ]; then
1385
1426
  OPEN=$(echo "$RESP" | jq -r '.session_context.open_findings // 0' 2>/dev/null)
@@ -1389,9 +1430,9 @@ if [ "$OPEN" = "0" ] || [ -z "$OPEN" ]; then
1389
1430
  jq -n --arg m "$ROUTE_LINE" '{systemMessage: $m}'
1390
1431
  else
1391
1432
  if [ "$OPEN" = "1" ]; then
1392
- SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 1 open finding in this repo from a prior session."
1433
+ SYS_MSG="$ROUTE_LINE"$'\\n'"$TAG session start \u2192 1 open finding in this repo from a prior session."
1393
1434
  else
1394
- SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 \${OPEN} open findings in this repo from prior sessions."
1435
+ SYS_MSG="$ROUTE_LINE"$'\\n'"$TAG session start \u2192 \${OPEN} open findings in this repo from prior sessions."
1395
1436
  fi
1396
1437
  jq -n --arg m "$SYS_MSG" '{systemMessage: $m}'
1397
1438
  fi
@@ -1427,13 +1468,10 @@ BODY=$(jq -n --arg sid "$SESSION_ID" --arg tid "$TOOL_USE_ID" \\
1427
1468
  # Local consent tracking: command ran = user consented
1428
1469
  # On success \u2192 consume (next attempt blocks fresh)
1429
1470
  # On failure \u2192 grant active (retry allowed)
1471
+ # Command ran (user approved it) \u2014 grant persistent consent for this session
1430
1472
  if [ -n "$CMD_HASH" ] && [ -n "$SESSION_ID" ]; then
1431
- if [ "$IS_ERROR" = "false" ]; then
1432
- synkro_consent_consume "$SESSION_ID" "$CMD_HASH"
1433
- else
1434
- if ! synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
1435
- synkro_consent_grant "$SESSION_ID" "$CMD_HASH"
1436
- fi
1473
+ if ! synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
1474
+ synkro_consent_grant "$SESSION_ID" "$CMD_HASH"
1437
1475
  fi
1438
1476
  fi
1439
1477
 
@@ -1460,18 +1498,14 @@ if [ -z "$SESSION_ID" ] || [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH
1460
1498
  echo '{}'; exit 0
1461
1499
  fi
1462
1500
 
1463
- # Usage telemetry \u2014 always fires (metadata only, ungated by privacy/consent)
1501
+ # Usage telemetry \u2014 last turn only (metadata, ungated by privacy/consent)
1464
1502
  _LAST_ASST=$(grep '"type":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1)
1465
1503
  if [ -n "$_LAST_ASST" ]; then
1466
1504
  _CC_MODEL=$(echo "$_LAST_ASST" | jq -r '.message.model // empty' 2>/dev/null)
1467
- _TOTALS=$(grep '"type":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null \\
1468
- | jq -c '.message.usage' 2>/dev/null \\
1469
- | jq -s '{in:(map(.input_tokens//0)|add//0),out:(map(.output_tokens//0)|add//0),cw:(map(.cache_creation_input_tokens//0)|add//0),cr:(map(.cache_read_input_tokens//0)|add//0)}' 2>/dev/null \\
1470
- || echo '{"in":0,"out":0,"cw":0,"cr":0}')
1471
- _TI=$(echo "$_TOTALS" | jq -r '.in // 0')
1472
- _TO=$(echo "$_TOTALS" | jq -r '.out // 0')
1473
- _TCW=$(echo "$_TOTALS" | jq -r '.cw // 0')
1474
- _TCR=$(echo "$_TOTALS" | jq -r '.cr // 0')
1505
+ _TI=$(echo "$_LAST_ASST" | jq -r '.message.usage.input_tokens // 0' 2>/dev/null)
1506
+ _TO=$(echo "$_LAST_ASST" | jq -r '.message.usage.output_tokens // 0' 2>/dev/null)
1507
+ _TCW=$(echo "$_LAST_ASST" | jq -r '.message.usage.cache_creation_input_tokens // 0' 2>/dev/null)
1508
+ _TCR=$(echo "$_LAST_ASST" | jq -r '.message.usage.cache_read_input_tokens // 0' 2>/dev/null)
1475
1509
  if [ "\${_TI:-0}" != "0" ] || [ "\${_TO:-0}" != "0" ]; then
1476
1510
  (
1477
1511
  _USAGE="{\\"input_tokens\\":$_TI,\\"output_tokens\\":$_TO,\\"cache_creation_input_tokens\\":$_TCW,\\"cache_read_input_tokens\\":$_TCR}"
@@ -1568,6 +1602,9 @@ GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
1568
1602
  CMD_SHORT=$(printf '%s' "$COMMAND" | head -c 80)
1569
1603
  synkro_log "bashGuard checking: $CMD_SHORT"
1570
1604
 
1605
+ synkro_load_config
1606
+ if [ "$SYNKRO_SILENT" = "true" ]; then echo '{}'; exit 0; fi
1607
+
1571
1608
  BODY=$(jq -n \\
1572
1609
  --arg cmd "$COMMAND" \\
1573
1610
  --arg session_id "$SESSION_ID" \\
@@ -1621,6 +1658,9 @@ if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
1621
1658
  BASENAME=$(basename "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
1622
1659
  synkro_log "editGuard checking: $BASENAME"
1623
1660
 
1661
+ synkro_load_config
1662
+ if [ "$SYNKRO_SILENT" = "true" ]; then echo '{}'; exit 0; fi
1663
+
1624
1664
  BODY=$(jq -n \\
1625
1665
  --arg file_path "$FILE_PATH" \\
1626
1666
  --arg content "$CONTENT" \\
@@ -1728,12 +1768,8 @@ if [ -n "$CMD" ]; then
1728
1768
  fi
1729
1769
 
1730
1770
  if [ -n "$CMD_HASH" ] && [ -n "$SESSION_ID" ]; then
1731
- if [ "$IS_ERROR" = "false" ]; then
1732
- synkro_consent_consume "$SESSION_ID" "$CMD_HASH"
1733
- else
1734
- if ! synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
1735
- synkro_consent_grant "$SESSION_ID" "$CMD_HASH"
1736
- fi
1771
+ if ! synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
1772
+ synkro_consent_grant "$SESSION_ID" "$CMD_HASH"
1737
1773
  fi
1738
1774
  fi
1739
1775
 
@@ -3946,7 +3982,7 @@ function writeConfigEnv(opts) {
3946
3982
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3947
3983
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3948
3984
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3949
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.36")}`
3985
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.38")}`
3950
3986
  ];
3951
3987
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
3952
3988
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);