direxio-deployer 0.1.0

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 (77) hide show
  1. package/AGENTS.md +92 -0
  2. package/LICENSE +21 -0
  3. package/README.md +221 -0
  4. package/README_zh.md +218 -0
  5. package/SKILL.md +722 -0
  6. package/agents/README.md +25 -0
  7. package/agents/openai.yaml +12 -0
  8. package/bin/direxio-deployer.mjs +375 -0
  9. package/package.json +28 -0
  10. package/references/agent-targets.md +128 -0
  11. package/references/architecture.md +44 -0
  12. package/references/bug-history.md +78 -0
  13. package/references/deployment-lessons.md +218 -0
  14. package/references/deployment-optimization-audit.md +317 -0
  15. package/references/deployment-workflow.md +341 -0
  16. package/references/iam-policy.json +52 -0
  17. package/references/runtime-wiring.md +209 -0
  18. package/references/state-machine.md +46 -0
  19. package/references/token-refresh.md +81 -0
  20. package/references/tooling.md +106 -0
  21. package/references/troubleshooting.md +26 -0
  22. package/references/user-journey.md +75 -0
  23. package/references/verification-recovery.md +84 -0
  24. package/references/voip-turn-runbook.md +154 -0
  25. package/references/windows-deployment-notes.md +119 -0
  26. package/scripts/aws-credentials.sh +195 -0
  27. package/scripts/cloud-init/Caddyfile +48 -0
  28. package/scripts/cloud-init/docker-compose.yml +125 -0
  29. package/scripts/cloud-init/init-tokens.sh +238 -0
  30. package/scripts/cloud-init/user-data.yaml +40 -0
  31. package/scripts/destroy.ps1 +77 -0
  32. package/scripts/destroy.sh +589 -0
  33. package/scripts/lib/aws.sh +73 -0
  34. package/scripts/lib/domain.sh +175 -0
  35. package/scripts/lib/operation_report.sh +240 -0
  36. package/scripts/lib/ops.sh +230 -0
  37. package/scripts/lib/paths.sh +35 -0
  38. package/scripts/lib/state.sh +137 -0
  39. package/scripts/mcp-tools-list.mjs +95 -0
  40. package/scripts/orchestrate.ps1 +112 -0
  41. package/scripts/orchestrate.sh +1126 -0
  42. package/scripts/phases/s0_prereq_aws.sh +39 -0
  43. package/scripts/phases/s1_preflight.sh +72 -0
  44. package/scripts/phases/s2_domain.sh +103 -0
  45. package/scripts/phases/s3_provision.sh +421 -0
  46. package/scripts/phases/s4_bootstrap_stack.sh +38 -0
  47. package/scripts/phases/s5_init_tokens.sh +118 -0
  48. package/scripts/phases/s6_wire_local.sh +1435 -0
  49. package/scripts/phases/s7_verify_e2e.sh +136 -0
  50. package/scripts/pricing-estimate.sh +256 -0
  51. package/scripts/render/render-userdata.sh +86 -0
  52. package/scripts/reset-app-data.sh +40 -0
  53. package/scripts/update.sh +30 -0
  54. package/tests/aws_credentials_test.sh +139 -0
  55. package/tests/connect_daemon_runtime_check_test.sh +120 -0
  56. package/tests/default_paths_test.sh +58 -0
  57. package/tests/destroy_local_bridge_test.sh +154 -0
  58. package/tests/destroy_root_identity_test.sh +91 -0
  59. package/tests/destroy_route53_zone_test.sh +80 -0
  60. package/tests/domain_authoritative_dns_test.sh +49 -0
  61. package/tests/mcp_doctor_runtime_check_test.sh +86 -0
  62. package/tests/mcp_smoke_runtime_check_test.sh +121 -0
  63. package/tests/mcp_tools_runtime_check_test.sh +123 -0
  64. package/tests/npm_skill_distribution_test.sh +95 -0
  65. package/tests/operation_report_test.sh +258 -0
  66. package/tests/orchestrate_status_recovery_test.sh +91 -0
  67. package/tests/phase_timeout_test.sh +88 -0
  68. package/tests/pricing_estimate_test.sh +159 -0
  69. package/tests/render_userdata_remote_nodes_test.sh +40 -0
  70. package/tests/root_volume_tracking_test.sh +41 -0
  71. package/tests/route53_overwrite_guard_test.sh +86 -0
  72. package/tests/route53_zone_auto_create_test.sh +66 -0
  73. package/tests/runtime_summary_check_test.sh +203 -0
  74. package/tests/s6_wire_local_test.sh +405 -0
  75. package/tests/skill_structure_test.sh +298 -0
  76. package/tests/update_reset_ops_test.sh +230 -0
  77. package/tests/user_confirmation_gates_test.sh +152 -0
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT=$(cd "$(dirname "$0")/.." && pwd)
5
+ tmp=$(mktemp -d)
6
+ trap 'rm -rf "$tmp"' EXIT
7
+
8
+ export HOME="$tmp/home"
9
+ export XDG_CONFIG_HOME="$tmp/config"
10
+ mkdir -p "$HOME"
11
+
12
+ # shellcheck disable=SC1090
13
+ source "$ROOT/scripts/phases/s6_wire_local.sh"
14
+
15
+ ok() { echo "[test-ok] $*" >&2; }
16
+ warn() { echo "[test-warn] $*" >&2; }
17
+ fail() { echo "$*" >&2; return 1; }
18
+ state_set() { printf '%s=%s\n' "$1" "$2" >> "${STATE_CALLS:?}"; }
19
+
20
+ clear_runtime_env() {
21
+ local env_name
22
+ while IFS='=' read -r env_name _; do
23
+ case "$env_name" in
24
+ ACP_*|ANTIGRAVITY_*|GOOGLE_ANTIGRAVITY_*|AGY_*|CODEX_*|CLAUDECODE|CLAUDECODE_*|CLAUDE_CODE_*|GEMINI_CLI|GEMINI_CLI_*|GEMINI_AGENT_*|GOOGLE_GEMINI_CLI_*|CURSOR_*|COPILOT_*|GITHUB_COPILOT_*|DEVIN_*|IFLOW_*|KIMI_*|OPENCODE_*|OPEN_CODE_*|PI_CODING_AGENT_*|PI_AGENT_*|QODER_*|REASONIX_*|TMUX*|OPENCLAW_*|HERMES_*)
25
+ unset "$env_name"
26
+ ;;
27
+ esac
28
+ done < <(env)
29
+ unset ACP_HOME ANTIGRAVITY_HOME AGY_HOME HERMES_HOME CODEX_HOME CLAUDE_HOME CLAUDECODE_HOME GEMINI_HOME CURSOR_HOME COPILOT_HOME DEVIN_HOME IFLOW_HOME KIMI_HOME OPENCODE_HOME OPEN_CODE_HOME PI_CODING_AGENT_DIR PI_HOME QODER_HOME REASONIX_HOME TMUX_HOME OPENCLAW_HOME
30
+ unset HERMES_SESSION CODEX_SANDBOX CLAUDECODE GEMINI_CLI CURSOR_TRACE_ID GITHUB_COPILOT_TOKEN DEVIN_SESSION IFLOW_SESSION KIMI_SESSION OPENCODE_SESSION QODER_SESSION PI_AGENT_SESSION ANTIGRAVITY_SESSION OPENCLAW_SESSION
31
+ unset DIREXIO_CC_CONNECT_AGENT DIREXIO_CC_CONNECT_AGENT_CMD
32
+ }
33
+
34
+ clear_speech_env() {
35
+ local env_name
36
+ while IFS='=' read -r env_name _; do
37
+ case "$env_name" in
38
+ DIREXIO_SPEECH_*|OPENAI_API_KEY|OPENAI_BASE_URL|GROQ_API_KEY|DASHSCOPE_API_KEY|DASH_SCOPE_API_KEY|GEMINI_API_KEY|GOOGLE_API_KEY)
39
+ unset "$env_name"
40
+ ;;
41
+ esac
42
+ done < <(env)
43
+ }
44
+
45
+ clear_runtime_env
46
+ clear_speech_env
47
+ export DIREXIO_AGENT_DETECT_PROCESS=0
48
+
49
+ unset DIREXIO_HOME
50
+ [ "$(_direxio_home)" = "$HOME/.direxio" ]
51
+ [ "$(DIREXIO_HOME="$tmp/custom-direxio" _direxio_home)" = "$tmp/custom-direxio" ]
52
+ [ "$(_direxio_service_id "https://IM.Example.test:8443/_p2p")" = "im.example.test-8443" ]
53
+ [ "$(_direxio_service_dir "https://IM.Example.test:8443/_p2p")" = "$HOME/.direxio/nodes/im.example.test-8443" ]
54
+
55
+ envfile=$(_write_agent_env_file "https://im.example.test" "agent-token" "access-token" "!agents-real:im.example.test")
56
+
57
+ [ "$envfile" = "$HOME/.direxio/env" ]
58
+ grep -q 'DIREXIO_DOMAIN=https://im.example.test' "$envfile"
59
+ grep -q 'DIREXIO_AGENT_TOKEN=agent-token' "$envfile"
60
+ grep -q 'DIREXIO_AGENT_ROOM_ID=\\!agents-real:im.example.test' "$envfile"
61
+ ! grep -q '^export P2P_' "$envfile"
62
+ ! grep -q 'P2P_ADMIN_ACCESS_TOKEN' "$envfile"
63
+ ! grep -q 'P2P_MATRIX_ACCESS_TOKEN' "$envfile"
64
+
65
+ # shellcheck disable=SC1090
66
+ source "$envfile"
67
+ [ "$DIREXIO_AGENT_ROOM_ID" = "!agents-real:im.example.test" ]
68
+
69
+ if grep -R 'P2P_MATRIX_AS_URL\|P2P_MATRIX_AGENT_TOKEN\|P2P_AGENT_RUNTIME\|p2p-agent-skill\|p2p-matrix-agent' "$ROOT/scripts" "$ROOT/SKILL.md" "$ROOT/references/runtime-wiring.md"; then
70
+ echo "deprecated Matrix-AS env names or old agent skill wiring must not be used by deployer wiring" >&2
71
+ exit 1
72
+ fi
73
+
74
+ [ "$(DIREXIO_AGENT_PLATFORM=hermes _detect_agent_runtime)" = "hermes" ]
75
+ [ "$(DIREXIO_AGENT_PLATFORM=openclaw _detect_agent_runtime)" = "openclaw" ]
76
+ [ "$(DIREXIO_AGENT_PLATFORM=claude-code _detect_agent_runtime)" = "claude-code" ]
77
+ [ "$(DIREXIO_AGENT_PLATFORM=opencode _detect_agent_runtime)" = "opencode" ]
78
+ [ "$(DIREXIO_CC_CONNECT_AGENT=qodercli _detect_agent_runtime)" = "qoder" ]
79
+ [ "$(DIREXIO_AGENT_PLATFORM=hermes DIREXIO_CC_CONNECT_AGENT=codex _detect_agent_runtime)" = "hermes" ]
80
+ assert_active_runtime() {
81
+ local expected=$1 signal=$2
82
+ shift 2
83
+ (
84
+ clear_runtime_env
85
+ mkdir -p "$HOME/.hermes" "$HOME/.codex" "$HOME/.claude" "$HOME/.gemini" "$HOME/.cursor" "$HOME/.copilot" "$HOME/.devin" "$HOME/.iflow" "$HOME/.kimi" "$HOME/.opencode" "$HOME/.qoder" "$HOME/.pi/agent" "$HOME/.antigravity" "$HOME/.openclaw" "$tmp/neutral"
86
+ cd "$tmp/neutral"
87
+ PATH="/usr/bin:/bin"
88
+ local kv
89
+ for kv in "$@"; do
90
+ export "$kv"
91
+ done
92
+ actual=$(_detect_agent_runtime)
93
+ if [ "$actual" != "$expected" ]; then
94
+ echo "expected active $expected runtime from $signal, got $actual" >&2
95
+ exit 1
96
+ fi
97
+ )
98
+ }
99
+
100
+ assert_active_runtime codex CODEX_SANDBOX CODEX_SANDBOX=1
101
+ assert_active_runtime claudecode CLAUDECODE CLAUDECODE=1
102
+ assert_active_runtime gemini GEMINI_CLI GEMINI_CLI=1
103
+ assert_active_runtime cursor CURSOR_TRACE_ID CURSOR_TRACE_ID=1
104
+ assert_active_runtime copilot GITHUB_COPILOT_TOKEN GITHUB_COPILOT_TOKEN=1
105
+ assert_active_runtime devin DEVIN_SESSION DEVIN_SESSION=1
106
+ assert_active_runtime iflow IFLOW_SESSION IFLOW_SESSION=1
107
+ assert_active_runtime kimi KIMI_SESSION KIMI_SESSION=1
108
+ assert_active_runtime opencode OPENCODE_SESSION OPENCODE_SESSION=1
109
+ assert_active_runtime qoder QODER_SESSION QODER_SESSION=1
110
+ assert_active_runtime pi PI_AGENT_SESSION PI_AGENT_SESSION=1
111
+ assert_active_runtime antigravity ANTIGRAVITY_SESSION ANTIGRAVITY_SESSION=1
112
+ assert_active_runtime openclaw OPENCLAW_SESSION OPENCLAW_SESSION=1
113
+ assert_active_runtime hermes HERMES_SESSION HERMES_SESSION=1
114
+ assert_active_runtime codex .codex/tmp PATH="/tmp/.codex/tmp/codex-arg123:/usr/bin:/bin"
115
+ (
116
+ clear_runtime_env
117
+ mkdir -p "$HOME/.hermes" "$HOME/.codex" "$HOME/.claude" "$HOME/.gemini" "$HOME/.cursor" "$HOME/.copilot" "$HOME/.openclaw" "$tmp/neutral"
118
+ cd "$tmp/neutral"
119
+ PATH="/usr/bin:/bin"
120
+ export CLAUDE_API_KEY=test GEMINI_API_KEY=test
121
+ [ "$(_detect_agent_runtime)" = "unknown" ]
122
+ )
123
+ [ "$(DIREXIO_AGENT_INSTALL=skip _agent_install_policy)" = "skip" ]
124
+ [ "$(DIREXIO_AGENT_INSTALL=recommend _agent_install_policy)" = "recommend" ]
125
+ [ "$(DIREXIO_AGENT_INSTALL=auto _agent_install_policy)" = "auto" ]
126
+ [ "$(_agent_install_mode hermes)" = "cc-connect" ]
127
+ [ "$(_agent_install_mode openclaw)" = "cc-connect" ]
128
+ [ "$(_agent_install_mode codex)" = "cc-connect" ]
129
+ [ "$(_agent_install_mode cursor)" = "cc-connect" ]
130
+ [ "$(_agent_install_mode opencode)" = "cc-connect" ]
131
+ [ "$(DIREXIO_AGENT_INSTALL_MODE=cc-connect _agent_install_mode hermes)" = "cc-connect" ]
132
+ if DIREXIO_AGENT_INSTALL_MODE=gateway _agent_install_mode hermes >/dev/null 2>&1; then
133
+ echo "legacy install mode should be rejected" >&2
134
+ exit 1
135
+ fi
136
+
137
+ [ "$(_agent_skill_install_path codex)" = "PROJECT_ROOT/.codex/skills/direxio-deployer" ]
138
+ [ "$(_agent_skill_install_path claude-code)" = "PROJECT_ROOT/.claude/skills/direxio-deployer" ]
139
+ [ "$(_agent_skill_install_path claudecode)" = "PROJECT_ROOT/.claude/skills/direxio-deployer" ]
140
+ [ "$(_agent_skill_install_path gemini)" = "PROJECT_ROOT/.gemini/skills/direxio-deployer" ]
141
+ [ "$(_agent_skill_install_path cursor)" = "PROJECT_ROOT/.cursor/skills/direxio-deployer" ]
142
+ [ "$(_agent_skill_install_path copilot)" = "PROJECT_ROOT/.github/copilot/skills/direxio-deployer" ]
143
+ [ "$(_agent_skill_install_path devin)" = "PROJECT_ROOT/.devin/skills/direxio-deployer" ]
144
+ [ "$(_agent_skill_install_path opencode)" = "PROJECT_ROOT/.opencode/skills/direxio-deployer" ]
145
+ [ "$(_agent_skill_install_path qoder)" = "PROJECT_ROOT/.qoder/skills/direxio-deployer" ]
146
+ [ "$(_agent_skill_install_path pi)" = "PROJECT_ROOT/.pi/agent/skills/direxio-deployer" ]
147
+ [ "$(_agent_skill_install_path openclaw)" = "PROJECT_ROOT/.openclaw/skills/direxio-deployer" ]
148
+ [ "$(_agent_skill_install_path hermes)" = "PROJECT_ROOT/.hermes/skills/direxio-deployer" ]
149
+ [ "$(_agent_skill_install_path unknown)" = "PROJECT_ROOT/.agent/skills/direxio-deployer" ]
150
+
151
+ [ "$(_agent_global_skill_install_path codex)" = '${CODEX_HOME:-$HOME/.codex}/skills/direxio-deployer' ]
152
+ [ "$(_agent_global_skill_install_path claude-code)" = '${CLAUDE_HOME:-${CLAUDECODE_HOME:-$HOME/.claude}}/skills/direxio-deployer' ]
153
+ [ "$(_agent_global_skill_install_path claudecode)" = '${CLAUDE_HOME:-${CLAUDECODE_HOME:-$HOME/.claude}}/skills/direxio-deployer' ]
154
+ [ "$(_agent_global_skill_install_path generic)" = '$HOME/.agent/skills/direxio-deployer' ]
155
+ [ "$(_agent_workspace "$tmp/service")" = "$tmp/service/workspace" ]
156
+ [ "$(DIREXIO_AGENT_WORKSPACE="$tmp/custom-workspace" _agent_workspace "$tmp/service")" = "$tmp/custom-workspace" ]
157
+
158
+ install_command=$(_agent_install_command "direxio-connect" "$HOME/.direxio/nodes/im.example.test/cc-connect/config.toml" "im.example.test")
159
+ case "$install_command" in
160
+ *"npm install -g"*"direxio-connent@latest"*"direxio-connect"*"daemon install"*"--config"*"im.example.test/cc-connect/config.toml"*"--service-name"*"im.example.test"*"--force"*) ;;
161
+ *)
162
+ echo "install command did not include expected cc-connect daemon flags: $install_command" >&2
163
+ exit 1
164
+ ;;
165
+ esac
166
+ custom_install_command=$(DIREXIO_CC_CONNECT_NPM_PACKAGE='direxio-connent@1.3.11' _agent_install_command "direxio-connect" "$HOME/.direxio/nodes/im.example.test/cc-connect/config.toml" "im.example.test")
167
+ [[ "$custom_install_command" == *"direxio-connent@1.3.11"* ]]
168
+
169
+ [ "$(DIREXIO_LOCAL_PATH_STYLE=windows _local_connect_path '/mnt/c/Users/alice/.direxio/nodes/im/cc-connect/config.toml')" = "C:/Users/alice/.direxio/nodes/im/cc-connect/config.toml" ]
170
+ [ "$(DIREXIO_LOCAL_PATH_STYLE=windows _local_connect_path '/c/Users/alice/.direxio/nodes/im/cc-connect/config.toml')" = "C:/Users/alice/.direxio/nodes/im/cc-connect/config.toml" ]
171
+ windows_install_command=$(DIREXIO_LOCAL_PATH_STYLE=windows _agent_install_command "direxio-connect" "/mnt/c/Users/alice/.direxio/nodes/im/cc-connect/config.toml" "im")
172
+ [[ "$windows_install_command" == *"C:/Users/alice/.direxio/nodes/im/cc-connect/config.toml"* ]]
173
+ [[ "$windows_install_command" == *"--service-name im"* ]]
174
+
175
+ [ "$(_mcp_server_name "im.example.test")" = "direxio-im_example_test" ]
176
+ [ "$(_mcp_server_name "T1.Direxio.AI")" = "direxio-t1_direxio_ai" ]
177
+
178
+ mcp_service_dir="$tmp/mcp-service"
179
+ mcp_credentials="$mcp_service_dir/credentials.json"
180
+ mkdir -p "$mcp_service_dir"
181
+ : > "$mcp_credentials"
182
+ mkdir -p "$mcp_service_dir/mcp"
183
+ : > "$mcp_service_dir/mcp/openclaw.mcp.json"
184
+ expected_mcp_credentials="$mcp_credentials"
185
+ if command -v cygpath >/dev/null 2>&1; then
186
+ expected_mcp_credentials=$(cygpath -m "$expected_mcp_credentials")
187
+ fi
188
+ _write_mcp_config_artifacts "im.example.test" "$mcp_service_dir" "$mcp_credentials" "codex-im-example"
189
+ [ -s "$mcp_service_dir/mcp/codex.toml" ]
190
+ [ -s "$mcp_service_dir/mcp/openclaw.md" ]
191
+ [ -s "$mcp_service_dir/mcp/openclaw-server.json" ]
192
+ [ ! -e "$mcp_service_dir/mcp/openclaw.mcp.json" ]
193
+ [ -s "$mcp_service_dir/mcp/hermes.mcp.json" ]
194
+ [ -s "$mcp_service_dir/mcp/mcp-servers.json" ]
195
+ [ -s "$mcp_service_dir/mcp/env" ]
196
+ grep -q '\[mcp_servers."direxio-im_example_test"\]' "$mcp_service_dir/mcp/codex.toml"
197
+ grep -q 'command = "direxio-mcp"' "$mcp_service_dir/mcp/codex.toml"
198
+ grep -q 'DIREXIO_CREDENTIALS_FILE' "$mcp_service_dir/mcp/codex.toml"
199
+ grep -q "$mcp_credentials" "$mcp_service_dir/mcp/codex.toml"
200
+ jq -e '.command == "direxio-mcp"' "$mcp_service_dir/mcp/openclaw-server.json" >/dev/null
201
+ jq -e '.env.DIREXIO_CREDENTIALS_FILE == "'"$expected_mcp_credentials"'"' "$mcp_service_dir/mcp/openclaw-server.json" >/dev/null
202
+ if jq -e 'has("mcp") or has("mcpServers")' "$mcp_service_dir/mcp/openclaw-server.json" >/dev/null; then
203
+ echo "OpenClaw server object must not be a root openclaw.json or mcpServers snippet" >&2
204
+ exit 1
205
+ fi
206
+ grep -q 'openclaw mcp set direxio-im_example_test' "$mcp_service_dir/mcp/openclaw.md"
207
+ grep -q 'Do not paste' "$mcp_service_dir/mcp/openclaw.md"
208
+ grep -q 'openclaw.json' "$mcp_service_dir/mcp/openclaw.md"
209
+ jq -e '.mcpServers["direxio-im_example_test"].env.DIREXIO_CREDENTIALS_FILE == "'"$expected_mcp_credentials"'"' "$mcp_service_dir/mcp/hermes.mcp.json" >/dev/null
210
+ grep -q 'DIREXIO_AGENT_NODE_ID=codex-im-example' "$mcp_service_dir/mcp/env"
211
+ mcp_install_command=$(_mcp_install_command)
212
+ [[ "$mcp_install_command" == *"npm install -g"*"direxio-mcp@latest"* ]]
213
+ custom_mcp_install_command=$(DIREXIO_MCP_NPM_PACKAGE='direxio-mcp@0.1.7' _mcp_install_command)
214
+ [[ "$custom_mcp_install_command" == *"direxio-mcp@0.1.7"* ]]
215
+ mcp_doctor_command=$(_mcp_doctor_command "$mcp_credentials" "codex-im-example")
216
+ [[ "$mcp_doctor_command" == *"DIREXIO_CREDENTIALS_FILE="* ]]
217
+ [[ "$mcp_doctor_command" == *"direxio-mcp doctor --json"* ]]
218
+
219
+ stale_node_id=$(DIREXIO_AGENT_NODE_ID=codex-old.example.test _agent_node_id codex new.example.test '!agents-real:new.example.test')
220
+ [[ "$stale_node_id" == codex-new.example.test-* ]]
221
+
222
+ matching_node_id=$(DIREXIO_AGENT_NODE_ID=codex-new.example.test-123 _agent_node_id codex new.example.test '!agents-real:new.example.test')
223
+ [ "$matching_node_id" = "codex-new.example.test-123" ]
224
+
225
+ config_path="$tmp/cc-connect/config.toml"
226
+ _write_cc_connect_config "$config_path" "$tmp/cc-connect/data" "codex-node" "codex" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test"
227
+ grep -q 'type = "matrix"' "$config_path"
228
+ grep -q 'type = "codex"' "$config_path"
229
+ grep -q 'admin_from = "@owner:im.example.test"' "$config_path"
230
+ awk '/^\[projects.agent.options\]/{in_options=1} /^admin_from = / && in_options{exit 1}' "$config_path"
231
+ grep -q 'backend = "app_server"' "$config_path"
232
+ grep -q 'app_server_url = "stdio"' "$config_path"
233
+ grep -q 'mode = "yolo"' "$config_path"
234
+ grep -q 'room_id = "!agents-real:im.example.test"' "$config_path"
235
+ grep -q 'user_id = "@agent:im.example.test"' "$config_path"
236
+ grep -q 'share_session_in_channel = true' "$config_path"
237
+ grep -q 'group_reply_all = true' "$config_path"
238
+ grep -q 'auto_join = false' "$config_path"
239
+ ! grep -q '^\[speech\]' "$config_path"
240
+ ! grep -q 'DIREXIO_CREDENTIALS_FILE' "$config_path"
241
+
242
+ speech_config_path="$tmp/cc-connect/config-with-speech.toml"
243
+ DIREXIO_SPEECH_API_KEY=speech-key \
244
+ DIREXIO_SPEECH_BASE_URL=https://stt.example.test/v1 \
245
+ DIREXIO_SPEECH_MODEL=whisper-test \
246
+ _write_cc_connect_config "$speech_config_path" "$tmp/cc-connect/data-speech" "codex-node" "codex" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test"
247
+ grep -q '^\[speech\]$' "$speech_config_path"
248
+ grep -q 'enabled = true' "$speech_config_path"
249
+ grep -q 'provider = "openai"' "$speech_config_path"
250
+ grep -q 'language = "zh"' "$speech_config_path"
251
+ grep -q '^\[speech.openai\]$' "$speech_config_path"
252
+ grep -q 'api_key = "speech-key"' "$speech_config_path"
253
+ grep -q 'base_url = "https://stt.example.test/v1"' "$speech_config_path"
254
+ grep -q 'model = "whisper-test"' "$speech_config_path"
255
+
256
+ [ "$(_cc_connect_agent_type codex)" = "codex" ]
257
+ [ "$(_cc_connect_agent_type claude-code)" = "claudecode" ]
258
+ [ "$(_cc_connect_agent_type claudecode)" = "claudecode" ]
259
+ [ "$(_cc_connect_agent_type opencode)" = "opencode" ]
260
+ [ "$(_cc_connect_agent_type qodercli)" = "qoder" ]
261
+ [ "$(_cc_connect_agent_type antigravity)" = "antigravity" ]
262
+ [ "$(_cc_connect_agent_type openclaw)" = "acp" ]
263
+ [ "$(_cc_connect_agent_type hermes)" = "acp" ]
264
+ [ "$(DIREXIO_CC_CONNECT_AGENT=gemini _cc_connect_agent_type unknown)" = "gemini" ]
265
+ [ "$(DIREXIO_CC_CONNECT_AGENT=codex _cc_connect_agent_type hermes)" = "codex" ]
266
+ [ "$(DIREXIO_CODEX_COMMAND=/opt/codex/bin/codex _cc_connect_agent_command codex)" = "/opt/codex/bin/codex" ]
267
+ [ "$(DIREXIO_GEMINI_COMMAND=/opt/gemini/bin/gemini _cc_connect_agent_command gemini)" = "/opt/gemini/bin/gemini" ]
268
+ [ "$(DIREXIO_CLAUDE_CODE_COMMAND=/opt/claude/bin/claude _cc_connect_agent_command claudecode)" = "/opt/claude/bin/claude" ]
269
+ [ "$(DIREXIO_QODERCLI_COMMAND=/opt/qoder/qodercli _cc_connect_agent_command qoder)" = "/opt/qoder/qodercli" ]
270
+ [ "$(DIREXIO_CC_CONNECT_AGENT_CMD=/custom/agent _cc_connect_agent_command codex)" = "/custom/agent" ]
271
+ [ "$(_cc_connect_agent_command acp openclaw)" = "openclaw" ]
272
+ [ "$(_cc_connect_agent_command acp hermes)" = "direxio-connect" ]
273
+ [ "$(DIREXIO_OPENCLAW_COMMAND=/opt/openclaw/bin/openclaw _cc_connect_agent_command acp openclaw)" = "/opt/openclaw/bin/openclaw" ]
274
+ [ "$(DIREXIO_HERMES_COMMAND=/opt/hermes/bin/hermes _cc_connect_agent_command acp hermes)" = "direxio-connect" ]
275
+
276
+ cmd_config_path="$tmp/cc-connect/config-with-cmd.toml"
277
+ _write_cc_connect_config "$cmd_config_path" "$tmp/cc-connect/data-cmd" "codex-node" "codex" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test" "/opt/codex/bin/codex"
278
+ grep -q 'cmd = "/opt/codex/bin/codex"' "$cmd_config_path"
279
+
280
+ options_config_path="$tmp/cc-connect/config-with-extra-options.toml"
281
+ _write_cc_connect_config "$options_config_path" "$tmp/cc-connect/data-options" "reasonix-node" "reasonix" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test" "" 'serve_url = "http://127.0.0.1:8080"'
282
+ grep -q 'type = "reasonix"' "$options_config_path"
283
+ grep -q 'serve_url = "http://127.0.0.1:8080"' "$options_config_path"
284
+ ! grep -q 'backend = "app_server"' "$options_config_path"
285
+
286
+ codex_options_config_path="$tmp/cc-connect/config-with-codex-extra-options.toml"
287
+ _write_cc_connect_config "$codex_options_config_path" "$tmp/cc-connect/data-codex-options" "codex-node" "codex" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test" "" $'mode = "full-auto"\nmodel = "gpt-5.5"'
288
+ grep -q 'backend = "app_server"' "$codex_options_config_path"
289
+ grep -q 'app_server_url = "stdio"' "$codex_options_config_path"
290
+ grep -q 'mode = "full-auto"' "$codex_options_config_path"
291
+ [ "$(grep -c '^[[:space:]]*mode[[:space:]]*=' "$codex_options_config_path")" = "1" ]
292
+ grep -q 'model = "gpt-5.5"' "$codex_options_config_path"
293
+
294
+ fakebin="$tmp/fakebin"
295
+ mkdir -p "$fakebin"
296
+ cat > "$fakebin/npm" <<'EOF'
297
+ #!/usr/bin/env bash
298
+ exit 0
299
+ EOF
300
+ cat > "$fakebin/direxio-connect" <<'EOF'
301
+ #!/usr/bin/env bash
302
+ set -euo pipefail
303
+ if [ "${1:-}" = "daemon" ] && [ "${2:-}" = "install" ]; then
304
+ [ "${5:-}" = "--service-name" ]
305
+ [ "${6:-}" = "im.example.test" ]
306
+ exit 0
307
+ fi
308
+ if [ "${1:-}" = "daemon" ] && [ "${2:-}" = "status" ]; then
309
+ [ "${3:-}" = "--service-name" ]
310
+ [ "${4:-}" = "im.example.test" ]
311
+ cat <<STATUS
312
+ direxio-connect daemon status
313
+
314
+ Status: Stopped
315
+ Platform: launchd
316
+ WorkDir: /tmp/direxio-test/cc-connect
317
+ STATUS
318
+ exit 0
319
+ fi
320
+ exit 1
321
+ EOF
322
+ chmod 700 "$fakebin/npm" "$fakebin/direxio-connect"
323
+ STATE_CALLS="$tmp/state.calls"
324
+ : > "$STATE_CALLS"
325
+ PATH="$fakebin:$PATH" _maybe_auto_install_cc_connect auto codex codex "$tmp/service" "$tmp/service/cc-connect/config.toml" direxio-connect im.example.test
326
+ grep -q '^agent_install_status=install_failed$' "$STATE_CALLS"
327
+
328
+ if _cc_connect_agent_options_toml openclaw acp > "$tmp/openclaw-missing.out" 2> "$tmp/openclaw-missing.err"; then
329
+ echo "OpenClaw ACP options must require real gateway URL, token file, and session" >&2
330
+ exit 1
331
+ fi
332
+ grep -q 'DIREXIO_OPENCLAW_ACP_URL' "$tmp/openclaw-missing.err"
333
+ grep -q 'DIREXIO_OPENCLAW_ACP_TOKEN_FILE' "$tmp/openclaw-missing.err"
334
+ grep -q 'DIREXIO_OPENCLAW_ACP_SESSION' "$tmp/openclaw-missing.err"
335
+
336
+ openclaw_options=$(
337
+ DIREXIO_OPENCLAW_ACP_URL=ws://127.0.0.1:18790 \
338
+ DIREXIO_OPENCLAW_ACP_TOKEN_FILE=/mnt/c/Users/alice/.openclaw/gateway.token \
339
+ DIREXIO_OPENCLAW_ACP_SESSION=agent:main:main \
340
+ _cc_connect_agent_options_toml openclaw acp
341
+ )
342
+ [[ "$openclaw_options" == *'args = ["acp", "--url", "ws://127.0.0.1:18790", "--token-file", "/mnt/c/Users/alice/.openclaw/gateway.token", "--session", "agent:main:main"]'* ]]
343
+ [[ "$openclaw_options" == *'display_name = "OpenClaw ACP"'* ]]
344
+
345
+ openclaw_session_options=$(
346
+ DIREXIO_OPENCLAW_ACP_URL=ws://127.0.0.1:18790 \
347
+ DIREXIO_OPENCLAW_ACP_TOKEN_FILE=/mnt/c/Users/alice/.openclaw/gateway.token \
348
+ DIREXIO_OPENCLAW_ACP_SESSION=agent:direxio:main \
349
+ _cc_connect_agent_options_toml openclaw acp
350
+ )
351
+ [[ "$openclaw_session_options" == *'--session", "agent:direxio:main"'* ]]
352
+
353
+ openclaw_url_options=$(DIREXIO_OPENCLAW_ACP_ARGS_TOML='["acp", "--url", "wss://gateway.example.test:18789", "--session", "agent:main:main"]' _cc_connect_agent_options_toml openclaw acp)
354
+ [[ "$openclaw_url_options" == *'args = ["acp", "--url", "wss://gateway.example.test:18789", "--session", "agent:main:main"]'* ]]
355
+
356
+ openclaw_posix_token_options=$(DIREXIO_OPENCLAW_ACP_URL=ws://127.0.0.1:18790 DIREXIO_LOCAL_PATH_STYLE=posix DIREXIO_OPENCLAW_ACP_TOKEN_FILE=/mnt/c/Users/alice/.openclaw/token.json DIREXIO_OPENCLAW_ACP_SESSION=agent:main:main _cc_connect_agent_options_toml openclaw acp)
357
+ [[ "$openclaw_posix_token_options" == *'args = ["acp", "--url", "ws://127.0.0.1:18790", "--token-file", "/mnt/c/Users/alice/.openclaw/token.json", "--session", "agent:main:main"]'* ]]
358
+
359
+ openclaw_token_options=$(DIREXIO_OPENCLAW_ACP_URL=ws://127.0.0.1:18790 DIREXIO_LOCAL_PATH_STYLE=windows DIREXIO_OPENCLAW_ACP_TOKEN_FILE=/mnt/c/Users/alice/.openclaw/token.json DIREXIO_OPENCLAW_ACP_SESSION=agent:main:main _cc_connect_agent_options_toml openclaw acp)
360
+ [[ "$openclaw_token_options" == *'args = ["acp", "--url", "ws://127.0.0.1:18790", "--token-file", "C:/Users/alice/.openclaw/token.json", "--session", "agent:main:main"]'* ]]
361
+
362
+ hermes_options=$(_cc_connect_agent_options_toml hermes acp)
363
+ [[ "$hermes_options" == *'args = ["hermes-acp-adapter", "--", "hermes", "acp"]'* ]]
364
+ [[ "$hermes_options" == *'display_name = "Hermes ACP"'* ]]
365
+
366
+ hermes_custom_command_options=$(DIREXIO_HERMES_COMMAND=/opt/hermes/bin/hermes _cc_connect_agent_options_toml hermes acp)
367
+ [[ "$hermes_custom_command_options" == *'args = ["hermes-acp-adapter", "--", "/opt/hermes/bin/hermes", "acp"]'* ]]
368
+
369
+ hermes_custom_args_options=$(DIREXIO_HERMES_ACP_ARGS_TOML='["acp", "--profile", "direxio"]' _cc_connect_agent_options_toml hermes acp)
370
+ [[ "$hermes_custom_args_options" == *'args = ["hermes-acp-adapter", "--", "hermes", "acp", "--profile", "direxio"]'* ]]
371
+
372
+ openclaw_config_path="$tmp/cc-connect/config-openclaw.toml"
373
+ _write_cc_connect_config "$openclaw_config_path" "$tmp/cc-connect/data-openclaw" "openclaw-node" "$(_cc_connect_agent_type openclaw)" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test" "$(_cc_connect_agent_command acp openclaw)" "$openclaw_options"
374
+ grep -q 'type = "acp"' "$openclaw_config_path"
375
+ grep -q 'cmd = "openclaw"' "$openclaw_config_path"
376
+ grep -q 'args = \["acp", "--url", "ws://127.0.0.1:18790", "--token-file", "/mnt/c/Users/alice/.openclaw/gateway.token", "--session", "agent:main:main"\]' "$openclaw_config_path"
377
+ grep -q 'display_name = "OpenClaw ACP"' "$openclaw_config_path"
378
+
379
+ hermes_config_path="$tmp/cc-connect/config-hermes.toml"
380
+ _write_cc_connect_config "$hermes_config_path" "$tmp/cc-connect/data-hermes" "hermes-node" "$(_cc_connect_agent_type hermes)" "$tmp/workspace" "https://im.example.test" "matrix-token" "@agent:im.example.test" "!agents-real:im.example.test" "@owner:im.example.test" "$(_cc_connect_agent_command acp hermes)" "$(_cc_connect_agent_options_toml hermes acp)"
381
+ grep -q 'type = "acp"' "$hermes_config_path"
382
+ grep -q 'cmd = "direxio-connect"' "$hermes_config_path"
383
+ grep -q 'args = \["hermes-acp-adapter", "--", "hermes", "acp"\]' "$hermes_config_path"
384
+ grep -q 'display_name = "Hermes ACP"' "$hermes_config_path"
385
+
386
+ guidance=$(
387
+ _print_cc_connect_guidance codex https://im.example.test "$HOME/.direxio/nodes/im.example.test/credentials.json" "$HOME/.direxio/nodes/im.example.test/env" recommend cc-connect "install command" codex-im "$config_path" "$HOME/.direxio/nodes/im.example.test/cc-connect/bin/direxio-connect" codex "/opt/codex/bin/codex" im.example.test 2>&1 >/dev/null
388
+ )
389
+ [[ "$guidance" == *"DIREXIO_DOMAIN"* ]]
390
+ [[ "$guidance" == *"DIREXIO_AGENT_TOKEN"* ]]
391
+ [[ "$guidance" == *"cc-connect service"* ]]
392
+ [[ "$guidance" == *"DIREXIO_AGENT_ROOM_ID"* ]]
393
+ [[ "$guidance" == *"DIREXIO_AGENT_NODE_ID"* ]]
394
+ [[ "$guidance" == *"cc-connect config"* ]]
395
+ [[ "$guidance" == *"/opt/codex/bin/codex"* ]]
396
+ [[ "$guidance" == *"daemon install"* ]]
397
+ [[ "$guidance" == *"direxio-connent@latest"* || "$install_command" == *"direxio-connent@latest"* ]]
398
+ [[ "$guidance" == *"type = \"matrix\""* || "$guidance" == *"cc-connect will use Matrix"* ]]
399
+ bad_credentials_env_name="DIREXIO_CREDENTIALS""_FILE"
400
+ if [[ "$guidance" == *"$bad_credentials_env_name"* ]]; then
401
+ echo "cc-connect guidance must not use $bad_credentials_env_name; it writes direct Matrix config" >&2
402
+ exit 1
403
+ fi
404
+
405
+ echo "s6 wire local ok"