mu-agent 0.1.4 → 0.1.7

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 (119) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +7 -3
  3. package/dist/bundle/chunks/anthropic-messages-VMESUHQ7.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-K232MQFF.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/{chunk-FUXEF6JQ.js → chunk-4O2SCVYF.js} +1 -1
  7. package/dist/bundle/chunks/chunk-AYFRR3OD.js +2 -0
  8. package/dist/bundle/chunks/chunk-FU4XQUSL.js +11 -0
  9. package/dist/bundle/chunks/{chunk-S7SZN6Z3.js → chunk-GNYZK6XN.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-XOBBIK6P.js → chunk-HEMXN7VJ.js} +75 -67
  11. package/dist/bundle/chunks/chunk-MVPXOVOJ.js +2 -0
  12. package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
  13. package/dist/bundle/chunks/{chunk-WIUZI2CJ.js → chunk-V7IXECST.js} +2 -2
  14. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NR2TDCXU.js +2 -0
  15. package/dist/bundle/chunks/github-copilot.js +1 -1
  16. package/dist/bundle/chunks/google-generative-ai-C44KRRZ4.js +2 -0
  17. package/dist/bundle/chunks/google-vertex-25DUXPTJ.js +2 -0
  18. package/dist/bundle/chunks/meta.js +2 -0
  19. package/dist/bundle/chunks/mistral-conversations-6IHQOR4R.js +5 -0
  20. package/dist/bundle/chunks/{openai-codex-responses-QDSOTBBO.js → openai-codex-responses-3UAYOSSR.js} +2 -2
  21. package/dist/bundle/chunks/{openai-completions-KQPOPEVM.js → openai-completions-FIEQGWAH.js} +3 -3
  22. package/dist/bundle/chunks/openai-responses-OZJXLHFC.js +2 -0
  23. package/dist/bundle/chunks/{openrouter-images-N4XVVFSD.js → openrouter-images-JVEJN44Q.js} +1 -1
  24. package/dist/bundle/chunks/{pi-messages-GFKBWJHZ.js → pi-messages-TM5SOOIW.js} +2 -2
  25. package/dist/bundle/chunks/typesafe-system-one-VKYX4OGY.js +2 -0
  26. package/dist/bundle/chunks/{virtual-modules-MYFARBUK.js → virtual-modules-V7SXVVLP.js} +1 -1
  27. package/dist/bundle/cli-runtime.js +3 -0
  28. package/dist/bundle/cli.js +4 -2
  29. package/dist/bundle/index.js +1 -1
  30. package/dist/bundle/rpc-entry.js +1 -1
  31. package/dist/core/export-html/template.css +10 -0
  32. package/dist/core/export-html/template.js +56 -18
  33. package/dist/modes/interactive/theme/theme-controller.d.ts +6 -0
  34. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  35. package/dist/modes/interactive/theme/theme-controller.js +41 -3
  36. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  37. package/dist/modes/interactive/theme/theme-json.d.ts +1 -0
  38. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -1
  39. package/dist/modes/interactive/theme/theme-json.js +3 -1
  40. package/dist/modes/interactive/theme/theme-json.js.map +1 -1
  41. package/dist/modes/interactive/theme/theme-schema.json +7 -2
  42. package/dist/modes/interactive/theme/theme.d.ts +40 -7
  43. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  44. package/dist/modes/interactive/theme/theme.js +122 -214
  45. package/dist/modes/interactive/theme/theme.js.map +1 -1
  46. package/docs/cli-integration.md +106 -0
  47. package/docs/cli.md +268 -0
  48. package/docs/compaction.md +45 -26
  49. package/docs/configuration.md +45 -0
  50. package/docs/containerization.md +109 -82
  51. package/docs/custom-provider.md +109 -722
  52. package/docs/docs.json +139 -99
  53. package/docs/environment-variables.md +3 -5
  54. package/docs/extensions.md +137 -2953
  55. package/docs/how-pi-works.md +49 -0
  56. package/docs/images/interactive-mode.png +0 -0
  57. package/docs/index.md +24 -69
  58. package/docs/json.md +193 -65
  59. package/docs/keybindings.md +57 -102
  60. package/docs/llama-cpp.md +3 -3
  61. package/docs/message-types.md +261 -0
  62. package/docs/models.md +64 -546
  63. package/docs/packages.md +67 -166
  64. package/docs/prompt-templates.md +31 -68
  65. package/docs/providers.md +102 -232
  66. package/docs/quickstart.md +61 -106
  67. package/docs/rpc-commands.md +854 -0
  68. package/docs/rpc-extension-ui.md +200 -0
  69. package/docs/rpc.md +129 -1556
  70. package/docs/sdk.md +76 -1160
  71. package/docs/security.md +70 -32
  72. package/docs/session-format.md +25 -216
  73. package/docs/sessions.md +35 -141
  74. package/docs/settings.md +109 -387
  75. package/docs/shell-aliases.md +85 -5
  76. package/docs/skills.md +51 -190
  77. package/docs/slash-commands.md +60 -0
  78. package/docs/terminal-setup.md +105 -78
  79. package/docs/termux.md +74 -83
  80. package/docs/themes.md +71 -279
  81. package/docs/tmux.md +31 -39
  82. package/docs/tui.md +74 -910
  83. package/docs/usage.md +54 -272
  84. package/docs/windows.md +43 -17
  85. package/examples/README.md +13 -2
  86. package/examples/extensions/README.md +1 -0
  87. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  88. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  89. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  90. package/examples/extensions/debug-provider.ts +90 -0
  91. package/examples/extensions/gondolin/package-lock.json +2 -2
  92. package/examples/extensions/gondolin/package.json +1 -1
  93. package/examples/extensions/sandbox/package-lock.json +2 -2
  94. package/examples/extensions/sandbox/package.json +1 -1
  95. package/examples/extensions/with-deps/package-lock.json +2 -2
  96. package/examples/extensions/with-deps/package.json +1 -1
  97. package/examples/plugins/pi-example-plugin/src/session.ts +3 -2
  98. package/examples/rpc-client.ts +35 -0
  99. package/examples/rpc-extension-ui.ts +25 -5
  100. package/examples/sdk/README.md +1 -1
  101. package/judge/dist/auth.js +166 -28
  102. package/judge/dist/chrome.js +12 -1
  103. package/judge/dist/kyrn-judge.js +1424 -589
  104. package/judge/dist/setup.js +2051 -0
  105. package/judge/manifest.json +13 -13
  106. package/judge/skills/mu-browser/SKILL.md +1 -0
  107. package/kyrn/bin/kyrn-doctor +107 -18
  108. package/kyrn/bin/mu.mjs +389 -29
  109. package/package.json +4 -4
  110. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +0 -6
  111. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +0 -2
  112. package/dist/bundle/chunks/chunk-6R7UI4IY.js +0 -2
  113. package/dist/bundle/chunks/chunk-CK5Z65DM.js +0 -2
  114. package/dist/bundle/chunks/chunk-RCZIEVGO.js +0 -11
  115. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +0 -2
  116. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +0 -2
  117. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +0 -5
  118. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +0 -2
  119. package/docs/development.md +0 -90
@@ -0,0 +1,2051 @@
1
+ import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
+
3
+ // packages/kyrn-judge/src/setup/main.ts
4
+ import { constants } from "node:os";
5
+ import { join as join4 } from "node:path";
6
+ import {
7
+ applyHttpProxySettings,
8
+ configureHttpDispatcher,
9
+ ModelRuntime,
10
+ SettingsManager as SettingsManager2
11
+ } from "../../dist/bundle/index.js";
12
+
13
+ // packages/kyrn-judge/src/auth/exit.ts
14
+ import { mkdirSync, rmdirSync } from "node:fs";
15
+ import { resolve } from "node:path";
16
+ import { setTimeout as sleep } from "node:timers/promises";
17
+ async function exitWhenUnlocked(code, files, exit, { limitMs = 5e3, pollMs = 20 } = {}) {
18
+ const locks = lockFolders(files);
19
+ const deadline = Date.now() + limitMs;
20
+ while (true) {
21
+ const taken = takeAll(locks);
22
+ if (taken || Date.now() >= deadline) {
23
+ for (const lock of taken ?? []) drop(lock);
24
+ exit(code);
25
+ return;
26
+ }
27
+ await sleep(pollMs);
28
+ }
29
+ }
30
+ function lockFolders(files) {
31
+ return [...new Set(files.map((file) => `${resolve(file)}.lock`))];
32
+ }
33
+ function takeAll(locks) {
34
+ const taken = [];
35
+ for (const lock of locks) {
36
+ try {
37
+ mkdirSync(lock);
38
+ taken.push(lock);
39
+ } catch (error) {
40
+ if (error.code !== "EEXIST") continue;
41
+ for (const mine of taken) drop(mine);
42
+ return void 0;
43
+ }
44
+ }
45
+ return taken;
46
+ }
47
+ function drop(lock) {
48
+ try {
49
+ rmdirSync(lock);
50
+ } catch {
51
+ }
52
+ }
53
+
54
+ // packages/kyrn-judge/src/naming.ts
55
+ import { existsSync } from "node:fs";
56
+ import { homedir } from "node:os";
57
+ import { join } from "node:path";
58
+ function muEnv(name, env = process.env) {
59
+ return env[`MU_${name}`] || env[`KYRN_${name}`] || void 0;
60
+ }
61
+ var CONFIG_FILE = "mu.json";
62
+ var LEGACY_CONFIG_FILE = "kyrn.json";
63
+ function muHome(home2 = homedir()) {
64
+ const current = join(home2, ".mu");
65
+ const legacy = join(home2, ".kyrn");
66
+ return existsSync(current) || !existsSync(legacy) ? current : legacy;
67
+ }
68
+
69
+ // packages/kyrn-judge/src/redact.ts
70
+ var REDACTED = "[redacted]";
71
+ var SHAPES = [
72
+ // A private key, the whole block, or to the end when the text was cut inside it.
73
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g,
74
+ // OpenAI, Anthropic, OpenRouter, DeepSeek and the like.
75
+ /\bsk-[A-Za-z0-9_-]{20,}/g,
76
+ /\b(?:gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{22,})/g,
77
+ /\bglpat-[A-Za-z0-9_-]{20,}/g,
78
+ /\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
79
+ /\bAIza[0-9A-Za-z_-]{35}/g,
80
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
81
+ /\b[rs]k_(?:live|test)_[0-9A-Za-z]{16,}/g,
82
+ /\bnpm_[A-Za-z0-9]{36}\b/g,
83
+ /\bhf_[A-Za-z0-9]{30,}/g,
84
+ // A JSON web token.
85
+ /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
86
+ ];
87
+ var BEARER = /\b(Bearer|Basic)(\s+)(?=[A-Za-z._~+/=-]*[0-9])[A-Za-z0-9._~+/=-]{12,}/g;
88
+ var URL_PASSWORD = /\b([a-z][a-z0-9+.-]*:\/\/[^\s:@/]+:)[^\s@/]+(@)/gi;
89
+ var KEY_WORD = "(?:SECRET|TOKEN|PASSWD|PASSWORD|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY)";
90
+ var ENV_VALUE = new RegExp(`\\b([A-Z0-9_]*${KEY_WORD}[A-Z0-9_]*=)("[^"\\n]{8,}"|'[^'\\n]{8,}'|[^\\s"';&|]{8,})`, "g");
91
+ var QUOTED_VALUE = new RegExp(
92
+ `(["']?[A-Za-z0-9_-]*${KEY_WORD.replaceAll("_?", "[_-]?")}[A-Za-z0-9_-]*["']?\\s*[:=]\\s*)(["'])[^"'\\s]{8,}\\2`,
93
+ "gi"
94
+ );
95
+ var LINE_VALUE = new RegExp(
96
+ `^(\\s*[A-Za-z0-9_.-]*${KEY_WORD.replaceAll("_?", "[_.-]?")}[A-Za-z0-9_.-]*\\s*[:=][ \\t]*)[^\\s#"']{8,}[ \\t]*$`,
97
+ "gim"
98
+ );
99
+ function redactSecrets(text, known = []) {
100
+ let clean = text;
101
+ for (const secret of known) if (clean.includes(secret)) clean = clean.split(secret).join(REDACTED);
102
+ for (const shape of SHAPES) clean = clean.replace(shape, REDACTED);
103
+ return clean.replace(BEARER, `$1$2${REDACTED}`).replace(URL_PASSWORD, `$1${REDACTED}$2`).replace(ENV_VALUE, `$1${REDACTED}`).replace(QUOTED_VALUE, `$1$2${REDACTED}$2`).replace(LINE_VALUE, `$1${REDACTED}`);
104
+ }
105
+
106
+ // packages/kyrn-judge/src/setup/env-file.ts
107
+ var DEFINITION = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
108
+ function closingQuote(text, quote) {
109
+ for (let index = 0; index < text.length; index++) {
110
+ if (quote === '"' && text[index] === "\\") index++;
111
+ else if (text[index] === quote) return index;
112
+ }
113
+ return -1;
114
+ }
115
+ function readEnvEntries(text) {
116
+ const entries = [];
117
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
118
+ for (let index = 0; index < lines.length; index++) {
119
+ const line = lines[index].trim();
120
+ if (!line || line.startsWith("#")) continue;
121
+ const match = DEFINITION.exec(line);
122
+ if (!match) continue;
123
+ const [, name, rest] = match;
124
+ const quote = rest[0];
125
+ if (quote !== '"' && quote !== "'") {
126
+ entries.push({ name, value: rest.replace(/\s+#.*$/, "").trim(), first: index, last: index });
127
+ continue;
128
+ }
129
+ let body = rest.slice(1);
130
+ let end = closingQuote(body, quote);
131
+ const startedAt = index;
132
+ while (end < 0 && index + 1 < lines.length) {
133
+ body += `
134
+ ${lines[++index]}`;
135
+ end = closingQuote(body, quote);
136
+ }
137
+ if (end < 0) {
138
+ index = startedAt;
139
+ continue;
140
+ }
141
+ const value = body.slice(0, end);
142
+ entries.push({
143
+ name,
144
+ value: quote === '"' ? value.replace(/\\(["\\$`])/g, "$1") : value,
145
+ first: startedAt,
146
+ last: index
147
+ });
148
+ }
149
+ return entries;
150
+ }
151
+ function envFileValue(text, name) {
152
+ return readEnvEntries(text).find((entry) => entry.name === name)?.value;
153
+ }
154
+ function formatEnvValue(value) {
155
+ if (/^[A-Za-z0-9_./:@+-]*$/.test(value)) return value;
156
+ if (!value.includes("'")) return `'${value}'`;
157
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
158
+ }
159
+ function setEnvValue(text, name, value) {
160
+ const newline = text.includes("\r\n") ? "\r\n" : "\n";
161
+ const bom = text.startsWith("\uFEFF") ? "\uFEFF" : "";
162
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
163
+ const found = readEnvEntries(text).filter((entry) => entry.name === name);
164
+ const assignment = (exported2) => `${exported2 ? "export " : ""}${name}=${formatEnvValue(value)}`;
165
+ if (found.length === 0) {
166
+ const body = lines.join(newline);
167
+ const separator = body === "" || body.endsWith(newline) ? "" : newline;
168
+ return `${bom}${body}${separator}${assignment(false)}${newline}`;
169
+ }
170
+ const drop2 = /* @__PURE__ */ new Set();
171
+ for (const entry of found.slice(1)) for (let line = entry.first; line <= entry.last; line++) drop2.add(line);
172
+ const [first] = found;
173
+ const exported = /^\s*export\s/.test(lines[first.first]);
174
+ const next = [];
175
+ for (let index = 0; index < lines.length; index++) {
176
+ if (drop2.has(index)) continue;
177
+ if (index === first.first) next.push(assignment(exported));
178
+ else if (index > first.first && index <= first.last) continue;
179
+ else next.push(lines[index]);
180
+ }
181
+ return `${bom}${next.join(newline)}`;
182
+ }
183
+
184
+ // packages/kyrn-judge/src/setup/files.ts
185
+ import {
186
+ chmodSync,
187
+ copyFileSync,
188
+ existsSync as existsSync2,
189
+ mkdirSync as mkdirSync2,
190
+ readFileSync,
191
+ renameSync,
192
+ statSync,
193
+ unlinkSync,
194
+ writeFileSync
195
+ } from "node:fs";
196
+ import { basename, dirname, join as join2 } from "node:path";
197
+ var PRIVATE_FILE = 384;
198
+ var PRIVATE_FOLDER = 448;
199
+ function backupStamp(now) {
200
+ const two = (value) => String(value).padStart(2, "0");
201
+ return `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())}T${two(now.getHours())}-${two(now.getMinutes())}-${two(now.getSeconds())}`;
202
+ }
203
+ var Backups = class {
204
+ constructor(home2, now) {
205
+ this.saved = /* @__PURE__ */ new Set();
206
+ this.root = join2(home2, "backups");
207
+ this.stamp = backupStamp(now);
208
+ }
209
+ /** Copies `file` the first time it is about to change in this run. A file that does not exist has nothing to keep. */
210
+ save(file) {
211
+ if (this.saved.has(file) || !existsSync2(file)) return;
212
+ const folder = this.folder();
213
+ const copy = join2(folder, basename(file));
214
+ copyFileSync(file, copy);
215
+ try {
216
+ chmodSync(copy, PRIVATE_FILE);
217
+ } catch {
218
+ }
219
+ this.saved.add(file);
220
+ }
221
+ /** Where this run's copies are, once one was made. */
222
+ get path() {
223
+ return this.made;
224
+ }
225
+ folder() {
226
+ if (this.made) return this.made;
227
+ mkdirSync2(this.root, { recursive: true, mode: PRIVATE_FOLDER });
228
+ for (let attempt = 1; ; attempt++) {
229
+ const candidate = join2(this.root, attempt === 1 ? this.stamp : `${this.stamp}-${attempt}`);
230
+ try {
231
+ mkdirSync2(candidate, { mode: PRIVATE_FOLDER });
232
+ this.made = candidate;
233
+ return candidate;
234
+ } catch (error) {
235
+ if (error.code !== "EEXIST") throw error;
236
+ }
237
+ }
238
+ }
239
+ };
240
+ function readJsonFile(path) {
241
+ let text;
242
+ try {
243
+ text = readFileSync(path, "utf8");
244
+ } catch (error) {
245
+ if (error.code === "ENOENT") return { state: "absent" };
246
+ return { state: "unreadable" };
247
+ }
248
+ if (!text.trim()) return { state: "ok", value: {} };
249
+ try {
250
+ const value = JSON.parse(text.replace(/^/, ""));
251
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? { state: "ok", value } : { state: "unreadable" };
252
+ } catch {
253
+ return { state: "unreadable" };
254
+ }
255
+ }
256
+ function writeFileAtomic(path, text) {
257
+ mkdirSync2(dirname(path), { recursive: true });
258
+ let mode = PRIVATE_FILE;
259
+ try {
260
+ mode = statSync(path).mode & 511;
261
+ } catch {
262
+ }
263
+ const temporary = `${path}.mu-setup-${process.pid}`;
264
+ writeFileSync(temporary, text, { mode });
265
+ try {
266
+ chmodSync(temporary, mode);
267
+ renameSync(temporary, path);
268
+ } catch (error) {
269
+ try {
270
+ unlinkSync(temporary);
271
+ } catch {
272
+ }
273
+ throw error;
274
+ }
275
+ }
276
+ function writeJsonFile(path, value) {
277
+ writeFileAtomic(path, `${JSON.stringify(value, null, 2)}
278
+ `);
279
+ }
280
+ function isRecord(value) {
281
+ return typeof value === "object" && value !== null && !Array.isArray(value);
282
+ }
283
+ function existingProvider(models, id) {
284
+ const providers = models.providers;
285
+ return isRecord(providers) && isRecord(providers[id]) ? providers[id] : void 0;
286
+ }
287
+ function mergeModels(existing, added) {
288
+ const kept = Array.isArray(existing) ? [...existing] : [];
289
+ const have = new Set(kept.flatMap((model) => isRecord(model) && typeof model.id === "string" ? [model.id] : []));
290
+ return [...kept, ...added.filter((model) => !have.has(model.id)).map((model) => ({ ...model }))];
291
+ }
292
+ function withProvider(models, id, entry) {
293
+ const providers = isRecord(models.providers) ? models.providers : {};
294
+ const current = isRecord(providers[id]) ? providers[id] : {};
295
+ const next = {
296
+ ...current,
297
+ name: typeof current.name === "string" ? current.name : entry.name,
298
+ baseUrl: entry.baseUrl,
299
+ api: entry.api,
300
+ models: mergeModels(current.models, entry.models)
301
+ };
302
+ if (entry.apiKey !== void 0 && current.apiKey === void 0) next.apiKey = entry.apiKey;
303
+ if (entry.compat !== void 0 && current.compat === void 0) next.compat = { ...entry.compat };
304
+ if (next.name === void 0) delete next.name;
305
+ return { ...models, providers: { ...providers, [id]: next } };
306
+ }
307
+ function withModel(models, providerId, modelId) {
308
+ const providers = isRecord(models.providers) ? models.providers : {};
309
+ const current = isRecord(providers[providerId]) ? providers[providerId] : {};
310
+ return {
311
+ ...models,
312
+ providers: { ...providers, [providerId]: { ...current, models: mergeModels(current.models, [{ id: modelId }]) } }
313
+ };
314
+ }
315
+ function judgeConfigPath(agentDir2, exists = existsSync2) {
316
+ const current = join2(agentDir2, CONFIG_FILE);
317
+ const legacy = join2(agentDir2, LEGACY_CONFIG_FILE);
318
+ return !exists(current) && exists(legacy) ? legacy : current;
319
+ }
320
+ function tiersOf(config) {
321
+ const tiers = config?.tiers;
322
+ const named2 = Array.isArray(tiers) ? tiers.filter((tier) => typeof tier === "string") : [];
323
+ return named2.length > 0 ? named2 : ["jev"];
324
+ }
325
+ function withTiers(config, tiers) {
326
+ return { ...config, tiers: [...tiers] };
327
+ }
328
+ function readTextFile(path) {
329
+ try {
330
+ return readFileSync(path, "utf8");
331
+ } catch {
332
+ return void 0;
333
+ }
334
+ }
335
+ function writeEnvValue(path, name, value) {
336
+ writeFileAtomic(path, setEnvValue(readTextFile(path) ?? "", name, value));
337
+ }
338
+
339
+ // packages/kyrn-judge/src/setup/language.ts
340
+ function languageOf(value) {
341
+ const tag = value?.trim().split(/[.@]/)[0];
342
+ if (!tag || tag === "C" || tag === "POSIX") return void 0;
343
+ return /^zh(?:[-_]|$)/i.test(tag) ? "zh" : "en";
344
+ }
345
+ function setupLanguage(env, systemLocale2) {
346
+ for (const name of ["MU_LANG", "KYRN_LANG", "LC_ALL", "LC_MESSAGES", "LANG"]) {
347
+ const found = languageOf(env[name]);
348
+ if (found) return found;
349
+ }
350
+ return languageOf(systemLocale2) ?? "en";
351
+ }
352
+ function systemLocale() {
353
+ try {
354
+ return Intl.DateTimeFormat().resolvedOptions().locale;
355
+ } catch {
356
+ return void 0;
357
+ }
358
+ }
359
+ function article(name) {
360
+ return `${/^[AEIOUaeioux]/.test(name) ? "an" : "a"} ${name}`;
361
+ }
362
+
363
+ // packages/kyrn-judge/src/setup/pi-access.ts
364
+ import { CredentialSynchronizationError, SettingsManager } from "../../dist/bundle/index.js";
365
+ function piAccess(runtime, agentDir2) {
366
+ return {
367
+ providerExists: (provider) => runtime.getProvider(provider) !== void 0,
368
+ models: (provider) => runtime.getModels(provider).map((model) => model.id),
369
+ baseUrl: (provider, api) => runtime.getModels(provider).find((model) => model.api === api)?.baseUrl ?? runtime.getProvider(provider)?.baseUrl,
370
+ credential: async (provider) => {
371
+ const found = (await runtime.listCredentials()).find((credential) => credential.providerId === provider);
372
+ if (!found) return void 0;
373
+ return found.type === "oauth" ? "oauth" : "api_key";
374
+ },
375
+ storeKey: async (provider, key) => {
376
+ await runtime.refresh({ allowNetwork: false, providers: [provider] });
377
+ if (!runtime.getProvider(provider)) {
378
+ throw new Error(`pi has no provider ${provider}${runtime.getError() ? `: ${runtime.getError()}` : ""}`);
379
+ }
380
+ let asked = false;
381
+ try {
382
+ await runtime.login(provider, "api_key", {
383
+ // pi's key login asks one question, the key. Anything else is not answered, and nothing is guessed.
384
+ prompt: async (prompt) => {
385
+ if (asked || prompt.type !== "secret") throw new Error(`pi asked ${provider} for more than a key`);
386
+ asked = true;
387
+ return key;
388
+ },
389
+ notify: () => {
390
+ }
391
+ });
392
+ } catch (error) {
393
+ if (!(error instanceof CredentialSynchronizationError)) throw error;
394
+ }
395
+ },
396
+ setDefaultModel: async (provider, model) => {
397
+ const settings = SettingsManager.create(agentDir2, agentDir2, { projectTrusted: false });
398
+ const unreadable = settings.drainErrors().find((problem) => problem.scope === "global");
399
+ if (unreadable) throw new Error(`settings.json cannot be read: ${unreadable.error.message}`);
400
+ settings.setDefaultModelAndProvider(provider, model);
401
+ await settings.flush();
402
+ const failed = settings.drainErrors()[0];
403
+ if (failed) throw new Error(`settings.json was not written: ${failed.error.message}`);
404
+ }
405
+ };
406
+ }
407
+
408
+ // packages/kyrn-judge/src/setup/terminal.ts
409
+ import { createInterface } from "node:readline";
410
+ var ESCAPES = /\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b[@-_]/g;
411
+ function askLine(input, output, question) {
412
+ return new Promise((resolve2) => {
413
+ const lines = createInterface({ input, output, terminal: input.isTTY === true });
414
+ let settled = false;
415
+ const finish = (answer) => {
416
+ if (settled) return;
417
+ settled = true;
418
+ lines.close();
419
+ resolve2(answer);
420
+ };
421
+ lines.on("SIGINT", () => {
422
+ output.write("\n");
423
+ finish(void 0);
424
+ });
425
+ lines.on("close", () => finish(void 0));
426
+ lines.question(question, (answer) => finish(answer));
427
+ });
428
+ }
429
+ function askSecret(input, output, question) {
430
+ if (!input.isTTY) return askLine(input, output, question);
431
+ return new Promise((resolve2) => {
432
+ output.write(question);
433
+ const wasRaw = input.isRaw;
434
+ input.setRawMode(true);
435
+ let value = "";
436
+ const finish = (answer) => {
437
+ input.removeListener("data", onData);
438
+ input.setRawMode(wasRaw);
439
+ input.pause();
440
+ output.write("\n");
441
+ resolve2(answer);
442
+ };
443
+ const onData = (chunk) => {
444
+ const text = (typeof chunk === "string" ? chunk : chunk.toString("utf8")).replace(ESCAPES, "");
445
+ for (const char of text) {
446
+ if (char === "\r" || char === "\n") return finish(value);
447
+ if (char === "") return finish(void 0);
448
+ if (char === "") {
449
+ if (value === "") return finish(void 0);
450
+ } else if (char === "\x7F" || char === "\b") value = Array.from(value).slice(0, -1).join("");
451
+ else if (char === "") value = "";
452
+ else if (char >= " ") value += char;
453
+ }
454
+ };
455
+ input.on("data", onData);
456
+ input.resume();
457
+ });
458
+ }
459
+ function terminalPrompter(input = process.stdin, output = process.stdout, errors = process.stderr) {
460
+ return {
461
+ say: (text) => output.write(`${text}
462
+ `),
463
+ warn: (text) => errors.write(`${text}
464
+ `),
465
+ ask: (question) => askLine(input, output, question),
466
+ secret: (question) => askSecret(input, output, question)
467
+ };
468
+ }
469
+ async function readAll(input = process.stdin) {
470
+ const chunks = [];
471
+ for await (const chunk of input) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
472
+ return Buffer.concat(chunks).toString("utf8");
473
+ }
474
+
475
+ // packages/kyrn-judge/src/setup/wizard.ts
476
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "node:fs";
477
+ import { join as join3 } from "node:path";
478
+
479
+ // packages/kyrn-judge/src/setup/check.ts
480
+ var CHECK_TIMEOUT_MS = 1e4;
481
+ function checkRequest(kind, baseUrl, key) {
482
+ const base = baseUrl.replace(/\/+$/, "");
483
+ const headers = { accept: "application/json" };
484
+ switch (kind) {
485
+ case "anthropic-models":
486
+ headers["anthropic-version"] = "2023-06-01";
487
+ if (key?.includes("sk-ant-oat")) {
488
+ headers.authorization = `Bearer ${key}`;
489
+ headers["anthropic-beta"] = "oauth-2025-04-20";
490
+ } else if (key) headers["x-api-key"] = key;
491
+ return { url: `${base}/v1/models?limit=1000`, headers };
492
+ case "google-models":
493
+ if (key) headers["x-goog-api-key"] = key;
494
+ return { url: `${base}/models?pageSize=1000`, headers };
495
+ case "openrouter-key":
496
+ if (key) headers.authorization = `Bearer ${key}`;
497
+ return { url: `${base}/key`, headers };
498
+ default:
499
+ if (key) headers.authorization = `Bearer ${key}`;
500
+ return { url: `${base}/models`, headers };
501
+ }
502
+ }
503
+ var BALANCE = /insufficient[\s_-]*(?:balance|quota|credit|funds)|arrear|balance|quota|credit|billing|payment|recharge|top[\s-]?up|余额|欠费|充值|额度/i;
504
+ var REGION = /unsupported_country|country|region|territory|location is not supported|user location|request not allowed|不支持.*地区|地区.*不支持/i;
505
+ var WRONG_KEY = /api[\s_-]?key[\s_-]*(?:not valid|invalid)|invalid[\s_-]*api[\s_-]*key|incorrect api key|invalid authentication|authentication fails|unauthori[sz]ed|token is invalid|令牌.*(?:过期|不正确)|invalid[\s_-]*(?:token|x-api-key)/i;
506
+ var PERMISSION = /scope|permission|not allowed to|insufficient permissions/i;
507
+ function messageOf(body) {
508
+ try {
509
+ const parsed = JSON.parse(body);
510
+ const error = parsed.error;
511
+ const candidates = [
512
+ typeof error === "object" && error !== null ? error.message : void 0,
513
+ typeof error === "string" ? error : void 0,
514
+ parsed.message,
515
+ typeof error === "object" && error !== null ? error.code : void 0,
516
+ parsed.code
517
+ ];
518
+ const found = candidates.find((value) => typeof value === "string" && value.trim());
519
+ if (typeof found === "string") return found;
520
+ } catch {
521
+ }
522
+ return body.trimStart().startsWith("<") ? "" : body;
523
+ }
524
+ function safeMessage(text, key) {
525
+ const known = key ? [key] : [];
526
+ const clean = redactSecrets(text, known).replace(/\*{2,}[A-Za-z0-9_-]{0,12}/g, "\u2026").replace(/\s+/g, " ").trim();
527
+ return clean.length > 160 ? `${clean.slice(0, 157)}...` : clean;
528
+ }
529
+ function classifyAnswer(status, body) {
530
+ const text = `${messageOf(body)} ${body}`;
531
+ if (status === 401) return "key";
532
+ if (status === 402) return "balance";
533
+ if (status === 404) return "address";
534
+ if (status >= 500) return "server";
535
+ if (status === 400 || status === 403 || status === 429) {
536
+ if (BALANCE.test(text)) return "balance";
537
+ if (REGION.test(text)) return "region";
538
+ if (WRONG_KEY.test(text)) return "key";
539
+ if (status === 429) return "rate";
540
+ if (status === 403) return PERMISSION.test(text) ? "permission" : "refused";
541
+ }
542
+ return "other";
543
+ }
544
+ function errorCode(error, depth = 0) {
545
+ if (typeof error !== "object" || error === null || depth > 4) return "";
546
+ const { code, cause, errors } = error;
547
+ if (typeof code === "string" && code) return code;
548
+ if (Array.isArray(errors) && errors.length > 0) return errorCode(errors[0], depth + 1);
549
+ return errorCode(cause, depth + 1);
550
+ }
551
+ function classifyFailure(error) {
552
+ const name = error instanceof Error ? error.name : "";
553
+ if (name === "TimeoutError" || name === "AbortError") return "timeout";
554
+ const code = errorCode(error);
555
+ if (code === "UND_ERR_CONNECT_TIMEOUT" || code === "ETIMEDOUT" || code === "UND_ERR_HEADERS_TIMEOUT")
556
+ return "timeout";
557
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN") return "dns";
558
+ if (/CERT|SSL|TLS|SELF_SIGNED|UNABLE_TO_VERIFY/.test(code)) return "tls";
559
+ return "connection";
560
+ }
561
+ function listedModels(kind, body) {
562
+ if (typeof body !== "object" || body === null) return void 0;
563
+ const record = body;
564
+ const list = Array.isArray(record.data) ? record.data : Array.isArray(record.models) ? record.models : void 0;
565
+ if (!list) return void 0;
566
+ const ids = [];
567
+ for (const entry of list) {
568
+ if (typeof entry === "string") {
569
+ ids.push(entry);
570
+ continue;
571
+ }
572
+ if (typeof entry !== "object" || entry === null) continue;
573
+ const model = entry;
574
+ if (kind === "google-models") {
575
+ const methods = model.supportedGenerationMethods;
576
+ if (Array.isArray(methods) && !methods.includes("generateContent")) continue;
577
+ }
578
+ const id = typeof model.id === "string" ? model.id : typeof model.name === "string" ? model.name : void 0;
579
+ if (id) ids.push(kind === "google-models" ? id.replace(/^models\//, "") : id);
580
+ }
581
+ return [...new Set(ids)];
582
+ }
583
+ async function checkKey({
584
+ kind,
585
+ baseUrl,
586
+ key,
587
+ fetch,
588
+ timeoutMs = CHECK_TIMEOUT_MS
589
+ }) {
590
+ const request = checkRequest(kind, baseUrl, key);
591
+ const url = request.url.split("?")[0];
592
+ let response;
593
+ try {
594
+ response = await fetch(request.url, { headers: request.headers, signal: AbortSignal.timeout(timeoutMs) });
595
+ } catch (error) {
596
+ return { ok: false, problem: { kind: classifyFailure(error), url } };
597
+ }
598
+ let text = "";
599
+ try {
600
+ text = await response.text();
601
+ } catch {
602
+ }
603
+ if (!response.ok) {
604
+ return {
605
+ ok: false,
606
+ problem: {
607
+ kind: classifyAnswer(response.status, text),
608
+ url,
609
+ status: response.status,
610
+ said: safeMessage(messageOf(text), key) || void 0
611
+ }
612
+ };
613
+ }
614
+ if (kind === "openrouter-key") return { ok: true, url };
615
+ let body;
616
+ try {
617
+ body = JSON.parse(text);
618
+ } catch {
619
+ return { ok: false, problem: { kind: "response", url, status: response.status } };
620
+ }
621
+ const models = listedModels(kind, body);
622
+ if (!models) return { ok: false, problem: { kind: "response", url, status: response.status } };
623
+ return { ok: true, url, models };
624
+ }
625
+ function hostOf(url) {
626
+ try {
627
+ return new URL(url).host;
628
+ } catch {
629
+ return url;
630
+ }
631
+ }
632
+ function explainProblem(problem, context, language) {
633
+ const { service } = context;
634
+ const host = hostOf(problem.url);
635
+ const seconds = Math.round((context.timeoutMs ?? CHECK_TIMEOUT_MS) / 1e3);
636
+ const local = /^(localhost|127\.|\[::1\])/.test(host);
637
+ const proxyHint = language === "zh" ? context.proxy ? `\u8FD9\u6B21\u8BF7\u6C42\u8D70\u4E86\u4EE3\u7406\uFF08${context.proxy}\uFF09\uFF0C\u786E\u8BA4\u4EE3\u7406\u6B63\u5728\u8FD0\u884C\u3002` : "\u5982\u679C\u9700\u8981\u4EE3\u7406\uFF1A\u8BBE\u7F6E HTTPS_PROXY\uFF0C\u6216\u5728 ~/.mu/agent/settings.json \u91CC\u5199 httpProxy\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C mu setup\u3002" : context.proxy ? `The request went through your proxy (${context.proxy}): check that it is running.` : "If you need a proxy, set HTTPS_PROXY (or httpProxy in ~/.mu/agent/settings.json) and run mu setup again.";
638
+ const said = problem.said ? language === "zh" ? `\uFF08\u670D\u52A1\u7684\u539F\u8BDD\uFF1A${problem.said}\uFF09` : ` (The service said: ${problem.said})` : "";
639
+ const zh = language === "zh";
640
+ switch (problem.kind) {
641
+ case "key":
642
+ return zh ? `${service} \u4E0D\u63A5\u53D7\u8FD9\u4E2A\u5BC6\u94A5\u3002\u68C0\u67E5\u662F\u5426\u5B8C\u6574\u590D\u5236\uFF0C\u4EE5\u53CA\u5B83\u786E\u5B9E\u662F ${service} \u7684\u5BC6\u94A5\u3002` : `${service} does not accept this key. Check that you copied all of it, and that it is ${article(service)} key.`;
643
+ case "refused":
644
+ return zh ? `${service} \u62D2\u7EDD\u4E86\u8FD9\u4E2A\u5BC6\u94A5\uFF08HTTP 403\uFF09\uFF1A\u53EF\u80FD\u662F\u5BC6\u94A5\u4E0D\u5BF9\uFF0C\u6216\u5B83\u6CA1\u6709\u8BBF\u95EE\u6743\u9650\u3002${said}` : `${service} refused this key (HTTP 403): it may be wrong, or not allowed access.${said}`;
645
+ case "permission":
646
+ return zh ? "\u8FD9\u4E2A\u5BC6\u94A5\u6709\u6548\uFF0C\u4F46\u6CA1\u6709\u8BFB\u53D6\u6A21\u578B\u5217\u8868\u7684\u6743\u9650\uFF08\u53D7\u9650\u5BC6\u94A5\uFF09\u3002\u7ED9\u5B83\u8BFB\u53D6\u6A21\u578B\u7684\u6743\u9650\uFF0C\u6216\u6362\u4E00\u4E2A\u5BC6\u94A5\u3002" : "The key works, but may not read the list of models (a restricted key). Allow it to read models, or use another key.";
647
+ case "balance":
648
+ return zh ? `${service} \u7684\u8D26\u6237\u6CA1\u6709\u4F59\u989D\u6216\u989D\u5EA6\u4E86\u3002\u5145\u503C\u540E\u91CD\u65B0\u8FD0\u884C mu setup\u3002` : `The ${service} account has no balance or quota left. Top it up, then run mu setup again.`;
649
+ case "region":
650
+ return zh ? `${service} \u4E0D\u5411\u4F60\u6240\u5728\u7684\u5730\u533A\u63D0\u4F9B\u670D\u52A1\u3002\u9700\u8981\u4E00\u4E2A\u4F4D\u4E8E\u652F\u6301\u5730\u533A\u7684\u4EE3\u7406\u6216 VPN\u3002${proxyHint}` : `${service} does not serve the region this request came from. A proxy or VPN in a supported region is needed. ${proxyHint}`;
651
+ case "rate":
652
+ return zh ? `${service} \u8BF4\u8BF7\u6C42\u592A\u9891\u7E41\u3002\u7B49\u4E00\u5206\u949F\u518D\u8BD5\u3002` : `${service} says there were too many requests. Wait a minute and try again.`;
653
+ case "address":
654
+ return zh ? `${problem.url} \u8FD9\u4E2A\u5730\u5740\u4E0D\u5B58\u5728\uFF08HTTP 404\uFF09\uFF1A\u5730\u5740\u586B\u9519\u4E86\u3002` : `Nothing answers at ${problem.url} (HTTP 404): the address is wrong.`;
655
+ case "server":
656
+ return zh ? `${service} \u81EA\u5DF1\u51FA\u9519\u4E86\uFF08HTTP ${problem.status}\uFF09\u3002\u8FC7\u4E00\u4F1A\u513F\u518D\u8BD5\u3002` : `${service} had an error of its own (HTTP ${problem.status}). Try again in a while.`;
657
+ case "response":
658
+ return zh ? `${problem.url} \u6709\u56DE\u5E94\uFF0C\u4F46\u4E0D\u662F\u6A21\u578B\u5217\u8868\uFF1A\u68C0\u67E5\u5730\u5740\u548C\u63A5\u53E3\u7C7B\u578B\u3002` : `${problem.url} answered, but not with a list of models: check the address and the API type.`;
659
+ case "timeout":
660
+ return zh ? `${seconds} \u79D2\u5185 ${host} \u6CA1\u6709\u56DE\u5E94\u3002\u7F51\u7EDC\u53EF\u80FD\u4E0D\u901A\uFF1A\u6709\u4E9B\u5730\u533A\u8981\u7528\u4EE3\u7406\u6216 VPN \u624D\u80FD\u8BBF\u95EE\u3002${proxyHint}` : `No answer from ${host} within ${seconds} s. The network may block it: in some regions a proxy or VPN is needed. ${proxyHint}`;
661
+ case "dns":
662
+ return zh ? `\u627E\u4E0D\u5230 ${host}\uFF1A\u6CA1\u6709\u8054\u7F51\uFF0C\u6216\u8FD9\u91CC\u89E3\u6790\u4E0D\u4E86\u8FD9\u4E2A\u57DF\u540D\u3002${proxyHint}` : `Could not find ${host}: there is no internet connection, or the name cannot be resolved here. ${proxyHint}`;
663
+ case "connection":
664
+ if (local) {
665
+ return zh ? `${host} \u4E0A\u6CA1\u6709\u670D\u52A1\u5728\u76D1\u542C\uFF1A\u5B83\u542F\u52A8\u4E86\u5417\uFF1F` : `Nothing is listening at ${host}: is the service running?`;
666
+ }
667
+ return zh ? `\u8FDE\u4E0D\u4E0A ${host}\u3002${proxyHint}` : `Could not connect to ${host}. ${proxyHint}`;
668
+ case "tls":
669
+ return zh ? `\u548C ${host} \u7684\u5B89\u5168\u8FDE\u63A5\u5931\u8D25\u4E86\uFF1A\u53EF\u80FD\u6709\u4EE3\u7406\u6216\u9632\u706B\u5899\u5728\u62E6\u622A HTTPS\u3002` : `The secure connection to ${host} failed: a proxy or firewall may be intercepting HTTPS.`;
670
+ default:
671
+ return zh ? `${service} \u62D2\u7EDD\u4E86\u8FD9\u6B21\u68C0\u67E5\uFF08HTTP ${problem.status ?? "?"}\uFF09\u3002${said}` : `${service} refused the check (HTTP ${problem.status ?? "?"}).${said}`;
672
+ }
673
+ }
674
+
675
+ // packages/kyrn-judge/src/setup/services.ts
676
+ var SK_HEX32 = /^sk-[0-9a-f]{32}$/;
677
+ var ZHIPU = /^[0-9a-f]{32}\.[A-Za-z0-9]{16}$/;
678
+ var SETUP_SERVICES = [
679
+ {
680
+ id: "deepseek",
681
+ name: "DeepSeek",
682
+ baseUrls: ["https://api.deepseek.com"],
683
+ api: "openai-completions",
684
+ models: ["deepseek-v4-pro", "deepseek-chat"],
685
+ keyShape: SK_HEX32,
686
+ keyPage: "https://platform.deepseek.com/api_keys",
687
+ providers: ["deepseek"],
688
+ builtIn: true
689
+ },
690
+ {
691
+ id: "qwen",
692
+ name: "Qwen",
693
+ baseUrls: ["https://dashscope.aliyuncs.com/compatible-mode/v1"],
694
+ api: "openai-completions",
695
+ models: ["qwen3-coder-plus", "qwen3-max", "qwen-plus"],
696
+ // The same shape as DeepSeek's: such a key is asked about, never tried on both.
697
+ keyShape: SK_HEX32,
698
+ keyPage: "https://bailian.console.aliyun.com/cn-beijing/model/settings/api-key",
699
+ providers: ["qwen"],
700
+ builtIn: false,
701
+ // As pi's own catalog has Alibaba's compatible mode (its qwen-token-plan providers): no developer role, no `store`.
702
+ compat: { supportsDeveloperRole: false, supportsStore: false }
703
+ },
704
+ {
705
+ id: "kimi",
706
+ name: "Kimi",
707
+ // platform.moonshot.cn issues keys for the first address, platform.moonshot.ai for the second.
708
+ baseUrls: ["https://api.moonshot.cn/v1", "https://api.moonshot.ai/v1"],
709
+ api: "openai-completions",
710
+ models: ["kimi-k2.6"],
711
+ // 48 letters and digits, but not SiliconFlow's 48 lowercase letters nor an OpenAI key (its T3BlbkFJ).
712
+ keyShape: /^sk-(?![a-z]{48}$)(?![A-Za-z0-9]*T3BlbkFJ)[A-Za-z0-9]{48}$/,
713
+ // platform.moonshot.cn/console/api-keys redirects here.
714
+ keyPage: "https://platform.kimi.com/console/api-keys",
715
+ providers: ["moonshotai-cn", "moonshotai"],
716
+ builtIn: true
717
+ },
718
+ {
719
+ id: "glm",
720
+ name: "GLM",
721
+ // pi's provider on open.bigmodel.cn (zai-coding-cn) is the Coding Plan's address, below. A pay-as-you-go key
722
+ // goes here, to a provider of mu's.
723
+ baseUrls: ["https://open.bigmodel.cn/api/paas/v4"],
724
+ api: "openai-completions",
725
+ models: ["glm-*"],
726
+ keyShape: ZHIPU,
727
+ keyPage: "https://open.bigmodel.cn/usercenter/apikeys",
728
+ providers: ["glm"],
729
+ builtIn: false
730
+ },
731
+ {
732
+ // Not in the app's table: pi has a provider of its own for Zhipu's Coding Plan, which bills the subscription.
733
+ id: "glm-coding",
734
+ name: "GLM Coding Plan",
735
+ baseUrls: ["https://open.bigmodel.cn/api/coding/paas/v4"],
736
+ api: "openai-completions",
737
+ models: ["glm-5.3", "glm-*"],
738
+ keyShape: ZHIPU,
739
+ keyPage: "https://open.bigmodel.cn/usercenter/apikeys",
740
+ providers: ["zai-coding-cn"],
741
+ builtIn: true
742
+ },
743
+ {
744
+ id: "siliconflow",
745
+ name: "SiliconFlow",
746
+ baseUrls: ["https://api.siliconflow.cn/v1"],
747
+ api: "openai-completions",
748
+ models: ["deepseek-ai/DeepSeek-V3*", "moonshotai/Kimi-K2*", "Qwen/Qwen3-Coder*"],
749
+ keyShape: /^sk-[a-z]{48}$/,
750
+ keyPage: "https://cloud.siliconflow.cn/account/ak",
751
+ providers: ["siliconflow"],
752
+ builtIn: false
753
+ },
754
+ {
755
+ id: "stepfun",
756
+ name: "StepFun",
757
+ baseUrls: ["https://api.stepfun.com/v1"],
758
+ api: "openai-completions",
759
+ models: ["step-*"],
760
+ keyPage: "https://platform.stepfun.com/interface-key",
761
+ providers: ["stepfun"],
762
+ builtIn: false
763
+ },
764
+ {
765
+ id: "openrouter",
766
+ name: "OpenRouter",
767
+ baseUrls: ["https://openrouter.ai/api/v1"],
768
+ api: "openai-completions",
769
+ models: ["moonshotai/kimi-k2.6"],
770
+ keyShape: /^sk-or-/,
771
+ keyPage: "https://openrouter.ai/keys",
772
+ providers: ["openrouter"],
773
+ builtIn: true,
774
+ check: "openrouter-key"
775
+ },
776
+ {
777
+ id: "openai",
778
+ name: "OpenAI",
779
+ baseUrls: ["https://api.openai.com/v1"],
780
+ api: "openai-responses",
781
+ models: ["gpt-5.5"],
782
+ keyShape: /^sk-(?:proj-|svcacct-|admin-|(?!ant-|or-)[A-Za-z0-9_-]*T3BlbkFJ)/,
783
+ keyPage: "https://platform.openai.com/api-keys",
784
+ providers: ["openai"],
785
+ builtIn: true
786
+ },
787
+ {
788
+ id: "anthropic",
789
+ name: "Anthropic",
790
+ baseUrls: ["https://api.anthropic.com"],
791
+ api: "anthropic-messages",
792
+ models: ["claude-opus-4-8"],
793
+ keyShape: /^sk-ant-/,
794
+ // console.anthropic.com/settings/keys redirects here.
795
+ keyPage: "https://platform.claude.com/settings/keys",
796
+ providers: ["anthropic"],
797
+ builtIn: true
798
+ },
799
+ {
800
+ id: "google",
801
+ name: "Google Gemini",
802
+ baseUrls: ["https://generativelanguage.googleapis.com/v1beta"],
803
+ api: "google-generative-ai",
804
+ models: ["gemini-3.1-pro-preview"],
805
+ keyShape: /^AIza[0-9A-Za-z_-]{35}$/,
806
+ keyPage: "https://aistudio.google.com/apikey",
807
+ providers: ["google"],
808
+ builtIn: true
809
+ },
810
+ {
811
+ id: "xai",
812
+ name: "xAI",
813
+ baseUrls: ["https://api.x.ai/v1"],
814
+ // pi's xai provider speaks the Responses API.
815
+ api: "openai-responses",
816
+ models: ["grok-4.7"],
817
+ keyShape: /^xai-/,
818
+ keyPage: "https://console.x.ai",
819
+ providers: ["xai"],
820
+ builtIn: true
821
+ },
822
+ {
823
+ id: "ollama",
824
+ name: "Ollama",
825
+ baseUrls: ["http://localhost:11434/v1"],
826
+ api: "openai-completions",
827
+ models: [],
828
+ keyless: true,
829
+ providers: ["ollama"],
830
+ builtIn: false
831
+ },
832
+ {
833
+ // Not in the app's table: any address one types, with the API one names.
834
+ id: "other",
835
+ name: "Other",
836
+ baseUrls: [],
837
+ api: "openai-completions",
838
+ models: [],
839
+ providers: [],
840
+ builtIn: false,
841
+ keyOptional: true
842
+ }
843
+ ];
844
+ function serviceById(id) {
845
+ return SETUP_SERVICES.find((service) => service.id === id);
846
+ }
847
+ function cleanKey(raw) {
848
+ let key = raw.trim();
849
+ if (key.length >= 2 && (key[0] === '"' || key[0] === "'") && key.at(-1) === key[0]) key = key.slice(1, -1).trim();
850
+ return key;
851
+ }
852
+ function servicesForKey(key) {
853
+ const clean = cleanKey(key);
854
+ return clean ? SETUP_SERVICES.filter((service) => service.keyShape?.test(clean)) : [];
855
+ }
856
+ function checkKindOf(service, api) {
857
+ if (service.check) return service.check;
858
+ if (api === "anthropic-messages") return "anthropic-models";
859
+ if (api === "google-generative-ai") return "google-models";
860
+ return "openai-models";
861
+ }
862
+ var NOT_CHAT = /embed|rerank|\btts\b|tts-|whisper|audio|speech|\basr\b|transcri|dall-e|image|moderation|\bocr\b|paraformer|cosyvoice|sambert|wanx|flux|stable-diffusion|kolors|video|realtime|davinci|babbage/i;
863
+ function chatModels(listed) {
864
+ const chat = listed.filter((id) => !NOT_CHAT.test(id));
865
+ return chat.length > 0 ? chat : [...listed];
866
+ }
867
+ function versionOf(id) {
868
+ const found = /\d+(?:\.\d+)*/.exec(id);
869
+ return found ? found[0].split(".").map(Number) : [];
870
+ }
871
+ function newerFirst(a, b) {
872
+ const [x, y] = [versionOf(a), versionOf(b)];
873
+ for (let part = 0; part < Math.max(x.length, y.length); part += 1) {
874
+ if (x[part] === void 0) return 1;
875
+ if (y[part] === void 0) return -1;
876
+ if (x[part] !== y[part]) return y[part] - x[part];
877
+ }
878
+ return a.length - b.length;
879
+ }
880
+ function named(wanted, listed) {
881
+ if (!wanted.endsWith("*")) return listed.includes(wanted) ? wanted : void 0;
882
+ const prefix = wanted.slice(0, -1);
883
+ return listed.filter((id) => id.startsWith(prefix)).sort(newerFirst)[0];
884
+ }
885
+ function tableModel(service, listed) {
886
+ for (const wanted of service.models) {
887
+ const found = named(wanted, listed);
888
+ if (found) return found;
889
+ }
890
+ return void 0;
891
+ }
892
+ function namedModel(service) {
893
+ return service.models.find((wanted) => !wanted.endsWith("*"));
894
+ }
895
+
896
+ // packages/kyrn-judge/src/setup/wizard.ts
897
+ var SETUP_DECLINED_FILE = "setup-declined";
898
+ var SETUP_EXIT = { done: 0, failed: 1, usage: 2, start: 3, cancelled: 130 };
899
+ var SETUP_VALUE_FLAGS = [
900
+ "--service",
901
+ "--model",
902
+ "--base-url",
903
+ "--api",
904
+ "--name",
905
+ "--judge"
906
+ ];
907
+ var SETUP_FLAGS = {
908
+ "-h": "help",
909
+ "--help": "help",
910
+ help: "help",
911
+ "--list": "list",
912
+ "--first-run": "firstRun",
913
+ "-y": "yes",
914
+ "--yes": "yes",
915
+ "--key-stdin": "keyStdin"
916
+ };
917
+ var JEV_KEY_NAMES = ["TYPESAFE_API_KEY", "MU_JUDGE_OPENROUTER_API_KEY", "AI_GATEWAY_API_KEY"];
918
+ var JEV_TIERS = /* @__PURE__ */ new Set(["jev", "jev-direct", "jev-openrouter", "jev-gateway"]);
919
+ var JEV_PAGE = "https://typesafe.ai";
920
+ var BRANDED = /* @__PURE__ */ new Set(["openai", "anthropic", "openrouter", "google", "xai"]);
921
+ var OTHER = {
922
+ zh: "\u5176\u4ED6 OpenAI \u6216 Anthropic \u517C\u5BB9\u7684\u670D\u52A1",
923
+ en: "Another OpenAI- or Anthropic-compatible service"
924
+ };
925
+ var NOT_READY = {
926
+ zh: "Ollama \u8FD8\u6CA1\u51C6\u5907\u597D\uFF08\u7528 `ollama serve` \u542F\u52A8\u5B83\uFF0C\u518D\u62C9\u53D6\u4E00\u4E2A\u6A21\u578B\uFF09\u3002",
927
+ en: "Ollama is not ready yet (start it with `ollama serve`, pull a model)."
928
+ };
929
+ function parseSetupArgs(argv) {
930
+ const values = /* @__PURE__ */ new Map();
931
+ const flags = /* @__PURE__ */ new Set();
932
+ const words = [];
933
+ for (let index = 0; index < argv.length; index++) {
934
+ const arg = argv[index];
935
+ const equals = arg.startsWith("--") ? arg.indexOf("=") : -1;
936
+ const flag = equals > 0 ? arg.slice(0, equals) : arg;
937
+ if (SETUP_VALUE_FLAGS.includes(flag)) {
938
+ const value = equals > 0 ? arg.slice(equals + 1) : argv[++index];
939
+ if (value === void 0 || value === "" || equals < 0 && value.startsWith("-")) {
940
+ return { error: { zh: `${flag} \u540E\u9762\u8981\u8DDF\u4E00\u4E2A\u503C`, en: `${flag} needs a value` } };
941
+ }
942
+ values.set(flag, value);
943
+ } else if (SETUP_FLAGS[arg]) flags.add(SETUP_FLAGS[arg]);
944
+ else if (arg.startsWith("-") && arg.length > 1) {
945
+ return { error: { zh: `\u4E0D\u8BA4\u8BC6\u7684\u9009\u9879 ${arg}`, en: `unknown option ${arg}` } };
946
+ } else words.push(arg);
947
+ }
948
+ const api = values.get("--api");
949
+ if (api !== void 0 && api !== "openai" && api !== "anthropic") {
950
+ return { error: { zh: "--api \u53EA\u80FD\u662F openai \u6216 anthropic", en: "--api is openai or anthropic" } };
951
+ }
952
+ const judge = values.get("--judge");
953
+ if (judge !== void 0 && judge !== "jev" && judge !== "model" && judge !== "none") {
954
+ return { error: { zh: "--judge \u53EA\u80FD\u662F jev\u3001model \u6216 none", en: "--judge is jev, model or none" } };
955
+ }
956
+ const service = values.get("--service");
957
+ if (service !== void 0 && !serviceById(service)) {
958
+ return {
959
+ error: {
960
+ zh: `\u6CA1\u6709\u53EB ${service} \u7684\u670D\u52A1\uFF08mu setup --list \u5217\u51FA\u5168\u90E8\uFF09`,
961
+ en: `there is no service called ${service} (mu setup --list shows them)`
962
+ }
963
+ };
964
+ }
965
+ if (words.length > 1) return { error: { zh: "\u53EA\u80FD\u7ED9\u4E00\u4E2A\u5BC6\u94A5", en: "give one key at most" } };
966
+ return {
967
+ help: flags.has("help"),
968
+ list: flags.has("list"),
969
+ firstRun: flags.has("firstRun"),
970
+ yes: flags.has("yes"),
971
+ keyStdin: flags.has("keyStdin"),
972
+ service,
973
+ model: values.get("--model"),
974
+ baseUrl: values.get("--base-url"),
975
+ api,
976
+ name: values.get("--name"),
977
+ judge,
978
+ words
979
+ };
980
+ }
981
+ function setupUsage(language) {
982
+ if (language === "zh") {
983
+ return [
984
+ "mu setup\uFF1A\u7ED9 mu \u63A5\u4E0A\u4E00\u4E2A\u5E72\u6D3B\u7684\u6A21\u578B\uFF0C\u518D\u9009\u4E00\u4E2A\u5224\u5B9A\u5668\u3002",
985
+ "",
986
+ " mu setup \u5728\u7EC8\u7AEF\u91CC\u4E00\u6B65\u4E00\u6B65\u95EE",
987
+ " mu setup <\u5BC6\u94A5> \u4ECE\u4E00\u4E2A API \u5BC6\u94A5\u5F00\u59CB\uFF1Amu \u6309\u5BC6\u94A5\u7684\u6837\u5B50\u8BA4\u51FA\u5B83\u5C5E\u4E8E\u54EA\u4E2A\u670D\u52A1",
988
+ " mu setup --service <id> --key-stdin [--model <id>] [--base-url <\u5730\u5740>] [--judge jev|model|none] [--yes]",
989
+ " \u7ED9\u811A\u672C\u7528\uFF1A\u5BC6\u94A5\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB\u53D6",
990
+ " mu setup --list mu \u8BA4\u8BC6\u7684\u670D\u52A1\u548C\u5B83\u4EEC\u7684 id",
991
+ "",
992
+ "\u4E3A\u4EC0\u4E48\u811A\u672C\u8981\u4ECE\u6807\u51C6\u8F93\u5165\u7ED9\u5BC6\u94A5\uFF1A\u5199\u5728\u547D\u4EE4\u884C\u4E0A\u7684\u5BC6\u94A5\u4F1A\u7559\u5728 shell \u7684\u5386\u53F2\u8BB0\u5F55\u91CC\uFF0C",
993
+ "\u8FD9\u53F0\u7535\u8111\u4E0A\u80FD\u5217\u51FA\u8FDB\u7A0B\u7684\u7A0B\u5E8F\u4E5F\u90FD\u770B\u5F97\u5230\u5B83\u3002mu setup <\u5BC6\u94A5> \u53EA\u662F\u4E3A\u4E86\u4E0A\u624B\u5FEB\uFF1B\u80FD\u5728\u63D0\u793A\u91CC\u7C98\u8D34\u5C31\u7C98\u8D34\u3002",
994
+ "",
995
+ " --judge jev Jev \u5BC6\u94A5\uFF08TypeSafe\uFF09\u5199\u5728\u6807\u51C6\u8F93\u5165\u7684\u7B2C\u4E8C\u884C\uFF1B\u5DF2\u7ECF\u8BBE\u7F6E\u4E86 TYPESAFE_API_KEY \u5C31\u4E0D\u7528",
996
+ " --judge model \u7528\u521A\u8BBE\u7F6E\u7684\u6A21\u578B\u5F53\u5224\u5B9A\u5668\uFF08\u66F4\u6162\uFF0C\u6BCF\u6B21\u5224\u65AD\u90FD\u82B1 token\uFF09",
997
+ " --judge none \u4E0D\u6539\u5224\u5B9A\u5668\uFF1B\u4E0D\u5199 --judge \u65F6\u4E5F\u4E0D\u6539",
998
+ " --api, --name \u670D\u52A1 other \u7684\u63A5\u53E3\uFF08openai \u6216 anthropic\uFF09\u548C\u5B83\u5728 mu \u91CC\u7684\u540D\u5B57",
999
+ " --yes \u4E0D\u63D0\u95EE\uFF1B\u66FF\u6362\u5DF2\u6709\u7684\u8BBE\u7F6E\uFF08\u8BA2\u9605\u767B\u5F55\u3001\u540C\u540D\u670D\u52A1\u7684\u5730\u5740\u3001Jev \u5BC6\u94A5\uFF09",
1000
+ "",
1001
+ "\u4F1A\u6539\u52A8\u7684\u6587\u4EF6\uFF1A~/.mu/agent \u91CC\u7684 auth.json\uFF08\u5BC6\u94A5\uFF09\u3001models.json\u3001settings.json\u3001mu.json\uFF0C",
1002
+ "\u4EE5\u53CA mu \u8BFB\u53D6\u7684 .env\uFF08Jev \u5BC6\u94A5\uFF09\u3002\u6BCF\u4E2A\u6587\u4EF6\u6539\u52A8\u524D\u90FD\u4F1A\u5148\u590D\u5236\u5230 ~/.mu/backups/<\u65F6\u95F4>/\u3002"
1003
+ ].join("\n");
1004
+ }
1005
+ return [
1006
+ "mu setup: connect a model for mu to work with, and choose its judge.",
1007
+ "",
1008
+ " mu setup asks step by step, in a terminal",
1009
+ " mu setup <key> starts from an API key: mu tells by its shape which service it belongs to",
1010
+ " mu setup --service <id> --key-stdin [--model <id>] [--base-url <url>] [--judge jev|model|none] [--yes]",
1011
+ " for scripts: the key is read from stdin",
1012
+ " mu setup --list the services mu knows, with their ids",
1013
+ "",
1014
+ "Why stdin for scripts: a key on the command line stays in your shell's history, and every program on this",
1015
+ "computer that lists processes can read it. `mu setup <key>` is there for a quick start; paste the key at the",
1016
+ "prompt when you can.",
1017
+ "",
1018
+ " --judge jev the Jev key (TypeSafe) is the second line of stdin, unless TYPESAFE_API_KEY is set already",
1019
+ " --judge model the model just set up answers as the judge (slower, and every decision costs tokens)",
1020
+ " --judge none leaves the judge as it is, as leaving out --judge does",
1021
+ " --api, --name for the service `other`: its API (openai or anthropic) and its name in mu",
1022
+ " --yes no questions; replaces what is set up already (a subscription sign-in, a provider's",
1023
+ " address, the Jev key)",
1024
+ "",
1025
+ "Files: auth.json (keys), models.json, settings.json and mu.json in ~/.mu/agent, and the .env mu reads",
1026
+ "(the Jev key). Each is copied to ~/.mu/backups/<time>/ before it changes."
1027
+ ].join("\n");
1028
+ }
1029
+ function label(service, language) {
1030
+ return service.id === "other" ? OTHER[language] : service.name;
1031
+ }
1032
+ function serviceList(language) {
1033
+ const width = Math.max(...SETUP_SERVICES.map((service) => service.id.length));
1034
+ return SETUP_SERVICES.map((service) => ` ${service.id.padEnd(width)} ${label(service, language)}`).join("\n");
1035
+ }
1036
+ function providerIdFor(baseUrl) {
1037
+ let host;
1038
+ try {
1039
+ host = new URL(baseUrl).host.toLowerCase();
1040
+ } catch {
1041
+ return "custom";
1042
+ }
1043
+ const [name, port] = host.split(/:(?=\d+$)/);
1044
+ if (name === "localhost" || /^\d+(\.\d+){3}$/.test(name) || name.startsWith("[")) {
1045
+ return port ? `local-${port}` : "local";
1046
+ }
1047
+ const parts = name.split(".").filter((part) => part !== "api" && part !== "www");
1048
+ const main2 = parts.length >= 2 ? parts[parts.length - 2] : parts[0] ?? "";
1049
+ return main2.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "custom";
1050
+ }
1051
+ function hostOf2(url) {
1052
+ try {
1053
+ return new URL(url).host;
1054
+ } catch {
1055
+ return url;
1056
+ }
1057
+ }
1058
+ function tilde(path, env) {
1059
+ const home2 = env.HOME || env.USERPROFILE;
1060
+ return home2 && path.startsWith(home2) ? `~${path.slice(home2.length)}` : path;
1061
+ }
1062
+ function jevKeyAt(env, envFileText) {
1063
+ for (const name of JEV_KEY_NAMES) {
1064
+ if (name in env) {
1065
+ if (env[name]) return { name, where: "env" };
1066
+ continue;
1067
+ }
1068
+ if (envFileText !== void 0 && envFileValue(envFileText, name)) return { name, where: "file" };
1069
+ }
1070
+ return void 0;
1071
+ }
1072
+ var Stop = class extends Error {
1073
+ constructor(code) {
1074
+ super(`mu setup stopped (${code})`);
1075
+ this.code = code;
1076
+ }
1077
+ };
1078
+ var Setup = class {
1079
+ constructor(args, deps) {
1080
+ this.args = args;
1081
+ this.deps = deps;
1082
+ this.io = deps.io;
1083
+ this.language = deps.language;
1084
+ this.scripted = args.keyStdin || args.yes || !deps.interactive;
1085
+ }
1086
+ async run() {
1087
+ try {
1088
+ return await this.steps();
1089
+ } catch (error) {
1090
+ if (error instanceof Stop) return error.code;
1091
+ throw error;
1092
+ }
1093
+ }
1094
+ t(text) {
1095
+ return text[this.language];
1096
+ }
1097
+ /** A service in a sentence: `other` by the name it was given, once it has one ("box does not accept this key"). */
1098
+ name(service) {
1099
+ return service.id === "other" && this.customName ? this.customName : label(service, this.language);
1100
+ }
1101
+ names(services) {
1102
+ return services.map((service) => this.name(service)).join(this.language === "zh" ? " \u548C " : " and ");
1103
+ }
1104
+ path(path) {
1105
+ return tilde(path, this.deps.env);
1106
+ }
1107
+ cancelled() {
1108
+ this.io.say(this.t({ zh: "\u5DF2\u505C\u6B62\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u6709\u6539\u3002", en: "Stopped. Nothing was changed." }));
1109
+ return new Stop(SETUP_EXIT.cancelled);
1110
+ }
1111
+ /** A run that cannot go on, with the reason on stderr. */
1112
+ fail(text, code = SETUP_EXIT.failed) {
1113
+ this.io.warn(`mu setup: ${this.t(text)}`);
1114
+ return new Stop(code);
1115
+ }
1116
+ async ask(question) {
1117
+ const answer = await this.io.ask(question);
1118
+ if (answer === void 0) throw this.cancelled();
1119
+ return answer.trim();
1120
+ }
1121
+ /** A key, typed where it does not show; asked again while it is empty. */
1122
+ async secret(question, allowEmpty = false) {
1123
+ for (; ; ) {
1124
+ const answer = await this.io.secret(question);
1125
+ if (answer === void 0) throw this.cancelled();
1126
+ const key = cleanKey(answer);
1127
+ if (key || allowEmpty) return key;
1128
+ }
1129
+ }
1130
+ pasteKey() {
1131
+ return this.secret(
1132
+ this.t({ zh: "\u7C98\u8D34 API \u5BC6\u94A5\uFF08\u4E0D\u4F1A\u663E\u793A\u51FA\u6765\uFF09\uFF1A", en: "Paste the API key (it is not shown): " })
1133
+ );
1134
+ }
1135
+ async choose(question, choices) {
1136
+ this.io.say(question);
1137
+ for (const [index, choice] of choices.entries()) this.io.say(` ${index + 1}) ${choice.label}`);
1138
+ const range = `1-${choices.length}`;
1139
+ for (; ; ) {
1140
+ const answer = await this.ask(this.t({ zh: `\u8BF7\u9009\u62E9 ${range} [1]\uFF1A`, en: `Choose ${range} [1]: ` }));
1141
+ if (answer === "") return choices[0].value;
1142
+ const number = Number(answer);
1143
+ if (Number.isInteger(number) && number >= 1 && number <= choices.length) return choices[number - 1].value;
1144
+ this.io.say(this.t({ zh: `\u8BF7\u8F93\u5165 ${range} \u4E4B\u95F4\u7684\u6570\u5B57\u3002`, en: `Type a number from ${range}.` }));
1145
+ }
1146
+ }
1147
+ async confirm(question, yes) {
1148
+ for (; ; ) {
1149
+ const answer = (await this.ask(question)).toLowerCase();
1150
+ if (answer === "") return yes;
1151
+ if (["y", "yes", "\u662F", "\u597D", "\u8981"].includes(answer)) return true;
1152
+ if (["n", "no", "\u5426", "\u4E0D", "\u4E0D\u8981"].includes(answer)) return false;
1153
+ }
1154
+ }
1155
+ async steps() {
1156
+ const { args, deps } = this;
1157
+ if (args.firstRun && !await this.firstStart()) return SETUP_EXIT.done;
1158
+ let key = deps.givenKey ?? args.words[0];
1159
+ if (args.keyStdin) {
1160
+ if (deps.stdinTerminal) {
1161
+ throw this.fail(
1162
+ {
1163
+ zh: "--key-stdin \u4ECE\u7BA1\u9053\u8BFB\u5BC6\u94A5\u3002\u5728\u7EC8\u7AEF\u91CC\u8BF7\u76F4\u63A5\u8FD0\u884C mu setup\uFF0C\u5728\u63D0\u793A\u91CC\u7C98\u8D34\u5BC6\u94A5\uFF08\u4E0D\u4F1A\u663E\u793A\uFF09\u3002",
1164
+ en: "--key-stdin reads the key from a pipe. In a terminal, run mu setup and paste the key at its prompt (it is not shown)."
1165
+ },
1166
+ SETUP_EXIT.usage
1167
+ );
1168
+ }
1169
+ const [modelKey, jevKey] = (await deps.readStdin()).split(/\r?\n/).map((line) => cleanKey(line)).filter(Boolean);
1170
+ key = modelKey;
1171
+ this.jevFromStdin = jevKey;
1172
+ }
1173
+ if (this.scripted && key === void 0 && args.service === void 0) {
1174
+ throw this.fail(
1175
+ deps.interactive ? { zh: "\u6CA1\u6709\u5BC6\u94A5\uFF0C\u4E5F\u6CA1\u6709 --service", en: "no key and no --service were given" } : {
1176
+ zh: "\u95EE\u9898\u8981\u5728\u7EC8\u7AEF\u91CC\u56DE\u7B54\u3002\u811A\u672C\u91CC\u7528\uFF1Amu setup --service <id> --key-stdin\uFF08\u89C1 mu setup --help\uFF09",
1177
+ en: "its questions need a terminal. For a script: mu setup --service <id> --key-stdin (see mu setup --help)"
1178
+ },
1179
+ SETUP_EXIT.usage
1180
+ );
1181
+ }
1182
+ if (!args.firstRun) {
1183
+ this.io.say(
1184
+ this.t({
1185
+ zh: "mu setup\uFF1A\u7ED9 mu \u63A5\u4E0A\u4E00\u4E2A\u5E72\u6D3B\u7684\u6A21\u578B\uFF0C\u518D\u9009\u4E00\u4E2A\u5224\u5B9A\u5668\u3002",
1186
+ en: "mu setup: connect a model for mu to work with, and choose its judge."
1187
+ })
1188
+ );
1189
+ }
1190
+ const start = await this.start(key === void 0 ? void 0 : cleanKey(key));
1191
+ if (start === "subscription") {
1192
+ const code2 = await this.write(void 0, void 0, await this.judge(void 0));
1193
+ return code2 === SETUP_EXIT.done ? this.offerSession() : code2;
1194
+ }
1195
+ const target = await this.checked(start.service, start.key);
1196
+ this.modelKey = target.key;
1197
+ const model = await this.model(target);
1198
+ await this.conflicts(target);
1199
+ const judge = await this.judge({ provider: target.reach.provider, model });
1200
+ const code = await this.write(target, model, judge);
1201
+ return code === SETUP_EXIT.done ? this.offerSession() : code;
1202
+ }
1203
+ /** The launcher's question on a first start. A no is remembered: the question is never asked again. */
1204
+ async firstStart() {
1205
+ const yes = await this.confirm(
1206
+ this.t({
1207
+ zh: "mu \u8FD8\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u3002\u73B0\u5728\u8BBE\u7F6E\u4E00\u4E2A\u5417\uFF1F[Y/n] ",
1208
+ en: "mu has no model to work with yet. Set one up now? [Y/n] "
1209
+ }),
1210
+ true
1211
+ );
1212
+ if (yes) return true;
1213
+ try {
1214
+ mkdirSync3(this.deps.paths.agentDir, { recursive: true });
1215
+ writeFileSync2(
1216
+ join3(this.deps.paths.agentDir, SETUP_DECLINED_FILE),
1217
+ "mu asked on its first start whether to set up a model, and the answer was no.\nDelete this file to be asked again; run mu setup any time.\n"
1218
+ );
1219
+ } catch {
1220
+ }
1221
+ this.io.say(
1222
+ this.t({
1223
+ zh: "\u597D\u7684\uFF0C\u4EE5\u540E\u4E0D\u518D\u95EE\u4E86\u3002\u9700\u8981\u65F6\u8FD0\u884C mu setup\uFF0C\u6216\u5728 mu \u91CC\u8F93\u5165 /login\u3002",
1224
+ en: "OK, mu will not ask again. Run mu setup when you want, or type /login inside mu."
1225
+ })
1226
+ );
1227
+ return false;
1228
+ }
1229
+ // -----------------------------------------------------------------------------------------------------------
1230
+ // Steps 1 and 2: how a model is reached, and which service a key belongs to
1231
+ // -----------------------------------------------------------------------------------------------------------
1232
+ async start(key) {
1233
+ const named2 = this.args.service === void 0 ? void 0 : serviceById(this.args.service);
1234
+ if (named2) return this.keyFor(named2, key);
1235
+ if (key !== void 0) return { service: await this.serviceOf(key), key };
1236
+ const route = await this.choose(this.t({ zh: "mu \u7528\u4EC0\u4E48\u65B9\u5F0F\u8FDE\u63A5\u6A21\u578B\uFF1F", en: "How should mu reach a model?" }), [
1237
+ {
1238
+ label: this.t({
1239
+ zh: "\u7C98\u8D34 API \u5BC6\u94A5\uFF08mu \u4F1A\u8BA4\u51FA\u5B83\u5C5E\u4E8E\u54EA\u4E2A\u670D\u52A1\uFF09",
1240
+ en: "Paste an API key (mu tells which service it belongs to)"
1241
+ }),
1242
+ value: "key"
1243
+ },
1244
+ { label: this.t({ zh: "\u4ECE\u5217\u8868\u91CC\u9009\u4E00\u4E2A\u670D\u52A1", en: "Choose a service from the list" }), value: "list" },
1245
+ {
1246
+ label: this.t({
1247
+ zh: "\u7528\u8BA2\u9605\u8D26\u53F7\u767B\u5F55\uFF1AChatGPT\u3001Claude\u3001Grok \u6216 Google",
1248
+ en: "Sign in with a subscription: ChatGPT, Claude, Grok or Google"
1249
+ }),
1250
+ value: "subscription"
1251
+ }
1252
+ ]);
1253
+ if (route === "subscription") {
1254
+ const auth = this.path(join3(this.deps.paths.agentDir, "auth.json"));
1255
+ this.io.say(
1256
+ this.t({
1257
+ zh: `\u8BA2\u9605\u8D26\u53F7\u5728 mu \u91CC\u9762\u767B\u5F55\uFF1A\u542F\u52A8 mu\uFF0C\u8F93\u5165 /login\uFF0C\u9009 ChatGPT\uFF08OpenAI Codex\uFF09\u3001Claude\u3001Grok \u6216 Google\uFF0C\u6309\u63D0\u793A\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u3002\u767B\u5F55\u4FE1\u606F\u4FDD\u5B58\u5728 ${auth}\u3002`,
1258
+ en: `A subscription signs in inside mu: start mu, type /login, choose ChatGPT (OpenAI Codex), Claude, Grok or Google, and finish in the browser. The sign-in is kept in ${auth}.`
1259
+ })
1260
+ );
1261
+ return "subscription";
1262
+ }
1263
+ if (route === "list") return this.keyFor(await this.pickService(SETUP_SERVICES), void 0);
1264
+ const pasted = await this.pasteKey();
1265
+ return { service: await this.serviceOf(pasted), key: pasted };
1266
+ }
1267
+ pickService(services, question) {
1268
+ return this.choose(
1269
+ question ?? this.t({ zh: "\u9009\u54EA\u4E2A\u670D\u52A1\uFF1F", en: "Which service?" }),
1270
+ services.map((service) => ({ label: label(service, this.language), value: service }))
1271
+ );
1272
+ }
1273
+ /**
1274
+ * The service a key belongs to, by its shape. When the shape fits several, or none, the person says which: a key is
1275
+ * never tried on one vendor after another.
1276
+ */
1277
+ async serviceOf(key) {
1278
+ const matches = servicesForKey(key);
1279
+ if (matches.length === 1) {
1280
+ this.io.say(
1281
+ this.t({
1282
+ zh: `\u8FD9\u662F ${this.name(matches[0])} \u7684\u5BC6\u94A5\u3002`,
1283
+ en: `This is ${article(this.name(matches[0]))} key.`
1284
+ })
1285
+ );
1286
+ return matches[0];
1287
+ }
1288
+ if (this.scripted) {
1289
+ throw this.fail(
1290
+ matches.length > 1 ? {
1291
+ zh: `${this.names(matches)} \u7684\u5BC6\u94A5\u957F\u5F97\u4E00\u6837\u3002\u7528 --service \u8BF4\u660E\u662F\u54EA\u5BB6\uFF1A${matches.map((service) => service.id).join(" \u6216 ")}`,
1292
+ en: `keys of ${this.names(matches)} look the same. Say which with --service ${matches.map((service) => service.id).join(" or ")}`
1293
+ } : {
1294
+ zh: "\u8BA4\u4E0D\u51FA\u8FD9\u4E2A\u5BC6\u94A5\u5C5E\u4E8E\u54EA\u4E2A\u670D\u52A1\u3002\u7528 --service <id> \u8BF4\u660E\uFF08mu setup --list \u5217\u51FA\u5168\u90E8\uFF09",
1295
+ en: "this key's shape names no service mu knows. Say which with --service <id> (mu setup --list)"
1296
+ },
1297
+ SETUP_EXIT.usage
1298
+ );
1299
+ }
1300
+ if (matches.length > 1) {
1301
+ return this.pickService(
1302
+ matches,
1303
+ this.t({
1304
+ zh: `${this.names(matches)} \u7684\u5BC6\u94A5\u957F\u5F97\u4E00\u6837\u3002\u8FD9\u4E2A\u5BC6\u94A5\u662F\u54EA\u5BB6\u7684\uFF1Fmu \u53EA\u628A\u5BC6\u94A5\u53D1\u7ED9\u5B83\u6240\u5C5E\u7684\u670D\u52A1\u3002`,
1305
+ en: `Keys of ${this.names(matches)} look the same. Which service is this key for? mu sends a key only to the service it belongs to.`
1306
+ })
1307
+ );
1308
+ }
1309
+ return this.pickService(
1310
+ SETUP_SERVICES.filter((service) => !service.keyless),
1311
+ this.t({
1312
+ zh: "mu \u8BA4\u4E0D\u51FA\u8FD9\u4E2A\u5BC6\u94A5\u3002\u5B83\u662F\u54EA\u4E2A\u670D\u52A1\u7684\uFF1F",
1313
+ en: "mu does not recognise this key. Which service is it for?"
1314
+ })
1315
+ );
1316
+ }
1317
+ /** The key for a service that was named: asked for when there is none yet, and held against the service's shapes. */
1318
+ async keyFor(service, given) {
1319
+ if (service.keyless) return { service };
1320
+ if (service.keyOptional && given === void 0) return { service };
1321
+ let key = given;
1322
+ if (key === void 0 && !this.scripted) {
1323
+ if (service.keyPage) {
1324
+ this.io.say(
1325
+ this.t({ zh: `\u5BC6\u94A5\u5728\u8FD9\u91CC\u521B\u5EFA\uFF1A${service.keyPage}`, en: `Keys are made at ${service.keyPage}` })
1326
+ );
1327
+ }
1328
+ key = await this.pasteKey();
1329
+ }
1330
+ if (!key) {
1331
+ if (service.keyOptional) return { service };
1332
+ throw this.fail(
1333
+ { zh: `${this.name(service)} \u9700\u8981\u4E00\u4E2A API \u5BC6\u94A5`, en: `${this.name(service)} needs an API key` },
1334
+ SETUP_EXIT.usage
1335
+ );
1336
+ }
1337
+ const matches = servicesForKey(key);
1338
+ const ownAddress = service.id === "other" || this.args.baseUrl !== void 0;
1339
+ if (matches.length === 0 || matches.includes(service) || ownAddress) return { service, key };
1340
+ if (this.scripted) {
1341
+ this.io.warn(
1342
+ this.t({
1343
+ zh: `\u6CE8\u610F\uFF1A\u8FD9\u4E2A\u5BC6\u94A5\u770B\u8D77\u6765\u662F ${this.names(matches)} \u7684\uFF1B\u6309 --service \u53D1\u7ED9 ${this.name(service)}\u3002`,
1344
+ en: `Note: this key looks like ${article(this.names(matches))} key; it goes to ${this.name(service)}, as --service says.`
1345
+ })
1346
+ );
1347
+ return { service, key };
1348
+ }
1349
+ const chosen = await this.pickService(
1350
+ [service, ...matches],
1351
+ this.t({
1352
+ zh: `\u8FD9\u4E2A\u5BC6\u94A5\u770B\u8D77\u6765\u662F ${this.names(matches)} \u7684\uFF0C\u4E0D\u50CF ${this.name(service)} \u7684\u3002\u5B83\u662F\u54EA\u5BB6\u7684\uFF1F`,
1353
+ en: `This key looks like ${article(this.names(matches))} key, not ${article(this.name(service))} one. Which service is it for?`
1354
+ })
1355
+ );
1356
+ return { service: chosen, key };
1357
+ }
1358
+ // -----------------------------------------------------------------------------------------------------------
1359
+ // Step 3: one request to the service
1360
+ // -----------------------------------------------------------------------------------------------------------
1361
+ /** The addresses to try, in order: the service's own (where pi has them), or the one typed for `other`. */
1362
+ async reaches(service) {
1363
+ const { args, deps } = this;
1364
+ if (service.id !== "other") {
1365
+ const own = service.baseUrls.map((baseUrl2, index) => ({
1366
+ provider: service.providers[index],
1367
+ builtIn: service.builtIn,
1368
+ // pi's own address, with a models.json override: the key goes where pi will send it anyway.
1369
+ baseUrl: service.builtIn && deps.pi.baseUrl(service.providers[index], service.api) || baseUrl2,
1370
+ api: service.api
1371
+ }));
1372
+ return args.baseUrl ? [{ ...own[0], baseUrl: args.baseUrl.replace(/\/+$/, "") }] : own;
1373
+ }
1374
+ let baseUrl = args.baseUrl;
1375
+ if (!baseUrl) {
1376
+ if (this.scripted) {
1377
+ throw this.fail(
1378
+ { zh: "\u670D\u52A1 other \u9700\u8981 --base-url", en: "--service other needs --base-url" },
1379
+ SETUP_EXIT.usage
1380
+ );
1381
+ }
1382
+ for (; ; ) {
1383
+ baseUrl = await this.ask(
1384
+ this.t({
1385
+ zh: "\u670D\u52A1\u7684\u5730\u5740\uFF08base URL\uFF09\uFF0C\u4F8B\u5982 https://api.example.com/v1\uFF1A",
1386
+ en: "The service's address (base URL), e.g. https://api.example.com/v1: "
1387
+ })
1388
+ );
1389
+ if (/^https?:\/\/[^\s/]+/i.test(baseUrl)) break;
1390
+ this.io.say(
1391
+ this.t({ zh: "\u5730\u5740\u4EE5 http:// \u6216 https:// \u5F00\u5934\u3002", en: "The address starts with http:// or https://." })
1392
+ );
1393
+ }
1394
+ }
1395
+ const api = args.api === "anthropic" ? "anthropic-messages" : args.api === "openai" || this.scripted ? "openai-completions" : await this.choose(this.t({ zh: "\u5B83\u7528\u54EA\u79CD\u63A5\u53E3\uFF1F", en: "Which API does it speak?" }), [
1396
+ {
1397
+ label: this.t({
1398
+ zh: "OpenAI \u517C\u5BB9\uFF08chat completions\uFF09",
1399
+ en: "OpenAI-compatible (chat completions)"
1400
+ }),
1401
+ value: "openai-completions"
1402
+ },
1403
+ {
1404
+ label: this.t({ zh: "Anthropic \u517C\u5BB9\uFF08messages\uFF09", en: "Anthropic-compatible (messages)" }),
1405
+ value: "anthropic-messages"
1406
+ }
1407
+ ]);
1408
+ let base = baseUrl.replace(/\/+$/, "");
1409
+ if (api === "anthropic-messages") base = base.replace(/\/v1$/, "");
1410
+ let provider = args.name ?? providerIdFor(base);
1411
+ if (!args.name && deps.pi.providerExists(provider) && !this.inModelsFile(provider))
1412
+ provider = `${provider}-custom`;
1413
+ if (!args.name && !this.scripted) {
1414
+ const typed = await this.ask(
1415
+ this.t({
1416
+ zh: `\u7ED9\u5B83\u8D77\u4E2A\u7B80\u77ED\u7684\u540D\u5B57\uFF08\u5B57\u6BCD\u3001\u6570\u5B57\u3001-\uFF09[${provider}]\uFF1A`,
1417
+ en: `A short name for it in mu (letters, digits, -) [${provider}]: `
1418
+ })
1419
+ );
1420
+ if (typed) provider = typed.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
1421
+ }
1422
+ this.customName = provider;
1423
+ return [{ provider, builtIn: false, baseUrl: base, api }];
1424
+ }
1425
+ inModelsFile(provider) {
1426
+ const models = readJsonFile(join3(this.deps.paths.agentDir, "models.json"));
1427
+ return models.state === "ok" && existingProvider(models.value, provider) !== void 0;
1428
+ }
1429
+ /** A failure as the person can act on it: a local service that is silent is not ready yet, whatever the key. */
1430
+ explain(service, problem) {
1431
+ if (service.keyless && ["connection", "timeout", "dns"].includes(problem.kind)) return this.t(NOT_READY);
1432
+ return explainProblem(
1433
+ problem,
1434
+ { service: this.name(service), proxy: this.deps.proxy, timeoutMs: this.deps.timeoutMs },
1435
+ this.language
1436
+ );
1437
+ }
1438
+ /** The key, checked: asked again, retried, or taken unchecked until the person has one that works, or stops. */
1439
+ async checked(service, firstKey) {
1440
+ let key = firstKey;
1441
+ const reaches = await this.reaches(service);
1442
+ if (service.keyOptional && key === void 0 && !this.scripted) {
1443
+ key = await this.secret(
1444
+ this.t({
1445
+ zh: "\u7C98\u8D34\u5B83\u7684 API \u5BC6\u94A5\uFF1B\u4E0D\u9700\u8981\u5BC6\u94A5\u5C31\u76F4\u63A5\u56DE\u8F66\uFF08\u4E0D\u4F1A\u663E\u793A\uFF09\uFF1A",
1446
+ en: "Paste its API key, or press Enter if it takes none (not shown): "
1447
+ }),
1448
+ true
1449
+ ) || void 0;
1450
+ }
1451
+ if (!reaches[0].builtIn || this.args.baseUrl) this.modelsFileWritable();
1452
+ await this.oauthConflict(service, reaches);
1453
+ for (; ; ) {
1454
+ const result = await this.tryReaches(service, reaches, key);
1455
+ if (result.ok) return { service, reach: result.reach, key, listed: result.listed };
1456
+ const said = this.explain(service, result.problem);
1457
+ if (this.scripted) throw this.fail({ zh: said, en: said });
1458
+ this.io.say(said);
1459
+ const choices = [];
1460
+ if (!service.keyless)
1461
+ choices.push({ label: this.t({ zh: "\u6362\u4E00\u4E2A\u5BC6\u94A5", en: "Paste another key" }), value: "key" });
1462
+ choices.push({ label: this.t({ zh: "\u518D\u8BD5\u4E00\u6B21", en: "Try again" }), value: "again" });
1463
+ if (result.problem.kind !== "key") {
1464
+ choices.push({
1465
+ label: this.t({ zh: "\u4E0D\u68C0\u67E5\u4E86\uFF0C\u7167\u6837\u4FDD\u5B58", en: "Save it without the check" }),
1466
+ value: "unchecked"
1467
+ });
1468
+ }
1469
+ choices.push({
1470
+ label: this.t({ zh: "\u5230\u6B64\u4E3A\u6B62\uFF08\u4EC0\u4E48\u90FD\u4E0D\u6539\uFF09", en: "Stop here (nothing is changed)" }),
1471
+ value: "stop"
1472
+ });
1473
+ const next = await this.choose(this.t({ zh: "\u63A5\u4E0B\u6765\u600E\u4E48\u529E\uFF1F", en: "What now?" }), choices);
1474
+ if (next === "stop") throw this.cancelled();
1475
+ if (next === "unchecked") return { service, reach: reaches[0], key };
1476
+ if (next === "key") key = await this.pasteKey();
1477
+ }
1478
+ }
1479
+ /** Each address of the service in turn: Kimi's .cn, then .ai; an address of one's own, then with /v1 added. */
1480
+ async tryReaches(service, reaches, key) {
1481
+ let first;
1482
+ const candidates = [...reaches];
1483
+ for (let index = 0; index < candidates.length; index++) {
1484
+ const reach = candidates[index];
1485
+ const where = `${this.name(service)}\uFF08${hostOf2(reach.baseUrl)}\uFF09`;
1486
+ this.io.say(
1487
+ key ? this.t({
1488
+ zh: `\u6B63\u5728\u7528 ${where}\u68C0\u67E5\u5BC6\u94A5\u2026\u2026`,
1489
+ en: `Checking the key with ${this.name(service)} (${hostOf2(reach.baseUrl)})...`
1490
+ }) : this.t({
1491
+ zh: `\u6B63\u5728\u68C0\u67E5 ${where}\u2026\u2026`,
1492
+ en: `Checking ${this.name(service)} (${hostOf2(reach.baseUrl)})...`
1493
+ })
1494
+ );
1495
+ const result = await checkKey({
1496
+ kind: checkKindOf(service, reach.api),
1497
+ baseUrl: reach.baseUrl,
1498
+ key,
1499
+ fetch: this.deps.fetch,
1500
+ timeoutMs: this.deps.timeoutMs ?? CHECK_TIMEOUT_MS
1501
+ });
1502
+ if (result.ok) {
1503
+ const count = result.models?.length;
1504
+ this.io.say(
1505
+ count === void 0 ? this.t({ zh: "\u5BC6\u94A5\u53EF\u4EE5\u7528\u3002", en: "The key works." }) : this.t({
1506
+ zh: `\u53EF\u4EE5\u7528\uFF1A\u5217\u51FA\u4E86 ${count} \u4E2A\u6A21\u578B\u3002`,
1507
+ en: `It works: ${count} model${count === 1 ? "" : "s"} listed.`
1508
+ })
1509
+ );
1510
+ return { ok: true, reach, listed: result.models };
1511
+ }
1512
+ if (result.problem.kind === "permission") {
1513
+ this.io.say(this.explain(service, result.problem));
1514
+ return { ok: true, reach };
1515
+ }
1516
+ first ??= result.problem;
1517
+ if (service.id === "other" && result.problem.kind === "address" && reach.api === "openai-completions" && !reach.baseUrl.endsWith("/v1") && index === candidates.length - 1) {
1518
+ candidates.push({ ...reach, baseUrl: `${reach.baseUrl}/v1` });
1519
+ }
1520
+ }
1521
+ return { ok: false, problem: first ?? { kind: "other", url: reaches[0]?.baseUrl ?? "" } };
1522
+ }
1523
+ /** A subscription sign-in is replaced by a key only with a yes. */
1524
+ async oauthConflict(service, reaches) {
1525
+ for (const reach of reaches) {
1526
+ if (await this.deps.pi.credential(reach.provider) !== "oauth") continue;
1527
+ if (this.args.yes) return;
1528
+ if (this.scripted) {
1529
+ throw this.fail({
1530
+ zh: `${this.name(service)} \u5DF2\u7ECF\u7528\u8BA2\u9605\u8D26\u53F7\u767B\u5F55\u4E86\u3002\u8981\u6539\u5B58\u5BC6\u94A5\uFF0C\u52A0\u4E0A --yes`,
1531
+ en: `${this.name(service)} is signed in with a subscription. To store a key instead, add --yes`
1532
+ });
1533
+ }
1534
+ const replace = await this.confirm(
1535
+ this.t({
1536
+ zh: `\u4F60\u5DF2\u7ECF\u7528\u8BA2\u9605\u8D26\u53F7\u767B\u5F55\u4E86 ${this.name(service)}\u3002\u6539\u7528\u8FD9\u4E2A\u5BC6\u94A5\u5417\uFF1F\u767B\u5F55\u4F1A\u88AB\u66FF\u6362\uFF08\u7559\u6709\u5907\u4EFD\uFF09\u3002[y/N] `,
1537
+ en: `You are signed in to ${this.name(service)} with a subscription. Use this key instead? The sign-in is replaced (a copy is kept). [y/N] `
1538
+ }),
1539
+ false
1540
+ );
1541
+ if (!replace) throw this.cancelled();
1542
+ return;
1543
+ }
1544
+ }
1545
+ modelsFileWritable() {
1546
+ const path = join3(this.deps.paths.agentDir, "models.json");
1547
+ if (readJsonFile(path).state !== "unreadable") return;
1548
+ throw this.fail({
1549
+ zh: `${this.path(path)} \u4E0D\u662F\u7EAF JSON\uFF08\u4E5F\u8BB8\u6709\u6CE8\u91CA\u6216\u5199\u9519\u4E86\uFF09\uFF0Cmu setup \u4E0D\u6539\u5199\u5B83\u3002\u5148\u4FEE\u597D\u5B83\uFF0C\u6216\u624B\u52A8\u628A\u670D\u52A1\u52A0\u8FDB\u53BB\u3002`,
1550
+ en: `${this.path(path)} is not plain JSON (it may have comments, or a typo), and mu setup does not rewrite it. Fix it, or add the service by hand.`
1551
+ });
1552
+ }
1553
+ // -----------------------------------------------------------------------------------------------------------
1554
+ // Step 4: the model to start with
1555
+ // -----------------------------------------------------------------------------------------------------------
1556
+ async model(target) {
1557
+ const { service, listed } = target;
1558
+ if (this.args.model) {
1559
+ if (listed && listed.length > 0 && !listed.includes(this.args.model)) {
1560
+ this.io.say(
1561
+ this.t({
1562
+ zh: `\u6CE8\u610F\uFF1A${this.name(service)} \u6CA1\u6709\u5217\u51FA ${this.args.model}\uFF0C\u7167\u6837\u4F7F\u7528\u3002`,
1563
+ en: `Note: ${this.name(service)} does not list ${this.args.model}; it is used anyway.`
1564
+ })
1565
+ );
1566
+ }
1567
+ return this.args.model;
1568
+ }
1569
+ const startWith = this.t({ zh: "\u5148\u7528\u8FD9\u4E2A\u6A21\u578B\uFF1A", en: "Model to start with: " });
1570
+ const chosen = listed && listed.length > 0 ? tableModel(service, listed) : namedModel(service);
1571
+ if (chosen) {
1572
+ this.io.say(`${startWith}${chosen}`);
1573
+ return chosen;
1574
+ }
1575
+ const options = chatModels(listed ?? []);
1576
+ if (options.length === 0) {
1577
+ const none = service.keyless ? NOT_READY : {
1578
+ zh: `${this.name(service)} \u6CA1\u6709\u5217\u51FA\u6A21\u578B\u3002`,
1579
+ en: `${this.name(service)} lists no models.`
1580
+ };
1581
+ if (this.scripted) {
1582
+ throw this.fail({ zh: `${none.zh}\u53EF\u4EE5\u7528 --model \u6307\u5B9A\u4E00\u4E2A\u3002`, en: `${none.en} Name one with --model.` });
1583
+ }
1584
+ this.io.say(this.t(none));
1585
+ for (; ; ) {
1586
+ const typed = await this.ask(this.t({ zh: "\u8981\u7528\u7684\u6A21\u578B\u540D\uFF1A", en: "The name of the model to use: " }));
1587
+ if (typed) return typed;
1588
+ }
1589
+ }
1590
+ if (this.scripted) {
1591
+ this.io.say(`${startWith}${options[0]}`);
1592
+ return options[0];
1593
+ }
1594
+ const shown = options.slice(0, 20);
1595
+ this.io.say(this.t({ zh: "mu \u5148\u7528\u54EA\u4E2A\u6A21\u578B\uFF1F", en: "Which model should mu start with?" }));
1596
+ for (const [index, id] of shown.entries()) this.io.say(` ${index + 1}) ${id}`);
1597
+ if (options.length > shown.length) {
1598
+ const more = options.length - shown.length;
1599
+ this.io.say(
1600
+ this.t({
1601
+ zh: ` \uFF08\u8FD8\u6709 ${more} \u4E2A\uFF0C\u8F93\u5165\u540D\u5B57\u4E5F\u53EF\u4EE5\uFF09`,
1602
+ en: ` (${more} more: type a name to use one of them)`
1603
+ })
1604
+ );
1605
+ }
1606
+ for (; ; ) {
1607
+ const answer = await this.ask(
1608
+ this.t({
1609
+ zh: `\u8BF7\u9009\u62E9 1-${shown.length}\uFF0C\u6216\u8F93\u5165\u6A21\u578B\u540D [1]\uFF1A`,
1610
+ en: `Choose 1-${shown.length}, or type a model name [1]: `
1611
+ })
1612
+ );
1613
+ if (answer === "") return shown[0];
1614
+ const number = Number(answer);
1615
+ if (Number.isInteger(number) && number >= 1 && number <= shown.length) return shown[number - 1];
1616
+ if (options.includes(answer) || !/^\d+$/.test(answer)) return answer;
1617
+ this.io.say(
1618
+ this.t({
1619
+ zh: `\u8BF7\u8F93\u5165 1-${shown.length} \u4E4B\u95F4\u7684\u6570\u5B57\uFF0C\u6216\u6A21\u578B\u540D\u3002`,
1620
+ en: `Type a number from 1-${shown.length}, or a model name.`
1621
+ })
1622
+ );
1623
+ }
1624
+ }
1625
+ /** A provider models.json has at another address is moved only with a yes. */
1626
+ async conflicts(target) {
1627
+ if (target.reach.builtIn && !this.args.baseUrl) return;
1628
+ const models = readJsonFile(join3(this.deps.paths.agentDir, "models.json"));
1629
+ if (models.state !== "ok") return;
1630
+ const current = existingProvider(models.value, target.reach.provider)?.baseUrl;
1631
+ const old = typeof current === "string" ? current.replace(/\/+$/, "") : void 0;
1632
+ if (!old || old === target.reach.baseUrl || this.args.yes) return;
1633
+ if (this.scripted) {
1634
+ throw this.fail({
1635
+ zh: `models.json \u91CC\u7684 ${target.reach.provider} \u6307\u5411 ${old}\u3002\u8981\u6539\u6210 ${target.reach.baseUrl}\uFF0C\u52A0\u4E0A --yes`,
1636
+ en: `models.json has ${target.reach.provider} at ${old}. To point it at ${target.reach.baseUrl}, add --yes`
1637
+ });
1638
+ }
1639
+ const move = await this.confirm(
1640
+ this.t({
1641
+ zh: `models.json \u91CC\u5DF2\u7ECF\u6709 ${target.reach.provider}\uFF0C\u5730\u5740\u662F ${old}\u3002\u6539\u6210 ${target.reach.baseUrl} \u5417\uFF1F[y/N] `,
1642
+ en: `models.json already has ${target.reach.provider}, at ${old}. Point it at ${target.reach.baseUrl}? [y/N] `
1643
+ }),
1644
+ false
1645
+ );
1646
+ if (!move) throw this.cancelled();
1647
+ }
1648
+ // -----------------------------------------------------------------------------------------------------------
1649
+ // Step 5: the judge
1650
+ // -----------------------------------------------------------------------------------------------------------
1651
+ async judge(model) {
1652
+ const { env, paths } = this.deps;
1653
+ const envText = readTextFile(paths.envFile);
1654
+ const existing = jevKeyAt(env, envText);
1655
+ if (this.scripted) return this.scriptedJudge(model, envText, existing !== void 0);
1656
+ const where = existing ? existing.where === "env" ? this.t({ zh: `\u73AF\u5883\u53D8\u91CF ${existing.name}`, en: `${existing.name} in your environment` }) : this.t({
1657
+ zh: `${this.path(paths.envFile)} \u91CC\u7684 ${existing.name}`,
1658
+ en: `${existing.name} in ${this.path(paths.envFile)}`
1659
+ }) : void 0;
1660
+ const choices = [];
1661
+ if (where) {
1662
+ choices.push({
1663
+ label: this.t({
1664
+ zh: `Jev\uFF08\u63A8\u8350\uFF09\uFF0C\u7528\u5DF2\u7ECF\u8BBE\u7F6E\u7684\u5BC6\u94A5\uFF08${where}\uFF09`,
1665
+ en: `Jev (recommended), with the key already set (${where})`
1666
+ }),
1667
+ value: "keep"
1668
+ });
1669
+ }
1670
+ choices.push({
1671
+ label: where ? this.t({ zh: "Jev\uFF0C\u6362\u4E00\u4E2A\u65B0\u5BC6\u94A5", en: "Jev, with a new key" }) : this.t({
1672
+ zh: `Jev\uFF08\u63A8\u8350\uFF09\uFF1A\u53C8\u5FEB\u53C8\u51C6\uFF0C\u6309\u6B21\u8BA1\u8D39\u3002\u5BC6\u94A5\u5728 TypeSafe \u7533\u8BF7\uFF1A${JEV_PAGE}`,
1673
+ en: `Jev (recommended): fast and accurate, billed per call. Get a key from TypeSafe: ${JEV_PAGE}`
1674
+ }),
1675
+ value: "jev"
1676
+ });
1677
+ if (model) {
1678
+ choices.push({
1679
+ label: this.t({
1680
+ zh: `\u521A\u8BBE\u7F6E\u7684\u6A21\u578B\uFF08${model.provider}/${model.model}\uFF09\uFF1A\u66F4\u6162\uFF0C\u800C\u4E14\u6BCF\u6B21\u5224\u65AD\u90FD\u8981\u82B1 token`,
1681
+ en: `The model you just set up (${model.provider}/${model.model}): slower, and every decision costs tokens`
1682
+ }),
1683
+ value: "model"
1684
+ });
1685
+ }
1686
+ choices.push({
1687
+ label: this.t({
1688
+ zh: "\u6682\u65F6\u4E0D\u8981\uFF1Amu \u5C31\u50CF pi \u4E00\u6837\u5DE5\u4F5C\uFF0C\u6CA1\u6709\u5224\u5B9A\u5668\u56DE\u7B54",
1689
+ en: "Not now: mu then works like pi, with no judge answering"
1690
+ }),
1691
+ value: "none"
1692
+ });
1693
+ const picked = await this.choose(
1694
+ this.t({
1695
+ zh: "mu \u5DE5\u4F5C\u65F6\u8981\u505A\u5F88\u591A\u5C0F\u5224\u65AD\uFF1A\u8FD9\u6761\u6D88\u606F\u8BE5\u600E\u4E48\u5904\u7406\u3001\u8FD9\u4E00\u6B65\u4F1A\u4E0D\u4F1A\u8FDD\u53CD\u4F60\u5B9A\u7684\u89C4\u5219\u2026\u2026\u5224\u5B9A\u5668\u8D1F\u8D23\u56DE\u7B54\u3002\u9009\u54EA\u4E2A\u5224\u5B9A\u5668\uFF1F",
1696
+ en: "mu makes many small decisions as it works: how to take a message, whether a step breaks your rules. A judge answers them. Which judge?"
1697
+ }),
1698
+ choices
1699
+ );
1700
+ if (picked === "jev") return { kind: "jev", key: await this.jevKey() };
1701
+ return { kind: picked };
1702
+ }
1703
+ scriptedJudge(model, envText, keySet) {
1704
+ switch (this.args.judge) {
1705
+ case "jev": {
1706
+ const key = this.jevFromStdin;
1707
+ if (!key) {
1708
+ if (keySet) return { kind: "keep" };
1709
+ throw this.fail(
1710
+ {
1711
+ zh: "--judge jev \u9700\u8981 Jev \u5BC6\u94A5\uFF1A\u5199\u5728\u6807\u51C6\u8F93\u5165\u7684\u7B2C\u4E8C\u884C\uFF0C\u6216\u5148\u8BBE\u7F6E TYPESAFE_API_KEY",
1712
+ en: "--judge jev needs a Jev key: the second line of stdin, or TYPESAFE_API_KEY set beforehand"
1713
+ },
1714
+ SETUP_EXIT.usage
1715
+ );
1716
+ }
1717
+ if (key === this.modelKey) {
1718
+ throw this.fail(
1719
+ { zh: "Jev \u5BC6\u94A5\u548C\u6A21\u578B\u7684\u5BC6\u94A5\u662F\u540C\u4E00\u4E2A", en: "the Jev key is the same as the model's key" },
1720
+ SETUP_EXIT.usage
1721
+ );
1722
+ }
1723
+ const current = envText === void 0 ? void 0 : envFileValue(envText, "TYPESAFE_API_KEY");
1724
+ if (current && current !== key && !this.args.yes) {
1725
+ throw this.fail({
1726
+ zh: `${this.path(this.deps.paths.envFile)} \u91CC\u5DF2\u7ECF\u6709 TYPESAFE_API_KEY\u3002\u8981\u66FF\u6362\u5B83\uFF0C\u52A0\u4E0A --yes`,
1727
+ en: `${this.path(this.deps.paths.envFile)} has a TYPESAFE_API_KEY already. To replace it, add --yes`
1728
+ });
1729
+ }
1730
+ return { kind: "jev", key };
1731
+ }
1732
+ case "model":
1733
+ return model ? { kind: "model" } : void 0;
1734
+ default:
1735
+ return void 0;
1736
+ }
1737
+ }
1738
+ async jevKey() {
1739
+ for (; ; ) {
1740
+ const key = await this.secret(
1741
+ this.t({
1742
+ zh: "\u7C98\u8D34 Jev \u5BC6\u94A5\uFF08TYPESAFE_API_KEY\uFF0C\u4E0D\u4F1A\u663E\u793A\uFF09\uFF1A",
1743
+ en: "Paste the Jev key (TYPESAFE_API_KEY, not shown): "
1744
+ })
1745
+ );
1746
+ if (key === this.modelKey) {
1747
+ this.io.say(
1748
+ this.t({
1749
+ zh: `\u8FD9\u662F\u521A\u624D\u7ED9\u6A21\u578B\u7684\u5BC6\u94A5\u3002Jev \u7684\u5BC6\u94A5\u5728 TypeSafe \u7533\u8BF7\uFF1A${JEV_PAGE}`,
1750
+ en: `That is the key you gave for the model. A Jev key comes from TypeSafe: ${JEV_PAGE}`
1751
+ })
1752
+ );
1753
+ continue;
1754
+ }
1755
+ const branded = servicesForKey(key).find((service) => BRANDED.has(service.id));
1756
+ if (!branded) return key;
1757
+ const anyway = await this.confirm(
1758
+ this.t({
1759
+ zh: `\u8FD9\u770B\u8D77\u6765\u662F ${this.name(branded)} \u7684\u5BC6\u94A5\uFF0C\u4E0D\u662F Jev \u7684\uFF0C\u5B83\u4F1A\u88AB\u53D1\u7ED9 TypeSafe\u3002\u4ECD\u7136\u4F7F\u7528\u5417\uFF1F[y/N] `,
1760
+ en: `This looks like ${article(this.name(branded))} key, not a Jev key, and it would be sent to TypeSafe. Use it anyway? [y/N] `
1761
+ }),
1762
+ false
1763
+ );
1764
+ if (anyway) return key;
1765
+ }
1766
+ }
1767
+ // -----------------------------------------------------------------------------------------------------------
1768
+ // Step 6: the files
1769
+ // -----------------------------------------------------------------------------------------------------------
1770
+ async write(target, model, judge) {
1771
+ const { deps } = this;
1772
+ const { agentDir: agentDir2 } = deps.paths;
1773
+ const backups = new Backups(deps.paths.home, deps.now());
1774
+ const saved = [];
1775
+ const notes = [];
1776
+ const modelsPath = join3(agentDir2, "models.json");
1777
+ const authPath2 = join3(agentDir2, "auth.json");
1778
+ const settingsPath = join3(agentDir2, "settings.json");
1779
+ try {
1780
+ if (target && model) {
1781
+ const { service, reach, key } = target;
1782
+ const provider = reach.provider;
1783
+ const custom = !reach.builtIn;
1784
+ const moved = reach.builtIn && this.args.baseUrl !== void 0;
1785
+ const unknownModel = reach.builtIn && !deps.pi.models(provider).includes(model);
1786
+ if (custom || moved || unknownModel) {
1787
+ const file = readJsonFile(modelsPath);
1788
+ if (file.state === "unreadable") this.modelsFileWritable();
1789
+ let next = file.state === "ok" ? file.value : {};
1790
+ if (custom) {
1791
+ next = withProvider(next, provider, {
1792
+ name: service.id === "other" ? void 0 : service.name,
1793
+ baseUrl: reach.baseUrl,
1794
+ api: reach.api,
1795
+ // pi counts a provider without a key as unusable: a keyless service gets a placeholder, never a real key.
1796
+ apiKey: service.keyless ? "ollama" : key ? void 0 : "none",
1797
+ compat: service.compat,
1798
+ // What the service lists goes into /model, the one to start with first.
1799
+ models: [model, ...chatModels(target.listed ?? []).filter((id) => id !== model)].map((id) => ({
1800
+ id
1801
+ }))
1802
+ });
1803
+ } else {
1804
+ if (moved) {
1805
+ const providers = existingProviders(next);
1806
+ next = {
1807
+ ...next,
1808
+ providers: { ...providers, [provider]: { ...providers[provider], baseUrl: reach.baseUrl } }
1809
+ };
1810
+ }
1811
+ if (unknownModel) next = withModel(next, provider, model);
1812
+ }
1813
+ backups.save(modelsPath);
1814
+ writeJsonFile(modelsPath, next);
1815
+ saved.push(
1816
+ this.savedLine(
1817
+ modelsPath,
1818
+ custom || moved ? this.t({ zh: `${provider}\uFF1A${reach.baseUrl}`, en: `${provider} at ${reach.baseUrl}` }) : this.t({ zh: `\u7ED9 ${provider} \u52A0\u4E0A ${model}`, en: `${model} added to ${provider}` })
1819
+ )
1820
+ );
1821
+ }
1822
+ if (key) {
1823
+ backups.save(authPath2);
1824
+ await deps.pi.storeKey(provider, key);
1825
+ saved.push(
1826
+ this.savedLine(
1827
+ authPath2,
1828
+ this.t({ zh: `${this.name(service)} \u7684\u5BC6\u94A5`, en: `the ${this.name(service)} key` })
1829
+ )
1830
+ );
1831
+ }
1832
+ backups.save(settingsPath);
1833
+ try {
1834
+ await deps.pi.setDefaultModel(provider, model);
1835
+ saved.push(
1836
+ this.savedLine(
1837
+ settingsPath,
1838
+ this.t({ zh: `\u9ED8\u8BA4\u6A21\u578B ${provider}/${model}`, en: `start on ${provider}/${model}` })
1839
+ )
1840
+ );
1841
+ } catch {
1842
+ notes.push(
1843
+ this.t({
1844
+ zh: `${this.path(settingsPath)} \u8BFB\u4E0D\u4E86\uFF0C\u9ED8\u8BA4\u6A21\u578B\u6CA1\u6709\u4FDD\u5B58\u3002\u5728 mu \u91CC\u7528 /model \u9009\u5B83\u3002`,
1845
+ en: `${this.path(settingsPath)} could not be read, so the default model was not saved. Choose it inside mu with /model.`
1846
+ })
1847
+ );
1848
+ }
1849
+ }
1850
+ if (judge && judge.kind !== "none") this.writeJudge(judge, target, model, backups, saved, notes);
1851
+ } catch (error) {
1852
+ if (error instanceof Stop) throw error;
1853
+ let reason = error instanceof Error ? error.message : String(error);
1854
+ for (const secret of [target?.key, judge?.kind === "jev" ? judge.key : void 0]) {
1855
+ if (secret) reason = reason.replaceAll(secret, "\u2026");
1856
+ }
1857
+ this.io.warn(this.t({ zh: `mu setup\uFF1A\u4FDD\u5B58\u65F6\u51FA\u9519\uFF1A${reason}`, en: `mu setup: saving failed: ${reason}` }));
1858
+ if (saved.length > 0) {
1859
+ this.io.warn(this.t({ zh: "\u51FA\u9519\u524D\u5DF2\u7ECF\u4FDD\u5B58\u7684\uFF1A", en: "Saved before that:" }));
1860
+ for (const entry of saved) this.io.warn(entry);
1861
+ }
1862
+ if (backups.path) {
1863
+ this.io.warn(
1864
+ this.t({
1865
+ zh: `\u539F\u6765\u7684\u6587\u4EF6\u5728 ${this.path(backups.path)}`,
1866
+ en: `The earlier versions are in ${this.path(backups.path)}`
1867
+ })
1868
+ );
1869
+ }
1870
+ return SETUP_EXIT.failed;
1871
+ }
1872
+ if (saved.length > 0) {
1873
+ this.io.say(this.t({ zh: "\u5DF2\u4FDD\u5B58\uFF1A", en: "Saved:" }));
1874
+ for (const entry of saved) this.io.say(entry);
1875
+ }
1876
+ if (backups.path) {
1877
+ this.io.say(
1878
+ this.t({
1879
+ zh: `\u6539\u52A8\u524D\u7684\u6587\u4EF6\u5DF2\u590D\u5236\u5230 ${this.path(backups.path)}`,
1880
+ en: `The files as they were are copied to ${this.path(backups.path)}`
1881
+ })
1882
+ );
1883
+ }
1884
+ for (const note of notes) this.io.say(note);
1885
+ this.summary(target, model, judge);
1886
+ return SETUP_EXIT.done;
1887
+ }
1888
+ savedLine(path, what) {
1889
+ return ` ${this.path(path).padEnd(30)} ${what}`;
1890
+ }
1891
+ writeJudge(judge, target, model, backups, saved, notes) {
1892
+ const { env, paths } = this.deps;
1893
+ const configPath = judgeConfigPath(paths.agentDir);
1894
+ const config = readJsonFile(configPath);
1895
+ const current = config.state === "ok" ? config.value : void 0;
1896
+ let tiers;
1897
+ if (judge.kind === "model") {
1898
+ if (target && model) tiers = [`llm:${target.reach.provider}/${model}`];
1899
+ } else if (!tiersOf(current).some((tier) => JEV_TIERS.has(tier))) tiers = ["jev"];
1900
+ if (tiers && config.state === "unreadable") {
1901
+ notes.push(
1902
+ this.t({
1903
+ zh: `${this.path(configPath)} \u4E0D\u662F\u7EAF JSON\uFF0C\u6CA1\u6709\u6539\u5B83\u3002\u8BF7\u624B\u52A8\u52A0\u4E0A "tiers": ${JSON.stringify(tiers)}`,
1904
+ en: `${this.path(configPath)} is not plain JSON and was left as it is. Add "tiers": ${JSON.stringify(tiers)} by hand.`
1905
+ })
1906
+ );
1907
+ } else if (tiers) {
1908
+ backups.save(configPath);
1909
+ writeJsonFile(configPath, withTiers(current ?? {}, tiers));
1910
+ saved.push(
1911
+ this.savedLine(configPath, this.t({ zh: `\u5224\u5B9A\u5668 ${tiers.join(", ")}`, en: `judge: ${tiers.join(", ")}` }))
1912
+ );
1913
+ }
1914
+ if (judge.kind === "jev") {
1915
+ backups.save(paths.envFile);
1916
+ writeEnvValue(paths.envFile, "TYPESAFE_API_KEY", judge.key);
1917
+ saved.push(
1918
+ this.savedLine(
1919
+ paths.envFile,
1920
+ this.t({ zh: "TYPESAFE_API_KEY\uFF08Jev \u5BC6\u94A5\uFF09", en: "TYPESAFE_API_KEY (the Jev key)" })
1921
+ )
1922
+ );
1923
+ if ("TYPESAFE_API_KEY" in env) {
1924
+ notes.push(
1925
+ this.t({
1926
+ zh: `\u6CE8\u610F\uFF1A\u73AF\u5883\u53D8\u91CF\u91CC\u4E5F\u6709 TYPESAFE_API_KEY\uFF0C\u5B83\u6BD4 ${this.path(paths.envFile)} \u4F18\u5148\u3002\u5230\u8BBE\u7F6E\u5B83\u7684\u5730\u65B9\uFF08\u4F8B\u5982 ~/.zshrc\uFF09\u6539\u6389\u6216\u5220\u6389\u3002`,
1927
+ en: `Note: TYPESAFE_API_KEY is also set in your environment, and it wins over ${this.path(paths.envFile)}. Change or remove it where it is set (~/.zshrc, for example).`
1928
+ })
1929
+ );
1930
+ }
1931
+ }
1932
+ const override = env.MU_JUDGE || env.KYRN_JUDGE;
1933
+ if (override) {
1934
+ notes.push(
1935
+ this.t({
1936
+ zh: `\u6CE8\u610F\uFF1A\u73AF\u5883\u53D8\u91CF MU_JUDGE=${override} \u6BD4 mu.json \u4F18\u5148\uFF0C\u7531\u5B83\u51B3\u5B9A\u5224\u5B9A\u5668\u3002`,
1937
+ en: `Note: MU_JUDGE=${override} is set in your environment; it decides the judge over mu.json.`
1938
+ })
1939
+ );
1940
+ }
1941
+ }
1942
+ summary(target, model, judge) {
1943
+ const done = target && model ? this.t({
1944
+ zh: `\u5B8C\u6210\u3002mu \u4F1A\u7528 ${this.name(target.service)} \xB7 ${model} \u5F00\u59CB\u5DE5\u4F5C\u3002`,
1945
+ en: `Done. mu starts on ${this.name(target.service)} \xB7 ${model}.`
1946
+ }) : this.t({ zh: "\u5B8C\u6210\u3002", en: "Done." });
1947
+ const judged = judge === void 0 ? "" : judge.kind === "model" ? this.t({ zh: "\u5224\u5B9A\u5668\uFF1A\u540C\u4E00\u4E2A\u6A21\u578B\u3002", en: " Judge: the same model." }) : judge.kind === "none" ? this.t({ zh: "\u5224\u5B9A\u5668\uFF1A\u6682\u65F6\u6CA1\u6709\u3002", en: " Judge: none for now." }) : this.t({ zh: "\u5224\u5B9A\u5668\uFF1AJev\u3002", en: " Judge: Jev." });
1948
+ this.io.say("");
1949
+ this.io.say(`${done}${judged}`);
1950
+ this.io.say(
1951
+ target ? this.t({
1952
+ zh: "\u5728 mu \u91CC\u7528 /model \u6362\u6A21\u578B\uFF1B\u968F\u65F6\u53EF\u4EE5\u518D\u8FD0\u884C mu setup\u3002",
1953
+ en: "Switch models inside mu with /model; run mu setup again any time."
1954
+ }) : this.t({ zh: "\u542F\u52A8 mu \u540E\u8F93\u5165 /login \u767B\u5F55\u3002", en: "Start mu and type /login to sign in." })
1955
+ );
1956
+ }
1957
+ /** On its own, a finished setup offers a session; on a first start the launcher goes on into one anyway. */
1958
+ async offerSession() {
1959
+ if (this.args.firstRun || this.scripted) return SETUP_EXIT.done;
1960
+ const start = await this.confirm(this.t({ zh: "\u73B0\u5728\u542F\u52A8 mu \u5417\uFF1F[Y/n] ", en: "Start mu now? [Y/n] " }), true);
1961
+ return start ? SETUP_EXIT.start : SETUP_EXIT.done;
1962
+ }
1963
+ };
1964
+ function existingProviders(models) {
1965
+ const providers = models.providers;
1966
+ return typeof providers === "object" && providers !== null && !Array.isArray(providers) ? providers : {};
1967
+ }
1968
+ async function runSetup(argv, deps) {
1969
+ const parsed = parseSetupArgs(argv);
1970
+ if ("error" in parsed) {
1971
+ deps.io.warn(`mu setup: ${parsed.error[deps.language]}`);
1972
+ deps.io.warn(setupUsage(deps.language));
1973
+ return SETUP_EXIT.usage;
1974
+ }
1975
+ if (parsed.help) {
1976
+ deps.io.say(setupUsage(deps.language));
1977
+ return SETUP_EXIT.done;
1978
+ }
1979
+ if (parsed.list) {
1980
+ deps.io.say(serviceList(deps.language));
1981
+ return SETUP_EXIT.done;
1982
+ }
1983
+ return new Setup(parsed, deps).run();
1984
+ }
1985
+
1986
+ // packages/kyrn-judge/src/setup/main.ts
1987
+ var givenKey = process.env.MU_SETUP_KEY || void 0;
1988
+ delete process.env.MU_SETUP_KEY;
1989
+ var home = process.env.MU_SETUP_HOME || muHome();
1990
+ var agentDir = muEnv("AGENT_DIR") || join4(home, "agent");
1991
+ var envFile = process.env.MU_SETUP_ENV_FILE || join4(home, ".env");
1992
+ var authPath = join4(agentDir, "auth.json");
1993
+ var modelsStorePath = join4(agentDir, "models-store.json");
1994
+ var givenEnv = { ...process.env };
1995
+ var PROXY_VARIABLES = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
1996
+ function useSessionProxy() {
1997
+ const text = readTextFile(envFile);
1998
+ for (const entry of text === void 0 ? [] : readEnvEntries(text)) {
1999
+ const proxyVariable = PROXY_VARIABLES.includes(entry.name) || /^no_proxy$/i.test(entry.name);
2000
+ if (proxyVariable && !(entry.name in process.env)) process.env[entry.name] = entry.value;
2001
+ }
2002
+ const named2 = PROXY_VARIABLES.find((name) => process.env[name]);
2003
+ let fromSettings;
2004
+ try {
2005
+ fromSettings = SettingsManager2.create(agentDir, agentDir, { projectTrusted: false }).getGlobalSettings().httpProxy?.trim();
2006
+ } catch {
2007
+ }
2008
+ applyHttpProxySettings(fromSettings);
2009
+ configureHttpDispatcher();
2010
+ if (named2) return named2 in givenEnv ? named2 : `${named2}, .env`;
2011
+ return fromSettings ? "httpProxy, settings.json" : void 0;
2012
+ }
2013
+ async function main() {
2014
+ const proxy = useSessionProxy();
2015
+ const runtime = await ModelRuntime.create({
2016
+ authPath,
2017
+ modelsPath: join4(agentDir, "models.json"),
2018
+ modelsStorePath,
2019
+ refreshOnCreate: false
2020
+ });
2021
+ return runSetup(process.argv.slice(2), {
2022
+ io: terminalPrompter(),
2023
+ language: setupLanguage(process.env, systemLocale()),
2024
+ env: givenEnv,
2025
+ paths: { agentDir, home, envFile },
2026
+ pi: piAccess(runtime, agentDir),
2027
+ // pi's fetch now (configureHttpDispatcher installs it): through the proxy, as a session's requests go.
2028
+ fetch: globalThis.fetch,
2029
+ now: () => /* @__PURE__ */ new Date(),
2030
+ interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
2031
+ stdinTerminal: process.stdin.isTTY === true,
2032
+ readStdin: () => readAll(),
2033
+ givenKey,
2034
+ proxy
2035
+ });
2036
+ }
2037
+ var leaving = false;
2038
+ var leave = (code) => {
2039
+ if (leaving) return;
2040
+ leaving = true;
2041
+ if (process.stdin.isTTY && process.stdin.isRaw) process.stdin.setRawMode(false);
2042
+ void exitWhenUnlocked(code, [authPath, modelsStorePath], (exitCode) => process.exit(exitCode));
2043
+ };
2044
+ for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"])
2045
+ process.on(signal, () => leave(128 + constants.signals[signal]));
2046
+ main().then(leave, (error) => {
2047
+ const message = error instanceof Error ? error.message : String(error);
2048
+ process.stderr.write(`mu setup: ${redactSecrets(message, givenKey ? [givenKey] : [])}
2049
+ `);
2050
+ leave(1);
2051
+ });