flavor-code 1.4.1 → 1.4.3-beta.2

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 (76) hide show
  1. package/README.md +32 -2
  2. package/README.zh-CN.md +32 -2
  3. package/dist/{app-4YRKE7VI.js → app-G35YBHPR.js} +873 -641
  4. package/dist/apply-WOW3QU5I.js +51 -0
  5. package/dist/broker-cli-X5H46FZV.js +15 -0
  6. package/dist/check-CSKTV4O2.js +16 -0
  7. package/dist/chunk-3563SW4X.js +10513 -0
  8. package/dist/chunk-3UX2CGDW.js +1955 -0
  9. package/dist/chunk-4TMLXZVZ.js +75 -0
  10. package/dist/chunk-6F66UXBI.js +48 -0
  11. package/dist/{chunk-FAORPP4F.js → chunk-6URQ7FIF.js} +24 -88
  12. package/dist/chunk-6X62CGMA.js +193 -0
  13. package/dist/chunk-7Y64WNBB.js +8930 -0
  14. package/dist/chunk-AA4QNKTF.js +501 -0
  15. package/dist/chunk-BDX777JR.js +11 -0
  16. package/dist/chunk-CVP5OUHD.js +103 -0
  17. package/dist/chunk-EWD7FWTH.js +14547 -0
  18. package/dist/chunk-FAJ6YDZK.js +630 -0
  19. package/dist/{chunk-N5MXHJKD.js → chunk-GTFP2R5I.js} +1 -1
  20. package/dist/{chunk-7YFHRORG.js → chunk-HGTCIHWA.js} +383 -172
  21. package/dist/chunk-IDJAHEG6.js +43501 -0
  22. package/dist/chunk-IQEEFDEE.js +389 -0
  23. package/dist/chunk-IU3UBETC.js +30 -0
  24. package/dist/chunk-K7UGGWJV.js +1003 -0
  25. package/dist/chunk-KRWWCVXA.js +74 -0
  26. package/dist/chunk-OKHV7CTB.js +482 -0
  27. package/dist/chunk-PULWAH6E.js +474 -0
  28. package/dist/chunk-PZ5NKRVA.js +85 -0
  29. package/dist/chunk-RBAZLFKB.js +1022 -0
  30. package/dist/chunk-RV3EXUXM.js +30 -0
  31. package/dist/chunk-SJUIIHIA.js +2130 -0
  32. package/dist/{chunk-VMQWXQEC.js → chunk-T6RE76D2.js} +2003 -493
  33. package/dist/chunk-TIEOVQW6.js +61 -0
  34. package/dist/chunk-WLZUAVVU.js +15 -0
  35. package/dist/chunk-X442CJJH.js +114 -0
  36. package/dist/{chunk-RAYH6K2H.js → chunk-Y7EH4NVO.js} +1 -1
  37. package/dist/{chunk-S32JRDZK.js → chunk-ZKKVSR37.js} +6 -1
  38. package/dist/{claude-ink-JZXNSAQS.js → claude-ink-BE56DB6N.js} +9 -4
  39. package/dist/cli-3MF6EL2H.js +163 -0
  40. package/dist/cli-main.js +3805 -473
  41. package/dist/cli.js +50 -4
  42. package/dist/config/load.d.ts +8 -0
  43. package/dist/config/schema.d.ts +11 -0
  44. package/dist/context/manager.d.ts +2 -0
  45. package/dist/desktop/main.js +4303 -1171
  46. package/dist/desktop/preload.cjs +30 -0
  47. package/dist/desktop-renderer/assets/index-BWklSoOO.css +1 -0
  48. package/dist/desktop-renderer/assets/index-DCqIrF1S.js +153 -0
  49. package/dist/desktop-renderer/assets/{interactive-terminal-1agnYY5L.js → interactive-terminal-B6GbpzIn.js} +1 -1
  50. package/dist/desktop-renderer/index.html +2 -2
  51. package/dist/doctor-OHCGK52S.js +20 -0
  52. package/dist/load-QK6CMLDF.js +25 -0
  53. package/dist/log-ATFIPRNU.js +19 -0
  54. package/dist/manager-5SI4MPES.js +191 -0
  55. package/dist/models/anthropic.d.ts +3 -0
  56. package/dist/node-LOIXT44J.js +879 -0
  57. package/dist/plugin-worker-entry.js +1 -1
  58. package/dist/production-6DIZ6YLQ.js +46 -0
  59. package/dist/production.d.ts +28 -1
  60. package/dist/project-3QXE22TG.js +11 -0
  61. package/dist/react-M6JGXSJZ.js +7 -0
  62. package/dist/recorder-FAGJJH3N.js +11 -0
  63. package/dist/sdk/index.js +30 -8
  64. package/dist/server-2KLKPHPL.js +11 -0
  65. package/dist/session/store.d.ts +1 -0
  66. package/dist/store-ZIHN6363.js +18 -0
  67. package/dist/ui/commands.d.ts +1 -5
  68. package/dist/usage-summary-G5FBOJLK.js +13 -0
  69. package/dist/utils/execFileNoThrow.d.ts +1 -0
  70. package/dist/write-stream-CGD57URC.js +77 -0
  71. package/package.json +1 -1
  72. package/dist/chunk-O2PC44GY.js +0 -26120
  73. package/dist/chunk-ZJCAQXA7.js +0 -169
  74. package/dist/desktop-renderer/assets/index-BM9ugkh9.js +0 -154
  75. package/dist/desktop-renderer/assets/index-Bk6FYhyh.css +0 -1
  76. package/dist/load-UPOG3XQR.js +0 -19
@@ -0,0 +1,1003 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as createFlavorRequire } from "node:module"; const require = createFlavorRequire(import.meta.url);
3
+ import {
4
+ execFileNoThrow
5
+ } from "./chunk-IU3UBETC.js";
6
+ import {
7
+ prepareSpawnInvocation,
8
+ resolveExecutablePath
9
+ } from "./chunk-PZ5NKRVA.js";
10
+ import {
11
+ fetchLatestVersion
12
+ } from "./chunk-6F66UXBI.js";
13
+ import {
14
+ gte
15
+ } from "./chunk-3UX2CGDW.js";
16
+ import {
17
+ PluginManifestSchema
18
+ } from "./chunk-CVP5OUHD.js";
19
+ import {
20
+ packageVersion
21
+ } from "./chunk-RV3EXUXM.js";
22
+ import {
23
+ loadConfig
24
+ } from "./chunk-HGTCIHWA.js";
25
+ import {
26
+ external_exports
27
+ } from "./chunk-EWD7FWTH.js";
28
+
29
+ // src/doctor.ts
30
+ import { constants } from "fs";
31
+ import { access, lstat, readFile, readdir, stat } from "fs/promises";
32
+ import { homedir } from "os";
33
+ import { isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
34
+
35
+ // node_modules/@vscode/ripgrep/lib/index.js
36
+ import { createRequire } from "module";
37
+ var require2 = createRequire(import.meta.url);
38
+ var arch = process.env.npm_config_arch || process.arch;
39
+ var binaryName = process.platform === "win32" ? "rg.exe" : "rg";
40
+ var platformPkg = `@vscode/ripgrep-${process.platform}-${arch}`;
41
+ var resolved;
42
+ try {
43
+ resolved = require2.resolve(`${platformPkg}/bin/${binaryName}`);
44
+ } catch {
45
+ throw new Error(
46
+ `Could not find ${platformPkg}. Ensure optionalDependencies are installed for this platform (${process.platform}-${arch}).`
47
+ );
48
+ }
49
+ var rgPath = resolved;
50
+
51
+ // src/tools/shell.ts
52
+ import { spawn as spawn2 } from "child_process";
53
+ import { existsSync } from "fs";
54
+ import { basename, delimiter, isAbsolute, join, relative, resolve, sep } from "path";
55
+
56
+ // src/tools/jobs.ts
57
+ var JobId = external_exports.string().regex(/^job-[a-f0-9-]+$/);
58
+ var ReadInput = external_exports.object({ id: JobId, cursor: external_exports.coerce.number().int().min(0).optional() });
59
+ var IdInput = external_exports.object({ id: JobId });
60
+ function createJobTools(registry) {
61
+ const tools = [
62
+ {
63
+ name: "JobList",
64
+ description: "List background jobs owned by this agent",
65
+ inputSchema: external_exports.object({}),
66
+ paths: () => [],
67
+ execute: async (_input, _signal, context) => registry.list(owner(context)),
68
+ presentResult: (output) => jobListPresentation(output)
69
+ },
70
+ {
71
+ name: "JobRead",
72
+ description: "Read incremental output from an owned background job",
73
+ inputSchema: ReadInput,
74
+ paths: () => [],
75
+ execute: async (input, _signal, context) => registry.read(input.id, owner(context), input.cursor ?? 0),
76
+ renderForModel: (output) => JSON.stringify(output),
77
+ presentResult: (output) => jobPresentation("read", output)
78
+ },
79
+ {
80
+ name: "JobWait",
81
+ description: "Wait for an owned background job to finish",
82
+ inputSchema: IdInput,
83
+ paths: () => [],
84
+ execute: async (input, signal, context) => registry.wait(input.id, owner(context), signal),
85
+ presentResult: (output) => jobPresentation("wait", output)
86
+ },
87
+ {
88
+ name: "JobKill",
89
+ description: "Cancel an owned background job and its process tree",
90
+ inputSchema: IdInput,
91
+ paths: () => [],
92
+ execute: async (input, _signal, context) => {
93
+ await registry.kill(input.id, owner(context));
94
+ return registry.read(input.id, owner(context));
95
+ },
96
+ presentResult: (output) => jobPresentation("kill", output)
97
+ }
98
+ ];
99
+ return tools;
100
+ }
101
+ function jobPresentation(action, job) {
102
+ const read = "output" in job ? job : void 0;
103
+ return {
104
+ kind: "job",
105
+ action,
106
+ id: job.id,
107
+ jobKind: job.kind,
108
+ label: job.label,
109
+ state: job.state,
110
+ ...job.exitCode === void 0 ? {} : { exitCode: job.exitCode },
111
+ ...job.error === void 0 ? {} : { error: job.error },
112
+ ...read === void 0 ? {} : { output: read.output, cursor: read.cursor },
113
+ ...job.truncated ? { truncated: true } : {}
114
+ };
115
+ }
116
+ function jobListPresentation(jobs) {
117
+ return {
118
+ kind: "job",
119
+ action: "list",
120
+ jobs: jobs.map((job) => ({
121
+ id: job.id,
122
+ kind: job.kind,
123
+ label: job.label,
124
+ state: job.state,
125
+ ...job.exitCode === void 0 ? {} : { exitCode: job.exitCode }
126
+ }))
127
+ };
128
+ }
129
+ function owner(context) {
130
+ return context?.ownerId ?? context?.agent ?? "main";
131
+ }
132
+
133
+ // src/utils/process-tree.ts
134
+ import { spawn } from "child_process";
135
+ async function terminateProcessTree(pid) {
136
+ if (pid === void 0) return;
137
+ if (process.platform === "win32") {
138
+ await new Promise((resolve3) => {
139
+ const child = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, windowsHide: true });
140
+ const finish = () => {
141
+ clearTimeout(timer);
142
+ resolve3();
143
+ };
144
+ const timer = setTimeout(finish, 5e3);
145
+ timer.unref();
146
+ child.once("error", finish);
147
+ child.once("close", finish);
148
+ });
149
+ try {
150
+ process.kill(pid);
151
+ } catch {
152
+ }
153
+ } else {
154
+ try {
155
+ process.kill(-pid, "SIGTERM");
156
+ } catch {
157
+ try {
158
+ process.kill(pid, "SIGTERM");
159
+ } catch {
160
+ }
161
+ }
162
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
163
+ try {
164
+ process.kill(-pid, "SIGKILL");
165
+ } catch {
166
+ try {
167
+ process.kill(pid, "SIGKILL");
168
+ } catch {
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ // src/tools/shell.ts
175
+ var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
176
+ var DEFAULT_SHELL_TIMEOUT_MS = 10 * 6e4;
177
+ var ELLIPSIS = "\u2026";
178
+ var TERMINATION_FAILURE_MS = 5e3;
179
+ var STREAM_CLOSE_GRACE_MS = 500;
180
+ var ShellInput = external_exports.object({
181
+ command: external_exports.string().trim().min(1).refine((value) => !value.includes("\0"), "command cannot contain a null byte").describe("Executable name or path. Put each argument in args; use a shell expression here only for pipes, redirects, or other shell syntax."),
182
+ args: external_exports.array(external_exports.string().refine((value) => !value.includes("\0"), "argument cannot contain a null byte").describe("One logical argument without surrounding shell quotes.")),
183
+ cwd: external_exports.string().min(1).nullable().optional(),
184
+ timeoutMs: external_exports.coerce.number().int().positive().max(864e5).nullable().optional(),
185
+ // Accepts booleans and their string forms (weak-typed models emit "true");
186
+ // kept transform-free so the schema converts to JSON Schema for providers.
187
+ background: external_exports.union([external_exports.boolean(), external_exports.string().refine((value) => value === "true" || value === "false")]).optional()
188
+ });
189
+ function normalizeShellCommand(input) {
190
+ const command = unquoteToken(input.command.trim());
191
+ const args = [...input.args];
192
+ const executable = basename(command).toLowerCase();
193
+ if ((executable === "cmd" || executable === "cmd.exe") && /^\/[ck]$/iu.test(args[0] ?? "")) {
194
+ return { command, args: [args[0], ...args.slice(1).map(unquoteToken)] };
195
+ }
196
+ if (!/\s/u.test(command) || containsShellSyntax(command)) return { command, args };
197
+ const tokens = tokenizeCommandLine(command);
198
+ if (tokens.length <= 1) return { command, args };
199
+ const head = unquoteToken(tokens[0]);
200
+ if (head.includes("/") || head.includes("\\")) return { command, args };
201
+ return { command: head, args: [...tokens.slice(1).map(unquoteToken), ...args] };
202
+ }
203
+ function tokenizeCommandLine(command) {
204
+ const tokens = [];
205
+ let current = "";
206
+ let quote;
207
+ for (let index = 0; index < command.length; index += 1) {
208
+ const char = command[index];
209
+ if (char === "\\" && quote === '"' && command[index + 1] !== void 0) {
210
+ current += `${char}${command[index + 1]}`;
211
+ index += 1;
212
+ } else if (char === '"' || char === "'") {
213
+ if (quote === void 0) quote = char;
214
+ else if (quote === char) quote = void 0;
215
+ current += char;
216
+ } else if (/\s/u.test(char) && quote === void 0) {
217
+ if (current !== "") {
218
+ tokens.push(current);
219
+ current = "";
220
+ }
221
+ } else {
222
+ current += char;
223
+ }
224
+ }
225
+ if (current !== "") tokens.push(current);
226
+ return tokens;
227
+ }
228
+ function unquoteToken(token) {
229
+ const trimmed = token.trim();
230
+ if (trimmed.length < 2) return trimmed;
231
+ const first = trimmed[0];
232
+ return (first === '"' || first === "'") && trimmed.endsWith(first) ? trimmed.slice(1, -1) : trimmed;
233
+ }
234
+ function serializeCmdCommand(args) {
235
+ return args.map((arg) => {
236
+ const value = unquoteToken(arg);
237
+ return /[\s&|<>^()]/u.test(value) ? `"${value.replace(/"/gu, '""')}"` : value;
238
+ }).join(" ");
239
+ }
240
+ function serializeCmdPayload(args) {
241
+ if (args.length === 1) return unquoteToken(args[0]);
242
+ return args.map((arg) => /^(?:&&|\|\||[|&<>])$/u.test(arg) ? arg : serializeCmdCommand([arg])).join(" ");
243
+ }
244
+ function containsShellSyntax(command, platform = process.platform) {
245
+ let quote;
246
+ for (let index = 0; index < command.length; index += 1) {
247
+ const char = command[index];
248
+ if (quote !== void 0) {
249
+ if (char === quote) quote = void 0;
250
+ else if (char === "\\" && quote === '"') index += 1;
251
+ else if (char === "$" && quote === '"') return true;
252
+ continue;
253
+ }
254
+ if (char === "\\" && platform !== "win32") {
255
+ index += 1;
256
+ continue;
257
+ }
258
+ if (char === '"' || char === "'") {
259
+ quote = char;
260
+ continue;
261
+ }
262
+ if (/[|&;<>`$*?()\n\r]/u.test(char)) return true;
263
+ if (char === "[" && command.indexOf("]", index + 1) > index) return true;
264
+ if (char === "~" && (index === 0 || /\s/u.test(command[index - 1])) && /[\\/]/u.test(command[index + 1] ?? "")) return true;
265
+ if (platform === "win32" && char === "%" && /^%[A-Za-z_][A-Za-z0-9_]*%/u.test(command.slice(index))) return true;
266
+ }
267
+ return quote !== void 0;
268
+ }
269
+ function hasUnmatchedShellQuote(command) {
270
+ let quote;
271
+ for (let index = 0; index < command.length; index += 1) {
272
+ const char = command[index];
273
+ if (char === "\\" && quote !== "'") {
274
+ index += 1;
275
+ continue;
276
+ }
277
+ if (char !== '"' && char !== "'") continue;
278
+ if (quote === void 0) quote = char;
279
+ else if (quote === char) quote = void 0;
280
+ }
281
+ return quote !== void 0;
282
+ }
283
+ function resolveRuntimeShell(platform = process.platform, environment = process.env) {
284
+ if (platform === "win32") {
285
+ for (const candidate of ["pwsh", "powershell"]) {
286
+ const command2 = resolveExecutablePath(candidate, { platform: "win32", env: environment });
287
+ if (command2 !== void 0) return { kind: candidate, command: command2 };
288
+ }
289
+ const configured = environment.ComSpec ?? environment.COMSPEC ?? "cmd.exe";
290
+ const command = resolveExecutablePath(configured, { platform: "win32", env: environment });
291
+ return command === void 0 ? void 0 : { kind: "cmd", command };
292
+ }
293
+ for (const candidate of [environment.SHELL, "/bin/sh"]) {
294
+ if (!candidate) continue;
295
+ const command = resolveExecutablePath(candidate, { platform, env: environment });
296
+ if (command !== void 0) return { kind: "posix", command };
297
+ }
298
+ return void 0;
299
+ }
300
+ function psQuote(token) {
301
+ return `'${token.replace(/'/g, "''")}'`;
302
+ }
303
+ function shellInvocation(input, cwd, environment) {
304
+ const resolved2 = resolveExecutablePath(input.command, { cwd, env: environment });
305
+ if (resolved2 !== void 0) {
306
+ const executable = basename(resolved2).toLowerCase();
307
+ if (process.platform === "win32" && (executable === "cmd" || executable === "cmd.exe")) {
308
+ const commandFlag = input.args.findIndex((arg) => /^\/[ck]$/iu.test(arg));
309
+ if (commandFlag >= 0 && input.args[commandFlag + 1] !== void 0) {
310
+ const payload = serializeCmdPayload(input.args.slice(commandFlag + 1));
311
+ return {
312
+ command: resolved2,
313
+ args: [...input.args.slice(0, commandFlag + 1), `chcp 65001>nul & ${payload}`],
314
+ windowsVerbatimArguments: true
315
+ };
316
+ }
317
+ }
318
+ return prepareSpawnInvocation(resolved2, input.args, { cwd, env: environment });
319
+ }
320
+ const isScript = containsShellSyntax(input.command);
321
+ if (!isScript && (isAbsolute(input.command) || /[\\/]/u.test(input.command))) {
322
+ return { command: input.command, args: [...input.args] };
323
+ }
324
+ const shell = resolveRuntimeShell(process.platform, environment);
325
+ if (shell === void 0) return { command: input.command, args: [...input.args] };
326
+ const script = isScript ? appendShellArguments(shell, input.command, input.args) : structuredShellScript(shell, input.command, input.args);
327
+ if (shell.kind === "posix") return { command: shell.command, args: ["-c", script] };
328
+ if (shell.kind === "cmd") return {
329
+ command: shell.command,
330
+ args: ["/d", "/s", "/c", `chcp 65001>nul & ${script}`],
331
+ windowsVerbatimArguments: true
332
+ };
333
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
334
+ return {
335
+ command: shell.command,
336
+ args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded]
337
+ };
338
+ }
339
+ function appendShellArguments(shell, script, args) {
340
+ if (args.length === 0) return script;
341
+ if (shell.kind === "posix") return `${script} ${args.map(posixQuote).join(" ")}`;
342
+ if (shell.kind === "cmd") return `${script} ${serializeCmdCommand(args)}`;
343
+ return `${script} ${args.map(psQuote).join(" ")}`;
344
+ }
345
+ var POSIX_SHELL_BUILTINS = /* @__PURE__ */ new Set([".", ":", "alias", "cd", "command", "eval", "exec", "export", "read", "set", "source", "trap", "type", "ulimit", "umask", "unalias", "unset", "wait"]);
346
+ function executionEnvironmentCommand(environment, command, args) {
347
+ if (environment.kind !== "docker") return { command, args: [...args] };
348
+ const script = containsShellSyntax(command, "linux") ? appendShellArguments({ kind: "posix", command: "/bin/sh" }, command, args) : POSIX_SHELL_BUILTINS.has(command) ? structuredShellScript({ kind: "posix", command: "/bin/sh" }, command, args) : void 0;
349
+ return script === void 0 ? { command, args: [...args] } : { command: "/bin/sh", args: ["-c", script] };
350
+ }
351
+ function structuredShellScript(shell, command, args) {
352
+ if (shell.kind === "posix") return [command, ...args].map(posixQuote).join(" ");
353
+ if (shell.kind === "cmd") return serializeCmdCommand([command, ...args]);
354
+ const invocation = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map((arg) => /^-[A-Za-z][A-Za-z0-9-]*$/u.test(arg) ? arg : psQuote(arg)).join(" ")}` : ""}`;
355
+ return `$ErrorActionPreference='Stop'; try { ${invocation}; if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 } } catch { [Console]::Error.WriteLine($_.Exception.Message); if ($_.Exception -is [System.Management.Automation.CommandNotFoundException]) { exit 127 } else { exit 1 } }`;
356
+ }
357
+ function posixQuote(value) {
358
+ return `'${value.replace(/'/gu, `'"'"'`)}'`;
359
+ }
360
+ function withShellDiagnostic(result, command, spawnError) {
361
+ const diagnostic = classifyShellFailure(result, command, spawnError);
362
+ if (diagnostic === void 0) return result;
363
+ return { ...result, diagnostic };
364
+ }
365
+ function classifyShellFailure(result, command, spawnError) {
366
+ if (result.terminationReason === "timeout") return {
367
+ kind: "timeout",
368
+ message: `Command exceeded its timeout and was terminated.`,
369
+ hint: "Increase timeoutMs only if this command is expected to take longer."
370
+ };
371
+ if (result.terminationReason === "cancelled") return { kind: "cancelled", message: "Command was cancelled." };
372
+ if (result.exitCode === 0 && result.signal === null && spawnError === void 0) return void 0;
373
+ if (hasUnmatchedShellQuote(command)) return {
374
+ kind: "shell-syntax",
375
+ message: "The command contains an unmatched shell quote.",
376
+ hint: "Close the quoted value, or pass it as one unquoted args item when shell expansion is not required."
377
+ };
378
+ const detail = `${spawnError?.message ?? ""}
379
+ ${result.stderr}`;
380
+ const code = errorCode(spawnError);
381
+ if (code === "ENOENT" && (isAbsolute(command) || /[\\/]/u.test(command))) {
382
+ return { kind: "path-not-found", message: `Executable path "${command}" does not exist.` };
383
+ }
384
+ if (code === "ENOENT" || /(?:not recognized as (?:an internal|(?:a|the) name)|command not found|not found\s*$|无法将.+识别为|术语.+不会被识别为|不会被识别为|不是内部或外部命令)/imu.test(detail)) {
385
+ return {
386
+ kind: "command-not-found",
387
+ message: `Executable "${command}" was not found by the selected runtime shell.`,
388
+ hint: "Use a command available on PATH, or pass an existing executable path without surrounding quotes. For an unavailable PowerShell cmdlet, check Get-Command and its module in the selected shell; do not keep retrying the same command."
389
+ };
390
+ }
391
+ if (code === "EACCES" || /(?:permission denied|access is denied|拒绝访问)/iu.test(detail)) {
392
+ return { kind: "permission-denied", message: `Command "${command}" could not be executed because access was denied.` };
393
+ }
394
+ if (/(?:no such file or directory|cannot find (?:the )?path|system cannot find|系统找不到指定的路径)/iu.test(detail)) {
395
+ return { kind: "path-not-found", message: "The command referenced a path that does not exist." };
396
+ }
397
+ if (/(?:syntax\s*error|unexpected token|parsererror|unterminated (?:regexp|string)|语法错误)/iu.test(detail)) {
398
+ return {
399
+ kind: "shell-syntax",
400
+ message: "The command or interpreter rejected the syntax.",
401
+ hint: 'Pass normal arguments through args. For JavaScript use command="node", args=["-e", "<complete script>"] without an extra shell or surrounding quotes. Legacy Windows PowerShell can strip quotes when forwarding native arguments.'
402
+ };
403
+ }
404
+ return {
405
+ kind: "non-zero-exit",
406
+ message: result.signal === null ? `Command exited with code ${result.exitCode ?? "unknown"}.` : `Command was terminated by signal ${result.signal}.`
407
+ };
408
+ }
409
+ function errorCode(error) {
410
+ return error instanceof Error && "code" in error ? String(error.code) : void 0;
411
+ }
412
+ function createShellTool(workspace, options = {}) {
413
+ const root = resolve(workspace);
414
+ const environment = shellEnvironment(root, options.environment ?? process.env);
415
+ const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
416
+ const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
417
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
418
+ if (!Number.isSafeInteger(defaultTimeoutMs) || defaultTimeoutMs <= 0 || defaultTimeoutMs > 864e5) {
419
+ throw new Error("defaultTimeoutMs must be a positive integer no greater than 86400000");
420
+ }
421
+ return {
422
+ name: "Shell",
423
+ description: 'Run a bounded command reliably inside the workspace. Pass a bare executable in command and one logical value per args entry, without adding surrounding shell quotes. For Node scripts use command="node", args=["-e", "<complete JavaScript>"]; do not wrap native commands in cmd /c or PowerShell -Command unless shell syntax is needed, since legacy PowerShell can strip embedded quotes. Use Grep for text searches and Read/Edit for file changes. stdout/stderr are returned verbatim and failures include a structured diagnostic. background=true returns a managed job id.',
424
+ inputSchema: ShellInput,
425
+ paths: (input) => [workingDirectory(root, input.cwd ?? void 0)],
426
+ summarize: (input) => [input.command, ...input.args].join(" "),
427
+ presentCall: (input) => ({ kind: "terminal", variant: "command", title: backgroundFlag(input.background) ? "Starting background command" : "Running command", command: [input.command, ...input.args].join(" "), state: "running" }),
428
+ permissions: (input) => {
429
+ const normalized = normalizeShellCommand(input);
430
+ return {
431
+ paths: [workingDirectory(root, input.cwd ?? void 0)],
432
+ command: normalized.command,
433
+ args: normalized.args,
434
+ cwd: workingDirectory(root, input.cwd ?? void 0)
435
+ };
436
+ },
437
+ presentResult: (output, input) => output && "jobId" in output ? {
438
+ kind: "job",
439
+ action: "start",
440
+ id: output.jobId,
441
+ jobKind: "shell",
442
+ label: [input.command, ...input.args].join(" "),
443
+ state: "running"
444
+ } : { kind: "terminal", variant: "command", title: [input.command, ...input.args].join(" "), command: [input.command, ...input.args].join(" "), stdout: output.stdout, stderr: output.stderr, ...output.diagnostic === void 0 ? {} : { diagnostic: output.diagnostic.message }, exitCode: output.exitCode, state: output.terminationReason === "cancelled" ? "cancelled" : output.exitCode === 0 ? "completed" : "failed", truncated: output.truncated },
445
+ execute: async (input, signal, context) => {
446
+ const normalized = normalizeShellCommand(input);
447
+ const effective = { ...input, command: normalized.command, args: normalized.args };
448
+ const timeoutMs = effective.timeoutMs ?? defaultTimeoutMs;
449
+ if (backgroundFlag(effective.background)) {
450
+ if (options.jobs === void 0) throw new Error("Background shell requires a JobRegistry");
451
+ const cancellation = new AbortController();
452
+ const job = options.jobs.create({
453
+ kind: "shell",
454
+ owner: owner(context),
455
+ label: [effective.command, ...effective.args].join(" "),
456
+ cancel: () => cancellation.abort(new Error("Background job cancelled"))
457
+ });
458
+ void (async () => {
459
+ try {
460
+ let result2;
461
+ if (options.executionEnvironment === void 0) {
462
+ result2 = await executeShell(root, { ...effective, timeoutMs }, cancellation.signal, maxBytes, environment, (stream, text) => job.append(stream === "stderr" ? `[stderr] ${text}` : text));
463
+ } else {
464
+ const invocation2 = executionEnvironmentCommand(options.executionEnvironment, effective.command, effective.args);
465
+ const raw = await options.executionEnvironment.exec({
466
+ ...invocation2,
467
+ cwd: workingDirectory(root, effective.cwd ?? void 0),
468
+ timeoutMs,
469
+ maxOutputBytes: maxBytes
470
+ }, cancellation.signal);
471
+ result2 = boundedExecutionResult(raw, maxBytes, effective.command);
472
+ job.append(result2.stdout);
473
+ if (result2.stderr !== "") job.append(`[stderr] ${result2.stderr}`);
474
+ }
475
+ job.complete({
476
+ exitCode: result2.exitCode,
477
+ ...result2.diagnostic === void 0 ? {} : { error: result2.diagnostic.message }
478
+ });
479
+ } catch (error) {
480
+ job.fail(error);
481
+ }
482
+ })();
483
+ return {
484
+ jobId: job.id,
485
+ state: "running",
486
+ command: [effective.command, ...effective.args].join(" "),
487
+ exitCode: null,
488
+ signal: null,
489
+ stdout: "",
490
+ stderr: "",
491
+ truncated: false,
492
+ truncation: {
493
+ stdout: { truncated: false, originalBytes: 0, limitBytes: maxBytes },
494
+ stderr: { truncated: false, originalBytes: 0, limitBytes: maxBytes }
495
+ },
496
+ terminationReason: null
497
+ };
498
+ }
499
+ if (options.executionEnvironment === void 0) {
500
+ return executeShell(root, { ...effective, timeoutMs }, signal, maxBytes, environment);
501
+ }
502
+ const invocation = executionEnvironmentCommand(options.executionEnvironment, effective.command, effective.args);
503
+ const result = await options.executionEnvironment.exec({
504
+ ...invocation,
505
+ cwd: workingDirectory(root, effective.cwd ?? void 0),
506
+ timeoutMs,
507
+ maxOutputBytes: maxBytes
508
+ }, signal);
509
+ return boundedExecutionResult(result, maxBytes, effective.command);
510
+ }
511
+ };
512
+ }
513
+ async function executeShell(root, input, cancellation, maxBytes, environment = process.env, onOutput) {
514
+ const cwd = workingDirectory(root, input.cwd ?? void 0);
515
+ const stdout = new BoundedOutput(maxBytes);
516
+ const stderr = new BoundedOutput(maxBytes);
517
+ return new Promise((resolvePromise, reject) => {
518
+ if (cancellation.aborted) {
519
+ reject(cancellation.reason);
520
+ return;
521
+ }
522
+ const invocation = shellInvocation(input, cwd, environment);
523
+ const child = spawn2(invocation.command, invocation.args, {
524
+ cwd,
525
+ shell: false,
526
+ windowsHide: true,
527
+ detached: process.platform !== "win32",
528
+ env: environment,
529
+ ...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
530
+ });
531
+ let exitObserved = false;
532
+ let observedExitCode = null;
533
+ let observedSignal = null;
534
+ let settled = false;
535
+ let terminationReason = null;
536
+ let termination;
537
+ let terminalTimer;
538
+ let streamCloseTimer;
539
+ const stdoutDecoder = onOutput === void 0 ? void 0 : new AdaptiveOutputDecoder();
540
+ const stderrDecoder = onOutput === void 0 ? void 0 : new AdaptiveOutputDecoder();
541
+ const terminate = (reason) => {
542
+ if (termination !== void 0 || settled) return;
543
+ terminationReason = reason;
544
+ if (exitObserved || child.exitCode !== null || child.signalCode !== null) {
545
+ child.stdout.destroy();
546
+ child.stderr.destroy();
547
+ void finishResolve(observedExitCode ?? child.exitCode, observedSignal ?? child.signalCode);
548
+ return;
549
+ }
550
+ termination = terminateProcessTree(child.pid);
551
+ terminalTimer = setTimeout(() => finishReject(new Error(`Process did not close after ${reason} termination`)), TERMINATION_FAILURE_MS);
552
+ terminalTimer.unref();
553
+ };
554
+ const timeoutMs = input.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
555
+ let timer;
556
+ child.once("spawn", () => {
557
+ if (timeoutMs === void 0 || exitObserved || settled) return;
558
+ timer = setTimeout(() => terminate("timeout"), timeoutMs);
559
+ timer.unref();
560
+ });
561
+ const onCancel = () => terminate("cancelled");
562
+ cancellation.addEventListener("abort", onCancel, { once: true });
563
+ child.stdout.on("data", (chunk) => {
564
+ stdout.add(chunk);
565
+ if (stdoutDecoder !== void 0) {
566
+ const text = stdoutDecoder.write(chunk);
567
+ if (text !== "") onOutput?.("stdout", text);
568
+ }
569
+ });
570
+ child.stderr.on("data", (chunk) => {
571
+ stderr.add(chunk);
572
+ if (stderrDecoder !== void 0) {
573
+ const text = stderrDecoder.write(chunk);
574
+ if (text !== "") onOutput?.("stderr", text);
575
+ }
576
+ });
577
+ child.once("exit", (exitCode, exitSignal) => {
578
+ exitObserved = true;
579
+ observedExitCode = exitCode;
580
+ observedSignal = exitSignal;
581
+ if (timer !== void 0) clearTimeout(timer);
582
+ streamCloseTimer = setTimeout(() => {
583
+ child.stdout.destroy();
584
+ child.stderr.destroy();
585
+ void finishResolve(observedExitCode, observedSignal);
586
+ }, STREAM_CLOSE_GRACE_MS);
587
+ streamCloseTimer.unref();
588
+ });
589
+ child.once("error", (error) => {
590
+ void finishResolve(errorCode(error) === "ENOENT" ? 127 : 1, null, error);
591
+ });
592
+ child.once("close", (exitCode, signal) => {
593
+ void finishResolve(exitCode, signal);
594
+ });
595
+ async function finishResolve(exitCode, exitSignal, spawnError) {
596
+ if (settled) return;
597
+ settled = true;
598
+ cleanup();
599
+ if (termination !== void 0) await termination;
600
+ const stdoutTail = stdoutDecoder?.end() ?? "";
601
+ const stderrTail = stderrDecoder?.end() ?? "";
602
+ if (stdoutTail !== "") onOutput?.("stdout", stdoutTail);
603
+ if (stderrTail !== "") onOutput?.("stderr", stderrTail);
604
+ resolvePromise(withShellDiagnostic({
605
+ exitCode,
606
+ signal: exitSignal,
607
+ stdout: stdout.text(),
608
+ stderr: stderr.text(),
609
+ truncated: stdout.truncated || stderr.truncated,
610
+ truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() },
611
+ terminationReason
612
+ }, input.command, spawnError));
613
+ }
614
+ function finishReject(error) {
615
+ if (settled) return;
616
+ settled = true;
617
+ cleanup();
618
+ reject(error);
619
+ }
620
+ function cleanup() {
621
+ if (timer !== void 0) clearTimeout(timer);
622
+ if (terminalTimer !== void 0) clearTimeout(terminalTimer);
623
+ if (streamCloseTimer !== void 0) clearTimeout(streamCloseTimer);
624
+ cancellation.removeEventListener("abort", onCancel);
625
+ }
626
+ });
627
+ }
628
+ var BoundedOutput = class {
629
+ #limit;
630
+ #headLimit;
631
+ #tailLimit;
632
+ #head = Buffer.alloc(0);
633
+ #tailChunks = [];
634
+ #tailBytes = 0;
635
+ #complete = Buffer.alloc(0);
636
+ #bytes = 0;
637
+ constructor(limit) {
638
+ this.#limit = limit;
639
+ this.#headLimit = Math.ceil(limit / 2);
640
+ this.#tailLimit = Math.floor(limit / 2);
641
+ }
642
+ get truncated() {
643
+ return this.#bytes > this.#limit;
644
+ }
645
+ add(chunk) {
646
+ this.#bytes += chunk.length;
647
+ if (this.#complete.length < this.#limit) {
648
+ this.#complete = Buffer.concat([this.#complete, chunk]).subarray(0, this.#limit);
649
+ }
650
+ if (this.#head.length < this.#headLimit) {
651
+ const count = Math.min(this.#headLimit - this.#head.length, chunk.length);
652
+ this.#head = Buffer.concat([this.#head, chunk.subarray(0, count)]);
653
+ }
654
+ if (this.#tailLimit > 0) {
655
+ this.#tailChunks.push(chunk);
656
+ this.#tailBytes += chunk.length;
657
+ if (this.#tailBytes >= this.#tailLimit * 2) {
658
+ const merged = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
659
+ this.#tailChunks = [merged];
660
+ this.#tailBytes = merged.length;
661
+ }
662
+ }
663
+ }
664
+ metadata() {
665
+ return { truncated: this.truncated, originalBytes: this.#bytes, limitBytes: this.#limit };
666
+ }
667
+ text() {
668
+ if (!this.truncated) return decodeOutput(this.#complete);
669
+ const tail = Buffer.concat(this.#tailChunks).subarray(-this.#tailLimit);
670
+ const encoding = outputEncoding(this.#complete);
671
+ return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(tail, encoding)}`;
672
+ }
673
+ };
674
+ var AdaptiveOutputDecoder = class {
675
+ #pending = Buffer.alloc(0);
676
+ #decoder;
677
+ write(chunk) {
678
+ if (this.#decoder !== void 0) return this.#decoder.decode(chunk, { stream: true });
679
+ this.#pending = Buffer.concat([this.#pending, chunk]);
680
+ if (this.#pending.length === 1 && (this.#pending[0] === 255 || this.#pending[0] === 254)) return "";
681
+ if (!this.#pending.includes(0) && this.#pending.every((byte) => byte < 128)) {
682
+ const ascii = this.#pending.toString("ascii");
683
+ this.#pending = Buffer.alloc(0);
684
+ return ascii;
685
+ }
686
+ const classification = classifyUtf8(this.#pending);
687
+ if (classification === "incomplete") return "";
688
+ const encoding = outputEncoding(this.#pending, classification);
689
+ this.#decoder = new TextDecoder(encoding);
690
+ const text = this.#decoder.decode(this.#pending, { stream: true });
691
+ this.#pending = Buffer.alloc(0);
692
+ return text;
693
+ }
694
+ end() {
695
+ if (this.#decoder !== void 0) return this.#decoder.decode();
696
+ const text = decodeOutput(this.#pending);
697
+ this.#pending = Buffer.alloc(0);
698
+ return text;
699
+ }
700
+ };
701
+ function backgroundFlag(value) {
702
+ return value === true || value === "true";
703
+ }
704
+ function workingDirectory(root, cwd = ".") {
705
+ const candidate = resolve(root, cwd);
706
+ const delta = relative(root, candidate);
707
+ if (delta === ".." || delta.startsWith(`..${sep}`) || isAbsolute(delta)) throw new Error("Working directory is outside the workspace");
708
+ return candidate;
709
+ }
710
+ function decodeOutput(buffer) {
711
+ const encoding = outputEncoding(buffer);
712
+ return new TextDecoder(encoding).decode(buffer);
713
+ }
714
+ function shellEnvironment(root, environment) {
715
+ const localBin = join(root, "node_modules", ".bin");
716
+ if (!existsSync(localBin)) return environment;
717
+ const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
718
+ const current = environment[pathKey] ?? "";
719
+ const entries = current.split(delimiter).filter(Boolean);
720
+ if (entries.some((entry) => resolve(entry) === resolve(localBin))) return environment;
721
+ return { ...environment, [pathKey]: [localBin, ...entries].join(delimiter) };
722
+ }
723
+ function boundedExecutionResult(result, maxBytes, command) {
724
+ if (result.truncation !== void 0) {
725
+ return withShellDiagnostic({
726
+ ...result,
727
+ truncated: result.truncated ?? (result.truncation.stdout.truncated || result.truncation.stderr.truncated),
728
+ truncation: result.truncation
729
+ }, command);
730
+ }
731
+ const stdout = new BoundedOutput(maxBytes);
732
+ const stderr = new BoundedOutput(maxBytes);
733
+ stdout.add(Buffer.from(result.stdout));
734
+ stderr.add(Buffer.from(result.stderr));
735
+ return withShellDiagnostic({
736
+ ...result,
737
+ stdout: stdout.text(),
738
+ stderr: stderr.text(),
739
+ truncated: stdout.truncated || stderr.truncated,
740
+ truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() }
741
+ }, command);
742
+ }
743
+ function outputEncoding(buffer, utf8 = classifyUtf8(buffer)) {
744
+ if (buffer.length >= 2) {
745
+ if (buffer[0] === 255 && buffer[1] === 254) return "utf-16le";
746
+ if (buffer[0] === 254 && buffer[1] === 255) return "utf-16be";
747
+ }
748
+ const sampleLength = Math.min(buffer.length, 256);
749
+ let evenZeros = 0;
750
+ let oddZeros = 0;
751
+ for (let index = 0; index < sampleLength; index += 1) {
752
+ if (buffer[index] !== 0) continue;
753
+ if (index % 2 === 0) evenZeros += 1;
754
+ else oddZeros += 1;
755
+ }
756
+ const threshold = Math.max(2, Math.floor(sampleLength / 8));
757
+ if (oddZeros >= threshold && oddZeros > evenZeros * 2) return "utf-16le";
758
+ if (evenZeros >= threshold && evenZeros > oddZeros * 2) return "utf-16be";
759
+ return utf8 === "valid" || process.platform !== "win32" ? "utf-8" : "gb18030";
760
+ }
761
+ function decodePrefix(buffer, encoding) {
762
+ for (let end = buffer.length; end >= Math.max(0, buffer.length - 3); end -= 1) {
763
+ const candidate = buffer.subarray(0, end);
764
+ try {
765
+ return new TextDecoder(encoding, { fatal: true }).decode(candidate);
766
+ } catch {
767
+ }
768
+ }
769
+ return new TextDecoder(encoding).decode(buffer);
770
+ }
771
+ function decodeSuffix(buffer, encoding) {
772
+ for (let start = 0; start <= Math.min(3, buffer.length); start += 1) {
773
+ const candidate = buffer.subarray(start);
774
+ try {
775
+ return new TextDecoder(encoding, { fatal: true }).decode(candidate);
776
+ } catch {
777
+ }
778
+ }
779
+ return new TextDecoder(encoding).decode(buffer);
780
+ }
781
+ function classifyUtf8(buffer) {
782
+ try {
783
+ new TextDecoder("utf-8", { fatal: true }).decode(buffer);
784
+ return "valid";
785
+ } catch {
786
+ const suffixLength = incompleteUtf8SuffixLength(buffer);
787
+ if (suffixLength === 0) return "invalid";
788
+ try {
789
+ new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, buffer.length - suffixLength));
790
+ return "incomplete";
791
+ } catch {
792
+ return "invalid";
793
+ }
794
+ }
795
+ }
796
+ function incompleteUtf8SuffixLength(buffer) {
797
+ for (let start = Math.max(0, buffer.length - 3); start < buffer.length; start += 1) {
798
+ const lead = buffer[start];
799
+ const expected = lead >= 194 && lead <= 223 ? 2 : lead >= 224 && lead <= 239 ? 3 : lead >= 240 && lead <= 244 ? 4 : 0;
800
+ if (expected === 0 || buffer.length - start >= expected) continue;
801
+ let continuation = true;
802
+ for (let index = start + 1; index < buffer.length; index += 1) {
803
+ if ((buffer[index] & 192) !== 128) {
804
+ continuation = false;
805
+ break;
806
+ }
807
+ }
808
+ if (continuation) return buffer.length - start;
809
+ }
810
+ return 0;
811
+ }
812
+
813
+ // src/doctor.ts
814
+ async function runDoctor(options = {}, dependencies = {}) {
815
+ const workspace = resolve2(options.workspace ?? process.cwd());
816
+ const home = resolve2(options.home ?? homedir());
817
+ const environment = options.environment ?? process.env;
818
+ const platform = options.platform ?? process.platform;
819
+ const arch2 = options.arch ?? process.arch;
820
+ const nodeVersion = options.nodeVersion ?? process.version;
821
+ const checks = [];
822
+ checks.push(runtimeCheck(nodeVersion, platform, arch2));
823
+ checks.push(await directoryCheck("workspace", workspace, false));
824
+ checks.push(await directoryCheck("state directory", join2(home, ".flavor-code"), true, home));
825
+ let loaded;
826
+ try {
827
+ loaded = await (dependencies.loadConfig ?? loadConfig)({
828
+ cwd: workspace,
829
+ home,
830
+ environment,
831
+ seedGlobalEnv: false
832
+ });
833
+ checks.push({
834
+ name: "configuration",
835
+ status: "pass",
836
+ message: loaded.sources.length === 0 ? "defaults are valid; no config files found" : `valid (${loaded.sources.length} source${loaded.sources.length === 1 ? "" : "s"})`
837
+ });
838
+ } catch (error) {
839
+ checks.push({ name: "configuration", status: "fail", message: errorMessage(error) });
840
+ }
841
+ checks.push(providerCheck(loaded?.config, environment));
842
+ checks.push(await searchCheck(dependencies.rgPath ?? rgPath));
843
+ try {
844
+ const shell = await (dependencies.probeShell ?? probeShell)(platform, environment);
845
+ checks.push(shell === void 0 ? { name: "shell", status: "fail", message: "no usable command shell found" } : { name: "shell", status: "pass", message: shell });
846
+ } catch (error) {
847
+ checks.push({ name: "shell", status: "fail", message: errorMessage(error) });
848
+ }
849
+ checks.push(await pluginCheck([
850
+ join2(home, ".flavor-code", "plugins"),
851
+ join2(workspace, ".flavor", "plugins")
852
+ ]));
853
+ try {
854
+ const latest = await (dependencies.fetchLatestVersion ?? fetchLatestVersion)({
855
+ timeoutMs: options.registryTimeoutMs ?? 3e3
856
+ });
857
+ checks.push(latest === void 0 ? { name: "npm registry", status: "warn", message: "unreachable or returned an invalid response" } : { name: "npm registry", status: "pass", message: `reachable; latest flavor-code is v${latest}` });
858
+ } catch (error) {
859
+ checks.push({ name: "npm registry", status: "warn", message: errorMessage(error) });
860
+ }
861
+ const summary = {
862
+ passed: checks.filter(({ status }) => status === "pass").length,
863
+ warnings: checks.filter(({ status }) => status === "warn").length,
864
+ failed: checks.filter(({ status }) => status === "fail").length
865
+ };
866
+ return {
867
+ version: packageVersion(),
868
+ platform,
869
+ arch: arch2,
870
+ workspace,
871
+ checks,
872
+ summary,
873
+ ok: summary.failed === 0
874
+ };
875
+ }
876
+ function formatDoctorReport(report) {
877
+ const labels = { pass: "ok", warn: "warn", fail: "fail" };
878
+ const lines = [
879
+ `Flavor Doctor v${report.version}`,
880
+ `Platform: ${report.platform} ${report.arch}`,
881
+ `Workspace: ${report.workspace}`,
882
+ "",
883
+ ...report.checks.map((check) => `[${labels[check.status]}] ${check.name}: ${check.message}`),
884
+ "",
885
+ `Summary: ${report.summary.passed} passed, ${report.summary.warnings} warning${report.summary.warnings === 1 ? "" : "s"}, ${report.summary.failed} failed`
886
+ ];
887
+ return `${lines.join("\n")}
888
+ `;
889
+ }
890
+ function runtimeCheck(version, platform, arch2) {
891
+ const major = Number.parseInt(version.replace(/^v/u, "").split(".")[0] ?? "", 10);
892
+ if (!Number.isInteger(major) || !gte(`${major}.0.0`, "20.0.0")) {
893
+ return { name: "runtime", status: "fail", message: `Node ${version} is unsupported; Node 20+ is required` };
894
+ }
895
+ return { name: "runtime", status: "pass", message: `Node ${version} (${platform} ${arch2})` };
896
+ }
897
+ async function directoryCheck(name, path, mayBeCreated, parent = path) {
898
+ try {
899
+ const info = await stat(path);
900
+ if (!info.isDirectory()) return { name, status: "fail", message: `${path} is not a directory` };
901
+ await access(path, constants.R_OK | constants.W_OK);
902
+ return { name, status: "pass", message: `${path} is readable and writable` };
903
+ } catch (error) {
904
+ if (mayBeCreated && errorCode2(error) === "ENOENT") {
905
+ try {
906
+ await access(parent, constants.R_OK | constants.W_OK);
907
+ return { name, status: "pass", message: `${path} can be created` };
908
+ } catch (parentError) {
909
+ return { name, status: "fail", message: errorMessage(parentError) };
910
+ }
911
+ }
912
+ return { name, status: "fail", message: errorMessage(error) };
913
+ }
914
+ }
915
+ function providerCheck(config, environment) {
916
+ if (config === void 0) return { name: "providers", status: "warn", message: "skipped because configuration is invalid" };
917
+ const configured = Object.keys(config.providers);
918
+ const environmentProviders = [
919
+ environment.OPENAI_API_KEY ? "openai env" : void 0,
920
+ environment.ANTHROPIC_API_KEY ? "anthropic env" : void 0
921
+ ].filter((value) => value !== void 0);
922
+ if (configured.length === 0 && environmentProviders.length === 0) {
923
+ return { name: "providers", status: "warn", message: "no provider or API credential detected" };
924
+ }
925
+ const parts = [
926
+ configured.length > 0 ? `${configured.length} configured` : void 0,
927
+ environmentProviders.length > 0 ? environmentProviders.join(", ") : void 0
928
+ ].filter((value) => value !== void 0);
929
+ return { name: "providers", status: "pass", message: parts.join("; ") };
930
+ }
931
+ async function searchCheck(path) {
932
+ try {
933
+ await access(path, constants.R_OK);
934
+ return { name: "search", status: "pass", message: `bundled ripgrep found at ${path}` };
935
+ } catch (error) {
936
+ return { name: "search", status: "fail", message: `bundled ripgrep is unavailable: ${errorMessage(error)}` };
937
+ }
938
+ }
939
+ async function probeShell(platform, environment) {
940
+ const shell = resolveRuntimeShell(platform, environment);
941
+ if (shell === void 0) return void 0;
942
+ const sentinel = "flavor-shell-ok";
943
+ const args = shell.kind === "posix" ? ["-c", `printf '${sentinel}'`] : shell.kind === "cmd" ? ["/d", "/s", "/c", `echo|set /p=${sentinel}`] : ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", `[Console]::Out.Write('${sentinel}')`];
944
+ const result = await execFileNoThrow(shell.command, args, { timeout: 2e3, useCwd: false, env: environment });
945
+ if (result.code !== 0 || result.stdout !== sentinel) {
946
+ const detail = result.stderr.trim() || result.error || `unexpected output ${JSON.stringify(result.stdout)}`;
947
+ throw new Error(`${shell.command} was found but failed its execution probe: ${detail}`);
948
+ }
949
+ return `${shell.command} (${shell.kind}; execution probe passed)`;
950
+ }
951
+ async function pluginCheck(directories) {
952
+ let valid = 0;
953
+ const diagnostics = [];
954
+ for (const directory of directories) {
955
+ let entries;
956
+ try {
957
+ entries = await readdir(directory, { withFileTypes: true });
958
+ } catch (error) {
959
+ if (errorCode2(error) !== "ENOENT") diagnostics.push(`${directory}: ${errorMessage(error)}`);
960
+ continue;
961
+ }
962
+ for (const entry of entries) {
963
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
964
+ const root = resolve2(directory, entry.name);
965
+ try {
966
+ const rootInfo = await lstat(root);
967
+ if (rootInfo.isSymbolicLink()) throw new Error("plugin directory must not be a symbolic link");
968
+ const manifest = PluginManifestSchema.parse(JSON.parse(await readFile(join2(root, "flavor-plugin.json"), "utf8")));
969
+ const main = resolve2(root, manifest.main);
970
+ const delta = relative2(root, main);
971
+ if (delta === ".." || delta.startsWith(`..${sep2}`) || isAbsolute2(delta)) throw new Error("plugin entry escapes its root");
972
+ const mainInfo = await lstat(main);
973
+ if (!mainInfo.isFile() || mainInfo.isSymbolicLink()) throw new Error("plugin entry must be a regular file");
974
+ valid += 1;
975
+ } catch (error) {
976
+ diagnostics.push(`${entry.name}: ${errorMessage(error)}`);
977
+ }
978
+ }
979
+ }
980
+ if (diagnostics.length > 0) {
981
+ return { name: "plugins", status: "warn", message: `${valid} valid; ${diagnostics.join("; ")}` };
982
+ }
983
+ return { name: "plugins", status: "pass", message: valid === 0 ? "no project or global plugins found" : `${valid} manifest${valid === 1 ? "" : "s"} valid` };
984
+ }
985
+ function errorCode2(error) {
986
+ return error instanceof Error && "code" in error ? String(error.code) : void 0;
987
+ }
988
+ function errorMessage(error) {
989
+ return error instanceof Error ? error.message : String(error);
990
+ }
991
+
992
+ export {
993
+ rgPath,
994
+ createJobTools,
995
+ owner,
996
+ terminateProcessTree,
997
+ DEFAULT_MAX_OUTPUT_BYTES,
998
+ resolveRuntimeShell,
999
+ createShellTool,
1000
+ executeShell,
1001
+ runDoctor,
1002
+ formatDoctorReport
1003
+ };