direxio-deployer 0.1.0 → 0.1.2

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.
Files changed (51) hide show
  1. package/README.md +10 -2
  2. package/README_zh.md +10 -2
  3. package/SKILL.md +32 -8
  4. package/bin/direxio-deployer.mjs +1 -2
  5. package/package.json +2 -3
  6. package/references/agent-targets.md +7 -1
  7. package/references/deployment-lessons.md +5 -7
  8. package/references/deployment-workflow.md +8 -4
  9. package/references/runtime-wiring.md +5 -5
  10. package/references/tooling.md +11 -12
  11. package/references/user-journey.md +2 -2
  12. package/references/voip-turn-runbook.md +2 -2
  13. package/references/windows-deployment-notes.md +2 -1
  14. package/scripts/destroy.sh +24 -43
  15. package/scripts/json.mjs +841 -0
  16. package/scripts/lib/aws.sh +5 -1
  17. package/scripts/lib/json.sh +114 -0
  18. package/scripts/lib/operation_report.sh +8 -195
  19. package/scripts/lib/ops.sh +8 -21
  20. package/scripts/lib/state.sh +18 -44
  21. package/scripts/mcp-tools-list.mjs +66 -5
  22. package/scripts/orchestrate.sh +166 -249
  23. package/scripts/phases/s3_provision.sh +5 -10
  24. package/scripts/phases/s5_init_tokens.sh +7 -17
  25. package/scripts/phases/s6_wire_local.sh +22 -42
  26. package/scripts/phases/s7_verify_e2e.sh +5 -5
  27. package/scripts/pricing-estimate.sh +36 -80
  28. package/tests/aws_credentials_test.sh +0 -139
  29. package/tests/connect_daemon_runtime_check_test.sh +0 -120
  30. package/tests/default_paths_test.sh +0 -58
  31. package/tests/destroy_local_bridge_test.sh +0 -154
  32. package/tests/destroy_root_identity_test.sh +0 -91
  33. package/tests/destroy_route53_zone_test.sh +0 -80
  34. package/tests/domain_authoritative_dns_test.sh +0 -49
  35. package/tests/mcp_doctor_runtime_check_test.sh +0 -86
  36. package/tests/mcp_smoke_runtime_check_test.sh +0 -121
  37. package/tests/mcp_tools_runtime_check_test.sh +0 -123
  38. package/tests/npm_skill_distribution_test.sh +0 -95
  39. package/tests/operation_report_test.sh +0 -258
  40. package/tests/orchestrate_status_recovery_test.sh +0 -91
  41. package/tests/phase_timeout_test.sh +0 -88
  42. package/tests/pricing_estimate_test.sh +0 -159
  43. package/tests/render_userdata_remote_nodes_test.sh +0 -40
  44. package/tests/root_volume_tracking_test.sh +0 -41
  45. package/tests/route53_overwrite_guard_test.sh +0 -86
  46. package/tests/route53_zone_auto_create_test.sh +0 -66
  47. package/tests/runtime_summary_check_test.sh +0 -203
  48. package/tests/s6_wire_local_test.sh +0 -405
  49. package/tests/skill_structure_test.sh +0 -298
  50. package/tests/update_reset_ops_test.sh +0 -230
  51. package/tests/user_confirmation_gates_test.sh +0 -152
@@ -23,9 +23,7 @@ set -uo pipefail
23
23
  HERE=$(cd "$(dirname "$0")" && pwd)
24
24
  P2P_INSTALL_SCRIPTS_DIR="$HERE"
25
25
 
26
- # Prefer workspace-local tools when present. On Windows, jq may be downloaded
27
- # into .tools/bin/jq.exe by the operator/system and is discoverable from
28
- # Git Bash/MSYS only when this path is prepended.
26
+ # Prefer workspace-local tools when present.
29
27
  REPO_ROOT=$(cd "$HERE/.." && pwd)
30
28
  if [ -d "$REPO_ROOT/.tools/bin" ]; then
31
29
  PATH="$REPO_ROOT/.tools/bin:$PATH"
@@ -58,17 +56,12 @@ phase_file() {
58
56
  # Dependency check.
59
57
  check_deps() {
60
58
  local b missing=""
61
- for b in aws jq ssh scp curl; do
59
+ for b in aws ssh scp curl; do
62
60
  command -v "$b" >/dev/null 2>&1 || missing="$missing $b"
63
61
  done
64
62
  [ -z "$missing" ] && return 0
65
63
 
66
64
  warn "Missing dependencies:$missing"
67
- case " $missing " in
68
- *" jq "*)
69
- warn "jq is required for state.json. If this workspace has .tools/bin/jq.exe, run from a POSIX shell that can see that path."
70
- ;;
71
- esac
72
65
  case " $missing " in
73
66
  *" aws "*)
74
67
  warn "Install AWS CLI v2 and configure credentials first:"
@@ -108,9 +101,9 @@ cmd_status_inventory() {
108
101
  [ -f "$state" ] || continue
109
102
  found=1
110
103
  service_dir=${state%/state.json}
111
- domain=$(jq -r '.domain // empty' "$state")
112
- phase=$(jq -r '.phase // empty' "$state")
113
- instance=$(jq -r '.resources.instance_id // empty' "$state")
104
+ domain=$(json_get "$state" domain)
105
+ phase=$(json_get "$state" phase)
106
+ instance=$(json_get "$state" resources.instance_id)
114
107
  if STATE_JSON="$state" first_unfinished_phase >/dev/null 2>&1; then
115
108
  current=$(STATE_JSON="$state" first_unfinished_phase)
116
109
  else
@@ -287,7 +280,7 @@ cmd_status() {
287
280
  printf " %-20s %s\n" "$p" "$(phase_status "$p")"
288
281
  done
289
282
  echo "-- resources --"
290
- jq -r '.resources | to_entries[]? | " \(.key)=\(.value)"' "$STATE_JSON"
283
+ json_entries "$STATE_JSON" resources | sed 's/^/ /'
291
284
  print_recovery_summary "$current"
292
285
  }
293
286
 
@@ -320,10 +313,10 @@ print_delivery() {
320
313
  install_mode=$(state_get agent_install_mode)
321
314
  install_status=$(state_get agent_install_status)
322
315
  install_command=$(state_get agent_install_command)
323
- runtime_summary=$(jq -r '.runtime_checks.summary.status // "not_run"' "$STATE_JSON")
324
- app_gate=$(jq -r '.user_confirmations.app_initialization.status // "pending_user_confirmation"' "$STATE_JSON")
325
- real_chat_gate=$(jq -r '.user_confirmations.real_chat.status // "pending_user_confirmation"' "$STATE_JSON")
326
- agent_runtime_gate=$(jq -r '.user_confirmations.agent_mcp_runtime.status // "pending_runtime_confirmation"' "$STATE_JSON")
316
+ runtime_summary=$(json_get "$STATE_JSON" runtime_checks.summary.status "not_run")
317
+ app_gate=$(json_get "$STATE_JSON" user_confirmations.app_initialization.status "pending_user_confirmation")
318
+ real_chat_gate=$(json_get "$STATE_JSON" user_confirmations.real_chat.status "pending_user_confirmation")
319
+ agent_runtime_gate=$(json_get "$STATE_JSON" user_confirmations.agent_mcp_runtime.status "pending_runtime_confirmation")
327
320
  echo
328
321
  echo -e "\033[32m========== Automated Deployment Gates Passed ==========\033[0m"
329
322
  echo " App domain : $domain"
@@ -391,10 +384,10 @@ ensure_cost_estimate() {
391
384
  fi
392
385
 
393
386
  if output=$(bash "$HERE/pricing-estimate.sh" "${args[@]}" 2>/dev/null); then
394
- status=$(printf '%s\n' "$output" | jq -r '.pricing_status // "unknown"' 2>/dev/null)
395
- total=$(printf '%s\n' "$output" | jq -r '.total_monthly_usd // "unknown"' 2>/dev/null)
396
- region=$(printf '%s\n' "$output" | jq -r '.region // "unknown"' 2>/dev/null)
397
- instance_type=$(printf '%s\n' "$output" | jq -r '.components.ec2_instance.instance_type // "unknown"' 2>/dev/null)
387
+ status=$(printf '%s\n' "$output" | json_stdin_get pricing_status "unknown" 2>/dev/null)
388
+ total=$(printf '%s\n' "$output" | json_stdin_get total_monthly_usd "unknown" 2>/dev/null)
389
+ region=$(printf '%s\n' "$output" | json_stdin_get region "unknown" 2>/dev/null)
390
+ instance_type=$(printf '%s\n' "$output" | json_stdin_get components.ec2_instance.instance_type "unknown" 2>/dev/null)
398
391
  log "Cost estimate recorded (status=${status:-unknown}, region=${region:-unknown}, instance=${instance_type:-unknown}, monthly_usd≈${total:-unknown})."
399
392
  if [ "$status" = "fallback" ]; then
400
393
  warn "AWS Pricing API was unavailable or incomplete; cost_estimate uses conservative fallback values."
@@ -402,6 +395,25 @@ ensure_cost_estimate() {
402
395
  else
403
396
  warn "Could not write AWS cost estimate. Continue only after giving the user a manual billing estimate."
404
397
  fi
398
+ ensure_free_tier_credit_notice
399
+ }
400
+
401
+ ensure_free_tier_credit_notice() {
402
+ local output plan_status plan_type amount unit expires
403
+ if output=$(aws freetier get-account-plan-state --output json 2>/dev/null); then
404
+ plan_type=$(printf '%s\n' "$output" | json_stdin_get accountPlanType "unknown" 2>/dev/null)
405
+ plan_status=$(printf '%s\n' "$output" | json_stdin_get accountPlanStatus "unknown" 2>/dev/null)
406
+ amount=$(printf '%s\n' "$output" | json_stdin_get accountPlanRemainingCredits.amount "" 2>/dev/null)
407
+ unit=$(printf '%s\n' "$output" | json_stdin_get accountPlanRemainingCredits.unit "USD" 2>/dev/null)
408
+ expires=$(printf '%s\n' "$output" | json_stdin_get accountPlanExpirationDate "" 2>/dev/null)
409
+ if [ -n "$amount" ]; then
410
+ log "AWS Free Tier plan: type=${plan_type:-unknown}, status=${plan_status:-unknown}, remaining_credits=${amount} ${unit:-USD}${expires:+, expires=$expires}."
411
+ warn "Credits can reduce actual charges, but AWS resources still accrue charges until destroyed; verify credit coverage in AWS Billing Console."
412
+ return 0
413
+ fi
414
+ fi
415
+ warn "AWS new customer accounts may include Free Tier credits, currently advertised as 100 USD initial credits plus possible additional credits."
416
+ warn "Credits may cover a small trial deployment, but coverage is account-specific; verify credits in AWS Billing Console and destroy the node when finished."
405
417
  }
406
418
 
407
419
  precheck_new_deploy_domain_env() {
@@ -439,7 +451,7 @@ ensure_production_domain_selected() {
439
451
  state_domain=$(domain_normalize "$state_domain")
440
452
  state_mode=$(state_get domain_mode)
441
453
  env_domain=$(domain_normalize "${DOMAIN:-}")
442
- confirmed=$(jq -r '.domain_confirmed_irreversible // false' "$STATE_JSON")
454
+ confirmed=$(json_get "$STATE_JSON" domain_confirmed_irreversible false)
443
455
 
444
456
  if [ -n "$env_domain" ] && [ -n "$state_domain" ] && [ "$env_domain" != "$state_domain" ]; then
445
457
  warn "Deployment blocked: current state is bound to DOMAIN=$state_domain, but this run passed DOMAIN=${env_domain}."
@@ -484,22 +496,22 @@ ensure_production_domain_selected() {
484
496
  guard_existing_state() {
485
497
  [ -f "$STATE_JSON" ] || return 0
486
498
  local resources_count confirmed action
487
- resources_count=$(jq -r '.resources | length' "$STATE_JSON")
499
+ resources_count=$(json_length "$STATE_JSON" resources)
488
500
  [ "$resources_count" -eq 0 ] && return 0
489
- if [ "$(jq -r '.domain_mode // empty' "$STATE_JSON")" = "ec2" ]; then
501
+ if [ "$(json_get "$STATE_JSON" domain_mode)" = "ec2" ]; then
490
502
  warn "Found legacy temporary-domain deployment state (domain_mode=ec2). Production deployment no longer supports resuming this mode."
491
503
  warn "Destroy and rebuild, or use a new service directory:"
492
504
  warn " P2P_EXISTING_STATE_ACTION=destroy bash $0"
493
505
  warn " DOMAIN=__DOMAIN__ DOMAIN_MODE=user CONFIRM_DOMAIN_BINDING=1 bash $0"
494
506
  return 2
495
507
  fi
496
- confirmed=$(jq -r '.existing_state_confirmed // false' "$STATE_JSON")
508
+ confirmed=$(json_get "$STATE_JSON" existing_state_confirmed false)
497
509
  [ "$confirmed" = "true" ] && return 0
498
510
 
499
511
  action=${P2P_EXISTING_STATE_ACTION:-}
500
512
  if [ -z "$action" ] && [ -t 0 ]; then
501
513
  warn "Found existing deployment state with recorded AWS resources:"
502
- jq -r '.resources | to_entries[]? | " \(.key)=\(.value)"' "$STATE_JSON" >&2
514
+ json_entries "$STATE_JSON" resources | sed 's/^/ /' >&2
503
515
  warn "Choose: continue=resume / destroy=destroy and rebuild / abort=stop now"
504
516
  printf "Action [abort]: " >&2
505
517
  read -r action
@@ -601,7 +613,7 @@ cmd_confirm() {
601
613
  warn "DIREXIO_CONFIRM_EVIDENCE is too short; provide a concrete user/runtime evidence note."
602
614
  return 1
603
615
  fi
604
- runtime_summary_status=$(jq -r '.runtime_checks.summary.status // "not_run"' "$STATE_JSON")
616
+ runtime_summary_status=$(json_get "$STATE_JSON" runtime_checks.summary.status "not_run")
605
617
  runtime_probe_confirmed=false
606
618
  if [ "$gate" = "agent_mcp_runtime" ]; then
607
619
  if [ "$runtime_summary_status" != "passed" ]; then
@@ -614,21 +626,19 @@ cmd_confirm() {
614
626
  fi
615
627
  runtime_probe_confirmed=true
616
628
  fi
617
- _state_write '
618
- .user_confirmations[$gate] = {
619
- status: "confirmed",
620
- ts: $ts,
621
- evidence: $evidence
622
- }
623
- + (if $gate == "agent_mcp_runtime" then {
624
- runtime_summary_status: $runtime_summary_status,
625
- runtime_probe_confirmed: ($runtime_probe_confirmed == "true")
626
- } else {} end)
627
- ' --arg gate "$gate" \
628
- --arg ts "$(_now)" \
629
- --arg evidence "$evidence" \
630
- --arg runtime_summary_status "$runtime_summary_status" \
631
- --arg runtime_probe_confirmed "$runtime_probe_confirmed"
629
+ if [ "$gate" = "agent_mcp_runtime" ]; then
630
+ state_set_object "user_confirmations.$gate" \
631
+ status=confirmed \
632
+ "ts=$(_now)" \
633
+ "evidence=$evidence" \
634
+ "runtime_summary_status=$runtime_summary_status" \
635
+ "runtime_probe_confirmed=$runtime_probe_confirmed"
636
+ else
637
+ state_set_object "user_confirmations.$gate" \
638
+ status=confirmed \
639
+ "ts=$(_now)" \
640
+ "evidence=$evidence"
641
+ fi
632
642
  echo "confirmed gate: $gate"
633
643
  }
634
644
 
@@ -638,10 +648,11 @@ cmd_verify_mcp_doctor() {
638
648
  return 1
639
649
  }
640
650
 
641
- local credentials mcp_cmd node_id out err report token_status
642
- credentials=$(jq -r '.agent_credentials_file // .mcp_credentials_file // empty' "$STATE_JSON")
643
- mcp_cmd=$(jq -r '.mcp_command // "direxio-mcp"' "$STATE_JSON")
644
- node_id=$(jq -r '.agent_node_id // empty' "$STATE_JSON")
651
+ local credentials mcp_cmd node_id out err report token_status report_domain report_room
652
+ credentials=$(json_get "$STATE_JSON" agent_credentials_file)
653
+ [ -n "$credentials" ] || credentials=$(json_get "$STATE_JSON" mcp_credentials_file)
654
+ mcp_cmd=$(json_get "$STATE_JSON" mcp_command "direxio-mcp")
655
+ node_id=$(json_get "$STATE_JSON" agent_node_id)
645
656
  [ -n "$credentials" ] || {
646
657
  warn "mcp doctor check requires agent_credentials_file or mcp_credentials_file in state.json"
647
658
  return 1
@@ -651,45 +662,34 @@ cmd_verify_mcp_doctor() {
651
662
  out=$(mktemp)
652
663
  err=$(mktemp)
653
664
  if ! DIREXIO_CREDENTIALS_FILE="$credentials" DIREXIO_AGENT_NODE_ID="$node_id" bash -c "$mcp_cmd doctor --json" > "$out" 2> "$err"; then
654
- _state_write '
655
- .runtime_checks.mcp_doctor = {
656
- status: "failed",
657
- ts: $ts,
658
- evidence: "direxio-mcp doctor failed"
659
- }
660
- ' --arg ts "$(_now)"
665
+ state_set_object runtime_checks.mcp_doctor status=failed "ts=$(_now)" "evidence=direxio-mcp doctor failed"
661
666
  cat "$err" >&2
662
667
  rm -f "$out" "$err"
663
668
  return 1
664
669
  fi
665
- if ! jq empty "$out" >/dev/null 2>&1; then
666
- _state_write '
667
- .runtime_checks.mcp_doctor = {
668
- status: "failed",
669
- ts: $ts,
670
- evidence: "direxio-mcp doctor returned non-json output"
671
- }
672
- ' --arg ts "$(_now)"
670
+ if ! json_valid "$out" >/dev/null 2>&1; then
671
+ state_set_object runtime_checks.mcp_doctor status=failed "ts=$(_now)" "evidence=direxio-mcp doctor returned non-json output"
673
672
  rm -f "$out" "$err"
674
673
  return 1
675
674
  fi
676
675
  report=$(cat "$out")
677
- token_status=$(printf '%s\n' "$report" | jq -r '
678
- if (.token // "") == "redacted" then "redacted"
679
- elif ((.token // "") | tostring | length) > 0 then "present_redacted"
680
- else "missing"
681
- end
682
- ')
683
- _state_write '
684
- .runtime_checks.mcp_doctor = {
685
- status: "passed",
686
- ts: $ts,
687
- evidence: "direxio-mcp doctor --json succeeded",
688
- domain: ($report.domain // ""),
689
- agent_room_id: ($report.agent_room_id // ""),
690
- token: $token_status
691
- }
692
- ' --arg ts "$(_now)" --argjson report "$report" --arg token_status "$token_status"
676
+ token_status=$(printf '%s\n' "$report" | json_stdin_get token)
677
+ if [ "$token_status" = "redacted" ]; then
678
+ token_status=redacted
679
+ elif [ -n "$token_status" ]; then
680
+ token_status=present_redacted
681
+ else
682
+ token_status=missing
683
+ fi
684
+ report_domain=$(json_get "$out" domain)
685
+ report_room=$(json_get "$out" agent_room_id)
686
+ state_set_object runtime_checks.mcp_doctor \
687
+ status=passed \
688
+ "ts=$(_now)" \
689
+ "evidence=direxio-mcp doctor --json succeeded" \
690
+ "domain=$report_domain" \
691
+ "agent_room_id=$report_room" \
692
+ "token=$token_status"
693
693
  rm -f "$out" "$err"
694
694
  echo "verified runtime check: mcp_doctor"
695
695
  }
@@ -700,56 +700,49 @@ cmd_verify_mcp_smoke() {
700
700
  return 1
701
701
  }
702
702
 
703
- local service_url token room_id body code payload tmp url
704
- service_url=$(jq -r '.as_url // empty' "$STATE_JSON")
703
+ local service_url token room_id body code payload url response_room_id response_messages_type
704
+ service_url=$(json_get "$STATE_JSON" as_url)
705
705
  if [ -z "$service_url" ]; then
706
706
  local domain
707
- domain=$(jq -r '.domain // empty' "$STATE_JSON")
707
+ domain=$(json_get "$STATE_JSON" domain)
708
708
  [ -n "$domain" ] && service_url="https://$domain"
709
709
  fi
710
- token=$(jq -r '.agent_token // empty' "$STATE_JSON")
711
- room_id=$(jq -r '.agent_room_id // empty' "$STATE_JSON")
710
+ token=$(json_get "$STATE_JSON" agent_token)
711
+ room_id=$(json_get "$STATE_JSON" agent_room_id)
712
712
  if [ -z "$service_url" ] || [ -z "$token" ] || [ -z "$room_id" ]; then
713
713
  warn "mcp smoke check requires as_url/domain, agent_token, and agent_room_id in state.json"
714
714
  return 1
715
715
  fi
716
716
 
717
717
  body=$(mktemp)
718
- payload=$(jq -cn --arg room_id "$room_id" '{action:"mcp.messages.list", params:{room_id:$room_id, limit:1}}')
718
+ payload=$(json_build mcp-messages-list "$room_id")
719
719
  url="${service_url%/}/_p2p/query"
720
720
  code=$(curl -sk -o "$body" -w '%{http_code}' \
721
721
  -X POST "$url" \
722
722
  -H 'Content-Type: application/json' \
723
723
  -H "Authorization: Bearer $token" \
724
724
  -d "$payload" 2>/dev/null)
725
- if [ "$code" != "200" ] || ! jq -e '(.messages | type == "array") and (.room_id | type == "string")' "$body" >/dev/null 2>&1; then
726
- _state_write '
727
- .runtime_checks.mcp_smoke = {
728
- status: "failed",
729
- ts: $ts,
730
- action: "mcp.messages.list",
731
- evidence: $evidence
732
- }
733
- ' --arg ts "$(_now)" --arg evidence "mcp.messages.list returned HTTP $code or invalid response"
725
+ if [ "$code" != "200" ] || ! json_assert "$body" messages-response >/dev/null 2>&1; then
726
+ state_set_object runtime_checks.mcp_smoke \
727
+ status=failed \
728
+ "ts=$(_now)" \
729
+ action=mcp.messages.list \
730
+ "evidence=mcp.messages.list returned HTTP $code or invalid response"
734
731
  rm -f "$body"
735
732
  return 1
736
733
  fi
737
734
 
738
- tmp=$(mktemp)
739
- jq -n --slurpfile response "$body" \
740
- --arg ts "$(_now)" \
741
- --arg room_id "$room_id" \
742
- '{
743
- status: "passed",
744
- ts: $ts,
745
- action: "mcp.messages.list",
746
- room_id: $room_id,
747
- response_room_id: ($response[0].room_id // ""),
748
- response_messages_type: (($response[0].messages // null) | type),
749
- evidence: "read-only backend smoke check succeeded"
750
- }' > "$tmp"
751
- _state_write '.runtime_checks.mcp_smoke = $check[0]' --slurpfile check "$tmp"
752
- rm -f "$body" "$tmp"
735
+ response_room_id=$(json_get "$body" room_id)
736
+ response_messages_type=$(json_type "$body" messages)
737
+ state_set_object runtime_checks.mcp_smoke \
738
+ status=passed \
739
+ "ts=$(_now)" \
740
+ action=mcp.messages.list \
741
+ "room_id=$room_id" \
742
+ "response_room_id=$response_room_id" \
743
+ "response_messages_type=$response_messages_type" \
744
+ "evidence=read-only backend smoke check succeeded"
745
+ rm -f "$body"
753
746
  echo "verified runtime check: mcp_smoke"
754
747
  }
755
748
 
@@ -760,9 +753,10 @@ cmd_verify_mcp_tools() {
760
753
  }
761
754
 
762
755
  local credentials mcp_cmd node_id node_cmd node_script out err report
763
- credentials=$(jq -r '.agent_credentials_file // .mcp_credentials_file // empty' "$STATE_JSON")
764
- mcp_cmd=$(jq -r '.mcp_command // "direxio-mcp"' "$STATE_JSON")
765
- node_id=$(jq -r '.agent_node_id // empty' "$STATE_JSON")
756
+ credentials=$(json_get "$STATE_JSON" agent_credentials_file)
757
+ [ -n "$credentials" ] || credentials=$(json_get "$STATE_JSON" mcp_credentials_file)
758
+ mcp_cmd=$(json_get "$STATE_JSON" mcp_command "direxio-mcp")
759
+ node_id=$(json_get "$STATE_JSON" agent_node_id)
766
760
  [ -n "$credentials" ] || {
767
761
  warn "mcp tools check requires agent_credentials_file or mcp_credentials_file in state.json"
768
762
  return 1
@@ -778,52 +772,29 @@ cmd_verify_mcp_tools() {
778
772
  out=$(mktemp)
779
773
  err=$(mktemp)
780
774
  if ! DIREXIO_CREDENTIALS_FILE="$credentials" DIREXIO_AGENT_NODE_ID="$node_id" "$node_cmd" "$node_script" "$mcp_cmd" > "$out" 2> "$err"; then
781
- _state_write '
782
- .runtime_checks.mcp_tools = {
783
- status: "failed",
784
- ts: $ts,
785
- evidence: "MCP tools/list failed"
786
- }
787
- ' --arg ts "$(_now)"
775
+ state_set_object runtime_checks.mcp_tools status=failed "ts=$(_now)" "evidence=MCP tools/list failed"
788
776
  cat "$err" >&2
789
777
  rm -f "$out" "$err"
790
778
  return 1
791
779
  fi
792
- if ! jq -e '(.tools | type == "array") and (.tool_count | type == "number")' "$out" >/dev/null 2>&1; then
793
- _state_write '
794
- .runtime_checks.mcp_tools = {
795
- status: "failed",
796
- ts: $ts,
797
- evidence: "MCP tools/list returned invalid output"
798
- }
799
- ' --arg ts "$(_now)"
780
+ if ! json_assert "$out" tools-list >/dev/null 2>&1; then
781
+ state_set_object runtime_checks.mcp_tools status=failed "ts=$(_now)" "evidence=MCP tools/list returned invalid output"
800
782
  rm -f "$out" "$err"
801
783
  return 1
802
784
  fi
803
785
  report=$(cat "$out")
804
- _state_write '
805
- .runtime_checks.mcp_tools = {
806
- status: "passed",
807
- ts: $ts,
808
- evidence: "MCP tools/list succeeded",
809
- tool_count: ($report.tool_count // 0),
810
- tools: ($report.tools // [])
811
- }
812
- ' --arg ts "$(_now)" --argjson report "$report"
786
+ state_set_object runtime_checks.mcp_tools \
787
+ status=passed \
788
+ "ts=$(_now)" \
789
+ "evidence=MCP tools/list succeeded" \
790
+ "tool_count=$(json_get "$out" tool_count 0)" \
791
+ "tools=$(json_get "$out" tools "[]")"
813
792
  rm -f "$out" "$err"
814
793
  echo "verified runtime check: mcp_tools"
815
794
  }
816
795
 
817
796
  _node_command() {
818
- if command -v node >/dev/null 2>&1; then
819
- command -v node
820
- return 0
821
- fi
822
- if command -v node.exe >/dev/null 2>&1; then
823
- command -v node.exe
824
- return 0
825
- fi
826
- return 1
797
+ json_node
827
798
  }
828
799
 
829
800
  _node_script_path() {
@@ -907,11 +878,12 @@ cmd_verify_connect_daemon() {
907
878
  }
908
879
 
909
880
  local service_name service_dir config runtime_dir binary target_work_dir status_out daemon_status work_dir evidence agent_error
910
- service_name=$(jq -r '.agent_service_id // .domain // empty' "$STATE_JSON")
911
- service_dir=$(jq -r '.agent_service_dir // empty' "$STATE_JSON")
912
- config=$(jq -r '.cc_connect_config // empty' "$STATE_JSON")
913
- runtime_dir=$(jq -r '.cc_connect_runtime_dir // empty' "$STATE_JSON")
914
- binary=$(jq -r '.cc_connect_binary // "direxio-connect"' "$STATE_JSON")
881
+ service_name=$(json_get "$STATE_JSON" agent_service_id)
882
+ [ -n "$service_name" ] || service_name=$(json_get "$STATE_JSON" domain)
883
+ service_dir=$(json_get "$STATE_JSON" agent_service_dir)
884
+ config=$(json_get "$STATE_JSON" cc_connect_config)
885
+ runtime_dir=$(json_get "$STATE_JSON" cc_connect_runtime_dir)
886
+ binary=$(json_get "$STATE_JSON" cc_connect_binary "direxio-connect")
915
887
  [ -n "$service_name" ] || service_name=cc-connect
916
888
  [ -n "$binary" ] || binary=direxio-connect
917
889
 
@@ -930,13 +902,7 @@ cmd_verify_connect_daemon() {
930
902
  */*|[A-Za-z]:/*|[A-Za-z]:\\*) ;;
931
903
  *)
932
904
  command -v "$binary" >/dev/null 2>&1 || {
933
- _state_write '
934
- .runtime_checks.connect_daemon = {
935
- status: "failed",
936
- ts: $ts,
937
- evidence: "direxio-connect binary not found"
938
- }
939
- ' --arg ts "$(_now)"
905
+ state_set_object runtime_checks.connect_daemon status=failed "ts=$(_now)" "evidence=direxio-connect binary not found"
940
906
  warn "connect daemon check could not find binary: $binary"
941
907
  return 1
942
908
  }
@@ -944,14 +910,7 @@ cmd_verify_connect_daemon() {
944
910
  esac
945
911
 
946
912
  status_out=$("$binary" daemon status --service-name "$service_name" 2>/dev/null) || {
947
- _state_write '
948
- .runtime_checks.connect_daemon = {
949
- status: "failed",
950
- ts: $ts,
951
- service_name: $service_name,
952
- evidence: "direxio-connect daemon status failed"
953
- }
954
- ' --arg ts "$(_now)" --arg service_name "$service_name"
913
+ state_set_object runtime_checks.connect_daemon status=failed "ts=$(_now)" "service_name=$service_name" "evidence=direxio-connect daemon status failed"
955
914
  return 1
956
915
  }
957
916
  daemon_status=$(printf '%s\n' "$status_out" | sed -nE 's/^[[:space:]]*Status:[[:space:]]*//p' | head -n 1)
@@ -966,68 +925,45 @@ cmd_verify_connect_daemon() {
966
925
  else
967
926
  agent_error=$(connect_daemon_agent_error_from_logs "$binary" "$service_name")
968
927
  if [ -n "$agent_error" ]; then
969
- _state_write '
970
- .runtime_checks.connect_daemon = {
971
- status: "failed",
972
- ts: $ts,
973
- evidence: "direxio-connect daemon logs report ACP session initialization failure",
974
- service_name: $service_name,
975
- daemon_status: $daemon_status,
976
- work_dir: $work_dir,
977
- expected_work_dir: $target_work_dir,
978
- agent_error: $agent_error
979
- }
980
- ' --arg ts "$(_now)" \
981
- --arg service_name "$service_name" \
982
- --arg daemon_status "$daemon_status" \
983
- --arg work_dir "$(normalize_check_path "$work_dir")" \
984
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")" \
985
- --arg agent_error "$agent_error"
928
+ state_set_object runtime_checks.connect_daemon \
929
+ status=failed \
930
+ "ts=$(_now)" \
931
+ "evidence=direxio-connect daemon logs report ACP session initialization failure" \
932
+ "service_name=$service_name" \
933
+ "daemon_status=$daemon_status" \
934
+ "work_dir=$(normalize_check_path "$work_dir")" \
935
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")" \
936
+ "agent_error=$agent_error"
986
937
  warn "direxio-connect daemon logs report ACP session initialization failure"
987
938
  return 1
988
939
  fi
989
- _state_write '
990
- .runtime_checks.connect_daemon = {
991
- status: "passed",
992
- ts: $ts,
993
- evidence: "direxio-connect daemon is running for this service",
994
- service_name: $service_name,
995
- daemon_status: $daemon_status,
996
- work_dir: $work_dir,
997
- expected_work_dir: $target_work_dir
998
- }
999
- ' --arg ts "$(_now)" \
1000
- --arg service_name "$service_name" \
1001
- --arg daemon_status "$daemon_status" \
1002
- --arg work_dir "$(normalize_check_path "$work_dir")" \
1003
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")"
940
+ state_set_object runtime_checks.connect_daemon \
941
+ status=passed \
942
+ "ts=$(_now)" \
943
+ "evidence=direxio-connect daemon is running for this service" \
944
+ "service_name=$service_name" \
945
+ "daemon_status=$daemon_status" \
946
+ "work_dir=$(normalize_check_path "$work_dir")" \
947
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")"
1004
948
  echo "verified runtime check: connect_daemon"
1005
949
  return 0
1006
950
  fi
1007
951
 
1008
- _state_write '
1009
- .runtime_checks.connect_daemon = {
1010
- status: "failed",
1011
- ts: $ts,
1012
- evidence: $evidence,
1013
- service_name: $service_name,
1014
- daemon_status: $daemon_status,
1015
- work_dir: $work_dir,
1016
- expected_work_dir: $target_work_dir
1017
- }
1018
- ' --arg ts "$(_now)" \
1019
- --arg evidence "$evidence" \
1020
- --arg service_name "$service_name" \
1021
- --arg daemon_status "$daemon_status" \
1022
- --arg work_dir "$(normalize_check_path "$work_dir")" \
1023
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")"
952
+ state_set_object runtime_checks.connect_daemon \
953
+ status=failed \
954
+ "ts=$(_now)" \
955
+ "evidence=$evidence" \
956
+ "service_name=$service_name" \
957
+ "daemon_status=$daemon_status" \
958
+ "work_dir=$(normalize_check_path "$work_dir")" \
959
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")"
1024
960
  warn "$evidence"
1025
961
  return 1
1026
962
  }
1027
963
 
1028
964
  runtime_check_status() {
1029
965
  local check=$1
1030
- jq -r --arg check "$check" '.runtime_checks[$check].status // "not_run"' "$STATE_JSON"
966
+ json_get "$STATE_JSON" "runtime_checks.$check.status" "not_run"
1031
967
  }
1032
968
 
1033
969
  cmd_verify_runtime() {
@@ -1053,47 +989,28 @@ cmd_verify_runtime() {
1053
989
  done
1054
990
 
1055
991
  if [ "$failed_count" -eq 0 ]; then
1056
- _state_write '
1057
- .runtime_checks.summary = {
1058
- status: "passed",
1059
- ts: $ts,
1060
- failed_count: 0,
1061
- evidence: "all runtime checks passed",
1062
- checks: {
1063
- connect_daemon: $connect_status,
1064
- mcp_doctor: $doctor_status,
1065
- mcp_tools: $tools_status,
1066
- mcp_smoke: $smoke_status
1067
- }
1068
- }
1069
- ' --arg ts "$(_now)" \
1070
- --arg connect_status "$connect_status" \
1071
- --arg doctor_status "$doctor_status" \
1072
- --arg tools_status "$tools_status" \
1073
- --arg smoke_status "$smoke_status"
992
+ state_set_object runtime_checks.summary \
993
+ status=passed \
994
+ "ts=$(_now)" \
995
+ failed_count=0 \
996
+ "evidence=all runtime checks passed" \
997
+ "checks.connect_daemon=$connect_status" \
998
+ "checks.mcp_doctor=$doctor_status" \
999
+ "checks.mcp_tools=$tools_status" \
1000
+ "checks.mcp_smoke=$smoke_status"
1074
1001
  echo "verified runtime checks: passed"
1075
1002
  return 0
1076
1003
  fi
1077
1004
 
1078
- _state_write '
1079
- .runtime_checks.summary = {
1080
- status: "failed",
1081
- ts: $ts,
1082
- failed_count: ($failed_count | tonumber),
1083
- evidence: "one or more runtime checks failed",
1084
- checks: {
1085
- connect_daemon: $connect_status,
1086
- mcp_doctor: $doctor_status,
1087
- mcp_tools: $tools_status,
1088
- mcp_smoke: $smoke_status
1089
- }
1090
- }
1091
- ' --arg ts "$(_now)" \
1092
- --arg failed_count "$failed_count" \
1093
- --arg connect_status "$connect_status" \
1094
- --arg doctor_status "$doctor_status" \
1095
- --arg tools_status "$tools_status" \
1096
- --arg smoke_status "$smoke_status"
1005
+ state_set_object runtime_checks.summary \
1006
+ status=failed \
1007
+ "ts=$(_now)" \
1008
+ "failed_count=$failed_count" \
1009
+ "evidence=one or more runtime checks failed" \
1010
+ "checks.connect_daemon=$connect_status" \
1011
+ "checks.mcp_doctor=$doctor_status" \
1012
+ "checks.mcp_tools=$tools_status" \
1013
+ "checks.mcp_smoke=$smoke_status"
1097
1014
  warn "runtime checks failed: $failed_count"
1098
1015
  return "${rc:-1}"
1099
1016
  }