zelari-code 2.20.0 → 2.22.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/dist/cli/main.bundled.js +461 -76
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +22 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/httpTransport.js +184 -0
- package/dist/cli/mcp/httpTransport.js.map +1 -0
- package/dist/cli/mcp/mcpClient.js +153 -54
- package/dist/cli/mcp/mcpClient.js.map +1 -1
- package/dist/cli/mcp/mcpConfigIo.js +26 -5
- package/dist/cli/mcp/mcpConfigIo.js.map +1 -1
- package/dist/cli/mcp/mcpManager.js +51 -3
- package/dist/cli/mcp/mcpManager.js.map +1 -1
- package/dist/cli/mcp/mcpPresets.js +42 -1
- package/dist/cli/mcp/mcpPresets.js.map +1 -1
- package/dist/cli/provider/anthropic.js +30 -2
- package/dist/cli/provider/anthropic.js.map +1 -1
- package/dist/cli/provider/chatgpt.js +29 -2
- package/dist/cli/provider/chatgpt.js.map +1 -1
- package/dist/cli/provider/openai-compatible.js +3 -3
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/dist/cli/skillConfigIo.js +1 -0
- package/dist/cli/skillConfigIo.js.map +1 -1
- package/dist/cli/utils/doctor.js +10 -3
- package/dist/cli/utils/doctor.js.map +1 -1
- package/dist/cli/utils/prereqChecks.js +2 -2
- package/package.json +2 -2
package/dist/cli/main.bundled.js
CHANGED
|
@@ -44725,7 +44725,10 @@ var init_semantic2 = __esm({
|
|
|
44725
44725
|
// src/cli/provider/openai-compatible.ts
|
|
44726
44726
|
var openai_compatible_exports = {};
|
|
44727
44727
|
__export(openai_compatible_exports, {
|
|
44728
|
+
PROVIDER_CONNECT_TIMEOUT_MS: () => PROVIDER_CONNECT_TIMEOUT_MS,
|
|
44728
44729
|
PROVIDER_ENDPOINTS: () => PROVIDER_ENDPOINTS,
|
|
44730
|
+
PROVIDER_STREAM_IDLE_MS: () => PROVIDER_STREAM_IDLE_MS,
|
|
44731
|
+
PROVIDER_STREAM_MAX_MS: () => PROVIDER_STREAM_MAX_MS,
|
|
44729
44732
|
dataUriFromImage: () => dataUriFromImage,
|
|
44730
44733
|
modelSupportsVision: () => modelSupportsVision,
|
|
44731
44734
|
openaiCompatibleProvider: () => openaiCompatibleProvider,
|
|
@@ -46760,14 +46763,27 @@ function anthropicMessagesProvider(config2) {
|
|
|
46760
46763
|
const base2 = config2.baseUrl.replace(/\/$/, "").replace(/\/v1$/, "");
|
|
46761
46764
|
const url2 = `${base2}/v1/messages`;
|
|
46762
46765
|
let response;
|
|
46766
|
+
const connectController = new AbortController();
|
|
46767
|
+
const connectTimer = setTimeout(
|
|
46768
|
+
() => connectController.abort(
|
|
46769
|
+
new Error(
|
|
46770
|
+
`Provider connect timeout after ${Math.round(PROVIDER_CONNECT_TIMEOUT_MS / 1e3)}s (no response headers). Override ZELARI_PROVIDER_CONNECT_TIMEOUT_MS.`
|
|
46771
|
+
)
|
|
46772
|
+
),
|
|
46773
|
+
PROVIDER_CONNECT_TIMEOUT_MS
|
|
46774
|
+
);
|
|
46775
|
+
const signals = [connectController.signal];
|
|
46776
|
+
if (params.signal) signals.push(params.signal);
|
|
46763
46777
|
try {
|
|
46764
46778
|
response = await fetch(url2, {
|
|
46765
46779
|
method: "POST",
|
|
46766
46780
|
headers: authHeaders(config2.apiKey, ttlPref === "1h"),
|
|
46767
46781
|
body: JSON.stringify(body),
|
|
46768
|
-
signal:
|
|
46782
|
+
signal: signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
|
46769
46783
|
});
|
|
46784
|
+
clearTimeout(connectTimer);
|
|
46770
46785
|
} catch (err) {
|
|
46786
|
+
clearTimeout(connectTimer);
|
|
46771
46787
|
yield {
|
|
46772
46788
|
kind: "error",
|
|
46773
46789
|
message: `Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -46802,9 +46818,17 @@ function anthropicMessagesProvider(config2) {
|
|
|
46802
46818
|
};
|
|
46803
46819
|
currentTool = null;
|
|
46804
46820
|
};
|
|
46821
|
+
const streamStartedAt = Date.now();
|
|
46822
|
+
let lastUsefulAt = streamStartedAt;
|
|
46823
|
+
const streamDeadline = streamStartedAt + PROVIDER_STREAM_MAX_MS;
|
|
46805
46824
|
try {
|
|
46806
46825
|
while (true) {
|
|
46807
|
-
const { value, done } = await reader
|
|
46826
|
+
const { value, done } = await readChunkWithTimeout(reader, {
|
|
46827
|
+
idleMs: PROVIDER_STREAM_IDLE_MS,
|
|
46828
|
+
deadlineMs: streamDeadline,
|
|
46829
|
+
signal: params.signal,
|
|
46830
|
+
lastUsefulAt: () => lastUsefulAt
|
|
46831
|
+
});
|
|
46808
46832
|
if (done) break;
|
|
46809
46833
|
buffer += decoder.decode(value, { stream: true });
|
|
46810
46834
|
const lines = buffer.split("\n");
|
|
@@ -46821,6 +46845,7 @@ function anthropicMessagesProvider(config2) {
|
|
|
46821
46845
|
continue;
|
|
46822
46846
|
}
|
|
46823
46847
|
const type = ev.type;
|
|
46848
|
+
if (type !== "ping") lastUsefulAt = Date.now();
|
|
46824
46849
|
if (type === "content_block_delta") {
|
|
46825
46850
|
const delta = ev.delta;
|
|
46826
46851
|
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
@@ -46894,6 +46919,7 @@ var ANTHROPIC_VERSION, ANTHROPIC_BETA, ANTHROPIC_BETA_EXTENDED_CACHE_TTL;
|
|
|
46894
46919
|
var init_anthropic = __esm({
|
|
46895
46920
|
"src/cli/provider/anthropic.ts"() {
|
|
46896
46921
|
"use strict";
|
|
46922
|
+
init_openai_compatible();
|
|
46897
46923
|
init_chatStats();
|
|
46898
46924
|
init_thinking();
|
|
46899
46925
|
ANTHROPIC_VERSION = "2023-06-01";
|
|
@@ -46972,14 +46998,27 @@ function chatgptResponsesProvider(config2) {
|
|
|
46972
46998
|
const base2 = config2.baseUrl.replace(/\/$/, "");
|
|
46973
46999
|
const url2 = `${base2}/responses`;
|
|
46974
47000
|
let response;
|
|
47001
|
+
const connectController = new AbortController();
|
|
47002
|
+
const connectTimer = setTimeout(
|
|
47003
|
+
() => connectController.abort(
|
|
47004
|
+
new Error(
|
|
47005
|
+
`Provider connect timeout after ${Math.round(PROVIDER_CONNECT_TIMEOUT_MS / 1e3)}s (no response headers). Override ZELARI_PROVIDER_CONNECT_TIMEOUT_MS.`
|
|
47006
|
+
)
|
|
47007
|
+
),
|
|
47008
|
+
PROVIDER_CONNECT_TIMEOUT_MS
|
|
47009
|
+
);
|
|
47010
|
+
const signals = [connectController.signal];
|
|
47011
|
+
if (params.signal) signals.push(params.signal);
|
|
46975
47012
|
try {
|
|
46976
47013
|
response = await fetch(url2, {
|
|
46977
47014
|
method: "POST",
|
|
46978
47015
|
headers: headers(config2),
|
|
46979
47016
|
body: JSON.stringify(body),
|
|
46980
|
-
signal:
|
|
47017
|
+
signal: signals.length === 1 ? signals[0] : AbortSignal.any(signals)
|
|
46981
47018
|
});
|
|
47019
|
+
clearTimeout(connectTimer);
|
|
46982
47020
|
} catch (err) {
|
|
47021
|
+
clearTimeout(connectTimer);
|
|
46983
47022
|
yield {
|
|
46984
47023
|
kind: "error",
|
|
46985
47024
|
message: `Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -47009,9 +47048,17 @@ function chatgptResponsesProvider(config2) {
|
|
|
47009
47048
|
emittedTool = true;
|
|
47010
47049
|
yield { kind: "tool_call", toolCallId: t.id, toolName: t.name, args };
|
|
47011
47050
|
};
|
|
47051
|
+
const streamStartedAt = Date.now();
|
|
47052
|
+
let lastUsefulAt = streamStartedAt;
|
|
47053
|
+
const streamDeadline = streamStartedAt + PROVIDER_STREAM_MAX_MS;
|
|
47012
47054
|
try {
|
|
47013
47055
|
while (true) {
|
|
47014
|
-
const { value, done } = await reader
|
|
47056
|
+
const { value, done } = await readChunkWithTimeout(reader, {
|
|
47057
|
+
idleMs: PROVIDER_STREAM_IDLE_MS,
|
|
47058
|
+
deadlineMs: streamDeadline,
|
|
47059
|
+
signal: params.signal,
|
|
47060
|
+
lastUsefulAt: () => lastUsefulAt
|
|
47061
|
+
});
|
|
47015
47062
|
if (done) break;
|
|
47016
47063
|
buffer += decoder.decode(value, { stream: true });
|
|
47017
47064
|
const lines = buffer.split("\n");
|
|
@@ -47028,6 +47075,7 @@ function chatgptResponsesProvider(config2) {
|
|
|
47028
47075
|
continue;
|
|
47029
47076
|
}
|
|
47030
47077
|
const type = typeof ev.type === "string" ? ev.type : "";
|
|
47078
|
+
if (type) lastUsefulAt = Date.now();
|
|
47031
47079
|
if (type === "response.output_text.delta" && typeof ev.delta === "string") {
|
|
47032
47080
|
yield { kind: "text", delta: ev.delta };
|
|
47033
47081
|
} else if (type === "response.reasoning_text.delta" && typeof ev.delta === "string") {
|
|
@@ -47083,6 +47131,7 @@ function chatgptResponsesProvider(config2) {
|
|
|
47083
47131
|
var init_chatgpt = __esm({
|
|
47084
47132
|
"src/cli/provider/chatgpt.ts"() {
|
|
47085
47133
|
"use strict";
|
|
47134
|
+
init_openai_compatible();
|
|
47086
47135
|
init_thinking();
|
|
47087
47136
|
}
|
|
47088
47137
|
});
|
|
@@ -55304,39 +55353,224 @@ var init_toolRegistry2 = __esm({
|
|
|
55304
55353
|
}
|
|
55305
55354
|
});
|
|
55306
55355
|
|
|
55356
|
+
// src/cli/mcp/httpTransport.ts
|
|
55357
|
+
var SSE_DATA_RE, ABORT_GRACE_MS, McpHttpTransport;
|
|
55358
|
+
var init_httpTransport = __esm({
|
|
55359
|
+
"src/cli/mcp/httpTransport.ts"() {
|
|
55360
|
+
"use strict";
|
|
55361
|
+
SSE_DATA_RE = /^data:\s?(.*)$/;
|
|
55362
|
+
ABORT_GRACE_MS = 250;
|
|
55363
|
+
McpHttpTransport = class {
|
|
55364
|
+
constructor(opts) {
|
|
55365
|
+
this.opts = opts;
|
|
55366
|
+
}
|
|
55367
|
+
sessionId = null;
|
|
55368
|
+
closed = false;
|
|
55369
|
+
reinit = null;
|
|
55370
|
+
controllers = /* @__PURE__ */ new Set();
|
|
55371
|
+
get hasSession() {
|
|
55372
|
+
return this.sessionId !== null;
|
|
55373
|
+
}
|
|
55374
|
+
/**
|
|
55375
|
+
* Deliver one JSON-RPC message. Resolves once the response (if any) has
|
|
55376
|
+
* been fed to onMessage. Transport-level failures are converted into
|
|
55377
|
+
* JSON-RPC error responses so the client's pending map rejects cleanly
|
|
55378
|
+
* through the same pump used for stdio.
|
|
55379
|
+
*/
|
|
55380
|
+
async send(msg, timeoutMs2) {
|
|
55381
|
+
if (this.closed) throw new Error(`[mcp:${this.opts.serverName}] transport closed`);
|
|
55382
|
+
try {
|
|
55383
|
+
await this.post(msg, timeoutMs2, false);
|
|
55384
|
+
} catch (err) {
|
|
55385
|
+
if (msg.id !== void 0) {
|
|
55386
|
+
this.opts.onMessage({
|
|
55387
|
+
jsonrpc: "2.0",
|
|
55388
|
+
id: msg.id,
|
|
55389
|
+
error: {
|
|
55390
|
+
code: -32e3,
|
|
55391
|
+
message: err instanceof Error ? err.message : String(err)
|
|
55392
|
+
}
|
|
55393
|
+
});
|
|
55394
|
+
}
|
|
55395
|
+
}
|
|
55396
|
+
}
|
|
55397
|
+
/** Best-effort session teardown (HTTP DELETE), then abort in-flight POSTs. */
|
|
55398
|
+
close() {
|
|
55399
|
+
this.closed = true;
|
|
55400
|
+
for (const ac of this.controllers) ac.abort();
|
|
55401
|
+
this.controllers.clear();
|
|
55402
|
+
const sid = this.sessionId;
|
|
55403
|
+
this.sessionId = null;
|
|
55404
|
+
if (!sid) return;
|
|
55405
|
+
const headers2 = {
|
|
55406
|
+
...this.opts.headers ?? {},
|
|
55407
|
+
"mcp-session-id": sid
|
|
55408
|
+
};
|
|
55409
|
+
void fetch(this.opts.url, { method: "DELETE", headers: headers2 }).catch(() => {
|
|
55410
|
+
});
|
|
55411
|
+
}
|
|
55412
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
55413
|
+
async post(msg, timeoutMs2, replayed) {
|
|
55414
|
+
const ac = new AbortController();
|
|
55415
|
+
this.controllers.add(ac);
|
|
55416
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs2 + ABORT_GRACE_MS);
|
|
55417
|
+
const hadSession = this.sessionId !== null;
|
|
55418
|
+
try {
|
|
55419
|
+
const headers2 = {
|
|
55420
|
+
"content-type": "application/json",
|
|
55421
|
+
accept: "application/json, text/event-stream",
|
|
55422
|
+
...this.opts.headers ?? {}
|
|
55423
|
+
};
|
|
55424
|
+
if (this.sessionId) headers2["mcp-session-id"] = this.sessionId;
|
|
55425
|
+
const res = await fetch(this.opts.url, {
|
|
55426
|
+
method: "POST",
|
|
55427
|
+
headers: headers2,
|
|
55428
|
+
signal: ac.signal,
|
|
55429
|
+
body: JSON.stringify({ jsonrpc: "2.0", ...msg })
|
|
55430
|
+
});
|
|
55431
|
+
const sid = res.headers.get("mcp-session-id");
|
|
55432
|
+
if (sid) this.sessionId = sid;
|
|
55433
|
+
if (res.status === 404 && hadSession && !replayed && msg.id !== void 0) {
|
|
55434
|
+
this.sessionId = null;
|
|
55435
|
+
await this.ensureSession();
|
|
55436
|
+
return this.post(msg, timeoutMs2, true);
|
|
55437
|
+
}
|
|
55438
|
+
if (!res.ok && res.status !== 202) {
|
|
55439
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());
|
|
55440
|
+
}
|
|
55441
|
+
if (msg.id === void 0) {
|
|
55442
|
+
await res.body?.cancel();
|
|
55443
|
+
return;
|
|
55444
|
+
}
|
|
55445
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
55446
|
+
if (contentType.includes("text/event-stream")) {
|
|
55447
|
+
await this.readSse(res);
|
|
55448
|
+
} else {
|
|
55449
|
+
const body = await res.text();
|
|
55450
|
+
if (body) this.opts.onMessage(JSON.parse(body));
|
|
55451
|
+
}
|
|
55452
|
+
} finally {
|
|
55453
|
+
clearTimeout(timer);
|
|
55454
|
+
this.controllers.delete(ac);
|
|
55455
|
+
}
|
|
55456
|
+
}
|
|
55457
|
+
/** Dedupe concurrent session-loss recoveries into one handshake. */
|
|
55458
|
+
async ensureSession() {
|
|
55459
|
+
if (!this.reinit) {
|
|
55460
|
+
this.reinit = this.opts.onSessionLost().finally(() => {
|
|
55461
|
+
this.reinit = null;
|
|
55462
|
+
}).catch(() => {
|
|
55463
|
+
});
|
|
55464
|
+
}
|
|
55465
|
+
await this.reinit;
|
|
55466
|
+
}
|
|
55467
|
+
/** Minimal SSE reader: dispatch complete `data:` events to onMessage. */
|
|
55468
|
+
async readSse(res) {
|
|
55469
|
+
const reader = res.body?.getReader();
|
|
55470
|
+
if (!reader) return;
|
|
55471
|
+
const decoder = new TextDecoder();
|
|
55472
|
+
let buf = "";
|
|
55473
|
+
let data = "";
|
|
55474
|
+
const dispatch = () => {
|
|
55475
|
+
const payload = data.trim();
|
|
55476
|
+
data = "";
|
|
55477
|
+
if (!payload) return;
|
|
55478
|
+
try {
|
|
55479
|
+
this.opts.onMessage(JSON.parse(payload));
|
|
55480
|
+
} catch {
|
|
55481
|
+
}
|
|
55482
|
+
};
|
|
55483
|
+
for (; ; ) {
|
|
55484
|
+
const { done, value } = await reader.read();
|
|
55485
|
+
if (done) break;
|
|
55486
|
+
buf += decoder.decode(value, { stream: true });
|
|
55487
|
+
let nl;
|
|
55488
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
55489
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
55490
|
+
buf = buf.slice(nl + 1);
|
|
55491
|
+
if (line === "") {
|
|
55492
|
+
dispatch();
|
|
55493
|
+
continue;
|
|
55494
|
+
}
|
|
55495
|
+
const m = SSE_DATA_RE.exec(line);
|
|
55496
|
+
if (m) data += (data ? "\n" : "") + m[1];
|
|
55497
|
+
}
|
|
55498
|
+
}
|
|
55499
|
+
dispatch();
|
|
55500
|
+
}
|
|
55501
|
+
};
|
|
55502
|
+
}
|
|
55503
|
+
});
|
|
55504
|
+
|
|
55307
55505
|
// src/cli/mcp/mcpClient.ts
|
|
55308
55506
|
import { spawn as spawn14 } from "node:child_process";
|
|
55309
|
-
var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
|
|
55507
|
+
var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MAX_LIST_PAGES, MCP_PROTOCOL_VERSION, McpClient;
|
|
55310
55508
|
var init_mcpClient = __esm({
|
|
55311
55509
|
"src/cli/mcp/mcpClient.ts"() {
|
|
55312
55510
|
"use strict";
|
|
55313
55511
|
init_cmdline();
|
|
55314
55512
|
init_updater();
|
|
55513
|
+
init_httpTransport();
|
|
55315
55514
|
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
55316
55515
|
INIT_TIMEOUT_MS = 15e3;
|
|
55516
|
+
MAX_LIST_PAGES = 50;
|
|
55317
55517
|
MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
55318
55518
|
McpClient = class {
|
|
55319
55519
|
constructor(serverName, config2) {
|
|
55320
55520
|
this.serverName = serverName;
|
|
55321
55521
|
this.config = config2;
|
|
55522
|
+
this.defaultTimeoutMs = config2.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
55523
|
+
this.serial = config2.serial ?? this.transportKind() === "http";
|
|
55322
55524
|
}
|
|
55323
55525
|
child = null;
|
|
55526
|
+
transport = null;
|
|
55324
55527
|
nextId = 1;
|
|
55325
55528
|
pending = /* @__PURE__ */ new Map();
|
|
55326
55529
|
stdoutBuffer = "";
|
|
55327
55530
|
closed = false;
|
|
55328
|
-
/**
|
|
55531
|
+
/** Tail of the serial queue (config.serial); requests run one at a time. */
|
|
55532
|
+
queueTail = Promise.resolve();
|
|
55533
|
+
/** True while the 404-recovery handshake runs: bypasses the serial queue
|
|
55534
|
+
* (the queued request that triggered the 404 is waiting on this handshake). */
|
|
55535
|
+
recovering = false;
|
|
55536
|
+
serial;
|
|
55537
|
+
defaultTimeoutMs;
|
|
55538
|
+
transportKind() {
|
|
55539
|
+
if (this.config.type) return this.config.type;
|
|
55540
|
+
return this.config.url && !this.config.command ? "http" : "stdio";
|
|
55541
|
+
}
|
|
55542
|
+
/** Connect (spawn / HTTP session) and run the MCP initialize handshake. */
|
|
55329
55543
|
async start() {
|
|
55330
|
-
if (this.child) return;
|
|
55544
|
+
if (this.child || this.transport) return;
|
|
55545
|
+
if (this.transportKind() === "http") {
|
|
55546
|
+
const url2 = this.config.url;
|
|
55547
|
+
if (!url2) {
|
|
55548
|
+
throw new Error(`[mcp:${this.serverName}] http server requires a url`);
|
|
55549
|
+
}
|
|
55550
|
+
this.transport = new McpHttpTransport({
|
|
55551
|
+
serverName: this.serverName,
|
|
55552
|
+
url: url2,
|
|
55553
|
+
// env may carry an explicit Authorization header for remote servers.
|
|
55554
|
+
headers: this.config.env?.AUTHORIZATION ? { Authorization: this.config.env.AUTHORIZATION } : void 0,
|
|
55555
|
+
onMessage: (msg) => this.handleMessage(msg),
|
|
55556
|
+
onSessionLost: () => this.handshake()
|
|
55557
|
+
});
|
|
55558
|
+
await this.handshake();
|
|
55559
|
+
return;
|
|
55560
|
+
}
|
|
55561
|
+
const command = this.config.command;
|
|
55562
|
+
if (!command) {
|
|
55563
|
+
throw new Error(`[mcp:${this.serverName}] stdio server requires a command`);
|
|
55564
|
+
}
|
|
55331
55565
|
const spawnOpts = {
|
|
55332
55566
|
stdio: ["pipe", "pipe", "pipe"],
|
|
55333
55567
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
55334
55568
|
windowsHide: true
|
|
55335
55569
|
};
|
|
55336
|
-
const child = process.platform === "win32" ? spawn14(buildCmdLine(
|
|
55570
|
+
const child = process.platform === "win32" ? spawn14(buildCmdLine(command, this.config.args ?? []), {
|
|
55337
55571
|
...spawnOpts,
|
|
55338
55572
|
shell: true
|
|
55339
|
-
}) : spawn14(
|
|
55573
|
+
}) : spawn14(command, this.config.args ?? [], spawnOpts);
|
|
55340
55574
|
this.child = child;
|
|
55341
55575
|
child.stdout.setEncoding("utf8");
|
|
55342
55576
|
child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
@@ -55355,37 +55589,60 @@ var init_mcpClient = __esm({
|
|
|
55355
55589
|
);
|
|
55356
55590
|
}
|
|
55357
55591
|
});
|
|
55358
|
-
await this.
|
|
55359
|
-
"initialize",
|
|
55360
|
-
{
|
|
55361
|
-
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
55362
|
-
capabilities: {},
|
|
55363
|
-
clientInfo: { name: "zelari-code", version: getCurrentVersion() }
|
|
55364
|
-
},
|
|
55365
|
-
INIT_TIMEOUT_MS
|
|
55366
|
-
);
|
|
55367
|
-
this.notify("notifications/initialized", {});
|
|
55592
|
+
await this.handshake();
|
|
55368
55593
|
}
|
|
55369
|
-
|
|
55594
|
+
async handshake() {
|
|
55595
|
+
const prev2 = this.recovering;
|
|
55596
|
+
this.recovering = true;
|
|
55597
|
+
try {
|
|
55598
|
+
await this.request(
|
|
55599
|
+
"initialize",
|
|
55600
|
+
{
|
|
55601
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
55602
|
+
capabilities: {},
|
|
55603
|
+
clientInfo: { name: "zelari-code", version: getCurrentVersion() }
|
|
55604
|
+
},
|
|
55605
|
+
INIT_TIMEOUT_MS
|
|
55606
|
+
);
|
|
55607
|
+
this.notify("notifications/initialized", {});
|
|
55608
|
+
} finally {
|
|
55609
|
+
this.recovering = prev2;
|
|
55610
|
+
}
|
|
55611
|
+
}
|
|
55612
|
+
/**
|
|
55613
|
+
* Discover the server's tools. Follows `nextCursor` pagination so
|
|
55614
|
+
* large servers (hundreds of tools) are listed completely.
|
|
55615
|
+
*/
|
|
55370
55616
|
async listTools() {
|
|
55371
|
-
const
|
|
55372
|
-
|
|
55373
|
-
|
|
55374
|
-
|
|
55375
|
-
|
|
55376
|
-
|
|
55377
|
-
|
|
55378
|
-
|
|
55617
|
+
const tools = [];
|
|
55618
|
+
let cursor;
|
|
55619
|
+
for (let page = 0; page < MAX_LIST_PAGES; page++) {
|
|
55620
|
+
const res = await this.request(
|
|
55621
|
+
"tools/list",
|
|
55622
|
+
cursor ? { cursor } : {}
|
|
55623
|
+
);
|
|
55624
|
+
for (const t of res.tools ?? []) {
|
|
55625
|
+
if (!t.name) continue;
|
|
55626
|
+
tools.push({
|
|
55627
|
+
name: t.name,
|
|
55628
|
+
description: t.description ?? "",
|
|
55629
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} }
|
|
55630
|
+
});
|
|
55631
|
+
}
|
|
55632
|
+
cursor = typeof res.nextCursor === "string" ? res.nextCursor : void 0;
|
|
55633
|
+
if (!cursor) break;
|
|
55634
|
+
}
|
|
55635
|
+
return tools;
|
|
55379
55636
|
}
|
|
55380
55637
|
/**
|
|
55381
55638
|
* Call a tool. Returns the concatenated text content; non-text content
|
|
55382
55639
|
* items are summarized by type. Throws when the server flags isError.
|
|
55383
55640
|
*/
|
|
55384
|
-
async callTool(name, args, timeoutMs2
|
|
55641
|
+
async callTool(name, args, timeoutMs2) {
|
|
55385
55642
|
const res = await this.request(
|
|
55386
55643
|
"tools/call",
|
|
55387
55644
|
{ name, arguments: args },
|
|
55388
|
-
timeoutMs2
|
|
55645
|
+
timeoutMs2 ?? this.defaultTimeoutMs
|
|
55389
55646
|
);
|
|
55390
55647
|
const text = (res.content ?? []).map(
|
|
55391
55648
|
(c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
|
|
@@ -55394,20 +55651,39 @@ var init_mcpClient = __esm({
|
|
|
55394
55651
|
throw new Error(text || `tool "${name}" reported an error`);
|
|
55395
55652
|
return text;
|
|
55396
55653
|
}
|
|
55397
|
-
/** Terminate the server
|
|
55654
|
+
/** Terminate the server / session and reject all in-flight requests. */
|
|
55398
55655
|
close() {
|
|
55399
55656
|
this.closed = true;
|
|
55400
55657
|
this.failAll(new Error(`[mcp:${this.serverName}] client closed`));
|
|
55658
|
+
if (this.transport) {
|
|
55659
|
+
this.transport.close();
|
|
55660
|
+
this.transport = null;
|
|
55661
|
+
return;
|
|
55662
|
+
}
|
|
55401
55663
|
this.child?.kill();
|
|
55402
55664
|
this.child = null;
|
|
55403
55665
|
}
|
|
55404
|
-
// ── JSON-RPC plumbing
|
|
55405
|
-
request(method, params, timeoutMs2
|
|
55666
|
+
// ── JSON-RPC plumbing (shared by both transports) ────────────────────
|
|
55667
|
+
request(method, params, timeoutMs2) {
|
|
55668
|
+
const effective = timeoutMs2 ?? this.defaultTimeoutMs;
|
|
55669
|
+
if (!this.serial || this.recovering)
|
|
55670
|
+
return this.dispatch(method, params, effective);
|
|
55671
|
+
const run = this.queueTail.then(
|
|
55672
|
+
() => this.dispatch(method, params, effective),
|
|
55673
|
+
() => this.dispatch(method, params, effective)
|
|
55674
|
+
);
|
|
55675
|
+
this.queueTail = run.then(
|
|
55676
|
+
() => void 0,
|
|
55677
|
+
() => void 0
|
|
55678
|
+
);
|
|
55679
|
+
return run;
|
|
55680
|
+
}
|
|
55681
|
+
dispatch(method, params, timeoutMs2) {
|
|
55682
|
+
const transport = this.transport;
|
|
55406
55683
|
const child = this.child;
|
|
55407
|
-
if (!child)
|
|
55684
|
+
if (!transport && !child)
|
|
55408
55685
|
return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
|
|
55409
55686
|
const id3 = this.nextId++;
|
|
55410
|
-
const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
|
|
55411
55687
|
return new Promise((resolve9, reject) => {
|
|
55412
55688
|
const timer = setTimeout(() => {
|
|
55413
55689
|
this.pending.delete(id3);
|
|
@@ -55418,16 +55694,25 @@ var init_mcpClient = __esm({
|
|
|
55418
55694
|
);
|
|
55419
55695
|
}, timeoutMs2);
|
|
55420
55696
|
this.pending.set(id3, { resolve: resolve9, reject, timer });
|
|
55421
|
-
|
|
55422
|
-
|
|
55423
|
-
|
|
55424
|
-
|
|
55425
|
-
|
|
55426
|
-
|
|
55427
|
-
|
|
55697
|
+
if (transport) {
|
|
55698
|
+
void transport.send({ id: id3, method, params }, timeoutMs2);
|
|
55699
|
+
} else {
|
|
55700
|
+
const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
|
|
55701
|
+
child.stdin.write(payload + "\n", (err) => {
|
|
55702
|
+
if (err) {
|
|
55703
|
+
clearTimeout(timer);
|
|
55704
|
+
this.pending.delete(id3);
|
|
55705
|
+
reject(err);
|
|
55706
|
+
}
|
|
55707
|
+
});
|
|
55708
|
+
}
|
|
55428
55709
|
});
|
|
55429
55710
|
}
|
|
55430
55711
|
notify(method, params) {
|
|
55712
|
+
if (this.transport) {
|
|
55713
|
+
void this.transport.send({ method, params }, this.defaultTimeoutMs);
|
|
55714
|
+
return;
|
|
55715
|
+
}
|
|
55431
55716
|
this.child?.stdin.write(
|
|
55432
55717
|
JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n"
|
|
55433
55718
|
);
|
|
@@ -55439,26 +55724,29 @@ var init_mcpClient = __esm({
|
|
|
55439
55724
|
const line = this.stdoutBuffer.slice(0, nl).trim();
|
|
55440
55725
|
this.stdoutBuffer = this.stdoutBuffer.slice(nl + 1);
|
|
55441
55726
|
if (!line) continue;
|
|
55442
|
-
let msg;
|
|
55443
55727
|
try {
|
|
55444
|
-
|
|
55728
|
+
this.handleMessage(JSON.parse(line));
|
|
55445
55729
|
} catch {
|
|
55446
55730
|
continue;
|
|
55447
55731
|
}
|
|
55448
|
-
|
|
55449
|
-
|
|
55450
|
-
|
|
55451
|
-
|
|
55452
|
-
|
|
55453
|
-
|
|
55454
|
-
|
|
55455
|
-
|
|
55456
|
-
|
|
55457
|
-
|
|
55458
|
-
|
|
55459
|
-
|
|
55460
|
-
|
|
55461
|
-
|
|
55732
|
+
}
|
|
55733
|
+
}
|
|
55734
|
+
/** Route one parsed JSON-RPC message to its pending request (both transports). */
|
|
55735
|
+
handleMessage(msg) {
|
|
55736
|
+
const m = msg;
|
|
55737
|
+
if (typeof m.id !== "number") return;
|
|
55738
|
+
const pending = this.pending.get(m.id);
|
|
55739
|
+
if (!pending) return;
|
|
55740
|
+
this.pending.delete(m.id);
|
|
55741
|
+
clearTimeout(pending.timer);
|
|
55742
|
+
if (m.error) {
|
|
55743
|
+
pending.reject(
|
|
55744
|
+
new Error(
|
|
55745
|
+
`[mcp:${this.serverName}] ${m.error.message ?? "JSON-RPC error"} (code ${m.error.code ?? "?"})`
|
|
55746
|
+
)
|
|
55747
|
+
);
|
|
55748
|
+
} else {
|
|
55749
|
+
pending.resolve(m.result);
|
|
55462
55750
|
}
|
|
55463
55751
|
}
|
|
55464
55752
|
failAll(err) {
|
|
@@ -55493,11 +55781,17 @@ function readFile6(path91) {
|
|
|
55493
55781
|
const parsed = JSON.parse(readFileSync29(path91, "utf8"));
|
|
55494
55782
|
const out = {};
|
|
55495
55783
|
for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
|
|
55496
|
-
|
|
55784
|
+
const hasCommand = !!cfg && typeof cfg.command === "string" && !!cfg.command.trim();
|
|
55785
|
+
const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
|
|
55786
|
+
if (!cfg || !hasCommand && !hasUrl) continue;
|
|
55497
55787
|
out[name] = {
|
|
55498
|
-
command: cfg.command.trim(),
|
|
55788
|
+
command: hasCommand ? cfg.command.trim() : void 0,
|
|
55499
55789
|
args: Array.isArray(cfg.args) ? cfg.args.map(String) : void 0,
|
|
55500
55790
|
env: cfg.env && typeof cfg.env === "object" ? cfg.env : void 0,
|
|
55791
|
+
type: hasUrl && !hasCommand ? "http" : cfg.type === "http" ? "http" : "stdio",
|
|
55792
|
+
url: hasUrl ? cfg.url.trim() : void 0,
|
|
55793
|
+
timeoutMs: typeof cfg.timeoutMs === "number" && cfg.timeoutMs > 0 ? cfg.timeoutMs : void 0,
|
|
55794
|
+
serial: typeof cfg.serial === "boolean" ? cfg.serial : void 0,
|
|
55501
55795
|
enabled: cfg.enabled !== false
|
|
55502
55796
|
};
|
|
55503
55797
|
}
|
|
@@ -55539,8 +55833,13 @@ function upsertMcpServer(opts) {
|
|
|
55539
55833
|
error: "Invalid server name (use letters, digits, _ -)"
|
|
55540
55834
|
};
|
|
55541
55835
|
}
|
|
55542
|
-
|
|
55543
|
-
|
|
55836
|
+
const hasCommand = !!opts.config.command?.trim();
|
|
55837
|
+
const hasUrl = typeof opts.config.url === "string" && /^https?:\/\//i.test(opts.config.url);
|
|
55838
|
+
if (!hasCommand && !hasUrl) {
|
|
55839
|
+
return {
|
|
55840
|
+
ok: false,
|
|
55841
|
+
error: "either command (stdio) or url (http) is required"
|
|
55842
|
+
};
|
|
55544
55843
|
}
|
|
55545
55844
|
let path91;
|
|
55546
55845
|
if (opts.scope === "user") {
|
|
@@ -55557,9 +55856,13 @@ function upsertMcpServer(opts) {
|
|
|
55557
55856
|
}
|
|
55558
55857
|
const current = readFile6(path91);
|
|
55559
55858
|
current[name] = {
|
|
55560
|
-
command: opts.config.command.trim(),
|
|
55859
|
+
command: hasCommand ? opts.config.command.trim() : void 0,
|
|
55561
55860
|
args: opts.config.args,
|
|
55562
55861
|
env: opts.config.env,
|
|
55862
|
+
type: hasUrl ? "http" : opts.config.type === "http" ? "http" : "stdio",
|
|
55863
|
+
url: hasUrl ? opts.config.url.trim() : void 0,
|
|
55864
|
+
timeoutMs: opts.config.timeoutMs,
|
|
55865
|
+
serial: opts.config.serial,
|
|
55563
55866
|
enabled: opts.config.enabled !== false
|
|
55564
55867
|
};
|
|
55565
55868
|
writeFile2(path91, current);
|
|
@@ -55634,9 +55937,35 @@ function buildQwenMmPreset() {
|
|
|
55634
55937
|
]
|
|
55635
55938
|
};
|
|
55636
55939
|
}
|
|
55940
|
+
function buildUnrealPreset() {
|
|
55941
|
+
const url2 = process.env.UNREAL_MCP_URL?.trim() || "http://127.0.0.1:8000/mcp";
|
|
55942
|
+
return {
|
|
55943
|
+
id: "unreal-mcp",
|
|
55944
|
+
servers: {
|
|
55945
|
+
"unreal-mcp": {
|
|
55946
|
+
type: "http",
|
|
55947
|
+
url: url2,
|
|
55948
|
+
// Editor tool calls (asset scans, builds, PIE) can be slow.
|
|
55949
|
+
timeoutMs: 12e4,
|
|
55950
|
+
// Epic guidance: never overlap calls on the editor's game thread.
|
|
55951
|
+
serial: true,
|
|
55952
|
+
enabled: true
|
|
55953
|
+
}
|
|
55954
|
+
},
|
|
55955
|
+
notes: [
|
|
55956
|
+
"Unreal Engine 5.8+ \u2014 MCP server embedded in the editor (Experimental feature).",
|
|
55957
|
+
"Editor: enable the 'Model Context Protocol' plugin, then Edit \u2192 Project Settings \u2192 Plugins \u2192 MCP Server.",
|
|
55958
|
+
`Endpoint: ${url2} (override with UNREAL_MCP_URL; port/path configurable in the editor).`,
|
|
55959
|
+
"Tool Search ON by default: tools surface as list_toolsets / describe_toolset / call_tool.",
|
|
55960
|
+
"Requests are serialized and time out after 120s (editor tools can be slow).",
|
|
55961
|
+
"Start the editor before OR after zelari \u2014 unreachable servers are retried on each turn.",
|
|
55962
|
+
"Kill switch: disable the unreal-mcp server in mcp.json or ZELARI_MCP=0"
|
|
55963
|
+
]
|
|
55964
|
+
};
|
|
55965
|
+
}
|
|
55637
55966
|
function listMcpPresetIds() {
|
|
55638
55967
|
return Object.keys(PRESETS).filter(
|
|
55639
|
-
(k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins"
|
|
55968
|
+
(k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins" || k === "unreal-mcp"
|
|
55640
55969
|
);
|
|
55641
55970
|
}
|
|
55642
55971
|
function getMcpPreset(id3) {
|
|
@@ -55705,7 +56034,10 @@ var init_mcpPresets = __esm({
|
|
|
55705
56034
|
"cua-driver": () => CUA_DRIVER_PRESET,
|
|
55706
56035
|
composio: buildComposioPreset,
|
|
55707
56036
|
"qwen-mm-plugins": buildQwenMmPreset,
|
|
55708
|
-
"qwen-mm": buildQwenMmPreset
|
|
56037
|
+
"qwen-mm": buildQwenMmPreset,
|
|
56038
|
+
unreal: buildUnrealPreset,
|
|
56039
|
+
"unreal-mcp": buildUnrealPreset,
|
|
56040
|
+
unrealEditor: buildUnrealPreset
|
|
55709
56041
|
};
|
|
55710
56042
|
}
|
|
55711
56043
|
});
|
|
@@ -55735,7 +56067,9 @@ function readMcpConfig(projectRoot = process.cwd(), opts) {
|
|
|
55735
56067
|
try {
|
|
55736
56068
|
const parsed = JSON.parse(readFileSync30(p3, "utf8"));
|
|
55737
56069
|
for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
|
|
55738
|
-
|
|
56070
|
+
const hasCommand = !!cfg && typeof cfg.command === "string" && cfg.command.length > 0;
|
|
56071
|
+
const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
|
|
56072
|
+
if (!cfg || !hasCommand && !hasUrl) continue;
|
|
55739
56073
|
merged[name] = cfg;
|
|
55740
56074
|
}
|
|
55741
56075
|
} catch {
|
|
@@ -55776,9 +56110,44 @@ async function ensureLoaded(projectRoot) {
|
|
|
55776
56110
|
}
|
|
55777
56111
|
} catch (err) {
|
|
55778
56112
|
client.close();
|
|
55779
|
-
|
|
56113
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56114
|
+
if (isHttpConfig(cfg)) {
|
|
56115
|
+
state2.pendingHttp.push({ name, cfg });
|
|
56116
|
+
state2.warnings.push(
|
|
56117
|
+
`[mcp:${name}] not reachable yet (${msg}) \u2014 will retry each turn`
|
|
56118
|
+
);
|
|
56119
|
+
} else {
|
|
56120
|
+
state2.warnings.push(`[mcp:${name}] disabled: ${msg}`);
|
|
56121
|
+
}
|
|
56122
|
+
}
|
|
56123
|
+
}
|
|
56124
|
+
}
|
|
56125
|
+
function isHttpConfig(cfg) {
|
|
56126
|
+
return cfg.type === "http" || !!cfg.url && !cfg.command;
|
|
56127
|
+
}
|
|
56128
|
+
async function retryPendingHttp() {
|
|
56129
|
+
if (state2.pendingHttp.length === 0) return;
|
|
56130
|
+
const remaining = [];
|
|
56131
|
+
for (const p3 of state2.pendingHttp) {
|
|
56132
|
+
const client = new McpClient(p3.name, p3.cfg);
|
|
56133
|
+
try {
|
|
56134
|
+
await client.start();
|
|
56135
|
+
const tools = await client.listTools();
|
|
56136
|
+
state2.clients.push(client);
|
|
56137
|
+
for (const info of tools) {
|
|
56138
|
+
state2.tools.push({
|
|
56139
|
+
registryName: sanitizeToolName(`mcp_${p3.name}_${info.name}`),
|
|
56140
|
+
serverName: p3.name,
|
|
56141
|
+
info,
|
|
56142
|
+
client
|
|
56143
|
+
});
|
|
56144
|
+
}
|
|
56145
|
+
} catch {
|
|
56146
|
+
client.close();
|
|
56147
|
+
remaining.push(p3);
|
|
55780
56148
|
}
|
|
55781
56149
|
}
|
|
56150
|
+
state2.pendingHttp = remaining;
|
|
55782
56151
|
}
|
|
55783
56152
|
function sanitizeToolName(raw) {
|
|
55784
56153
|
return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
@@ -55786,6 +56155,7 @@ function sanitizeToolName(raw) {
|
|
|
55786
56155
|
async function registerMcpTools(registry4, projectRoot = process.cwd(), opts) {
|
|
55787
56156
|
if (process.env["ZELARI_MCP"] === "0") return { registered: [], warnings: [] };
|
|
55788
56157
|
await ensureLoaded(projectRoot);
|
|
56158
|
+
await retryPendingHttp();
|
|
55789
56159
|
const skipCuaForCouncil = opts?.councilMode === true && !isCuaAllowedForCouncil();
|
|
55790
56160
|
const registered = [];
|
|
55791
56161
|
for (const t of state2.tools) {
|
|
@@ -55823,6 +56193,7 @@ function closeMcpClients() {
|
|
|
55823
56193
|
state2.tools = [];
|
|
55824
56194
|
state2.loaded = false;
|
|
55825
56195
|
state2.warnings = [];
|
|
56196
|
+
state2.pendingHttp = [];
|
|
55826
56197
|
}
|
|
55827
56198
|
function _resetMcpForTests() {
|
|
55828
56199
|
closeMcpClients();
|
|
@@ -55836,7 +56207,7 @@ var init_mcpManager = __esm({
|
|
|
55836
56207
|
init_mcpClient();
|
|
55837
56208
|
init_mcpPresets();
|
|
55838
56209
|
init_folderTrust();
|
|
55839
|
-
state2 = { loaded: false, tools: [], warnings: [], clients: [] };
|
|
56210
|
+
state2 = { loaded: false, tools: [], warnings: [], clients: [], pendingHttp: [] };
|
|
55840
56211
|
}
|
|
55841
56212
|
});
|
|
55842
56213
|
|
|
@@ -62016,7 +62387,7 @@ var MIN_NODE_MAJOR, STANDARD_BASH_PATHS2, POWERSHELL_EXES2, STANDARD_POWERSHELL_
|
|
|
62016
62387
|
var init_prereqChecks = __esm({
|
|
62017
62388
|
"src/cli/utils/prereqChecks.ts"() {
|
|
62018
62389
|
"use strict";
|
|
62019
|
-
MIN_NODE_MAJOR =
|
|
62390
|
+
MIN_NODE_MAJOR = 24;
|
|
62020
62391
|
STANDARD_BASH_PATHS2 = [
|
|
62021
62392
|
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
62022
62393
|
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
@@ -67537,6 +67908,7 @@ var init_skillConfigIo = __esm({
|
|
|
67537
67908
|
"@zelari/core/skills/builtin/planning",
|
|
67538
67909
|
"@zelari/core/skills/builtin/refactoring",
|
|
67539
67910
|
"@zelari/core/skills/builtin/review",
|
|
67911
|
+
"@zelari/core/skills/builtin/unrealEditor",
|
|
67540
67912
|
"@zelari/core/skills/builtin/testing",
|
|
67541
67913
|
"@zelari/core/skills/builtin/schema-loop",
|
|
67542
67914
|
"@zelari/core/skills/builtin/computer-use-cua",
|
|
@@ -69851,12 +70223,15 @@ function checkNode(pkg) {
|
|
|
69851
70223
|
return WARN(`could not parse node version: ${raw}`);
|
|
69852
70224
|
}
|
|
69853
70225
|
const major = Number(m[1]);
|
|
69854
|
-
|
|
70226
|
+
const enginesNode = pkg?.engines?.node;
|
|
70227
|
+
const requiredMajor = Number(enginesNode?.match(/\d+/)?.[0] ?? 20);
|
|
70228
|
+
if (major < requiredMajor) {
|
|
69855
70229
|
return FAIL(
|
|
69856
|
-
`node ${raw} is older than the required engines.node (>= 20.0.0)
|
|
70230
|
+
`node ${raw} is older than the required engines.node (${enginesNode ?? ">= 20.0.0"})
|
|
70231
|
+
fix: install Node.js ${requiredMajor}+ (LTS) and reopen the terminal`
|
|
69857
70232
|
);
|
|
69858
70233
|
}
|
|
69859
|
-
return OK(`node ${raw}`);
|
|
70234
|
+
return OK(`node ${raw} (engines.node ${enginesNode ?? ">= 20.0.0"})`);
|
|
69860
70235
|
}
|
|
69861
70236
|
function checkBundle() {
|
|
69862
70237
|
const bundle = path88.join(packageRoot, "dist", "cli", "main.bundled.js");
|
|
@@ -80423,7 +80798,7 @@ function pickRootComponent() {
|
|
|
80423
80798
|
}
|
|
80424
80799
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
80425
80800
|
console.log(
|
|
80426
|
-
"zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (
|
|
80801
|
+
"zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
|
|
80427
80802
|
);
|
|
80428
80803
|
process.exit(0);
|
|
80429
80804
|
}
|
|
@@ -80449,6 +80824,8 @@ function pickRootComponent() {
|
|
|
80449
80824
|
};
|
|
80450
80825
|
const name = get("--name");
|
|
80451
80826
|
const command = get("--command");
|
|
80827
|
+
const url2 = get("--url");
|
|
80828
|
+
const timeoutRaw = get("--timeout");
|
|
80452
80829
|
const scopeRaw = get("--scope") ?? "user";
|
|
80453
80830
|
const scope = scopeRaw === "project" ? "project" : "user";
|
|
80454
80831
|
const cwd = get("--cwd") ?? process.cwd();
|
|
@@ -80461,14 +80838,22 @@ function pickRootComponent() {
|
|
|
80461
80838
|
if (!Array.isArray(parsed)) throw new Error("--args must be a JSON array");
|
|
80462
80839
|
args = parsed.map(String);
|
|
80463
80840
|
}
|
|
80464
|
-
|
|
80465
|
-
|
|
80841
|
+
const timeoutMs2 = timeoutRaw !== void 0 ? Number(timeoutRaw) : void 0;
|
|
80842
|
+
if (timeoutMs2 !== void 0 && (!Number.isFinite(timeoutMs2) || timeoutMs2 <= 0)) {
|
|
80843
|
+
throw new Error("--timeout must be a positive number of milliseconds");
|
|
80844
|
+
}
|
|
80845
|
+
if (!name) throw new Error("--name is required");
|
|
80846
|
+
if (!command && !url2) {
|
|
80847
|
+
throw new Error("either --command (stdio) or --url (http) is required");
|
|
80848
|
+
}
|
|
80849
|
+
if (url2 && !/^https?:\/\//i.test(url2)) {
|
|
80850
|
+
throw new Error("--url must be an http(s) endpoint");
|
|
80466
80851
|
}
|
|
80467
80852
|
const result = upsertMcpServer({
|
|
80468
80853
|
scope,
|
|
80469
80854
|
name,
|
|
80470
80855
|
projectRoot: cwd,
|
|
80471
|
-
config: { command, args, enabled }
|
|
80856
|
+
config: url2 ? { type: "http", url: url2, timeoutMs: timeoutMs2, serial: true, enabled } : { command, args, enabled }
|
|
80472
80857
|
});
|
|
80473
80858
|
if (!result.ok) throw new Error(result.error);
|
|
80474
80859
|
console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
|