@voybio/ace-swarm 2.4.0 → 2.4.2
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/CHANGELOG.md +16 -0
- package/README.md +502 -56
- package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
- package/assets/agent-state/runtime-tool-specs.json +70 -2
- package/assets/instructions/ACE_Coder.instructions.md +13 -0
- package/assets/instructions/ACE_UI.instructions.md +11 -0
- package/dist/ace-context.js +70 -11
- package/dist/ace-internal-tools.d.ts +3 -1
- package/dist/ace-internal-tools.js +10 -2
- package/dist/agent-runtime/role-adapters.d.ts +18 -1
- package/dist/agent-runtime/role-adapters.js +49 -5
- package/dist/astgrep-index.d.ts +48 -0
- package/dist/astgrep-index.js +126 -1
- package/dist/cli.js +487 -17
- package/dist/discovery-runtime-wrappers.d.ts +108 -0
- package/dist/discovery-runtime-wrappers.js +615 -0
- package/dist/helpers/bootstrap.js +1 -1
- package/dist/helpers/constants.d.ts +4 -2
- package/dist/helpers/constants.js +8 -0
- package/dist/helpers/path-utils.d.ts +8 -1
- package/dist/helpers/path-utils.js +27 -8
- package/dist/helpers/store-resolution.js +7 -3
- package/dist/hermes/bridge-protocol.d.ts +41 -0
- package/dist/hermes/bridge-protocol.js +70 -0
- package/dist/hermes/launch-profile.d.ts +19 -0
- package/dist/hermes/launch-profile.js +81 -0
- package/dist/hermes/session-manager.d.ts +42 -0
- package/dist/hermes/session-manager.js +187 -0
- package/dist/job-scheduler.js +30 -4
- package/dist/json-sanitizer.d.ts +16 -0
- package/dist/json-sanitizer.js +26 -0
- package/dist/local-model-policy.d.ts +27 -0
- package/dist/local-model-policy.js +84 -0
- package/dist/local-model-runtime.d.ts +17 -0
- package/dist/local-model-runtime.js +77 -20
- package/dist/model-bridge.d.ts +6 -1
- package/dist/model-bridge.js +338 -21
- package/dist/orchestrator-supervisor.d.ts +42 -0
- package/dist/orchestrator-supervisor.js +110 -3
- package/dist/plan-proposal.d.ts +115 -0
- package/dist/plan-proposal.js +1073 -0
- package/dist/runtime-executor.d.ts +6 -1
- package/dist/runtime-executor.js +72 -5
- package/dist/runtime-tool-specs.d.ts +19 -1
- package/dist/runtime-tool-specs.js +67 -26
- package/dist/schemas.js +30 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.js +73 -4
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +2 -0
- package/dist/store/bootstrap-store.d.ts +1 -0
- package/dist/store/bootstrap-store.js +8 -2
- package/dist/store/materializers/vericify-projector.js +3 -0
- package/dist/store/repositories/local-model-runtime-repository.d.ts +13 -1
- package/dist/store/repositories/local-model-runtime-repository.js +4 -1
- package/dist/store/repositories/vericify-repository.d.ts +1 -1
- package/dist/tools-agent.d.ts +20 -0
- package/dist/tools-agent.js +544 -29
- package/dist/tools-discovery.js +135 -0
- package/dist/tools-files.js +768 -66
- package/dist/tools-framework.js +80 -61
- package/dist/tools.d.ts +4 -1
- package/dist/tools.js +35 -13
- package/dist/tui/chat.d.ts +8 -0
- package/dist/tui/chat.js +74 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.js +45 -2
- package/dist/tui/layout.d.ts +1 -0
- package/dist/tui/layout.js +4 -1
- package/dist/tui/ollama.d.ts +8 -1
- package/dist/tui/ollama.js +53 -12
- package/dist/tui/openai-compatible.d.ts +13 -0
- package/dist/tui/openai-compatible.js +305 -5
- package/dist/tui/provider-discovery.d.ts +1 -0
- package/dist/tui/provider-discovery.js +50 -24
- package/dist/vericify-bridge.d.ts +4 -1
- package/dist/vericify-bridge.js +3 -0
- package/package.json +2 -1
- package/scripts/hermes_bridge_worker.py +136 -0
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { ACE_TASKS_ROOT_REL, ALL_MCP_CLIENTS, ALL_LLM_PROVIDERS, DEFAULTS_ROOT, PACKAGE_ROOT, WORKSPACE_ROOT, fileExists, getAllMcpServerConfigSnippets, getMcpClientInstallHint, getMcpServerConfigSnippet, wsPath, } from "./helpers.js";
|
|
3
3
|
import { refreshAstgrepIndex } from "./astgrep-index.js";
|
|
4
4
|
import { scanWorkspaceDelta } from "./index-store.js";
|
|
5
|
-
import { startStdioServer } from "./server.js";
|
|
5
|
+
import { startHermesShadowStdioServer, startStdioServer } from "./server.js";
|
|
6
6
|
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
7
|
-
import { waitForPendingStatusEventMirrors } from "./status-events.js";
|
|
7
|
+
import { appendStatusEventSafe, waitForPendingStatusEventMirrors } from "./status-events.js";
|
|
8
8
|
import { bootstrapStoreWorkspace } from "./store/bootstrap-store.js";
|
|
9
9
|
import { HostFileMaterializer } from "./store/materializers/host-file-materializer.js";
|
|
10
10
|
import { openStore } from "./store/ace-packed-store.js";
|
|
@@ -12,18 +12,24 @@ import { DiscoveryRepository } from "./store/repositories/discovery-repository.j
|
|
|
12
12
|
import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
|
|
13
13
|
import { getWorkspaceStorePath, readStoreBlobSync, readStoreJsonSync, } from "./store/store-snapshot.js";
|
|
14
14
|
import { ensureCanonicalWorkspaceStore } from "./store/workspace-store-paths.js";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { resolve } from "node:path";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
16
18
|
import { runTui } from "./tui/index.js";
|
|
17
|
-
import { buildOpenAiCompatibleBaseUrl, buildProviderDoctorCommands, defaultModelForProvider, discoverProviderContext, isLocalLlmProvider, normalizeLocalBaseUrl, normalizeProvider, scanLocalModelRuntimes, } from "./tui/provider-discovery.js";
|
|
19
|
+
import { buildOpenAiCompatibleBaseUrl, buildProviderDoctorCommands, defaultModelForProvider, discoverProviderContext, isLocalLlmProvider, normalizeLocalBaseUrl, normalizeLlamaCppHfModelName, normalizeProvider, scanLocalModelRuntimes, } from "./tui/provider-discovery.js";
|
|
18
20
|
import { diagnoseChatRuntimeConfig, OpenAICompatibleClient, } from "./tui/openai-compatible.js";
|
|
21
|
+
import { runShellCommand } from "./runtime-command.js";
|
|
22
|
+
import { resolveHermesLaunchProfile } from "./hermes/launch-profile.js";
|
|
19
23
|
function printHelp() {
|
|
20
24
|
console.log(`ACE Swarm CLI
|
|
21
25
|
|
|
22
26
|
Usage:
|
|
23
27
|
ace mcp Start MCP server over stdio
|
|
28
|
+
ace mcp-shadow --tools <csv> Start filtered Hermes-local MCP shadow server over stdio
|
|
24
29
|
ace serve Alias for mcp
|
|
25
30
|
ace tui [options] Launch interactive TUI dashboard
|
|
26
31
|
ace init [options] Bootstrap the ACE store into current workspace
|
|
32
|
+
ace connect [provider] Register a live local runtime or hosted provider in the store
|
|
27
33
|
ace turnkey [options] Project minimal workspace bootstrap stubs from the ACE store
|
|
28
34
|
ace doctor [options] Validate ACE runtime + MCP readiness
|
|
29
35
|
ace cache [options] Cache ACE artifacts into ace-state.ace and optionally clean projections
|
|
@@ -37,6 +43,16 @@ Options for tui:
|
|
|
37
43
|
--model <name> Model name override (defaults from profile/settings discovery)
|
|
38
44
|
--base-url <url> Local runtime base URL override
|
|
39
45
|
--ollama-url <url> Legacy alias for --base-url
|
|
46
|
+
--engine <name> direct|hermes_local execution engine
|
|
47
|
+
--hermes Alias for --engine hermes_local
|
|
48
|
+
--hermes-root <path> User-supplied Hermes checkout/import root
|
|
49
|
+
--hermes-python <path> User-supplied Python executable with Hermes deps
|
|
50
|
+
--hermes-uv-project <path> Optional uv project root for operator-managed Hermes
|
|
51
|
+
--hermes-command-json <json> Advanced command array override for Hermes Python
|
|
52
|
+
|
|
53
|
+
Hermes is optional and user-supplied. \`ace tui\` and \`ace doctor\` only resolve
|
|
54
|
+
Hermes when you pass \`--hermes\` / \`--engine hermes_local\` or set the matching
|
|
55
|
+
Hermes environment variables.
|
|
40
56
|
|
|
41
57
|
Options for init:
|
|
42
58
|
--project <name> Project name stored in agent-state/ace-state.ace metadata
|
|
@@ -48,12 +64,25 @@ Options for init:
|
|
|
48
64
|
--base-url <url> Runtime base URL override (local or OpenAI-compatible)
|
|
49
65
|
--ollama-url <url> Legacy alias for --base-url
|
|
50
66
|
|
|
67
|
+
Options for connect:
|
|
68
|
+
llama-server|llama-cli Optional launcher name for llama.cpp HF runtimes
|
|
69
|
+
--hf <repo> Hugging Face model repo or local model identifier
|
|
70
|
+
--model <name> Alias for --hf when you already know the model name
|
|
71
|
+
--base-url <url> OpenAI-compatible endpoint for the live runtime
|
|
72
|
+
--provider <name> Explicit provider override when not using a launcher alias
|
|
73
|
+
|
|
51
74
|
Options for doctor:
|
|
52
75
|
--llm <provider> ollama|llama.cpp|codex|claude|gemini|copilot|... (default: auto from agent-state/ace-state.ace)
|
|
53
76
|
--model <name> Model name override
|
|
54
77
|
--base-url <url> Runtime base URL override
|
|
55
78
|
--ollama-url <url> Legacy alias for --base-url
|
|
56
79
|
--scan Probe common local Ollama + llama.cpp endpoints when URL is unset
|
|
80
|
+
--hermes Also run optional Hermes-local readiness checks
|
|
81
|
+
--hermes-root <path> User-supplied Hermes checkout/import root (or ACE_HERMES_ROOT)
|
|
82
|
+
--hermes-python <path> User-supplied Python executable (or HERMES_PYTHON)
|
|
83
|
+
--hermes-uv-project <path> Optional uv project root for operator-managed Hermes
|
|
84
|
+
--hermes-command-json <json> Advanced command array override for Hermes Python
|
|
85
|
+
--repair-ollama Opt-in: run ollama pull <model> when doctor finds a missing Ollama model
|
|
57
86
|
|
|
58
87
|
Options for cache:
|
|
59
88
|
--dry-run Preview what would be cached and cleaned (no writes/deletes)
|
|
@@ -63,6 +92,11 @@ Options for mcp-config:
|
|
|
63
92
|
--client <name> codex|vscode|copilot|claude|cursor|antigravity
|
|
64
93
|
--all Print all client snippets for optional global install
|
|
65
94
|
|
|
95
|
+
Hermes environment:
|
|
96
|
+
ACE_HERMES_ROOT User-supplied Hermes checkout/import root
|
|
97
|
+
HERMES_PYTHON User-supplied Python with Hermes dependencies
|
|
98
|
+
ACE_HERMES_UV_PROJECT Optional operator-managed uv project root
|
|
99
|
+
|
|
66
100
|
preconfig writes .mcp-config/ at the workspace root with ready-to-use config files for every
|
|
67
101
|
supported MCP client plus a root .mcp.json for GitHub Copilot CLI. Run once after ace init.
|
|
68
102
|
Each file includes the install hint for its client.
|
|
@@ -77,6 +111,180 @@ function readFlagValue(args, flag) {
|
|
|
77
111
|
return undefined;
|
|
78
112
|
return args[index + 1];
|
|
79
113
|
}
|
|
114
|
+
async function runCommandArray(command, args, options) {
|
|
115
|
+
const [bin, ...prefixArgs] = command;
|
|
116
|
+
if (!bin)
|
|
117
|
+
return { ok: false, stdout: "", stderr: "", detail: "empty command array" };
|
|
118
|
+
return await new Promise((resolveCommand) => {
|
|
119
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
120
|
+
cwd: options.cwd,
|
|
121
|
+
env: options.env,
|
|
122
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
123
|
+
});
|
|
124
|
+
let stdout = "";
|
|
125
|
+
let stderr = "";
|
|
126
|
+
const timeout = setTimeout(() => {
|
|
127
|
+
child.kill("SIGTERM");
|
|
128
|
+
}, options.timeoutMs ?? 5000);
|
|
129
|
+
child.stdout?.setEncoding("utf8");
|
|
130
|
+
child.stderr?.setEncoding("utf8");
|
|
131
|
+
child.stdout?.on("data", (chunk) => {
|
|
132
|
+
stdout += String(chunk);
|
|
133
|
+
});
|
|
134
|
+
child.stderr?.on("data", (chunk) => {
|
|
135
|
+
stderr += String(chunk);
|
|
136
|
+
});
|
|
137
|
+
child.on("error", (error) => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
resolveCommand({ ok: false, stdout, stderr, detail: error.message });
|
|
140
|
+
});
|
|
141
|
+
child.on("close", (code) => {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
resolveCommand({
|
|
144
|
+
ok: code === 0,
|
|
145
|
+
stdout,
|
|
146
|
+
stderr,
|
|
147
|
+
detail: code === 0 ? "exit 0" : `exit ${code}; ${stderr.slice(0, 300)}`,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function mcpFrame(payload) {
|
|
153
|
+
const body = JSON.stringify(payload);
|
|
154
|
+
return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
155
|
+
}
|
|
156
|
+
async function probeHermesShadowMcp(cliPath, toolAllowlist) {
|
|
157
|
+
return await new Promise((resolveProbe, rejectProbe) => {
|
|
158
|
+
const child = spawn(process.execPath, [cliPath, "mcp-shadow", "--tools", toolAllowlist.join(",")], {
|
|
159
|
+
cwd: PACKAGE_ROOT,
|
|
160
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
+
});
|
|
162
|
+
let stdout = "";
|
|
163
|
+
let stderr = "";
|
|
164
|
+
const timeout = setTimeout(() => {
|
|
165
|
+
child.kill("SIGTERM");
|
|
166
|
+
rejectProbe(new Error(`shadow MCP probe timed out${stderr ? `: ${stderr.slice(0, 200)}` : ""}`));
|
|
167
|
+
}, 2500);
|
|
168
|
+
child.stdout.on("data", (chunk) => {
|
|
169
|
+
stdout += chunk.toString("utf8");
|
|
170
|
+
if (stdout.includes('"tools"') && stdout.includes(toolAllowlist[0] ?? "")) {
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
child.kill("SIGTERM");
|
|
173
|
+
resolveProbe(`shadow MCP initialize/tools-list probe succeeded; tools=${toolAllowlist.join(",")}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
child.stderr.on("data", (chunk) => {
|
|
177
|
+
stderr += chunk.toString("utf8");
|
|
178
|
+
});
|
|
179
|
+
child.on("error", (error) => {
|
|
180
|
+
clearTimeout(timeout);
|
|
181
|
+
rejectProbe(error);
|
|
182
|
+
});
|
|
183
|
+
child.on("close", () => {
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
if (stdout.includes('"tools"') && stdout.includes(toolAllowlist[0] ?? "")) {
|
|
186
|
+
resolveProbe(`shadow MCP initialize/tools-list probe succeeded; tools=${toolAllowlist.join(",")}`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
rejectProbe(new Error(`shadow MCP probe did not return tools/list${stderr ? `: ${stderr.slice(0, 200)}` : ""}`));
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
child.stdin.write(mcpFrame({
|
|
193
|
+
jsonrpc: "2.0",
|
|
194
|
+
id: 1,
|
|
195
|
+
method: "initialize",
|
|
196
|
+
params: {
|
|
197
|
+
protocolVersion: "2024-11-05",
|
|
198
|
+
capabilities: {},
|
|
199
|
+
clientInfo: { name: "ace-doctor", version: "0.0.0" },
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
child.stdin.write(mcpFrame({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }));
|
|
203
|
+
child.stdin.write(mcpFrame({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async function appendHermesLocalReadinessChecks(checks, profile) {
|
|
207
|
+
const hermesRoot = profile.hermes_root;
|
|
208
|
+
const workerPath = profile.worker_path;
|
|
209
|
+
const cliPath = resolve(PACKAGE_ROOT, "dist", "cli.js");
|
|
210
|
+
const toolAllowlist = ["get_all_agents_summary"];
|
|
211
|
+
checks.push({
|
|
212
|
+
name: "Hermes launch profile",
|
|
213
|
+
ok: true,
|
|
214
|
+
detail: `source=${profile.source}; mode=${profile.mode}; command=${JSON.stringify(profile.command)}; hermes_root=${profile.hermes_root ?? "not configured"}`,
|
|
215
|
+
});
|
|
216
|
+
checks.push({
|
|
217
|
+
name: "Hermes worker packaged",
|
|
218
|
+
ok: existsSync(workerPath),
|
|
219
|
+
detail: existsSync(workerPath)
|
|
220
|
+
? workerPath
|
|
221
|
+
: `Missing ${workerPath}; run npm run build.`,
|
|
222
|
+
});
|
|
223
|
+
checks.push({
|
|
224
|
+
name: "Hermes checkout import root",
|
|
225
|
+
ok: Boolean(hermesRoot &&
|
|
226
|
+
existsSync(resolve(hermesRoot, "run_agent.py")) &&
|
|
227
|
+
existsSync(resolve(hermesRoot, "tools", "mcp_tool.py"))),
|
|
228
|
+
detail: hermesRoot && existsSync(resolve(hermesRoot, "run_agent.py"))
|
|
229
|
+
? hermesRoot
|
|
230
|
+
: "ACE does not install Hermes. Set ACE_HERMES_ROOT or pass --hermes-root to a Hermes checkout.",
|
|
231
|
+
});
|
|
232
|
+
const pythonResolves = await runCommandArray(profile.command, ["-c", "import sys; print(sys.executable)"], {
|
|
233
|
+
cwd: PACKAGE_ROOT,
|
|
234
|
+
timeoutMs: 5000,
|
|
235
|
+
});
|
|
236
|
+
checks.push({
|
|
237
|
+
name: "Hermes Python command",
|
|
238
|
+
ok: pythonResolves.ok,
|
|
239
|
+
detail: pythonResolves.ok ? `${JSON.stringify(profile.command)} -> ${pythonResolves.stdout.trim()}` : pythonResolves.detail,
|
|
240
|
+
});
|
|
241
|
+
const importEnv = {
|
|
242
|
+
...process.env,
|
|
243
|
+
PYTHONPATH: [hermesRoot, process.env.PYTHONPATH].filter(Boolean).join(":"),
|
|
244
|
+
};
|
|
245
|
+
const importResult = hermesRoot
|
|
246
|
+
? await runCommandArray(profile.command, ["-c", "from run_agent import AIAgent; print(AIAgent.__name__)"], {
|
|
247
|
+
cwd: hermesRoot,
|
|
248
|
+
env: importEnv,
|
|
249
|
+
timeoutMs: 5000,
|
|
250
|
+
})
|
|
251
|
+
: { ok: false, detail: "Hermes root not configured", stdout: "", stderr: "" };
|
|
252
|
+
checks.push({
|
|
253
|
+
name: "Hermes Python imports",
|
|
254
|
+
ok: importResult.ok,
|
|
255
|
+
detail: importResult.ok ? "run_agent.AIAgent import ok" : importResult.detail,
|
|
256
|
+
});
|
|
257
|
+
checks.push({
|
|
258
|
+
name: "ACE shadow MCP command",
|
|
259
|
+
ok: existsSync(cliPath),
|
|
260
|
+
detail: existsSync(cliPath)
|
|
261
|
+
? JSON.stringify([process.execPath, cliPath, "mcp-shadow", "--tools", toolAllowlist.join(",")])
|
|
262
|
+
: `Missing ${cliPath}; run npm run build.`,
|
|
263
|
+
});
|
|
264
|
+
if (existsSync(cliPath)) {
|
|
265
|
+
try {
|
|
266
|
+
checks.push({
|
|
267
|
+
name: "ACE shadow MCP handshake",
|
|
268
|
+
ok: true,
|
|
269
|
+
detail: await probeHermesShadowMcp(cliPath, toolAllowlist),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
checks.push({
|
|
274
|
+
name: "ACE shadow MCP handshake",
|
|
275
|
+
ok: false,
|
|
276
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
checks.push({
|
|
282
|
+
name: "ACE shadow MCP handshake",
|
|
283
|
+
ok: false,
|
|
284
|
+
detail: `Cannot probe shadow MCP until ${cliPath} exists; run npm run build.`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
80
288
|
function readLlmProfile() {
|
|
81
289
|
const storeProfile = readStoreJsonSync(WORKSPACE_ROOT, "state/runtime/llm_profile") ??
|
|
82
290
|
readStoreJsonSync(WORKSPACE_ROOT, "state/runtime/llm-profile");
|
|
@@ -98,6 +306,29 @@ function readLlmProfile() {
|
|
|
98
306
|
function readBaseUrlFlag(args) {
|
|
99
307
|
return normalizeLocalBaseUrl(readFlagValue(args, "--base-url") ?? readFlagValue(args, "--ollama-url"));
|
|
100
308
|
}
|
|
309
|
+
function readConnectModelFlag(args) {
|
|
310
|
+
return (readFlagValue(args, "--hf") ??
|
|
311
|
+
readFlagValue(args, "-hf") ??
|
|
312
|
+
readFlagValue(args, "--model"))?.trim();
|
|
313
|
+
}
|
|
314
|
+
function parseConnectArgs(args) {
|
|
315
|
+
const firstToken = args[0];
|
|
316
|
+
const hasLauncher = Boolean(firstToken && !firstToken.startsWith("-"));
|
|
317
|
+
const launcher = hasLauncher ? firstToken?.trim().toLowerCase() : undefined;
|
|
318
|
+
const providerOverride = normalizeProvider(readFlagValue(args, "--provider")?.trim());
|
|
319
|
+
const provider = providerOverride ??
|
|
320
|
+
(launcher === "llama-server" || launcher === "llama-cli" ? "llama.cpp" : providerOverride);
|
|
321
|
+
const rawModel = readConnectModelFlag(args);
|
|
322
|
+
const model = provider === "llama.cpp" ? normalizeLlamaCppHfModelName(rawModel) ?? rawModel : rawModel;
|
|
323
|
+
const baseUrl = readBaseUrlFlag(args);
|
|
324
|
+
const launcherCommand = hasLauncher ? args.join(" ").trim() : undefined;
|
|
325
|
+
return {
|
|
326
|
+
provider: provider,
|
|
327
|
+
model,
|
|
328
|
+
baseUrl,
|
|
329
|
+
launcherCommand,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
101
332
|
function parseLlmOptions(args) {
|
|
102
333
|
const profile = readLlmProfile();
|
|
103
334
|
const selected = normalizeProvider(readFlagValue(args, "--llm")?.trim() ?? profile?.provider?.trim());
|
|
@@ -107,8 +338,11 @@ function parseLlmOptions(args) {
|
|
|
107
338
|
throw new Error(`Unsupported LLM provider: ${selected}`);
|
|
108
339
|
}
|
|
109
340
|
const provider = selected;
|
|
110
|
-
const
|
|
111
|
-
const
|
|
341
|
+
const explicitModel = readFlagValue(args, "--model")?.trim();
|
|
342
|
+
const profileModel = profile?.model?.trim();
|
|
343
|
+
const llmModel = explicitModel ||
|
|
344
|
+
profileModel ||
|
|
345
|
+
(provider === "ollama" ? defaultModelForProvider(provider) : undefined);
|
|
112
346
|
const llmBaseUrl = readBaseUrlFlag(args) ?? normalizeLocalBaseUrl(profile?.base_url);
|
|
113
347
|
return {
|
|
114
348
|
llmProvider: provider,
|
|
@@ -121,9 +355,11 @@ async function writeLlmProfile(profile) {
|
|
|
121
355
|
await withStoreWriteCoordinator(storePath, async () => {
|
|
122
356
|
const payload = {
|
|
123
357
|
provider: profile.provider,
|
|
124
|
-
model: profile.model,
|
|
125
358
|
generated_at: new Date().toISOString(),
|
|
126
359
|
};
|
|
360
|
+
if (profile.model) {
|
|
361
|
+
payload.model = profile.model;
|
|
362
|
+
}
|
|
127
363
|
if (profile.baseUrl) {
|
|
128
364
|
payload.base_url = profile.baseUrl;
|
|
129
365
|
if (profile.provider === "ollama") {
|
|
@@ -131,6 +367,9 @@ async function writeLlmProfile(profile) {
|
|
|
131
367
|
payload.default_api_key = "ollama";
|
|
132
368
|
}
|
|
133
369
|
}
|
|
370
|
+
if (profile.launcherCommand) {
|
|
371
|
+
payload.launch_command = profile.launcherCommand;
|
|
372
|
+
}
|
|
134
373
|
const doctorCommands = buildProviderDoctorCommands(profile.provider, profile.model, profile.baseUrl);
|
|
135
374
|
const store = await openStore(storePath);
|
|
136
375
|
try {
|
|
@@ -153,6 +392,40 @@ async function writeLlmProfile(profile) {
|
|
|
153
392
|
}, { operation_label: "writeLlmProfile" });
|
|
154
393
|
return `${storePath}#state/runtime/llm_profile`;
|
|
155
394
|
}
|
|
395
|
+
async function runConnect(args) {
|
|
396
|
+
const parsed = parseConnectArgs(args);
|
|
397
|
+
if (!parsed.provider) {
|
|
398
|
+
throw new Error("ace connect expects a provider or launcher command. Try `ace connect llama-server -hf <model> --base-url http://127.0.0.1:8080`.");
|
|
399
|
+
}
|
|
400
|
+
if (!ALL_LLM_PROVIDERS.includes(parsed.provider)) {
|
|
401
|
+
throw new Error(`Unsupported LLM provider: ${parsed.provider}`);
|
|
402
|
+
}
|
|
403
|
+
if (isLocalLlmProvider(parsed.provider) && !parsed.model) {
|
|
404
|
+
throw new Error("ace connect for local runtimes requires an explicit model. Pass `--hf <repo>` or `--model <name>`.");
|
|
405
|
+
}
|
|
406
|
+
const storeResult = await bootstrapStoreWorkspace({
|
|
407
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
408
|
+
llm: parsed.provider,
|
|
409
|
+
model: parsed.model,
|
|
410
|
+
baseUrl: parsed.baseUrl,
|
|
411
|
+
includeMcpConfig: false,
|
|
412
|
+
includeClientConfigBundle: false,
|
|
413
|
+
launcherCommand: parsed.launcherCommand,
|
|
414
|
+
});
|
|
415
|
+
console.log("ACE connect complete");
|
|
416
|
+
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
417
|
+
console.log(`Store: ${storeResult.storePath}`);
|
|
418
|
+
console.log(`Provider: ${parsed.provider}`);
|
|
419
|
+
console.log(`Model: ${parsed.model ?? "(not set)"}`);
|
|
420
|
+
console.log(`Base URL: ${parsed.baseUrl ?? "(not set)"}`);
|
|
421
|
+
if (parsed.launcherCommand) {
|
|
422
|
+
console.log(`Launcher: ${parsed.launcherCommand}`);
|
|
423
|
+
}
|
|
424
|
+
console.log(`Profile path: ${storeResult.storePath}#state/runtime/llm_profile`);
|
|
425
|
+
for (const warning of storeResult.warnings) {
|
|
426
|
+
console.warn(` [store] ${warning}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
156
429
|
async function recordDiscoveryProfile(input) {
|
|
157
430
|
const storePath = getWorkspaceStorePath(WORKSPACE_ROOT);
|
|
158
431
|
await withStoreWriteCoordinator(storePath, async () => {
|
|
@@ -296,6 +569,13 @@ async function listLocalRuntimeModels(provider, baseUrl) {
|
|
|
296
569
|
headers: { Accept: "application/json" },
|
|
297
570
|
});
|
|
298
571
|
if (!response.ok) {
|
|
572
|
+
const text = await response.text().catch(() => "");
|
|
573
|
+
if (response.status >= 500 && /unable to load model/i.test(text)) {
|
|
574
|
+
const error = new Error(`${response.status} ${response.statusText}: ollama_model_load_error. ` +
|
|
575
|
+
`Suggested remediation: run ollama pull <model> or ace doctor --repair-ollama.`);
|
|
576
|
+
error.reason_code = "ollama_model_load_error";
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
299
579
|
throw new Error(`${response.status} ${response.statusText}`);
|
|
300
580
|
}
|
|
301
581
|
const payload = (await response.json().catch(() => ({})));
|
|
@@ -321,6 +601,7 @@ async function listLocalRuntimeModels(provider, baseUrl) {
|
|
|
321
601
|
}
|
|
322
602
|
async function runDoctor(args) {
|
|
323
603
|
const llm = parseLlmOptions(args);
|
|
604
|
+
const repairOllama = args.includes("--repair-ollama");
|
|
324
605
|
const checks = [];
|
|
325
606
|
const mcpConfigPaths = [wsPath(".vscode", "mcp.json")];
|
|
326
607
|
const hasWorkspaceMcpConfig = mcpConfigPaths.some((path) => fileExists(path));
|
|
@@ -347,7 +628,9 @@ async function runDoctor(args) {
|
|
|
347
628
|
let provider = llm.llmProvider;
|
|
348
629
|
let model = llm.llmModel;
|
|
349
630
|
let baseUrl = llm.llmBaseUrl;
|
|
350
|
-
const shouldScan = args.includes("--scan") ||
|
|
631
|
+
const shouldScan = args.includes("--scan") ||
|
|
632
|
+
(!provider && !baseUrl) ||
|
|
633
|
+
(isLocalLlmProvider(provider) && (!baseUrl || !model));
|
|
351
634
|
if (shouldScan) {
|
|
352
635
|
const scanned = await scanLocalModelRuntimes({
|
|
353
636
|
workspaceRoot: WORKSPACE_ROOT,
|
|
@@ -359,7 +642,7 @@ async function runDoctor(args) {
|
|
|
359
642
|
if (chosen) {
|
|
360
643
|
provider = chosen.provider ?? provider;
|
|
361
644
|
baseUrl = baseUrl ?? chosen.baseUrl;
|
|
362
|
-
model = model || chosen.models[0]
|
|
645
|
+
model = model || chosen.models[0];
|
|
363
646
|
checks.push({
|
|
364
647
|
name: "Runtime endpoint discovered",
|
|
365
648
|
ok: true,
|
|
@@ -367,7 +650,7 @@ async function runDoctor(args) {
|
|
|
367
650
|
});
|
|
368
651
|
const writtenProfilePath = await writeLlmProfile({
|
|
369
652
|
provider: provider ?? chosen.provider,
|
|
370
|
-
model
|
|
653
|
+
model,
|
|
371
654
|
baseUrl,
|
|
372
655
|
});
|
|
373
656
|
checks.push({
|
|
@@ -388,6 +671,30 @@ async function runDoctor(args) {
|
|
|
388
671
|
throw new Error(`No runtime provider configured. Use --llm <provider>, bootstrap one into agent-state/ace-state.ace#state/runtime/llm_profile, or run \`ace doctor --scan\` for a local runtime.`);
|
|
389
672
|
}
|
|
390
673
|
if (!model) {
|
|
674
|
+
if (isLocalLlmProvider(provider)) {
|
|
675
|
+
checks.push({
|
|
676
|
+
name: "Runtime model configured",
|
|
677
|
+
ok: false,
|
|
678
|
+
detail: "No local runtime model is configured yet. Run `ace connect` with a user-supplied HF model or run `ace doctor --scan` after the runtime is up.",
|
|
679
|
+
});
|
|
680
|
+
const failed = checks.filter((check) => !check.ok);
|
|
681
|
+
console.log("ACE Doctor Report");
|
|
682
|
+
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
683
|
+
console.log(`Provider: ${provider}`);
|
|
684
|
+
console.log("Model: (not set)");
|
|
685
|
+
if (baseUrl) {
|
|
686
|
+
console.log(`Base URL: ${baseUrl}`);
|
|
687
|
+
}
|
|
688
|
+
console.log("");
|
|
689
|
+
for (const check of checks) {
|
|
690
|
+
const status = check.ok ? "PASS" : "FAIL";
|
|
691
|
+
console.log(`- [${status}] ${check.name}: ${check.detail}`);
|
|
692
|
+
}
|
|
693
|
+
if (failed.length > 0) {
|
|
694
|
+
process.exitCode = 1;
|
|
695
|
+
}
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
391
698
|
model = defaultModelForProvider(provider);
|
|
392
699
|
}
|
|
393
700
|
if (isLocalLlmProvider(provider) && !baseUrl) {
|
|
@@ -425,6 +732,23 @@ async function runDoctor(args) {
|
|
|
425
732
|
});
|
|
426
733
|
}
|
|
427
734
|
catch (error) {
|
|
735
|
+
const reasonCode = typeof error === "object" && error !== null && "reason_code" in error
|
|
736
|
+
? String(error.reason_code)
|
|
737
|
+
: undefined;
|
|
738
|
+
if (reasonCode === "ollama_model_load_error") {
|
|
739
|
+
await appendStatusEventSafe({
|
|
740
|
+
source_module: "capability-ops",
|
|
741
|
+
event_type: "OLLAMA_MODEL_LOAD_ERROR",
|
|
742
|
+
status: "blocked",
|
|
743
|
+
summary: `Ollama model-load error for ${model}: ${error instanceof Error ? error.message : String(error)}`,
|
|
744
|
+
objective_id: "ollama-doctor",
|
|
745
|
+
payload: {
|
|
746
|
+
reason_code: "ollama_model_load_error",
|
|
747
|
+
model,
|
|
748
|
+
base_url: baseUrl,
|
|
749
|
+
},
|
|
750
|
+
}).catch(() => undefined);
|
|
751
|
+
}
|
|
428
752
|
checks.push({
|
|
429
753
|
name: "Runtime endpoint reachable",
|
|
430
754
|
ok: false,
|
|
@@ -450,6 +774,44 @@ async function runDoctor(args) {
|
|
|
450
774
|
? `${model} found`
|
|
451
775
|
: `${model} not reported by llama.cpp (available: ${modelNames.join(", ") || "none"})`,
|
|
452
776
|
});
|
|
777
|
+
if (provider === "ollama" && !hasModel && repairOllama) {
|
|
778
|
+
const repair = await runShellCommand(`ollama pull ${model}`, {
|
|
779
|
+
cwd: WORKSPACE_ROOT,
|
|
780
|
+
timeout_ms: 10 * 60_000,
|
|
781
|
+
});
|
|
782
|
+
await appendRunLedgerEntrySafe({
|
|
783
|
+
tool: "doctor",
|
|
784
|
+
category: repair.exit_code === 0 ? "info" : "regression",
|
|
785
|
+
message: `Opt-in Ollama repair attempted for ${model}`,
|
|
786
|
+
artifacts: [],
|
|
787
|
+
metadata: {
|
|
788
|
+
reason_code: "ollama_model_load_error",
|
|
789
|
+
model,
|
|
790
|
+
exit_code: repair.exit_code,
|
|
791
|
+
timed_out: repair.timed_out,
|
|
792
|
+
},
|
|
793
|
+
}).catch(() => undefined);
|
|
794
|
+
await appendStatusEventSafe({
|
|
795
|
+
source_module: "capability-ops",
|
|
796
|
+
event_type: "OLLAMA_REPAIR_ATTEMPTED",
|
|
797
|
+
status: repair.exit_code === 0 ? "done" : "fail",
|
|
798
|
+
summary: `Opt-in Ollama repair attempted for ${model}`,
|
|
799
|
+
objective_id: "ollama-doctor",
|
|
800
|
+
payload: {
|
|
801
|
+
reason_code: "ollama_model_load_error",
|
|
802
|
+
model,
|
|
803
|
+
exit_code: repair.exit_code,
|
|
804
|
+
timed_out: repair.timed_out,
|
|
805
|
+
},
|
|
806
|
+
}).catch(() => undefined);
|
|
807
|
+
checks.push({
|
|
808
|
+
name: "Ollama model repair attempted",
|
|
809
|
+
ok: repair.exit_code === 0,
|
|
810
|
+
detail: repair.exit_code === 0
|
|
811
|
+
? `ollama pull ${model} completed`
|
|
812
|
+
: `ollama pull ${model} failed with exit ${repair.exit_code}: ${repair.stderr || repair.stdout || "no output"}`,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
453
815
|
}
|
|
454
816
|
else {
|
|
455
817
|
const diagnosis = diagnoseChatRuntimeConfig(provider, model, baseUrl ? { baseUrl } : undefined);
|
|
@@ -490,6 +852,25 @@ async function runDoctor(args) {
|
|
|
490
852
|
}
|
|
491
853
|
}
|
|
492
854
|
}
|
|
855
|
+
if (isLocalLlmProvider(provider) && args.includes("--hermes")) {
|
|
856
|
+
try {
|
|
857
|
+
const hermesProfile = resolveHermesLaunchProfile({
|
|
858
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
859
|
+
cliHermesRoot: readFlagValue(args, "--hermes-root"),
|
|
860
|
+
cliHermesPython: readFlagValue(args, "--hermes-python"),
|
|
861
|
+
cliHermesUvProject: readFlagValue(args, "--hermes-uv-project"),
|
|
862
|
+
cliHermesCommandJson: readFlagValue(args, "--hermes-command-json"),
|
|
863
|
+
});
|
|
864
|
+
await appendHermesLocalReadinessChecks(checks, hermesProfile);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
checks.push({
|
|
868
|
+
name: "Hermes launch profile",
|
|
869
|
+
ok: false,
|
|
870
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
493
874
|
const failed = checks.filter((check) => !check.ok);
|
|
494
875
|
console.log("ACE Doctor Report");
|
|
495
876
|
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
@@ -576,11 +957,32 @@ async function main() {
|
|
|
576
957
|
await startStdioServer();
|
|
577
958
|
return;
|
|
578
959
|
}
|
|
960
|
+
if (command === "mcp-shadow") {
|
|
961
|
+
const shadowArgs = args.slice(1);
|
|
962
|
+
const tools = (readFlagValue(shadowArgs, "--tools") ?? "")
|
|
963
|
+
.split(",")
|
|
964
|
+
.map((tool) => tool.trim())
|
|
965
|
+
.filter(Boolean);
|
|
966
|
+
await startHermesShadowStdioServer(tools);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
579
969
|
if (command === "tui") {
|
|
580
970
|
const tuiArgs = args.slice(1);
|
|
581
971
|
const cliProvider = readFlagValue(tuiArgs, "--provider")?.trim() ||
|
|
582
972
|
readFlagValue(tuiArgs, "--llm")?.trim();
|
|
583
973
|
const cliModel = readFlagValue(tuiArgs, "--model")?.trim();
|
|
974
|
+
const cliEngine = readFlagValue(tuiArgs, "--engine")?.trim() ||
|
|
975
|
+
(tuiArgs.includes("--hermes") ? "hermes_local" : undefined);
|
|
976
|
+
const normalizedCliEngine = cliEngine?.toLowerCase().replace(/-/g, "_");
|
|
977
|
+
const hermesLaunchProfile = normalizedCliEngine === "hermes_local"
|
|
978
|
+
? resolveHermesLaunchProfile({
|
|
979
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
980
|
+
cliHermesRoot: readFlagValue(tuiArgs, "--hermes-root"),
|
|
981
|
+
cliHermesPython: readFlagValue(tuiArgs, "--hermes-python"),
|
|
982
|
+
cliHermesUvProject: readFlagValue(tuiArgs, "--hermes-uv-project"),
|
|
983
|
+
cliHermesCommandJson: readFlagValue(tuiArgs, "--hermes-command-json"),
|
|
984
|
+
})
|
|
985
|
+
: undefined;
|
|
584
986
|
const cliBaseUrl = readBaseUrlFlag(tuiArgs);
|
|
585
987
|
const discovered = discoverProviderContext({
|
|
586
988
|
workspaceRoot: WORKSPACE_ROOT,
|
|
@@ -588,14 +990,78 @@ async function main() {
|
|
|
588
990
|
cliModel,
|
|
589
991
|
cliBaseUrl,
|
|
590
992
|
});
|
|
993
|
+
const needsLocalScan = isLocalLlmProvider(discovered.provider) &&
|
|
994
|
+
(!discovered.baseUrl || !discovered.model || discovered.model === defaultModelForProvider(discovered.provider));
|
|
995
|
+
const runtime = needsLocalScan
|
|
996
|
+
? await scanLocalModelRuntimes({
|
|
997
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
998
|
+
preferredProvider: discovered.provider,
|
|
999
|
+
explicitBaseUrl: discovered.baseUrl,
|
|
1000
|
+
})
|
|
1001
|
+
: undefined;
|
|
1002
|
+
const scanned = runtime?.candidates.find((candidate) => candidate.provider === discovered.provider) ??
|
|
1003
|
+
runtime?.candidates[0];
|
|
1004
|
+
const resolvedTui = scanned?.models[0]
|
|
1005
|
+
? {
|
|
1006
|
+
...discovered,
|
|
1007
|
+
model: scanned.models[0],
|
|
1008
|
+
baseUrl: discovered.baseUrl ?? scanned.baseUrl,
|
|
1009
|
+
providerBaseUrls: {
|
|
1010
|
+
...discovered.providerBaseUrls,
|
|
1011
|
+
[scanned.provider]: discovered.baseUrl ?? scanned.baseUrl,
|
|
1012
|
+
},
|
|
1013
|
+
}
|
|
1014
|
+
: discovered;
|
|
1015
|
+
// Merge any persisted discovery records from the workspace store so recent `ace doctor`
|
|
1016
|
+
// results are reflected immediately in the TUI startup options.
|
|
1017
|
+
try {
|
|
1018
|
+
const store = await openStore(getWorkspaceStorePath(WORKSPACE_ROOT), { readOnly: true });
|
|
1019
|
+
try {
|
|
1020
|
+
const discoveryRepo = new DiscoveryRepository(store);
|
|
1021
|
+
const stored = await discoveryRepo.listAll();
|
|
1022
|
+
const mergedProviders = new Set(resolvedTui.providers ?? []);
|
|
1023
|
+
const mergedModelsByProvider = { ...(resolvedTui.modelsByProvider ?? {}) };
|
|
1024
|
+
const mergedProviderBaseUrls = { ...(resolvedTui.providerBaseUrls ?? {}) };
|
|
1025
|
+
for (const p of stored) {
|
|
1026
|
+
if (!p || !p.provider)
|
|
1027
|
+
continue;
|
|
1028
|
+
const prov = String(p.provider).trim();
|
|
1029
|
+
if (!prov)
|
|
1030
|
+
continue;
|
|
1031
|
+
mergedProviders.add(prov);
|
|
1032
|
+
if (Array.isArray(p.models) && p.models.length > 0) {
|
|
1033
|
+
const existing = new Set(mergedModelsByProvider[prov] ?? []);
|
|
1034
|
+
for (const m of p.models)
|
|
1035
|
+
if (typeof m === "string" && m.trim())
|
|
1036
|
+
existing.add(m.trim());
|
|
1037
|
+
mergedModelsByProvider[prov] = [...existing].sort((a, b) => a.localeCompare(b));
|
|
1038
|
+
}
|
|
1039
|
+
if (p.endpoint && typeof p.endpoint === "string") {
|
|
1040
|
+
mergedProviderBaseUrls[prov] = p.endpoint;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
resolvedTui.providers = [...mergedProviders].sort((a, b) => a.localeCompare(b));
|
|
1044
|
+
resolvedTui.modelsByProvider = { ...(resolvedTui.modelsByProvider ?? {}), ...mergedModelsByProvider };
|
|
1045
|
+
resolvedTui.providerBaseUrls = { ...(resolvedTui.providerBaseUrls ?? {}), ...mergedProviderBaseUrls };
|
|
1046
|
+
}
|
|
1047
|
+
finally {
|
|
1048
|
+
await store.close();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
catch (err) {
|
|
1052
|
+
// Non-fatal — continue with discovered defaults if store read fails.
|
|
1053
|
+
console.warn(`Could not merge discovery records into TUI startup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1054
|
+
}
|
|
591
1055
|
await runTui({
|
|
592
|
-
provider:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1056
|
+
provider: resolvedTui.provider,
|
|
1057
|
+
engine: cliEngine,
|
|
1058
|
+
hermesLaunchProfile,
|
|
1059
|
+
model: resolvedTui.model,
|
|
1060
|
+
providers: resolvedTui.providers,
|
|
1061
|
+
modelsByProvider: resolvedTui.modelsByProvider,
|
|
1062
|
+
baseUrl: resolvedTui.baseUrl,
|
|
1063
|
+
ollamaUrl: resolvedTui.ollamaUrl,
|
|
1064
|
+
providerBaseUrls: resolvedTui.providerBaseUrls,
|
|
599
1065
|
workspaceRoot: WORKSPACE_ROOT,
|
|
600
1066
|
});
|
|
601
1067
|
return;
|
|
@@ -604,6 +1070,10 @@ async function main() {
|
|
|
604
1070
|
await runInit(args.slice(1), "init");
|
|
605
1071
|
return;
|
|
606
1072
|
}
|
|
1073
|
+
if (command === "connect") {
|
|
1074
|
+
await runConnect(args.slice(1));
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
607
1077
|
if (command === "turnkey") {
|
|
608
1078
|
await runInit(args.slice(1), "turnkey");
|
|
609
1079
|
return;
|