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,25 @@
1
+ # Agent Runtime Notes
2
+
3
+ This skill is runtime-neutral. Claude, Codex/OpenAI, Gemini, Cursor, Copilot, OpenClaw, Hermes, and other shell-capable agents should use the same root entrypoint:
4
+
5
+ ```text
6
+ SKILL.md
7
+ ```
8
+
9
+ When an agent runtime supports skill metadata, point it at `SKILL.md` and use `scripts/orchestrate.sh` as the deployment command. Read `references/agent-targets.md` before installing this skill or wiring MCP/plugin access for a runtime. S6 writes current Direxio MCP/plugin variables and records the detected runtime plus target paths. After deployment, ask the user before installing or configuring the runtime-specific plugin and MCP service.
10
+
11
+ Recognition keywords:
12
+
13
+ - deploy P2P-IM Matrix
14
+ - resume P2P Matrix deployment
15
+ - verify P2P Matrix server
16
+ - destroy P2P Matrix AWS resources
17
+ - wire Direxio MCP/plugin
18
+ - refresh P2P agent token
19
+
20
+ Required capabilities:
21
+
22
+ - Read local files.
23
+ - Run POSIX shell commands.
24
+ - Use `aws`, `jq`, `ssh`, `scp`, and `curl` after the user approves any missing installs.
25
+ - Preserve secrets outside the repository.
@@ -0,0 +1,12 @@
1
+ display_name: Direxio Deployer
2
+ short_description: Deploy, resume, verify, destroy, and wire Direxio MCP/plugin access for a production P2P-IM Matrix server on AWS.
3
+ default_prompt: Deploy a P2P-IM Matrix server using my production domain, following SKILL.md and scripts/orchestrate.sh.
4
+ entrypoint: ../SKILL.md
5
+ runtime_notes:
6
+ - Read SKILL.md before running deployment commands.
7
+ - When installing or updating this skill itself, read references/agent-targets.md and use the runtime-specific project-local Git clone path before any global fallback.
8
+ - Use scripts/orchestrate.sh from the repository root.
9
+ - Use scripts/destroy.sh for teardown.
10
+ - S6 writes DIREXIO_DOMAIN, DIREXIO_AGENT_TOKEN, and DIREXIO_AGENT_ROOM_ID, then records runtime-specific skill and MCP/config targets for direxio-mcp and P2P-IM/direxio-agent-plugins.
11
+ - Ask before mutating the current agent runtime's plugin or MCP configuration.
12
+ - The instructions are compatible with Claude, Codex/OpenAI, Gemini, Cursor, Copilot, OpenClaw, Hermes, and other shell-capable agent runtimes.
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const packageJson = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
10
+ const packageName = packageJson.name || "direxio-deployer";
11
+ const packageVersion = packageJson.version || "0.0.0";
12
+
13
+ const skillFiles = [
14
+ "AGENTS.md",
15
+ "LICENSE",
16
+ "README.md",
17
+ "README_zh.md",
18
+ "SKILL.md",
19
+ "agents",
20
+ "bin",
21
+ "package.json",
22
+ "references",
23
+ "scripts",
24
+ "tests"
25
+ ];
26
+
27
+ const projectTargets = {
28
+ acp: [".agents", "skills", "direxio-deployer"],
29
+ antigravity: [".antigravity", "skills", "direxio-deployer"],
30
+ claudecode: [".claude", "skills", "direxio-deployer"],
31
+ codex: [".codex", "skills", "direxio-deployer"],
32
+ copilot: [".github", "copilot", "skills", "direxio-deployer"],
33
+ cursor: [".cursor", "skills", "direxio-deployer"],
34
+ devin: [".devin", "skills", "direxio-deployer"],
35
+ gemini: [".gemini", "skills", "direxio-deployer"],
36
+ hermes: [".hermes", "skills", "direxio-deployer"],
37
+ iflow: [".iflow", "skills", "direxio-deployer"],
38
+ kimi: [".kimi", "skills", "direxio-deployer"],
39
+ opencode: [".opencode", "skills", "direxio-deployer"],
40
+ openclaw: [".openclaw", "skills", "direxio-deployer"],
41
+ pi: [".pi", "agent", "skills", "direxio-deployer"],
42
+ qoder: [".qoder", "skills", "direxio-deployer"],
43
+ reasonix: [".reasonix", "skills", "direxio-deployer"],
44
+ tmux: [".agent", "skills", "direxio-deployer"],
45
+ generic: [".agent", "skills", "direxio-deployer"],
46
+ unknown: [".agent", "skills", "direxio-deployer"]
47
+ };
48
+
49
+ const globalTargets = {
50
+ acp: { env: null, defaultSegments: [".agents", "skills", "direxio-deployer"] },
51
+ antigravity: { env: "ANTIGRAVITY_HOME", defaultSegments: [".antigravity", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
52
+ claudecode: { env: "CLAUDE_HOME", defaultSegments: [".claude", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
53
+ codex: { env: "CODEX_HOME", defaultSegments: [".codex", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
54
+ copilot: { env: null, defaultSegments: [".github", "copilot", "skills", "direxio-deployer"] },
55
+ cursor: { env: "CURSOR_HOME", defaultSegments: [".cursor", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
56
+ devin: { env: "DEVIN_HOME", defaultSegments: [".devin", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
57
+ gemini: { env: "GEMINI_HOME", defaultSegments: [".gemini", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
58
+ hermes: { env: "HERMES_HOME", defaultSegments: [".hermes", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
59
+ iflow: { env: "IFLOW_HOME", defaultSegments: [".iflow", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
60
+ kimi: { env: "KIMI_HOME", defaultSegments: [".kimi", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
61
+ opencode: { env: "OPENCODE_HOME", defaultSegments: [".opencode", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
62
+ openclaw: { env: "OPENCLAW_HOME", defaultSegments: [".openclaw", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
63
+ pi: { env: "PI_CODING_AGENT_DIR", defaultSegments: [".pi", "agent", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
64
+ qoder: { env: "QODER_HOME", defaultSegments: [".qoder", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
65
+ reasonix: { env: "REASONIX_HOME", defaultSegments: [".reasonix", "skills", "direxio-deployer"], envSuffix: ["skills", "direxio-deployer"] },
66
+ tmux: { env: null, defaultSegments: [".agent", "skills", "direxio-deployer"] },
67
+ generic: { env: null, defaultSegments: [".agent", "skills", "direxio-deployer"] },
68
+ unknown: { env: null, defaultSegments: [".agent", "skills", "direxio-deployer"] }
69
+ };
70
+
71
+ const aliases = {
72
+ "claude": "claudecode",
73
+ "claude-code": "claudecode",
74
+ "open-code": "opencode",
75
+ "qodercli": "qoder",
76
+ "agy": "antigravity"
77
+ };
78
+
79
+ main();
80
+
81
+ function main() {
82
+ const [area, command, ...rawArgs] = process.argv.slice(2);
83
+ if (!area || area === "--help" || area === "-h") usage(0);
84
+ if (area === "--version" || area === "-v") {
85
+ console.log(packageVersion);
86
+ return;
87
+ }
88
+ if (area !== "skill") usage(1);
89
+ if (!["install", "update", "refresh"].includes(command)) usage(1);
90
+
91
+ const options = parseArgs(rawArgs);
92
+ const result = runSkillCommand(command, options);
93
+ console.log(JSON.stringify(result, null, 2));
94
+ }
95
+
96
+ function usage(exitCode) {
97
+ const output = `Usage:
98
+ direxio-deployer skill install --agent <runtime> [--scope project|global] [--project <path>]
99
+ direxio-deployer skill update --agent <runtime> [--scope project|global] [--project <path>]
100
+ direxio-deployer skill refresh --agent <runtime> [--scope project|global] [--project <path>]
101
+
102
+ Options:
103
+ --agent <runtime> Target agent runtime. Default: codex
104
+ --scope <scope> project or global. Default: project
105
+ --project <path> Project root for project installs. Default: current directory
106
+ --target <path> Explicit install target override
107
+ --home <path> Home directory override for global installs
108
+ --dry-run Resolve and print without writing
109
+ --force Replace an unmanaged existing target
110
+ `;
111
+ const stream = exitCode === 0 ? process.stdout : process.stderr;
112
+ stream.write(output);
113
+ process.exit(exitCode);
114
+ }
115
+
116
+ function parseArgs(args) {
117
+ const options = {
118
+ agent: "codex",
119
+ scope: "project",
120
+ project: process.cwd(),
121
+ home: homedir(),
122
+ target: null,
123
+ dryRun: false,
124
+ force: false,
125
+ skipNpmCheck: process.env.DIREXIO_DEPLOYER_REFRESH_CHILD === "1"
126
+ };
127
+
128
+ for (let i = 0; i < args.length; i += 1) {
129
+ const arg = args[i];
130
+ switch (arg) {
131
+ case "--agent":
132
+ options.agent = requiredValue(args, ++i, arg);
133
+ break;
134
+ case "--scope":
135
+ options.scope = requiredValue(args, ++i, arg);
136
+ break;
137
+ case "--project":
138
+ options.project = requiredValue(args, ++i, arg);
139
+ break;
140
+ case "--home":
141
+ options.home = requiredValue(args, ++i, arg);
142
+ break;
143
+ case "--target":
144
+ options.target = requiredValue(args, ++i, arg);
145
+ break;
146
+ case "--dry-run":
147
+ options.dryRun = true;
148
+ break;
149
+ case "--force":
150
+ options.force = true;
151
+ break;
152
+ case "--skip-npm-check":
153
+ options.skipNpmCheck = true;
154
+ break;
155
+ default:
156
+ throwUserError(`unknown option: ${arg}`);
157
+ }
158
+ }
159
+
160
+ return options;
161
+ }
162
+
163
+ function requiredValue(args, index, flag) {
164
+ const value = args[index];
165
+ if (!value || value.startsWith("--")) throwUserError(`${flag} requires a value`);
166
+ return value;
167
+ }
168
+
169
+ function runSkillCommand(command, options) {
170
+ const agent = normalizeAgent(options.agent);
171
+ const scope = normalizeScope(options.scope);
172
+ const target = options.target
173
+ ? path.resolve(options.target)
174
+ : resolveTarget({ agent, scope, project: options.project, home: options.home });
175
+
176
+ const base = {
177
+ command,
178
+ package: packageName,
179
+ version: packageVersion,
180
+ agent,
181
+ scope,
182
+ target,
183
+ dryRun: options.dryRun
184
+ };
185
+
186
+ if (command === "refresh") {
187
+ const freshness = checkFreshness(options);
188
+ if (options.dryRun) return { ...base, freshness };
189
+ if (freshness.updateCommand) runNpmUpdateAndChildInstall({ agent, scope, target, options, freshness });
190
+ return { ...base, freshness, installed: installSkill({ agent, scope, target, dryRun: false, force: options.force }) };
191
+ }
192
+
193
+ return { ...base, installed: installSkill({ agent, scope, target, dryRun: options.dryRun, force: options.force }) };
194
+ }
195
+
196
+ function normalizeAgent(agent) {
197
+ const normalized = aliases[String(agent).toLowerCase()] || String(agent).toLowerCase();
198
+ if (!projectTargets[normalized]) {
199
+ const supported = Object.keys(projectTargets).filter((name) => name !== "unknown").join(", ");
200
+ throwUserError(`agent must be one of: ${supported}`);
201
+ }
202
+ return normalized;
203
+ }
204
+
205
+ function normalizeScope(scope) {
206
+ if (scope !== "project" && scope !== "global") throwUserError("scope must be project or global");
207
+ return scope;
208
+ }
209
+
210
+ function resolveTarget({ agent, scope, project, home }) {
211
+ if (scope === "project") return path.resolve(project, ...projectTargets[agent]);
212
+ const entry = globalTargets[agent];
213
+ const configuredHome = entry.env ? process.env[entry.env] : null;
214
+ if (configuredHome) {
215
+ const suffix = entry.envSuffix || entry.defaultSegments;
216
+ return path.resolve(configuredHome, ...suffix);
217
+ }
218
+ return path.resolve(home, ...entry.defaultSegments);
219
+ }
220
+
221
+ function installSkill({ agent, scope, target, dryRun, force }) {
222
+ if (dryRun) return { action: "would-install", fileCount: skillFiles.length };
223
+
224
+ if (existsSync(target)) {
225
+ const entries = readdirSync(target);
226
+ const managed = isManagedTarget(target);
227
+ if (entries.length > 0 && !managed && !force) {
228
+ throwUserError(`refusing to overwrite unmanaged target: ${target}. Re-run with --force if this is intentional.`);
229
+ }
230
+ rmSync(target, { recursive: true, force: true });
231
+ }
232
+
233
+ mkdirSync(target, { recursive: true });
234
+ for (const relative of skillFiles) {
235
+ const source = path.join(packageRoot, relative);
236
+ if (!existsSync(source)) continue;
237
+ copyRecursive(source, path.join(target, relative));
238
+ }
239
+
240
+ const manifest = {
241
+ package: packageName,
242
+ version: packageVersion,
243
+ agent,
244
+ scope,
245
+ source: packageRoot,
246
+ installedAt: new Date().toISOString()
247
+ };
248
+ writeFileSync(path.join(target, ".direxio-skill-install.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
249
+ return { action: "installed", manifest: path.join(target, ".direxio-skill-install.json") };
250
+ }
251
+
252
+ function isManagedTarget(target) {
253
+ const manifestPath = path.join(target, ".direxio-skill-install.json");
254
+ if (!existsSync(manifestPath)) return false;
255
+ try {
256
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
257
+ return manifest.package === packageName;
258
+ } catch {
259
+ return false;
260
+ }
261
+ }
262
+
263
+ function copyRecursive(source, destination) {
264
+ const stat = statSync(source);
265
+ if (stat.isDirectory()) {
266
+ mkdirSync(destination, { recursive: true });
267
+ for (const entry of readdirSync(source)) {
268
+ if (shouldSkip(entry)) continue;
269
+ copyRecursive(path.join(source, entry), path.join(destination, entry));
270
+ }
271
+ return;
272
+ }
273
+ mkdirSync(path.dirname(destination), { recursive: true });
274
+ copyFileSync(source, destination);
275
+ }
276
+
277
+ function shouldSkip(entry) {
278
+ return new Set([
279
+ ".cc-connect",
280
+ ".codegraph",
281
+ ".direxio-skill-install.json",
282
+ ".git",
283
+ ".idea",
284
+ "node_modules"
285
+ ]).has(entry);
286
+ }
287
+
288
+ function checkFreshness(options) {
289
+ if (options.skipNpmCheck) return { status: "skipped", reason: "child refresh install" };
290
+ if (options.dryRun) {
291
+ return {
292
+ status: "dry-run",
293
+ currentVersion: packageVersion,
294
+ latestVersion: null,
295
+ updateCommand: null
296
+ };
297
+ }
298
+ const npmView = spawnSync("npm", ["view", `${packageName}@latest`, "version"], {
299
+ encoding: "utf8",
300
+ shell: process.platform === "win32"
301
+ });
302
+ if (npmView.status !== 0) {
303
+ return {
304
+ status: "unavailable",
305
+ currentVersion: packageVersion,
306
+ latestVersion: null,
307
+ reason: (npmView.stderr || npmView.stdout || "npm view failed").trim()
308
+ };
309
+ }
310
+ const latestVersion = npmView.stdout.trim();
311
+ if (!isNewerVersion(latestVersion, packageVersion)) {
312
+ return { status: "current", currentVersion: packageVersion, latestVersion, updateCommand: null };
313
+ }
314
+ return {
315
+ status: "update-available",
316
+ currentVersion: packageVersion,
317
+ latestVersion,
318
+ updateCommand: `npm install -g ${packageName}@latest`
319
+ };
320
+ }
321
+
322
+ function runNpmUpdateAndChildInstall({ agent, scope, target, options, freshness }) {
323
+ const installResult = spawnSync("npm", ["install", "-g", `${packageName}@latest`], {
324
+ stdio: "inherit",
325
+ shell: process.platform === "win32"
326
+ });
327
+ if (installResult.status !== 0) {
328
+ throwUserError(`npm update failed for ${packageName}@latest`);
329
+ }
330
+
331
+ const childArgs = [
332
+ "skill",
333
+ "update",
334
+ "--agent",
335
+ agent,
336
+ "--scope",
337
+ scope,
338
+ "--target",
339
+ target,
340
+ "--skip-npm-check"
341
+ ];
342
+ if (options.force) childArgs.push("--force");
343
+ const child = spawnSync("direxio-deployer", childArgs, {
344
+ env: { ...process.env, DIREXIO_DEPLOYER_REFRESH_CHILD: "1" },
345
+ stdio: "inherit",
346
+ shell: process.platform === "win32"
347
+ });
348
+ if (child.status === 0) process.exit(0);
349
+ freshness.childRefresh = "failed; falling back to current package copy";
350
+ }
351
+
352
+ function isNewerVersion(candidate, current) {
353
+ const left = parseVersion(candidate);
354
+ const right = parseVersion(current);
355
+ for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
356
+ const a = left[i] || 0;
357
+ const b = right[i] || 0;
358
+ if (a > b) return true;
359
+ if (a < b) return false;
360
+ }
361
+ return false;
362
+ }
363
+
364
+ function parseVersion(version) {
365
+ return String(version)
366
+ .replace(/^v/, "")
367
+ .split(/[.-]/)
368
+ .map((part) => Number.parseInt(part, 10))
369
+ .filter((part) => Number.isFinite(part));
370
+ }
371
+
372
+ function throwUserError(message) {
373
+ console.error(message);
374
+ process.exit(1);
375
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "direxio-deployer",
3
+ "version": "0.1.0",
4
+ "description": "Versioned Direxio deployer agent skill and portable deployment orchestration tools.",
5
+ "type": "module",
6
+ "bin": {
7
+ "direxio-deployer": "bin/direxio-deployer.mjs"
8
+ },
9
+ "files": [
10
+ "AGENTS.md",
11
+ "LICENSE",
12
+ "README.md",
13
+ "README_zh.md",
14
+ "SKILL.md",
15
+ "agents/",
16
+ "bin/",
17
+ "references/",
18
+ "scripts/",
19
+ "tests/"
20
+ ],
21
+ "scripts": {
22
+ "test": "bash tests/npm_skill_distribution_test.sh && bash tests/skill_structure_test.sh && bash tests/s6_wire_local_test.sh && bash tests/render_userdata_remote_nodes_test.sh"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "license": "MIT"
28
+ }
@@ -0,0 +1,128 @@
1
+ # Agent Targets
2
+
3
+ Use this file when installing or updating this skill and when reviewing S6 local bridge output. Direxio no longer ships extra chat-platform adapters from this deployer; the only post-deploy local conversation bridge is `direxio-connect`. MCP-capable hosts can also use the generated `direxio-mcp` snippets.
4
+
5
+ ## Npm Skill Installation
6
+
7
+ Prefer a project-local npm-managed install when a project or workspace exists. Install the versioned package, then let the CLI copy the skill bundle into the runtime-specific target:
8
+
9
+ POSIX shells:
10
+
11
+ ```bash
12
+ npm install -g direxio-deployer@latest
13
+ direxio-deployer skill install --agent codex --scope project --project PROJECT_ROOT
14
+ direxio-deployer skill update --agent codex --scope project --project PROJECT_ROOT
15
+ ```
16
+
17
+ Windows PowerShell:
18
+
19
+ ```powershell
20
+ npm install -g direxio-deployer@latest
21
+ direxio-deployer skill install --agent codex --scope project --project PROJECT_ROOT
22
+ direxio-deployer skill update --agent codex --scope project --project PROJECT_ROOT
23
+ ```
24
+
25
+ Use `--scope global` only when the user explicitly asks for a global install or no project target exists. Use a Git clone only for deployer development or local patching, not as the normal end-user installation path. The npm installer writes `.direxio-skill-install.json` and refuses to overwrite unmanaged existing target directories unless `--force` is passed.
26
+
27
+ | Runtime | Project-local skill target | Global fallback only when explicitly requested or no project exists |
28
+ | --- | --- | --- |
29
+ | Codex | `PROJECT_ROOT/.codex/skills/direxio-deployer` | `${CODEX_HOME:-$HOME/.codex}/skills/direxio-deployer` |
30
+ | Claude Code | `PROJECT_ROOT/.claude/skills/direxio-deployer` | `${CLAUDE_HOME:-$HOME/.claude}/skills/direxio-deployer` |
31
+ | Gemini | `PROJECT_ROOT/.gemini/skills/direxio-deployer` | `${GEMINI_HOME:-$HOME/.gemini}/skills/direxio-deployer` |
32
+ | Cursor | `PROJECT_ROOT/.cursor/skills/direxio-deployer` | `${CURSOR_HOME:-$HOME/.cursor}/skills/direxio-deployer` |
33
+ | GitHub Copilot | `PROJECT_ROOT/.github/copilot/skills/direxio-deployer` | `$HOME/.github/copilot/skills/direxio-deployer` |
34
+ | OpenClaw | `PROJECT_ROOT/.openclaw/skills/direxio-deployer` | `${OPENCLAW_HOME:-$HOME/.openclaw}/skills/direxio-deployer` |
35
+ | Hermes | `PROJECT_ROOT/.hermes/skills/direxio-deployer` | `${HERMES_HOME:-$HOME/.hermes}/skills/direxio-deployer` |
36
+ | ACP-compatible | `PROJECT_ROOT/.agents/skills/direxio-deployer` | `$HOME/.agents/skills/direxio-deployer` |
37
+ | Antigravity | `PROJECT_ROOT/.antigravity/skills/direxio-deployer` | `${ANTIGRAVITY_HOME:-$HOME/.antigravity}/skills/direxio-deployer` |
38
+ | Devin | `PROJECT_ROOT/.devin/skills/direxio-deployer` | `${DEVIN_HOME:-$HOME/.devin}/skills/direxio-deployer` |
39
+ | iFlow | `PROJECT_ROOT/.iflow/skills/direxio-deployer` | `${IFLOW_HOME:-$HOME/.iflow}/skills/direxio-deployer` |
40
+ | Kimi | `PROJECT_ROOT/.kimi/skills/direxio-deployer` | `${KIMI_HOME:-$HOME/.kimi}/skills/direxio-deployer` |
41
+ | OpenCode | `PROJECT_ROOT/.opencode/skills/direxio-deployer` | `${OPENCODE_HOME:-$HOME/.opencode}/skills/direxio-deployer` |
42
+ | Pi | `PROJECT_ROOT/.pi/agent/skills/direxio-deployer` | `${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/skills/direxio-deployer` |
43
+ | Qoder | `PROJECT_ROOT/.qoder/skills/direxio-deployer` | `${QODER_HOME:-$HOME/.qoder}/skills/direxio-deployer` |
44
+ | Reasonix | `PROJECT_ROOT/.reasonix/skills/direxio-deployer` | `${REASONIX_HOME:-$HOME/.reasonix}/skills/direxio-deployer` |
45
+ | tmux | `PROJECT_ROOT/.agent/skills/direxio-deployer` | `$HOME/.agent/skills/direxio-deployer` |
46
+ | Generic or unknown | `PROJECT_ROOT/.agent/skills/direxio-deployer` | `$HOME/.agent/skills/direxio-deployer` |
47
+
48
+ ## Direxio Connect Target
49
+
50
+ The bridge agent type is selected independently from the host operating system. `DIREXIO_CC_CONNECT_AGENT` may be any agent supported by connent/connect:
51
+
52
+ ```text
53
+ acp antigravity claudecode codex copilot cursor devin gemini iflow kimi opencode pi qoder reasonix tmux
54
+ ```
55
+
56
+ `DIREXIO_AGENT_PLATFORM=auto` is a convenience detector. If it detects OpenClaw or Hermes, S6 wires `direxio-connect` through the generic `acp` agent. OpenClaw writes `cmd = "openclaw"` and requires the current agent/operator to provide the real Gateway URL, token-file, and ACP session. Hermes writes `cmd = "direxio-connect"` with `args = ["hermes-acp-adapter", "--", "hermes", "acp"]` so the Direxio ACP compatibility layer can buffer and clean Hermes output before it reaches the Matrix room. OpenClaw and Hermes are not native connent/connect agent types. If detection is ambiguous or the detected host runtime should use a different connect backend, set `DIREXIO_CC_CONNECT_AGENT` explicitly.
57
+
58
+ S6 writes service-specific files to `~/.direxio/nodes/<service_id>/`, where `service_id` is derived from the deployed domain:
59
+
60
+ ```text
61
+ credentials.json
62
+ env
63
+ cc-connect/config.toml
64
+ cc-connect/data/
65
+ cc-connect/matrix-session.json
66
+ mcp/codex.toml
67
+ mcp/openclaw.md
68
+ mcp/openclaw-server.json
69
+ mcp/hermes.mcp.json
70
+ mcp/mcp-servers.json
71
+ ```
72
+
73
+ The generated `cc-connect/config.toml` contains exactly one Matrix platform and includes:
74
+
75
+ ```toml
76
+ [speech]
77
+ enabled = true
78
+ provider = "openai"
79
+ language = "zh"
80
+
81
+ [speech.openai]
82
+ api_key = "<optional speech-to-text key>"
83
+
84
+ [[projects]]
85
+ name = "<agent-node-id>"
86
+ admin_from = "@owner:<server>"
87
+
88
+ [projects.agent.options]
89
+ work_dir = "<workspace>"
90
+ cmd = "<optional explicit agent executable path>"
91
+
92
+ [[projects.platforms]]
93
+ type = "matrix"
94
+
95
+ [projects.platforms.options]
96
+ homeserver = "https://<domain>"
97
+ user_id = "@agent:<server>"
98
+ room_id = "!<real-agent-room>:<server>"
99
+ share_session_in_channel = true
100
+ group_reply_all = true
101
+ auto_join = false
102
+ auto_verify = false
103
+ ```
104
+
105
+ The `[speech]` block is present only when S6 finds a speech-to-text API key from `DIREXIO_SPEECH_*` or supported provider environment variables. Voice input is not available without STT credentials.
106
+
107
+ `admin_from` must stay at the `[[projects]]` level. `direxio-connect` uses the full Matrix sender ID, so S6 writes `@owner:<server>`; privileged commands such as `/dir`, `/shell`, `/show`, `/restart`, and `/upgrade` are blocked for other room members. `/dir reset` returns to the generated `work_dir` and clears the runtime override stored under `cc-connect/data/projects/<project>.state.json`.
108
+
109
+ ## MCP Targets
110
+
111
+ S6 writes MCP artifacts for Codex, OpenClaw, and Hermes under `~/.direxio/nodes/<service_id>/mcp/`. These artifacts use `direxio-mcp` from `direxio-mcp@latest` by default and point to the service credential file through `DIREXIO_CREDENTIALS_FILE`.
112
+
113
+ ```bash
114
+ npm install -g direxio-mcp@latest
115
+ DIREXIO_CREDENTIALS_FILE=~/.direxio/nodes/<service_id>/credentials.json direxio-mcp doctor --json
116
+ ```
117
+
118
+ Use `mcp/codex.toml` for Codex and `mcp/hermes.mcp.json` for Hermes. For OpenClaw, use `mcp/openclaw.md`; it runs `openclaw mcp set` with `mcp/openclaw-server.json` so OpenClaw validates and writes its own `mcp.servers` config. Do not paste MCP JSON into `~/.openclaw/openclaw.json`. The deployer writes local artifacts only; it does not mutate each host application's global MCP config.
119
+
120
+ ## Installation Policy
121
+
122
+ - `DIREXIO_AGENT_INSTALL=skip`: write credentials/env and cc-connect config only.
123
+ - `DIREXIO_AGENT_INSTALL=recommend`: write files, record state, and print the install command.
124
+ - `DIREXIO_AGENT_INSTALL=auto`: run `npm install -g direxio-connent@latest` and then `direxio-connect daemon install --config ~/.direxio/nodes/<service_id>/cc-connect/config.toml --service-name <service_id> --force`. S6 records this as installed only after `direxio-connect daemon status --service-name <service_id>` reports `Status: Running` and recent daemon logs do not show ACP session initialization failure; otherwise it records `agent_install_status=install_failed`.
125
+
126
+ Prefer `DIREXIO_CC_CONNECT_AGENT=<agent>` to choose the local agent that `direxio-connect` should run. Keep `DIREXIO_AGENT_PLATFORM=<runtime>` for auto-detection overrides and legacy host-runtime naming. Use `DIREXIO_AGENT_INSTALL_MODE=cc-connect` only when overriding the default `recommended` mapping explicitly.
127
+ Use `DIREXIO_CC_CONNECT_AGENT_OPTIONS_TOML` for agent-specific options that cannot be represented by `work_dir` or `cmd`; for example `reasonix` requires `serve_url`, `tmux` requires `session`, and generic `acp` requires a command when `DIREXIO_CC_CONNECT_AGENT_CMD` is not enough.
128
+ For OpenClaw Gateway ACP, complete OpenClaw pairing first, then set `DIREXIO_OPENCLAW_ACP_URL`, `DIREXIO_OPENCLAW_ACP_TOKEN_FILE`, and `DIREXIO_OPENCLAW_ACP_SESSION` from the current OpenClaw runtime. S6 writes `["acp", "--url", <url>, "--token-file", <local path>, "--session", <session>]` and converts the token-file with `DIREXIO_LOCAL_PATH_STYLE`. `DIREXIO_OPENCLAW_ACP_ARGS_TOML` replaces the OpenClaw ACP args array only when the runtime needs a fully custom argument list. `DIREXIO_HERMES_ACP_ARGS_TOML` supplies the child Hermes args and keeps the Direxio adapter prefix.
@@ -0,0 +1,44 @@
1
+ # 架构原理
2
+
3
+ 本部署器已经适配新版 Direxio message-server: AS 业务合并进 Dendrite 单体后端,不再部署独立 `asd` 服务。
4
+
5
+ ## 服务拓扑
6
+
7
+ ```text
8
+ 公网 443/80 -> Caddy
9
+ ├─ /_matrix/*, /_dendrite/*, /_synapse/* -> message-server:8008
10
+ ├─ /_p2p/* -> message-server:8008
11
+ ├─ /.well-known/matrix/* -> Caddy 静态响应
12
+ ├─ /.well-known/portal/* -> /opt/p2p/wellknown 静态文件
13
+ └─ /healthz -> /_p2p/health
14
+
15
+ message-server -> PostgreSQL 18
16
+ coturn -> TURN 3478 + 49160-49200/udp
17
+ ```
18
+
19
+ - **message-server**: `direxio/message-server:latest`,同时承载 Matrix homeserver 和 `/_p2p/query`/`/_p2p/command`。
20
+ - **PostgreSQL 18**: Matrix 与 P2P 业务表共库持久化,compose 使用 `/var/lib/postgresql`。
21
+ - **Caddy**: 唯一 HTTP/TLS 入口,自动签发 Let's Encrypt。
22
+ - **coturn**: WebRTC TURN relay,Direxio message-server 通过 shared-secret 动态签发 TURN 凭证。
23
+
24
+ ## 启动顺序
25
+
26
+ 1. `postgres` healthy。
27
+ 2. `message-init` 生成 `/etc/direxio-message-server/message-server.yaml` 和 signing key,并写入 TURN 配置。
28
+ 3. `message-server` 启动,加载 Matrix + P2P 业务,读取 `P2P_PORTAL_PASSWORD` 和 `P2P_PORTAL_CREDENTIALS_FILE`。
29
+ 4. `init-tokens.sh` 调用 `portal.bootstrap`,从容器复制凭据到宿主 `/opt/p2p/bootstrap.json`。如果最新服务端没有写入 `agent_room_id`,脚本会通过 Matrix Client API 创建真实 agent room、邀请并加入 `@agent:<server>`,再把 `agent_room_id` 回写到宿主和容器凭据文件。
30
+ 5. `init-tokens.sh` 生成 `/opt/p2p/wellknown/owner.json`。
31
+ 6. `caddy` 对外服务 Matrix、P2P API 和 well-known。
32
+
33
+ ## 凭据模型
34
+
35
+ `/opt/p2p/bootstrap.json` 会包含:
36
+
37
+ - `password`: 后端字段名;对用户展示时是八位 App 初始化码。
38
+ - `access_token`: 当前用户的统一 bearer token,可用于 Matrix `/_matrix/client/*` 和需要用户身份的 P2P 调用。
39
+ - `agent_token`: 本地服务凭据中的 agent bearer token;`direxio-connect` 对话桥接使用 S6 创建的 `@agent:<server>` Matrix session。
40
+ - `agent_room_id`: 真实 Matrix 房间 ID。部署脚本拒绝旧式 `!agent:<domain>` 伪房间。
41
+
42
+ ## 域名模型
43
+
44
+ Matrix `server_name` 必须是长期真实域名。部署后更换 `DOMAIN` 等同创建新的 homeserver 身份,不要保留旧 PostgreSQL/message-server 数据卷后直接改域名。