zelari-code 1.24.0 → 1.25.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/README.md +3 -0
- package/bin/zelari-code.js +44 -12
- package/dist/cli/atMentions.js +179 -0
- package/dist/cli/atMentions.js.map +1 -0
- package/dist/cli/companion/config.js +172 -0
- package/dist/cli/companion/config.js.map +1 -0
- package/dist/cli/companion/runManager.js +250 -0
- package/dist/cli/companion/runManager.js.map +1 -0
- package/dist/cli/companion/serve.js +376 -0
- package/dist/cli/companion/serve.js.map +1 -0
- package/dist/cli/components/InputBar.js +1 -1
- package/dist/cli/components/InputBar.js.map +1 -1
- package/dist/cli/generateSkillFromUrl.js +236 -0
- package/dist/cli/generateSkillFromUrl.js.map +1 -0
- package/dist/cli/hooks/useSlashDispatch.js +19 -6
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/main.bundled.js +2190 -528
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +155 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/provider/openai-compatible.js +164 -41
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/dist/cli/runHeadless.js +15 -0
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/skillCategories.js +15 -0
- package/dist/cli/skillCategories.js.map +1 -0
- package/dist/cli/skillConfigIo.js +285 -0
- package/dist/cli/skillConfigIo.js.map +1 -0
- package/dist/cli/slashCommands.js +14 -5
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/skills.js +30 -0
- package/dist/cli/slashHandlers/skills.js.map +1 -1
- package/package.json +3 -2
- package/scripts/start-companion-serve.ps1 +43 -0
package/dist/cli/main.bundled.js
CHANGED
|
@@ -4,6 +4,12 @@ var __defProp = Object.defineProperty;
|
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
7
13
|
var __esm = (fn, res) => function __init() {
|
|
8
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
15
|
};
|
|
@@ -389,7 +395,7 @@ async function refreshGrokToken(options) {
|
|
|
389
395
|
return parseTokenResponseBody(obj, accessToken);
|
|
390
396
|
}
|
|
391
397
|
async function openBrowser(url2) {
|
|
392
|
-
const { spawn:
|
|
398
|
+
const { spawn: spawn12 } = await import("node:child_process");
|
|
393
399
|
const cmd = (() => {
|
|
394
400
|
switch (process.platform) {
|
|
395
401
|
case "darwin":
|
|
@@ -400,18 +406,18 @@ async function openBrowser(url2) {
|
|
|
400
406
|
return { bin: "xdg-open", args: [url2] };
|
|
401
407
|
}
|
|
402
408
|
})();
|
|
403
|
-
return new Promise((
|
|
409
|
+
return new Promise((resolve3, reject) => {
|
|
404
410
|
try {
|
|
405
|
-
const child =
|
|
411
|
+
const child = spawn12(cmd.bin, cmd.args, {
|
|
406
412
|
stdio: "ignore",
|
|
407
413
|
detached: true
|
|
408
414
|
});
|
|
409
415
|
child.on("error", (err) => reject(err));
|
|
410
416
|
child.on("spawn", () => {
|
|
411
417
|
child.unref();
|
|
412
|
-
|
|
418
|
+
resolve3();
|
|
413
419
|
});
|
|
414
|
-
setTimeout(() =>
|
|
420
|
+
setTimeout(() => resolve3(), 100);
|
|
415
421
|
} catch (err) {
|
|
416
422
|
reject(err);
|
|
417
423
|
}
|
|
@@ -835,6 +841,180 @@ var init_providerConfig = __esm({
|
|
|
835
841
|
}
|
|
836
842
|
});
|
|
837
843
|
|
|
844
|
+
// src/cli/modelDiscovery.ts
|
|
845
|
+
import { promises as fs3, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
846
|
+
import { homedir } from "node:os";
|
|
847
|
+
import path4 from "node:path";
|
|
848
|
+
async function resolveDiscoveryBaseUrl(provider, options) {
|
|
849
|
+
if (options.baseUrl) return options.baseUrl;
|
|
850
|
+
const { getCustomEndpoint: getCustomEndpoint2 } = await Promise.resolve().then(() => (init_providerConfig(), providerConfig_exports));
|
|
851
|
+
const custom2 = getCustomEndpoint2(provider);
|
|
852
|
+
if (custom2) return custom2;
|
|
853
|
+
if (provider === "openai-compatible") {
|
|
854
|
+
const envBase = process.env.OPENAI_BASE_URL;
|
|
855
|
+
if (envBase && envBase.trim().length > 0) return envBase;
|
|
856
|
+
}
|
|
857
|
+
return PROVIDER_BASE_URLS[provider];
|
|
858
|
+
}
|
|
859
|
+
function defaultModelsFilePath() {
|
|
860
|
+
return process.env.ANATHEMA_MODELS_FILE ?? path4.join(homedir(), ".tmp", "zelari-code", "models.json");
|
|
861
|
+
}
|
|
862
|
+
function getModelsFilePath() {
|
|
863
|
+
return defaultModelsFilePath();
|
|
864
|
+
}
|
|
865
|
+
function loadModelsRegistry(file2 = getModelsFilePath()) {
|
|
866
|
+
if (!existsSync3(file2)) return {};
|
|
867
|
+
try {
|
|
868
|
+
const raw = readFileSync3(file2, "utf-8");
|
|
869
|
+
const parsed = JSON.parse(raw);
|
|
870
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
871
|
+
return parsed;
|
|
872
|
+
} catch {
|
|
873
|
+
return {};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function getCachedModels(provider, file2 = getModelsFilePath()) {
|
|
877
|
+
const registry3 = loadModelsRegistry(file2);
|
|
878
|
+
return registry3[provider];
|
|
879
|
+
}
|
|
880
|
+
function isModelsCacheStale(provider, maxAgeMs = 6 * 60 * 60 * 1e3, file2 = getModelsFilePath(), now = Date.now()) {
|
|
881
|
+
const entry = getCachedModels(provider, file2);
|
|
882
|
+
if (!entry) return true;
|
|
883
|
+
return now - entry.fetchedAt > maxAgeMs;
|
|
884
|
+
}
|
|
885
|
+
async function withRegistryLock(fn) {
|
|
886
|
+
const next = writeChain.then(async () => fn());
|
|
887
|
+
writeChain = next.catch(() => void 0);
|
|
888
|
+
return next;
|
|
889
|
+
}
|
|
890
|
+
async function readModifyWriteRegistry(mutator, file2 = getModelsFilePath()) {
|
|
891
|
+
return withRegistryLock(async () => {
|
|
892
|
+
const onDisk = loadModelsRegistry(file2);
|
|
893
|
+
const merged = mutator(onDisk);
|
|
894
|
+
await fs3.mkdir(path4.dirname(file2), { recursive: true });
|
|
895
|
+
const tmp = `${file2}.tmp-${process.pid}-${Date.now()}`;
|
|
896
|
+
await fs3.writeFile(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
897
|
+
await fs3.rename(tmp, file2);
|
|
898
|
+
return merged;
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
async function resolveAuthToken(provider, options) {
|
|
902
|
+
if (options.authToken) return options.authToken;
|
|
903
|
+
const { resolveApiKeyWithMeta: resolveApiKeyWithMeta2, getOAuthToken: getOAuthToken2 } = await Promise.resolve().then(() => (init_keyStore(), keyStore_exports));
|
|
904
|
+
if (provider === "grok") {
|
|
905
|
+
const oauth = getOAuthToken2("grok");
|
|
906
|
+
if (oauth?.apiKey) return oauth.apiKey;
|
|
907
|
+
}
|
|
908
|
+
const resolved = await resolveApiKeyWithMeta2(provider);
|
|
909
|
+
return resolved?.apiKey;
|
|
910
|
+
}
|
|
911
|
+
function parseOpenAIModelsResponse(json2, baseUrl) {
|
|
912
|
+
if (!json2 || typeof json2 !== "object") return [];
|
|
913
|
+
const obj = json2;
|
|
914
|
+
if (!Array.isArray(obj.data)) return [];
|
|
915
|
+
const models = [];
|
|
916
|
+
for (const m of obj.data) {
|
|
917
|
+
if (!m || typeof m.id !== "string") continue;
|
|
918
|
+
const out = { id: m.id };
|
|
919
|
+
if (typeof m.created === "number") out.created = m.created;
|
|
920
|
+
if (typeof m.owned_by === "string") out.ownedBy = m.owned_by;
|
|
921
|
+
const ctx = m.context_length;
|
|
922
|
+
if (typeof ctx === "number") out.contextLength = ctx;
|
|
923
|
+
models.push(out);
|
|
924
|
+
}
|
|
925
|
+
models.sort((a, b) => a.id.localeCompare(b.id));
|
|
926
|
+
return models;
|
|
927
|
+
}
|
|
928
|
+
async function discoverModelsForProvider(provider, options = {}) {
|
|
929
|
+
const baseUrl = await resolveDiscoveryBaseUrl(provider, options);
|
|
930
|
+
if (!baseUrl || baseUrl.trim().length === 0) {
|
|
931
|
+
throw new ModelDiscoveryError(
|
|
932
|
+
`No base URL for provider "${provider}" \u2014 set one with /provider custom <url> before discovering models`,
|
|
933
|
+
"no_base_url"
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const url2 = `${baseUrl.replace(/\/$/, "")}/models`;
|
|
937
|
+
const authToken = await resolveAuthToken(provider, options);
|
|
938
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
939
|
+
let response;
|
|
940
|
+
try {
|
|
941
|
+
const headers = { Accept: "application/json" };
|
|
942
|
+
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
943
|
+
response = await fetchImpl(url2, { method: "GET", headers });
|
|
944
|
+
} catch (err) {
|
|
945
|
+
throw new ModelDiscoveryError(
|
|
946
|
+
`Network error contacting ${url2}: ${err instanceof Error ? err.message : String(err)}`,
|
|
947
|
+
"network_error"
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
if (!response.ok) {
|
|
951
|
+
const body = await response.text().catch(() => "");
|
|
952
|
+
throw new ModelDiscoveryError(
|
|
953
|
+
`HTTP ${response.status} from ${url2}: ${body.slice(0, 200)}`,
|
|
954
|
+
`http_${response.status}`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
let json2;
|
|
958
|
+
try {
|
|
959
|
+
json2 = await response.json();
|
|
960
|
+
} catch (err) {
|
|
961
|
+
throw new ModelDiscoveryError(
|
|
962
|
+
`Invalid JSON from ${url2}: ${err instanceof Error ? err.message : String(err)}`,
|
|
963
|
+
"invalid_json"
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
const models = parseOpenAIModelsResponse(json2, baseUrl);
|
|
967
|
+
if (models.length === 0) {
|
|
968
|
+
throw new ModelDiscoveryError(
|
|
969
|
+
`Provider ${provider} returned 0 models \u2014 refusing to overwrite cache`,
|
|
970
|
+
"empty_response"
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const entry = {
|
|
974
|
+
models,
|
|
975
|
+
fetchedAt: Date.now(),
|
|
976
|
+
baseUrl
|
|
977
|
+
};
|
|
978
|
+
if (!options.skipCacheWrite) {
|
|
979
|
+
const file2 = getModelsFilePath();
|
|
980
|
+
await readModifyWriteRegistry((current) => {
|
|
981
|
+
current[provider] = entry;
|
|
982
|
+
return current;
|
|
983
|
+
}, file2);
|
|
984
|
+
}
|
|
985
|
+
return entry;
|
|
986
|
+
}
|
|
987
|
+
function discoverModelsInBackground(provider, options = {}) {
|
|
988
|
+
discoverModelsForProvider(provider, options).catch((err) => {
|
|
989
|
+
if (err instanceof ModelDiscoveryError && options.onError) {
|
|
990
|
+
options.onError(err);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
var PROVIDER_BASE_URLS, writeChain, ModelDiscoveryError;
|
|
995
|
+
var init_modelDiscovery = __esm({
|
|
996
|
+
"src/cli/modelDiscovery.ts"() {
|
|
997
|
+
"use strict";
|
|
998
|
+
PROVIDER_BASE_URLS = {
|
|
999
|
+
"grok": "https://api.x.ai/v1",
|
|
1000
|
+
// Must match PROVIDER_ENDPOINTS in provider/openai-compatible.ts (chat host).
|
|
1001
|
+
"glm": "https://api.z.ai/api/coding/paas/v4",
|
|
1002
|
+
"minimax": "https://api.minimax.io/v1",
|
|
1003
|
+
// Must match PROVIDER_ENDPOINTS in provider/openai-compatible.ts (chat host).
|
|
1004
|
+
"deepseek": "https://api.deepseek.com",
|
|
1005
|
+
"openai-compatible": "https://api.x.ai/v1"
|
|
1006
|
+
};
|
|
1007
|
+
writeChain = Promise.resolve();
|
|
1008
|
+
ModelDiscoveryError = class extends Error {
|
|
1009
|
+
constructor(message, code) {
|
|
1010
|
+
super(message);
|
|
1011
|
+
this.code = code;
|
|
1012
|
+
this.name = "ModelDiscoveryError";
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
|
|
838
1018
|
// packages/core/dist/agents/skills.js
|
|
839
1019
|
function getSkillsByCategory(cat) {
|
|
840
1020
|
return SKILL_CATALOG.filter((s) => s.category === cat);
|
|
@@ -16818,12 +16998,12 @@ function withNodeDirOnPath(base) {
|
|
|
16818
16998
|
const nodeDir = dirname(process.execPath);
|
|
16819
16999
|
if (!nodeDir)
|
|
16820
17000
|
return env;
|
|
16821
|
-
const
|
|
17001
|
+
const sep2 = process.platform === "win32" ? ";" : ":";
|
|
16822
17002
|
const current = env.PATH ?? env.Path ?? "";
|
|
16823
|
-
const parts = current.split(
|
|
17003
|
+
const parts = current.split(sep2).filter((p3) => p3.length > 0);
|
|
16824
17004
|
const has = parts.some((p3) => p3.toLowerCase() === nodeDir.toLowerCase());
|
|
16825
17005
|
if (!has) {
|
|
16826
|
-
env.PATH = `${nodeDir}${
|
|
17006
|
+
env.PATH = `${nodeDir}${sep2}${current}`;
|
|
16827
17007
|
}
|
|
16828
17008
|
} catch {
|
|
16829
17009
|
}
|
|
@@ -16850,7 +17030,7 @@ var init_shell = __esm({
|
|
|
16850
17030
|
timeoutMs: 6e4,
|
|
16851
17031
|
inputSchema: BashArgsSchema,
|
|
16852
17032
|
execute: async (args, ctx) => {
|
|
16853
|
-
return new Promise((
|
|
17033
|
+
return new Promise((resolve3) => {
|
|
16854
17034
|
const start = Date.now();
|
|
16855
17035
|
const cwd = args.cwd ?? ctx.cwd;
|
|
16856
17036
|
const resolved = resolveShell();
|
|
@@ -16898,14 +17078,14 @@ var init_shell = __esm({
|
|
|
16898
17078
|
});
|
|
16899
17079
|
const timer = setTimeout(() => {
|
|
16900
17080
|
child.kill("SIGTERM");
|
|
16901
|
-
|
|
17081
|
+
resolve3(typedErr(`Bash command timed out after ${args.timeoutMs}ms. If it was waiting for interactive input: stdin is closed \u2014 pass non-interactive flags or create the files directly instead of retrying.`));
|
|
16902
17082
|
}, args.timeoutMs);
|
|
16903
17083
|
child.on("close", (code) => {
|
|
16904
17084
|
clearTimeout(timer);
|
|
16905
17085
|
const cappedStdout = stdout.slice(0, maxBuffer);
|
|
16906
17086
|
const cappedStderr = stderr.slice(0, maxBuffer);
|
|
16907
17087
|
const interactive = INTERACTIVE_CANCEL_RE.test(cappedStdout) || INTERACTIVE_CANCEL_RE.test(cappedStderr);
|
|
16908
|
-
|
|
17088
|
+
resolve3(typedOk({
|
|
16909
17089
|
stdout: cappedStdout,
|
|
16910
17090
|
stderr: cappedStderr,
|
|
16911
17091
|
exitCode: code ?? -1,
|
|
@@ -16916,7 +17096,7 @@ var init_shell = __esm({
|
|
|
16916
17096
|
});
|
|
16917
17097
|
child.on("error", (err) => {
|
|
16918
17098
|
clearTimeout(timer);
|
|
16919
|
-
|
|
17099
|
+
resolve3(typedErr(err.message));
|
|
16920
17100
|
});
|
|
16921
17101
|
});
|
|
16922
17102
|
}
|
|
@@ -20629,8 +20809,8 @@ function wrapLegacyStream(legacyStream, params, _messages, _tools) {
|
|
|
20629
20809
|
}
|
|
20630
20810
|
if (done)
|
|
20631
20811
|
break;
|
|
20632
|
-
await new Promise((
|
|
20633
|
-
resolveNext =
|
|
20812
|
+
await new Promise((resolve3) => {
|
|
20813
|
+
resolveNext = resolve3;
|
|
20634
20814
|
});
|
|
20635
20815
|
}
|
|
20636
20816
|
await promise2;
|
|
@@ -20749,19 +20929,60 @@ __export(openai_compatible_exports, {
|
|
|
20749
20929
|
resolveBaseUrl: () => resolveBaseUrl
|
|
20750
20930
|
});
|
|
20751
20931
|
function abortableSleep(ms, signal) {
|
|
20752
|
-
return new Promise((
|
|
20753
|
-
if (signal?.aborted) return
|
|
20754
|
-
const t = setTimeout(
|
|
20932
|
+
return new Promise((resolve3) => {
|
|
20933
|
+
if (signal?.aborted) return resolve3();
|
|
20934
|
+
const t = setTimeout(resolve3, ms);
|
|
20755
20935
|
signal?.addEventListener(
|
|
20756
20936
|
"abort",
|
|
20757
20937
|
() => {
|
|
20758
20938
|
clearTimeout(t);
|
|
20759
|
-
|
|
20939
|
+
resolve3();
|
|
20760
20940
|
},
|
|
20761
20941
|
{ once: true }
|
|
20762
20942
|
);
|
|
20763
20943
|
});
|
|
20764
20944
|
}
|
|
20945
|
+
function isTimeoutAbortMessage(msg) {
|
|
20946
|
+
const m = msg.toLowerCase();
|
|
20947
|
+
return m.includes("aborted due to timeout") || m.includes("timeout") || m.includes("the operation was aborted");
|
|
20948
|
+
}
|
|
20949
|
+
async function readChunkWithTimeout(reader, opts) {
|
|
20950
|
+
if (opts.signal?.aborted) {
|
|
20951
|
+
throw new Error("aborted");
|
|
20952
|
+
}
|
|
20953
|
+
const remaining = opts.deadlineMs - Date.now();
|
|
20954
|
+
if (remaining <= 0) {
|
|
20955
|
+
throw new Error(
|
|
20956
|
+
`Provider stream exceeded max duration (${Math.round(PROVIDER_STREAM_MAX_MS / 1e3)}s). Raise ZELARI_PROVIDER_STREAM_MAX_MS if needed.`
|
|
20957
|
+
);
|
|
20958
|
+
}
|
|
20959
|
+
const waitMs = Math.min(opts.idleMs, remaining);
|
|
20960
|
+
let idleTimer;
|
|
20961
|
+
let onAbort;
|
|
20962
|
+
try {
|
|
20963
|
+
return await Promise.race([
|
|
20964
|
+
reader.read(),
|
|
20965
|
+
new Promise((_, reject) => {
|
|
20966
|
+
idleTimer = setTimeout(() => {
|
|
20967
|
+
reject(
|
|
20968
|
+
new Error(
|
|
20969
|
+
`Provider stream idle for ${Math.round(waitMs / 1e3)}s (no tokens). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS.`
|
|
20970
|
+
)
|
|
20971
|
+
);
|
|
20972
|
+
}, waitMs);
|
|
20973
|
+
if (opts.signal) {
|
|
20974
|
+
onAbort = () => reject(new Error("aborted"));
|
|
20975
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
20976
|
+
}
|
|
20977
|
+
})
|
|
20978
|
+
]);
|
|
20979
|
+
} finally {
|
|
20980
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
20981
|
+
if (onAbort && opts.signal) {
|
|
20982
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
20983
|
+
}
|
|
20984
|
+
}
|
|
20985
|
+
}
|
|
20765
20986
|
function backoffDelay(attempt, retryAfterHeader) {
|
|
20766
20987
|
if (retryAfterHeader) {
|
|
20767
20988
|
const seconds = Number.parseFloat(retryAfterHeader);
|
|
@@ -20863,31 +21084,46 @@ function openaiCompatibleProvider(config2) {
|
|
|
20863
21084
|
return;
|
|
20864
21085
|
}
|
|
20865
21086
|
try {
|
|
20866
|
-
const
|
|
20867
|
-
const
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
21087
|
+
const connectController = new AbortController();
|
|
21088
|
+
const connectTimer = setTimeout(() => {
|
|
21089
|
+
connectController.abort(
|
|
21090
|
+
new Error(
|
|
21091
|
+
`Provider connect timeout after ${Math.round(PROVIDER_CONNECT_TIMEOUT_MS / 1e3)}s (no response headers). Override ZELARI_PROVIDER_CONNECT_TIMEOUT_MS.`
|
|
21092
|
+
)
|
|
21093
|
+
);
|
|
21094
|
+
}, PROVIDER_CONNECT_TIMEOUT_MS);
|
|
21095
|
+
const signals = [connectController.signal];
|
|
21096
|
+
if (params.signal) signals.push(params.signal);
|
|
21097
|
+
const fetchSignal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
21098
|
+
try {
|
|
21099
|
+
response = await fetch(`${config2.baseUrl}/chat/completions`, {
|
|
21100
|
+
method: "POST",
|
|
21101
|
+
headers: {
|
|
21102
|
+
"Content-Type": "application/json",
|
|
21103
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
21104
|
+
},
|
|
21105
|
+
body: JSON.stringify(body),
|
|
21106
|
+
// Cancel aborts the HTTP request; stream idle is enforced below
|
|
21107
|
+
// per-chunk so active multi-minute streams are not killed.
|
|
21108
|
+
signal: fetchSignal
|
|
21109
|
+
});
|
|
21110
|
+
} finally {
|
|
21111
|
+
clearTimeout(connectTimer);
|
|
21112
|
+
}
|
|
20883
21113
|
} catch (err) {
|
|
20884
21114
|
lastStatus = 0;
|
|
20885
|
-
|
|
21115
|
+
const asErr = err instanceof Error ? err : null;
|
|
21116
|
+
const causeMsg = asErr?.cause instanceof Error ? asErr.cause.message : typeof asErr?.cause === "string" ? String(asErr.cause) : "";
|
|
21117
|
+
lastErrText = (causeMsg && causeMsg.includes("Provider connect") ? causeMsg : null) || asErr?.message || String(err);
|
|
21118
|
+
if (isTimeoutAbortMessage(lastErrText) && !lastErrText.includes("Provider connect")) {
|
|
21119
|
+
lastErrText = `Provider connect timeout after ${Math.round(PROVIDER_CONNECT_TIMEOUT_MS / 1e3)}s (no response headers). Last error: ${lastErrText}`;
|
|
21120
|
+
}
|
|
20886
21121
|
if (params.signal?.aborted) {
|
|
20887
21122
|
yield { kind: "error", message: "aborted" };
|
|
20888
21123
|
return;
|
|
20889
21124
|
}
|
|
20890
|
-
|
|
21125
|
+
const maxAttempts = isTimeoutAbortMessage(lastErrText) ? Math.min(1, MAX_RETRIES) : MAX_RETRIES;
|
|
21126
|
+
if (attempt < maxAttempts) {
|
|
20891
21127
|
await abortableSleep(backoffDelay(attempt, null), params.signal);
|
|
20892
21128
|
continue;
|
|
20893
21129
|
}
|
|
@@ -20942,9 +21178,30 @@ function openaiCompatibleProvider(config2) {
|
|
|
20942
21178
|
}
|
|
20943
21179
|
toolCallAccumulator.clear();
|
|
20944
21180
|
};
|
|
21181
|
+
const streamDeadline = Date.now() + PROVIDER_STREAM_MAX_MS;
|
|
20945
21182
|
try {
|
|
20946
21183
|
while (true) {
|
|
20947
|
-
|
|
21184
|
+
let chunk;
|
|
21185
|
+
try {
|
|
21186
|
+
chunk = await readChunkWithTimeout(reader, {
|
|
21187
|
+
idleMs: PROVIDER_STREAM_IDLE_MS,
|
|
21188
|
+
deadlineMs: streamDeadline,
|
|
21189
|
+
signal: params.signal
|
|
21190
|
+
});
|
|
21191
|
+
} catch (err) {
|
|
21192
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21193
|
+
if (msg === "aborted" || params.signal?.aborted) {
|
|
21194
|
+
yield { kind: "error", message: "aborted" };
|
|
21195
|
+
return;
|
|
21196
|
+
}
|
|
21197
|
+
try {
|
|
21198
|
+
await reader.cancel(msg);
|
|
21199
|
+
} catch {
|
|
21200
|
+
}
|
|
21201
|
+
yield { kind: "error", message: msg };
|
|
21202
|
+
return;
|
|
21203
|
+
}
|
|
21204
|
+
const { value, done } = chunk;
|
|
20948
21205
|
if (done) break;
|
|
20949
21206
|
buffer += decoder.decode(value, { stream: true });
|
|
20950
21207
|
const lines = buffer.split("\n");
|
|
@@ -21070,7 +21327,7 @@ async function providerConfigFor(providerId) {
|
|
|
21070
21327
|
providerId
|
|
21071
21328
|
};
|
|
21072
21329
|
}
|
|
21073
|
-
var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS,
|
|
21330
|
+
var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_STREAM_MAX_MS, PROVIDER_ENDPOINTS;
|
|
21074
21331
|
var init_openai_compatible = __esm({
|
|
21075
21332
|
"src/cli/provider/openai-compatible.ts"() {
|
|
21076
21333
|
"use strict";
|
|
@@ -21084,10 +21341,20 @@ var init_openai_compatible = __esm({
|
|
|
21084
21341
|
})();
|
|
21085
21342
|
BACKOFF_BASE_MS = 500;
|
|
21086
21343
|
BACKOFF_CAP_MS = 8e3;
|
|
21087
|
-
|
|
21088
|
-
const raw = process.env.
|
|
21344
|
+
PROVIDER_CONNECT_TIMEOUT_MS = (() => {
|
|
21345
|
+
const raw = process.env.ZELARI_PROVIDER_CONNECT_TIMEOUT_MS;
|
|
21346
|
+
const n = raw ? Number.parseInt(raw, 10) : 9e4;
|
|
21347
|
+
return Number.isFinite(n) && n >= 5e3 ? n : 9e4;
|
|
21348
|
+
})();
|
|
21349
|
+
PROVIDER_STREAM_IDLE_MS = (() => {
|
|
21350
|
+
const raw = process.env.ZELARI_PROVIDER_STREAM_IDLE_MS ?? process.env.ZELARI_PROVIDER_TIMEOUT_MS;
|
|
21089
21351
|
const n = raw ? Number.parseInt(raw, 10) : 3e5;
|
|
21090
|
-
return Number.isFinite(n) && n >=
|
|
21352
|
+
return Number.isFinite(n) && n >= 15e3 ? n : 3e5;
|
|
21353
|
+
})();
|
|
21354
|
+
PROVIDER_STREAM_MAX_MS = (() => {
|
|
21355
|
+
const raw = process.env.ZELARI_PROVIDER_STREAM_MAX_MS;
|
|
21356
|
+
const n = raw ? Number.parseInt(raw, 10) : 18e5;
|
|
21357
|
+
return Number.isFinite(n) && n >= 6e4 ? n : 18e5;
|
|
21091
21358
|
})();
|
|
21092
21359
|
PROVIDER_ENDPOINTS = {
|
|
21093
21360
|
"openai-compatible": "https://api.x.ai/v1",
|
|
@@ -21662,14 +21929,14 @@ var init_engine = __esm({
|
|
|
21662
21929
|
parse: parseRuffJson
|
|
21663
21930
|
}
|
|
21664
21931
|
];
|
|
21665
|
-
defaultRunner = (cmd, args, opts) => new Promise((
|
|
21932
|
+
defaultRunner = (cmd, args, opts) => new Promise((resolve3) => {
|
|
21666
21933
|
let stdout = "";
|
|
21667
21934
|
let stderr = "";
|
|
21668
21935
|
let settled = false;
|
|
21669
21936
|
const done = (r) => {
|
|
21670
21937
|
if (settled) return;
|
|
21671
21938
|
settled = true;
|
|
21672
|
-
|
|
21939
|
+
resolve3(r);
|
|
21673
21940
|
};
|
|
21674
21941
|
let child;
|
|
21675
21942
|
try {
|
|
@@ -22338,12 +22605,12 @@ var init_client = __esm({
|
|
|
22338
22605
|
if (this.closed) return Promise.reject(new Error("LSP transport is closed"));
|
|
22339
22606
|
const id = this.nextId++;
|
|
22340
22607
|
const msg = { jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} };
|
|
22341
|
-
return new Promise((
|
|
22608
|
+
return new Promise((resolve3, reject) => {
|
|
22342
22609
|
const timer = setTimeout(() => {
|
|
22343
22610
|
this.pending.delete(id);
|
|
22344
22611
|
reject(new Error(`LSP request "${method}" timed out after ${this.timeoutMs}ms`));
|
|
22345
22612
|
}, this.timeoutMs);
|
|
22346
|
-
this.pending.set(id, { resolve, reject, timer });
|
|
22613
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
22347
22614
|
this.transport.send(encodeMessage(msg));
|
|
22348
22615
|
});
|
|
22349
22616
|
}
|
|
@@ -22609,8 +22876,8 @@ var init_manager = __esm({
|
|
|
22609
22876
|
if (cached2 !== void 0) return cached2;
|
|
22610
22877
|
let resolveInit;
|
|
22611
22878
|
let rejectInit;
|
|
22612
|
-
const initialized = new Promise((
|
|
22613
|
-
resolveInit =
|
|
22879
|
+
const initialized = new Promise((resolve3, reject) => {
|
|
22880
|
+
resolveInit = resolve3;
|
|
22614
22881
|
rejectInit = reject;
|
|
22615
22882
|
});
|
|
22616
22883
|
let entry = null;
|
|
@@ -23329,8 +23596,8 @@ async function runBrowserCheck(options, loader) {
|
|
|
23329
23596
|
const failedRequests = [];
|
|
23330
23597
|
const evaluateResults = [];
|
|
23331
23598
|
const base = { ok: false, consoleErrors, pageErrors, failedRequests };
|
|
23332
|
-
const
|
|
23333
|
-
const pw = await
|
|
23599
|
+
const resolve3 = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
|
|
23600
|
+
const pw = await resolve3();
|
|
23334
23601
|
if (!pw) {
|
|
23335
23602
|
return {
|
|
23336
23603
|
...base,
|
|
@@ -23822,9 +24089,9 @@ exec node "$(dirname "$0")/askpass.cjs"
|
|
|
23822
24089
|
return sh;
|
|
23823
24090
|
}
|
|
23824
24091
|
function runSsh(target, remoteCommand, timeoutMs = 6e4) {
|
|
23825
|
-
return new Promise((
|
|
24092
|
+
return new Promise((resolve3) => {
|
|
23826
24093
|
if (target.auth === "password" && !getSshPassword(target.id)) {
|
|
23827
|
-
|
|
24094
|
+
resolve3({
|
|
23828
24095
|
code: 1,
|
|
23829
24096
|
stdout: "",
|
|
23830
24097
|
stderr: "No password stored for this target \u2014 edit target and set password"
|
|
@@ -23856,7 +24123,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
|
|
|
23856
24123
|
});
|
|
23857
24124
|
const timer = setTimeout(() => {
|
|
23858
24125
|
child.kill("SIGTERM");
|
|
23859
|
-
|
|
24126
|
+
resolve3({
|
|
23860
24127
|
code: 124,
|
|
23861
24128
|
stdout,
|
|
23862
24129
|
stderr: stderr + "\n[ssh] timeout"
|
|
@@ -23864,7 +24131,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
|
|
|
23864
24131
|
}, timeoutMs);
|
|
23865
24132
|
child.on("error", (err) => {
|
|
23866
24133
|
clearTimeout(timer);
|
|
23867
|
-
|
|
24134
|
+
resolve3({
|
|
23868
24135
|
code: 127,
|
|
23869
24136
|
stdout,
|
|
23870
24137
|
stderr: err.message.includes("ENOENT") ? "ssh not found on PATH \u2014 install OpenSSH client" : err.message
|
|
@@ -23872,7 +24139,7 @@ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
|
|
|
23872
24139
|
});
|
|
23873
24140
|
child.on("close", (code) => {
|
|
23874
24141
|
clearTimeout(timer);
|
|
23875
|
-
|
|
24142
|
+
resolve3({ code: code ?? 1, stdout, stderr });
|
|
23876
24143
|
});
|
|
23877
24144
|
});
|
|
23878
24145
|
}
|
|
@@ -24091,7 +24358,7 @@ async function readChecks(cwd) {
|
|
|
24091
24358
|
}
|
|
24092
24359
|
}
|
|
24093
24360
|
function runShell(command, cwd, timeoutMs, signal) {
|
|
24094
|
-
return new Promise((
|
|
24361
|
+
return new Promise((resolve3) => {
|
|
24095
24362
|
const isWin = process.platform === "win32";
|
|
24096
24363
|
const child = spawn5(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
|
|
24097
24364
|
cwd,
|
|
@@ -24105,7 +24372,7 @@ function runShell(command, cwd, timeoutMs, signal) {
|
|
|
24105
24372
|
const finish = (exitCode) => {
|
|
24106
24373
|
if (settled) return;
|
|
24107
24374
|
settled = true;
|
|
24108
|
-
|
|
24375
|
+
resolve3({ exitCode, stdout, stderr });
|
|
24109
24376
|
};
|
|
24110
24377
|
const timer = setTimeout(() => {
|
|
24111
24378
|
try {
|
|
@@ -30544,8 +30811,8 @@ var init_storage = __esm({
|
|
|
30544
30811
|
const prev2 = this.chains.get(key) ?? Promise.resolve();
|
|
30545
30812
|
let release = () => {
|
|
30546
30813
|
};
|
|
30547
|
-
const next = new Promise((
|
|
30548
|
-
release =
|
|
30814
|
+
const next = new Promise((resolve3) => {
|
|
30815
|
+
release = resolve3;
|
|
30549
30816
|
});
|
|
30550
30817
|
const chained = prev2.then(() => next);
|
|
30551
30818
|
this.chains.set(key, chained);
|
|
@@ -31430,7 +31697,7 @@ ${fallback.output}`
|
|
|
31430
31697
|
return primary;
|
|
31431
31698
|
}
|
|
31432
31699
|
function runNpm(executor, args, mode, npmCliPath) {
|
|
31433
|
-
return new Promise((
|
|
31700
|
+
return new Promise((resolve3) => {
|
|
31434
31701
|
let stdout = "";
|
|
31435
31702
|
let stderr = "";
|
|
31436
31703
|
const stdio = ["ignore", "pipe", "pipe"];
|
|
@@ -31442,7 +31709,7 @@ function runNpm(executor, args, mode, npmCliPath) {
|
|
|
31442
31709
|
stderr += chunk.toString();
|
|
31443
31710
|
});
|
|
31444
31711
|
child.on("error", (err) => {
|
|
31445
|
-
|
|
31712
|
+
resolve3({
|
|
31446
31713
|
ok: false,
|
|
31447
31714
|
output: stdout + stderr,
|
|
31448
31715
|
error: err.message,
|
|
@@ -31451,7 +31718,7 @@ function runNpm(executor, args, mode, npmCliPath) {
|
|
|
31451
31718
|
});
|
|
31452
31719
|
child.on("close", (code) => {
|
|
31453
31720
|
const ok = code === 0;
|
|
31454
|
-
|
|
31721
|
+
resolve3({
|
|
31455
31722
|
ok,
|
|
31456
31723
|
output: stdout + stderr,
|
|
31457
31724
|
error: ok ? void 0 : `npm exited with code ${code}`,
|
|
@@ -31575,7 +31842,7 @@ var init_mcpClient = __esm({
|
|
|
31575
31842
|
return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
|
|
31576
31843
|
const id = this.nextId++;
|
|
31577
31844
|
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
31578
|
-
return new Promise((
|
|
31845
|
+
return new Promise((resolve3, reject) => {
|
|
31579
31846
|
const timer = setTimeout(() => {
|
|
31580
31847
|
this.pending.delete(id);
|
|
31581
31848
|
reject(
|
|
@@ -31584,7 +31851,7 @@ var init_mcpClient = __esm({
|
|
|
31584
31851
|
)
|
|
31585
31852
|
);
|
|
31586
31853
|
}, timeoutMs);
|
|
31587
|
-
this.pending.set(id, { resolve, reject, timer });
|
|
31854
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
31588
31855
|
child.stdin.write(payload + "\n", (err) => {
|
|
31589
31856
|
if (err) {
|
|
31590
31857
|
clearTimeout(timer);
|
|
@@ -34067,14 +34334,14 @@ function agentProbeEnv() {
|
|
|
34067
34334
|
try {
|
|
34068
34335
|
const nodeDir = dirname7(process.execPath);
|
|
34069
34336
|
if (!nodeDir) return env;
|
|
34070
|
-
const
|
|
34337
|
+
const sep2 = process.platform === "win32" ? ";" : ":";
|
|
34071
34338
|
const current = env.PATH ?? env.Path ?? "";
|
|
34072
|
-
const parts = current.split(
|
|
34339
|
+
const parts = current.split(sep2).filter((p3) => p3.length > 0);
|
|
34073
34340
|
const has = parts.some(
|
|
34074
34341
|
(p3) => p3.toLowerCase() === nodeDir.toLowerCase()
|
|
34075
34342
|
);
|
|
34076
34343
|
if (!has) {
|
|
34077
|
-
env.PATH = `${nodeDir}${
|
|
34344
|
+
env.PATH = `${nodeDir}${sep2}${current}`;
|
|
34078
34345
|
}
|
|
34079
34346
|
} catch {
|
|
34080
34347
|
}
|
|
@@ -34410,8 +34677,8 @@ function isBinaryOnPath(bin, opts = {}) {
|
|
|
34410
34677
|
const exists = opts.exists ?? existsSync34;
|
|
34411
34678
|
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
34412
34679
|
const pathMod = platform === "win32" ? path33.win32 : path33.posix;
|
|
34413
|
-
const
|
|
34414
|
-
const dirs = pathEnv.split(
|
|
34680
|
+
const sep2 = platform === "win32" ? ";" : ":";
|
|
34681
|
+
const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
|
|
34415
34682
|
const candidates = [bin];
|
|
34416
34683
|
if (platform === "win32") {
|
|
34417
34684
|
const pathExt = opts.pathExt ?? process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM";
|
|
@@ -34539,6 +34806,178 @@ var init_registry2 = __esm({
|
|
|
34539
34806
|
}
|
|
34540
34807
|
});
|
|
34541
34808
|
|
|
34809
|
+
// src/cli/atMentions.ts
|
|
34810
|
+
var atMentions_exports = {};
|
|
34811
|
+
__export(atMentions_exports, {
|
|
34812
|
+
expandAtMentions: () => expandAtMentions,
|
|
34813
|
+
extractAtMentions: () => extractAtMentions,
|
|
34814
|
+
hasAtMentions: () => hasAtMentions
|
|
34815
|
+
});
|
|
34816
|
+
import { existsSync as existsSync37, readFileSync as readFileSync30, statSync as statSync6 } from "node:fs";
|
|
34817
|
+
import { isAbsolute, relative as relative3, resolve, sep } from "node:path";
|
|
34818
|
+
function isProbablyText(name, head) {
|
|
34819
|
+
if (/\.(txt|md|markdown|json|jsonc|ts|tsx|js|jsx|mjs|cjs|css|scss|html|htm|xml|yml|yaml|toml|ini|cfg|conf|rs|go|py|java|kt|swift|c|cc|cpp|h|hpp|cs|rb|php|sh|bash|zsh|ps1|sql|graphql|env|gitignore|dockerfile|makefile|cmake|lock|svg)$/i.test(
|
|
34820
|
+
name
|
|
34821
|
+
)) {
|
|
34822
|
+
return true;
|
|
34823
|
+
}
|
|
34824
|
+
return !head.includes("\0") && /[\x09\x0a\x0d\x20-\x7e]/.test(head.slice(0, 200));
|
|
34825
|
+
}
|
|
34826
|
+
function underRoot(abs, root) {
|
|
34827
|
+
const a = resolve(abs);
|
|
34828
|
+
const r = resolve(root);
|
|
34829
|
+
const prefix = r.endsWith(sep) ? r : r + sep;
|
|
34830
|
+
return a === r || a.startsWith(prefix);
|
|
34831
|
+
}
|
|
34832
|
+
function extractAtMentions(text) {
|
|
34833
|
+
const out = [];
|
|
34834
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34835
|
+
AT_PATH_RE.lastIndex = 0;
|
|
34836
|
+
let m;
|
|
34837
|
+
while ((m = AT_PATH_RE.exec(text)) !== null) {
|
|
34838
|
+
const token = (m[2] ?? "").trim();
|
|
34839
|
+
if (!token || token.includes("@")) continue;
|
|
34840
|
+
const key = token.replace(/\\/g, "/").toLowerCase();
|
|
34841
|
+
if (seen.has(key)) continue;
|
|
34842
|
+
seen.add(key);
|
|
34843
|
+
out.push(token);
|
|
34844
|
+
}
|
|
34845
|
+
return out;
|
|
34846
|
+
}
|
|
34847
|
+
function resolveMention(token, cwd) {
|
|
34848
|
+
const abs = isAbsolute(token) ? resolve(token) : resolve(cwd, token);
|
|
34849
|
+
if (!underRoot(abs, cwd) && !isAbsolute(token)) {
|
|
34850
|
+
if (!underRoot(abs, cwd)) {
|
|
34851
|
+
return {
|
|
34852
|
+
raw: token,
|
|
34853
|
+
path: token,
|
|
34854
|
+
absolute: abs,
|
|
34855
|
+
isDir: false,
|
|
34856
|
+
note: "outside project root \u2014 skipped"
|
|
34857
|
+
};
|
|
34858
|
+
}
|
|
34859
|
+
}
|
|
34860
|
+
if (isAbsolute(token) && !underRoot(abs, cwd)) {
|
|
34861
|
+
return {
|
|
34862
|
+
raw: token,
|
|
34863
|
+
path: token,
|
|
34864
|
+
absolute: abs,
|
|
34865
|
+
isDir: false,
|
|
34866
|
+
note: "outside project root \u2014 skipped"
|
|
34867
|
+
};
|
|
34868
|
+
}
|
|
34869
|
+
if (!existsSync37(abs)) {
|
|
34870
|
+
return {
|
|
34871
|
+
raw: token,
|
|
34872
|
+
path: token,
|
|
34873
|
+
absolute: abs,
|
|
34874
|
+
isDir: false,
|
|
34875
|
+
note: "not found"
|
|
34876
|
+
};
|
|
34877
|
+
}
|
|
34878
|
+
let st;
|
|
34879
|
+
try {
|
|
34880
|
+
st = statSync6(abs);
|
|
34881
|
+
} catch {
|
|
34882
|
+
return {
|
|
34883
|
+
raw: token,
|
|
34884
|
+
path: token,
|
|
34885
|
+
absolute: abs,
|
|
34886
|
+
isDir: false,
|
|
34887
|
+
note: "unreadable"
|
|
34888
|
+
};
|
|
34889
|
+
}
|
|
34890
|
+
const rel2 = relative3(cwd, abs).replace(/\\/g, "/") || ".";
|
|
34891
|
+
if (st.isDirectory()) {
|
|
34892
|
+
return {
|
|
34893
|
+
raw: token,
|
|
34894
|
+
path: rel2,
|
|
34895
|
+
absolute: abs,
|
|
34896
|
+
isDir: true,
|
|
34897
|
+
note: "directory \u2014 list/read with tools as needed"
|
|
34898
|
+
};
|
|
34899
|
+
}
|
|
34900
|
+
if (st.size > ATTACH_FILE_MAX_BYTES) {
|
|
34901
|
+
return {
|
|
34902
|
+
raw: token,
|
|
34903
|
+
path: rel2,
|
|
34904
|
+
absolute: abs,
|
|
34905
|
+
isDir: false,
|
|
34906
|
+
note: `too large (${Math.round(st.size / 1024)} KB) \u2014 path only`
|
|
34907
|
+
};
|
|
34908
|
+
}
|
|
34909
|
+
try {
|
|
34910
|
+
const buf = readFileSync30(abs);
|
|
34911
|
+
const head = buf.subarray(0, 800).toString("utf8");
|
|
34912
|
+
if (!isProbablyText(abs, head)) {
|
|
34913
|
+
return {
|
|
34914
|
+
raw: token,
|
|
34915
|
+
path: rel2,
|
|
34916
|
+
absolute: abs,
|
|
34917
|
+
isDir: false,
|
|
34918
|
+
note: "binary \u2014 path only"
|
|
34919
|
+
};
|
|
34920
|
+
}
|
|
34921
|
+
let text = buf.toString("utf8");
|
|
34922
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
34923
|
+
if (text.length > ATTACH_TEXT_MAX) {
|
|
34924
|
+
text = text.slice(0, ATTACH_TEXT_MAX) + `
|
|
34925
|
+
|
|
34926
|
+
\u2026 [truncated, ${text.length - ATTACH_TEXT_MAX} more chars]`;
|
|
34927
|
+
}
|
|
34928
|
+
return { raw: token, path: rel2, absolute: abs, isDir: false, text };
|
|
34929
|
+
} catch (err) {
|
|
34930
|
+
return {
|
|
34931
|
+
raw: token,
|
|
34932
|
+
path: rel2,
|
|
34933
|
+
absolute: abs,
|
|
34934
|
+
isDir: false,
|
|
34935
|
+
note: err instanceof Error ? err.message : "read failed"
|
|
34936
|
+
};
|
|
34937
|
+
}
|
|
34938
|
+
}
|
|
34939
|
+
function expandAtMentions(userText, cwd = process.cwd()) {
|
|
34940
|
+
const tokens = extractAtMentions(userText);
|
|
34941
|
+
if (tokens.length === 0) {
|
|
34942
|
+
return { text: userText, hits: [] };
|
|
34943
|
+
}
|
|
34944
|
+
const hits = [];
|
|
34945
|
+
for (const t of tokens) {
|
|
34946
|
+
const hit = resolveMention(t, cwd);
|
|
34947
|
+
if (hit) hits.push(hit);
|
|
34948
|
+
}
|
|
34949
|
+
if (hits.length === 0) {
|
|
34950
|
+
return { text: userText, hits: [] };
|
|
34951
|
+
}
|
|
34952
|
+
const blocks = hits.map((h) => {
|
|
34953
|
+
const label = h.path || h.raw;
|
|
34954
|
+
if (h.text != null && h.text.length > 0) {
|
|
34955
|
+
return `--- File: ${label} ---
|
|
34956
|
+
${h.text}
|
|
34957
|
+
--- End file ---`;
|
|
34958
|
+
}
|
|
34959
|
+
const extra = h.note ? ` (${h.note})` : "";
|
|
34960
|
+
return `--- ${h.isDir ? "Dir" : "File"}: ${label}${extra} ---`;
|
|
34961
|
+
});
|
|
34962
|
+
const text = `${userText.trim()}
|
|
34963
|
+
|
|
34964
|
+
[Tagged paths]
|
|
34965
|
+
${blocks.join("\n\n")}`;
|
|
34966
|
+
return { text, hits };
|
|
34967
|
+
}
|
|
34968
|
+
function hasAtMentions(text) {
|
|
34969
|
+
return extractAtMentions(text).length > 0;
|
|
34970
|
+
}
|
|
34971
|
+
var ATTACH_TEXT_MAX, ATTACH_FILE_MAX_BYTES, AT_PATH_RE;
|
|
34972
|
+
var init_atMentions = __esm({
|
|
34973
|
+
"src/cli/atMentions.ts"() {
|
|
34974
|
+
"use strict";
|
|
34975
|
+
ATTACH_TEXT_MAX = 48e3;
|
|
34976
|
+
ATTACH_FILE_MAX_BYTES = 512e3;
|
|
34977
|
+
AT_PATH_RE = /(^|[\s([{])@((?:\.{1,2}\/)?[A-Za-z0-9_.+-]+(?:[\\/][A-Za-z0-9_.+-]+)*)/g;
|
|
34978
|
+
}
|
|
34979
|
+
});
|
|
34980
|
+
|
|
34542
34981
|
// src/cli/triggerLock.ts
|
|
34543
34982
|
var triggerLock_exports = {};
|
|
34544
34983
|
__export(triggerLock_exports, {
|
|
@@ -34592,13 +35031,1024 @@ var init_triggerLock = __esm({
|
|
|
34592
35031
|
}
|
|
34593
35032
|
});
|
|
34594
35033
|
|
|
35034
|
+
// src/cli/desktopConfig.ts
|
|
35035
|
+
function wantsPrintConfig(argv) {
|
|
35036
|
+
return argv.includes("--print-config");
|
|
35037
|
+
}
|
|
35038
|
+
function wantsSetKey(argv) {
|
|
35039
|
+
return argv.includes("--set-key");
|
|
35040
|
+
}
|
|
35041
|
+
function wantsDiscoverModels(argv) {
|
|
35042
|
+
return argv.includes("--discover-models");
|
|
35043
|
+
}
|
|
35044
|
+
function parseSetConfigFlags(argv) {
|
|
35045
|
+
if (!argv.includes("--set-config")) {
|
|
35046
|
+
return { request: null };
|
|
35047
|
+
}
|
|
35048
|
+
let provider;
|
|
35049
|
+
let model;
|
|
35050
|
+
let endpoint;
|
|
35051
|
+
let endpointClear = false;
|
|
35052
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35053
|
+
const arg = argv[i];
|
|
35054
|
+
if (arg === "--provider") {
|
|
35055
|
+
provider = argv[i + 1];
|
|
35056
|
+
i++;
|
|
35057
|
+
} else if (arg === "--model") {
|
|
35058
|
+
model = argv[i + 1];
|
|
35059
|
+
i++;
|
|
35060
|
+
} else if (arg === "--endpoint") {
|
|
35061
|
+
endpoint = argv[i + 1];
|
|
35062
|
+
i++;
|
|
35063
|
+
} else if (arg === "--endpoint-clear") {
|
|
35064
|
+
endpointClear = true;
|
|
35065
|
+
}
|
|
35066
|
+
}
|
|
35067
|
+
if (!provider && !model && !endpoint && !endpointClear) {
|
|
35068
|
+
return {
|
|
35069
|
+
request: null,
|
|
35070
|
+
error: "--set-config requires --provider, --model, --endpoint, and/or --endpoint-clear"
|
|
35071
|
+
};
|
|
35072
|
+
}
|
|
35073
|
+
if (provider !== void 0 && provider.trim().length === 0) {
|
|
35074
|
+
return { request: null, error: "--provider cannot be empty" };
|
|
35075
|
+
}
|
|
35076
|
+
if (model !== void 0 && model.trim().length === 0) {
|
|
35077
|
+
return { request: null, error: "--model cannot be empty" };
|
|
35078
|
+
}
|
|
35079
|
+
if (endpoint !== void 0 && endpoint.trim().length === 0) {
|
|
35080
|
+
return { request: null, error: "--endpoint cannot be empty" };
|
|
35081
|
+
}
|
|
35082
|
+
if (endpoint && endpointClear) {
|
|
35083
|
+
return { request: null, error: "--endpoint and --endpoint-clear conflict" };
|
|
35084
|
+
}
|
|
35085
|
+
return {
|
|
35086
|
+
request: {
|
|
35087
|
+
provider: provider?.trim(),
|
|
35088
|
+
model: model?.trim(),
|
|
35089
|
+
endpoint: endpoint?.trim(),
|
|
35090
|
+
endpointClear: endpointClear || void 0
|
|
35091
|
+
}
|
|
35092
|
+
};
|
|
35093
|
+
}
|
|
35094
|
+
function parseSetKeyFlags(argv) {
|
|
35095
|
+
if (!argv.includes("--set-key")) {
|
|
35096
|
+
return { request: null };
|
|
35097
|
+
}
|
|
35098
|
+
let provider;
|
|
35099
|
+
let key;
|
|
35100
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35101
|
+
const arg = argv[i];
|
|
35102
|
+
if (arg === "--provider") {
|
|
35103
|
+
provider = argv[i + 1];
|
|
35104
|
+
i++;
|
|
35105
|
+
} else if (arg === "--key") {
|
|
35106
|
+
key = argv[i + 1];
|
|
35107
|
+
i++;
|
|
35108
|
+
}
|
|
35109
|
+
}
|
|
35110
|
+
if (!provider || provider.trim().length === 0) {
|
|
35111
|
+
return { request: null, error: "--set-key requires --provider <id>" };
|
|
35112
|
+
}
|
|
35113
|
+
if (!key || key.trim().length === 0) {
|
|
35114
|
+
return { request: null, error: "--set-key requires --key <secret>" };
|
|
35115
|
+
}
|
|
35116
|
+
return {
|
|
35117
|
+
request: {
|
|
35118
|
+
provider: provider.trim(),
|
|
35119
|
+
key: key.trim()
|
|
35120
|
+
}
|
|
35121
|
+
};
|
|
35122
|
+
}
|
|
35123
|
+
function parseDiscoverModelsFlags(argv) {
|
|
35124
|
+
if (!argv.includes("--discover-models")) {
|
|
35125
|
+
return { provider: null, present: false };
|
|
35126
|
+
}
|
|
35127
|
+
let provider;
|
|
35128
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35129
|
+
if (argv[i] === "--provider") {
|
|
35130
|
+
provider = argv[i + 1];
|
|
35131
|
+
i++;
|
|
35132
|
+
}
|
|
35133
|
+
}
|
|
35134
|
+
return {
|
|
35135
|
+
present: true,
|
|
35136
|
+
provider: provider?.trim() || null
|
|
35137
|
+
};
|
|
35138
|
+
}
|
|
35139
|
+
function buildDesktopConfigSnapshot() {
|
|
35140
|
+
const config2 = getProviderConfig();
|
|
35141
|
+
const providers = PROVIDERS.map((p3) => {
|
|
35142
|
+
const cached2 = getCachedModels(p3.id);
|
|
35143
|
+
const models = cached2?.models.map((m) => m.id) ?? [];
|
|
35144
|
+
const defaultModel = config2.modelByProvider[p3.id] ?? "";
|
|
35145
|
+
if (defaultModel && !models.includes(defaultModel)) {
|
|
35146
|
+
models.unshift(defaultModel);
|
|
35147
|
+
}
|
|
35148
|
+
const custom2 = getCustomEndpoint(p3.id);
|
|
35149
|
+
const builtin = p3.baseUrl ?? null;
|
|
35150
|
+
return {
|
|
35151
|
+
id: p3.id,
|
|
35152
|
+
displayName: p3.displayName,
|
|
35153
|
+
hasKey: !!resolveApiKey(p3.id),
|
|
35154
|
+
envVar: p3.envVar,
|
|
35155
|
+
models,
|
|
35156
|
+
defaultModel,
|
|
35157
|
+
endpoint: custom2 ?? null,
|
|
35158
|
+
baseUrl: custom2 ?? builtin
|
|
35159
|
+
};
|
|
35160
|
+
});
|
|
35161
|
+
return {
|
|
35162
|
+
activeProviderId: config2.activeProviderId,
|
|
35163
|
+
modelByProvider: { ...config2.modelByProvider },
|
|
35164
|
+
providers,
|
|
35165
|
+
cliVersion: getCurrentVersion(),
|
|
35166
|
+
configPaths: {
|
|
35167
|
+
provider: getProviderConfigPath(),
|
|
35168
|
+
keys: getKeyStorePath()
|
|
35169
|
+
}
|
|
35170
|
+
};
|
|
35171
|
+
}
|
|
35172
|
+
function printDesktopConfig() {
|
|
35173
|
+
const snap = buildDesktopConfigSnapshot();
|
|
35174
|
+
process.stdout.write(JSON.stringify(snap, null, 2) + "\n");
|
|
35175
|
+
}
|
|
35176
|
+
function applySetConfig(req) {
|
|
35177
|
+
try {
|
|
35178
|
+
const config2 = getProviderConfig();
|
|
35179
|
+
let targetProvider = req.provider ?? config2.activeProviderId;
|
|
35180
|
+
if (req.provider) {
|
|
35181
|
+
const exists = PROVIDERS.some((p3) => p3.id === req.provider);
|
|
35182
|
+
if (!exists) {
|
|
35183
|
+
return {
|
|
35184
|
+
ok: false,
|
|
35185
|
+
error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
|
|
35186
|
+
};
|
|
35187
|
+
}
|
|
35188
|
+
setActiveProviderId(req.provider);
|
|
35189
|
+
targetProvider = req.provider;
|
|
35190
|
+
}
|
|
35191
|
+
if (req.endpointClear) {
|
|
35192
|
+
clearCustomEndpoint(targetProvider);
|
|
35193
|
+
}
|
|
35194
|
+
if (req.endpoint) {
|
|
35195
|
+
setCustomEndpoint(targetProvider, req.endpoint);
|
|
35196
|
+
}
|
|
35197
|
+
if (req.model) {
|
|
35198
|
+
setModelForProvider(targetProvider, req.model);
|
|
35199
|
+
}
|
|
35200
|
+
const after = getProviderConfig();
|
|
35201
|
+
const ep = getCustomEndpoint(after.activeProviderId);
|
|
35202
|
+
return {
|
|
35203
|
+
ok: true,
|
|
35204
|
+
message: `activeProvider=${after.activeProviderId} model=${after.modelByProvider[after.activeProviderId]}` + (ep ? ` endpoint=${ep}` : "")
|
|
35205
|
+
};
|
|
35206
|
+
} catch (err) {
|
|
35207
|
+
return {
|
|
35208
|
+
ok: false,
|
|
35209
|
+
error: err instanceof Error ? err.message : String(err)
|
|
35210
|
+
};
|
|
35211
|
+
}
|
|
35212
|
+
}
|
|
35213
|
+
function applySetKey(req) {
|
|
35214
|
+
try {
|
|
35215
|
+
const exists = PROVIDERS.some((p3) => p3.id === req.provider);
|
|
35216
|
+
if (!exists) {
|
|
35217
|
+
return {
|
|
35218
|
+
ok: false,
|
|
35219
|
+
error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
|
|
35220
|
+
};
|
|
35221
|
+
}
|
|
35222
|
+
setApiKey(req.provider, req.key);
|
|
35223
|
+
setActiveProviderId(req.provider);
|
|
35224
|
+
return {
|
|
35225
|
+
ok: true,
|
|
35226
|
+
provider: req.provider,
|
|
35227
|
+
masked: maskKey(req.key)
|
|
35228
|
+
};
|
|
35229
|
+
} catch (err) {
|
|
35230
|
+
return {
|
|
35231
|
+
ok: false,
|
|
35232
|
+
error: err instanceof Error ? err.message : String(err)
|
|
35233
|
+
};
|
|
35234
|
+
}
|
|
35235
|
+
}
|
|
35236
|
+
async function runDiscoverModels(providerArg) {
|
|
35237
|
+
const config2 = getProviderConfig();
|
|
35238
|
+
const providerId = (providerArg ?? config2.activeProviderId).trim();
|
|
35239
|
+
if (!DISCOVERABLE.includes(providerId)) {
|
|
35240
|
+
if (providerId === "custom") {
|
|
35241
|
+
} else {
|
|
35242
|
+
return {
|
|
35243
|
+
ok: false,
|
|
35244
|
+
error: `provider '${providerId}' is not discoverable. Use: ${DISCOVERABLE.join(", ")}`
|
|
35245
|
+
};
|
|
35246
|
+
}
|
|
35247
|
+
}
|
|
35248
|
+
const discoveryId = providerId === "custom" ? "openai-compatible" : providerId;
|
|
35249
|
+
try {
|
|
35250
|
+
const entry = await discoverModelsForProvider(discoveryId);
|
|
35251
|
+
return {
|
|
35252
|
+
ok: true,
|
|
35253
|
+
payload: {
|
|
35254
|
+
ok: true,
|
|
35255
|
+
provider: providerId,
|
|
35256
|
+
models: entry.models.map((m) => m.id),
|
|
35257
|
+
fetchedAt: entry.fetchedAt,
|
|
35258
|
+
baseUrl: entry.baseUrl
|
|
35259
|
+
}
|
|
35260
|
+
};
|
|
35261
|
+
} catch (err) {
|
|
35262
|
+
return {
|
|
35263
|
+
ok: false,
|
|
35264
|
+
error: err instanceof Error ? err.message : String(err)
|
|
35265
|
+
};
|
|
35266
|
+
}
|
|
35267
|
+
}
|
|
35268
|
+
var DISCOVERABLE;
|
|
35269
|
+
var init_desktopConfig = __esm({
|
|
35270
|
+
"src/cli/desktopConfig.ts"() {
|
|
35271
|
+
"use strict";
|
|
35272
|
+
init_keyStore();
|
|
35273
|
+
init_providerConfig();
|
|
35274
|
+
init_modelDiscovery();
|
|
35275
|
+
init_updater();
|
|
35276
|
+
DISCOVERABLE = [
|
|
35277
|
+
"grok",
|
|
35278
|
+
"glm",
|
|
35279
|
+
"minimax",
|
|
35280
|
+
"deepseek",
|
|
35281
|
+
"openai-compatible"
|
|
35282
|
+
];
|
|
35283
|
+
}
|
|
35284
|
+
});
|
|
35285
|
+
|
|
35286
|
+
// src/cli/companion/config.ts
|
|
35287
|
+
import {
|
|
35288
|
+
existsSync as existsSync40,
|
|
35289
|
+
mkdirSync as mkdirSync17,
|
|
35290
|
+
readFileSync as readFileSync33,
|
|
35291
|
+
writeFileSync as writeFileSync21
|
|
35292
|
+
} from "node:fs";
|
|
35293
|
+
import { join as join35 } from "node:path";
|
|
35294
|
+
import { homedir as homedir11 } from "node:os";
|
|
35295
|
+
import { createHash as createHash6, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
35296
|
+
function getZelariHome() {
|
|
35297
|
+
return join35(homedir11(), ".zelari-code");
|
|
35298
|
+
}
|
|
35299
|
+
function getCompanionConfigPath() {
|
|
35300
|
+
return join35(getZelariHome(), "companion.json");
|
|
35301
|
+
}
|
|
35302
|
+
function getCompanionTokenPath() {
|
|
35303
|
+
return join35(getZelariHome(), "companion.token");
|
|
35304
|
+
}
|
|
35305
|
+
function ensureHome() {
|
|
35306
|
+
const home = getZelariHome();
|
|
35307
|
+
if (!existsSync40(home)) {
|
|
35308
|
+
mkdirSync17(home, { recursive: true });
|
|
35309
|
+
}
|
|
35310
|
+
}
|
|
35311
|
+
function loadCompanionConfig() {
|
|
35312
|
+
const path40 = getCompanionConfigPath();
|
|
35313
|
+
if (!existsSync40(path40)) {
|
|
35314
|
+
return { projects: [] };
|
|
35315
|
+
}
|
|
35316
|
+
try {
|
|
35317
|
+
const raw = JSON.parse(readFileSync33(path40, "utf8"));
|
|
35318
|
+
const projects = Array.isArray(raw.projects) ? raw.projects.filter(
|
|
35319
|
+
(p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
|
|
35320
|
+
).map((p3) => ({
|
|
35321
|
+
id: String(p3.id || p3.name).trim().toLowerCase().replace(/[^a-z0-9-_]+/g, "-").slice(0, 64),
|
|
35322
|
+
name: String(p3.name || p3.id || "project").trim() || "project",
|
|
35323
|
+
path: String(p3.path).trim()
|
|
35324
|
+
})) : [];
|
|
35325
|
+
return {
|
|
35326
|
+
projects,
|
|
35327
|
+
bind: typeof raw.bind === "string" ? raw.bind : void 0,
|
|
35328
|
+
port: typeof raw.port === "number" ? raw.port : void 0
|
|
35329
|
+
};
|
|
35330
|
+
} catch {
|
|
35331
|
+
return { projects: [] };
|
|
35332
|
+
}
|
|
35333
|
+
}
|
|
35334
|
+
function saveCompanionConfig(cfg) {
|
|
35335
|
+
ensureHome();
|
|
35336
|
+
writeFileSync21(
|
|
35337
|
+
getCompanionConfigPath(),
|
|
35338
|
+
JSON.stringify(
|
|
35339
|
+
{
|
|
35340
|
+
bind: cfg.bind,
|
|
35341
|
+
port: cfg.port,
|
|
35342
|
+
projects: cfg.projects
|
|
35343
|
+
},
|
|
35344
|
+
null,
|
|
35345
|
+
2
|
|
35346
|
+
) + "\n",
|
|
35347
|
+
"utf8"
|
|
35348
|
+
);
|
|
35349
|
+
}
|
|
35350
|
+
function loadOrCreateToken(explicit) {
|
|
35351
|
+
if (explicit && explicit.trim()) {
|
|
35352
|
+
return { token: explicit.trim(), created: false };
|
|
35353
|
+
}
|
|
35354
|
+
ensureHome();
|
|
35355
|
+
const path40 = getCompanionTokenPath();
|
|
35356
|
+
if (existsSync40(path40)) {
|
|
35357
|
+
const t = readFileSync33(path40, "utf8").trim();
|
|
35358
|
+
if (t) return { token: t, created: false };
|
|
35359
|
+
}
|
|
35360
|
+
const token = randomBytes2(24).toString("base64url");
|
|
35361
|
+
writeFileSync21(path40, token + "\n", "utf8");
|
|
35362
|
+
try {
|
|
35363
|
+
const fs24 = __require("node:fs");
|
|
35364
|
+
fs24.chmodSync?.(path40, 384);
|
|
35365
|
+
} catch {
|
|
35366
|
+
}
|
|
35367
|
+
return { token, created: true };
|
|
35368
|
+
}
|
|
35369
|
+
function tokenMatches(expected, provided) {
|
|
35370
|
+
if (!provided) return false;
|
|
35371
|
+
const a = createHash6("sha256").update(expected).digest();
|
|
35372
|
+
const b = createHash6("sha256").update(provided).digest();
|
|
35373
|
+
try {
|
|
35374
|
+
return timingSafeEqual(a, b);
|
|
35375
|
+
} catch {
|
|
35376
|
+
return false;
|
|
35377
|
+
}
|
|
35378
|
+
}
|
|
35379
|
+
function slugFromPath(p3) {
|
|
35380
|
+
const base = p3.replace(/[/\\]+$/, "").split(/[/\\]/).pop() || "project";
|
|
35381
|
+
return base.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "project";
|
|
35382
|
+
}
|
|
35383
|
+
function mergeProjects(cfg, extraPaths) {
|
|
35384
|
+
const byId = /* @__PURE__ */ new Map();
|
|
35385
|
+
for (const p3 of cfg.projects) {
|
|
35386
|
+
byId.set(p3.id, p3);
|
|
35387
|
+
}
|
|
35388
|
+
for (const raw of extraPaths) {
|
|
35389
|
+
const path40 = raw.trim();
|
|
35390
|
+
if (!path40) continue;
|
|
35391
|
+
let id = slugFromPath(path40);
|
|
35392
|
+
let n = 2;
|
|
35393
|
+
while (byId.has(id) && byId.get(id).path !== path40) {
|
|
35394
|
+
id = `${slugFromPath(path40)}-${n++}`;
|
|
35395
|
+
}
|
|
35396
|
+
byId.set(id, {
|
|
35397
|
+
id,
|
|
35398
|
+
name: slugFromPath(path40),
|
|
35399
|
+
path: path40
|
|
35400
|
+
});
|
|
35401
|
+
}
|
|
35402
|
+
return [...byId.values()];
|
|
35403
|
+
}
|
|
35404
|
+
function resolveProjectPath(projects, cwdOrId) {
|
|
35405
|
+
if (!projects.length) {
|
|
35406
|
+
return {
|
|
35407
|
+
ok: false,
|
|
35408
|
+
error: "No projects configured. Pass --project <path> or edit ~/.zelari-code/companion.json"
|
|
35409
|
+
};
|
|
35410
|
+
}
|
|
35411
|
+
if (!cwdOrId || !String(cwdOrId).trim()) {
|
|
35412
|
+
return { ok: true, project: projects[0] };
|
|
35413
|
+
}
|
|
35414
|
+
const key = String(cwdOrId).trim();
|
|
35415
|
+
const byId = projects.find((p3) => p3.id === key || p3.name === key);
|
|
35416
|
+
if (byId) return { ok: true, project: byId };
|
|
35417
|
+
const norm = key.replace(/\\/g, "/").toLowerCase();
|
|
35418
|
+
const byPath = projects.find(
|
|
35419
|
+
(p3) => p3.path.replace(/\\/g, "/").toLowerCase() === norm
|
|
35420
|
+
);
|
|
35421
|
+
if (byPath) return { ok: true, project: byPath };
|
|
35422
|
+
const under = projects.find((p3) => {
|
|
35423
|
+
const root = p3.path.replace(/\\/g, "/").toLowerCase().replace(/\/$/, "");
|
|
35424
|
+
return norm === root || norm.startsWith(root + "/");
|
|
35425
|
+
});
|
|
35426
|
+
if (under) {
|
|
35427
|
+
return {
|
|
35428
|
+
ok: true,
|
|
35429
|
+
project: { ...under, path: key }
|
|
35430
|
+
};
|
|
35431
|
+
}
|
|
35432
|
+
return {
|
|
35433
|
+
ok: false,
|
|
35434
|
+
error: `cwd/project not in allowlist: ${key}. Allowed: ${projects.map((p3) => p3.id).join(", ")}`
|
|
35435
|
+
};
|
|
35436
|
+
}
|
|
35437
|
+
var DEFAULT_COMPANION_PORT, DEFAULT_COMPANION_BIND;
|
|
35438
|
+
var init_config = __esm({
|
|
35439
|
+
"src/cli/companion/config.ts"() {
|
|
35440
|
+
"use strict";
|
|
35441
|
+
DEFAULT_COMPANION_PORT = 7421;
|
|
35442
|
+
DEFAULT_COMPANION_BIND = "127.0.0.1";
|
|
35443
|
+
}
|
|
35444
|
+
});
|
|
35445
|
+
|
|
35446
|
+
// src/cli/companion/runManager.ts
|
|
35447
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
35448
|
+
import { createInterface } from "node:readline";
|
|
35449
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
35450
|
+
import { writeFileSync as writeFileSync22, unlinkSync as unlinkSync2 } from "node:fs";
|
|
35451
|
+
import { join as join36 } from "node:path";
|
|
35452
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
35453
|
+
var RunManager;
|
|
35454
|
+
var init_runManager = __esm({
|
|
35455
|
+
"src/cli/companion/runManager.ts"() {
|
|
35456
|
+
"use strict";
|
|
35457
|
+
RunManager = class {
|
|
35458
|
+
active = null;
|
|
35459
|
+
recent = [];
|
|
35460
|
+
getActive() {
|
|
35461
|
+
return this.active?.run ?? null;
|
|
35462
|
+
}
|
|
35463
|
+
getRun(id) {
|
|
35464
|
+
if (this.active?.run.id === id) return this.active.run;
|
|
35465
|
+
return this.recent.find((r) => r.id === id) ?? null;
|
|
35466
|
+
}
|
|
35467
|
+
listRecent(limit = 20) {
|
|
35468
|
+
const cur = this.active ? [this.active.run] : [];
|
|
35469
|
+
return [...cur, ...this.recent].slice(0, limit);
|
|
35470
|
+
}
|
|
35471
|
+
subscribe(runId, fn) {
|
|
35472
|
+
if (this.active?.run.id === runId) {
|
|
35473
|
+
for (const ev of this.active.run.events) {
|
|
35474
|
+
try {
|
|
35475
|
+
fn(ev);
|
|
35476
|
+
} catch {
|
|
35477
|
+
}
|
|
35478
|
+
}
|
|
35479
|
+
this.active.listeners.add(fn);
|
|
35480
|
+
return () => {
|
|
35481
|
+
this.active?.listeners.delete(fn);
|
|
35482
|
+
};
|
|
35483
|
+
}
|
|
35484
|
+
const past = this.recent.find((r) => r.id === runId);
|
|
35485
|
+
if (past) {
|
|
35486
|
+
for (const ev of past.events) {
|
|
35487
|
+
try {
|
|
35488
|
+
fn(ev);
|
|
35489
|
+
} catch {
|
|
35490
|
+
}
|
|
35491
|
+
}
|
|
35492
|
+
}
|
|
35493
|
+
return () => {
|
|
35494
|
+
};
|
|
35495
|
+
}
|
|
35496
|
+
emit(ev) {
|
|
35497
|
+
if (!this.active) return;
|
|
35498
|
+
this.active.run.events.push(ev);
|
|
35499
|
+
if (this.active.run.events.length > 5e3) {
|
|
35500
|
+
this.active.run.events.splice(0, this.active.run.events.length - 4e3);
|
|
35501
|
+
}
|
|
35502
|
+
for (const fn of this.active.listeners) {
|
|
35503
|
+
try {
|
|
35504
|
+
fn(ev);
|
|
35505
|
+
} catch {
|
|
35506
|
+
}
|
|
35507
|
+
}
|
|
35508
|
+
}
|
|
35509
|
+
start(args) {
|
|
35510
|
+
if (this.active) {
|
|
35511
|
+
return {
|
|
35512
|
+
ok: false,
|
|
35513
|
+
error: `A run is already active (${this.active.run.id}). Cancel it first.`
|
|
35514
|
+
};
|
|
35515
|
+
}
|
|
35516
|
+
const prompt = args.prompt?.trim();
|
|
35517
|
+
if (!prompt) return { ok: false, error: "prompt is required" };
|
|
35518
|
+
const id = randomUUID6();
|
|
35519
|
+
const mode = (args.mode || "agent").toLowerCase();
|
|
35520
|
+
const phase2 = (args.phase || "build").toLowerCase();
|
|
35521
|
+
const run = {
|
|
35522
|
+
id,
|
|
35523
|
+
status: "running",
|
|
35524
|
+
prompt,
|
|
35525
|
+
mode,
|
|
35526
|
+
phase: phase2,
|
|
35527
|
+
cwd: args.cwd,
|
|
35528
|
+
createdAt: Date.now(),
|
|
35529
|
+
events: []
|
|
35530
|
+
};
|
|
35531
|
+
const cliEntry = process.argv[1];
|
|
35532
|
+
if (!cliEntry) {
|
|
35533
|
+
return { ok: false, error: "Cannot resolve CLI entry (process.argv[1])" };
|
|
35534
|
+
}
|
|
35535
|
+
const argv = [
|
|
35536
|
+
cliEntry,
|
|
35537
|
+
"--headless",
|
|
35538
|
+
"--task",
|
|
35539
|
+
prompt,
|
|
35540
|
+
"--output",
|
|
35541
|
+
"json",
|
|
35542
|
+
"--mode",
|
|
35543
|
+
mode === "council" || mode === "zelari" ? mode : "agent",
|
|
35544
|
+
"--phase",
|
|
35545
|
+
phase2 === "plan" ? "plan" : "build"
|
|
35546
|
+
];
|
|
35547
|
+
if (args.provider?.trim()) {
|
|
35548
|
+
argv.push("--provider", args.provider.trim());
|
|
35549
|
+
}
|
|
35550
|
+
if (args.model?.trim()) {
|
|
35551
|
+
argv.push("--model", args.model.trim());
|
|
35552
|
+
}
|
|
35553
|
+
let historyFile;
|
|
35554
|
+
if (args.history && Array.isArray(args.history) && args.history.length > 0) {
|
|
35555
|
+
historyFile = join36(tmpdir3(), `zelari-companion-hist-${id}.json`);
|
|
35556
|
+
try {
|
|
35557
|
+
writeFileSync22(historyFile, JSON.stringify(args.history), "utf8");
|
|
35558
|
+
argv.push("--history-file", historyFile);
|
|
35559
|
+
} catch {
|
|
35560
|
+
historyFile = void 0;
|
|
35561
|
+
}
|
|
35562
|
+
}
|
|
35563
|
+
const child = spawn11(process.execPath, argv, {
|
|
35564
|
+
cwd: args.cwd,
|
|
35565
|
+
env: {
|
|
35566
|
+
...process.env,
|
|
35567
|
+
ZELARI_SKIP_PREFLIGHT: "1",
|
|
35568
|
+
ANATHEMA_DEV: "1",
|
|
35569
|
+
FORCE_COLOR: "0"
|
|
35570
|
+
},
|
|
35571
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
35572
|
+
windowsHide: true
|
|
35573
|
+
});
|
|
35574
|
+
this.active = { run, child, listeners: /* @__PURE__ */ new Set(), historyFile };
|
|
35575
|
+
this.emit({
|
|
35576
|
+
type: "log",
|
|
35577
|
+
message: `[companion] run ${id} started mode=${mode} phase=${phase2} cwd=${args.cwd}`
|
|
35578
|
+
});
|
|
35579
|
+
if (child.stdout) {
|
|
35580
|
+
const rl = createInterface({ input: child.stdout });
|
|
35581
|
+
rl.on("line", (line) => {
|
|
35582
|
+
const t = line.trim();
|
|
35583
|
+
if (!t) return;
|
|
35584
|
+
try {
|
|
35585
|
+
const ev = JSON.parse(t);
|
|
35586
|
+
this.emit(ev);
|
|
35587
|
+
} catch {
|
|
35588
|
+
this.emit({ type: "log", message: t });
|
|
35589
|
+
}
|
|
35590
|
+
});
|
|
35591
|
+
}
|
|
35592
|
+
child.stderr?.on("data", (buf) => {
|
|
35593
|
+
const text = buf.toString("utf8").trim();
|
|
35594
|
+
if (!text) return;
|
|
35595
|
+
for (const line of text.split(/\r?\n/)) {
|
|
35596
|
+
if (line.trim()) {
|
|
35597
|
+
this.emit({ type: "log", message: line.trim() });
|
|
35598
|
+
}
|
|
35599
|
+
}
|
|
35600
|
+
});
|
|
35601
|
+
child.on("error", (err) => {
|
|
35602
|
+
run.status = "error";
|
|
35603
|
+
run.error = err.message;
|
|
35604
|
+
run.finishedAt = Date.now();
|
|
35605
|
+
this.emit({
|
|
35606
|
+
type: "error",
|
|
35607
|
+
severity: "fatal",
|
|
35608
|
+
message: err.message,
|
|
35609
|
+
code: "spawn"
|
|
35610
|
+
});
|
|
35611
|
+
this.finishActive();
|
|
35612
|
+
});
|
|
35613
|
+
child.on("close", (code) => {
|
|
35614
|
+
if (run.status === "running") {
|
|
35615
|
+
run.status = code === 0 ? "completed" : "error";
|
|
35616
|
+
run.exitCode = code;
|
|
35617
|
+
run.finishedAt = Date.now();
|
|
35618
|
+
if (code !== 0 && !run.error) {
|
|
35619
|
+
run.error = `exit ${code}`;
|
|
35620
|
+
}
|
|
35621
|
+
}
|
|
35622
|
+
this.emit({
|
|
35623
|
+
type: "run_finished",
|
|
35624
|
+
runId: id,
|
|
35625
|
+
status: run.status,
|
|
35626
|
+
exitCode: code
|
|
35627
|
+
});
|
|
35628
|
+
this.finishActive();
|
|
35629
|
+
});
|
|
35630
|
+
return { ok: true, run };
|
|
35631
|
+
}
|
|
35632
|
+
cancel(runId) {
|
|
35633
|
+
if (!this.active) {
|
|
35634
|
+
return { ok: false, error: "No active run" };
|
|
35635
|
+
}
|
|
35636
|
+
if (runId && this.active.run.id !== runId) {
|
|
35637
|
+
return { ok: false, error: `Run ${runId} is not active` };
|
|
35638
|
+
}
|
|
35639
|
+
const { run, child } = this.active;
|
|
35640
|
+
run.status = "cancelled";
|
|
35641
|
+
run.finishedAt = Date.now();
|
|
35642
|
+
this.emit({
|
|
35643
|
+
type: "log",
|
|
35644
|
+
message: `[companion] cancelling run ${run.id}`
|
|
35645
|
+
});
|
|
35646
|
+
try {
|
|
35647
|
+
child.kill("SIGTERM");
|
|
35648
|
+
} catch {
|
|
35649
|
+
}
|
|
35650
|
+
setTimeout(() => {
|
|
35651
|
+
try {
|
|
35652
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
35653
|
+
} catch {
|
|
35654
|
+
}
|
|
35655
|
+
}, 1500);
|
|
35656
|
+
return { ok: true };
|
|
35657
|
+
}
|
|
35658
|
+
finishActive() {
|
|
35659
|
+
if (!this.active) return;
|
|
35660
|
+
const { run, historyFile } = this.active;
|
|
35661
|
+
if (historyFile) {
|
|
35662
|
+
try {
|
|
35663
|
+
unlinkSync2(historyFile);
|
|
35664
|
+
} catch {
|
|
35665
|
+
}
|
|
35666
|
+
}
|
|
35667
|
+
this.recent.unshift(run);
|
|
35668
|
+
if (this.recent.length > 30) this.recent.length = 30;
|
|
35669
|
+
this.active = null;
|
|
35670
|
+
}
|
|
35671
|
+
};
|
|
35672
|
+
}
|
|
35673
|
+
});
|
|
35674
|
+
|
|
35675
|
+
// src/cli/companion/serve.ts
|
|
35676
|
+
var serve_exports = {};
|
|
35677
|
+
__export(serve_exports, {
|
|
35678
|
+
parseServeFlags: () => parseServeFlags,
|
|
35679
|
+
runCompanionServe: () => runCompanionServe
|
|
35680
|
+
});
|
|
35681
|
+
import { createServer } from "node:http";
|
|
35682
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
35683
|
+
import { resolve as resolve2 } from "node:path";
|
|
35684
|
+
function readBody(req, max = 2e6) {
|
|
35685
|
+
return new Promise((resolveBody, reject) => {
|
|
35686
|
+
const chunks = [];
|
|
35687
|
+
let size = 0;
|
|
35688
|
+
req.on("data", (c) => {
|
|
35689
|
+
size += c.length;
|
|
35690
|
+
if (size > max) {
|
|
35691
|
+
reject(new Error("body too large"));
|
|
35692
|
+
req.destroy();
|
|
35693
|
+
return;
|
|
35694
|
+
}
|
|
35695
|
+
chunks.push(c);
|
|
35696
|
+
});
|
|
35697
|
+
req.on("end", () => resolveBody(Buffer.concat(chunks).toString("utf8")));
|
|
35698
|
+
req.on("error", reject);
|
|
35699
|
+
});
|
|
35700
|
+
}
|
|
35701
|
+
function sendJson(res, status, body, extraHeaders) {
|
|
35702
|
+
const data = JSON.stringify(body);
|
|
35703
|
+
res.writeHead(status, {
|
|
35704
|
+
"content-type": "application/json; charset=utf-8",
|
|
35705
|
+
"content-length": Buffer.byteLength(data),
|
|
35706
|
+
"access-control-allow-origin": "*",
|
|
35707
|
+
"access-control-allow-headers": "authorization, content-type",
|
|
35708
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
35709
|
+
...extraHeaders
|
|
35710
|
+
});
|
|
35711
|
+
res.end(data);
|
|
35712
|
+
}
|
|
35713
|
+
function getBearer(req) {
|
|
35714
|
+
const h = req.headers.authorization;
|
|
35715
|
+
if (!h) return null;
|
|
35716
|
+
const m = /^Bearer\s+(.+)$/i.exec(h.trim());
|
|
35717
|
+
return m?.[1]?.trim() || null;
|
|
35718
|
+
}
|
|
35719
|
+
function parseUrl(req) {
|
|
35720
|
+
return new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
35721
|
+
}
|
|
35722
|
+
async function runCompanionServe(opts = {}) {
|
|
35723
|
+
const fileCfg = loadCompanionConfig();
|
|
35724
|
+
const bind = (opts.bind || fileCfg.bind || DEFAULT_COMPANION_BIND).trim();
|
|
35725
|
+
const port = opts.port ?? fileCfg.port ?? DEFAULT_COMPANION_PORT;
|
|
35726
|
+
const { token, created } = loadOrCreateToken(opts.token);
|
|
35727
|
+
let projects = mergeProjects(fileCfg, opts.projects ?? []);
|
|
35728
|
+
projects = projects.filter((p3) => {
|
|
35729
|
+
const abs = resolve2(p3.path);
|
|
35730
|
+
if (!existsSync41(abs)) {
|
|
35731
|
+
process.stderr.write(
|
|
35732
|
+
`[zelari-code serve] skip missing project path: ${p3.path}
|
|
35733
|
+
`
|
|
35734
|
+
);
|
|
35735
|
+
return false;
|
|
35736
|
+
}
|
|
35737
|
+
p3.path = abs;
|
|
35738
|
+
return true;
|
|
35739
|
+
});
|
|
35740
|
+
if (opts.persistProjects && projects.length > 0) {
|
|
35741
|
+
saveCompanionConfig({
|
|
35742
|
+
...fileCfg,
|
|
35743
|
+
bind,
|
|
35744
|
+
port,
|
|
35745
|
+
projects
|
|
35746
|
+
});
|
|
35747
|
+
}
|
|
35748
|
+
if (projects.length === 0) {
|
|
35749
|
+
const cwd = resolve2(process.cwd());
|
|
35750
|
+
projects = [
|
|
35751
|
+
{
|
|
35752
|
+
id: "default",
|
|
35753
|
+
name: "default",
|
|
35754
|
+
path: cwd
|
|
35755
|
+
}
|
|
35756
|
+
];
|
|
35757
|
+
process.stderr.write(
|
|
35758
|
+
`[zelari-code serve] no projects configured \u2014 using cwd as default: ${cwd}
|
|
35759
|
+
Add more: zelari-code serve --project <path>
|
|
35760
|
+
Or edit ~/.zelari-code/companion.json
|
|
35761
|
+
`
|
|
35762
|
+
);
|
|
35763
|
+
}
|
|
35764
|
+
const runs = new RunManager();
|
|
35765
|
+
const server = createServer(async (req, res) => {
|
|
35766
|
+
if (req.method === "OPTIONS") {
|
|
35767
|
+
res.writeHead(204, {
|
|
35768
|
+
"access-control-allow-origin": "*",
|
|
35769
|
+
"access-control-allow-headers": "authorization, content-type",
|
|
35770
|
+
"access-control-allow-methods": "GET, POST, OPTIONS"
|
|
35771
|
+
});
|
|
35772
|
+
res.end();
|
|
35773
|
+
return;
|
|
35774
|
+
}
|
|
35775
|
+
const url2 = parseUrl(req);
|
|
35776
|
+
const path40 = url2.pathname.replace(/\/+$/, "") || "/";
|
|
35777
|
+
try {
|
|
35778
|
+
if (req.method === "GET" && (path40 === "/health" || path40 === "/v1/health")) {
|
|
35779
|
+
sendJson(res, 200, {
|
|
35780
|
+
ok: true,
|
|
35781
|
+
service: "zelari-companion",
|
|
35782
|
+
version: getCurrentVersion(),
|
|
35783
|
+
bind,
|
|
35784
|
+
port,
|
|
35785
|
+
projects: projects.length,
|
|
35786
|
+
activeRun: runs.getActive()?.id ?? null
|
|
35787
|
+
});
|
|
35788
|
+
return;
|
|
35789
|
+
}
|
|
35790
|
+
if (path40.startsWith("/v1")) {
|
|
35791
|
+
if (!tokenMatches(token, getBearer(req))) {
|
|
35792
|
+
sendJson(res, 401, { ok: false, error: "unauthorized" });
|
|
35793
|
+
return;
|
|
35794
|
+
}
|
|
35795
|
+
}
|
|
35796
|
+
if (req.method === "GET" && path40 === "/v1/config") {
|
|
35797
|
+
const snap = buildDesktopConfigSnapshot();
|
|
35798
|
+
sendJson(res, 200, { ok: true, ...snap });
|
|
35799
|
+
return;
|
|
35800
|
+
}
|
|
35801
|
+
if (req.method === "GET" && path40 === "/v1/projects") {
|
|
35802
|
+
sendJson(res, 200, {
|
|
35803
|
+
ok: true,
|
|
35804
|
+
projects: projects.map((p3) => ({
|
|
35805
|
+
id: p3.id,
|
|
35806
|
+
name: p3.name,
|
|
35807
|
+
path: p3.path
|
|
35808
|
+
}))
|
|
35809
|
+
});
|
|
35810
|
+
return;
|
|
35811
|
+
}
|
|
35812
|
+
if (req.method === "GET" && path40 === "/v1/runs") {
|
|
35813
|
+
sendJson(res, 200, {
|
|
35814
|
+
ok: true,
|
|
35815
|
+
active: runs.getActive(),
|
|
35816
|
+
recent: runs.listRecent().map((r) => ({
|
|
35817
|
+
id: r.id,
|
|
35818
|
+
status: r.status,
|
|
35819
|
+
mode: r.mode,
|
|
35820
|
+
phase: r.phase,
|
|
35821
|
+
cwd: r.cwd,
|
|
35822
|
+
createdAt: r.createdAt,
|
|
35823
|
+
finishedAt: r.finishedAt,
|
|
35824
|
+
exitCode: r.exitCode,
|
|
35825
|
+
promptPreview: r.prompt.slice(0, 120)
|
|
35826
|
+
}))
|
|
35827
|
+
});
|
|
35828
|
+
return;
|
|
35829
|
+
}
|
|
35830
|
+
if (req.method === "POST" && path40 === "/v1/runs") {
|
|
35831
|
+
const raw = await readBody(req);
|
|
35832
|
+
let body = {};
|
|
35833
|
+
try {
|
|
35834
|
+
body = raw ? JSON.parse(raw) : {};
|
|
35835
|
+
} catch {
|
|
35836
|
+
sendJson(res, 400, { ok: false, error: "invalid JSON body" });
|
|
35837
|
+
return;
|
|
35838
|
+
}
|
|
35839
|
+
const prompt = String(body.prompt ?? body.task ?? "").trim();
|
|
35840
|
+
const mode = String(body.mode ?? "agent");
|
|
35841
|
+
const phase2 = String(body.phase ?? "build");
|
|
35842
|
+
const cwdArg = body.cwd != null ? String(body.cwd) : body.projectId != null ? String(body.projectId) : null;
|
|
35843
|
+
const resolved = resolveProjectPath(projects, cwdArg);
|
|
35844
|
+
if (!resolved.ok) {
|
|
35845
|
+
sendJson(res, 400, { ok: false, error: resolved.error });
|
|
35846
|
+
return;
|
|
35847
|
+
}
|
|
35848
|
+
const history2 = Array.isArray(body.history) ? body.history : void 0;
|
|
35849
|
+
const result = runs.start({
|
|
35850
|
+
prompt,
|
|
35851
|
+
mode,
|
|
35852
|
+
phase: phase2,
|
|
35853
|
+
cwd: resolved.project.path,
|
|
35854
|
+
provider: body.provider != null ? String(body.provider) : void 0,
|
|
35855
|
+
model: body.model != null ? String(body.model) : void 0,
|
|
35856
|
+
history: history2
|
|
35857
|
+
});
|
|
35858
|
+
if (!result.ok) {
|
|
35859
|
+
sendJson(res, 409, { ok: false, error: result.error });
|
|
35860
|
+
return;
|
|
35861
|
+
}
|
|
35862
|
+
sendJson(res, 201, {
|
|
35863
|
+
ok: true,
|
|
35864
|
+
run: {
|
|
35865
|
+
id: result.run.id,
|
|
35866
|
+
status: result.run.status,
|
|
35867
|
+
mode: result.run.mode,
|
|
35868
|
+
phase: result.run.phase,
|
|
35869
|
+
cwd: result.run.cwd,
|
|
35870
|
+
createdAt: result.run.createdAt
|
|
35871
|
+
},
|
|
35872
|
+
eventsUrl: `/v1/runs/${result.run.id}/events`,
|
|
35873
|
+
cancelUrl: `/v1/runs/${result.run.id}/cancel`
|
|
35874
|
+
});
|
|
35875
|
+
return;
|
|
35876
|
+
}
|
|
35877
|
+
const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path40);
|
|
35878
|
+
if (req.method === "GET" && eventsMatch) {
|
|
35879
|
+
const runId = eventsMatch[1];
|
|
35880
|
+
const run = runs.getRun(runId);
|
|
35881
|
+
if (!run) {
|
|
35882
|
+
sendJson(res, 404, { ok: false, error: "run not found" });
|
|
35883
|
+
return;
|
|
35884
|
+
}
|
|
35885
|
+
res.writeHead(200, {
|
|
35886
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
35887
|
+
"cache-control": "no-cache, no-transform",
|
|
35888
|
+
connection: "keep-alive",
|
|
35889
|
+
"access-control-allow-origin": "*",
|
|
35890
|
+
"access-control-allow-headers": "authorization, content-type"
|
|
35891
|
+
});
|
|
35892
|
+
res.write(`: connected run=${runId}
|
|
35893
|
+
|
|
35894
|
+
`);
|
|
35895
|
+
const writeEv = (ev) => {
|
|
35896
|
+
try {
|
|
35897
|
+
res.write(`data: ${JSON.stringify(ev)}
|
|
35898
|
+
|
|
35899
|
+
`);
|
|
35900
|
+
} catch {
|
|
35901
|
+
}
|
|
35902
|
+
};
|
|
35903
|
+
const unsub = runs.subscribe(runId, writeEv);
|
|
35904
|
+
if (run.status !== "running" && run.status !== "queued") {
|
|
35905
|
+
writeEv({
|
|
35906
|
+
type: "run_finished",
|
|
35907
|
+
runId,
|
|
35908
|
+
status: run.status,
|
|
35909
|
+
exitCode: run.exitCode ?? null
|
|
35910
|
+
});
|
|
35911
|
+
unsub();
|
|
35912
|
+
res.end();
|
|
35913
|
+
return;
|
|
35914
|
+
}
|
|
35915
|
+
const heartbeat = setInterval(() => {
|
|
35916
|
+
try {
|
|
35917
|
+
res.write(`: ping
|
|
35918
|
+
|
|
35919
|
+
`);
|
|
35920
|
+
} catch {
|
|
35921
|
+
}
|
|
35922
|
+
}, 15e3);
|
|
35923
|
+
const onClose = () => {
|
|
35924
|
+
clearInterval(heartbeat);
|
|
35925
|
+
unsub();
|
|
35926
|
+
};
|
|
35927
|
+
req.on("close", onClose);
|
|
35928
|
+
const check2 = setInterval(() => {
|
|
35929
|
+
const r = runs.getRun(runId);
|
|
35930
|
+
if (!r || r.status !== "running" && r.status !== "queued") {
|
|
35931
|
+
clearInterval(check2);
|
|
35932
|
+
clearInterval(heartbeat);
|
|
35933
|
+
unsub();
|
|
35934
|
+
try {
|
|
35935
|
+
res.end();
|
|
35936
|
+
} catch {
|
|
35937
|
+
}
|
|
35938
|
+
}
|
|
35939
|
+
}, 500);
|
|
35940
|
+
return;
|
|
35941
|
+
}
|
|
35942
|
+
const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path40);
|
|
35943
|
+
if (req.method === "POST" && cancelMatch) {
|
|
35944
|
+
const runId = cancelMatch[1];
|
|
35945
|
+
const result = runs.cancel(runId);
|
|
35946
|
+
if (!result.ok) {
|
|
35947
|
+
sendJson(res, 404, { ok: false, error: result.error });
|
|
35948
|
+
return;
|
|
35949
|
+
}
|
|
35950
|
+
sendJson(res, 200, { ok: true, cancelled: runId });
|
|
35951
|
+
return;
|
|
35952
|
+
}
|
|
35953
|
+
sendJson(res, 404, { ok: false, error: "not found" });
|
|
35954
|
+
} catch (err) {
|
|
35955
|
+
sendJson(res, 500, {
|
|
35956
|
+
ok: false,
|
|
35957
|
+
error: err instanceof Error ? err.message : String(err)
|
|
35958
|
+
});
|
|
35959
|
+
}
|
|
35960
|
+
});
|
|
35961
|
+
await new Promise((resolveListen, reject) => {
|
|
35962
|
+
server.once("error", reject);
|
|
35963
|
+
server.listen(port, bind, () => resolveListen());
|
|
35964
|
+
});
|
|
35965
|
+
server.ref();
|
|
35966
|
+
try {
|
|
35967
|
+
process.stdin?.resume?.();
|
|
35968
|
+
} catch {
|
|
35969
|
+
}
|
|
35970
|
+
const tokenHint = created ? `NEW token saved to ~/.zelari-code/companion.token` : `token: ~/.zelari-code/companion.token`;
|
|
35971
|
+
const displayHost = bind === "0.0.0.0" || bind === "::" ? "127.0.0.1 (and LAN/Tailscale interfaces)" : bind;
|
|
35972
|
+
process.stderr.write(
|
|
35973
|
+
`
|
|
35974
|
+
[zelari-code serve] companion host listening
|
|
35975
|
+
URL http://${displayHost === "127.0.0.1 (and LAN/Tailscale interfaces)" ? "127.0.0.1" : bind}:${port}
|
|
35976
|
+
Bind ${bind}
|
|
35977
|
+
Health GET /health
|
|
35978
|
+
Auth Authorization: Bearer <token>
|
|
35979
|
+
${tokenHint}
|
|
35980
|
+
Projects (${projects.length}): ${projects.map((p3) => p3.id).join(", ")}
|
|
35981
|
+
Phone use http://<PC-LAN-or-Tailscale-IP>:${port} (not 127.0.0.1 on the phone)
|
|
35982
|
+
Stop Ctrl+C (keep this window open)
|
|
35983
|
+
|
|
35984
|
+
`
|
|
35985
|
+
);
|
|
35986
|
+
if (created) {
|
|
35987
|
+
process.stderr.write(` Token (copy now): ${token}
|
|
35988
|
+
|
|
35989
|
+
`);
|
|
35990
|
+
}
|
|
35991
|
+
await new Promise((resolveStop) => {
|
|
35992
|
+
let stopped = false;
|
|
35993
|
+
const stop = (sig) => {
|
|
35994
|
+
if (stopped) return;
|
|
35995
|
+
stopped = true;
|
|
35996
|
+
process.stderr.write(`[zelari-code serve] shutting down (${sig})\u2026
|
|
35997
|
+
`);
|
|
35998
|
+
try {
|
|
35999
|
+
runs.cancel();
|
|
36000
|
+
} catch {
|
|
36001
|
+
}
|
|
36002
|
+
server.close(() => resolveStop());
|
|
36003
|
+
setTimeout(() => resolveStop(), 3e3).unref();
|
|
36004
|
+
};
|
|
36005
|
+
process.once("SIGINT", () => stop("SIGINT"));
|
|
36006
|
+
process.once("SIGTERM", () => stop("SIGTERM"));
|
|
36007
|
+
process.once("SIGHUP", () => stop("SIGHUP"));
|
|
36008
|
+
});
|
|
36009
|
+
}
|
|
36010
|
+
function parseServeFlags(argv) {
|
|
36011
|
+
if (!argv.includes("serve") && !argv.includes("--serve")) {
|
|
36012
|
+
return null;
|
|
36013
|
+
}
|
|
36014
|
+
const get = (flag) => {
|
|
36015
|
+
const i = argv.indexOf(flag);
|
|
36016
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
|
|
36017
|
+
};
|
|
36018
|
+
const projects = [];
|
|
36019
|
+
for (let i = 0; i < argv.length; i++) {
|
|
36020
|
+
if (argv[i] === "--project" && argv[i + 1]) {
|
|
36021
|
+
projects.push(argv[i + 1]);
|
|
36022
|
+
i++;
|
|
36023
|
+
}
|
|
36024
|
+
}
|
|
36025
|
+
const portRaw = get("--port");
|
|
36026
|
+
const port = portRaw ? Number.parseInt(portRaw, 10) : void 0;
|
|
36027
|
+
return {
|
|
36028
|
+
bind: get("--bind"),
|
|
36029
|
+
port: Number.isFinite(port) ? port : void 0,
|
|
36030
|
+
token: get("--token"),
|
|
36031
|
+
projects,
|
|
36032
|
+
persistProjects: argv.includes("--save-projects")
|
|
36033
|
+
};
|
|
36034
|
+
}
|
|
36035
|
+
var init_serve = __esm({
|
|
36036
|
+
"src/cli/companion/serve.ts"() {
|
|
36037
|
+
"use strict";
|
|
36038
|
+
init_updater();
|
|
36039
|
+
init_desktopConfig();
|
|
36040
|
+
init_config();
|
|
36041
|
+
init_runManager();
|
|
36042
|
+
}
|
|
36043
|
+
});
|
|
36044
|
+
|
|
34595
36045
|
// src/cli/utils/doctor.ts
|
|
34596
36046
|
var doctor_exports = {};
|
|
34597
36047
|
__export(doctor_exports, {
|
|
34598
36048
|
runDoctor: () => runDoctor
|
|
34599
36049
|
});
|
|
34600
36050
|
import { execSync as execSync2 } from "node:child_process";
|
|
34601
|
-
import { existsSync as
|
|
36051
|
+
import { existsSync as existsSync42, readFileSync as readFileSync34, readlinkSync, statSync as statSync7 } from "node:fs";
|
|
34602
36052
|
import { createRequire as createRequire3 } from "node:module";
|
|
34603
36053
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
34604
36054
|
import path39 from "node:path";
|
|
@@ -34606,9 +36056,9 @@ function findPackageRoot(start) {
|
|
|
34606
36056
|
let dir = start;
|
|
34607
36057
|
for (let i = 0; i < 6; i += 1) {
|
|
34608
36058
|
const candidate = path39.join(dir, "package.json");
|
|
34609
|
-
if (
|
|
36059
|
+
if (existsSync42(candidate)) {
|
|
34610
36060
|
try {
|
|
34611
|
-
const pkg = JSON.parse(
|
|
36061
|
+
const pkg = JSON.parse(readFileSync34(candidate, "utf8"));
|
|
34612
36062
|
if (pkg.name === "zelari-code") return dir;
|
|
34613
36063
|
} catch {
|
|
34614
36064
|
}
|
|
@@ -34632,7 +36082,7 @@ function tryExec(cmd) {
|
|
|
34632
36082
|
function readPackageJson3() {
|
|
34633
36083
|
try {
|
|
34634
36084
|
const pkgPath = path39.join(packageRoot, "package.json");
|
|
34635
|
-
return JSON.parse(
|
|
36085
|
+
return JSON.parse(readFileSync34(pkgPath, "utf8"));
|
|
34636
36086
|
} catch {
|
|
34637
36087
|
return null;
|
|
34638
36088
|
}
|
|
@@ -34648,16 +36098,16 @@ function checkShim(pkgName) {
|
|
|
34648
36098
|
const isWin = process.platform === "win32";
|
|
34649
36099
|
const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
|
|
34650
36100
|
const shimPath = path39.join(prefix, shimName);
|
|
34651
|
-
if (!
|
|
36101
|
+
if (!existsSync42(shimPath)) {
|
|
34652
36102
|
return FAIL(
|
|
34653
36103
|
`shim not found at ${shimPath}
|
|
34654
36104
|
fix: npm install -g ${pkgName}@latest --force`
|
|
34655
36105
|
);
|
|
34656
36106
|
}
|
|
34657
36107
|
try {
|
|
34658
|
-
const st =
|
|
36108
|
+
const st = statSync7(shimPath);
|
|
34659
36109
|
if (isWin) {
|
|
34660
|
-
const content =
|
|
36110
|
+
const content = readFileSync34(shimPath, "utf8");
|
|
34661
36111
|
if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
|
|
34662
36112
|
return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
|
|
34663
36113
|
}
|
|
@@ -34716,14 +36166,14 @@ function checkNode(pkg) {
|
|
|
34716
36166
|
}
|
|
34717
36167
|
function checkBundle() {
|
|
34718
36168
|
const bundle = path39.join(packageRoot, "dist", "cli", "main.bundled.js");
|
|
34719
|
-
if (!
|
|
36169
|
+
if (!existsSync42(bundle)) {
|
|
34720
36170
|
return FAIL(
|
|
34721
36171
|
`dist/cli/main.bundled.js missing at ${bundle}
|
|
34722
36172
|
fix: npm run build:cli (then reinstall or run via tsx)`
|
|
34723
36173
|
);
|
|
34724
36174
|
}
|
|
34725
36175
|
try {
|
|
34726
|
-
const st =
|
|
36176
|
+
const st = statSync7(bundle);
|
|
34727
36177
|
return OK(`bundle OK (${(st.size / 1024 / 1024).toFixed(2)} MB)`);
|
|
34728
36178
|
} catch (err) {
|
|
34729
36179
|
return FAIL(
|
|
@@ -35115,7 +36565,7 @@ function InputBarImpl({ value, onChange, onSubmit, disabled }) {
|
|
|
35115
36565
|
value,
|
|
35116
36566
|
onChange: stableChange,
|
|
35117
36567
|
onSubmit: stableSubmit,
|
|
35118
|
-
placeholder: disabled ? "..." : "
|
|
36568
|
+
placeholder: disabled ? "..." : "Prompt, /skills, or @path"
|
|
35119
36569
|
}
|
|
35120
36570
|
));
|
|
35121
36571
|
}
|
|
@@ -35666,175 +37116,8 @@ function StartupBanner({
|
|
|
35666
37116
|
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, "zelari-code v", version2, " \u2014 ", providerId, "/", model), /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "cwd: ", cwd), /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "/help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode"));
|
|
35667
37117
|
}
|
|
35668
37118
|
|
|
35669
|
-
// src/cli/modelDiscovery.ts
|
|
35670
|
-
import { promises as fs3, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
35671
|
-
import { homedir } from "node:os";
|
|
35672
|
-
import path4 from "node:path";
|
|
35673
|
-
var PROVIDER_BASE_URLS = {
|
|
35674
|
-
"grok": "https://api.x.ai/v1",
|
|
35675
|
-
// Must match PROVIDER_ENDPOINTS in provider/openai-compatible.ts (chat host).
|
|
35676
|
-
"glm": "https://api.z.ai/api/coding/paas/v4",
|
|
35677
|
-
"minimax": "https://api.minimax.io/v1",
|
|
35678
|
-
// Must match PROVIDER_ENDPOINTS in provider/openai-compatible.ts (chat host).
|
|
35679
|
-
"deepseek": "https://api.deepseek.com",
|
|
35680
|
-
"openai-compatible": "https://api.x.ai/v1"
|
|
35681
|
-
};
|
|
35682
|
-
async function resolveDiscoveryBaseUrl(provider, options) {
|
|
35683
|
-
if (options.baseUrl) return options.baseUrl;
|
|
35684
|
-
const { getCustomEndpoint: getCustomEndpoint2 } = await Promise.resolve().then(() => (init_providerConfig(), providerConfig_exports));
|
|
35685
|
-
const custom2 = getCustomEndpoint2(provider);
|
|
35686
|
-
if (custom2) return custom2;
|
|
35687
|
-
if (provider === "openai-compatible") {
|
|
35688
|
-
const envBase = process.env.OPENAI_BASE_URL;
|
|
35689
|
-
if (envBase && envBase.trim().length > 0) return envBase;
|
|
35690
|
-
}
|
|
35691
|
-
return PROVIDER_BASE_URLS[provider];
|
|
35692
|
-
}
|
|
35693
|
-
function defaultModelsFilePath() {
|
|
35694
|
-
return process.env.ANATHEMA_MODELS_FILE ?? path4.join(homedir(), ".tmp", "zelari-code", "models.json");
|
|
35695
|
-
}
|
|
35696
|
-
function getModelsFilePath() {
|
|
35697
|
-
return defaultModelsFilePath();
|
|
35698
|
-
}
|
|
35699
|
-
function loadModelsRegistry(file2 = getModelsFilePath()) {
|
|
35700
|
-
if (!existsSync3(file2)) return {};
|
|
35701
|
-
try {
|
|
35702
|
-
const raw = readFileSync3(file2, "utf-8");
|
|
35703
|
-
const parsed = JSON.parse(raw);
|
|
35704
|
-
if (!parsed || typeof parsed !== "object") return {};
|
|
35705
|
-
return parsed;
|
|
35706
|
-
} catch {
|
|
35707
|
-
return {};
|
|
35708
|
-
}
|
|
35709
|
-
}
|
|
35710
|
-
function getCachedModels(provider, file2 = getModelsFilePath()) {
|
|
35711
|
-
const registry3 = loadModelsRegistry(file2);
|
|
35712
|
-
return registry3[provider];
|
|
35713
|
-
}
|
|
35714
|
-
function isModelsCacheStale(provider, maxAgeMs = 6 * 60 * 60 * 1e3, file2 = getModelsFilePath(), now = Date.now()) {
|
|
35715
|
-
const entry = getCachedModels(provider, file2);
|
|
35716
|
-
if (!entry) return true;
|
|
35717
|
-
return now - entry.fetchedAt > maxAgeMs;
|
|
35718
|
-
}
|
|
35719
|
-
var writeChain = Promise.resolve();
|
|
35720
|
-
async function withRegistryLock(fn) {
|
|
35721
|
-
const next = writeChain.then(async () => fn());
|
|
35722
|
-
writeChain = next.catch(() => void 0);
|
|
35723
|
-
return next;
|
|
35724
|
-
}
|
|
35725
|
-
async function readModifyWriteRegistry(mutator, file2 = getModelsFilePath()) {
|
|
35726
|
-
return withRegistryLock(async () => {
|
|
35727
|
-
const onDisk = loadModelsRegistry(file2);
|
|
35728
|
-
const merged = mutator(onDisk);
|
|
35729
|
-
await fs3.mkdir(path4.dirname(file2), { recursive: true });
|
|
35730
|
-
const tmp = `${file2}.tmp-${process.pid}-${Date.now()}`;
|
|
35731
|
-
await fs3.writeFile(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
35732
|
-
await fs3.rename(tmp, file2);
|
|
35733
|
-
return merged;
|
|
35734
|
-
});
|
|
35735
|
-
}
|
|
35736
|
-
async function resolveAuthToken(provider, options) {
|
|
35737
|
-
if (options.authToken) return options.authToken;
|
|
35738
|
-
const { resolveApiKeyWithMeta: resolveApiKeyWithMeta2, getOAuthToken: getOAuthToken2 } = await Promise.resolve().then(() => (init_keyStore(), keyStore_exports));
|
|
35739
|
-
if (provider === "grok") {
|
|
35740
|
-
const oauth = getOAuthToken2("grok");
|
|
35741
|
-
if (oauth?.apiKey) return oauth.apiKey;
|
|
35742
|
-
}
|
|
35743
|
-
const resolved = await resolveApiKeyWithMeta2(provider);
|
|
35744
|
-
return resolved?.apiKey;
|
|
35745
|
-
}
|
|
35746
|
-
function parseOpenAIModelsResponse(json2, baseUrl) {
|
|
35747
|
-
if (!json2 || typeof json2 !== "object") return [];
|
|
35748
|
-
const obj = json2;
|
|
35749
|
-
if (!Array.isArray(obj.data)) return [];
|
|
35750
|
-
const models = [];
|
|
35751
|
-
for (const m of obj.data) {
|
|
35752
|
-
if (!m || typeof m.id !== "string") continue;
|
|
35753
|
-
const out = { id: m.id };
|
|
35754
|
-
if (typeof m.created === "number") out.created = m.created;
|
|
35755
|
-
if (typeof m.owned_by === "string") out.ownedBy = m.owned_by;
|
|
35756
|
-
const ctx = m.context_length;
|
|
35757
|
-
if (typeof ctx === "number") out.contextLength = ctx;
|
|
35758
|
-
models.push(out);
|
|
35759
|
-
}
|
|
35760
|
-
models.sort((a, b) => a.id.localeCompare(b.id));
|
|
35761
|
-
return models;
|
|
35762
|
-
}
|
|
35763
|
-
async function discoverModelsForProvider(provider, options = {}) {
|
|
35764
|
-
const baseUrl = await resolveDiscoveryBaseUrl(provider, options);
|
|
35765
|
-
if (!baseUrl || baseUrl.trim().length === 0) {
|
|
35766
|
-
throw new ModelDiscoveryError(
|
|
35767
|
-
`No base URL for provider "${provider}" \u2014 set one with /provider custom <url> before discovering models`,
|
|
35768
|
-
"no_base_url"
|
|
35769
|
-
);
|
|
35770
|
-
}
|
|
35771
|
-
const url2 = `${baseUrl.replace(/\/$/, "")}/models`;
|
|
35772
|
-
const authToken = await resolveAuthToken(provider, options);
|
|
35773
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
35774
|
-
let response;
|
|
35775
|
-
try {
|
|
35776
|
-
const headers = { Accept: "application/json" };
|
|
35777
|
-
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
|
35778
|
-
response = await fetchImpl(url2, { method: "GET", headers });
|
|
35779
|
-
} catch (err) {
|
|
35780
|
-
throw new ModelDiscoveryError(
|
|
35781
|
-
`Network error contacting ${url2}: ${err instanceof Error ? err.message : String(err)}`,
|
|
35782
|
-
"network_error"
|
|
35783
|
-
);
|
|
35784
|
-
}
|
|
35785
|
-
if (!response.ok) {
|
|
35786
|
-
const body = await response.text().catch(() => "");
|
|
35787
|
-
throw new ModelDiscoveryError(
|
|
35788
|
-
`HTTP ${response.status} from ${url2}: ${body.slice(0, 200)}`,
|
|
35789
|
-
`http_${response.status}`
|
|
35790
|
-
);
|
|
35791
|
-
}
|
|
35792
|
-
let json2;
|
|
35793
|
-
try {
|
|
35794
|
-
json2 = await response.json();
|
|
35795
|
-
} catch (err) {
|
|
35796
|
-
throw new ModelDiscoveryError(
|
|
35797
|
-
`Invalid JSON from ${url2}: ${err instanceof Error ? err.message : String(err)}`,
|
|
35798
|
-
"invalid_json"
|
|
35799
|
-
);
|
|
35800
|
-
}
|
|
35801
|
-
const models = parseOpenAIModelsResponse(json2, baseUrl);
|
|
35802
|
-
if (models.length === 0) {
|
|
35803
|
-
throw new ModelDiscoveryError(
|
|
35804
|
-
`Provider ${provider} returned 0 models \u2014 refusing to overwrite cache`,
|
|
35805
|
-
"empty_response"
|
|
35806
|
-
);
|
|
35807
|
-
}
|
|
35808
|
-
const entry = {
|
|
35809
|
-
models,
|
|
35810
|
-
fetchedAt: Date.now(),
|
|
35811
|
-
baseUrl
|
|
35812
|
-
};
|
|
35813
|
-
if (!options.skipCacheWrite) {
|
|
35814
|
-
const file2 = getModelsFilePath();
|
|
35815
|
-
await readModifyWriteRegistry((current) => {
|
|
35816
|
-
current[provider] = entry;
|
|
35817
|
-
return current;
|
|
35818
|
-
}, file2);
|
|
35819
|
-
}
|
|
35820
|
-
return entry;
|
|
35821
|
-
}
|
|
35822
|
-
function discoverModelsInBackground(provider, options = {}) {
|
|
35823
|
-
discoverModelsForProvider(provider, options).catch((err) => {
|
|
35824
|
-
if (err instanceof ModelDiscoveryError && options.onError) {
|
|
35825
|
-
options.onError(err);
|
|
35826
|
-
}
|
|
35827
|
-
});
|
|
35828
|
-
}
|
|
35829
|
-
var ModelDiscoveryError = class extends Error {
|
|
35830
|
-
constructor(message, code) {
|
|
35831
|
-
super(message);
|
|
35832
|
-
this.code = code;
|
|
35833
|
-
this.name = "ModelDiscoveryError";
|
|
35834
|
-
}
|
|
35835
|
-
};
|
|
35836
|
-
|
|
35837
37119
|
// src/cli/app.tsx
|
|
37120
|
+
init_modelDiscovery();
|
|
35838
37121
|
init_skills2();
|
|
35839
37122
|
|
|
35840
37123
|
// src/cli/hooks/useGitChanges.ts
|
|
@@ -35895,12 +37178,12 @@ function mergeChanges(unstaged, staged, untrackedPaths) {
|
|
|
35895
37178
|
return [...byPath.values()].sort((a, b) => churn(b) - churn(a));
|
|
35896
37179
|
}
|
|
35897
37180
|
function runGit(args, cwd) {
|
|
35898
|
-
return new Promise((
|
|
37181
|
+
return new Promise((resolve3, reject) => {
|
|
35899
37182
|
execFile(
|
|
35900
37183
|
"git",
|
|
35901
37184
|
args,
|
|
35902
37185
|
{ cwd, timeout: 5e3, windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
|
|
35903
|
-
(err, stdout) => err ? reject(err) :
|
|
37186
|
+
(err, stdout) => err ? reject(err) : resolve3(stdout)
|
|
35904
37187
|
);
|
|
35905
37188
|
});
|
|
35906
37189
|
}
|
|
@@ -38550,12 +39833,12 @@ var MetricsLogger = class {
|
|
|
38550
39833
|
}
|
|
38551
39834
|
/** Append a single record synchronously (file I/O). */
|
|
38552
39835
|
append(rec) {
|
|
38553
|
-
return new Promise((
|
|
39836
|
+
return new Promise((resolve3, reject) => {
|
|
38554
39837
|
try {
|
|
38555
39838
|
this.maybeRotate();
|
|
38556
39839
|
const line = JSON.stringify(rec) + "\n";
|
|
38557
39840
|
appendFileSync(this.file, line, { encoding: "utf-8" });
|
|
38558
|
-
|
|
39841
|
+
resolve3();
|
|
38559
39842
|
} catch (err) {
|
|
38560
39843
|
reject(err);
|
|
38561
39844
|
}
|
|
@@ -38699,7 +39982,7 @@ init_toolRegistry();
|
|
|
38699
39982
|
init_toolPermissions();
|
|
38700
39983
|
function createPermissionAskHandler(opts) {
|
|
38701
39984
|
const { setPicker: setPicker2, appendSystem: appendSystem2 } = opts;
|
|
38702
|
-
return (req) => new Promise((
|
|
39985
|
+
return (req) => new Promise((resolve3) => {
|
|
38703
39986
|
const cats = req.categories;
|
|
38704
39987
|
const catLabel = cats.length === 1 ? cats[0] : cats.join("+") || "action";
|
|
38705
39988
|
const title = `Allow tool "${req.toolName}"?`;
|
|
@@ -38716,7 +39999,7 @@ ${detail}
|
|
|
38716
39999
|
settled = true;
|
|
38717
40000
|
setPicker2(null);
|
|
38718
40001
|
if (note) appendSystem2?.(note, Date.now());
|
|
38719
|
-
|
|
40002
|
+
resolve3(ok);
|
|
38720
40003
|
};
|
|
38721
40004
|
const items = [
|
|
38722
40005
|
{ value: "allow", label: "Allow once" },
|
|
@@ -39110,10 +40393,10 @@ function useChatTurn(params) {
|
|
|
39110
40393
|
}
|
|
39111
40394
|
setBusy(true);
|
|
39112
40395
|
const workPhase = getPhase();
|
|
39113
|
-
const onAskUser = setPicker2 ? (req) => new Promise((
|
|
40396
|
+
const onAskUser = setPicker2 ? (req) => new Promise((resolve3) => {
|
|
39114
40397
|
const choices = req.choices ?? [];
|
|
39115
40398
|
if (choices.length < 2) {
|
|
39116
|
-
|
|
40399
|
+
resolve3(null);
|
|
39117
40400
|
return;
|
|
39118
40401
|
}
|
|
39119
40402
|
setLastClarification({
|
|
@@ -39134,7 +40417,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
|
|
|
39134
40417
|
if (settled) return;
|
|
39135
40418
|
settled = true;
|
|
39136
40419
|
setPicker2(null);
|
|
39137
|
-
|
|
40420
|
+
resolve3(value);
|
|
39138
40421
|
};
|
|
39139
40422
|
setPicker2({
|
|
39140
40423
|
kind: "clarification",
|
|
@@ -39730,10 +41013,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
39730
41013
|
const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
|
|
39731
41014
|
const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
|
|
39732
41015
|
const workPhase = getPhase();
|
|
39733
|
-
const onAskUserCouncil = setPicker2 ? (req) => new Promise((
|
|
41016
|
+
const onAskUserCouncil = setPicker2 ? (req) => new Promise((resolve3) => {
|
|
39734
41017
|
const choices = req.choices ?? [];
|
|
39735
41018
|
if (choices.length < 2) {
|
|
39736
|
-
|
|
41019
|
+
resolve3(null);
|
|
39737
41020
|
return;
|
|
39738
41021
|
}
|
|
39739
41022
|
setLastClarification({
|
|
@@ -39754,7 +41037,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
|
|
|
39754
41037
|
if (settled) return;
|
|
39755
41038
|
settled = true;
|
|
39756
41039
|
setPicker2(null);
|
|
39757
|
-
|
|
41040
|
+
resolve3(value);
|
|
39758
41041
|
};
|
|
39759
41042
|
setPicker2({
|
|
39760
41043
|
kind: "clarification",
|
|
@@ -39883,10 +41166,10 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
|
|
|
39883
41166
|
appendSystem(setMessages, message, Date.now());
|
|
39884
41167
|
},
|
|
39885
41168
|
// v1.8.0: pause council when a member asks a structured question.
|
|
39886
|
-
onClarification: setPicker2 ? (req) => new Promise((
|
|
41169
|
+
onClarification: setPicker2 ? (req) => new Promise((resolve3) => {
|
|
39887
41170
|
const choices = req.choices ?? [];
|
|
39888
41171
|
if (choices.length < 2) {
|
|
39889
|
-
|
|
41172
|
+
resolve3(null);
|
|
39890
41173
|
return;
|
|
39891
41174
|
}
|
|
39892
41175
|
setLastClarification({
|
|
@@ -39907,7 +41190,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
|
|
|
39907
41190
|
if (settled) return;
|
|
39908
41191
|
settled = true;
|
|
39909
41192
|
setPicker2(null);
|
|
39910
|
-
|
|
41193
|
+
resolve3(value);
|
|
39911
41194
|
};
|
|
39912
41195
|
setPicker2({
|
|
39913
41196
|
kind: "clarification",
|
|
@@ -40519,7 +41802,8 @@ function handleSlashCommand(text, availableSkills) {
|
|
|
40519
41802
|
/provider <name> \u2014 switch the active provider directly
|
|
40520
41803
|
/provider custom <baseUrl> \u2014 point the active provider at a self-hosted endpoint (Ollama, LM Studio, vLLM, ...)
|
|
40521
41804
|
/provider custom clear \u2014 clear the custom endpoint override
|
|
40522
|
-
/skill
|
|
41805
|
+
/skill [name] [input] \u2014 list+pick a skill (no args) or invoke /skill <name>
|
|
41806
|
+
/skills \u2014 interactive skill picker (same as /skill with no args)
|
|
40523
41807
|
/skill-stats [name] \u2014 show invocation stats (success rate, avg duration, total tokens)
|
|
40524
41808
|
/council <input> \u2014 invoke the multi-agent council on input
|
|
40525
41809
|
/zelari <input> \u2014 run an autonomous mission (multi-run council until the MVP slice is complete)
|
|
@@ -40767,13 +42051,19 @@ ${formatSkillList(availableSkills)}`
|
|
|
40767
42051
|
...note ? { feedbackNote: note } : {}
|
|
40768
42052
|
};
|
|
40769
42053
|
}
|
|
42054
|
+
case "skills": {
|
|
42055
|
+
return {
|
|
42056
|
+
handled: true,
|
|
42057
|
+
kind: "skill_picker",
|
|
42058
|
+
message: formatSkillList(availableSkills)
|
|
42059
|
+
};
|
|
42060
|
+
}
|
|
40770
42061
|
case "skill": {
|
|
40771
42062
|
const skillId = args[0];
|
|
40772
42063
|
if (!skillId) {
|
|
40773
42064
|
return {
|
|
40774
42065
|
handled: true,
|
|
40775
|
-
kind: "
|
|
40776
|
-
skillError: "Usage: /skill <name> [input]",
|
|
42066
|
+
kind: "skill_picker",
|
|
40777
42067
|
message: formatSkillList(availableSkills)
|
|
40778
42068
|
};
|
|
40779
42069
|
}
|
|
@@ -40782,7 +42072,7 @@ ${formatSkillList(availableSkills)}`
|
|
|
40782
42072
|
return {
|
|
40783
42073
|
handled: true,
|
|
40784
42074
|
kind: "skill",
|
|
40785
|
-
skillError: `Unknown skill: "${skillId}". Use /
|
|
42075
|
+
skillError: `Unknown skill: "${skillId}". Use /skills to pick one.`,
|
|
40786
42076
|
message: formatSkillList(availableSkills)
|
|
40787
42077
|
};
|
|
40788
42078
|
}
|
|
@@ -40790,7 +42080,8 @@ ${formatSkillList(availableSkills)}`
|
|
|
40790
42080
|
const expandedSkill = expandSkillTemplate(skill, { input });
|
|
40791
42081
|
return { handled: true, kind: "skill", expandedSkill };
|
|
40792
42082
|
}
|
|
40793
|
-
case "skill_stats":
|
|
42083
|
+
case "skill_stats":
|
|
42084
|
+
case "skill-stats": {
|
|
40794
42085
|
const skillId = args[0];
|
|
40795
42086
|
return {
|
|
40796
42087
|
handled: true,
|
|
@@ -41636,7 +42927,7 @@ ${browsers.output}` : "") + (browsers.ok ? "" : `
|
|
|
41636
42927
|
return result;
|
|
41637
42928
|
}
|
|
41638
42929
|
function runPlaywrightInstallChromium(cwd, executor) {
|
|
41639
|
-
return new Promise((
|
|
42930
|
+
return new Promise((resolve3) => {
|
|
41640
42931
|
let stdout = "";
|
|
41641
42932
|
let stderr = "";
|
|
41642
42933
|
const stdio = ["ignore", "pipe", "pipe"];
|
|
@@ -41649,7 +42940,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
|
|
|
41649
42940
|
stderr += chunk.toString();
|
|
41650
42941
|
});
|
|
41651
42942
|
child.on("error", (err) => {
|
|
41652
|
-
|
|
42943
|
+
resolve3({
|
|
41653
42944
|
ok: false,
|
|
41654
42945
|
output: stdout + stderr,
|
|
41655
42946
|
error: err.message,
|
|
@@ -41658,7 +42949,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
|
|
|
41658
42949
|
});
|
|
41659
42950
|
child.on("close", (code) => {
|
|
41660
42951
|
const ok = code === 0;
|
|
41661
|
-
|
|
42952
|
+
resolve3({
|
|
41662
42953
|
ok,
|
|
41663
42954
|
output: stdout + stderr,
|
|
41664
42955
|
exitCode: code,
|
|
@@ -41668,7 +42959,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
|
|
|
41668
42959
|
});
|
|
41669
42960
|
}
|
|
41670
42961
|
function runNpm2(executor, args, cwd, mode, npmCliPath) {
|
|
41671
|
-
return new Promise((
|
|
42962
|
+
return new Promise((resolve3) => {
|
|
41672
42963
|
let stdout = "";
|
|
41673
42964
|
let stderr = "";
|
|
41674
42965
|
const stdio = ["ignore", "pipe", "pipe"];
|
|
@@ -41680,7 +42971,7 @@ function runNpm2(executor, args, cwd, mode, npmCliPath) {
|
|
|
41680
42971
|
stderr += chunk.toString();
|
|
41681
42972
|
});
|
|
41682
42973
|
child.on("error", (err) => {
|
|
41683
|
-
|
|
42974
|
+
resolve3({
|
|
41684
42975
|
ok: false,
|
|
41685
42976
|
output: stdout + stderr,
|
|
41686
42977
|
error: err.message,
|
|
@@ -41689,7 +42980,7 @@ function runNpm2(executor, args, cwd, mode, npmCliPath) {
|
|
|
41689
42980
|
});
|
|
41690
42981
|
child.on("close", (code) => {
|
|
41691
42982
|
const ok = code === 0;
|
|
41692
|
-
|
|
42983
|
+
resolve3({
|
|
41693
42984
|
ok,
|
|
41694
42985
|
output: stdout + stderr,
|
|
41695
42986
|
exitCode: code,
|
|
@@ -42142,6 +43433,7 @@ async function validateApiKey(providerId, apiKey, options = {}) {
|
|
|
42142
43433
|
|
|
42143
43434
|
// src/cli/slashHandlers/provider.ts
|
|
42144
43435
|
init_providerConfig();
|
|
43436
|
+
init_modelDiscovery();
|
|
42145
43437
|
var UNKNOWN_PROVIDER_MSG = (id) => `[provider] unknown: ${id}. Available: openai-compatible, minimax, glm, grok, deepseek, custom`;
|
|
42146
43438
|
var DISCOVERABLE_PROVIDERS = ["grok", "glm", "minimax", "deepseek", "openai-compatible"];
|
|
42147
43439
|
function handleProviderList(ctx) {
|
|
@@ -42539,6 +43831,30 @@ async function applySteerInterrupt(options) {
|
|
|
42539
43831
|
}
|
|
42540
43832
|
|
|
42541
43833
|
// src/cli/slashHandlers/skills.ts
|
|
43834
|
+
function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
|
|
43835
|
+
if (!openPicker) {
|
|
43836
|
+
appendSystem(
|
|
43837
|
+
ctx.setMessages,
|
|
43838
|
+
fallbackMessage ?? formatSkillList(skills)
|
|
43839
|
+
);
|
|
43840
|
+
return;
|
|
43841
|
+
}
|
|
43842
|
+
if (skills.length === 0) {
|
|
43843
|
+
appendSystem(ctx.setMessages, "[skills] no skills registered");
|
|
43844
|
+
return;
|
|
43845
|
+
}
|
|
43846
|
+
const items = [...skills].sort((a, b) => a.id.localeCompare(b.id)).map((s) => ({
|
|
43847
|
+
value: s.id,
|
|
43848
|
+
label: s.name || s.id,
|
|
43849
|
+
hint: [s.category, s.estimatedCost, s.description?.slice(0, 60)].filter(Boolean).join(" \xB7 ")
|
|
43850
|
+
}));
|
|
43851
|
+
openPicker({
|
|
43852
|
+
kind: "skill",
|
|
43853
|
+
title: "Select a skill",
|
|
43854
|
+
items,
|
|
43855
|
+
commandPrefix: "/skill"
|
|
43856
|
+
});
|
|
43857
|
+
}
|
|
42542
43858
|
async function handleSkillStats(ctx, skillId) {
|
|
42543
43859
|
const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path37.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
|
|
42544
43860
|
try {
|
|
@@ -42612,6 +43928,7 @@ function handleClearChat(setMessages, setSessionActive) {
|
|
|
42612
43928
|
}
|
|
42613
43929
|
|
|
42614
43930
|
// src/cli/hooks/useSlashDispatch.ts
|
|
43931
|
+
init_atMentions();
|
|
42615
43932
|
function useSlashDispatch(params) {
|
|
42616
43933
|
const {
|
|
42617
43934
|
skills,
|
|
@@ -42652,19 +43969,27 @@ function useSlashDispatch(params) {
|
|
|
42652
43969
|
const steerCtx = { ...skillCtx, harnessRef, setQueueCount, dispatchPrompt };
|
|
42653
43970
|
const { onNewSession, onExit } = params;
|
|
42654
43971
|
if (!result.handled) {
|
|
43972
|
+
const expanded = expandAtMentions(value, process.cwd());
|
|
43973
|
+
const promptText = expanded.text;
|
|
42655
43974
|
appendUser(setMessages, value);
|
|
43975
|
+
if (expanded.hits.length > 0) {
|
|
43976
|
+
appendSystem(
|
|
43977
|
+
setMessages,
|
|
43978
|
+
`[tagged] ${expanded.hits.map((h) => h.path).join(", ")}`
|
|
43979
|
+
);
|
|
43980
|
+
}
|
|
42656
43981
|
setSessionActive(true);
|
|
42657
43982
|
if (mode === "zelari") {
|
|
42658
43983
|
setInput("");
|
|
42659
|
-
await dispatchZelariPrompt(
|
|
43984
|
+
await dispatchZelariPrompt(promptText);
|
|
42660
43985
|
return;
|
|
42661
43986
|
}
|
|
42662
43987
|
if (mode === "council") {
|
|
42663
43988
|
setInput("");
|
|
42664
|
-
await dispatchCouncilPrompt(
|
|
43989
|
+
await dispatchCouncilPrompt(promptText);
|
|
42665
43990
|
return;
|
|
42666
43991
|
}
|
|
42667
|
-
await dispatchPrompt(
|
|
43992
|
+
await dispatchPrompt(promptText);
|
|
42668
43993
|
setInput("");
|
|
42669
43994
|
return;
|
|
42670
43995
|
}
|
|
@@ -43014,11 +44339,20 @@ function useSlashDispatch(params) {
|
|
|
43014
44339
|
setInput("");
|
|
43015
44340
|
return;
|
|
43016
44341
|
}
|
|
44342
|
+
if (result.kind === "skill_picker") {
|
|
44343
|
+
handleSkillPicker(skillCtx, skills, params.openPicker, result.message);
|
|
44344
|
+
setInput("");
|
|
44345
|
+
return;
|
|
44346
|
+
}
|
|
43017
44347
|
if (result.kind === "skill" && result.expandedSkill) {
|
|
43018
44348
|
const sysMsg = result.message ?? `[skill] ${result.expandedSkill.skillId} \u2014 prompt ready (dispatch lands in Phase 14.7)`;
|
|
43019
44349
|
appendSystem(setMessages, sysMsg);
|
|
43020
44350
|
setInput("");
|
|
43021
|
-
|
|
44351
|
+
const skillPrompt = expandAtMentions(
|
|
44352
|
+
result.expandedSkill.prompt,
|
|
44353
|
+
process.cwd()
|
|
44354
|
+
).text;
|
|
44355
|
+
await dispatchPrompt(skillPrompt, {
|
|
43022
44356
|
requiredTools: result.expandedSkill.requiredTools
|
|
43023
44357
|
});
|
|
43024
44358
|
return;
|
|
@@ -43669,9 +45003,9 @@ function ContinueKey({ onContinue }) {
|
|
|
43669
45003
|
init_providerConfig();
|
|
43670
45004
|
|
|
43671
45005
|
// src/cli/wizard/firstRun.ts
|
|
43672
|
-
import { existsSync as
|
|
45006
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
43673
45007
|
function shouldRunWizard(input) {
|
|
43674
|
-
const exists = input.exists ??
|
|
45008
|
+
const exists = input.exists ?? existsSync38;
|
|
43675
45009
|
if (input.hasResetConfigFlag) {
|
|
43676
45010
|
return { shouldRun: true, reason: "--reset-config flag forced wizard" };
|
|
43677
45011
|
}
|
|
@@ -43956,7 +45290,7 @@ init_keyStore();
|
|
|
43956
45290
|
init_providerConfig();
|
|
43957
45291
|
init_openai_compatible();
|
|
43958
45292
|
init_phase();
|
|
43959
|
-
import { readFileSync as
|
|
45293
|
+
import { readFileSync as readFileSync31 } from "node:fs";
|
|
43960
45294
|
function parseHeadlessFlags(argv) {
|
|
43961
45295
|
if (!argv.includes("--headless")) {
|
|
43962
45296
|
return { options: null };
|
|
@@ -44025,7 +45359,7 @@ function parseHeadlessFlags(argv) {
|
|
|
44025
45359
|
let raw = null;
|
|
44026
45360
|
if (arg === "--history-file") {
|
|
44027
45361
|
try {
|
|
44028
|
-
raw =
|
|
45362
|
+
raw = readFileSync31(next, "utf-8");
|
|
44029
45363
|
} catch {
|
|
44030
45364
|
raw = null;
|
|
44031
45365
|
}
|
|
@@ -44155,6 +45489,17 @@ function createStreamScrubber() {
|
|
|
44155
45489
|
|
|
44156
45490
|
// src/cli/runHeadless.ts
|
|
44157
45491
|
async function runHeadless(opts) {
|
|
45492
|
+
try {
|
|
45493
|
+
const { expandAtMentions: expandAtMentions2 } = await Promise.resolve().then(() => (init_atMentions(), atMentions_exports));
|
|
45494
|
+
const task = typeof opts.task === "string" ? opts.task : "";
|
|
45495
|
+
if (task.includes("@")) {
|
|
45496
|
+
const { text } = expandAtMentions2(task, process.cwd());
|
|
45497
|
+
if (text !== task) {
|
|
45498
|
+
opts = { ...opts, task: text };
|
|
45499
|
+
}
|
|
45500
|
+
}
|
|
45501
|
+
} catch {
|
|
45502
|
+
}
|
|
44158
45503
|
let crashed = false;
|
|
44159
45504
|
const handleFatal = (label, err) => {
|
|
44160
45505
|
if (crashed) return;
|
|
@@ -45022,250 +46367,8 @@ ${ragContext}` : slicePrompt;
|
|
|
45022
46367
|
return exitCode;
|
|
45023
46368
|
}
|
|
45024
46369
|
|
|
45025
|
-
// src/cli/
|
|
45026
|
-
|
|
45027
|
-
init_providerConfig();
|
|
45028
|
-
init_updater();
|
|
45029
|
-
function wantsPrintConfig(argv) {
|
|
45030
|
-
return argv.includes("--print-config");
|
|
45031
|
-
}
|
|
45032
|
-
function wantsSetKey(argv) {
|
|
45033
|
-
return argv.includes("--set-key");
|
|
45034
|
-
}
|
|
45035
|
-
function wantsDiscoverModels(argv) {
|
|
45036
|
-
return argv.includes("--discover-models");
|
|
45037
|
-
}
|
|
45038
|
-
function parseSetConfigFlags(argv) {
|
|
45039
|
-
if (!argv.includes("--set-config")) {
|
|
45040
|
-
return { request: null };
|
|
45041
|
-
}
|
|
45042
|
-
let provider;
|
|
45043
|
-
let model;
|
|
45044
|
-
let endpoint;
|
|
45045
|
-
let endpointClear = false;
|
|
45046
|
-
for (let i = 0; i < argv.length; i++) {
|
|
45047
|
-
const arg = argv[i];
|
|
45048
|
-
if (arg === "--provider") {
|
|
45049
|
-
provider = argv[i + 1];
|
|
45050
|
-
i++;
|
|
45051
|
-
} else if (arg === "--model") {
|
|
45052
|
-
model = argv[i + 1];
|
|
45053
|
-
i++;
|
|
45054
|
-
} else if (arg === "--endpoint") {
|
|
45055
|
-
endpoint = argv[i + 1];
|
|
45056
|
-
i++;
|
|
45057
|
-
} else if (arg === "--endpoint-clear") {
|
|
45058
|
-
endpointClear = true;
|
|
45059
|
-
}
|
|
45060
|
-
}
|
|
45061
|
-
if (!provider && !model && !endpoint && !endpointClear) {
|
|
45062
|
-
return {
|
|
45063
|
-
request: null,
|
|
45064
|
-
error: "--set-config requires --provider, --model, --endpoint, and/or --endpoint-clear"
|
|
45065
|
-
};
|
|
45066
|
-
}
|
|
45067
|
-
if (provider !== void 0 && provider.trim().length === 0) {
|
|
45068
|
-
return { request: null, error: "--provider cannot be empty" };
|
|
45069
|
-
}
|
|
45070
|
-
if (model !== void 0 && model.trim().length === 0) {
|
|
45071
|
-
return { request: null, error: "--model cannot be empty" };
|
|
45072
|
-
}
|
|
45073
|
-
if (endpoint !== void 0 && endpoint.trim().length === 0) {
|
|
45074
|
-
return { request: null, error: "--endpoint cannot be empty" };
|
|
45075
|
-
}
|
|
45076
|
-
if (endpoint && endpointClear) {
|
|
45077
|
-
return { request: null, error: "--endpoint and --endpoint-clear conflict" };
|
|
45078
|
-
}
|
|
45079
|
-
return {
|
|
45080
|
-
request: {
|
|
45081
|
-
provider: provider?.trim(),
|
|
45082
|
-
model: model?.trim(),
|
|
45083
|
-
endpoint: endpoint?.trim(),
|
|
45084
|
-
endpointClear: endpointClear || void 0
|
|
45085
|
-
}
|
|
45086
|
-
};
|
|
45087
|
-
}
|
|
45088
|
-
function parseSetKeyFlags(argv) {
|
|
45089
|
-
if (!argv.includes("--set-key")) {
|
|
45090
|
-
return { request: null };
|
|
45091
|
-
}
|
|
45092
|
-
let provider;
|
|
45093
|
-
let key;
|
|
45094
|
-
for (let i = 0; i < argv.length; i++) {
|
|
45095
|
-
const arg = argv[i];
|
|
45096
|
-
if (arg === "--provider") {
|
|
45097
|
-
provider = argv[i + 1];
|
|
45098
|
-
i++;
|
|
45099
|
-
} else if (arg === "--key") {
|
|
45100
|
-
key = argv[i + 1];
|
|
45101
|
-
i++;
|
|
45102
|
-
}
|
|
45103
|
-
}
|
|
45104
|
-
if (!provider || provider.trim().length === 0) {
|
|
45105
|
-
return { request: null, error: "--set-key requires --provider <id>" };
|
|
45106
|
-
}
|
|
45107
|
-
if (!key || key.trim().length === 0) {
|
|
45108
|
-
return { request: null, error: "--set-key requires --key <secret>" };
|
|
45109
|
-
}
|
|
45110
|
-
return {
|
|
45111
|
-
request: {
|
|
45112
|
-
provider: provider.trim(),
|
|
45113
|
-
key: key.trim()
|
|
45114
|
-
}
|
|
45115
|
-
};
|
|
45116
|
-
}
|
|
45117
|
-
function parseDiscoverModelsFlags(argv) {
|
|
45118
|
-
if (!argv.includes("--discover-models")) {
|
|
45119
|
-
return { provider: null, present: false };
|
|
45120
|
-
}
|
|
45121
|
-
let provider;
|
|
45122
|
-
for (let i = 0; i < argv.length; i++) {
|
|
45123
|
-
if (argv[i] === "--provider") {
|
|
45124
|
-
provider = argv[i + 1];
|
|
45125
|
-
i++;
|
|
45126
|
-
}
|
|
45127
|
-
}
|
|
45128
|
-
return {
|
|
45129
|
-
present: true,
|
|
45130
|
-
provider: provider?.trim() || null
|
|
45131
|
-
};
|
|
45132
|
-
}
|
|
45133
|
-
function buildDesktopConfigSnapshot() {
|
|
45134
|
-
const config2 = getProviderConfig();
|
|
45135
|
-
const providers = PROVIDERS.map((p3) => {
|
|
45136
|
-
const cached2 = getCachedModels(p3.id);
|
|
45137
|
-
const models = cached2?.models.map((m) => m.id) ?? [];
|
|
45138
|
-
const defaultModel = config2.modelByProvider[p3.id] ?? "";
|
|
45139
|
-
if (defaultModel && !models.includes(defaultModel)) {
|
|
45140
|
-
models.unshift(defaultModel);
|
|
45141
|
-
}
|
|
45142
|
-
const custom2 = getCustomEndpoint(p3.id);
|
|
45143
|
-
const builtin = p3.baseUrl ?? null;
|
|
45144
|
-
return {
|
|
45145
|
-
id: p3.id,
|
|
45146
|
-
displayName: p3.displayName,
|
|
45147
|
-
hasKey: !!resolveApiKey(p3.id),
|
|
45148
|
-
envVar: p3.envVar,
|
|
45149
|
-
models,
|
|
45150
|
-
defaultModel,
|
|
45151
|
-
endpoint: custom2 ?? null,
|
|
45152
|
-
baseUrl: custom2 ?? builtin
|
|
45153
|
-
};
|
|
45154
|
-
});
|
|
45155
|
-
return {
|
|
45156
|
-
activeProviderId: config2.activeProviderId,
|
|
45157
|
-
modelByProvider: { ...config2.modelByProvider },
|
|
45158
|
-
providers,
|
|
45159
|
-
cliVersion: getCurrentVersion(),
|
|
45160
|
-
configPaths: {
|
|
45161
|
-
provider: getProviderConfigPath(),
|
|
45162
|
-
keys: getKeyStorePath()
|
|
45163
|
-
}
|
|
45164
|
-
};
|
|
45165
|
-
}
|
|
45166
|
-
function printDesktopConfig() {
|
|
45167
|
-
const snap = buildDesktopConfigSnapshot();
|
|
45168
|
-
process.stdout.write(JSON.stringify(snap, null, 2) + "\n");
|
|
45169
|
-
}
|
|
45170
|
-
function applySetConfig(req) {
|
|
45171
|
-
try {
|
|
45172
|
-
const config2 = getProviderConfig();
|
|
45173
|
-
let targetProvider = req.provider ?? config2.activeProviderId;
|
|
45174
|
-
if (req.provider) {
|
|
45175
|
-
const exists = PROVIDERS.some((p3) => p3.id === req.provider);
|
|
45176
|
-
if (!exists) {
|
|
45177
|
-
return {
|
|
45178
|
-
ok: false,
|
|
45179
|
-
error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
|
|
45180
|
-
};
|
|
45181
|
-
}
|
|
45182
|
-
setActiveProviderId(req.provider);
|
|
45183
|
-
targetProvider = req.provider;
|
|
45184
|
-
}
|
|
45185
|
-
if (req.endpointClear) {
|
|
45186
|
-
clearCustomEndpoint(targetProvider);
|
|
45187
|
-
}
|
|
45188
|
-
if (req.endpoint) {
|
|
45189
|
-
setCustomEndpoint(targetProvider, req.endpoint);
|
|
45190
|
-
}
|
|
45191
|
-
if (req.model) {
|
|
45192
|
-
setModelForProvider(targetProvider, req.model);
|
|
45193
|
-
}
|
|
45194
|
-
const after = getProviderConfig();
|
|
45195
|
-
const ep = getCustomEndpoint(after.activeProviderId);
|
|
45196
|
-
return {
|
|
45197
|
-
ok: true,
|
|
45198
|
-
message: `activeProvider=${after.activeProviderId} model=${after.modelByProvider[after.activeProviderId]}` + (ep ? ` endpoint=${ep}` : "")
|
|
45199
|
-
};
|
|
45200
|
-
} catch (err) {
|
|
45201
|
-
return {
|
|
45202
|
-
ok: false,
|
|
45203
|
-
error: err instanceof Error ? err.message : String(err)
|
|
45204
|
-
};
|
|
45205
|
-
}
|
|
45206
|
-
}
|
|
45207
|
-
function applySetKey(req) {
|
|
45208
|
-
try {
|
|
45209
|
-
const exists = PROVIDERS.some((p3) => p3.id === req.provider);
|
|
45210
|
-
if (!exists) {
|
|
45211
|
-
return {
|
|
45212
|
-
ok: false,
|
|
45213
|
-
error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
|
|
45214
|
-
};
|
|
45215
|
-
}
|
|
45216
|
-
setApiKey(req.provider, req.key);
|
|
45217
|
-
setActiveProviderId(req.provider);
|
|
45218
|
-
return {
|
|
45219
|
-
ok: true,
|
|
45220
|
-
provider: req.provider,
|
|
45221
|
-
masked: maskKey(req.key)
|
|
45222
|
-
};
|
|
45223
|
-
} catch (err) {
|
|
45224
|
-
return {
|
|
45225
|
-
ok: false,
|
|
45226
|
-
error: err instanceof Error ? err.message : String(err)
|
|
45227
|
-
};
|
|
45228
|
-
}
|
|
45229
|
-
}
|
|
45230
|
-
var DISCOVERABLE = [
|
|
45231
|
-
"grok",
|
|
45232
|
-
"glm",
|
|
45233
|
-
"minimax",
|
|
45234
|
-
"deepseek",
|
|
45235
|
-
"openai-compatible"
|
|
45236
|
-
];
|
|
45237
|
-
async function runDiscoverModels(providerArg) {
|
|
45238
|
-
const config2 = getProviderConfig();
|
|
45239
|
-
const providerId = (providerArg ?? config2.activeProviderId).trim();
|
|
45240
|
-
if (!DISCOVERABLE.includes(providerId)) {
|
|
45241
|
-
if (providerId === "custom") {
|
|
45242
|
-
} else {
|
|
45243
|
-
return {
|
|
45244
|
-
ok: false,
|
|
45245
|
-
error: `provider '${providerId}' is not discoverable. Use: ${DISCOVERABLE.join(", ")}`
|
|
45246
|
-
};
|
|
45247
|
-
}
|
|
45248
|
-
}
|
|
45249
|
-
const discoveryId = providerId === "custom" ? "openai-compatible" : providerId;
|
|
45250
|
-
try {
|
|
45251
|
-
const entry = await discoverModelsForProvider(discoveryId);
|
|
45252
|
-
return {
|
|
45253
|
-
ok: true,
|
|
45254
|
-
payload: {
|
|
45255
|
-
ok: true,
|
|
45256
|
-
provider: providerId,
|
|
45257
|
-
models: entry.models.map((m) => m.id),
|
|
45258
|
-
fetchedAt: entry.fetchedAt,
|
|
45259
|
-
baseUrl: entry.baseUrl
|
|
45260
|
-
}
|
|
45261
|
-
};
|
|
45262
|
-
} catch (err) {
|
|
45263
|
-
return {
|
|
45264
|
-
ok: false,
|
|
45265
|
-
error: err instanceof Error ? err.message : String(err)
|
|
45266
|
-
};
|
|
45267
|
-
}
|
|
45268
|
-
}
|
|
46370
|
+
// src/cli/main.ts
|
|
46371
|
+
init_desktopConfig();
|
|
45269
46372
|
|
|
45270
46373
|
// src/cli/plugins/cliFlags.ts
|
|
45271
46374
|
init_registry2();
|
|
@@ -45364,6 +46467,451 @@ init_skillsMd();
|
|
|
45364
46467
|
init_skills2();
|
|
45365
46468
|
init_updater();
|
|
45366
46469
|
init_mcpConfigIo();
|
|
46470
|
+
|
|
46471
|
+
// src/cli/skillConfigIo.ts
|
|
46472
|
+
init_skills2();
|
|
46473
|
+
init_skillsMd();
|
|
46474
|
+
import {
|
|
46475
|
+
existsSync as existsSync39,
|
|
46476
|
+
mkdirSync as mkdirSync16,
|
|
46477
|
+
readdirSync as readdirSync6,
|
|
46478
|
+
readFileSync as readFileSync32,
|
|
46479
|
+
rmSync as rmSync3,
|
|
46480
|
+
writeFileSync as writeFileSync20
|
|
46481
|
+
} from "node:fs";
|
|
46482
|
+
import { dirname as dirname9, join as join34 } from "node:path";
|
|
46483
|
+
import { homedir as homedir10 } from "node:os";
|
|
46484
|
+
|
|
46485
|
+
// src/cli/skillCategories.ts
|
|
46486
|
+
var CODING_CATEGORIES_LIST = [
|
|
46487
|
+
"plan",
|
|
46488
|
+
"refactor",
|
|
46489
|
+
"debug",
|
|
46490
|
+
"review",
|
|
46491
|
+
"test",
|
|
46492
|
+
"docs",
|
|
46493
|
+
"ops",
|
|
46494
|
+
"git",
|
|
46495
|
+
"db",
|
|
46496
|
+
"maint"
|
|
46497
|
+
];
|
|
46498
|
+
var CODING_CATEGORIES2 = new Set(CODING_CATEGORIES_LIST);
|
|
46499
|
+
|
|
46500
|
+
// src/cli/skillConfigIo.ts
|
|
46501
|
+
var NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
46502
|
+
var BUILTIN_SKILL_MODULES = [
|
|
46503
|
+
"@zelari/core/skills/builtin/debugging",
|
|
46504
|
+
"@zelari/core/skills/builtin/docs",
|
|
46505
|
+
"@zelari/core/skills/builtin/git-ops",
|
|
46506
|
+
"@zelari/core/skills/builtin/planning",
|
|
46507
|
+
"@zelari/core/skills/builtin/refactoring",
|
|
46508
|
+
"@zelari/core/skills/builtin/review",
|
|
46509
|
+
"@zelari/core/skills/builtin/testing",
|
|
46510
|
+
"@zelari/core/skills/builtin/schema-loop",
|
|
46511
|
+
"@zelari/core/skills/builtin/computer-use-cua"
|
|
46512
|
+
];
|
|
46513
|
+
var builtinsLoaded = false;
|
|
46514
|
+
function ensureBuiltinSkillsLoadedSync() {
|
|
46515
|
+
if (builtinsLoaded) return;
|
|
46516
|
+
for (const spec of BUILTIN_SKILL_MODULES) {
|
|
46517
|
+
try {
|
|
46518
|
+
require?.(spec);
|
|
46519
|
+
} catch {
|
|
46520
|
+
}
|
|
46521
|
+
}
|
|
46522
|
+
}
|
|
46523
|
+
function getUserSkillsDir() {
|
|
46524
|
+
return join34(homedir10(), ".zelari-code", "skills");
|
|
46525
|
+
}
|
|
46526
|
+
function getProjectSkillsDir(projectRoot) {
|
|
46527
|
+
return join34(projectRoot, ".zelari", "skills");
|
|
46528
|
+
}
|
|
46529
|
+
function skillFilePath(dir, name) {
|
|
46530
|
+
return join34(dir, name, "SKILL.md");
|
|
46531
|
+
}
|
|
46532
|
+
function classifyScope(skillPath, projectRoot) {
|
|
46533
|
+
const userDir = getUserSkillsDir().replace(/\\/g, "/");
|
|
46534
|
+
const norm = skillPath.replace(/\\/g, "/");
|
|
46535
|
+
if (norm.startsWith(userDir + "/") || norm === userDir) {
|
|
46536
|
+
return { scope: "user", writable: true };
|
|
46537
|
+
}
|
|
46538
|
+
if (projectRoot) {
|
|
46539
|
+
const projDir = getProjectSkillsDir(projectRoot).replace(/\\/g, "/");
|
|
46540
|
+
if (norm.startsWith(projDir + "/") || norm === projDir) {
|
|
46541
|
+
return { scope: "project", writable: true };
|
|
46542
|
+
}
|
|
46543
|
+
}
|
|
46544
|
+
return { scope: "compat", writable: false };
|
|
46545
|
+
}
|
|
46546
|
+
function entryFromParsed(parsed, projectRoot) {
|
|
46547
|
+
const { scope, writable } = classifyScope(parsed.sourcePath, projectRoot);
|
|
46548
|
+
return {
|
|
46549
|
+
id: parsed.name,
|
|
46550
|
+
name: parsed.name,
|
|
46551
|
+
description: parsed.description,
|
|
46552
|
+
category: parsed.category,
|
|
46553
|
+
estimatedCost: parsed.estimatedCost,
|
|
46554
|
+
requiredTools: parsed.requiredTools,
|
|
46555
|
+
scope,
|
|
46556
|
+
path: parsed.sourcePath,
|
|
46557
|
+
body: parsed.body,
|
|
46558
|
+
builtin: false,
|
|
46559
|
+
writable
|
|
46560
|
+
};
|
|
46561
|
+
}
|
|
46562
|
+
function entryFromBuiltin(skill) {
|
|
46563
|
+
return {
|
|
46564
|
+
id: skill.id,
|
|
46565
|
+
name: skill.name || skill.id,
|
|
46566
|
+
description: skill.description || "",
|
|
46567
|
+
category: skill.category,
|
|
46568
|
+
estimatedCost: skill.estimatedCost,
|
|
46569
|
+
requiredTools: skill.requiredTools,
|
|
46570
|
+
scope: "builtin",
|
|
46571
|
+
path: null,
|
|
46572
|
+
// Expose body so Desktop skill picker can expand like `/skill <id>`.
|
|
46573
|
+
body: skill.systemPromptFragment || void 0,
|
|
46574
|
+
builtin: true,
|
|
46575
|
+
writable: false
|
|
46576
|
+
};
|
|
46577
|
+
}
|
|
46578
|
+
function scanSkillsDir(dir, projectRoot, seen, out) {
|
|
46579
|
+
if (!existsSync39(dir)) return;
|
|
46580
|
+
let entries;
|
|
46581
|
+
try {
|
|
46582
|
+
entries = readdirSync6(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
46583
|
+
} catch {
|
|
46584
|
+
return;
|
|
46585
|
+
}
|
|
46586
|
+
for (const entry of entries) {
|
|
46587
|
+
const skillPath = skillFilePath(dir, entry);
|
|
46588
|
+
if (!existsSync39(skillPath)) continue;
|
|
46589
|
+
try {
|
|
46590
|
+
const parsed = parseSkillMd(readFileSync32(skillPath, "utf8"), skillPath);
|
|
46591
|
+
if (!parsed) continue;
|
|
46592
|
+
if (seen.has(parsed.name)) continue;
|
|
46593
|
+
seen.add(parsed.name);
|
|
46594
|
+
out.push(entryFromParsed(parsed, projectRoot));
|
|
46595
|
+
} catch {
|
|
46596
|
+
}
|
|
46597
|
+
}
|
|
46598
|
+
}
|
|
46599
|
+
function listSkillsSnapshot(projectRoot) {
|
|
46600
|
+
const root = projectRoot && projectRoot.trim() ? projectRoot.trim() : null;
|
|
46601
|
+
const userSkillsDir = getUserSkillsDir();
|
|
46602
|
+
const projectSkillsDir = root ? getProjectSkillsDir(root) : null;
|
|
46603
|
+
const skills = [];
|
|
46604
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46605
|
+
if (root) {
|
|
46606
|
+
scanSkillsDir(join34(root, ".zelari", "skills"), root, seen, skills);
|
|
46607
|
+
scanSkillsDir(join34(root, ".claude", "skills"), root, seen, skills);
|
|
46608
|
+
scanSkillsDir(join34(root, ".opencode", "skills"), root, seen, skills);
|
|
46609
|
+
}
|
|
46610
|
+
scanSkillsDir(userSkillsDir, root, seen, skills);
|
|
46611
|
+
for (const s of listCodingSkills()) {
|
|
46612
|
+
if (seen.has(s.id)) continue;
|
|
46613
|
+
seen.add(s.id);
|
|
46614
|
+
if (s.builtin) {
|
|
46615
|
+
skills.push(entryFromBuiltin(s));
|
|
46616
|
+
}
|
|
46617
|
+
}
|
|
46618
|
+
skills.sort((a, b) => {
|
|
46619
|
+
const order = (s) => s === "project" ? 0 : s === "user" ? 1 : s === "compat" ? 2 : 3;
|
|
46620
|
+
const d = order(a.scope) - order(b.scope);
|
|
46621
|
+
if (d !== 0) return d;
|
|
46622
|
+
return a.id.localeCompare(b.id);
|
|
46623
|
+
});
|
|
46624
|
+
return { userSkillsDir, projectSkillsDir, skills };
|
|
46625
|
+
}
|
|
46626
|
+
function serializeSkillMd(opts) {
|
|
46627
|
+
const name = opts.name.trim().toLowerCase();
|
|
46628
|
+
const description = opts.description.trim();
|
|
46629
|
+
const body = opts.body.trim();
|
|
46630
|
+
const lines = ["---", `name: ${name}`, `description: ${description}`];
|
|
46631
|
+
const cat = (opts.category ?? "").trim().toLowerCase();
|
|
46632
|
+
if (cat && CODING_CATEGORIES2.has(cat)) {
|
|
46633
|
+
lines.push(`category: ${cat}`);
|
|
46634
|
+
}
|
|
46635
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
46636
|
+
lines.push(`tools: [${opts.tools.map((t) => t.trim()).filter(Boolean).join(", ")}]`);
|
|
46637
|
+
}
|
|
46638
|
+
const cost = (opts.cost ?? "").trim().toLowerCase();
|
|
46639
|
+
if (cost === "low" || cost === "medium" || cost === "high") {
|
|
46640
|
+
lines.push(`cost: ${cost}`);
|
|
46641
|
+
}
|
|
46642
|
+
lines.push("---", "", body, "");
|
|
46643
|
+
return lines.join("\n");
|
|
46644
|
+
}
|
|
46645
|
+
function upsertSkill(opts) {
|
|
46646
|
+
const name = opts.name.trim().toLowerCase();
|
|
46647
|
+
if (!name || !NAME_RE.test(name)) {
|
|
46648
|
+
return {
|
|
46649
|
+
ok: false,
|
|
46650
|
+
error: "Invalid skill name (use lowercase letters, digits, hyphens; max 64 chars)"
|
|
46651
|
+
};
|
|
46652
|
+
}
|
|
46653
|
+
const description = opts.description.trim();
|
|
46654
|
+
if (!description) {
|
|
46655
|
+
return { ok: false, error: "description is required" };
|
|
46656
|
+
}
|
|
46657
|
+
const body = opts.body.trim();
|
|
46658
|
+
if (!body) {
|
|
46659
|
+
return { ok: false, error: "body is required" };
|
|
46660
|
+
}
|
|
46661
|
+
let dir;
|
|
46662
|
+
if (opts.scope === "user") {
|
|
46663
|
+
dir = getUserSkillsDir();
|
|
46664
|
+
} else {
|
|
46665
|
+
const root = opts.projectRoot?.trim();
|
|
46666
|
+
if (!root) {
|
|
46667
|
+
return {
|
|
46668
|
+
ok: false,
|
|
46669
|
+
error: "projectRoot required for project scope (Open Folder first)"
|
|
46670
|
+
};
|
|
46671
|
+
}
|
|
46672
|
+
dir = getProjectSkillsDir(root);
|
|
46673
|
+
}
|
|
46674
|
+
const path40 = skillFilePath(dir, name);
|
|
46675
|
+
const content = serializeSkillMd({
|
|
46676
|
+
name,
|
|
46677
|
+
description,
|
|
46678
|
+
body,
|
|
46679
|
+
category: opts.category,
|
|
46680
|
+
tools: opts.tools,
|
|
46681
|
+
cost: opts.cost
|
|
46682
|
+
});
|
|
46683
|
+
const parsed = parseSkillMd(content, path40);
|
|
46684
|
+
if (!parsed) {
|
|
46685
|
+
return { ok: false, error: "Generated SKILL.md failed validation" };
|
|
46686
|
+
}
|
|
46687
|
+
mkdirSync16(dirname9(path40), { recursive: true });
|
|
46688
|
+
writeFileSync20(path40, content, "utf8");
|
|
46689
|
+
return { ok: true, path: path40 };
|
|
46690
|
+
}
|
|
46691
|
+
function removeSkill(opts) {
|
|
46692
|
+
const name = opts.name.trim().toLowerCase();
|
|
46693
|
+
if (!name) {
|
|
46694
|
+
return { ok: false, error: "name is required" };
|
|
46695
|
+
}
|
|
46696
|
+
let dir;
|
|
46697
|
+
if (opts.scope === "user") {
|
|
46698
|
+
dir = getUserSkillsDir();
|
|
46699
|
+
} else {
|
|
46700
|
+
const root = opts.projectRoot?.trim();
|
|
46701
|
+
if (!root) {
|
|
46702
|
+
return { ok: false, error: "projectRoot required for project scope" };
|
|
46703
|
+
}
|
|
46704
|
+
dir = getProjectSkillsDir(root);
|
|
46705
|
+
}
|
|
46706
|
+
const skillDir = join34(dir, name);
|
|
46707
|
+
const path40 = skillFilePath(dir, name);
|
|
46708
|
+
if (!existsSync39(path40) && !existsSync39(skillDir)) {
|
|
46709
|
+
return { ok: false, error: `Skill "${name}" not found in ${dir}` };
|
|
46710
|
+
}
|
|
46711
|
+
try {
|
|
46712
|
+
rmSync3(skillDir, { recursive: true, force: true });
|
|
46713
|
+
} catch (err) {
|
|
46714
|
+
return {
|
|
46715
|
+
ok: false,
|
|
46716
|
+
error: err instanceof Error ? err.message : String(err)
|
|
46717
|
+
};
|
|
46718
|
+
}
|
|
46719
|
+
return { ok: true, path: path40 };
|
|
46720
|
+
}
|
|
46721
|
+
|
|
46722
|
+
// src/cli/generateSkillFromUrl.ts
|
|
46723
|
+
init_providerConfig();
|
|
46724
|
+
init_keyStore();
|
|
46725
|
+
init_openai_compatible();
|
|
46726
|
+
var MAX_PAGE_CHARS = 24e3;
|
|
46727
|
+
var FETCH_TIMEOUT_MS2 = 25e3;
|
|
46728
|
+
var LLM_TIMEOUT_MS = 9e4;
|
|
46729
|
+
var SYSTEM = `You convert web page content into a Zelari Code coding skill (SKILL.md style).
|
|
46730
|
+
|
|
46731
|
+
Return ONLY a single JSON object (no markdown fences) with keys:
|
|
46732
|
+
- name: string, kebab-case id (a-z0-9-hyphens, max 64, start with letter/digit)
|
|
46733
|
+
- description: string, one line, what the skill does
|
|
46734
|
+
- body: string, markdown instructions the agent should follow (steps, rules, checks)
|
|
46735
|
+
- category: one of ${CODING_CATEGORIES_LIST.join("|")} (optional)
|
|
46736
|
+
- tools: string[] of tool names the skill needs (optional, e.g. bash, read_file, write_file)
|
|
46737
|
+
- cost: "low" | "medium" | "high" (optional, default medium)
|
|
46738
|
+
|
|
46739
|
+
Rules:
|
|
46740
|
+
- Prefer actionable coding-agent instructions over marketing copy.
|
|
46741
|
+
- If the page is a skill/prompt already, adapt it cleanly to this schema.
|
|
46742
|
+
- If the page is docs/blog, distill a reusable workflow skill.
|
|
46743
|
+
- name must be unique-looking and descriptive (not "untitled" or "skill").
|
|
46744
|
+
- body must be non-empty markdown with clear steps.`;
|
|
46745
|
+
function stripHtml(html) {
|
|
46746
|
+
let t = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<noscript[\s\S]*?<\/noscript>/gi, " ");
|
|
46747
|
+
t = t.replace(/<!--[\s\S]*?-->/g, " ");
|
|
46748
|
+
t = t.replace(/<[^>]+>/g, " ");
|
|
46749
|
+
t = t.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
46750
|
+
t = t.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
|
|
46751
|
+
t = t.replace(/[ \t]{2,}/g, " ").trim();
|
|
46752
|
+
return t;
|
|
46753
|
+
}
|
|
46754
|
+
async function fetchUrlText(url2) {
|
|
46755
|
+
const u = url2.trim();
|
|
46756
|
+
if (!/^https?:\/\//i.test(u)) {
|
|
46757
|
+
throw new Error("URL must start with http:// or https://");
|
|
46758
|
+
}
|
|
46759
|
+
const controller = new AbortController();
|
|
46760
|
+
const t = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
|
|
46761
|
+
try {
|
|
46762
|
+
const res = await fetch(u, {
|
|
46763
|
+
signal: controller.signal,
|
|
46764
|
+
headers: {
|
|
46765
|
+
"user-agent": "zelari-code-skill-import/1.0",
|
|
46766
|
+
accept: "text/html,text/plain,application/json;q=0.9,*/*;q=0.8"
|
|
46767
|
+
},
|
|
46768
|
+
redirect: "follow"
|
|
46769
|
+
});
|
|
46770
|
+
if (!res.ok) {
|
|
46771
|
+
throw new Error(`Fetch failed HTTP ${res.status}`);
|
|
46772
|
+
}
|
|
46773
|
+
const ct = (res.headers.get("content-type") || "").toLowerCase();
|
|
46774
|
+
const raw = await res.text();
|
|
46775
|
+
if (ct.includes("json") || raw.trimStart().startsWith("{") || raw.trimStart().startsWith("[")) {
|
|
46776
|
+
return raw.slice(0, MAX_PAGE_CHARS);
|
|
46777
|
+
}
|
|
46778
|
+
if (ct.includes("html") || /<html[\s>]/i.test(raw) || /<body[\s>]/i.test(raw)) {
|
|
46779
|
+
return stripHtml(raw).slice(0, MAX_PAGE_CHARS);
|
|
46780
|
+
}
|
|
46781
|
+
return raw.slice(0, MAX_PAGE_CHARS);
|
|
46782
|
+
} finally {
|
|
46783
|
+
clearTimeout(t);
|
|
46784
|
+
}
|
|
46785
|
+
}
|
|
46786
|
+
function extractJsonObject(text) {
|
|
46787
|
+
const trimmed = text.trim();
|
|
46788
|
+
const fenced = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(trimmed);
|
|
46789
|
+
const body = fenced ? fenced[1].trim() : trimmed;
|
|
46790
|
+
try {
|
|
46791
|
+
return JSON.parse(body);
|
|
46792
|
+
} catch {
|
|
46793
|
+
const start = body.indexOf("{");
|
|
46794
|
+
const end = body.lastIndexOf("}");
|
|
46795
|
+
if (start >= 0 && end > start) {
|
|
46796
|
+
return JSON.parse(body.slice(start, end + 1));
|
|
46797
|
+
}
|
|
46798
|
+
throw new Error("Model did not return valid JSON");
|
|
46799
|
+
}
|
|
46800
|
+
}
|
|
46801
|
+
function normalizeDraft(raw, sourceUrl, provider, model) {
|
|
46802
|
+
if (!raw || typeof raw !== "object") {
|
|
46803
|
+
throw new Error("Invalid skill draft object");
|
|
46804
|
+
}
|
|
46805
|
+
const o = raw;
|
|
46806
|
+
let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
46807
|
+
if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
|
|
46808
|
+
try {
|
|
46809
|
+
const path40 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
46810
|
+
name = path40 && /^[a-z0-9]/.test(path40) ? path40 : "imported-skill";
|
|
46811
|
+
} catch {
|
|
46812
|
+
name = "imported-skill";
|
|
46813
|
+
}
|
|
46814
|
+
}
|
|
46815
|
+
const description = String(o.description ?? "").trim();
|
|
46816
|
+
const body = String(o.body ?? "").trim();
|
|
46817
|
+
if (!description) throw new Error("Model draft missing description");
|
|
46818
|
+
if (!body) throw new Error("Model draft missing body");
|
|
46819
|
+
const catRaw = String(o.category ?? "").trim().toLowerCase();
|
|
46820
|
+
const category = CODING_CATEGORIES_LIST.includes(catRaw) ? catRaw : void 0;
|
|
46821
|
+
let tools;
|
|
46822
|
+
if (Array.isArray(o.tools)) {
|
|
46823
|
+
tools = o.tools.map((t) => String(t).trim()).filter(Boolean);
|
|
46824
|
+
} else if (typeof o.tools === "string" && o.tools.trim()) {
|
|
46825
|
+
tools = o.tools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
46826
|
+
}
|
|
46827
|
+
const costRaw = String(o.cost ?? "medium").trim().toLowerCase();
|
|
46828
|
+
const cost = costRaw === "low" || costRaw === "high" || costRaw === "medium" ? costRaw : "medium";
|
|
46829
|
+
return {
|
|
46830
|
+
name,
|
|
46831
|
+
description,
|
|
46832
|
+
body,
|
|
46833
|
+
category,
|
|
46834
|
+
tools,
|
|
46835
|
+
cost,
|
|
46836
|
+
sourceUrl,
|
|
46837
|
+
model,
|
|
46838
|
+
provider
|
|
46839
|
+
};
|
|
46840
|
+
}
|
|
46841
|
+
async function resolveLlm(opts) {
|
|
46842
|
+
const active = opts.provider?.trim() || getProviderConfig().activeProviderId;
|
|
46843
|
+
const meta3 = await resolveApiKeyWithMeta(active);
|
|
46844
|
+
if (!meta3?.apiKey) {
|
|
46845
|
+
throw new Error(
|
|
46846
|
+
`No API key for provider '${active}'. Save a key in Settings \u2192 Provider.`
|
|
46847
|
+
);
|
|
46848
|
+
}
|
|
46849
|
+
const baseUrl = resolveBaseUrl(active);
|
|
46850
|
+
if (!baseUrl) {
|
|
46851
|
+
throw new Error(
|
|
46852
|
+
`No base URL for provider '${active}'. Set a custom endpoint in Settings.`
|
|
46853
|
+
);
|
|
46854
|
+
}
|
|
46855
|
+
const model = opts.model?.trim() || getModelForProvider(active) || process.env.ZELARI_MODEL || "";
|
|
46856
|
+
if (!model) {
|
|
46857
|
+
throw new Error(`No model selected for provider '${active}'`);
|
|
46858
|
+
}
|
|
46859
|
+
return { provider: active, model, apiKey: meta3.apiKey, baseUrl };
|
|
46860
|
+
}
|
|
46861
|
+
async function generateSkillFromUrl(opts) {
|
|
46862
|
+
const page = await fetchUrlText(opts.url);
|
|
46863
|
+
if (!page.trim()) {
|
|
46864
|
+
throw new Error("Page content is empty after fetch");
|
|
46865
|
+
}
|
|
46866
|
+
const llm = await resolveLlm({
|
|
46867
|
+
provider: opts.provider,
|
|
46868
|
+
model: opts.model
|
|
46869
|
+
});
|
|
46870
|
+
const controller = new AbortController();
|
|
46871
|
+
const t = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
|
|
46872
|
+
try {
|
|
46873
|
+
const url2 = `${llm.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
46874
|
+
const res = await fetch(url2, {
|
|
46875
|
+
method: "POST",
|
|
46876
|
+
signal: controller.signal,
|
|
46877
|
+
headers: {
|
|
46878
|
+
"content-type": "application/json",
|
|
46879
|
+
authorization: `Bearer ${llm.apiKey}`
|
|
46880
|
+
},
|
|
46881
|
+
body: JSON.stringify({
|
|
46882
|
+
model: llm.model,
|
|
46883
|
+
temperature: 0.2,
|
|
46884
|
+
max_tokens: 4096,
|
|
46885
|
+
stream: false,
|
|
46886
|
+
messages: [
|
|
46887
|
+
{ role: "system", content: SYSTEM },
|
|
46888
|
+
{
|
|
46889
|
+
role: "user",
|
|
46890
|
+
content: `Source URL: ${opts.url.trim()}
|
|
46891
|
+
|
|
46892
|
+
Page content (truncated):
|
|
46893
|
+
${page}`
|
|
46894
|
+
}
|
|
46895
|
+
]
|
|
46896
|
+
})
|
|
46897
|
+
});
|
|
46898
|
+
if (!res.ok) {
|
|
46899
|
+
const errBody = await res.text().catch(() => "");
|
|
46900
|
+
throw new Error(
|
|
46901
|
+
`LLM HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ""}`
|
|
46902
|
+
);
|
|
46903
|
+
}
|
|
46904
|
+
const json2 = await res.json();
|
|
46905
|
+
const text = json2.choices?.[0]?.message?.content?.trim();
|
|
46906
|
+
if (!text) throw new Error("Empty model response");
|
|
46907
|
+
const parsed = extractJsonObject(text);
|
|
46908
|
+
return normalizeDraft(parsed, opts.url.trim(), llm.provider, llm.model);
|
|
46909
|
+
} finally {
|
|
46910
|
+
clearTimeout(t);
|
|
46911
|
+
}
|
|
46912
|
+
}
|
|
46913
|
+
|
|
46914
|
+
// src/cli/main.ts
|
|
45367
46915
|
init_mcpPresets();
|
|
45368
46916
|
init_targets();
|
|
45369
46917
|
var VERSION = getCurrentVersion();
|
|
@@ -45403,7 +46951,7 @@ function runPreflight() {
|
|
|
45403
46951
|
}
|
|
45404
46952
|
async function backgroundUpdateCheck() {
|
|
45405
46953
|
if (process.env.ANATHEMA_DEV === "1") return;
|
|
45406
|
-
await new Promise((
|
|
46954
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
45407
46955
|
try {
|
|
45408
46956
|
const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
|
|
45409
46957
|
const info = await checkForUpdate2();
|
|
@@ -45441,6 +46989,35 @@ function pickRootComponent() {
|
|
|
45441
46989
|
void runPluginsInstall(argv).then((code) => process.exit(code));
|
|
45442
46990
|
return { kind: "done" };
|
|
45443
46991
|
}
|
|
46992
|
+
if (argv.includes("serve") || argv.includes("--serve")) {
|
|
46993
|
+
const { parseServeFlags: parseServeFlags2 } = (init_serve(), __toCommonJS(serve_exports));
|
|
46994
|
+
return { kind: "serve", serveOpts: parseServeFlags2(argv) ?? {} };
|
|
46995
|
+
}
|
|
46996
|
+
if (argv.includes("--generate-skill-from-url")) {
|
|
46997
|
+
void (async () => {
|
|
46998
|
+
try {
|
|
46999
|
+
const get = (flag) => {
|
|
47000
|
+
const i = argv.indexOf(flag);
|
|
47001
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
|
|
47002
|
+
};
|
|
47003
|
+
const url2 = get("--url");
|
|
47004
|
+
if (!url2) throw new Error("--url is required");
|
|
47005
|
+
const draft = await generateSkillFromUrl({
|
|
47006
|
+
url: url2,
|
|
47007
|
+
provider: get("--provider"),
|
|
47008
|
+
model: get("--model")
|
|
47009
|
+
});
|
|
47010
|
+
console.log(JSON.stringify({ ok: true, ...draft }, null, 2));
|
|
47011
|
+
process.exit(0);
|
|
47012
|
+
} catch (err) {
|
|
47013
|
+
console.error(
|
|
47014
|
+
`[zelari-code --generate-skill-from-url] ${err instanceof Error ? err.message : String(err)}`
|
|
47015
|
+
);
|
|
47016
|
+
process.exit(1);
|
|
47017
|
+
}
|
|
47018
|
+
})();
|
|
47019
|
+
return { kind: "done" };
|
|
47020
|
+
}
|
|
45444
47021
|
if (argv.includes("--doctor") || argv.includes("doctor")) {
|
|
45445
47022
|
const { runDoctor: runDoctor2 } = (init_doctor(), __toCommonJS(doctor_exports));
|
|
45446
47023
|
void runDoctor2().then((healthy) => process.exit(healthy ? 0 : 1));
|
|
@@ -45492,7 +47069,7 @@ function pickRootComponent() {
|
|
|
45492
47069
|
}
|
|
45493
47070
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
45494
47071
|
console.log(
|
|
45495
|
-
"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 --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 --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 agent|council|zelari Dispatch mode (default: agent)\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 --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 --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\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 (required)\n --args <json> JSON array of args (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 --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-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 ANATHEMA_DEV=1 Disable background update check + preflight\n"
|
|
47072
|
+
"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 --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 --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 agent|council|zelari Dispatch mode (default: agent)\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 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 --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\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 (required)\n --args <json> JSON array of args (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 --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 ANATHEMA_DEV=1 Disable background update check + preflight\n"
|
|
45496
47073
|
);
|
|
45497
47074
|
process.exit(0);
|
|
45498
47075
|
}
|
|
@@ -45571,6 +47148,82 @@ function pickRootComponent() {
|
|
|
45571
47148
|
process.exit(1);
|
|
45572
47149
|
}
|
|
45573
47150
|
}
|
|
47151
|
+
if (argv.includes("--print-skills")) {
|
|
47152
|
+
try {
|
|
47153
|
+
const cwdIdx = argv.indexOf("--cwd");
|
|
47154
|
+
const cwd = cwdIdx >= 0 && argv[cwdIdx + 1] ? argv[cwdIdx + 1] : process.cwd();
|
|
47155
|
+
ensureBuiltinSkillsLoadedSync();
|
|
47156
|
+
const snap = listSkillsSnapshot(cwd);
|
|
47157
|
+
console.log(JSON.stringify(snap, null, 2));
|
|
47158
|
+
process.exit(0);
|
|
47159
|
+
} catch (err) {
|
|
47160
|
+
console.error(
|
|
47161
|
+
`[zelari-code --print-skills] ${err instanceof Error ? err.message : String(err)}`
|
|
47162
|
+
);
|
|
47163
|
+
process.exit(1);
|
|
47164
|
+
}
|
|
47165
|
+
}
|
|
47166
|
+
if (argv.includes("--set-skill")) {
|
|
47167
|
+
try {
|
|
47168
|
+
const get = (flag) => {
|
|
47169
|
+
const i = argv.indexOf(flag);
|
|
47170
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
|
|
47171
|
+
};
|
|
47172
|
+
const name = get("--name");
|
|
47173
|
+
const description = get("--description");
|
|
47174
|
+
const body = get("--body");
|
|
47175
|
+
const category = get("--category");
|
|
47176
|
+
const toolsRaw = get("--tools");
|
|
47177
|
+
const cost = get("--cost");
|
|
47178
|
+
const scopeRaw = get("--scope") ?? "user";
|
|
47179
|
+
const scope = scopeRaw === "project" ? "project" : "user";
|
|
47180
|
+
const cwd = get("--cwd") ?? process.cwd();
|
|
47181
|
+
if (!name) throw new Error("--name is required");
|
|
47182
|
+
if (!description) throw new Error("--description is required");
|
|
47183
|
+
if (!body) throw new Error("--body is required");
|
|
47184
|
+
const tools = toolsRaw ? toolsRaw.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
|
|
47185
|
+
const result = upsertSkill({
|
|
47186
|
+
scope,
|
|
47187
|
+
name,
|
|
47188
|
+
description,
|
|
47189
|
+
body,
|
|
47190
|
+
category,
|
|
47191
|
+
tools,
|
|
47192
|
+
cost,
|
|
47193
|
+
projectRoot: cwd
|
|
47194
|
+
});
|
|
47195
|
+
if (!result.ok) throw new Error(result.error);
|
|
47196
|
+
console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
|
|
47197
|
+
process.exit(0);
|
|
47198
|
+
} catch (err) {
|
|
47199
|
+
console.error(
|
|
47200
|
+
`[zelari-code --set-skill] ${err instanceof Error ? err.message : String(err)}`
|
|
47201
|
+
);
|
|
47202
|
+
process.exit(1);
|
|
47203
|
+
}
|
|
47204
|
+
}
|
|
47205
|
+
if (argv.includes("--remove-skill")) {
|
|
47206
|
+
try {
|
|
47207
|
+
const get = (flag) => {
|
|
47208
|
+
const i = argv.indexOf(flag);
|
|
47209
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
|
|
47210
|
+
};
|
|
47211
|
+
const name = get("--name");
|
|
47212
|
+
const scopeRaw = get("--scope") ?? "user";
|
|
47213
|
+
const scope = scopeRaw === "project" ? "project" : "user";
|
|
47214
|
+
const cwd = get("--cwd") ?? process.cwd();
|
|
47215
|
+
if (!name) throw new Error("--name is required");
|
|
47216
|
+
const result = removeSkill({ scope, name, projectRoot: cwd });
|
|
47217
|
+
if (!result.ok) throw new Error(result.error);
|
|
47218
|
+
console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
|
|
47219
|
+
process.exit(0);
|
|
47220
|
+
} catch (err) {
|
|
47221
|
+
console.error(
|
|
47222
|
+
`[zelari-code --remove-skill] ${err instanceof Error ? err.message : String(err)}`
|
|
47223
|
+
);
|
|
47224
|
+
process.exit(1);
|
|
47225
|
+
}
|
|
47226
|
+
}
|
|
45574
47227
|
if (argv.includes("--set-mcp-preset")) {
|
|
45575
47228
|
try {
|
|
45576
47229
|
const get = (flag) => {
|
|
@@ -45843,6 +47496,15 @@ function loadUserSkills() {
|
|
|
45843
47496
|
function main() {
|
|
45844
47497
|
const picked = pickRootComponent();
|
|
45845
47498
|
if (picked.kind === "done") return;
|
|
47499
|
+
if (picked.kind === "serve") {
|
|
47500
|
+
void Promise.resolve().then(() => (init_serve(), serve_exports)).then(({ runCompanionServe: runCompanionServe2 }) => runCompanionServe2(picked.serveOpts ?? {})).then(() => process.exit(0)).catch((err) => {
|
|
47501
|
+
console.error(
|
|
47502
|
+
`[zelari-code serve] ${err instanceof Error ? err.message : String(err)}`
|
|
47503
|
+
);
|
|
47504
|
+
process.exit(1);
|
|
47505
|
+
});
|
|
47506
|
+
return;
|
|
47507
|
+
}
|
|
45846
47508
|
runPreflight();
|
|
45847
47509
|
loadUserSkills();
|
|
45848
47510
|
if (picked.kind === "headless") {
|
|
@@ -45856,7 +47518,7 @@ function main() {
|
|
|
45856
47518
|
closeMcpClients2();
|
|
45857
47519
|
} catch {
|
|
45858
47520
|
}
|
|
45859
|
-
await new Promise((
|
|
47521
|
+
await new Promise((resolve3) => setImmediate(resolve3));
|
|
45860
47522
|
process.exit(code);
|
|
45861
47523
|
}).catch(async (err) => {
|
|
45862
47524
|
console.error(
|