@synkro-sh/cli 1.4.12 → 1.4.13

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
@@ -709,19 +709,10 @@ ensure_fresh_jwt() {
709
709
 
710
710
  ensure_fresh_jwt
711
711
 
712
- # Parallel fetch: /cli/me + rules in background, wait for both.
713
- _ME_TMP=$(mktemp -t synkro-me.XXXXXX)
714
- _RULES_TMP=$(mktemp -t synkro-rules.XXXXXX)
715
- trap "rm -f \\"$_ME_TMP\\" \\"$_RULES_TMP\\"" EXIT
716
- curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_ME_TMP" &
717
- _ME_PID=$!
718
- curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_RULES_TMP" &
719
- _RULES_PID=$!
720
- wait $_ME_PID 2>/dev/null; wait $_RULES_PID 2>/dev/null
721
-
722
- ME_RESP=$(cat "$_ME_TMP" 2>/dev/null || echo "")
723
- SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
724
- SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
712
+ # Single fetch: /cli/hook-context returns me + rules in one call.
713
+ HOOK_CTX=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/hook-context" -H "Authorization: Bearer $JWT" --max-time 4 2>/dev/null || echo "")
714
+ SYNKRO_INFERENCE_TIER=$(echo "$HOOK_CTX" | jq -r '.tier // empty' 2>/dev/null)
715
+ SYNKRO_CAPTURE_DEPTH=$(echo "$HOOK_CTX" | jq -r '.capture_depth // empty' 2>/dev/null)
725
716
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
726
717
  SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
727
718
 
@@ -733,7 +724,7 @@ fi
733
724
  if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
734
725
  RULES_CACHE="$HOME/.synkro/.rules-cache-bash"
735
726
  RULES_JQ='[.rules[]? | select(.hook_stage == "pre" or .hook_stage == "both" or .hook_stage == null) | {rule_id, text, severity, category}]'
736
- ORG_RULES=$(jq -c "$RULES_JQ" "$_RULES_TMP" 2>/dev/null || echo "[]")
727
+ ORG_RULES=$(echo "$HOOK_CTX" | jq -c "$RULES_JQ" 2>/dev/null || echo "[]")
737
728
  if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
738
729
  printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
739
730
  elif [ -f "$RULES_CACHE" ]; then
@@ -1212,26 +1203,17 @@ ensure_fresh_jwt() {
1212
1203
  ensure_fresh_jwt
1213
1204
 
1214
1205
 
1215
- # Parallel fetch: /cli/me + rules in background, wait for both.
1216
- _ME_TMP=$(mktemp -t synkro-me.XXXXXX)
1217
- _RULES_TMP=$(mktemp -t synkro-rules.XXXXXX)
1218
- trap "rm -f \\"$_ME_TMP\\" \\"$_RULES_TMP\\"" EXIT
1219
- curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_ME_TMP" &
1220
- _ME_PID=$!
1221
- curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_RULES_TMP" &
1222
- _RULES_PID=$!
1223
- wait $_ME_PID 2>/dev/null; wait $_RULES_PID 2>/dev/null
1224
-
1225
- ME_RESP=$(cat "$_ME_TMP" 2>/dev/null || echo "")
1226
- SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
1227
- SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
1206
+ # Single fetch: /cli/hook-context returns me + rules in one call.
1207
+ HOOK_CTX=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/hook-context" -H "Authorization: Bearer $JWT" --max-time 4 2>/dev/null || echo "")
1208
+ SYNKRO_INFERENCE_TIER=$(echo "$HOOK_CTX" | jq -r '.tier // empty' 2>/dev/null)
1209
+ SYNKRO_CAPTURE_DEPTH=$(echo "$HOOK_CTX" | jq -r '.capture_depth // empty' 2>/dev/null)
1228
1210
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
1229
1211
  SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
1230
1212
 
1231
1213
  if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
1232
1214
  RULES_CACHE="$HOME/.synkro/.rules-cache-edit"
1233
1215
  RULES_JQ='[.rules[]? | select(.hook_stage == "pre" or .hook_stage == "both" or .hook_stage == null) | {rule_id, text, severity, category, mode}]'
1234
- ORG_RULES=$(jq -c "$RULES_JQ" "$_RULES_TMP" 2>/dev/null || echo "[]")
1216
+ ORG_RULES=$(echo "$HOOK_CTX" | jq -c "$RULES_JQ" 2>/dev/null || echo "[]")
1235
1217
  if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
1236
1218
  printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
1237
1219
  elif [ -f "$RULES_CACHE" ]; then
@@ -1658,25 +1640,16 @@ if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
1658
1640
  ) &
1659
1641
  fi
1660
1642
 
1661
- # Parallel fetch: /cli/me + rules in background, wait for both.
1662
- _ME_TMP=$(mktemp -t synkro-me.XXXXXX)
1663
- _RULES_TMP=$(mktemp -t synkro-rules.XXXXXX)
1664
- trap "rm -f \\"$_ME_TMP\\" \\"$_RULES_TMP\\"" EXIT
1665
- curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_ME_TMP" &
1666
- _ME_PID=$!
1667
- curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_RULES_TMP" &
1668
- _RULES_PID=$!
1669
- wait $_ME_PID 2>/dev/null; wait $_RULES_PID 2>/dev/null
1670
-
1671
- ME_RESP=$(cat "$_ME_TMP" 2>/dev/null || echo "")
1672
- SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
1673
- SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
1643
+ # Single fetch: /cli/hook-context returns me + rules in one call.
1644
+ HOOK_CTX=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/hook-context" -H "Authorization: Bearer $JWT" --max-time 4 2>/dev/null || echo "")
1645
+ SYNKRO_INFERENCE_TIER=$(echo "$HOOK_CTX" | jq -r '.tier // empty' 2>/dev/null)
1646
+ SYNKRO_CAPTURE_DEPTH=$(echo "$HOOK_CTX" | jq -r '.capture_depth // empty' 2>/dev/null)
1674
1647
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
1675
1648
  SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
1676
1649
 
1677
1650
  if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
1678
1651
  RULES_CACHE="$HOME/.synkro/.rules-cache-edit-capture"
1679
- ORG_RULES=$(jq -c '[.rules[]? | select(.hook_stage == "post" or .hook_stage == "both" or .hook_stage == null) | {rule_id, text, severity, category}]' "$_RULES_TMP" 2>/dev/null || echo "[]")
1652
+ ORG_RULES=$(echo "$HOOK_CTX" | jq -c '[.rules[]? | select(.hook_stage == "post" or .hook_stage == "both" or .hook_stage == null) | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
1680
1653
  if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
1681
1654
  printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
1682
1655
  elif [ -f "$RULES_CACHE" ]; then
@@ -1684,9 +1657,9 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1684
1657
  fi
1685
1658
  if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1686
1659
 
1687
- # Extract CVE config from rules response (allowlist + min_severity)
1688
- CVE_ALLOWLIST=$(echo "$RULES_RESP" | jq -c '.cve_config.allowlist // []' 2>/dev/null || echo "[]")
1689
- CVE_MIN_SEVERITY=$(echo "$RULES_RESP" | jq '.cve_config.min_severity // null' 2>/dev/null || echo "null")
1660
+ # Extract CVE config from hook-context response (allowlist + min_severity)
1661
+ CVE_ALLOWLIST=$(echo "$HOOK_CTX" | jq -c '.cve_config.allowlist // []' 2>/dev/null || echo "[]")
1662
+ CVE_MIN_SEVERITY=$(echo "$HOOK_CTX" | jq '.cve_config.min_severity // null' 2>/dev/null || echo "null")
1690
1663
 
1691
1664
  # CVE scan \u2014 runs server-side in parallel with local LLM grading
1692
1665
  CVE_RESULT_FILE=$(mktemp -t synkro-cve.XXXXXX)
@@ -1726,8 +1699,31 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1726
1699
  CVE_TEXT=""
1727
1700
  CVE_FINDINGS_JSON="[]"
1728
1701
  if [ -s "$CVE_RESULT_FILE" ]; then
1729
- CVE_TEXT=$(jq -r '.summary // empty' "$CVE_RESULT_FILE" 2>/dev/null || echo "")
1730
- CVE_FINDINGS_JSON=$(jq -c '[.findings[]? | {package: .package, version: .version, cve: .id, severity: .severity, score: .score}]' "$CVE_RESULT_FILE" 2>/dev/null || echo "[]")
1702
+ # Only flag CVEs for packages introduced by THIS edit, not pre-existing imports.
1703
+ # Extract new_string from the diff \u2014 if the import existed in old_string too, skip it.
1704
+ EDIT_NEW=$(echo "$DIFF_FIELD" | jq -r '.new_string // empty' 2>/dev/null)
1705
+ EDIT_OLD=$(echo "$DIFF_FIELD" | jq -r '.old_string // empty' 2>/dev/null)
1706
+ if [ -n "$EDIT_NEW" ] && [ "$DIFF_FIELD" != "null" ]; then
1707
+ CVE_FINDINGS_JSON=$(jq -c --arg new_s "$EDIT_NEW" --arg old_s "$EDIT_OLD" '
1708
+ [.findings[]? | . as $f |
1709
+ select(
1710
+ ($new_s | test($f.package; "i")) and
1711
+ (($old_s | length) == 0 or ($old_s | test($f.package; "i") | not))
1712
+ ) | {package, version, cve: .id, severity, score}]
1713
+ ' "$CVE_RESULT_FILE" 2>/dev/null || echo "[]")
1714
+ else
1715
+ CVE_FINDINGS_JSON=$(jq -c '[.findings[]? | {package: .package, version: .version, cve: .id, severity: .severity, score: .score}]' "$CVE_RESULT_FILE" 2>/dev/null || echo "[]")
1716
+ fi
1717
+ # Regenerate summary from filtered findings only
1718
+ if [ "$CVE_FINDINGS_JSON" != "[]" ] && [ -n "$CVE_FINDINGS_JSON" ]; then
1719
+ CVE_TEXT=$(echo "$CVE_FINDINGS_JSON" | jq -r '
1720
+ group_by(.package) | map(
1721
+ (.[0].package) + " (" + (length | tostring) + " CVEs, max CVSS " +
1722
+ ([.[].score // 0] | max | tostring) + ": " +
1723
+ ([.[].cve] | join(", ")) + ")"
1724
+ ) | join("; ")
1725
+ ' 2>/dev/null || echo "")
1726
+ fi
1731
1727
  fi
1732
1728
 
1733
1729
  # Wrapper extraction (greedy \u2014 tolerates nested XML tags).
@@ -4837,7 +4833,7 @@ function writeConfigEnv(opts) {
4837
4833
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
4838
4834
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
4839
4835
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
4840
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.12")}`
4836
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.13")}`
4841
4837
  ];
4842
4838
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
4843
4839
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -5215,10 +5211,19 @@ async function installCommand(opts = {}) {
5215
5211
  console.log(`Local-CC pueue task: id=${t.id} status=${t.status}`);
5216
5212
  console.log("Waiting for channel...");
5217
5213
  const ready = await waitForChannelReady(CHANNEL_PORT, 6e4, CHANNEL_HOST);
5218
- if (ready) console.log(` channel ready at ${CHANNEL_HOST}:${CHANNEL_PORT}
5219
- `);
5220
- else console.warn(` \u26A0 channel did not come up within 60s \u2014 check \`synkro local-cc logs\`
5214
+ if (ready) {
5215
+ console.log(` channel ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);
5216
+ try {
5217
+ console.log(" warming up inference...");
5218
+ await submitToChannel("grade-bash", "Proposed command: echo hello\nUser intent: warmup\nRecent user messages: []\nRecent actions: []\nOrg rules: []\n", { timeoutMs: 3e4 });
5219
+ console.log(" inference warm\n");
5220
+ } catch {
5221
+ console.log(" warmup skipped (non-fatal)\n");
5222
+ }
5223
+ } else {
5224
+ console.warn(` \u26A0 channel did not come up within 60s \u2014 check \`synkro local-cc logs\`
5221
5225
  `);
5226
+ }
5222
5227
  } catch (err) {
5223
5228
  console.warn(` \u26A0 Local-CC setup skipped: ${err.message}`);
5224
5229
  console.warn(" Install pueue, tmux, and claude, then re-run install.\n");