@synkro-sh/cli 1.4.11 → 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
@@ -1247,15 +1229,20 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1247
1229
  printf 'Diff:\\n' >> "$GRADER_PROMPT_FILE"
1248
1230
  printf '%s\\n' "$PROPOSED" >> "$GRADER_PROMPT_FILE"
1249
1231
 
1232
+ ROUTE_TAG=""
1250
1233
  if synkro_channel_up && [ -n "\${SYNKRO_CLI_BIN:-}" ] && [ -f "$SYNKRO_CLI_BIN" ] && command -v node >/dev/null 2>&1; then
1234
+ ROUTE_TAG="local-cc"
1251
1235
  synkro_log "editGuard $FILE_SHORT \u2192 routing via local-cc"
1252
1236
  CC_RESP=$(node "$SYNKRO_CLI_BIN" grade edit < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1253
1237
  elif synkro_channel_up && command -v synkro >/dev/null 2>&1; then
1238
+ ROUTE_TAG="local-cc"
1254
1239
  synkro_log "editGuard $FILE_SHORT \u2192 routing via local-cc (PATH)"
1255
1240
  CC_RESP=$(synkro grade edit < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1256
1241
  else
1242
+ ROUTE_TAG="cc-print"
1257
1243
  CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1258
1244
  fi
1245
+ SYNKRO_PREFIX="[synkro:\${ROUTE_TAG:-cloud}]"
1259
1246
 
1260
1247
  # Wrapper extraction (greedy \u2014 tolerates nested XML tags).
1261
1248
  V_INNER=$(printf '%s' "$CC_RESP" | tr '\\n' ' ' | sed -nE 's|.*<synkro-verdict>(.*)</synkro-verdict>.*|\\1|p' | tail -1)
@@ -1354,6 +1341,7 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1354
1341
  fi
1355
1342
  else
1356
1343
  # \u2500\u2500\u2500 Server-side grading. \u2500\u2500\u2500
1344
+ SYNKRO_PREFIX="[synkro:cloud]"
1357
1345
  RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit" \\
1358
1346
  -H "Content-Type: application/json" \\
1359
1347
  -H "Authorization: Bearer $JWT" \\
@@ -1373,13 +1361,13 @@ fi
1373
1361
 
1374
1362
  if [ -z "$RESP" ]; then
1375
1363
  synkro_log "editGuard $FILE_SHORT \u2192 error (timeout)"
1376
- jq -n --arg m "[synkro] editGuard $FILE_SHORT \u2192 error (timeout)" '{systemMessage: $m}'
1364
+ jq -n --arg m "$SYNKRO_PREFIX editGuard $FILE_SHORT \u2192 error (timeout)" '{systemMessage: $m}'
1377
1365
  exit 0
1378
1366
  fi
1379
1367
 
1380
1368
  if ! echo "$RESP" | jq -e 'type == "object"' >/dev/null 2>&1; then
1381
1369
  synkro_log "editGuard $FILE_SHORT \u2192 error (bad response)"
1382
- jq -n --arg m "[synkro] editGuard $FILE_SHORT \u2192 error (bad response)" '{systemMessage: $m}'
1370
+ jq -n --arg m "$SYNKRO_PREFIX editGuard $FILE_SHORT \u2192 error (bad response)" '{systemMessage: $m}'
1383
1371
  exit 0
1384
1372
  fi
1385
1373
 
@@ -1392,10 +1380,10 @@ else
1392
1380
  VERDICT_REASON=$(echo "$RESP" | jq -r '.reason // empty' 2>/dev/null)
1393
1381
  if [ -n "$VERDICT_REASON" ]; then
1394
1382
  synkro_log "editGuard $FILE_SHORT \u2192 pass: $VERDICT_REASON"
1395
- RESP_WITH_MSG=$(echo "$RESP" | jq --arg m "[synkro] editGuard $FILE_SHORT \u2192 pass: $VERDICT_REASON" '. + {systemMessage: $m}')
1383
+ RESP_WITH_MSG=$(echo "$RESP" | jq --arg m "$SYNKRO_PREFIX editGuard $FILE_SHORT \u2192 pass: $VERDICT_REASON" '. + {systemMessage: $m}')
1396
1384
  else
1397
1385
  synkro_log "editGuard $FILE_SHORT \u2192 pass"
1398
- RESP_WITH_MSG=$(echo "$RESP" | jq --arg m "[synkro] editGuard $FILE_SHORT \u2192 pass" '. + {systemMessage: $m}')
1386
+ RESP_WITH_MSG=$(echo "$RESP" | jq --arg m "$SYNKRO_PREFIX editGuard $FILE_SHORT \u2192 pass" '. + {systemMessage: $m}')
1399
1387
  fi
1400
1388
  echo "$RESP_WITH_MSG"
1401
1389
  fi
@@ -1652,25 +1640,16 @@ if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
1652
1640
  ) &
1653
1641
  fi
1654
1642
 
1655
- # Parallel fetch: /cli/me + rules in background, wait for both.
1656
- _ME_TMP=$(mktemp -t synkro-me.XXXXXX)
1657
- _RULES_TMP=$(mktemp -t synkro-rules.XXXXXX)
1658
- trap "rm -f \\"$_ME_TMP\\" \\"$_RULES_TMP\\"" EXIT
1659
- curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_ME_TMP" &
1660
- _ME_PID=$!
1661
- curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null > "$_RULES_TMP" &
1662
- _RULES_PID=$!
1663
- wait $_ME_PID 2>/dev/null; wait $_RULES_PID 2>/dev/null
1664
-
1665
- ME_RESP=$(cat "$_ME_TMP" 2>/dev/null || echo "")
1666
- SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
1667
- 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)
1668
1647
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
1669
1648
  SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
1670
1649
 
1671
1650
  if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
1672
1651
  RULES_CACHE="$HOME/.synkro/.rules-cache-edit-capture"
1673
- 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 "[]")
1674
1653
  if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
1675
1654
  printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
1676
1655
  elif [ -f "$RULES_CACHE" ]; then
@@ -1678,9 +1657,9 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1678
1657
  fi
1679
1658
  if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1680
1659
 
1681
- # Extract CVE config from rules response (allowlist + min_severity)
1682
- CVE_ALLOWLIST=$(echo "$RULES_RESP" | jq -c '.cve_config.allowlist // []' 2>/dev/null || echo "[]")
1683
- 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")
1684
1663
 
1685
1664
  # CVE scan \u2014 runs server-side in parallel with local LLM grading
1686
1665
  CVE_RESULT_FILE=$(mktemp -t synkro-cve.XXXXXX)
@@ -1700,23 +1679,51 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1700
1679
  printf 'Content:\\n' >> "$GRADER_PROMPT_FILE"
1701
1680
  printf '%s\\n' "$FILE_CONTENT" >> "$GRADER_PROMPT_FILE"
1702
1681
 
1682
+ ROUTE_TAG=""
1703
1683
  if synkro_channel_up && [ -n "\${SYNKRO_CLI_BIN:-}" ] && [ -f "$SYNKRO_CLI_BIN" ] && command -v node >/dev/null 2>&1; then
1684
+ ROUTE_TAG="local-cc"
1704
1685
  synkro_log "editGuard $BASENAME \u2192 routing via local-cc"
1705
1686
  CC_RESP=$(node "$SYNKRO_CLI_BIN" grade edit < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1706
1687
  elif synkro_channel_up && command -v synkro >/dev/null 2>&1; then
1688
+ ROUTE_TAG="local-cc"
1707
1689
  synkro_log "editGuard $BASENAME \u2192 routing via local-cc (PATH)"
1708
1690
  CC_RESP=$(synkro grade edit < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1709
1691
  else
1692
+ ROUTE_TAG="cc-print"
1710
1693
  CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
1711
1694
  fi
1695
+ SYNKRO_PREFIX="[synkro:\${ROUTE_TAG:-cloud}]"
1712
1696
 
1713
1697
  # Wait for CVE scan
1714
1698
  wait $CVE_PID 2>/dev/null
1715
1699
  CVE_TEXT=""
1716
1700
  CVE_FINDINGS_JSON="[]"
1717
1701
  if [ -s "$CVE_RESULT_FILE" ]; then
1718
- CVE_TEXT=$(jq -r '.summary // empty' "$CVE_RESULT_FILE" 2>/dev/null || echo "")
1719
- 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
1720
1727
  fi
1721
1728
 
1722
1729
  # Wrapper extraction (greedy \u2014 tolerates nested XML tags).
@@ -1765,6 +1772,7 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
1765
1772
  fi
1766
1773
  else
1767
1774
  # \u2500\u2500\u2500 Server-side grading. \u2500\u2500\u2500
1775
+ SYNKRO_PREFIX="[synkro:cloud]"
1768
1776
  RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/judge-edit" \\
1769
1777
  -H "Content-Type: application/json" \\
1770
1778
  -H "Authorization: Bearer $JWT" \\
@@ -1784,7 +1792,7 @@ fi
1784
1792
 
1785
1793
  if [ -z "$RESP" ] || ! echo "$RESP" | jq -e 'type == "object"' >/dev/null 2>&1; then
1786
1794
  synkro_log "editScan $BASENAME \u2192 error (no response)"
1787
- jq -n --arg m "[synkro] editScan $BASENAME \u2192 error (no response)" '{systemMessage: $m}'
1795
+ jq -n --arg m "$SYNKRO_PREFIX editScan $BASENAME \u2192 error (no response)" '{systemMessage: $m}'
1788
1796
  exit 0
1789
1797
  fi
1790
1798
 
@@ -1853,8 +1861,8 @@ fi
1853
1861
 
1854
1862
  if [ "$OK" = "false" ] && [ -n "$REASON" ]; then
1855
1863
  synkro_log "editScan $BASENAME \u2192 FAIL ($CATEGORY): $REASON"
1856
- SYS_MSG="[synkro] editScan $BASENAME \u2192 FAIL: \${REASON}"
1857
- ADDITIONAL_CTX="Synkro post-edit grader flagged \${BASENAME} (severity: \${SEVERITY}, category: \${CATEGORY}). Re-edit the file applying the retry guidance: \${REASON}"
1864
+ SYS_MSG="$SYNKRO_PREFIX editScan $BASENAME \u2192 FAIL: \${REASON}"
1865
+ ADDITIONAL_CTX="Synkro post-edit grader flagged \${BASENAME} (severity: \${SEVERITY}, category: \${CATEGORY}, route: \${ROUTE_TAG:-cloud}). Re-edit the file applying the retry guidance: \${REASON}"
1858
1866
  jq -n \\
1859
1867
  --arg sys_msg "$SYS_MSG" \\
1860
1868
  --arg ctx "$ADDITIONAL_CTX" \\
@@ -1870,10 +1878,10 @@ fi
1870
1878
 
1871
1879
  if [ -n "$REASON" ]; then
1872
1880
  synkro_log "editScan $BASENAME \u2192 pass ($CATEGORY): $REASON"
1873
- jq -n --arg m "[synkro] editScan $BASENAME \u2192 pass ($CATEGORY): $REASON" '{systemMessage: $m}'
1881
+ jq -n --arg m "$SYNKRO_PREFIX editScan $BASENAME \u2192 pass ($CATEGORY): $REASON" '{systemMessage: $m}'
1874
1882
  else
1875
1883
  synkro_log "editScan $BASENAME \u2192 pass"
1876
- jq -n --arg m "[synkro] editScan $BASENAME \u2192 pass" '{systemMessage: $m}'
1884
+ jq -n --arg m "$SYNKRO_PREFIX editScan $BASENAME \u2192 pass" '{systemMessage: $m}'
1877
1885
  fi
1878
1886
  exit 0
1879
1887
  `;
@@ -4825,7 +4833,7 @@ function writeConfigEnv(opts) {
4825
4833
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
4826
4834
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
4827
4835
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
4828
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.11")}`
4836
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.13")}`
4829
4837
  ];
4830
4838
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
4831
4839
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -5203,10 +5211,19 @@ async function installCommand(opts = {}) {
5203
5211
  console.log(`Local-CC pueue task: id=${t.id} status=${t.status}`);
5204
5212
  console.log("Waiting for channel...");
5205
5213
  const ready = await waitForChannelReady(CHANNEL_PORT, 6e4, CHANNEL_HOST);
5206
- if (ready) console.log(` channel ready at ${CHANNEL_HOST}:${CHANNEL_PORT}
5207
- `);
5208
- 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\`
5209
5225
  `);
5226
+ }
5210
5227
  } catch (err) {
5211
5228
  console.warn(` \u26A0 Local-CC setup skipped: ${err.message}`);
5212
5229
  console.warn(" Install pueue, tmux, and claude, then re-run install.\n");