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.
- package/AGENTS.md +92 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/README_zh.md +218 -0
- package/SKILL.md +722 -0
- package/agents/README.md +25 -0
- package/agents/openai.yaml +12 -0
- package/bin/direxio-deployer.mjs +375 -0
- package/package.json +28 -0
- package/references/agent-targets.md +128 -0
- package/references/architecture.md +44 -0
- package/references/bug-history.md +78 -0
- package/references/deployment-lessons.md +218 -0
- package/references/deployment-optimization-audit.md +317 -0
- package/references/deployment-workflow.md +341 -0
- package/references/iam-policy.json +52 -0
- package/references/runtime-wiring.md +209 -0
- package/references/state-machine.md +46 -0
- package/references/token-refresh.md +81 -0
- package/references/tooling.md +106 -0
- package/references/troubleshooting.md +26 -0
- package/references/user-journey.md +75 -0
- package/references/verification-recovery.md +84 -0
- package/references/voip-turn-runbook.md +154 -0
- package/references/windows-deployment-notes.md +119 -0
- package/scripts/aws-credentials.sh +195 -0
- package/scripts/cloud-init/Caddyfile +48 -0
- package/scripts/cloud-init/docker-compose.yml +125 -0
- package/scripts/cloud-init/init-tokens.sh +238 -0
- package/scripts/cloud-init/user-data.yaml +40 -0
- package/scripts/destroy.ps1 +77 -0
- package/scripts/destroy.sh +589 -0
- package/scripts/lib/aws.sh +73 -0
- package/scripts/lib/domain.sh +175 -0
- package/scripts/lib/operation_report.sh +240 -0
- package/scripts/lib/ops.sh +230 -0
- package/scripts/lib/paths.sh +35 -0
- package/scripts/lib/state.sh +137 -0
- package/scripts/mcp-tools-list.mjs +95 -0
- package/scripts/orchestrate.ps1 +112 -0
- package/scripts/orchestrate.sh +1126 -0
- package/scripts/phases/s0_prereq_aws.sh +39 -0
- package/scripts/phases/s1_preflight.sh +72 -0
- package/scripts/phases/s2_domain.sh +103 -0
- package/scripts/phases/s3_provision.sh +421 -0
- package/scripts/phases/s4_bootstrap_stack.sh +38 -0
- package/scripts/phases/s5_init_tokens.sh +118 -0
- package/scripts/phases/s6_wire_local.sh +1435 -0
- package/scripts/phases/s7_verify_e2e.sh +136 -0
- package/scripts/pricing-estimate.sh +256 -0
- package/scripts/render/render-userdata.sh +86 -0
- package/scripts/reset-app-data.sh +40 -0
- package/scripts/update.sh +30 -0
- package/tests/aws_credentials_test.sh +139 -0
- package/tests/connect_daemon_runtime_check_test.sh +120 -0
- package/tests/default_paths_test.sh +58 -0
- package/tests/destroy_local_bridge_test.sh +154 -0
- package/tests/destroy_root_identity_test.sh +91 -0
- package/tests/destroy_route53_zone_test.sh +80 -0
- package/tests/domain_authoritative_dns_test.sh +49 -0
- package/tests/mcp_doctor_runtime_check_test.sh +86 -0
- package/tests/mcp_smoke_runtime_check_test.sh +121 -0
- package/tests/mcp_tools_runtime_check_test.sh +123 -0
- package/tests/npm_skill_distribution_test.sh +95 -0
- package/tests/operation_report_test.sh +258 -0
- package/tests/orchestrate_status_recovery_test.sh +91 -0
- package/tests/phase_timeout_test.sh +88 -0
- package/tests/pricing_estimate_test.sh +159 -0
- package/tests/render_userdata_remote_nodes_test.sh +40 -0
- package/tests/root_volume_tracking_test.sh +41 -0
- package/tests/route53_overwrite_guard_test.sh +86 -0
- package/tests/route53_zone_auto_create_test.sh +66 -0
- package/tests/runtime_summary_check_test.sh +203 -0
- package/tests/s6_wire_local_test.sh +405 -0
- package/tests/skill_structure_test.sh +298 -0
- package/tests/update_reset_ops_test.sh +230 -0
- package/tests/user_confirmation_gates_test.sh +152 -0
package/agents/README.md
ADDED
|
@@ -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 数据卷后直接改域名。
|