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.
Files changed (34) hide show
  1. package/README.md +3 -0
  2. package/bin/zelari-code.js +44 -12
  3. package/dist/cli/atMentions.js +179 -0
  4. package/dist/cli/atMentions.js.map +1 -0
  5. package/dist/cli/companion/config.js +172 -0
  6. package/dist/cli/companion/config.js.map +1 -0
  7. package/dist/cli/companion/runManager.js +250 -0
  8. package/dist/cli/companion/runManager.js.map +1 -0
  9. package/dist/cli/companion/serve.js +376 -0
  10. package/dist/cli/companion/serve.js.map +1 -0
  11. package/dist/cli/components/InputBar.js +1 -1
  12. package/dist/cli/components/InputBar.js.map +1 -1
  13. package/dist/cli/generateSkillFromUrl.js +236 -0
  14. package/dist/cli/generateSkillFromUrl.js.map +1 -0
  15. package/dist/cli/hooks/useSlashDispatch.js +19 -6
  16. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  17. package/dist/cli/main.bundled.js +2190 -528
  18. package/dist/cli/main.bundled.js.map +4 -4
  19. package/dist/cli/main.js +155 -0
  20. package/dist/cli/main.js.map +1 -1
  21. package/dist/cli/provider/openai-compatible.js +164 -41
  22. package/dist/cli/provider/openai-compatible.js.map +1 -1
  23. package/dist/cli/runHeadless.js +15 -0
  24. package/dist/cli/runHeadless.js.map +1 -1
  25. package/dist/cli/skillCategories.js +15 -0
  26. package/dist/cli/skillCategories.js.map +1 -0
  27. package/dist/cli/skillConfigIo.js +285 -0
  28. package/dist/cli/skillConfigIo.js.map +1 -0
  29. package/dist/cli/slashCommands.js +14 -5
  30. package/dist/cli/slashCommands.js.map +1 -1
  31. package/dist/cli/slashHandlers/skills.js +30 -0
  32. package/dist/cli/slashHandlers/skills.js.map +1 -1
  33. package/package.json +3 -2
  34. package/scripts/start-companion-serve.ps1 +43 -0
@@ -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: spawn11 } = await import("node:child_process");
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((resolve, reject) => {
409
+ return new Promise((resolve3, reject) => {
404
410
  try {
405
- const child = spawn11(cmd.bin, cmd.args, {
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
- resolve();
418
+ resolve3();
413
419
  });
414
- setTimeout(() => resolve(), 100);
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 sep = process.platform === "win32" ? ";" : ":";
17001
+ const sep2 = process.platform === "win32" ? ";" : ":";
16822
17002
  const current = env.PATH ?? env.Path ?? "";
16823
- const parts = current.split(sep).filter((p3) => p3.length > 0);
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}${sep}${current}`;
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((resolve) => {
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
- resolve(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.`));
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
- resolve(typedOk({
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
- resolve(typedErr(err.message));
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((resolve) => {
20633
- resolveNext = resolve;
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((resolve) => {
20753
- if (signal?.aborted) return resolve();
20754
- const t = setTimeout(resolve, ms);
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
- resolve();
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 timeoutSignal = AbortSignal.timeout(PROVIDER_TIMEOUT_MS);
20867
- const fetchSignal = params.signal ? AbortSignal.any([params.signal, timeoutSignal]) : timeoutSignal;
20868
- response = await fetch(`${config2.baseUrl}/chat/completions`, {
20869
- method: "POST",
20870
- headers: {
20871
- "Content-Type": "application/json",
20872
- Authorization: `Bearer ${config2.apiKey}`
20873
- },
20874
- body: JSON.stringify(body),
20875
- // Use `params.signal` (per-call AbortSignal from AgentHarness
20876
- // controller) so `.cancel()` actually aborts the HTTP request.
20877
- // `config.signal` is the factory-level signal, typically undefined.
20878
- // v0.6.0 audit HIGH-2.
20879
- // v1.10.0: also apply PROVIDER_TIMEOUT_MS so a stalled connection
20880
- // can't hang the harness forever.
20881
- signal: fetchSignal
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
- lastErrText = err instanceof Error ? err.message : String(err);
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
- if (attempt < MAX_RETRIES) {
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
- const { value, done } = await reader.read();
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, PROVIDER_TIMEOUT_MS, PROVIDER_ENDPOINTS;
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
- PROVIDER_TIMEOUT_MS = (() => {
21088
- const raw = process.env.ZELARI_PROVIDER_TIMEOUT_MS;
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 >= 1e4 ? n : 3e5;
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((resolve) => {
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
- resolve(r);
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((resolve, reject) => {
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((resolve, reject) => {
22613
- resolveInit = resolve;
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 resolve = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
23333
- const pw = await resolve();
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((resolve) => {
24092
+ return new Promise((resolve3) => {
23826
24093
  if (target.auth === "password" && !getSshPassword(target.id)) {
23827
- resolve({
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
- resolve({
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
- resolve({
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
- resolve({ code: code ?? 1, stdout, stderr });
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((resolve) => {
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
- resolve({ exitCode, stdout, stderr });
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((resolve) => {
30548
- release = resolve;
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((resolve) => {
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
- resolve({
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
- resolve({
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((resolve, reject) => {
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 sep = process.platform === "win32" ? ";" : ":";
34337
+ const sep2 = process.platform === "win32" ? ";" : ":";
34071
34338
  const current = env.PATH ?? env.Path ?? "";
34072
- const parts = current.split(sep).filter((p3) => p3.length > 0);
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}${sep}${current}`;
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 sep = platform === "win32" ? ";" : ":";
34414
- const dirs = pathEnv.split(sep).filter((d) => d.length > 0);
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 existsSync38, readFileSync as readFileSync31, readlinkSync, statSync as statSync6 } from "node:fs";
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 (existsSync38(candidate)) {
36059
+ if (existsSync42(candidate)) {
34610
36060
  try {
34611
- const pkg = JSON.parse(readFileSync31(candidate, "utf8"));
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(readFileSync31(pkgPath, "utf8"));
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 (!existsSync38(shimPath)) {
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 = statSync6(shimPath);
36108
+ const st = statSync7(shimPath);
34659
36109
  if (isWin) {
34660
- const content = readFileSync31(shimPath, "utf8");
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 (!existsSync38(bundle)) {
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 = statSync6(bundle);
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 ? "..." : "Type a prompt or /skill <name>"
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((resolve, reject) => {
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) : resolve(stdout)
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((resolve, reject) => {
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
- resolve();
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((resolve) => {
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
- resolve(ok);
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((resolve) => {
40396
+ const onAskUser = setPicker2 ? (req) => new Promise((resolve3) => {
39114
40397
  const choices = req.choices ?? [];
39115
40398
  if (choices.length < 2) {
39116
- resolve(null);
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
- resolve(value);
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((resolve) => {
41016
+ const onAskUserCouncil = setPicker2 ? (req) => new Promise((resolve3) => {
39734
41017
  const choices = req.choices ?? [];
39735
41018
  if (choices.length < 2) {
39736
- resolve(null);
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
- resolve(value);
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((resolve) => {
41169
+ onClarification: setPicker2 ? (req) => new Promise((resolve3) => {
39887
41170
  const choices = req.choices ?? [];
39888
41171
  if (choices.length < 2) {
39889
- resolve(null);
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
- resolve(value);
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 <name> [input] \u2014 invoke a skill (autocomplete with /skill <TAB>)
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: "skill",
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 /skill <TAB> for autocomplete.`,
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((resolve) => {
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
- resolve({
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
- resolve({
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((resolve) => {
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
- resolve({
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
- resolve({
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(value);
43984
+ await dispatchZelariPrompt(promptText);
42660
43985
  return;
42661
43986
  }
42662
43987
  if (mode === "council") {
42663
43988
  setInput("");
42664
- await dispatchCouncilPrompt(value);
43989
+ await dispatchCouncilPrompt(promptText);
42665
43990
  return;
42666
43991
  }
42667
- await dispatchPrompt(value);
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
- await dispatchPrompt(result.expandedSkill.prompt, {
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 existsSync37 } from "node:fs";
45006
+ import { existsSync as existsSync38 } from "node:fs";
43673
45007
  function shouldRunWizard(input) {
43674
- const exists = input.exists ?? existsSync37;
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 readFileSync30 } from "node:fs";
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 = readFileSync30(next, "utf-8");
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/desktopConfig.ts
45026
- init_keyStore();
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(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/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((resolve) => setTimeout(resolve, 3e3));
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((resolve) => setImmediate(resolve));
47521
+ await new Promise((resolve3) => setImmediate(resolve3));
45860
47522
  process.exit(code);
45861
47523
  }).catch(async (err) => {
45862
47524
  console.error(