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,1435 @@
1
+ #!/usr/bin/env bash
2
+ # S6 WIRE_LOCAL_CLIENT - write service-scoped credentials and cc-connect env.
3
+ #
4
+ # ① ~/.direxio/nodes/<service_id>/credentials.json
5
+ # ② ~/.direxio/nodes/<service_id>/env
6
+ # ③ cc-connect Matrix config and install guidance for the detected agent runtime
7
+ # ④ MCP client snippets for Codex/OpenClaw/Hermes under the service directory
8
+ #
9
+ # Tokens change on every rebuild, so local credentials and cc-connect env must be refreshed.
10
+
11
+ S6_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
12
+ # shellcheck disable=SC1090
13
+ source "$S6_DIR/../lib/paths.sh"
14
+
15
+ _direxio_home() {
16
+ direxio_home
17
+ }
18
+
19
+ _direxio_service_id() {
20
+ direxio_service_id "$1"
21
+ }
22
+
23
+ _direxio_service_dir() {
24
+ direxio_service_dir "$1"
25
+ }
26
+
27
+ _cc_connect_supported_agents() {
28
+ printf '%s\n' acp antigravity claudecode codex copilot cursor devin gemini iflow kimi opencode pi qoder reasonix tmux
29
+ }
30
+
31
+ _cc_connect_supported_agents_csv() {
32
+ _cc_connect_supported_agents | paste -sd ',' - | sed 's/,/, /g'
33
+ }
34
+
35
+ _cc_connect_agent_alias() {
36
+ case "$1" in
37
+ claude|claude-code|claudecode) printf 'claudecode\n' ;;
38
+ open-code|opencode) printf 'opencode\n' ;;
39
+ qodercli|qoder) printf 'qoder\n' ;;
40
+ agy|antigravity) printf 'antigravity\n' ;;
41
+ acp|codex|copilot|cursor|devin|gemini|iflow|kimi|pi|reasonix|tmux) printf '%s\n' "$1" ;;
42
+ *) return 1 ;;
43
+ esac
44
+ }
45
+
46
+ _validate_cc_connect_agent() {
47
+ local agent
48
+ agent=$(_cc_connect_agent_alias "$1" 2>/dev/null) || {
49
+ fail "cc-connect agent must be one of: $(_cc_connect_supported_agents_csv)."
50
+ return 1
51
+ }
52
+ printf '%s\n' "$agent"
53
+ }
54
+
55
+ _detect_agent_runtime() {
56
+ local active_runtime explicit_agent home_runtime
57
+ if [ -n "${DIREXIO_AGENT_PLATFORM:-}" ] && [ "${DIREXIO_AGENT_PLATFORM:-}" != "auto" ]; then
58
+ _validate_agent_platform "$DIREXIO_AGENT_PLATFORM"
59
+ printf '%s\n' "$DIREXIO_AGENT_PLATFORM"
60
+ return 0
61
+ fi
62
+ if [ -n "${DIREXIO_CC_CONNECT_AGENT:-}" ]; then
63
+ explicit_agent=$(_validate_cc_connect_agent "$DIREXIO_CC_CONNECT_AGENT")
64
+ printf '%s\n' "$explicit_agent"
65
+ return 0
66
+ fi
67
+ # Active-process signals are stronger than stale config directories from
68
+ # other agents that have used this WSL home before.
69
+ active_runtime=$(_active_agent_runtime)
70
+ if [ -n "$active_runtime" ]; then printf '%s\n' "$active_runtime"; return 0; fi
71
+ home_runtime=$(_single_runtime_from_home_vars)
72
+ if [ -n "$home_runtime" ]; then printf '%s\n' "$home_runtime"; return 0; fi
73
+ # Fallback: check for agent config directories on disk.
74
+ home_runtime=$(_single_runtime_from_config_dirs)
75
+ if [ -n "$home_runtime" ]; then printf '%s\n' "$home_runtime"; return 0; fi
76
+ printf 'unknown\n'
77
+ }
78
+
79
+ _active_agent_runtime() {
80
+ local runtime
81
+ for runtime in $(_detectable_agent_runtimes); do
82
+ if _runtime_has_env_signal "$runtime"; then
83
+ printf '%s\n' "$runtime"
84
+ return 0
85
+ fi
86
+ done
87
+ for runtime in $(_detectable_agent_runtimes); do
88
+ if _runtime_has_context_signal "$runtime"; then
89
+ printf '%s\n' "$runtime"
90
+ return 0
91
+ fi
92
+ done
93
+ return 0
94
+ }
95
+
96
+ _detectable_agent_runtimes() {
97
+ printf '%s\n' claudecode codex gemini cursor copilot devin kimi opencode iflow qoder pi antigravity acp reasonix tmux openclaw hermes
98
+ }
99
+
100
+ _runtime_has_active_signal() {
101
+ _runtime_has_env_signal "$1" || _runtime_has_context_signal "$1"
102
+ }
103
+
104
+ _runtime_has_env_signal() {
105
+ local runtime=$1
106
+ case "$runtime" in
107
+ acp)
108
+ _env_name_matches '^ACP_'
109
+ ;;
110
+ antigravity)
111
+ _env_name_matches '^(ANTIGRAVITY_|GOOGLE_ANTIGRAVITY_|AGY_)'
112
+ ;;
113
+ codex)
114
+ _env_name_matches '^CODEX_'
115
+ ;;
116
+ claudecode|claude-code)
117
+ _env_name_matches '^(CLAUDECODE|CLAUDECODE_|CLAUDE_CODE_)'
118
+ ;;
119
+ gemini)
120
+ _env_name_matches '^(GEMINI_CLI|GEMINI_CLI_|GEMINI_AGENT_|GOOGLE_GEMINI_CLI_)'
121
+ ;;
122
+ cursor)
123
+ _env_name_matches '^CURSOR_'
124
+ ;;
125
+ copilot)
126
+ _env_name_matches '^(COPILOT_|GITHUB_COPILOT_)'
127
+ ;;
128
+ devin)
129
+ _env_name_matches '^(DEVIN_|WINDSURF_)'
130
+ ;;
131
+ iflow)
132
+ _env_name_matches '^IFLOW_'
133
+ ;;
134
+ kimi)
135
+ _env_name_matches '^KIMI_'
136
+ ;;
137
+ opencode)
138
+ _env_name_matches '^(OPENCODE_|OPEN_CODE_)'
139
+ ;;
140
+ pi)
141
+ _env_name_matches '^(PI_CODING_AGENT_|PI_AGENT_)'
142
+ ;;
143
+ qoder)
144
+ _env_name_matches '^QODER_'
145
+ ;;
146
+ reasonix)
147
+ _env_name_matches '^REASONIX_'
148
+ ;;
149
+ tmux)
150
+ _env_name_matches '^TMUX'
151
+ ;;
152
+ openclaw)
153
+ _env_name_matches '^OPENCLAW_'
154
+ ;;
155
+ hermes)
156
+ _env_name_matches '^HERMES_'
157
+ ;;
158
+ *) return 1 ;;
159
+ esac
160
+ }
161
+
162
+ _runtime_has_context_signal() {
163
+ local runtime=$1
164
+ case "$runtime" in
165
+ acp)
166
+ _active_text_contains '/.acp/' ||
167
+ _active_text_contains '/.agents/' ||
168
+ _process_name_matches 'acp'
169
+ ;;
170
+ antigravity)
171
+ _active_text_contains '/.antigravity/' ||
172
+ _active_text_contains 'antigravity' ||
173
+ _process_name_matches 'agy'
174
+ ;;
175
+ codex)
176
+ _active_text_contains '/.codex/tmp/' ||
177
+ _active_text_contains 'openai/codex' ||
178
+ _active_text_contains 'openai.codex' ||
179
+ _active_text_contains '/.codex/' ||
180
+ _process_name_matches 'codex'
181
+ ;;
182
+ claudecode|claude-code)
183
+ _active_text_contains '/.claude/tmp/' ||
184
+ _active_text_contains '/.claude/' ||
185
+ _active_text_contains 'claude-code' ||
186
+ _process_name_matches 'claude'
187
+ ;;
188
+ gemini)
189
+ _active_text_contains '/.gemini/tmp/' ||
190
+ _active_text_contains '/.gemini/' ||
191
+ _process_name_matches 'gemini'
192
+ ;;
193
+ cursor)
194
+ _active_text_contains '/.cursor/tmp/' ||
195
+ _active_text_contains '/.cursor/' ||
196
+ _active_text_contains 'cursor' ||
197
+ _process_name_matches 'cursor'
198
+ ;;
199
+ copilot)
200
+ _active_text_contains '/.github/copilot/' ||
201
+ _active_text_contains '/.copilot/' ||
202
+ _process_name_matches 'copilot'
203
+ ;;
204
+ devin)
205
+ _active_text_contains '/.devin/' ||
206
+ _process_name_matches 'devin'
207
+ ;;
208
+ iflow)
209
+ _active_text_contains '/.iflow/' ||
210
+ _process_name_matches 'iflow'
211
+ ;;
212
+ kimi)
213
+ _active_text_contains '/.kimi/' ||
214
+ _process_name_matches 'kimi'
215
+ ;;
216
+ opencode)
217
+ _active_text_contains '/.opencode/' ||
218
+ _active_text_contains '/.open-code/' ||
219
+ _process_name_matches 'opencode'
220
+ ;;
221
+ pi)
222
+ _active_text_contains '/.pi/agent/' ||
223
+ _process_name_matches 'pi'
224
+ ;;
225
+ qoder)
226
+ _active_text_contains '/.qoder/' ||
227
+ _process_name_matches 'qoder'
228
+ ;;
229
+ reasonix)
230
+ _active_text_contains '/.reasonix/' ||
231
+ _process_name_matches 'reasonix'
232
+ ;;
233
+ tmux)
234
+ _process_name_matches 'tmux'
235
+ ;;
236
+ openclaw)
237
+ _active_text_contains '/.openclaw/tmp/' ||
238
+ _active_text_contains '/.openclaw/' ||
239
+ _process_name_matches 'openclaw'
240
+ ;;
241
+ hermes)
242
+ _active_text_contains '/.hermes/tmp/' ||
243
+ _active_text_contains '/.hermes/' ||
244
+ _process_name_matches 'hermes'
245
+ ;;
246
+ *) return 1 ;;
247
+ esac
248
+ }
249
+
250
+ _single_runtime_from_home_vars() {
251
+ _single_runtime_from_sources vars
252
+ }
253
+
254
+ _single_runtime_from_config_dirs() {
255
+ _single_runtime_from_sources dirs
256
+ }
257
+
258
+ _single_runtime_from_sources() {
259
+ local source=$1 runtime match count=0
260
+ for runtime in $(_detectable_agent_runtimes); do
261
+ case "$source" in
262
+ vars)
263
+ _runtime_home_var_is_set "$runtime" || continue
264
+ ;;
265
+ dirs)
266
+ _runtime_config_dir_exists "$runtime" || continue
267
+ ;;
268
+ esac
269
+ match=$runtime
270
+ count=$((count+1))
271
+ done
272
+ if [ "$count" -eq 1 ]; then
273
+ printf '%s\n' "$match"
274
+ fi
275
+ return 0
276
+ }
277
+
278
+ _runtime_home_var_is_set() {
279
+ case "$1" in
280
+ acp) [ -n "${ACP_HOME:-}" ] ;;
281
+ antigravity) [ -n "${ANTIGRAVITY_HOME:-}" ] || [ -n "${AGY_HOME:-}" ] ;;
282
+ claudecode|claude-code) [ -n "${CLAUDE_HOME:-}" ] || [ -n "${CLAUDECODE_HOME:-}" ] ;;
283
+ codex) [ -n "${CODEX_HOME:-}" ] ;;
284
+ copilot) [ -n "${COPILOT_HOME:-}" ] ;;
285
+ cursor) [ -n "${CURSOR_HOME:-}" ] ;;
286
+ devin) [ -n "${DEVIN_HOME:-}" ] ;;
287
+ gemini) [ -n "${GEMINI_HOME:-}" ] ;;
288
+ iflow) [ -n "${IFLOW_HOME:-}" ] ;;
289
+ kimi) [ -n "${KIMI_HOME:-}" ] ;;
290
+ opencode) [ -n "${OPENCODE_HOME:-}" ] || [ -n "${OPEN_CODE_HOME:-}" ] ;;
291
+ pi) [ -n "${PI_CODING_AGENT_DIR:-}" ] || [ -n "${PI_HOME:-}" ] ;;
292
+ qoder) [ -n "${QODER_HOME:-}" ] ;;
293
+ reasonix) [ -n "${REASONIX_HOME:-}" ] ;;
294
+ tmux) [ -n "${TMUX_HOME:-}" ] ;;
295
+ openclaw) [ -n "${OPENCLAW_HOME:-}" ] ;;
296
+ hermes) [ -n "${HERMES_HOME:-}" ] ;;
297
+ *) return 1 ;;
298
+ esac
299
+ }
300
+
301
+ _runtime_config_dir_exists() {
302
+ case "$1" in
303
+ acp) [ -d "$HOME/.acp" ] ;;
304
+ antigravity) [ -d "$HOME/.antigravity" ] ;;
305
+ claudecode|claude-code) [ -d "$HOME/.claude" ] ;;
306
+ codex) [ -d "$HOME/.codex" ] ;;
307
+ copilot) [ -d "$HOME/.copilot" ] || [ -d "$HOME/.github/copilot" ] ;;
308
+ cursor) [ -d "$HOME/.cursor" ] ;;
309
+ devin) [ -d "$HOME/.devin" ] ;;
310
+ gemini) [ -d "$HOME/.gemini" ] ;;
311
+ iflow) [ -d "$HOME/.iflow" ] ;;
312
+ kimi) [ -d "$HOME/.kimi" ] ;;
313
+ opencode) [ -d "$HOME/.opencode" ] || [ -d "$HOME/.open-code" ] ;;
314
+ pi) [ -d "$HOME/.pi/agent" ] || [ -d "$HOME/.pi" ] ;;
315
+ qoder) [ -d "$HOME/.qoder" ] ;;
316
+ reasonix) [ -d "$HOME/.reasonix" ] ;;
317
+ tmux) [ -d "$HOME/.tmux" ] ;;
318
+ openclaw) [ -d "$HOME/.openclaw" ] ;;
319
+ hermes) [ -d "$HOME/.hermes" ] ;;
320
+ *) return 1 ;;
321
+ esac
322
+ }
323
+
324
+ _env_name_matches() {
325
+ env | sed -E 's/=.*$//' | grep -Eq "$1"
326
+ }
327
+
328
+ _active_text_contains() {
329
+ local needle=$1 text
330
+ text=$(printf '%s:%s' "${PATH:-}" "${PWD:-}" | tr '[:upper:]' '[:lower:]')
331
+ case "$text" in
332
+ *"$needle"*) return 0 ;;
333
+ *) return 1 ;;
334
+ esac
335
+ }
336
+
337
+ _process_name_matches() {
338
+ local needle=$1
339
+ [ "${DIREXIO_AGENT_DETECT_PROCESS:-1}" != "0" ] || return 1
340
+ _process_tree_names | tr '[:upper:]' '[:lower:]' | grep -Eq "(^|[^a-z0-9])${needle}([^a-z0-9]|$)"
341
+ }
342
+
343
+ _process_tree_names() {
344
+ local pid=${BASHPID:-$$} ppid depth=0
345
+ while [ -n "$pid" ] && [ "$pid" != "0" ] && [ "$depth" -lt 12 ]; do
346
+ ps -o comm= -p "$pid" 2>/dev/null || true
347
+ ppid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')
348
+ [ -n "$ppid" ] && [ "$ppid" != "$pid" ] || break
349
+ pid=$ppid
350
+ depth=$((depth+1))
351
+ done
352
+ }
353
+
354
+ _validate_agent_platform() {
355
+ case "$1" in
356
+ auto|generic|unknown|openclaw|hermes) return 0 ;;
357
+ *)
358
+ _cc_connect_agent_alias "$1" >/dev/null 2>&1 && return 0
359
+ fail "DIREXIO_AGENT_PLATFORM must be auto, a cc-connect agent ($(_cc_connect_supported_agents_csv)), openclaw, hermes, generic, or unknown."
360
+ ;;
361
+ esac
362
+ }
363
+
364
+ _agent_install_policy() {
365
+ local policy=${DIREXIO_AGENT_INSTALL:-recommend}
366
+ case "$policy" in
367
+ skip|recommend|auto) printf '%s\n' "$policy" ;;
368
+ *) fail "DIREXIO_AGENT_INSTALL must be skip, recommend, or auto." ;;
369
+ esac
370
+ }
371
+
372
+ _agent_install_mode() {
373
+ local runtime=$1 mode=${DIREXIO_AGENT_INSTALL_MODE:-recommended}
374
+ case "$mode" in
375
+ recommended)
376
+ printf 'cc-connect\n'
377
+ ;;
378
+ cc-connect) printf '%s\n' "$mode" ;;
379
+ *) fail "DIREXIO_AGENT_INSTALL_MODE must be recommended or cc-connect." ;;
380
+ esac
381
+ }
382
+
383
+ _validate_real_agent_room_id() {
384
+ local room_id=$1
385
+ [ -n "$room_id" ] || fail "state is missing real agent_room_id; complete S5 against a current message-server build."
386
+ case "$room_id" in
387
+ \!agent:*) fail "legacy agent_room_id $room_id is not supported; redeploy or restart message-server so it creates a real agents room." ;;
388
+ \!*) return 0 ;;
389
+ *) fail "agent_room_id must be a Matrix room id beginning with !, got: $room_id" ;;
390
+ esac
391
+ }
392
+
393
+ _cc_connect_agent_type() {
394
+ local runtime=$1 explicit=${DIREXIO_CC_CONNECT_AGENT:-}
395
+ if [ -n "$explicit" ]; then
396
+ _validate_cc_connect_agent "$explicit"
397
+ return 0
398
+ fi
399
+ case "$runtime" in
400
+ openclaw|hermes) printf 'acp\n'; return 0 ;;
401
+ esac
402
+ _validate_cc_connect_agent "$runtime"
403
+ }
404
+
405
+ _cc_connect_agent_command() {
406
+ local agent runtime raw_key var value
407
+ runtime=${2:-$1}
408
+ agent=$(_cc_connect_agent_alias "$1" 2>/dev/null || printf '%s\n' "$1")
409
+ if [ -n "${DIREXIO_CC_CONNECT_AGENT_CMD:-}" ]; then
410
+ printf '%s\n' "$DIREXIO_CC_CONNECT_AGENT_CMD"
411
+ return 0
412
+ fi
413
+ if [ "$runtime" = "hermes" ] && [ "$agent" = "acp" ]; then
414
+ _local_connect_path "${DIREXIO_HERMES_ACP_ADAPTER_COMMAND:-${DIREXIO_CC_CONNECT_BIN:-direxio-connect}}"
415
+ return 0
416
+ fi
417
+ for raw_key in $(_cc_connect_runtime_command_aliases "$runtime") "$agent" $(_cc_connect_agent_command_aliases "$agent"); do
418
+ var="DIREXIO_$(printf '%s' "$raw_key" | tr '[:lower:]-' '[:upper:]_')_COMMAND"
419
+ value=$(printenv "$var" 2>/dev/null || true)
420
+ if [ -n "$value" ]; then
421
+ printf '%s\n' "$value"
422
+ return 0
423
+ fi
424
+ done
425
+ case "$runtime" in
426
+ openclaw|hermes) printf '%s\n' "$runtime" ;;
427
+ esac
428
+ }
429
+
430
+ _cc_connect_runtime_command_aliases() {
431
+ case "$1" in
432
+ openclaw) printf '%s\n' openclaw ;;
433
+ hermes) printf '%s\n' hermes ;;
434
+ esac
435
+ }
436
+
437
+ _cc_connect_agent_command_aliases() {
438
+ case "$1" in
439
+ claudecode) printf '%s\n' claude-code claude ;;
440
+ opencode) printf '%s\n' open-code ;;
441
+ antigravity) printf '%s\n' agy ;;
442
+ qoder) printf '%s\n' qodercli ;;
443
+ esac
444
+ }
445
+
446
+ _cc_connect_repo() {
447
+ printf '%s\n' "${DIREXIO_CC_CONNECT_REPO:-https://github.com/YingSuiAI/direxio-connect.git}"
448
+ }
449
+
450
+ _cc_connect_npm_package() {
451
+ printf '%s\n' "${DIREXIO_CC_CONNECT_NPM_PACKAGE:-direxio-connent@latest}"
452
+ }
453
+
454
+ _cc_connect_ref() {
455
+ printf '%s\n' "${DIREXIO_CC_CONNECT_REF:-main}"
456
+ }
457
+
458
+ _cc_connect_source_dir() {
459
+ local service_dir=$1
460
+ printf '%s\n' "${DIREXIO_CC_CONNECT_DIR:-$service_dir/cc-connect-src}"
461
+ }
462
+
463
+ _cc_connect_runtime_dir() {
464
+ local service_dir=$1
465
+ printf '%s/cc-connect\n' "$service_dir"
466
+ }
467
+
468
+ _agent_workspace() {
469
+ local service_dir=$1
470
+ if [ -n "${DIREXIO_AGENT_WORKSPACE:-}" ]; then
471
+ printf '%s\n' "$DIREXIO_AGENT_WORKSPACE"
472
+ return 0
473
+ fi
474
+ if [ -n "${DIREXIO_AGENT_WORKSPACE_WINDOWS:-}" ]; then
475
+ printf '%s\n' "$DIREXIO_AGENT_WORKSPACE_WINDOWS"
476
+ return 0
477
+ fi
478
+ printf '%s/workspace\n' "$service_dir"
479
+ }
480
+
481
+ _cc_connect_config_path() {
482
+ local service_dir=$1
483
+ printf '%s/config.toml\n' "$(_cc_connect_runtime_dir "$service_dir")"
484
+ }
485
+
486
+ _mcp_npm_package() {
487
+ printf '%s\n' "${DIREXIO_MCP_NPM_PACKAGE:-direxio-mcp@latest}"
488
+ }
489
+
490
+ _mcp_command() {
491
+ printf '%s\n' "${DIREXIO_MCP_COMMAND:-direxio-mcp}"
492
+ }
493
+
494
+ _mcp_runtime_dir() {
495
+ local service_dir=$1
496
+ printf '%s/mcp\n' "$service_dir"
497
+ }
498
+
499
+ _mcp_codex_config_path() {
500
+ local service_dir=$1
501
+ printf '%s/codex.toml\n' "$(_mcp_runtime_dir "$service_dir")"
502
+ }
503
+
504
+ _mcp_json_config_path() {
505
+ local service_dir=$1
506
+ printf '%s/mcp-servers.json\n' "$(_mcp_runtime_dir "$service_dir")"
507
+ }
508
+
509
+ _mcp_openclaw_config_path() {
510
+ local service_dir=$1
511
+ printf '%s/openclaw.md\n' "$(_mcp_runtime_dir "$service_dir")"
512
+ }
513
+
514
+ _mcp_openclaw_server_config_path() {
515
+ local service_dir=$1
516
+ printf '%s/openclaw-server.json\n' "$(_mcp_runtime_dir "$service_dir")"
517
+ }
518
+
519
+ _mcp_hermes_config_path() {
520
+ local service_dir=$1
521
+ printf '%s/hermes.mcp.json\n' "$(_mcp_runtime_dir "$service_dir")"
522
+ }
523
+
524
+ _mcp_env_file_path() {
525
+ local service_dir=$1
526
+ printf '%s/env\n' "$(_mcp_runtime_dir "$service_dir")"
527
+ }
528
+
529
+ _mcp_readme_path() {
530
+ local service_dir=$1
531
+ printf '%s/README.md\n' "$(_mcp_runtime_dir "$service_dir")"
532
+ }
533
+
534
+ _mcp_server_name() {
535
+ local service_id=${1:-local}
536
+ printf 'direxio-%s\n' "$service_id" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]+/_/g; s/^_+//; s/_+$//; s/^$/direxio_local/'
537
+ }
538
+
539
+ _cc_connect_binary_path() {
540
+ local service_dir=$1
541
+ printf '%s\n' "${DIREXIO_CC_CONNECT_BIN:-direxio-connect}"
542
+ }
543
+
544
+ _cc_connect_agent_options_toml() {
545
+ local runtime=${1:-} agent=${2:-} args_toml q_display
546
+ if [ -n "${DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML:-}" ]; then
547
+ printf '%s\n' "$DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML"
548
+ return 0
549
+ fi
550
+ case "$runtime:$agent" in
551
+ openclaw:acp)
552
+ args_toml=$(_openclaw_acp_args_toml) || return 1
553
+ q_display=$(_toml_escape "OpenClaw ACP")
554
+ printf 'args = %s\n' "$args_toml"
555
+ printf 'display_name = "%s"\n' "$q_display"
556
+ ;;
557
+ hermes:acp)
558
+ args_toml=$(_hermes_acp_args_toml)
559
+ q_display=$(_toml_escape "Hermes ACP")
560
+ printf 'args = %s\n' "$args_toml"
561
+ printf 'display_name = "%s"\n' "$q_display"
562
+ ;;
563
+ esac
564
+ }
565
+
566
+ _openclaw_acp_args_toml() {
567
+ local url token_file session missing=
568
+ if [ -n "${DIREXIO_OPENCLAW_ACP_ARGS_TOML:-}" ]; then
569
+ printf '%s\n' "$DIREXIO_OPENCLAW_ACP_ARGS_TOML"
570
+ return 0
571
+ fi
572
+ url=${DIREXIO_OPENCLAW_ACP_URL:-}
573
+ token_file=${DIREXIO_OPENCLAW_ACP_TOKEN_FILE:-}
574
+ session=${DIREXIO_OPENCLAW_ACP_SESSION:-}
575
+ [ -n "$url" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_URL"
576
+ [ -n "$token_file" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_TOKEN_FILE"
577
+ [ -n "$session" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_SESSION"
578
+ if [ -n "$missing" ]; then
579
+ fail "OpenClaw ACP requires real Gateway settings:${missing}. Set them from the current OpenClaw runtime, or provide DIREXIO_OPENCLAW_ACP_ARGS_TOML with the complete args array."
580
+ return 1
581
+ fi
582
+ token_file=$(_local_connect_path "$token_file")
583
+ _toml_array acp --url "$url" --token-file "$token_file" --session "$session"
584
+ }
585
+
586
+ _hermes_acp_args_toml() {
587
+ local hermes_cmd
588
+ hermes_cmd=${DIREXIO_HERMES_COMMAND:-hermes}
589
+ hermes_cmd=$(_local_connect_path "$hermes_cmd")
590
+ if [ -n "${DIREXIO_HERMES_ACP_ARGS_TOML:-}" ]; then
591
+ _toml_array_prepend "$DIREXIO_HERMES_ACP_ARGS_TOML" hermes-acp-adapter -- "$hermes_cmd"
592
+ return 0
593
+ fi
594
+ _toml_array hermes-acp-adapter -- "$hermes_cmd" acp
595
+ }
596
+
597
+ _toml_array() {
598
+ local first=1 value q_value
599
+ printf '['
600
+ for value in "$@"; do
601
+ q_value=$(_toml_escape "$value")
602
+ if [ "$first" -eq 0 ]; then
603
+ printf ', '
604
+ fi
605
+ printf '"%s"' "$q_value"
606
+ first=0
607
+ done
608
+ printf ']\n'
609
+ }
610
+
611
+ _toml_array_prepend() {
612
+ local suffix_toml=$1 prefix_toml suffix_inner
613
+ shift
614
+ prefix_toml=$(_toml_array "$@")
615
+ suffix_inner=$(printf '%s' "$suffix_toml" | sed -E 's/^[[:space:]]*\[[[:space:]]*//; s/[[:space:]]*\][[:space:]]*$//')
616
+ if [ -z "$suffix_inner" ]; then
617
+ printf '%s\n' "$prefix_toml"
618
+ return 0
619
+ fi
620
+ printf '%s, %s]\n' "${prefix_toml%]}" "$suffix_inner"
621
+ }
622
+
623
+ _toml_has_key() {
624
+ local toml=$1 key=$2
625
+ printf '%s\n' "$toml" | grep -Eq "^[[:space:]]*${key}[[:space:]]*="
626
+ }
627
+
628
+ _cc_connect_default_agent_options_toml() {
629
+ local agent=$1 custom_toml=${2:-}
630
+ case "$agent" in
631
+ codex)
632
+ _toml_has_key "$custom_toml" backend || printf 'backend = "app_server"\n'
633
+ _toml_has_key "$custom_toml" app_server_url || printf 'app_server_url = "stdio"\n'
634
+ _toml_has_key "$custom_toml" mode || printf 'mode = "yolo"\n'
635
+ ;;
636
+ esac
637
+ }
638
+
639
+ _toml_escape() {
640
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
641
+ }
642
+
643
+ _powershell_single_quote() {
644
+ printf '%s' "$1" | sed "s/'/''/g"
645
+ }
646
+
647
+ _env_first() {
648
+ local name value
649
+ for name in "$@"; do
650
+ value=${!name:-}
651
+ if [ -n "$value" ]; then
652
+ printf '%s\n' "$value"
653
+ return 0
654
+ fi
655
+ done
656
+ return 0
657
+ }
658
+
659
+ _lower() {
660
+ printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
661
+ }
662
+
663
+ _cc_connect_speech_config_toml() {
664
+ local enabled provider language api_key base_url model
665
+ local q_provider q_language q_api_key q_base_url q_model
666
+ enabled=$(_lower "${DIREXIO_SPEECH_ENABLED:-auto}")
667
+ case "$enabled" in
668
+ 0|false|off|no|disabled) return 0 ;;
669
+ ""|1|true|on|yes|auto|enabled) ;;
670
+ *) fail "DIREXIO_SPEECH_ENABLED must be auto, true, or false." ;;
671
+ esac
672
+
673
+ provider=$(_lower "${DIREXIO_SPEECH_PROVIDER:-openai}")
674
+ language=${DIREXIO_SPEECH_LANGUAGE:-zh}
675
+ case "$provider" in
676
+ openai)
677
+ api_key=$(_env_first DIREXIO_SPEECH_OPENAI_API_KEY DIREXIO_SPEECH_API_KEY OPENAI_API_KEY)
678
+ base_url=$(_env_first DIREXIO_SPEECH_OPENAI_BASE_URL DIREXIO_SPEECH_BASE_URL OPENAI_BASE_URL)
679
+ model=$(_env_first DIREXIO_SPEECH_OPENAI_MODEL DIREXIO_SPEECH_MODEL)
680
+ ;;
681
+ groq)
682
+ api_key=$(_env_first DIREXIO_SPEECH_GROQ_API_KEY DIREXIO_SPEECH_API_KEY GROQ_API_KEY)
683
+ model=$(_env_first DIREXIO_SPEECH_GROQ_MODEL DIREXIO_SPEECH_MODEL)
684
+ ;;
685
+ qwen)
686
+ api_key=$(_env_first DIREXIO_SPEECH_QWEN_API_KEY DIREXIO_SPEECH_API_KEY DASHSCOPE_API_KEY DASH_SCOPE_API_KEY)
687
+ base_url=$(_env_first DIREXIO_SPEECH_QWEN_BASE_URL DIREXIO_SPEECH_BASE_URL)
688
+ model=$(_env_first DIREXIO_SPEECH_QWEN_MODEL DIREXIO_SPEECH_MODEL)
689
+ ;;
690
+ gemini)
691
+ api_key=$(_env_first DIREXIO_SPEECH_GEMINI_API_KEY DIREXIO_SPEECH_API_KEY GEMINI_API_KEY GOOGLE_API_KEY)
692
+ model=$(_env_first DIREXIO_SPEECH_GEMINI_MODEL DIREXIO_SPEECH_MODEL)
693
+ ;;
694
+ *) fail "DIREXIO_SPEECH_PROVIDER must be openai, groq, qwen, or gemini." ;;
695
+ esac
696
+ if [ -z "$api_key" ]; then
697
+ return 0
698
+ fi
699
+
700
+ q_provider=$(_toml_escape "$provider")
701
+ q_language=$(_toml_escape "$language")
702
+ q_api_key=$(_toml_escape "$api_key")
703
+ q_base_url=$(_toml_escape "$base_url")
704
+ q_model=$(_toml_escape "$model")
705
+ cat <<EOF
706
+ [speech]
707
+ enabled = true
708
+ provider = "$q_provider"
709
+ language = "$q_language"
710
+
711
+ [speech.$q_provider]
712
+ api_key = "$q_api_key"
713
+ EOF
714
+ if [ -n "$base_url" ]; then
715
+ printf 'base_url = "%s"\n' "$q_base_url"
716
+ fi
717
+ if [ -n "$model" ]; then
718
+ printf 'model = "%s"\n' "$q_model"
719
+ fi
720
+ }
721
+
722
+ _upper_drive() {
723
+ printf '%s' "$1" | tr '[:lower:]' '[:upper:]'
724
+ }
725
+
726
+ _local_path_style() {
727
+ printf '%s\n' "${DIREXIO_LOCAL_PATH_STYLE:-posix}"
728
+ }
729
+
730
+ _local_connect_path() {
731
+ local path=$1 drive rest
732
+ case "$(_local_path_style)" in
733
+ windows)
734
+ case "$path" in
735
+ [A-Za-z]:/*|[A-Za-z]:\\*) printf '%s\n' "$path" | sed 's#\\#/#g'; return 0 ;;
736
+ /mnt/[A-Za-z]/*)
737
+ drive=$(_upper_drive "$(printf '%s' "$path" | cut -d/ -f3)")
738
+ rest=$(printf '%s' "$path" | cut -d/ -f4-)
739
+ printf '%s:/%s\n' "$drive" "$rest"
740
+ return 0
741
+ ;;
742
+ /[A-Za-z]/*)
743
+ drive=$(_upper_drive "$(printf '%s' "$path" | cut -d/ -f2)")
744
+ rest=$(printf '%s' "$path" | cut -d/ -f3-)
745
+ printf '%s:/%s\n' "$drive" "$rest"
746
+ return 0
747
+ ;;
748
+ esac
749
+ ;;
750
+ esac
751
+ printf '%s\n' "$path"
752
+ }
753
+
754
+ _mcp_install_command() {
755
+ printf 'npm install -g %q' "$(_mcp_npm_package)"
756
+ }
757
+
758
+ _mcp_doctor_command() {
759
+ local credentials_file=$1 node_id=${2:-}
760
+ printf 'DIREXIO_CREDENTIALS_FILE=%q' "$(_local_connect_path "$credentials_file")"
761
+ if [ -n "$node_id" ]; then
762
+ printf ' DIREXIO_AGENT_NODE_ID=%q' "$node_id"
763
+ fi
764
+ printf ' %q doctor --json\n' "$(_mcp_command)"
765
+ }
766
+
767
+ _write_mcp_json_config() {
768
+ local path=$1 server_name=$2 command=$3 credentials_file=$4 node_id=${5:-}
769
+ mkdir -p "$(dirname "$path")"
770
+ umask 077
771
+ jq -n \
772
+ --arg server_name "$server_name" \
773
+ --arg command "$command" \
774
+ --arg credentials_file "$credentials_file" \
775
+ --arg node_id "$node_id" \
776
+ '{
777
+ mcpServers: {
778
+ ($server_name): {
779
+ command: $command,
780
+ env: {
781
+ DIREXIO_CREDENTIALS_FILE: $credentials_file,
782
+ DIREXIO_AGENT_NODE_ID: $node_id
783
+ }
784
+ }
785
+ }
786
+ }' > "$path"
787
+ chmod 600 "$path" 2>/dev/null || true
788
+ }
789
+
790
+ _write_mcp_openclaw_server_config() {
791
+ local path=$1 command=$2 credentials_file=$3 node_id=${4:-}
792
+ mkdir -p "$(dirname "$path")"
793
+ umask 077
794
+ jq -n \
795
+ --arg command "$command" \
796
+ --arg credentials_file "$credentials_file" \
797
+ --arg node_id "$node_id" \
798
+ '{
799
+ command: $command,
800
+ env: {
801
+ DIREXIO_CREDENTIALS_FILE: $credentials_file,
802
+ DIREXIO_AGENT_NODE_ID: $node_id
803
+ }
804
+ }' > "$path"
805
+ chmod 600 "$path" 2>/dev/null || true
806
+ }
807
+
808
+ _write_mcp_config_artifacts() {
809
+ local service_id=$1 service_dir=$2 credentials_file=$3 node_id=${4:-}
810
+ local mcp_dir server_name command credentials_local q_server q_command q_credentials q_node
811
+ local codex_config json_config openclaw_config openclaw_server_config hermes_config env_file readme
812
+ local openclaw_server_config_local openclaw_server_config_bash openclaw_server_config_ps
813
+ mcp_dir=$(_mcp_runtime_dir "$service_dir")
814
+ server_name=$(_mcp_server_name "$service_id")
815
+ command=$(_mcp_command)
816
+ credentials_local=$(_local_connect_path "$credentials_file")
817
+ q_server=$(_toml_escape "$server_name")
818
+ q_command=$(_toml_escape "$command")
819
+ q_credentials=$(_toml_escape "$credentials_local")
820
+ q_node=$(_toml_escape "$node_id")
821
+ codex_config=$(_mcp_codex_config_path "$service_dir")
822
+ json_config=$(_mcp_json_config_path "$service_dir")
823
+ openclaw_config=$(_mcp_openclaw_config_path "$service_dir")
824
+ openclaw_server_config=$(_mcp_openclaw_server_config_path "$service_dir")
825
+ hermes_config=$(_mcp_hermes_config_path "$service_dir")
826
+ env_file=$(_mcp_env_file_path "$service_dir")
827
+ readme=$(_mcp_readme_path "$service_dir")
828
+
829
+ mkdir -p "$mcp_dir"
830
+ umask 077
831
+ cat > "$codex_config" <<EOF
832
+ [mcp_servers."$q_server"]
833
+ command = "$q_command"
834
+ env = { DIREXIO_CREDENTIALS_FILE = "$q_credentials", DIREXIO_AGENT_NODE_ID = "$q_node" }
835
+ EOF
836
+ chmod 600 "$codex_config" 2>/dev/null || true
837
+
838
+ rm -f "$mcp_dir/openclaw.mcp.json"
839
+ _write_mcp_json_config "$json_config" "$server_name" "$command" "$credentials_local" "$node_id"
840
+ _write_mcp_openclaw_server_config "$openclaw_server_config" "$command" "$credentials_local" "$node_id"
841
+ _write_mcp_json_config "$hermes_config" "$server_name" "$command" "$credentials_local" "$node_id"
842
+
843
+ openclaw_server_config_local=$(_local_connect_path "$openclaw_server_config")
844
+ openclaw_server_config_bash=$(printf '%q' "$openclaw_server_config_local")
845
+ openclaw_server_config_ps=$(_powershell_single_quote "$openclaw_server_config_local")
846
+ cat > "$openclaw_config" <<EOF
847
+ # OpenClaw MCP Setup
848
+
849
+ OpenClaw must manage MCP servers through its own CLI/schema. Do not paste Codex/Hermes mcpServers snippets, or any raw top-level mcp block from this directory, into openclaw.json.
850
+
851
+ Server object for openclaw mcp set:
852
+
853
+ $openclaw_server_config_local
854
+
855
+ POSIX/Git Bash:
856
+
857
+ \`\`\`bash
858
+ openclaw mcp set $server_name "\$(cat $openclaw_server_config_bash)"
859
+ openclaw mcp doctor
860
+ openclaw mcp reload
861
+ \`\`\`
862
+
863
+ PowerShell:
864
+
865
+ \`\`\`powershell
866
+ openclaw mcp set $server_name (Get-Content -Raw -LiteralPath '$openclaw_server_config_ps')
867
+ openclaw mcp doctor
868
+ openclaw mcp reload
869
+ \`\`\`
870
+
871
+ This writes the server under OpenClaw's mcp.servers schema after OpenClaw validates it.
872
+ EOF
873
+ chmod 644 "$openclaw_config" 2>/dev/null || true
874
+
875
+ {
876
+ printf 'export DIREXIO_CREDENTIALS_FILE=%q\n' "$credentials_local"
877
+ [ -n "$node_id" ] && printf 'export DIREXIO_AGENT_NODE_ID=%q\n' "$node_id"
878
+ } > "$env_file"
879
+ chmod 600 "$env_file" 2>/dev/null || true
880
+
881
+ cat > "$readme" <<EOF
882
+ # Direxio MCP Config
883
+
884
+ Install the local MCP package:
885
+
886
+ \`\`\`bash
887
+ $(_mcp_install_command)
888
+ \`\`\`
889
+
890
+ Check this service's MCP credentials:
891
+
892
+ \`\`\`bash
893
+ $(_mcp_doctor_command "$credentials_file" "$node_id")
894
+ \`\`\`
895
+
896
+ Config snippets:
897
+
898
+ - Codex TOML: $(_local_connect_path "$codex_config")
899
+ - OpenClaw CLI setup: $(_local_connect_path "$openclaw_config")
900
+ - Hermes JSON: $(_local_connect_path "$hermes_config")
901
+ - Generic JSON: $(_local_connect_path "$json_config")
902
+ EOF
903
+ chmod 644 "$readme" 2>/dev/null || true
904
+ }
905
+
906
+ _create_cc_connect_matrix_session() {
907
+ local asurl=$1 access_token=$2 device_id=$3 out=$4 body code http_body
908
+ body=$(jq -n --arg device_id "$device_id" '{action:"agent.matrix_session.create",params:{device_id:$device_id}}')
909
+ http_body=$(mktemp)
910
+ code=$(curl -sk -o "$http_body" -w '%{http_code}' -X POST "$asurl/_p2p/command" \
911
+ -H 'Content-Type: application/json' \
912
+ -H "Authorization: Bearer $access_token" \
913
+ -d "$body" 2>/dev/null || true)
914
+ if [ "$code" != "200" ]; then
915
+ warn "agent.matrix_session.create returned HTTP ${code:-000}: $(head -c 200 "$http_body" 2>/dev/null)"
916
+ rm -f "$http_body"
917
+ return 1
918
+ fi
919
+ if ! jq -e '.access_token and .device_id and .user_id and .homeserver' "$http_body" >/dev/null; then
920
+ warn "agent.matrix_session.create response is missing Matrix session fields: $(head -c 200 "$http_body" 2>/dev/null)"
921
+ rm -f "$http_body"
922
+ return 1
923
+ fi
924
+ mv "$http_body" "$out"
925
+ chmod 600 "$out" 2>/dev/null || true
926
+ }
927
+
928
+ _write_cc_connect_config() {
929
+ local config_path=$1 data_dir=$2 project=$3 agent=$4 workspace=$5 homeserver=$6 matrix_token=$7 matrix_user=$8 room_id=$9 admin_from=${10:-} agent_cmd=${11:-} agent_options_toml=${12:-}
930
+ local q_data q_project q_agent q_workspace q_homeserver q_token q_user q_room q_admin_from q_agent_cmd speech_toml default_agent_options_toml
931
+ mkdir -p "$(dirname "$config_path")" "$data_dir"
932
+ q_data=$(_toml_escape "$data_dir")
933
+ q_project=$(_toml_escape "$project")
934
+ q_agent=$(_toml_escape "$agent")
935
+ q_workspace=$(_toml_escape "$workspace")
936
+ q_homeserver=$(_toml_escape "$homeserver")
937
+ q_token=$(_toml_escape "$matrix_token")
938
+ q_user=$(_toml_escape "$matrix_user")
939
+ q_room=$(_toml_escape "$room_id")
940
+ q_admin_from=$(_toml_escape "$admin_from")
941
+ q_agent_cmd=$(_toml_escape "$agent_cmd")
942
+ speech_toml=$(_cc_connect_speech_config_toml)
943
+ default_agent_options_toml=$(_cc_connect_default_agent_options_toml "$agent" "$agent_options_toml")
944
+ umask 077
945
+ cat > "$config_path" <<EOF
946
+ language = "zh"
947
+ data_dir = "$q_data"
948
+ EOF
949
+ if [ -n "$speech_toml" ]; then
950
+ printf '\n%s\n' "$speech_toml" >> "$config_path"
951
+ fi
952
+ cat >> "$config_path" <<EOF
953
+
954
+ [[projects]]
955
+ name = "$q_project"
956
+ admin_from = "$q_admin_from"
957
+
958
+ [projects.agent]
959
+ type = "$q_agent"
960
+
961
+ [projects.agent.options]
962
+ work_dir = "$q_workspace"
963
+ EOF
964
+ if [ -n "$agent_cmd" ]; then
965
+ cat >> "$config_path" <<EOF
966
+ cmd = "$q_agent_cmd"
967
+ EOF
968
+ fi
969
+ if [ -n "$default_agent_options_toml" ]; then
970
+ printf '%s\n' "$default_agent_options_toml" >> "$config_path"
971
+ fi
972
+ if [ -n "$agent_options_toml" ]; then
973
+ printf '%s\n' "$agent_options_toml" >> "$config_path"
974
+ fi
975
+ cat >> "$config_path" <<EOF
976
+
977
+ [[projects.platforms]]
978
+ type = "matrix"
979
+
980
+ [projects.platforms.options]
981
+ homeserver = "$q_homeserver"
982
+ access_token = "$q_token"
983
+ user_id = "$q_user"
984
+ room_id = "$q_room"
985
+ share_session_in_channel = true
986
+ group_reply_all = true
987
+ auto_join = false
988
+ auto_verify = false
989
+ EOF
990
+ chmod 600 "$config_path"
991
+ }
992
+
993
+ _cc_connect_install_command() {
994
+ local binary=$1 config=$2 service_name=$3
995
+ [ -n "$service_name" ] || service_name=cc-connect
996
+ printf 'npm install -g %q && %q daemon install --config %q --service-name %q --force' "$(_cc_connect_npm_package)" "$binary" "$(_local_connect_path "$config")" "$service_name"
997
+ }
998
+
999
+ _cc_connect_daemon_is_running() {
1000
+ local binary=$1 service_name=$2 status
1001
+ [ -n "$service_name" ] || service_name=cc-connect
1002
+ status=$("$binary" daemon status --service-name "$service_name" 2>/dev/null || true)
1003
+ printf '%s\n' "$status" | grep -Eq 'Status:[[:space:]]*Running'
1004
+ }
1005
+
1006
+ _cc_connect_daemon_has_agent_startup_error() {
1007
+ local binary=$1 service_name=$2 logs
1008
+ [ -n "$service_name" ] || service_name=cc-connect
1009
+ logs=$("$binary" daemon logs --service-name "$service_name" -n "${DIREXIO_CONNECT_LOG_TAIL_LINES:-120}" 2>/dev/null || true)
1010
+ printf '%s\n' "$logs" | grep -Eiq 'ACP_SESSION_INIT_FAILED|ACP metadata is missing|Recreate this ACP session'
1011
+ }
1012
+
1013
+ _maybe_auto_install_cc_connect() {
1014
+ local policy=$1 runtime=$2 cc_agent=$3 service_dir=$4 config_path=$5 binary=$6 service_name=$7
1015
+ local repo ref src commit config_arg
1016
+ [ -n "$service_name" ] || service_name=$(basename "$service_dir")
1017
+ if [ "$policy" != "auto" ]; then
1018
+ state_set agent_install_status "$policy" 2>/dev/null || true
1019
+ return 0
1020
+ fi
1021
+ config_arg=$(_local_connect_path "$config_path")
1022
+ if [ "${DIREXIO_CC_CONNECT_INSTALL_FROM:-npm}" != "source" ]; then
1023
+ if ! command -v npm >/dev/null 2>&1; then
1024
+ warn "DIREXIO_AGENT_INSTALL=auto requested, but npm is not on PATH. Install Node.js or set DIREXIO_CC_CONNECT_INSTALL_FROM=source."
1025
+ state_set agent_install_status "npm_missing" 2>/dev/null || true
1026
+ return 0
1027
+ fi
1028
+ if npm install -g "$(_cc_connect_npm_package)" && "$binary" daemon install --config "$config_arg" --service-name "$service_name" --force; then
1029
+ if ! _cc_connect_daemon_is_running "$binary" "$service_name"; then
1030
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1031
+ warn "cc-connect daemon install returned success, but daemon status is not Running. Check the local agent command and cc-connect logs."
1032
+ return 0
1033
+ fi
1034
+ if _cc_connect_daemon_has_agent_startup_error "$binary" "$service_name"; then
1035
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1036
+ warn "cc-connect daemon is Running, but logs show ACP session initialization failed. Check OpenClaw ACP URL, token-file, and session."
1037
+ return 0
1038
+ fi
1039
+ state_set agent_install_status "installed" 2>/dev/null || true
1040
+ ok "cc-connect daemon installed from npm for $runtime using Matrix room bridge."
1041
+ else
1042
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1043
+ warn "cc-connect npm install or daemon install failed. Config is available for manual start."
1044
+ fi
1045
+ return 0
1046
+ fi
1047
+
1048
+ repo=$(_cc_connect_repo)
1049
+ ref=$(_cc_connect_ref)
1050
+ src=$(_cc_connect_source_dir "$service_dir")
1051
+ if ! command -v git >/dev/null 2>&1 || ! command -v go >/dev/null 2>&1 || ! command -v make >/dev/null 2>&1; then
1052
+ warn "DIREXIO_CC_CONNECT_INSTALL_FROM=source requested, but git, go, and make are required to build cc-connect from source."
1053
+ state_set agent_install_status "build_tool_missing" 2>/dev/null || true
1054
+ return 0
1055
+ fi
1056
+ if [ ! -d "$src/.git" ]; then
1057
+ mkdir -p "$(dirname "$src")"
1058
+ if ! git clone "$repo" "$src"; then
1059
+ state_set agent_install_status "clone_failed" 2>/dev/null || true
1060
+ warn "cc-connect clone failed from $repo"
1061
+ return 0
1062
+ fi
1063
+ fi
1064
+ if ! git -C "$src" fetch --all --tags --prune; then
1065
+ state_set agent_install_status "fetch_failed" 2>/dev/null || true
1066
+ warn "cc-connect fetch failed in $src"
1067
+ return 0
1068
+ fi
1069
+ if ! git -C "$src" checkout "$ref"; then
1070
+ state_set agent_install_status "checkout_failed" 2>/dev/null || true
1071
+ warn "cc-connect checkout failed for ref $ref"
1072
+ return 0
1073
+ fi
1074
+ commit=$(git -C "$src" rev-parse --short HEAD 2>/dev/null || true)
1075
+ state_set cc_connect_commit "$commit" 2>/dev/null || true
1076
+ if ! (cd "$src" && AGENTS="$cc_agent" PLATFORMS_INCLUDE=matrix NO_WEB=1 make build-noweb); then
1077
+ state_set agent_install_status "build_failed" 2>/dev/null || true
1078
+ warn "cc-connect build failed for runtime=$runtime agent=$cc_agent"
1079
+ return 0
1080
+ fi
1081
+ binary="$(_cc_connect_runtime_dir "$service_dir")/bin/direxio-connect"
1082
+ mkdir -p "$(dirname "$binary")"
1083
+ if ! cp "$src/direxio-connect" "$binary" 2>/dev/null && ! cp "$src/direxio-connect.exe" "$binary" 2>/dev/null; then
1084
+ state_set agent_install_status "binary_copy_failed" 2>/dev/null || true
1085
+ warn "direxio-connect binary was not found after build in $src"
1086
+ return 0
1087
+ fi
1088
+ chmod 700 "$binary" 2>/dev/null || true
1089
+ if "$binary" daemon install --config "$config_arg" --service-name "$service_name" --force; then
1090
+ if ! _cc_connect_daemon_is_running "$binary" "$service_name"; then
1091
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1092
+ warn "cc-connect daemon install returned success, but daemon status is not Running. Check the local agent command and cc-connect logs."
1093
+ return 0
1094
+ fi
1095
+ if _cc_connect_daemon_has_agent_startup_error "$binary" "$service_name"; then
1096
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1097
+ warn "cc-connect daemon is Running, but logs show ACP session initialization failed. Check OpenClaw ACP URL, token-file, and session."
1098
+ return 0
1099
+ fi
1100
+ state_set agent_install_status "installed" 2>/dev/null || true
1101
+ ok "cc-connect daemon installed for $runtime using Matrix room bridge."
1102
+ else
1103
+ state_set agent_install_status "install_failed" 2>/dev/null || true
1104
+ warn "cc-connect daemon install failed. Config and binary are available for manual start."
1105
+ fi
1106
+ }
1107
+
1108
+ _agent_skill_install_path() {
1109
+ local runtime=$1
1110
+ case "$runtime" in
1111
+ acp) printf 'PROJECT_ROOT/.agents/skills/direxio-deployer\n' ;;
1112
+ antigravity) printf 'PROJECT_ROOT/.antigravity/skills/direxio-deployer\n' ;;
1113
+ codex) printf 'PROJECT_ROOT/.codex/skills/direxio-deployer\n' ;;
1114
+ claude|claude-code|claudecode) printf 'PROJECT_ROOT/.claude/skills/direxio-deployer\n' ;;
1115
+ devin) printf 'PROJECT_ROOT/.devin/skills/direxio-deployer\n' ;;
1116
+ iflow) printf 'PROJECT_ROOT/.iflow/skills/direxio-deployer\n' ;;
1117
+ kimi) printf 'PROJECT_ROOT/.kimi/skills/direxio-deployer\n' ;;
1118
+ opencode) printf 'PROJECT_ROOT/.opencode/skills/direxio-deployer\n' ;;
1119
+ pi) printf 'PROJECT_ROOT/.pi/agent/skills/direxio-deployer\n' ;;
1120
+ qoder) printf 'PROJECT_ROOT/.qoder/skills/direxio-deployer\n' ;;
1121
+ reasonix) printf 'PROJECT_ROOT/.reasonix/skills/direxio-deployer\n' ;;
1122
+ tmux) printf 'PROJECT_ROOT/.agent/skills/direxio-deployer\n' ;;
1123
+ gemini) printf 'PROJECT_ROOT/.gemini/skills/direxio-deployer\n' ;;
1124
+ cursor) printf 'PROJECT_ROOT/.cursor/skills/direxio-deployer\n' ;;
1125
+ copilot) printf 'PROJECT_ROOT/.github/copilot/skills/direxio-deployer\n' ;;
1126
+ openclaw) printf 'PROJECT_ROOT/.openclaw/skills/direxio-deployer\n' ;;
1127
+ hermes) printf 'PROJECT_ROOT/.hermes/skills/direxio-deployer\n' ;;
1128
+ generic|unknown|*) printf 'PROJECT_ROOT/.agent/skills/direxio-deployer\n' ;;
1129
+ esac
1130
+ }
1131
+
1132
+ _agent_global_skill_install_path() {
1133
+ local runtime=$1
1134
+ case "$runtime" in
1135
+ acp) printf '$HOME/.agents/skills/direxio-deployer\n' ;;
1136
+ antigravity) printf '${ANTIGRAVITY_HOME:-$HOME/.antigravity}/skills/direxio-deployer\n' ;;
1137
+ codex) printf '${CODEX_HOME:-$HOME/.codex}/skills/direxio-deployer\n' ;;
1138
+ claude|claude-code|claudecode) printf '${CLAUDE_HOME:-${CLAUDECODE_HOME:-$HOME/.claude}}/skills/direxio-deployer\n' ;;
1139
+ devin) printf '${DEVIN_HOME:-$HOME/.devin}/skills/direxio-deployer\n' ;;
1140
+ iflow) printf '${IFLOW_HOME:-$HOME/.iflow}/skills/direxio-deployer\n' ;;
1141
+ kimi) printf '${KIMI_HOME:-$HOME/.kimi}/skills/direxio-deployer\n' ;;
1142
+ opencode) printf '${OPENCODE_HOME:-$HOME/.opencode}/skills/direxio-deployer\n' ;;
1143
+ pi) printf '${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/skills/direxio-deployer\n' ;;
1144
+ qoder) printf '${QODER_HOME:-$HOME/.qoder}/skills/direxio-deployer\n' ;;
1145
+ reasonix) printf '${REASONIX_HOME:-$HOME/.reasonix}/skills/direxio-deployer\n' ;;
1146
+ tmux) printf '$HOME/.agent/skills/direxio-deployer\n' ;;
1147
+ gemini) printf '${GEMINI_HOME:-$HOME/.gemini}/skills/direxio-deployer\n' ;;
1148
+ cursor) printf '${CURSOR_HOME:-$HOME/.cursor}/skills/direxio-deployer\n' ;;
1149
+ copilot) printf '$HOME/.github/copilot/skills/direxio-deployer\n' ;;
1150
+ openclaw) printf '${OPENCLAW_HOME:-$HOME/.openclaw}/skills/direxio-deployer\n' ;;
1151
+ hermes) printf '${HERMES_HOME:-$HOME/.hermes}/skills/direxio-deployer\n' ;;
1152
+ generic|unknown|*) printf '$HOME/.agent/skills/direxio-deployer\n' ;;
1153
+ esac
1154
+ }
1155
+
1156
+ _agent_install_command() {
1157
+ local binary=$1 config=$2 service_name=$3
1158
+ _cc_connect_install_command "$binary" "$config" "$service_name"
1159
+ }
1160
+
1161
+ _print_runtime_install_summary() {
1162
+ local runtime=$1 mode=$2 config_path=$3 binary=$4 cc_agent=$5 cc_agent_cmd=${6:-} service_name=${7:-}
1163
+ cat >&2 <<EOF
1164
+ Recommended cc-connect install:
1165
+ runtime: $runtime
1166
+ cc-connect agent: $cc_agent
1167
+ agent command: ${cc_agent_cmd:-default PATH lookup}
1168
+ mode: $mode
1169
+ service name: $service_name
1170
+ config: $config_path
1171
+ binary: $binary
1172
+ daemon install: $(_cc_connect_install_command "$binary" "$config_path" "$service_name")
1173
+ EOF
1174
+ }
1175
+
1176
+ _maybe_auto_install_agent() {
1177
+ _maybe_auto_install_cc_connect "$@"
1178
+ }
1179
+
1180
+ _write_agent_env_file() {
1181
+ local asurl=$1 token=$2 access_token=$3 agent_room_id=$4 envfile=${5:-"$(_direxio_home)/env"} node_id=${6:-}
1182
+ mkdir -p "$(dirname "$envfile")"
1183
+ umask 077
1184
+ {
1185
+ [ -n "$node_id" ] && printf 'export DIREXIO_AGENT_NODE_ID=%q\n' "$node_id"
1186
+ printf 'export DIREXIO_DOMAIN=%q\n' "$asurl"
1187
+ printf 'export DIREXIO_AGENT_TOKEN=%q\n' "$token"
1188
+ printf 'export DIREXIO_AGENT_ROOM_ID=%q\n' "$agent_room_id"
1189
+ } > "$envfile"
1190
+ chmod 600 "$envfile"
1191
+ echo "$envfile"
1192
+ }
1193
+
1194
+ _agent_node_id() {
1195
+ local runtime=$1 domain=$2 room=$3 explicit host digest raw
1196
+ explicit=${DIREXIO_AGENT_NODE_ID:-}
1197
+ host=${domain#http://}
1198
+ host=${host#https://}
1199
+ host=${host%%/*}
1200
+ host=${host%%:*}
1201
+ if [ -n "$explicit" ] && { [ "${DIREXIO_AGENT_NODE_ID_FORCE:-}" = "1" ] || _agent_node_id_matches_host "$explicit" "$host"; }; then
1202
+ raw=$explicit
1203
+ else
1204
+ if command -v sha256sum >/dev/null 2>&1; then
1205
+ digest=$(printf '%s\n%s\n' "$domain" "$room" | sha256sum | awk '{print substr($1,1,10)}')
1206
+ else
1207
+ digest=$(printf '%s\n%s\n' "$domain" "$room" | shasum -a 256 | awk '{print substr($1,1,10)}')
1208
+ fi
1209
+ raw="${runtime:-agent}-${host:-direxio}-$digest"
1210
+ fi
1211
+ printf '%s\n' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//; s/^$/direxio-agent/'
1212
+ }
1213
+
1214
+ _agent_node_id_matches_host() {
1215
+ local node_id=$1 host=$2 normalized_node normalized_host
1216
+ normalized_node=$(printf '%s\n' "$node_id" | tr '[:upper:]' '[:lower:]')
1217
+ normalized_host=$(printf '%s\n' "$host" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//')
1218
+ [ -n "$normalized_host" ] && [[ "$normalized_node" == *"$normalized_host"* ]]
1219
+ }
1220
+
1221
+ _write_credentials_file() {
1222
+ local cred=$1 domain=$2 asurl=$3 token=$4 password=$5 access_token=$6 agent_room_id=$7 node_id=$8
1223
+ mkdir -p "$(dirname "$cred")"
1224
+ jq -n --arg domain "$domain" --arg url "$asurl" --arg tok "$token" --arg password "$password" --arg access "$access_token" --arg room "$agent_room_id" --arg node_id "$node_id" \
1225
+ '{profiles:{default:{domain:$domain,password:$password,access_token:$access,agent_room_id:$room,direxio_domain:$url,direxio_agent_token:$tok,direxio_agent_room_id:$room,direxio_agent_node_id:$node_id}}}' > "$cred"
1226
+ chmod 600 "$cred"
1227
+ }
1228
+
1229
+ _persist_agent_env() {
1230
+ local asurl=$1 token=$2 access_token=$3 agent_room_id=$4 envfile=${5:-"$(_direxio_home)/env"} node_id=${6:-}
1231
+ envfile=$(_write_agent_env_file "$asurl" "$token" "$access_token" "$agent_room_id" "$envfile" "$node_id")
1232
+ [ -n "$node_id" ] && export DIREXIO_AGENT_NODE_ID="$node_id"
1233
+ export DIREXIO_DOMAIN="$asurl"
1234
+ export DIREXIO_AGENT_TOKEN="$token"
1235
+ export DIREXIO_AGENT_ROOM_ID="$agent_room_id"
1236
+ ok "Persisted Direxio cc-connect env vars via $envfile."
1237
+ echo "$envfile"
1238
+ }
1239
+
1240
+ _print_cc_connect_guidance() {
1241
+ local runtime=$1 asurl=$2 cred=$3 envfile=$4 policy=$5 mode=$6 install_command=$7 node_id=$8 cc_config=$9 cc_binary=${10} cc_agent=${11} cc_agent_cmd=${12:-} service_name=${13:-}
1242
+ local skill_path global_skill_path
1243
+ skill_path=$(_agent_skill_install_path "$runtime")
1244
+ global_skill_path=$(_agent_global_skill_install_path "$runtime")
1245
+ if [ "$policy" = "skip" ]; then
1246
+ warn "Direxio cc-connect install guidance skipped by DIREXIO_AGENT_INSTALL=skip."
1247
+ return 0
1248
+ fi
1249
+ warn "Direxio cc-connect install policy: $policy; platform=$runtime; mode=$mode."
1250
+ cat >&2 <<EOF
1251
+ Detected agent runtime: $runtime
1252
+ cc-connect agent: $cc_agent
1253
+ Local env file: $envfile
1254
+ Credential file: $cred
1255
+ cc-connect config: $cc_config
1256
+ cc-connect binary: $cc_binary
1257
+ cc-connect agent cmd: ${cc_agent_cmd:-default PATH lookup}
1258
+ cc-connect service: $service_name
1259
+ Install command: $install_command
1260
+ Project skill clone: $skill_path
1261
+ Global skill fallback: $global_skill_path
1262
+ Env keys: DIREXIO_DOMAIN, DIREXIO_AGENT_TOKEN, DIREXIO_AGENT_ROOM_ID, DIREXIO_AGENT_NODE_ID
1263
+
1264
+ cc-connect will use Matrix Client-Server sync as @agent:<server> and is restricted to DIREXIO_AGENT_ROOM_ID.
1265
+ It talks directly to the Direxio homeserver for the agents room conversation.
1266
+ EOF
1267
+ _print_runtime_install_summary "$runtime" "$mode" "$cc_config" "$cc_binary" "$cc_agent" "$cc_agent_cmd" "$service_name"
1268
+ }
1269
+
1270
+ _print_mcp_guidance() {
1271
+ local runtime=$1 service_name=$2 server_name=$3 credentials_file=$4 config_dir=$5 codex_config=$6 openclaw_config=$7 hermes_config=$8 install_command=$9 doctor_command=${10}
1272
+ warn "Direxio MCP artifacts written for runtime=$runtime service=$service_name."
1273
+ cat >&2 <<EOF
1274
+ MCP server name: $server_name
1275
+ MCP config directory: $config_dir
1276
+ MCP credential file: $credentials_file
1277
+ MCP install command: $install_command
1278
+ MCP doctor command: $doctor_command
1279
+ Codex TOML snippet: $codex_config
1280
+ OpenClaw CLI setup: $openclaw_config
1281
+ Hermes JSON snippet: $hermes_config
1282
+
1283
+ These artifacts use direxio-mcp over stdio and point to the service-scoped DIREXIO_CREDENTIALS_FILE.
1284
+ For OpenClaw, use the CLI setup note so OpenClaw validates and writes mcp.servers itself; do not paste MCP JSON into openclaw.json.
1285
+ EOF
1286
+ }
1287
+
1288
+ run_phase() {
1289
+ phase_set S6_WIRE_LOCAL in_progress "writing credentials and cc-connect Matrix bridge config"
1290
+ local domain asurl token access_token password agent_room_id envfile runtime install_policy install_mode install_command
1291
+ local node_id service_dir node_cred workspace workspace_local service_id cc_agent cc_agent_cmd cc_agent_options_toml cc_runtime_dir cc_config cc_config_local cc_data cc_data_local cc_binary cc_session cc_source
1292
+ local mcp_dir mcp_dir_local mcp_server_name mcp_install_command mcp_doctor_command mcp_codex_config mcp_openclaw_config mcp_hermes_config mcp_json_config mcp_env_file mcp_readme
1293
+ local mcp_codex_config_local mcp_openclaw_config_local mcp_hermes_config_local mcp_json_config_local mcp_env_file_local mcp_readme_local node_cred_local
1294
+ local matrix_token matrix_user matrix_device matrix_homeserver
1295
+ local skill_path global_skill_path
1296
+ domain=$(state_get domain)
1297
+ asurl=$(state_get as_url)
1298
+ token=$(state_get agent_token)
1299
+ access_token=$(state_get access_token)
1300
+ password=$(state_get password)
1301
+ agent_room_id=$(state_get agent_room_id)
1302
+ [ -n "$domain" ] && [ -n "$asurl" ] && [ -n "$token" ] || { phase_set S6_WIRE_LOCAL failed "missing domain/as_url/token"; fail "state is missing domain/as_url/agent_token; complete S5 first."; }
1303
+ [ -n "$access_token" ] && [ -n "$password" ] || { phase_set S6_WIRE_LOCAL failed "missing bootstrap credentials"; fail "state is missing password/access_token; complete S5 first."; }
1304
+ _validate_real_agent_room_id "$agent_room_id"
1305
+
1306
+ runtime=$(_detect_agent_runtime)
1307
+ cc_agent=$(_cc_connect_agent_type "$runtime")
1308
+ cc_agent_cmd=$(_cc_connect_agent_command "$cc_agent" "$runtime")
1309
+ cc_agent_options_toml=$(_cc_connect_agent_options_toml "$runtime" "$cc_agent")
1310
+ node_id=$(_agent_node_id "$runtime" "$domain" "$agent_room_id")
1311
+ service_id=$(_direxio_service_id "${asurl:-$domain}")
1312
+ service_dir=$(_direxio_service_dir "${asurl:-$domain}")
1313
+ node_cred="$service_dir/credentials.json"
1314
+ envfile="$service_dir/env"
1315
+ workspace=$(_agent_workspace "$service_dir")
1316
+ admin_from="@owner:$domain"
1317
+ cc_runtime_dir=$(_cc_connect_runtime_dir "$service_dir")
1318
+ cc_config=$(_cc_connect_config_path "$service_dir")
1319
+ cc_config_local=$(_local_connect_path "$cc_config")
1320
+ cc_data="$cc_runtime_dir/data"
1321
+ cc_data_local=$(_local_connect_path "$cc_data")
1322
+ cc_binary=$(_cc_connect_binary_path "$service_dir")
1323
+ cc_session="$cc_runtime_dir/matrix-session.json"
1324
+ cc_source=$(_cc_connect_source_dir "$service_dir")
1325
+
1326
+ _write_credentials_file "$node_cred" "$domain" "$asurl" "$token" "$password" "$access_token" "$agent_room_id" "$node_id"
1327
+ ok "Wrote $node_cred (0600)."
1328
+ node_cred_local=$(_local_connect_path "$node_cred")
1329
+
1330
+ _write_mcp_config_artifacts "$service_id" "$service_dir" "$node_cred" "$node_id"
1331
+ mcp_dir=$(_mcp_runtime_dir "$service_dir")
1332
+ mcp_dir_local=$(_local_connect_path "$mcp_dir")
1333
+ mcp_server_name=$(_mcp_server_name "$service_id")
1334
+ mcp_codex_config=$(_mcp_codex_config_path "$service_dir")
1335
+ mcp_openclaw_config=$(_mcp_openclaw_config_path "$service_dir")
1336
+ mcp_hermes_config=$(_mcp_hermes_config_path "$service_dir")
1337
+ mcp_json_config=$(_mcp_json_config_path "$service_dir")
1338
+ mcp_env_file=$(_mcp_env_file_path "$service_dir")
1339
+ mcp_readme=$(_mcp_readme_path "$service_dir")
1340
+ mcp_codex_config_local=$(_local_connect_path "$mcp_codex_config")
1341
+ mcp_openclaw_config_local=$(_local_connect_path "$mcp_openclaw_config")
1342
+ mcp_hermes_config_local=$(_local_connect_path "$mcp_hermes_config")
1343
+ mcp_json_config_local=$(_local_connect_path "$mcp_json_config")
1344
+ mcp_env_file_local=$(_local_connect_path "$mcp_env_file")
1345
+ mcp_readme_local=$(_local_connect_path "$mcp_readme")
1346
+ mcp_install_command=$(_mcp_install_command)
1347
+ mcp_doctor_command=$(_mcp_doctor_command "$node_cred" "$node_id")
1348
+ ok "Wrote MCP config snippets under $mcp_dir."
1349
+
1350
+ if ! envfile=$(_persist_agent_env "$asurl" "$token" "$access_token" "$agent_room_id" "$envfile" "$node_id"); then
1351
+ phase_set S6_WIRE_LOCAL failed "persistent env write failed"
1352
+ fail "failed to persist Direxio cc-connect env vars."
1353
+ fi
1354
+
1355
+ mkdir -p "$workspace"
1356
+ mkdir -p "$cc_runtime_dir"
1357
+ if ! _create_cc_connect_matrix_session "$asurl" "$access_token" "DIREXIO_CC_CONNECT_${node_id}" "$cc_session"; then
1358
+ phase_set S6_WIRE_LOCAL failed "agent Matrix session creation failed"
1359
+ fail "failed to create cc-connect Matrix session via agent.matrix_session.create."
1360
+ fi
1361
+ matrix_token=$(jq -r '.access_token' "$cc_session")
1362
+ matrix_user=$(jq -r '.user_id' "$cc_session")
1363
+ matrix_device=$(jq -r '.device_id' "$cc_session")
1364
+ matrix_homeserver=$(jq -r '.homeserver' "$cc_session")
1365
+ if [ "$matrix_user" = "@owner:$domain" ]; then
1366
+ phase_set S6_WIRE_LOCAL failed "agent Matrix session returned owner user"
1367
+ fail "agent.matrix_session.create returned owner Matrix user; deploy a message-server build with agent Matrix session support."
1368
+ fi
1369
+ case "$matrix_user" in
1370
+ @agent:*) ;;
1371
+ *) warn "agent.matrix_session.create returned non-standard agent user_id: $matrix_user" ;;
1372
+ esac
1373
+ workspace_local=$(_local_connect_path "$workspace")
1374
+ _write_cc_connect_config "$cc_config" "$cc_data_local" "$node_id" "$cc_agent" "$workspace_local" "$matrix_homeserver" "$matrix_token" "$matrix_user" "$agent_room_id" "$admin_from" "$cc_agent_cmd" "$cc_agent_options_toml"
1375
+ ok "Wrote cc-connect Matrix config $cc_config (0600)."
1376
+
1377
+ state_set agent_env_file "$envfile" 2>/dev/null || true
1378
+ state_set agent_node_id "$node_id" 2>/dev/null || true
1379
+ state_set agent_service_id "$service_id" 2>/dev/null || true
1380
+ state_set agent_service_dir "$service_dir" 2>/dev/null || true
1381
+ state_set agent_credentials_file "$node_cred" 2>/dev/null || true
1382
+ state_set mcp_npm_package "$(_mcp_npm_package)" 2>/dev/null || true
1383
+ state_set mcp_command "$(_mcp_command)" 2>/dev/null || true
1384
+ state_set mcp_server_name "$mcp_server_name" 2>/dev/null || true
1385
+ state_set mcp_config_dir "$mcp_dir_local" 2>/dev/null || true
1386
+ state_set mcp_credentials_file "$node_cred_local" 2>/dev/null || true
1387
+ state_set mcp_codex_config "$mcp_codex_config_local" 2>/dev/null || true
1388
+ state_set mcp_openclaw_config "$mcp_openclaw_config_local" 2>/dev/null || true
1389
+ state_set mcp_hermes_config "$mcp_hermes_config_local" 2>/dev/null || true
1390
+ state_set mcp_json_config "$mcp_json_config_local" 2>/dev/null || true
1391
+ state_set mcp_env_file "$mcp_env_file_local" 2>/dev/null || true
1392
+ state_set mcp_readme "$mcp_readme_local" 2>/dev/null || true
1393
+ state_set mcp_install_command "$mcp_install_command" 2>/dev/null || true
1394
+ state_set mcp_doctor_command "$mcp_doctor_command" 2>/dev/null || true
1395
+ state_set agent_workspace "$workspace" 2>/dev/null || true
1396
+ state_set cc_connect_agent "$cc_agent" 2>/dev/null || true
1397
+ state_set cc_connect_agent_cmd "$cc_agent_cmd" 2>/dev/null || true
1398
+ if [ -n "$cc_agent_options_toml" ]; then
1399
+ state_set cc_connect_agent_options_toml_present "true" 2>/dev/null || true
1400
+ else
1401
+ state_set cc_connect_agent_options_toml_present "false" 2>/dev/null || true
1402
+ fi
1403
+ state_set cc_connect_npm_package "$(_cc_connect_npm_package)" 2>/dev/null || true
1404
+ state_set cc_connect_repo "$(_cc_connect_repo)" 2>/dev/null || true
1405
+ state_set cc_connect_ref "$(_cc_connect_ref)" 2>/dev/null || true
1406
+ state_set cc_connect_source_dir "$cc_source" 2>/dev/null || true
1407
+ state_set cc_connect_runtime_dir "$cc_runtime_dir" 2>/dev/null || true
1408
+ state_set cc_connect_config "$cc_config_local" 2>/dev/null || true
1409
+ state_set cc_connect_binary "$cc_binary" 2>/dev/null || true
1410
+ state_set cc_connect_data_dir "$cc_data_local" 2>/dev/null || true
1411
+ state_set cc_connect_admin_from "$admin_from" 2>/dev/null || true
1412
+ state_set cc_connect_matrix_session_file "$cc_session" 2>/dev/null || true
1413
+ state_set cc_connect_matrix_user "$matrix_user" 2>/dev/null || true
1414
+ state_set cc_connect_matrix_device "$matrix_device" 2>/dev/null || true
1415
+ state_set cc_connect_matrix_homeserver "$matrix_homeserver" 2>/dev/null || true
1416
+
1417
+ install_policy=$(_agent_install_policy)
1418
+ install_mode=$(_agent_install_mode "$runtime")
1419
+ install_command=$(_agent_install_command "$cc_binary" "$cc_config" "$service_id")
1420
+ skill_path=$(_agent_skill_install_path "$runtime")
1421
+ global_skill_path=$(_agent_global_skill_install_path "$runtime")
1422
+ state_set agent_runtime "$runtime" 2>/dev/null || true
1423
+ state_set agent_install_policy "$install_policy" 2>/dev/null || true
1424
+ state_set agent_install_mode "$install_mode" 2>/dev/null || true
1425
+ state_set agent_install_command "$install_command" 2>/dev/null || true
1426
+ state_set agent_skill_install_path "$skill_path" 2>/dev/null || true
1427
+ state_set agent_global_skill_install_path "$global_skill_path" 2>/dev/null || true
1428
+ state_set direxio_agent_bridge "cc-connect" 2>/dev/null || true
1429
+ _print_cc_connect_guidance "$runtime" "$asurl" "$node_cred" "$envfile" "$install_policy" "$install_mode" "$install_command" "$node_id" "$cc_config_local" "$cc_binary" "$cc_agent" "$cc_agent_cmd" "$service_id"
1430
+ _print_mcp_guidance "$runtime" "$service_id" "$mcp_server_name" "$node_cred_local" "$mcp_dir_local" "$mcp_codex_config_local" "$mcp_openclaw_config_local" "$mcp_hermes_config_local" "$mcp_install_command" "$mcp_doctor_command"
1431
+ _maybe_auto_install_agent "$install_policy" "$runtime" "$cc_agent" "$service_dir" "$cc_config" "$cc_binary" "$service_id"
1432
+
1433
+ phase_set S6_WIRE_LOCAL done "credentials.json written;node_id=$node_id;service_id=$service_id;env_file=$envfile;runtime=$runtime;install_policy=$install_policy;install_mode=$install_mode;cc_connect_config=$cc_config;mcp_config_dir=$mcp_dir;cc_connect_agent=$cc_agent"
1434
+ return 0
1435
+ }