dxai-cli 1.0.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/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/cli.js +272 -0
- package/package.json +64 -0
- package/src/auto-update.js +106 -0
- package/src/branding.js +80 -0
- package/src/cleanup.js +615 -0
- package/src/config-remover.js +325 -0
- package/src/config-writer.js +781 -0
- package/src/detect-project.js +316 -0
- package/src/detect.js +587 -0
- package/src/fs-atomic.js +35 -0
- package/src/handshake.js +123 -0
- package/src/index.js +966 -0
- package/src/inspect.js +283 -0
- package/src/manifest.js +179 -0
- package/src/mcp-cmd.js +282 -0
- package/src/net.js +72 -0
- package/src/profile.js +139 -0
- package/src/registry/automation-tools.js +6 -0
- package/src/registry/data/automation-tools.json +37 -0
- package/src/registry/data/mcp-servers.json +451 -0
- package/src/registry/data/skills.json +132 -0
- package/src/registry/loader.js +102 -0
- package/src/registry/mcp-registry.js +292 -0
- package/src/registry/mcp-servers.js +72 -0
- package/src/registry/skills.js +10 -0
- package/src/registry/stacks.js +769 -0
- package/src/registry/validate.js +209 -0
- package/src/rollback.js +182 -0
- package/src/runtime.js +40 -0
- package/src/select.js +72 -0
- package/src/update.js +126 -0
package/src/handshake.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// MCP stdio handshake — spawns a server and confirms it speaks JSON-RPC.
|
|
2
|
+
//
|
|
3
|
+
// Used by `dxai doctor --handshake`. This is the first use of child_process.spawn
|
|
4
|
+
// in the repo (everything else uses execSync); we need streaming stdio + a timeout,
|
|
5
|
+
// which execSync can't give us.
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import { AGENT_DEFINITIONS } from './detect.js';
|
|
9
|
+
import { isSafeSpawnSpec } from './registry/validate.js';
|
|
10
|
+
|
|
11
|
+
// The agent config keys that carry a spawnable stdio command/args block, derived
|
|
12
|
+
// from the agent definitions rather than hardcoded — so a newly added JSON-dialect
|
|
13
|
+
// agent (e.g. the antigravity-ide / antigravity-cli split) is covered automatically.
|
|
14
|
+
const STDIO_CONFIG_KEYS = AGENT_DEFINITIONS
|
|
15
|
+
.filter((a) => a.configFormat === 'json')
|
|
16
|
+
.map((a) => a.id);
|
|
17
|
+
|
|
18
|
+
const PROTOCOL_VERSION = '2025-06-18';
|
|
19
|
+
|
|
20
|
+
function initRequest(clientVersion) {
|
|
21
|
+
return (
|
|
22
|
+
JSON.stringify({
|
|
23
|
+
jsonrpc: '2.0',
|
|
24
|
+
id: 1,
|
|
25
|
+
method: 'initialize',
|
|
26
|
+
params: {
|
|
27
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
28
|
+
capabilities: {},
|
|
29
|
+
clientInfo: { name: 'dxai-doctor', version: clientVersion || '0.0.0' },
|
|
30
|
+
},
|
|
31
|
+
}) + '\n'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Spawn an MCP stdio server and confirm it answers `initialize` with a JSON-RPC result.
|
|
36
|
+
// spec: { command, args, env } — the stdio launch command.
|
|
37
|
+
// Resolves { ok, error? }; always kills the child before resolving.
|
|
38
|
+
export function handshakeServer(spec, { timeoutMs = 10000, clientVersion } = {}) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
let child;
|
|
41
|
+
try {
|
|
42
|
+
child = spawn(spec.command, spec.args || [], {
|
|
43
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
44
|
+
env: { ...process.env, ...(spec.env || {}) },
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
resolve({ ok: false, error: `spawn failed: ${err.message}` });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let settled = false;
|
|
52
|
+
let buf = '';
|
|
53
|
+
|
|
54
|
+
const finish = (result) => {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
settled = true;
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
try { child.kill('SIGKILL'); } catch { /* already gone */ }
|
|
59
|
+
resolve(result);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const timer = setTimeout(
|
|
63
|
+
() => finish({ ok: false, error: `no response within ${timeoutMs}ms` }),
|
|
64
|
+
timeoutMs
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
child.on('error', (err) => finish({ ok: false, error: `spawn failed: ${err.message}` }));
|
|
68
|
+
child.on('exit', (code) => finish({ ok: false, error: `exited (code ${code}) before responding` }));
|
|
69
|
+
|
|
70
|
+
child.stdout.on('data', (chunk) => {
|
|
71
|
+
buf += chunk.toString();
|
|
72
|
+
// stdio transport is newline-delimited JSON-RPC.
|
|
73
|
+
let nl;
|
|
74
|
+
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
75
|
+
const line = buf.slice(0, nl).trim();
|
|
76
|
+
buf = buf.slice(nl + 1);
|
|
77
|
+
if (!line) continue;
|
|
78
|
+
let msg;
|
|
79
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
80
|
+
if (msg && msg.id === 1) {
|
|
81
|
+
if (msg.result) finish({ ok: true });
|
|
82
|
+
else finish({ ok: false, error: msg.error?.message || 'initialize returned an error' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
child.stdin.write(initRequest(clientVersion));
|
|
90
|
+
} catch (err) {
|
|
91
|
+
finish({ ok: false, error: `stdin write failed: ${err.message}` });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Expand ${VAR} / $VAR references in a config env block from the current environment.
|
|
97
|
+
function resolveEnv(env) {
|
|
98
|
+
if (!env) return {};
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [k, v] of Object.entries(env)) {
|
|
101
|
+
if (typeof v !== 'string') continue;
|
|
102
|
+
out[k] = v.replace(/\$\{?(\w+)\}?/g, (_m, name) => process.env[name] ?? '');
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Resolve a stdio spawn spec from a server's registry configs. Prefers a
|
|
108
|
+
// command/args (npx-style) config. Returns null for remote/URL-only servers,
|
|
109
|
+
// which can't be stdio-handshaked.
|
|
110
|
+
export function resolveSpawnSpec(server) {
|
|
111
|
+
const configs = server.configs || {};
|
|
112
|
+
for (const key of STDIO_CONFIG_KEYS) {
|
|
113
|
+
const c = configs[key];
|
|
114
|
+
if (c && c.command && Array.isArray(c.args)) {
|
|
115
|
+
const spec = { command: c.command, args: c.args, env: resolveEnv(c.env) };
|
|
116
|
+
// Registry data is untrusted and this spec is about to be spawned — only
|
|
117
|
+
// hand back specs whose command is allowlisted and whose npx-style package
|
|
118
|
+
// argument is a clean spec. Unsafe specs are treated as un-handshakeable.
|
|
119
|
+
return isSafeSpawnSpec(spec) ? spec : null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|