@synkro-sh/cli 1.3.28 → 1.3.29

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
@@ -571,34 +571,49 @@ refresh_jwt() {
571
571
  return 0
572
572
  }
573
573
 
574
- # Resolve tier (cached 60 min) \u2014 server is canonical via /cli/me; fallback free.
574
+ # Resolve tier + capture_depth (cached 60 min) \u2014 server is canonical via /cli/me.
575
575
  TIER_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}"
576
+ CD_CACHE_FILE="\${TIER_CACHE_FILE}.cd"
576
577
  SYNKRO_INFERENCE_TIER=""
578
+ SYNKRO_CAPTURE_DEPTH=""
577
579
  if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
578
580
  SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
581
+ SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null)
579
582
  fi
580
583
  if [ -z "$SYNKRO_INFERENCE_TIER" ]; then
581
584
  ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
582
585
  if [ -n "$ME_RESP" ]; then
583
586
  SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
584
587
  [ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
588
+ SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
589
+ [ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
585
590
  fi
586
591
  fi
587
592
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
593
+ SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
588
594
 
595
+ USE_LOCAL=false
596
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && command -v claude >/dev/null 2>&1; then
597
+ USE_LOCAL=true
598
+ elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
599
+ USE_LOCAL=true
600
+ fi
589
601
 
590
- if [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
602
+ if [ "$USE_LOCAL" = "true" ]; then
591
603
  # \u2500\u2500\u2500 FREE TIER: grade via the persistent claude daemon (mode=bash). \u2500\u2500\u2500
592
604
 
593
- # Fetch org guardrail rules relevant to this command (same as edit hook).
594
- ORG_RULES=$(printf '%s' "$COMMAND" | head -c 4000 \\
595
- | jq -Rs '{content: .}' \\
596
- | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=15" \\
597
- -X POST -H "Content-Type: application/json" \\
598
- -H "Authorization: Bearer $JWT" \\
599
- -d @- --max-time 2 2>/dev/null \\
600
- | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
601
- if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
605
+ # Fetch org guardrail rules relevant to this command (skip in local_only \u2014 no content leaves device).
606
+ ORG_RULES="[]"
607
+ if [ "$SYNKRO_CAPTURE_DEPTH" != "local_only" ]; then
608
+ ORG_RULES=$(printf '%s' "$COMMAND" | head -c 4000 \\
609
+ | jq -Rs '{content: .}' \\
610
+ | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=15" \\
611
+ -X POST -H "Content-Type: application/json" \\
612
+ -H "Authorization: Bearer $JWT" \\
613
+ -d @- --max-time 2 2>/dev/null \\
614
+ | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
615
+ if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
616
+ fi
602
617
 
603
618
  GRADER_PROMPT_FILE=$(mktemp -t synkro-bash-prompt.XXXXXX)
604
619
  trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
@@ -711,6 +726,36 @@ case "$SEVERITY" in
711
726
  ;;
712
727
  esac
713
728
 
729
+ # Fire-and-forget anonymized telemetry for local_only mode
730
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$VERDICT_KIND" ]; then
731
+ (
732
+ ANON_BODY=$(jq -n \\
733
+ --arg event_id "$(uuidgen 2>/dev/null || echo "evt_$(date +%s)_$$")" \\
734
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
735
+ --arg hook_type "bash" \\
736
+ --arg verdict "$VERDICT_KIND" \\
737
+ --arg severity "$SEVERITY" \\
738
+ --arg category "$CATEGORY" \\
739
+ --arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
740
+ --arg tool_name "$TOOL_NAME" \\
741
+ '{
742
+ event_id: $event_id,
743
+ timestamp: $timestamp,
744
+ hook_type: $hook_type,
745
+ verdict: $verdict,
746
+ severity: $severity,
747
+ category: $category,
748
+ model: $model,
749
+ tool_name: $tool_name
750
+ }')
751
+ curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
752
+ -H "Content-Type: application/json" \\
753
+ -H "Authorization: Bearer $JWT" \\
754
+ -d "$ANON_BODY" \\
755
+ --max-time 2 >/dev/null 2>&1
756
+ ) &
757
+ fi
758
+
714
759
  exit 0
715
760
  `;
716
761
  CC_EDIT_PRECHECK_SCRIPT = `#!/bin/bash
@@ -938,31 +983,47 @@ refresh_jwt() {
938
983
  }
939
984
 
940
985
 
941
- # Resolve tier (cached 60 min) \u2014 server is canonical via /cli/me; fallback free.
986
+ # Resolve tier + capture_depth (cached 60 min) \u2014 server is canonical via /cli/me.
942
987
  TIER_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}"
988
+ CD_CACHE_FILE="\${TIER_CACHE_FILE}.cd"
943
989
  SYNKRO_INFERENCE_TIER=""
990
+ SYNKRO_CAPTURE_DEPTH=""
944
991
  if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
945
992
  SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
993
+ SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null)
946
994
  fi
947
995
  if [ -z "$SYNKRO_INFERENCE_TIER" ]; then
948
996
  ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
949
997
  if [ -n "$ME_RESP" ]; then
950
998
  SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
951
999
  [ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
1000
+ SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
1001
+ [ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
952
1002
  fi
953
1003
  fi
954
1004
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
1005
+ SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
955
1006
 
956
- if [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
957
- # \u2500\u2500\u2500 FREE TIER: grade via the persistent claude daemon (Python helper).
958
- ORG_RULES=$(printf '%s' "$PROPOSED" | head -c 8000 \\
959
- | jq -Rs '{content: .}' \\
960
- | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
961
- -X POST -H "Content-Type: application/json" \\
962
- -H "Authorization: Bearer $JWT" \\
963
- -d @- --max-time 2 2>/dev/null \\
964
- | jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
965
- if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1007
+ USE_LOCAL=false
1008
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && command -v claude >/dev/null 2>&1; then
1009
+ USE_LOCAL=true
1010
+ elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
1011
+ USE_LOCAL=true
1012
+ fi
1013
+
1014
+ if [ "$USE_LOCAL" = "true" ]; then
1015
+ # \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (Python helper).
1016
+ ORG_RULES="[]"
1017
+ if [ "$SYNKRO_CAPTURE_DEPTH" != "local_only" ]; then
1018
+ ORG_RULES=$(printf '%s' "$PROPOSED" | head -c 8000 \\
1019
+ | jq -Rs '{content: .}' \\
1020
+ | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
1021
+ -X POST -H "Content-Type: application/json" \\
1022
+ -H "Authorization: Bearer $JWT" \\
1023
+ -d @- --max-time 2 2>/dev/null \\
1024
+ | jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
1025
+ if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1026
+ fi
966
1027
 
967
1028
  GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)
968
1029
  trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
@@ -1088,6 +1149,44 @@ else
1088
1149
  echo "$RESP_WITH_MSG"
1089
1150
  fi
1090
1151
 
1152
+ # Fire-and-forget anonymized telemetry for local_only mode
1153
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
1154
+ LOCAL_VERDICT="allow"
1155
+ LOCAL_SEVERITY="audit"
1156
+ LOCAL_CATEGORY="edit_pass"
1157
+ if [ "$DECISION" = "deny" ]; then
1158
+ LOCAL_VERDICT="warn"
1159
+ LOCAL_SEVERITY="block"
1160
+ LOCAL_CATEGORY="edit_violation"
1161
+ fi
1162
+ (
1163
+ ANON_BODY=$(jq -n \\
1164
+ --arg event_id "$(uuidgen 2>/dev/null || echo "evt_$(date +%s)_$$")" \\
1165
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
1166
+ --arg hook_type "edit" \\
1167
+ --arg verdict "$LOCAL_VERDICT" \\
1168
+ --arg severity "$LOCAL_SEVERITY" \\
1169
+ --arg category "$LOCAL_CATEGORY" \\
1170
+ --arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
1171
+ --arg tool_name "$TOOL_NAME" \\
1172
+ '{
1173
+ event_id: $event_id,
1174
+ timestamp: $timestamp,
1175
+ hook_type: $hook_type,
1176
+ verdict: $verdict,
1177
+ severity: $severity,
1178
+ category: $category,
1179
+ model: $model,
1180
+ tool_name: $tool_name
1181
+ }')
1182
+ curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
1183
+ -H "Content-Type: application/json" \\
1184
+ -H "Authorization: Bearer $JWT" \\
1185
+ -d "$ANON_BODY" \\
1186
+ --max-time 2 >/dev/null 2>&1
1187
+ ) &
1188
+ fi
1189
+
1091
1190
  exit 0
1092
1191
  `;
1093
1192
  CC_EDIT_CAPTURE_SCRIPT = `#!/bin/bash
@@ -3371,7 +3470,7 @@ function writeConfigEnv(opts) {
3371
3470
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3372
3471
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3373
3472
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3374
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.28")}`
3473
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.29")}`
3375
3474
  ];
3376
3475
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3377
3476
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);