zelari-code 1.36.0 → 1.38.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.
@@ -794,16 +794,19 @@ __export(chatgptOAuth_exports, {
794
794
  CHATGPT_DEVICE_CODE_URL: () => CHATGPT_DEVICE_CODE_URL,
795
795
  CHATGPT_DEVICE_REDIRECT_URI: () => CHATGPT_DEVICE_REDIRECT_URI,
796
796
  CHATGPT_DEVICE_TOKEN_URL: () => CHATGPT_DEVICE_TOKEN_URL,
797
+ CHATGPT_DEVICE_VERIFY_URL: () => CHATGPT_DEVICE_VERIFY_URL,
797
798
  CHATGPT_SCOPE: () => CHATGPT_SCOPE,
798
799
  CHATGPT_TOKEN_URL: () => CHATGPT_TOKEN_URL,
799
800
  ChatgptOAuthError: () => ChatgptOAuthError,
800
801
  DEFAULT_CHATGPT_CLIENT_ID: () => DEFAULT_CHATGPT_CLIENT_ID,
801
802
  decodeJwtClaims: () => decodeJwtClaims,
802
803
  extractChatgptAccountId: () => extractChatgptAccountId,
804
+ pollChatgptDeviceAuth: () => pollChatgptDeviceAuth,
803
805
  refreshChatgptToken: () => refreshChatgptToken,
804
806
  runChatgptBrowserFlow: () => runChatgptBrowserFlow,
805
807
  runChatgptDeviceFlow: () => runChatgptDeviceFlow,
806
- runChatgptOAuthFlow: () => runChatgptOAuthFlow
808
+ runChatgptOAuthFlow: () => runChatgptOAuthFlow,
809
+ startChatgptDeviceAuth: () => startChatgptDeviceAuth
807
810
  });
808
811
  function decodeJwtClaims(token) {
809
812
  const parts = token.split(".");
@@ -845,6 +848,29 @@ function parseTokenPayload(obj, fallbackRefresh) {
845
848
  }
846
849
  return result;
847
850
  }
851
+ function errorSnippet(body) {
852
+ const err = body.error;
853
+ if (typeof err === "string" && err.trim()) return err.trim().slice(0, 160);
854
+ if (err && typeof err === "object") {
855
+ const msg2 = err.message;
856
+ if (typeof msg2 === "string" && msg2.trim()) return msg2.trim().slice(0, 160);
857
+ }
858
+ const msg = body.message;
859
+ if (typeof msg === "string" && msg.trim()) return msg.trim().slice(0, 160);
860
+ return "";
861
+ }
862
+ function formatHttpError(prefix, status, body) {
863
+ const detail = errorSnippet(body);
864
+ return detail ? `${prefix} HTTP ${status}: ${detail}` : `${prefix} HTTP ${status}`;
865
+ }
866
+ async function readJsonBody(response) {
867
+ try {
868
+ const parsed = await response.json();
869
+ if (parsed && typeof parsed === "object") return parsed;
870
+ } catch {
871
+ }
872
+ return {};
873
+ }
848
874
  async function postForm(url2, data, fetchImpl) {
849
875
  let response;
850
876
  try {
@@ -862,13 +888,34 @@ async function postForm(url2, data, fetchImpl) {
862
888
  "network_error"
863
889
  );
864
890
  }
865
- let body = {};
891
+ return { status: response.status, body: await readJsonBody(response) };
892
+ }
893
+ async function postJson(url2, data, fetchImpl) {
894
+ let response;
866
895
  try {
867
- const parsed = await response.json();
868
- if (parsed && typeof parsed === "object") body = parsed;
869
- } catch {
896
+ response = await fetchImpl(url2, {
897
+ method: "POST",
898
+ headers: {
899
+ "Content-Type": "application/json",
900
+ Accept: "application/json"
901
+ },
902
+ body: JSON.stringify(data)
903
+ });
904
+ } catch (err) {
905
+ throw new ChatgptOAuthError(
906
+ `ChatGPT OAuth network error: ${err instanceof Error ? err.message : String(err)}`,
907
+ "network_error"
908
+ );
870
909
  }
871
- return { status: response.status, body };
910
+ return { status: response.status, body: await readJsonBody(response) };
911
+ }
912
+ function parsePollInterval(raw, fallback = 5) {
913
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return raw;
914
+ if (typeof raw === "string") {
915
+ const n = Number.parseInt(raw, 10);
916
+ if (Number.isFinite(n) && n > 0) return n;
917
+ }
918
+ return fallback;
872
919
  }
873
920
  async function refreshChatgptToken(options) {
874
921
  if (!options.refreshToken.trim()) {
@@ -887,77 +934,82 @@ async function refreshChatgptToken(options) {
887
934
  );
888
935
  if (status >= 400) {
889
936
  const code = status === 400 || status === 401 ? "invalid_grant" : `http_${status}`;
890
- throw new ChatgptOAuthError(
891
- `ChatGPT token refresh HTTP ${status}: ${String(body.error ?? "").slice(0, 160)}`,
892
- code
893
- );
937
+ throw new ChatgptOAuthError(formatHttpError("ChatGPT token refresh", status, body), code);
894
938
  }
895
939
  return parseTokenPayload(body, options.refreshToken);
896
940
  }
897
- async function runChatgptDeviceFlow(options) {
941
+ async function startChatgptDeviceAuth(options = {}) {
898
942
  const clientId2 = options.clientId || process.env.CHATGPT_OAUTH_CLIENT_ID || DEFAULT_CHATGPT_CLIENT_ID;
899
943
  const fetchImpl = options.fetchImpl ?? fetch;
900
- const sleep = options.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
901
- const { verifier, challenge } = generatePkcePair();
902
- const start = await postForm(
903
- CHATGPT_DEVICE_CODE_URL,
904
- {
905
- client_id: clientId2,
906
- scope: CHATGPT_SCOPE,
907
- code_challenge: challenge,
908
- code_challenge_method: "S256"
909
- },
910
- fetchImpl
911
- );
944
+ const start = await postJson(CHATGPT_DEVICE_CODE_URL, { client_id: clientId2 }, fetchImpl);
912
945
  if (start.status >= 400) {
913
946
  throw new ChatgptOAuthError(
914
- `ChatGPT device-code HTTP ${start.status}`,
947
+ formatHttpError("ChatGPT device-code", start.status, start.body),
915
948
  `device_http_${start.status}`
916
949
  );
917
950
  }
918
- const deviceCode = start.body.device_code;
919
- const userCode = start.body.user_code;
920
- const verificationUri = typeof start.body.verification_uri === "string" && start.body.verification_uri || typeof start.body.verification_uri_complete === "string" && start.body.verification_uri_complete;
921
- if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string") {
951
+ const deviceAuthId = typeof start.body.device_auth_id === "string" && start.body.device_auth_id || typeof start.body.device_code === "string" && start.body.device_code || "";
952
+ const userCode = typeof start.body.user_code === "string" && start.body.user_code || typeof start.body.usercode === "string" && start.body.usercode || "";
953
+ const verificationUri = typeof start.body.verification_uri === "string" && start.body.verification_uri || typeof start.body.verification_url === "string" && start.body.verification_url || `${CHATGPT_DEVICE_VERIFY_URL}?user_code=${encodeURIComponent(userCode)}`;
954
+ if (!deviceAuthId || !userCode) {
922
955
  throw new ChatgptOAuthError("Device-code response missing fields", "no_device_code");
923
956
  }
924
- const info = {
957
+ return {
958
+ deviceAuthId,
925
959
  userCode,
926
960
  verificationUri,
961
+ interval: parsePollInterval(start.body.interval, 5),
927
962
  ...typeof start.body.verification_uri_complete === "string" ? { verificationUriComplete: start.body.verification_uri_complete } : {}
928
963
  };
929
- if (options.onUserCode) await options.onUserCode(info);
930
- try {
931
- await (options.openBrowserImpl ?? openBrowser)(
932
- info.verificationUriComplete ?? info.verificationUri
933
- );
934
- } catch {
935
- }
964
+ }
965
+ async function pollChatgptDeviceAuth(options) {
966
+ const clientId2 = options.clientId || process.env.CHATGPT_OAUTH_CLIENT_ID || DEFAULT_CHATGPT_CLIENT_ID;
967
+ const fetchImpl = options.fetchImpl ?? fetch;
968
+ const sleep = options.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
936
969
  const timeoutMs = options.timeoutMs ?? 3e5;
937
970
  const deadline = Date.now() + timeoutMs;
938
- let interval = 5;
971
+ let interval = options.interval ?? 5;
939
972
  let authorizationCode;
973
+ let codeVerifier;
940
974
  while (Date.now() < deadline) {
941
- const poll = await postForm(
975
+ const poll = await postJson(
942
976
  CHATGPT_DEVICE_TOKEN_URL,
943
- { client_id: clientId2, device_code: deviceCode },
977
+ { device_auth_id: options.deviceAuthId, user_code: options.userCode },
944
978
  fetchImpl
945
979
  );
946
980
  const authCode = poll.body.authorization_code;
947
981
  if (typeof authCode === "string" && authCode.length > 0) {
948
982
  authorizationCode = authCode;
983
+ if (typeof poll.body.code_verifier === "string" && poll.body.code_verifier) {
984
+ codeVerifier = poll.body.code_verifier;
985
+ }
949
986
  break;
950
987
  }
988
+ if (poll.status === 403 || poll.status === 404) {
989
+ await sleep(interval * 1e3);
990
+ continue;
991
+ }
951
992
  const err = typeof poll.body.error === "string" ? poll.body.error : "";
952
993
  if (err === "slow_down") interval += 5;
953
994
  else if (err && err !== "authorization_pending") {
954
995
  throw new ChatgptOAuthError(`Device authorization failed: ${err}`, err);
996
+ } else if (poll.status >= 400) {
997
+ throw new ChatgptOAuthError(
998
+ formatHttpError("ChatGPT device poll", poll.status, poll.body),
999
+ `poll_http_${poll.status}`
1000
+ );
955
1001
  }
956
1002
  await sleep(interval * 1e3);
957
1003
  }
958
1004
  if (!authorizationCode) {
959
1005
  throw new ChatgptOAuthError("Timed out waiting for ChatGPT device authorization", "timeout");
960
1006
  }
1007
+ if (!codeVerifier) {
1008
+ throw new ChatgptOAuthError(
1009
+ "Device token response missing code_verifier (server-issued PKCE)",
1010
+ "no_code_verifier"
1011
+ );
1012
+ }
961
1013
  const token = await postForm(
962
1014
  CHATGPT_TOKEN_URL,
963
1015
  {
@@ -965,13 +1017,13 @@ async function runChatgptDeviceFlow(options) {
965
1017
  code: authorizationCode,
966
1018
  redirect_uri: CHATGPT_DEVICE_REDIRECT_URI,
967
1019
  client_id: clientId2,
968
- code_verifier: verifier
1020
+ code_verifier: codeVerifier
969
1021
  },
970
1022
  fetchImpl
971
1023
  );
972
1024
  if (token.status >= 400) {
973
1025
  throw new ChatgptOAuthError(
974
- `ChatGPT token exchange HTTP ${token.status}`,
1026
+ formatHttpError("ChatGPT token exchange", token.status, token.body),
975
1027
  `token_http_${token.status}`
976
1028
  );
977
1029
  }
@@ -1024,12 +1076,47 @@ async function runChatgptBrowserFlow(options) {
1024
1076
  );
1025
1077
  if (token.status >= 400) {
1026
1078
  throw new ChatgptOAuthError(
1027
- `ChatGPT token exchange HTTP ${token.status}`,
1079
+ formatHttpError("ChatGPT token exchange", token.status, token.body),
1028
1080
  `token_http_${token.status}`
1029
1081
  );
1030
1082
  }
1031
1083
  return parseTokenPayload(token.body);
1032
1084
  }
1085
+ async function runChatgptDeviceFlow(options = {}) {
1086
+ const session = await startChatgptDeviceAuth({
1087
+ clientId: options.clientId,
1088
+ fetchImpl: options.fetchImpl
1089
+ });
1090
+ const info = {
1091
+ userCode: session.userCode,
1092
+ verificationUri: session.verificationUri,
1093
+ ...session.verificationUriComplete ? { verificationUriComplete: session.verificationUriComplete } : {}
1094
+ };
1095
+ if (options.onUserCode) {
1096
+ await options.onUserCode(info);
1097
+ } else {
1098
+ process.stderr.write(
1099
+ `[chatgpt oauth] Open ${info.verificationUri} and enter:
1100
+ ${info.userCode}
1101
+ `
1102
+ );
1103
+ }
1104
+ try {
1105
+ await (options.openBrowserImpl ?? openBrowser)(
1106
+ info.verificationUriComplete ?? info.verificationUri
1107
+ );
1108
+ } catch {
1109
+ }
1110
+ return pollChatgptDeviceAuth({
1111
+ deviceAuthId: session.deviceAuthId,
1112
+ userCode: session.userCode,
1113
+ clientId: options.clientId,
1114
+ interval: session.interval,
1115
+ fetchImpl: options.fetchImpl,
1116
+ sleepImpl: options.sleepImpl,
1117
+ timeoutMs: options.timeoutMs
1118
+ });
1119
+ }
1033
1120
  async function runChatgptOAuthFlow(options = {}) {
1034
1121
  const flow = (process.env.CHATGPT_OAUTH_FLOW ?? "device").toLowerCase();
1035
1122
  if (flow === "browser") {
@@ -1037,7 +1124,7 @@ async function runChatgptOAuthFlow(options = {}) {
1037
1124
  }
1038
1125
  return runChatgptDeviceFlow(options);
1039
1126
  }
1040
- var DEFAULT_CHATGPT_CLIENT_ID, CHATGPT_AUTHORIZE_URL, CHATGPT_TOKEN_URL, CHATGPT_DEVICE_CODE_URL, CHATGPT_DEVICE_TOKEN_URL, CHATGPT_DEVICE_REDIRECT_URI, CHATGPT_SCOPE, CHATGPT_AUTH_CLAIMS, ChatgptOAuthError;
1127
+ var DEFAULT_CHATGPT_CLIENT_ID, CHATGPT_AUTHORIZE_URL, CHATGPT_TOKEN_URL, CHATGPT_DEVICE_CODE_URL, CHATGPT_DEVICE_TOKEN_URL, CHATGPT_DEVICE_REDIRECT_URI, CHATGPT_DEVICE_VERIFY_URL, CHATGPT_SCOPE, CHATGPT_AUTH_CLAIMS, ChatgptOAuthError;
1041
1128
  var init_chatgptOAuth = __esm({
1042
1129
  "src/cli/chatgptOAuth.ts"() {
1043
1130
  "use strict";
@@ -1049,6 +1136,7 @@ var init_chatgptOAuth = __esm({
1049
1136
  CHATGPT_DEVICE_CODE_URL = "https://auth.openai.com/api/accounts/deviceauth/usercode";
1050
1137
  CHATGPT_DEVICE_TOKEN_URL = "https://auth.openai.com/api/accounts/deviceauth/token";
1051
1138
  CHATGPT_DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
1139
+ CHATGPT_DEVICE_VERIFY_URL = "https://auth.openai.com/codex/device";
1052
1140
  CHATGPT_SCOPE = "openid profile email offline_access";
1053
1141
  CHATGPT_AUTH_CLAIMS = "https://api.openai.com/auth";
1054
1142
  ChatgptOAuthError = class extends Error {
@@ -1152,7 +1240,7 @@ async function startAnthropicOAuth(options = {}) {
1152
1240
  }
1153
1241
  return { authorizeUrl, state: state3 };
1154
1242
  }
1155
- async function postJson(url2, payload, fetchImpl) {
1243
+ async function postJson2(url2, payload, fetchImpl) {
1156
1244
  let response;
1157
1245
  try {
1158
1246
  response = await fetchImpl(url2, {
@@ -1212,7 +1300,7 @@ async function completeAnthropicOAuth(options) {
1212
1300
  redirect_uri: ANTHROPIC_REDIRECT_URI,
1213
1301
  state: parsed.state ?? pending.state
1214
1302
  };
1215
- const { status, body } = await postJson(ANTHROPIC_TOKEN_URL, payload, fetchImpl);
1303
+ const { status, body } = await postJson2(ANTHROPIC_TOKEN_URL, payload, fetchImpl);
1216
1304
  if (status >= 400) {
1217
1305
  const code = status === 400 || status === 401 ? "invalid_grant" : `http_${status}`;
1218
1306
  throw new AnthropicOAuthError(
@@ -1227,7 +1315,7 @@ async function refreshAnthropicToken(options) {
1227
1315
  throw new AnthropicOAuthError("Missing refreshToken", "no_refresh_token");
1228
1316
  }
1229
1317
  const fetchImpl = options.fetchImpl ?? fetch;
1230
- const { status, body } = await postJson(
1318
+ const { status, body } = await postJson2(
1231
1319
  ANTHROPIC_TOKEN_URL,
1232
1320
  {
1233
1321
  grant_type: "refresh_token",
@@ -1532,6 +1620,112 @@ var init_keyStore = __esm({
1532
1620
  }
1533
1621
  });
1534
1622
 
1623
+ // src/cli/thinking.ts
1624
+ function thinkingCapabilityFor(id) {
1625
+ return PROVIDER_THINKING_CAPABILITY[id] ?? {};
1626
+ }
1627
+ function stringifyThinkingSpec(spec) {
1628
+ if (spec === "auto") return "auto";
1629
+ if (spec.kind === "off") return "off";
1630
+ if (spec.kind === "effort") return spec.effort;
1631
+ return `budget:${spec.budgetTokens}`;
1632
+ }
1633
+ function parseThinkingSpec(raw) {
1634
+ const s = (raw ?? "").trim().toLowerCase();
1635
+ if (!s || s === "auto") return "auto";
1636
+ if (s === "off") return { kind: "off" };
1637
+ if (s === "low" || s === "medium" || s === "high") return { kind: "effort", effort: s };
1638
+ const m = /^budget:(\d+)$/.exec(s);
1639
+ if (m) {
1640
+ const n = Number.parseInt(m[1], 10);
1641
+ if (Number.isFinite(n) && n > 0) return { kind: "budget", budgetTokens: n };
1642
+ }
1643
+ return "auto";
1644
+ }
1645
+ function isValidThinkingInput(raw) {
1646
+ const s = raw.trim().toLowerCase();
1647
+ if (s === "auto" || s === "off" || s === "low" || s === "medium" || s === "high") return true;
1648
+ return /^budget:\d+$/.test(s) && Number.parseInt(s.slice(7), 10) > 0;
1649
+ }
1650
+ function degrade(note) {
1651
+ return { patch: {}, degraded: true, note };
1652
+ }
1653
+ function translateOpenAiCompatibleThinking(providerId, spec) {
1654
+ if (spec === "auto") return { patch: {}, degraded: false };
1655
+ const cap3 = thinkingCapabilityFor(providerId);
1656
+ switch (spec.kind) {
1657
+ case "off":
1658
+ if (providerId === "deepseek" || providerId === "glm") {
1659
+ return { patch: { thinking: { type: "disabled" } }, degraded: false };
1660
+ }
1661
+ if (cap3.effort) return { patch: { reasoning_effort: "low" }, degraded: false };
1662
+ return degrade(`thinking 'off' is not supported for provider "${providerId}"`);
1663
+ case "effort":
1664
+ if (!cap3.effort) {
1665
+ return degrade(`thinking 'effort' is not supported for provider "${providerId}"`);
1666
+ }
1667
+ if (providerId === "deepseek") {
1668
+ return {
1669
+ patch: {
1670
+ thinking: { type: "enabled" },
1671
+ reasoning_effort: spec.effort === "high" ? "max" : "high"
1672
+ },
1673
+ degraded: false
1674
+ };
1675
+ }
1676
+ return { patch: { reasoning_effort: spec.effort }, degraded: false };
1677
+ case "budget":
1678
+ if (!cap3.budget) {
1679
+ return degrade(`thinking 'budget' is not supported for provider "${providerId}"`);
1680
+ }
1681
+ return {
1682
+ patch: { thinking: { type: "enabled", budget_tokens: spec.budgetTokens } },
1683
+ degraded: false
1684
+ };
1685
+ }
1686
+ }
1687
+ function translateResponsesThinking(spec) {
1688
+ if (spec === "auto") return { patch: {}, degraded: false };
1689
+ switch (spec.kind) {
1690
+ case "off":
1691
+ return { patch: { reasoning: { effort: "minimal" } }, degraded: false };
1692
+ case "effort":
1693
+ return { patch: { reasoning: { effort: spec.effort } }, degraded: false };
1694
+ case "budget":
1695
+ return degrade('thinking "budget" is not supported for chatgpt \u2014 use low/medium/high');
1696
+ }
1697
+ }
1698
+ function translateAnthropicThinking(spec) {
1699
+ if (spec === "auto") return { patch: {}, degraded: false };
1700
+ switch (spec.kind) {
1701
+ case "off":
1702
+ return { patch: { thinking: { type: "disabled" } }, degraded: false };
1703
+ case "budget":
1704
+ return {
1705
+ patch: { thinking: { type: "enabled", budget_tokens: spec.budgetTokens } },
1706
+ degraded: false
1707
+ };
1708
+ case "effort":
1709
+ return degrade('thinking "effort" is not supported for anthropic \u2014 use budget:N');
1710
+ }
1711
+ }
1712
+ var PROVIDER_THINKING_CAPABILITY;
1713
+ var init_thinking = __esm({
1714
+ "src/cli/thinking.ts"() {
1715
+ "use strict";
1716
+ PROVIDER_THINKING_CAPABILITY = {
1717
+ "openai-compatible": { effort: true },
1718
+ "grok": { effort: true },
1719
+ "chatgpt": { effort: true },
1720
+ "anthropic": { budget: true },
1721
+ "glm": { budget: true },
1722
+ "deepseek": { effort: true },
1723
+ "minimax": { effort: true },
1724
+ "custom": { effort: true }
1725
+ };
1726
+ }
1727
+ });
1728
+
1535
1729
  // src/cli/providerConfig.ts
1536
1730
  var providerConfig_exports = {};
1537
1731
  __export(providerConfig_exports, {
@@ -1542,10 +1736,12 @@ __export(providerConfig_exports, {
1542
1736
  getModelForProvider: () => getModelForProvider,
1543
1737
  getProviderConfig: () => getProviderConfig,
1544
1738
  getProviderConfigPath: () => getProviderConfigPath,
1739
+ getThinkingForProvider: () => getThinkingForProvider,
1545
1740
  loadProviderConfig: () => loadProviderConfig,
1546
1741
  setActiveProviderId: () => setActiveProviderId,
1547
1742
  setCustomEndpoint: () => setCustomEndpoint,
1548
- setModelForProvider: () => setModelForProvider
1743
+ setModelForProvider: () => setModelForProvider,
1744
+ setThinkingForProvider: () => setThinkingForProvider
1549
1745
  });
1550
1746
  import { promises as fs2, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
1551
1747
  import path4 from "node:path";
@@ -1566,6 +1762,7 @@ function getProviderConfig() {
1566
1762
  stored = {
1567
1763
  activeProviderId: parsed.activeProviderId,
1568
1764
  modelByProvider: { ...DEFAULTS.modelByProvider, ...parsed.modelByProvider },
1765
+ thinkingByProvider: { ...DEFAULTS.thinkingByProvider, ...parsed.thinkingByProvider },
1569
1766
  customEndpoints: mergeCustomEndpoints(parsed.customEndpoints)
1570
1767
  };
1571
1768
  }
@@ -1575,6 +1772,7 @@ function getProviderConfig() {
1575
1772
  const base = stored ?? {
1576
1773
  ...DEFAULTS,
1577
1774
  modelByProvider: { ...DEFAULTS.modelByProvider },
1775
+ thinkingByProvider: { ...DEFAULTS.thinkingByProvider },
1578
1776
  customEndpoints: { ...DEFAULTS.customEndpoints }
1579
1777
  };
1580
1778
  if (envActive && PROVIDERS.some((p3) => p3.id === envActive)) {
@@ -1662,6 +1860,19 @@ function getModelForProvider(id) {
1662
1860
  const config2 = getProviderConfig();
1663
1861
  return config2.modelByProvider[id] ?? DEFAULTS.modelByProvider[id] ?? "";
1664
1862
  }
1863
+ function getThinkingForProvider(id) {
1864
+ const config2 = getProviderConfig();
1865
+ return parseThinkingSpec(config2.thinkingByProvider[id]);
1866
+ }
1867
+ function setThinkingForProvider(id, spec) {
1868
+ const found = PROVIDERS.find((p3) => p3.id === id);
1869
+ if (!found) {
1870
+ throw new Error(`Unknown provider id: "${id}". Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`);
1871
+ }
1872
+ const config2 = getProviderConfig();
1873
+ config2.thinkingByProvider[id] = stringifyThinkingSpec(spec);
1874
+ writeProviderConfig(config2);
1875
+ }
1665
1876
  function getActiveProvider() {
1666
1877
  const config2 = getProviderConfig();
1667
1878
  const spec = PROVIDERS.find((p3) => p3.id === config2.activeProviderId);
@@ -1681,6 +1892,7 @@ async function loadProviderConfig() {
1681
1892
  return {
1682
1893
  activeProviderId: parsed.activeProviderId,
1683
1894
  modelByProvider: { ...DEFAULTS.modelByProvider, ...parsed.modelByProvider },
1895
+ thinkingByProvider: { ...DEFAULTS.thinkingByProvider, ...parsed.thinkingByProvider },
1684
1896
  customEndpoints: mergeCustomEndpoints(parsed.customEndpoints)
1685
1897
  };
1686
1898
  }
@@ -1689,6 +1901,7 @@ async function loadProviderConfig() {
1689
1901
  return {
1690
1902
  ...DEFAULTS,
1691
1903
  modelByProvider: { ...DEFAULTS.modelByProvider },
1904
+ thinkingByProvider: { ...DEFAULTS.thinkingByProvider },
1692
1905
  customEndpoints: { ...DEFAULTS.customEndpoints }
1693
1906
  };
1694
1907
  }
@@ -1697,6 +1910,7 @@ var init_providerConfig = __esm({
1697
1910
  "src/cli/providerConfig.ts"() {
1698
1911
  "use strict";
1699
1912
  init_keyStore();
1913
+ init_thinking();
1700
1914
  DEFAULTS = {
1701
1915
  activeProviderId: "openai-compatible",
1702
1916
  modelByProvider: {
@@ -1710,6 +1924,16 @@ var init_providerConfig = __esm({
1710
1924
  "anthropic": "claude-sonnet-4-5",
1711
1925
  "custom": ""
1712
1926
  },
1927
+ thinkingByProvider: {
1928
+ "openai-compatible": "auto",
1929
+ "minimax": "auto",
1930
+ "glm": "auto",
1931
+ "grok": "auto",
1932
+ "deepseek": "auto",
1933
+ "chatgpt": "auto",
1934
+ "anthropic": "auto",
1935
+ "custom": "auto"
1936
+ },
1713
1937
  customEndpoints: {}
1714
1938
  };
1715
1939
  }
@@ -28027,10 +28251,18 @@ function openaiCompatibleProvider(config2) {
28027
28251
  // the harness will fall back to the ~4-char/token approximation.
28028
28252
  stream_options: { include_usage: true }
28029
28253
  };
28030
- if (config2.providerId === "deepseek") {
28254
+ const thinkingSpec = config2.thinking ?? "auto";
28255
+ if (config2.providerId === "deepseek" && thinkingSpec === "auto") {
28031
28256
  const thinking = resolveDeepSeekThinking();
28032
28257
  if (thinking.thinking) body.thinking = { type: thinking.thinking };
28033
28258
  if (thinking.reasoningEffort) body.reasoning_effort = thinking.reasoningEffort;
28259
+ } else if (thinkingSpec !== "auto") {
28260
+ const t = translateOpenAiCompatibleThinking(config2.providerId, thinkingSpec);
28261
+ if (t.degraded) {
28262
+ console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
28263
+ } else {
28264
+ Object.assign(body, t.patch);
28265
+ }
28034
28266
  }
28035
28267
  if (params.tools && params.tools.length > 0) {
28036
28268
  const orderedTools = [...params.tools].sort(
@@ -28281,6 +28513,7 @@ async function providerFromEnv() {
28281
28513
  baseUrl: resolveBaseUrl(providerId),
28282
28514
  model: getModelForProvider(providerId),
28283
28515
  providerId,
28516
+ thinking: getThinkingForProvider(providerId),
28284
28517
  ...extraFromStored(providerId)
28285
28518
  };
28286
28519
  }
@@ -28292,6 +28525,7 @@ async function providerConfigFor(providerId) {
28292
28525
  baseUrl: resolveBaseUrl(providerId),
28293
28526
  model: getModelForProvider(providerId),
28294
28527
  providerId,
28528
+ thinking: getThinkingForProvider(providerId),
28295
28529
  ...extraFromStored(providerId)
28296
28530
  };
28297
28531
  }
@@ -28301,6 +28535,7 @@ var init_openai_compatible = __esm({
28301
28535
  "use strict";
28302
28536
  init_keyStore();
28303
28537
  init_providerConfig();
28538
+ init_thinking();
28304
28539
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
28305
28540
  MAX_RETRIES = (() => {
28306
28541
  const raw = process.env.ZELARI_PROVIDER_MAX_RETRIES;
@@ -28554,6 +28789,12 @@ function anthropicMessagesProvider(config2) {
28554
28789
  input_schema: t.parameters
28555
28790
  }));
28556
28791
  }
28792
+ const thinkingSpec = config2.thinking ?? "auto";
28793
+ if (thinkingSpec !== "auto") {
28794
+ const t = translateAnthropicThinking(thinkingSpec);
28795
+ if (t.degraded) console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
28796
+ else Object.assign(body, t.patch);
28797
+ }
28557
28798
  const base = config2.baseUrl.replace(/\/$/, "").replace(/\/v1$/, "");
28558
28799
  const url2 = `${base}/v1/messages`;
28559
28800
  let response;
@@ -28692,6 +28933,7 @@ var init_anthropic = __esm({
28692
28933
  "src/cli/provider/anthropic.ts"() {
28693
28934
  "use strict";
28694
28935
  init_chatStats();
28936
+ init_thinking();
28695
28937
  ANTHROPIC_VERSION = "2023-06-01";
28696
28938
  ANTHROPIC_BETA = "oauth-2025-04-20";
28697
28939
  ANTHROPIC_BETA_EXTENDED_CACHE_TTL = "extended-cache-ttl-2025-04-11";
@@ -28759,6 +29001,12 @@ function chatgptResponsesProvider(config2) {
28759
29001
  parameters: t.parameters
28760
29002
  }));
28761
29003
  }
29004
+ const thinkingSpec = config2.thinking ?? "auto";
29005
+ if (thinkingSpec !== "auto") {
29006
+ const t = translateResponsesThinking(thinkingSpec);
29007
+ if (t.degraded) console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
29008
+ else Object.assign(body, t.patch);
29009
+ }
28762
29010
  const base = config2.baseUrl.replace(/\/$/, "");
28763
29011
  const url2 = `${base}/responses`;
28764
29012
  let response;
@@ -28873,6 +29121,7 @@ function chatgptResponsesProvider(config2) {
28873
29121
  var init_chatgpt = __esm({
28874
29122
  "src/cli/provider/chatgpt.ts"() {
28875
29123
  "use strict";
29124
+ init_thinking();
28876
29125
  }
28877
29126
  });
28878
29127
 
@@ -42545,6 +42794,8 @@ var init_oauthDesktop = __esm({
42545
42794
  var provider_exports = {};
42546
42795
  __export(provider_exports, {
42547
42796
  buildModelPickerItems: () => buildModelPickerItems,
42797
+ handleEffortSet: () => handleEffortSet,
42798
+ handleEffortShow: () => handleEffortShow,
42548
42799
  handleLoginKey: () => handleLoginKey,
42549
42800
  handleLoginOAuth: () => handleLoginOAuth,
42550
42801
  handleLoginOAuthGrok: () => handleLoginOAuthGrok,
@@ -42792,6 +43043,39 @@ function handleModelSet(ctx, model) {
42792
43043
  appendSystem(ctx.setMessages, `[model error] ${err instanceof Error ? err.message : String(err)}`);
42793
43044
  }
42794
43045
  }
43046
+ function handleEffortShow(ctx) {
43047
+ const id = ctx.activeProviderSpec.id;
43048
+ const cap3 = thinkingCapabilityFor(id);
43049
+ const current = stringifyThinkingSpec(getThinkingForProvider(id));
43050
+ const options = [
43051
+ "auto",
43052
+ "off",
43053
+ ...cap3.effort ? ["low", "medium", "high"] : [],
43054
+ ...cap3.budget ? ["budget:<tokens>"] : []
43055
+ ];
43056
+ appendSystem(
43057
+ ctx.setMessages,
43058
+ `[effort] ${ctx.activeProviderSpec.displayName}: ${current} \u2014 options: ${options.join(", ")}`
43059
+ );
43060
+ }
43061
+ function handleEffortSet(ctx, raw) {
43062
+ const id = ctx.activeProviderSpec.id;
43063
+ if (!isValidThinkingInput(raw)) {
43064
+ appendSystem(
43065
+ ctx.setMessages,
43066
+ `[effort] invalid spec "${raw}" \u2014 use auto | off | low | medium | high | budget:<tokens>`
43067
+ );
43068
+ return;
43069
+ }
43070
+ const spec = parseThinkingSpec(raw);
43071
+ try {
43072
+ setThinkingForProvider(id, spec);
43073
+ ctx.setProviderConfig(getProviderConfig());
43074
+ appendSystem(ctx.setMessages, `[effort] ${ctx.activeProviderSpec.displayName} \u2192 ${stringifyThinkingSpec(spec)}`);
43075
+ } catch (err) {
43076
+ appendSystem(ctx.setMessages, `[effort error] ${err instanceof Error ? err.message : String(err)}`);
43077
+ }
43078
+ }
42795
43079
  function buildModelPickerItems(models, activeModel, defaultModel) {
42796
43080
  const items = models.map((m) => ({
42797
43081
  value: m.id,
@@ -42895,6 +43179,7 @@ var init_provider2 = __esm({
42895
43179
  init_refreshRegistry();
42896
43180
  init_keyValidator();
42897
43181
  init_providerConfig();
43182
+ init_thinking();
42898
43183
  init_modelDiscovery();
42899
43184
  init_messageHelpers();
42900
43185
  init_duration();
@@ -43238,6 +43523,7 @@ function parseSetConfigFlags(argv) {
43238
43523
  let provider;
43239
43524
  let model;
43240
43525
  let endpoint;
43526
+ let thinking;
43241
43527
  let endpointClear = false;
43242
43528
  for (let i = 0; i < argv.length; i++) {
43243
43529
  const arg = argv[i];
@@ -43250,14 +43536,17 @@ function parseSetConfigFlags(argv) {
43250
43536
  } else if (arg === "--endpoint") {
43251
43537
  endpoint = argv[i + 1];
43252
43538
  i++;
43539
+ } else if (arg === "--thinking") {
43540
+ thinking = argv[i + 1];
43541
+ i++;
43253
43542
  } else if (arg === "--endpoint-clear") {
43254
43543
  endpointClear = true;
43255
43544
  }
43256
43545
  }
43257
- if (!provider && !model && !endpoint && !endpointClear) {
43546
+ if (!provider && !model && !endpoint && !endpointClear && !thinking) {
43258
43547
  return {
43259
43548
  request: null,
43260
- error: "--set-config requires --provider, --model, --endpoint, and/or --endpoint-clear"
43549
+ error: "--set-config requires --provider, --model, --endpoint, --thinking, and/or --endpoint-clear"
43261
43550
  };
43262
43551
  }
43263
43552
  if (provider !== void 0 && provider.trim().length === 0) {
@@ -43272,12 +43561,16 @@ function parseSetConfigFlags(argv) {
43272
43561
  if (endpoint && endpointClear) {
43273
43562
  return { request: null, error: "--endpoint and --endpoint-clear conflict" };
43274
43563
  }
43564
+ if (thinking !== void 0 && !isValidThinkingInput(thinking)) {
43565
+ return { request: null, error: `invalid --thinking value '${thinking}'` };
43566
+ }
43275
43567
  return {
43276
43568
  request: {
43277
43569
  provider: provider?.trim(),
43278
43570
  model: model?.trim(),
43279
43571
  endpoint: endpoint?.trim(),
43280
- endpointClear: endpointClear || void 0
43572
+ endpointClear: endpointClear || void 0,
43573
+ thinking: thinking?.trim().toLowerCase()
43281
43574
  }
43282
43575
  };
43283
43576
  }
@@ -43352,7 +43645,9 @@ function buildDesktopConfigSnapshot() {
43352
43645
  authKind: !hasKey ? "none" : oauth ? "oauth" : "api_key",
43353
43646
  expiresAt: stored?.expiresAt ?? null,
43354
43647
  hasRefreshToken: Boolean(stored?.refreshToken),
43355
- oauthSupported: isOAuthProvider(p3.id)
43648
+ oauthSupported: isOAuthProvider(p3.id),
43649
+ thinking: config2.thinkingByProvider[p3.id] ?? "auto",
43650
+ thinkingCapability: thinkingCapabilityFor(p3.id)
43356
43651
  };
43357
43652
  });
43358
43653
  return {
@@ -43394,6 +43689,9 @@ function applySetConfig(req) {
43394
43689
  if (req.model) {
43395
43690
  setModelForProvider(targetProvider, req.model);
43396
43691
  }
43692
+ if (req.thinking) {
43693
+ setThinkingForProvider(targetProvider, parseThinkingSpec(req.thinking));
43694
+ }
43397
43695
  const after = getProviderConfig();
43398
43696
  const ep = getCustomEndpoint(after.activeProviderId);
43399
43697
  return {
@@ -43470,6 +43768,7 @@ var init_desktopConfig = __esm({
43470
43768
  init_providerConfig();
43471
43769
  init_modelDiscovery();
43472
43770
  init_updater();
43771
+ init_thinking();
43473
43772
  DISCOVERABLE = [
43474
43773
  "grok",
43475
43774
  "glm",
@@ -50629,6 +50928,13 @@ ${formatSkillList(availableSkills)}`
50629
50928
  }
50630
50929
  return { handled: true, kind: "provider_set", provider: subcommand };
50631
50930
  }
50931
+ case "effort": {
50932
+ const spec = args[0];
50933
+ if (!spec || spec === "show") {
50934
+ return { handled: true, kind: "effort_show" };
50935
+ }
50936
+ return { handled: true, kind: "effort_set", effortSpec: spec };
50937
+ }
50632
50938
  case "branch": {
50633
50939
  const name = args[0];
50634
50940
  if (!name) {
@@ -53122,6 +53428,16 @@ function useSlashDispatch(params) {
53122
53428
  setInput("");
53123
53429
  return;
53124
53430
  }
53431
+ if (result.kind === "effort_set" && result.effortSpec) {
53432
+ handleEffortSet(providerCtx, result.effortSpec);
53433
+ setInput("");
53434
+ return;
53435
+ }
53436
+ if (result.kind === "effort_show") {
53437
+ handleEffortShow(providerCtx);
53438
+ setInput("");
53439
+ return;
53440
+ }
53125
53441
  if (result.kind === "models_list") {
53126
53442
  handleModelsList(providerCtx);
53127
53443
  setInput("");
@@ -56186,7 +56502,7 @@ function pickRootComponent() {
56186
56502
  }
56187
56503
  if (argv.includes("--help") || argv.includes("-h")) {
56188
56504
  console.log(
56189
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n 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 --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (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"
56505
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (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"
56190
56506
  );
56191
56507
  process.exit(0);
56192
56508
  }