@synkro-sh/cli 1.3.27 → 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
@@ -2373,7 +2472,7 @@ async function refreshToken() {
2373
2472
  const creds = loadCredentials();
2374
2473
  if (!creds?.refresh_token) return false;
2375
2474
  try {
2376
- const response = await fetch(`${SYNKRO_WEB_AUTH_URL}/api/auth/refresh`, {
2475
+ const response = await fetch(`${SYNKRO_API_URL}/api/auth/refresh`, {
2377
2476
  method: "POST",
2378
2477
  headers: { "Content-Type": "application/json" },
2379
2478
  body: JSON.stringify({ refresh_token: creds.refresh_token })
@@ -2382,6 +2481,7 @@ async function refreshToken() {
2382
2481
  const data = await response.json();
2383
2482
  if (data.access_token) {
2384
2483
  saveCredentials({
2484
+ ...creds,
2385
2485
  access_token: data.access_token,
2386
2486
  refresh_token: data.refresh_token || creds.refresh_token
2387
2487
  });
@@ -2413,7 +2513,7 @@ function clearCredentials() {
2413
2513
  unlinkSync2(AUTH_FILE);
2414
2514
  }
2415
2515
  }
2416
- var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, ERROR_HTML, refreshPromise;
2516
+ var PORT, RAW_WEB_AUTH_URL, SYNKRO_WEB_AUTH_URL, AUTH_FILE, RAW_API_URL, SYNKRO_API_URL, ERROR_HTML, refreshPromise;
2417
2517
  var init_stub = __esm({
2418
2518
  "cli/auth/stub.ts"() {
2419
2519
  "use strict";
@@ -2421,6 +2521,8 @@ var init_stub = __esm({
2421
2521
  RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
2422
2522
  SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
2423
2523
  AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join3(homedir3(), ".synkro", "credentials.json");
2524
+ RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
2525
+ SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
2424
2526
  ERROR_HTML = `
2425
2527
  <!DOCTYPE html>
2426
2528
  <html>
@@ -3368,7 +3470,7 @@ function writeConfigEnv(opts) {
3368
3470
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3369
3471
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3370
3472
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3371
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.27")}`
3473
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.29")}`
3372
3474
  ];
3373
3475
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3374
3476
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -4035,6 +4137,7 @@ async function statusCommand() {
4035
4137
  const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/^['"]|['"]$/g, "");
4036
4138
  let serverTier = config.SYNKRO_TIER || "(unset)";
4037
4139
  let serverInference = config.SYNKRO_INFERENCE || "fast";
4140
+ await ensureValidToken();
4038
4141
  const token = getAccessToken();
4039
4142
  if (token) {
4040
4143
  try {