@synkro-sh/cli 1.3.30 → 1.3.32

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
@@ -602,9 +602,34 @@ fi
602
602
  if [ "$USE_LOCAL" = "true" ]; then
603
603
  # \u2500\u2500\u2500 FREE TIER: grade via the persistent claude daemon (mode=bash). \u2500\u2500\u2500
604
604
 
605
- # Fetch org guardrail rules relevant to this command (skip in local_only \u2014 no content leaves device).
605
+ # Refresh primer if older than 24h (server-side IP, fetched on demand).
606
+ PRIMER_FILE="$HOME/.synkro/grader-primer-bash.txt"
607
+ if [ ! -f "$PRIMER_FILE" ] || ! find "$PRIMER_FILE" -mmin -1440 2>/dev/null | grep -q .; then
608
+ NEW_PRIMER=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/judge-prompts" \\
609
+ -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null \\
610
+ | jq -r '.grader_primer_bash // empty' 2>/dev/null || echo "")
611
+ if [ -n "$NEW_PRIMER" ]; then
612
+ printf '%s' "$NEW_PRIMER" > "$PRIMER_FILE" 2>/dev/null || true
613
+ # Kill the daemon so it restarts with the fresh primer.
614
+ DAEMON_PID_FILE="$HOME/.synkro/daemon/bash/daemon.pid"
615
+ [ -f "$DAEMON_PID_FILE" ] && kill -TERM "$(cat "$DAEMON_PID_FILE" 2>/dev/null)" 2>/dev/null || true
616
+ fi
617
+ fi
618
+
619
+ # Fetch org guardrail rules. In local_only mode use GET (no command leaks);
620
+ # in other modes use POST with content for embedding-based top_k matching.
621
+ RULES_CACHE="$HOME/.synkro/.rules-cache-bash"
606
622
  ORG_RULES="[]"
607
- if [ "$SYNKRO_CAPTURE_DEPTH" != "local_only" ]; then
623
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
624
+ if find "$RULES_CACHE" -mmin -60 2>/dev/null | grep -q .; then
625
+ ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
626
+ else
627
+ ORG_RULES=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
628
+ -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null \\
629
+ | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
630
+ [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
631
+ fi
632
+ else
608
633
  ORG_RULES=$(printf '%s' "$COMMAND" | head -c 4000 \\
609
634
  | jq -Rs '{content: .}' \\
610
635
  | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=15" \\
@@ -612,8 +637,8 @@ if [ "$USE_LOCAL" = "true" ]; then
612
637
  -H "Authorization: Bearer $JWT" \\
613
638
  -d @- --max-time 2 2>/dev/null \\
614
639
  | 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
640
  fi
641
+ if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
617
642
 
618
643
  GRADER_PROMPT_FILE=$(mktemp -t synkro-bash-prompt.XXXXXX)
619
644
  trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
@@ -1029,8 +1054,31 @@ fi
1029
1054
 
1030
1055
  if [ "$USE_LOCAL" = "true" ]; then
1031
1056
  # \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (Python helper).
1057
+
1058
+ # Refresh primer if older than 24h (server-side IP, fetched on demand).
1059
+ PRIMER_FILE="$HOME/.synkro/grader-primer-edit.txt"
1060
+ if [ ! -f "$PRIMER_FILE" ] || ! find "$PRIMER_FILE" -mmin -1440 2>/dev/null | grep -q .; then
1061
+ NEW_PRIMER=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/judge-prompts" \\
1062
+ -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null \\
1063
+ | jq -r '.grader_primer_edit // empty' 2>/dev/null || echo "")
1064
+ if [ -n "$NEW_PRIMER" ]; then
1065
+ printf '%s' "$NEW_PRIMER" > "$PRIMER_FILE" 2>/dev/null || true
1066
+ DAEMON_PID_FILE="$HOME/.synkro/daemon/edit/daemon.pid"
1067
+ [ -f "$DAEMON_PID_FILE" ] && kill -TERM "$(cat "$DAEMON_PID_FILE" 2>/dev/null)" 2>/dev/null || true
1068
+ fi
1069
+ fi
1070
+ RULES_CACHE="$HOME/.synkro/.rules-cache-edit"
1032
1071
  ORG_RULES="[]"
1033
- if [ "$SYNKRO_CAPTURE_DEPTH" != "local_only" ]; then
1072
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
1073
+ if find "$RULES_CACHE" -mmin -60 2>/dev/null | grep -q .; then
1074
+ ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
1075
+ else
1076
+ ORG_RULES=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
1077
+ -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null \\
1078
+ | jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
1079
+ [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
1080
+ fi
1081
+ else
1034
1082
  ORG_RULES=$(printf '%s' "$PROPOSED" | head -c 8000 \\
1035
1083
  | jq -Rs '{content: .}' \\
1036
1084
  | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
@@ -1038,8 +1086,8 @@ if [ "$USE_LOCAL" = "true" ]; then
1038
1086
  -H "Authorization: Bearer $JWT" \\
1039
1087
  -d @- --max-time 2 2>/dev/null \\
1040
1088
  | 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
1089
  fi
1090
+ if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1043
1091
 
1044
1092
  GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)
1045
1093
  trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
@@ -1070,51 +1118,68 @@ if [ "$USE_LOCAL" = "true" ]; then
1070
1118
  VERDICT_JSON='{"ok":true,"violations":[]}'
1071
1119
  fi
1072
1120
 
1073
- LOCAL_BODY=$(jq -n \\
1074
- --argjson verdict "$VERDICT_JSON" \\
1075
- --arg file_path "$FILE_PATH" \\
1076
- --arg tool_name "$TOOL_NAME" \\
1077
- --arg content "$PROPOSED" \\
1078
- --arg file_before "$FILE_BEFORE" \\
1079
- --argjson diff "$DIFF_FIELD" \\
1080
- --arg user_intent "$USER_INTENT" \\
1081
- --argjson recent_actions "$RECENT_ACTIONS" \\
1082
- --arg session_id "$SESSION_ID" \\
1083
- --arg tool_use_id "$TOOL_USE_ID" \\
1084
- --arg cwd "$CWD" \\
1085
- --arg permission_mode "$PERMISSION_MODE" \\
1086
- --arg headless_flag "$HEADLESS_FLAG" \\
1087
- --arg repo "$GIT_REPO" \\
1088
- '{
1089
- verdict: $verdict,
1090
- file_path: $file_path,
1091
- tool_name: $tool_name,
1092
- content: $content,
1093
- file_before: (if ($file_before | length) > 0 then $file_before else null end),
1094
- diff: $diff,
1095
- user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),
1096
- recent_actions: $recent_actions,
1097
- session_id: (if ($session_id | length) > 0 then $session_id else null end),
1098
- tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),
1099
- cwd: (if ($cwd | length) > 0 then $cwd else null end),
1100
- permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),
1101
- headless: ($headless_flag == "1"),
1102
- repo: (if ($repo | length) > 0 then $repo else null end)
1103
- }')
1104
-
1105
- RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict" \\
1106
- -H "Content-Type: application/json" \\
1107
- -H "Authorization: Bearer $JWT" \\
1108
- -d "$LOCAL_BODY" \\
1109
- --max-time 5 2>/dev/null || echo "")
1121
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
1122
+ # No file content / diff / intent / actions leave the device. Synthesize
1123
+ # the hook response from the local verdict only.
1124
+ OK=$(echo "$VERDICT_JSON" | jq -r '.ok // true' 2>/dev/null)
1125
+ if [ "$OK" = "false" ]; then
1126
+ FIRST_REASON=$(echo "$VERDICT_JSON" | jq -r '.violations[0].reason // "policy violation"' 2>/dev/null)
1127
+ RULE_ID=$(echo "$VERDICT_JSON" | jq -r '.violations[0].rule_id // "local_violation"' 2>/dev/null)
1128
+ if [ "$IS_HEADLESS" = "1" ]; then DEC="deny"; else DEC="ask"; fi
1129
+ RESP=$(jq -n \\
1130
+ --arg dec "$DEC" \\
1131
+ --arg reason "[synkro] $RULE_ID: $FIRST_REASON" \\
1132
+ '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: $dec, permissionDecisionReason: $reason, additionalContext: $reason }, reason: $reason }')
1133
+ else
1134
+ RESP=$(jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" }, reason: "" }')
1135
+ fi
1136
+ else
1137
+ LOCAL_BODY=$(jq -n \\
1138
+ --argjson verdict "$VERDICT_JSON" \\
1139
+ --arg file_path "$FILE_PATH" \\
1140
+ --arg tool_name "$TOOL_NAME" \\
1141
+ --arg content "$PROPOSED" \\
1142
+ --arg file_before "$FILE_BEFORE" \\
1143
+ --argjson diff "$DIFF_FIELD" \\
1144
+ --arg user_intent "$USER_INTENT" \\
1145
+ --argjson recent_actions "$RECENT_ACTIONS" \\
1146
+ --arg session_id "$SESSION_ID" \\
1147
+ --arg tool_use_id "$TOOL_USE_ID" \\
1148
+ --arg cwd "$CWD" \\
1149
+ --arg permission_mode "$PERMISSION_MODE" \\
1150
+ --arg headless_flag "$HEADLESS_FLAG" \\
1151
+ --arg repo "$GIT_REPO" \\
1152
+ '{
1153
+ verdict: $verdict,
1154
+ file_path: $file_path,
1155
+ tool_name: $tool_name,
1156
+ content: $content,
1157
+ file_before: (if ($file_before | length) > 0 then $file_before else null end),
1158
+ diff: $diff,
1159
+ user_intent: (if ($user_intent | length) > 0 then $user_intent else null end),
1160
+ recent_actions: $recent_actions,
1161
+ session_id: (if ($session_id | length) > 0 then $session_id else null end),
1162
+ tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),
1163
+ cwd: (if ($cwd | length) > 0 then $cwd else null end),
1164
+ permission_mode: (if ($permission_mode | length) > 0 then $permission_mode else null end),
1165
+ headless: ($headless_flag == "1"),
1166
+ repo: (if ($repo | length) > 0 then $repo else null end)
1167
+ }')
1110
1168
 
1111
- if echo "$RESP" | grep -qE '"detail":"Token has expired|"detail":"Invalid or expired token'; then
1112
- if refresh_jwt; then
1113
- RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict" \\
1114
- -H "Content-Type: application/json" \\
1115
- -H "Authorization: Bearer $JWT" \\
1116
- -d "$LOCAL_BODY" \\
1117
- --max-time 5 2>/dev/null || echo "")
1169
+ RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict" \\
1170
+ -H "Content-Type: application/json" \\
1171
+ -H "Authorization: Bearer $JWT" \\
1172
+ -d "$LOCAL_BODY" \\
1173
+ --max-time 5 2>/dev/null || echo "")
1174
+
1175
+ if echo "$RESP" | grep -qE '"detail":"Token has expired|"detail":"Invalid or expired token'; then
1176
+ if refresh_jwt; then
1177
+ RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/local-verdict" \\
1178
+ -H "Content-Type: application/json" \\
1179
+ -H "Authorization: Bearer $JWT" \\
1180
+ -d "$LOCAL_BODY" \\
1181
+ --max-time 5 2>/dev/null || echo "")
1182
+ fi
1118
1183
  fi
1119
1184
  fi
1120
1185
  else
@@ -1354,32 +1419,57 @@ if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
1354
1419
  ) &
1355
1420
  fi
1356
1421
 
1357
- # Resolve tier (cached 60 min) \u2014 same logic as the other hooks.
1422
+ # Resolve tier + capture_depth (cached 60 min).
1358
1423
  TIER_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}"
1424
+ CD_CACHE_FILE="\${TIER_CACHE_FILE}.cd"
1359
1425
  SYNKRO_INFERENCE_TIER=""
1426
+ SYNKRO_CAPTURE_DEPTH=""
1360
1427
  if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
1361
1428
  SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null || true)
1429
+ SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null || true)
1362
1430
  fi
1363
1431
  if [ -z "$SYNKRO_INFERENCE_TIER" ]; then
1364
1432
  ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
1365
1433
  if [ -n "$ME_RESP" ]; then
1366
1434
  SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null || true)
1367
1435
  [ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
1436
+ SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null || true)
1437
+ [ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
1368
1438
  fi
1369
1439
  fi
1370
1440
  SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
1441
+ SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
1371
1442
 
1372
- if [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
1373
- # \u2500\u2500\u2500 FREE TIER: grade via the persistent claude daemon (mode=edit). \u2500\u2500\u2500
1443
+ USE_LOCAL=false
1444
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && command -v claude >/dev/null 2>&1; then
1445
+ USE_LOCAL=true
1446
+ elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
1447
+ USE_LOCAL=true
1448
+ fi
1374
1449
 
1375
- # Fetch org guardrail rules relevant to this file content.
1376
- ORG_RULES=$(printf '%s' "$FILE_CONTENT" | head -c 8000 \\
1377
- | jq -Rs '{content: .}' \\
1378
- | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
1379
- -X POST -H "Content-Type: application/json" \\
1380
- -H "Authorization: Bearer $JWT" \\
1381
- -d @- --max-time 2 2>/dev/null \\
1382
- | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
1450
+ if [ "$USE_LOCAL" = "true" ]; then
1451
+ # \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (mode=edit). \u2500\u2500\u2500
1452
+
1453
+ # In local_only: GET all rules (no content sent), cache 1h. Otherwise POST with content.
1454
+ RULES_CACHE="$HOME/.synkro/.rules-cache-edit-capture"
1455
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
1456
+ if find "$RULES_CACHE" -mmin -60 2>/dev/null | grep -q .; then
1457
+ ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
1458
+ else
1459
+ ORG_RULES=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
1460
+ -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null \\
1461
+ | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
1462
+ [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
1463
+ fi
1464
+ else
1465
+ ORG_RULES=$(printf '%s' "$FILE_CONTENT" | head -c 8000 \\
1466
+ | jq -Rs '{content: .}' \\
1467
+ | curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
1468
+ -X POST -H "Content-Type: application/json" \\
1469
+ -H "Authorization: Bearer $JWT" \\
1470
+ -d @- --max-time 2 2>/dev/null \\
1471
+ | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
1472
+ fi
1383
1473
  if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
1384
1474
 
1385
1475
  GRADER_PROMPT_FILE=$(mktemp -t synkro-edit-capture.XXXXXX)
@@ -1653,6 +1743,12 @@ CREDS_PATH="\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}"
1653
1743
 
1654
1744
  if [ ! -f "$CREDS_PATH" ]; then echo '{}'; exit 0; fi
1655
1745
  if [ "\${SYNKRO_TRANSCRIPT_CONSENT:-yes}" = "no" ]; then echo '{}'; exit 0; fi
1746
+
1747
+ # Hard-skip in local_only privacy mode \u2014 conversation content must never leave the device.
1748
+ CD_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}.cd"
1749
+ SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null || echo "full")
1750
+ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then echo '{}'; exit 0; fi
1751
+
1656
1752
  JWT=$(jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null)
1657
1753
  if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
1658
1754
 
@@ -3428,12 +3524,27 @@ function ensureSynkroDir() {
3428
3524
  mkdirSync5(BIN_DIR, { recursive: true });
3429
3525
  mkdirSync5(OFFSETS_DIR, { recursive: true });
3430
3526
  }
3431
- function writeGraderDaemon() {
3527
+ async function fetchGraderPrimers(gatewayUrl, token) {
3528
+ try {
3529
+ const resp = await fetch(`${gatewayUrl}/api/v1/cli/judge-prompts`, {
3530
+ headers: { "Authorization": `Bearer ${token}` }
3531
+ });
3532
+ if (!resp.ok) throw new Error(`status ${resp.status}`);
3533
+ const data = await resp.json();
3534
+ if (!data.grader_primer_bash || !data.grader_primer_edit) throw new Error("missing primer fields");
3535
+ return { bash: data.grader_primer_bash, edit: data.grader_primer_edit };
3536
+ } catch (err) {
3537
+ console.warn(`[synkro] primer fetch failed (${err.message}); using bundled fallback`);
3538
+ return { bash: GRADER_PRIMER_BASH, edit: GRADER_PRIMER_EDIT };
3539
+ }
3540
+ }
3541
+ async function writeGraderDaemon(gatewayUrl, token) {
3432
3542
  writeFileSync5(GRADER_DAEMON_PATH, GRADER_DAEMON_PY, "utf-8");
3433
3543
  chmodSync(GRADER_DAEMON_PATH, 493);
3434
- writeFileSync5(GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_EDIT, "utf-8");
3544
+ const primers = await fetchGraderPrimers(gatewayUrl, token);
3545
+ writeFileSync5(GRADER_PRIMER_EDIT_PATH, primers.edit, "utf-8");
3435
3546
  chmodSync(GRADER_PRIMER_EDIT_PATH, 420);
3436
- writeFileSync5(GRADER_PRIMER_BASH_PATH, GRADER_PRIMER_BASH, "utf-8");
3547
+ writeFileSync5(GRADER_PRIMER_BASH_PATH, primers.bash, "utf-8");
3437
3548
  chmodSync(GRADER_PRIMER_BASH_PATH, 420);
3438
3549
  }
3439
3550
  function writeHookScripts() {
@@ -3491,7 +3602,7 @@ function writeConfigEnv(opts) {
3491
3602
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3492
3603
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3493
3604
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3494
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.30")}`
3605
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.32")}`
3495
3606
  ];
3496
3607
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3497
3608
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -3719,7 +3830,7 @@ async function installCommand(opts = {}) {
3719
3830
  console.log(` ${scripts.sessionStartScript}`);
3720
3831
  console.log(` ${scripts.transcriptSyncScript}
3721
3832
  `);
3722
- writeGraderDaemon();
3833
+ await writeGraderDaemon(gatewayUrl, token);
3723
3834
  for (const mode of ["edit", "bash"]) {
3724
3835
  const pidFile = join6(SYNKRO_DIR2, "daemon", mode, "daemon.pid");
3725
3836
  try {