@synkro-sh/cli 1.3.28 → 1.3.30
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 +144 -24
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 [ "$
|
|
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 (
|
|
594
|
-
ORG_RULES
|
|
595
|
-
|
|
596
|
-
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
|
@@ -655,6 +670,20 @@ VERDICT_KIND=$(echo "$VERDICT" | jq -r '.verdict // "warn"' 2>/dev/null)
|
|
|
655
670
|
REASONING=$(echo "$VERDICT" | jq -r '.reasoning // "matched dangerous-verb regex"' 2>/dev/null)
|
|
656
671
|
ALTERNATIVE=$(echo "$VERDICT" | jq -r '.alternative // ""' 2>/dev/null)
|
|
657
672
|
CATEGORY=$(echo "$VERDICT" | jq -r '.category // "destructive_command"' 2>/dev/null)
|
|
673
|
+
RISK_LEVEL=$(echo "$VERDICT" | jq -r '.risk_level // empty' 2>/dev/null)
|
|
674
|
+
|
|
675
|
+
# Backwards-compat: if severity isn't block/audit, derive it from verdict_kind
|
|
676
|
+
# and treat the original severity as the risk_level.
|
|
677
|
+
case "$SEVERITY" in
|
|
678
|
+
block|audit) ;;
|
|
679
|
+
low|medium|high|critical)
|
|
680
|
+
[ -z "$RISK_LEVEL" ] && RISK_LEVEL="$SEVERITY"
|
|
681
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
682
|
+
;;
|
|
683
|
+
*)
|
|
684
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
685
|
+
;;
|
|
686
|
+
esac
|
|
658
687
|
|
|
659
688
|
# Severity-driven surfacing:
|
|
660
689
|
# block \u2192 permissionDecision: "ask" (interactive) or "deny" (headless)
|
|
@@ -711,6 +740,38 @@ case "$SEVERITY" in
|
|
|
711
740
|
;;
|
|
712
741
|
esac
|
|
713
742
|
|
|
743
|
+
# Fire-and-forget anonymized telemetry for local_only mode
|
|
744
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$VERDICT_KIND" ]; then
|
|
745
|
+
(
|
|
746
|
+
ANON_BODY=$(jq -n \\
|
|
747
|
+
--arg event_id "$(uuidgen 2>/dev/null || echo "evt_$(date +%s)_$$")" \\
|
|
748
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
|
|
749
|
+
--arg hook_type "bash" \\
|
|
750
|
+
--arg verdict "$VERDICT_KIND" \\
|
|
751
|
+
--arg severity "$SEVERITY" \\
|
|
752
|
+
--arg risk_level "\${RISK_LEVEL:-low}" \\
|
|
753
|
+
--arg category "$CATEGORY" \\
|
|
754
|
+
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
755
|
+
--arg tool_name "$TOOL_NAME" \\
|
|
756
|
+
'{
|
|
757
|
+
event_id: $event_id,
|
|
758
|
+
timestamp: $timestamp,
|
|
759
|
+
hook_type: $hook_type,
|
|
760
|
+
verdict: $verdict,
|
|
761
|
+
severity: $severity,
|
|
762
|
+
risk_level: $risk_level,
|
|
763
|
+
category: $category,
|
|
764
|
+
model: $model,
|
|
765
|
+
tool_name: $tool_name
|
|
766
|
+
}')
|
|
767
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
768
|
+
-H "Content-Type: application/json" \\
|
|
769
|
+
-H "Authorization: Bearer $JWT" \\
|
|
770
|
+
-d "$ANON_BODY" \\
|
|
771
|
+
--max-time 2 >/dev/null 2>&1
|
|
772
|
+
) &
|
|
773
|
+
fi
|
|
774
|
+
|
|
714
775
|
exit 0
|
|
715
776
|
`;
|
|
716
777
|
CC_EDIT_PRECHECK_SCRIPT = `#!/bin/bash
|
|
@@ -938,31 +999,47 @@ refresh_jwt() {
|
|
|
938
999
|
}
|
|
939
1000
|
|
|
940
1001
|
|
|
941
|
-
# Resolve tier (cached 60 min) \u2014 server is canonical via /cli/me
|
|
1002
|
+
# Resolve tier + capture_depth (cached 60 min) \u2014 server is canonical via /cli/me.
|
|
942
1003
|
TIER_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}"
|
|
1004
|
+
CD_CACHE_FILE="\${TIER_CACHE_FILE}.cd"
|
|
943
1005
|
SYNKRO_INFERENCE_TIER=""
|
|
1006
|
+
SYNKRO_CAPTURE_DEPTH=""
|
|
944
1007
|
if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
|
|
945
1008
|
SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
|
|
1009
|
+
SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null)
|
|
946
1010
|
fi
|
|
947
1011
|
if [ -z "$SYNKRO_INFERENCE_TIER" ]; then
|
|
948
1012
|
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
|
|
949
1013
|
if [ -n "$ME_RESP" ]; then
|
|
950
1014
|
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
951
1015
|
[ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
|
|
1016
|
+
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
1017
|
+
[ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
|
|
952
1018
|
fi
|
|
953
1019
|
fi
|
|
954
1020
|
SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
1021
|
+
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
|
|
955
1022
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1023
|
+
USE_LOCAL=false
|
|
1024
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && command -v claude >/dev/null 2>&1; then
|
|
1025
|
+
USE_LOCAL=true
|
|
1026
|
+
elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
|
|
1027
|
+
USE_LOCAL=true
|
|
1028
|
+
fi
|
|
1029
|
+
|
|
1030
|
+
if [ "$USE_LOCAL" = "true" ]; then
|
|
1031
|
+
# \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (Python helper).
|
|
1032
|
+
ORG_RULES="[]"
|
|
1033
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" != "local_only" ]; then
|
|
1034
|
+
ORG_RULES=$(printf '%s' "$PROPOSED" | head -c 8000 \\
|
|
1035
|
+
| jq -Rs '{content: .}' \\
|
|
1036
|
+
| curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
|
|
1037
|
+
-X POST -H "Content-Type: application/json" \\
|
|
1038
|
+
-H "Authorization: Bearer $JWT" \\
|
|
1039
|
+
-d @- --max-time 2 2>/dev/null \\
|
|
1040
|
+
| jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
|
|
1041
|
+
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
1042
|
+
fi
|
|
966
1043
|
|
|
967
1044
|
GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)
|
|
968
1045
|
trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
|
|
@@ -1088,6 +1165,44 @@ else
|
|
|
1088
1165
|
echo "$RESP_WITH_MSG"
|
|
1089
1166
|
fi
|
|
1090
1167
|
|
|
1168
|
+
# Fire-and-forget anonymized telemetry for local_only mode
|
|
1169
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
|
|
1170
|
+
LOCAL_VERDICT="allow"
|
|
1171
|
+
LOCAL_SEVERITY="audit"
|
|
1172
|
+
LOCAL_CATEGORY="edit_pass"
|
|
1173
|
+
if [ "$DECISION" = "deny" ]; then
|
|
1174
|
+
LOCAL_VERDICT="warn"
|
|
1175
|
+
LOCAL_SEVERITY="block"
|
|
1176
|
+
LOCAL_CATEGORY="edit_violation"
|
|
1177
|
+
fi
|
|
1178
|
+
(
|
|
1179
|
+
ANON_BODY=$(jq -n \\
|
|
1180
|
+
--arg event_id "$(uuidgen 2>/dev/null || echo "evt_$(date +%s)_$$")" \\
|
|
1181
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
|
|
1182
|
+
--arg hook_type "edit" \\
|
|
1183
|
+
--arg verdict "$LOCAL_VERDICT" \\
|
|
1184
|
+
--arg severity "$LOCAL_SEVERITY" \\
|
|
1185
|
+
--arg category "$LOCAL_CATEGORY" \\
|
|
1186
|
+
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
1187
|
+
--arg tool_name "$TOOL_NAME" \\
|
|
1188
|
+
'{
|
|
1189
|
+
event_id: $event_id,
|
|
1190
|
+
timestamp: $timestamp,
|
|
1191
|
+
hook_type: $hook_type,
|
|
1192
|
+
verdict: $verdict,
|
|
1193
|
+
severity: $severity,
|
|
1194
|
+
category: $category,
|
|
1195
|
+
model: $model,
|
|
1196
|
+
tool_name: $tool_name
|
|
1197
|
+
}')
|
|
1198
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
1199
|
+
-H "Content-Type: application/json" \\
|
|
1200
|
+
-H "Authorization: Bearer $JWT" \\
|
|
1201
|
+
-d "$ANON_BODY" \\
|
|
1202
|
+
--max-time 2 >/dev/null 2>&1
|
|
1203
|
+
) &
|
|
1204
|
+
fi
|
|
1205
|
+
|
|
1091
1206
|
exit 0
|
|
1092
1207
|
`;
|
|
1093
1208
|
CC_EDIT_CAPTURE_SCRIPT = `#!/bin/bash
|
|
@@ -2100,7 +2215,12 @@ OUTPUT RULES \u2014 strictest possible, no exceptions:
|
|
|
2100
2215
|
|
|
2101
2216
|
1. NO reasoning. NO preamble. NO commentary.
|
|
2102
2217
|
2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.
|
|
2103
|
-
3. JSON shape: {"verdict": "warn"|"allow", "severity": "low|medium|high|critical", "category": "snake_case", "reasoning": "<= 25 words, cites intent + match/mismatch", "alternative": "safer command or null"}
|
|
2218
|
+
3. JSON shape: {"verdict": "warn"|"allow", "severity": "block"|"audit", "risk_level": "low"|"medium"|"high"|"critical", "category": "snake_case", "reasoning": "<= 25 words, cites intent + match/mismatch", "alternative": "safer command or null"}
|
|
2219
|
+
|
|
2220
|
+
SEVERITY MAPPING (strict):
|
|
2221
|
+
- verdict="warn" \u2192 severity="block"
|
|
2222
|
+
- verdict="allow" \u2192 severity="audit"
|
|
2223
|
+
risk_level always reflects the underlying danger level (low/medium/high/critical), independent of the routing decision.
|
|
2104
2224
|
|
|
2105
2225
|
Rules:
|
|
2106
2226
|
- WARN if destructive/irreversible AND not aligned with user intent, OR has wildly disproportionate blast radius vs the request.
|
|
@@ -3371,7 +3491,7 @@ function writeConfigEnv(opts) {
|
|
|
3371
3491
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3372
3492
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3373
3493
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3374
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3494
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.30")}`
|
|
3375
3495
|
];
|
|
3376
3496
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3377
3497
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|