@warlock.js/ai-cli 0.0.0-stage → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +69 -2
  4. package/cjs/index.cjs +4568 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/adapters/aider.d.mts +41 -0
  7. package/esm/adapters/aider.d.mts.map +1 -0
  8. package/esm/adapters/aider.mjs +294 -0
  9. package/esm/adapters/aider.mjs.map +1 -0
  10. package/esm/adapters/amp.d.mts +33 -0
  11. package/esm/adapters/amp.d.mts.map +1 -0
  12. package/esm/adapters/amp.mjs +119 -0
  13. package/esm/adapters/amp.mjs.map +1 -0
  14. package/esm/adapters/antigravity.d.mts +37 -0
  15. package/esm/adapters/antigravity.d.mts.map +1 -0
  16. package/esm/adapters/antigravity.mjs +235 -0
  17. package/esm/adapters/antigravity.mjs.map +1 -0
  18. package/esm/adapters/claude-code.d.mts +30 -0
  19. package/esm/adapters/claude-code.d.mts.map +1 -0
  20. package/esm/adapters/claude-code.mjs +121 -0
  21. package/esm/adapters/claude-code.mjs.map +1 -0
  22. package/esm/adapters/claude-stream.d.mts +32 -0
  23. package/esm/adapters/claude-stream.d.mts.map +1 -0
  24. package/esm/adapters/claude-stream.mjs +140 -0
  25. package/esm/adapters/claude-stream.mjs.map +1 -0
  26. package/esm/adapters/codex.d.mts +37 -0
  27. package/esm/adapters/codex.d.mts.map +1 -0
  28. package/esm/adapters/codex.mjs +322 -0
  29. package/esm/adapters/codex.mjs.map +1 -0
  30. package/esm/adapters/copilot.d.mts +35 -0
  31. package/esm/adapters/copilot.d.mts.map +1 -0
  32. package/esm/adapters/copilot.mjs +242 -0
  33. package/esm/adapters/copilot.mjs.map +1 -0
  34. package/esm/adapters/cursor.d.mts +35 -0
  35. package/esm/adapters/cursor.d.mts.map +1 -0
  36. package/esm/adapters/cursor.mjs +203 -0
  37. package/esm/adapters/cursor.mjs.map +1 -0
  38. package/esm/adapters/droid.d.mts +32 -0
  39. package/esm/adapters/droid.d.mts.map +1 -0
  40. package/esm/adapters/droid.mjs +183 -0
  41. package/esm/adapters/droid.mjs.map +1 -0
  42. package/esm/adapters/grok.d.mts +32 -0
  43. package/esm/adapters/grok.d.mts.map +1 -0
  44. package/esm/adapters/grok.mjs +197 -0
  45. package/esm/adapters/grok.mjs.map +1 -0
  46. package/esm/adapters/index.d.mts +17 -0
  47. package/esm/adapters/index.mjs +18 -0
  48. package/esm/adapters/kimi.d.mts +34 -0
  49. package/esm/adapters/kimi.d.mts.map +1 -0
  50. package/esm/adapters/kimi.mjs +162 -0
  51. package/esm/adapters/kimi.mjs.map +1 -0
  52. package/esm/adapters/mistral-vibe.d.mts +32 -0
  53. package/esm/adapters/mistral-vibe.d.mts.map +1 -0
  54. package/esm/adapters/mistral-vibe.mjs +250 -0
  55. package/esm/adapters/mistral-vibe.mjs.map +1 -0
  56. package/esm/adapters/opencode.d.mts +36 -0
  57. package/esm/adapters/opencode.d.mts.map +1 -0
  58. package/esm/adapters/opencode.mjs +219 -0
  59. package/esm/adapters/opencode.mjs.map +1 -0
  60. package/esm/adapters/qwen-code.d.mts +30 -0
  61. package/esm/adapters/qwen-code.d.mts.map +1 -0
  62. package/esm/adapters/qwen-code.mjs +94 -0
  63. package/esm/adapters/qwen-code.mjs.map +1 -0
  64. package/esm/adapters/reasonix.d.mts +31 -0
  65. package/esm/adapters/reasonix.d.mts.map +1 -0
  66. package/esm/adapters/reasonix.mjs +248 -0
  67. package/esm/adapters/reasonix.mjs.map +1 -0
  68. package/esm/adapters/shared.d.mts +81 -0
  69. package/esm/adapters/shared.d.mts.map +1 -0
  70. package/esm/adapters/shared.mjs +161 -0
  71. package/esm/adapters/shared.mjs.map +1 -0
  72. package/esm/cli-agent.d.mts +41 -0
  73. package/esm/cli-agent.d.mts.map +1 -0
  74. package/esm/cli-agent.mjs +207 -0
  75. package/esm/cli-agent.mjs.map +1 -0
  76. package/esm/cli.d.mts +56 -0
  77. package/esm/cli.d.mts.map +1 -0
  78. package/esm/cli.mjs +61 -0
  79. package/esm/cli.mjs.map +1 -0
  80. package/esm/contracts/adapter.contract.d.mts +175 -0
  81. package/esm/contracts/adapter.contract.d.mts.map +1 -0
  82. package/esm/contracts/events.type.d.mts +123 -0
  83. package/esm/contracts/events.type.d.mts.map +1 -0
  84. package/esm/contracts/index.d.mts +5 -0
  85. package/esm/contracts/mcp.type.d.mts +28 -0
  86. package/esm/contracts/mcp.type.d.mts.map +1 -0
  87. package/esm/contracts/options.type.d.mts +112 -0
  88. package/esm/contracts/options.type.d.mts.map +1 -0
  89. package/esm/errors.d.mts +48 -0
  90. package/esm/errors.d.mts.map +1 -0
  91. package/esm/errors.mjs +32 -0
  92. package/esm/errors.mjs.map +1 -0
  93. package/esm/index.d.mts +33 -0
  94. package/esm/index.mjs +28 -0
  95. package/esm/model/cli-model.d.mts +49 -0
  96. package/esm/model/cli-model.d.mts.map +1 -0
  97. package/esm/model/cli-model.mjs +133 -0
  98. package/esm/model/cli-model.mjs.map +1 -0
  99. package/esm/probe/probe.d.mts +18 -0
  100. package/esm/probe/probe.d.mts.map +1 -0
  101. package/esm/probe/probe.mjs +148 -0
  102. package/esm/probe/probe.mjs.map +1 -0
  103. package/esm/session/aggregate.mjs +84 -0
  104. package/esm/session/aggregate.mjs.map +1 -0
  105. package/esm/session/environment.mjs +71 -0
  106. package/esm/session/environment.mjs.map +1 -0
  107. package/esm/session/run-directory.mjs +74 -0
  108. package/esm/session/run-directory.mjs.map +1 -0
  109. package/esm/session/session.mjs +188 -0
  110. package/esm/session/session.mjs.map +1 -0
  111. package/esm/spawn/invocation.d.mts +34 -0
  112. package/esm/spawn/invocation.d.mts.map +1 -0
  113. package/esm/spawn/invocation.mjs +76 -0
  114. package/esm/spawn/invocation.mjs.map +1 -0
  115. package/esm/spawn/process-tree.mjs +57 -0
  116. package/esm/spawn/process-tree.mjs.map +1 -0
  117. package/esm/spawn/resolve-command.d.mts +75 -0
  118. package/esm/spawn/resolve-command.d.mts.map +1 -0
  119. package/esm/spawn/resolve-command.mjs +109 -0
  120. package/esm/spawn/resolve-command.mjs.map +1 -0
  121. package/esm/stream/event-log.mjs +52 -0
  122. package/esm/stream/event-log.mjs.map +1 -0
  123. package/esm/stream/json-lines.d.mts +33 -0
  124. package/esm/stream/json-lines.d.mts.map +1 -0
  125. package/esm/stream/json-lines.mjs +65 -0
  126. package/esm/stream/json-lines.mjs.map +1 -0
  127. package/esm/stream/line-splitter.mjs +53 -0
  128. package/esm/stream/line-splitter.mjs.map +1 -0
  129. package/esm/utils/toml.d.mts +21 -0
  130. package/esm/utils/toml.d.mts.map +1 -0
  131. package/esm/utils/toml.mjs +53 -0
  132. package/esm/utils/toml.mjs.map +1 -0
  133. package/llms-full.txt +373 -0
  134. package/llms.txt +10 -0
  135. package/package.json +39 -4
  136. package/skills/README.md +13 -0
  137. package/skills/index.md +22 -0
  138. package/skills/run-cli-agents/SKILL.md +186 -0
  139. package/skills/write-a-cli-adapter/SKILL.md +173 -0
@@ -0,0 +1,109 @@
1
+ import path from "node:path";
2
+
3
+ //#region ../../packages/ai-cli/src/spawn/resolve-command.ts
4
+ /** Windows' default `PATHEXT` when the variable is unset. */
5
+ const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
6
+ /** Case-insensitive read on Windows (`Path` vs `PATH`), exact elsewhere. */
7
+ function readEnv(env, name, platform) {
8
+ if (platform !== "win32") return env[name];
9
+ const wanted = name.toUpperCase();
10
+ for (const [key, value] of Object.entries(env)) if (key.toUpperCase() === wanted && value !== void 0) return value;
11
+ }
12
+ function pathApi(platform) {
13
+ return platform === "win32" ? path.win32 : path.posix;
14
+ }
15
+ /**
16
+ * Find an executable the way the platform's shell would.
17
+ *
18
+ * POSIX walks `PATH`. Windows walks `PATH` trying each `PATHEXT` extension
19
+ * (`claude` → `claude.cmd`), and also accepts a name that already carries
20
+ * one of those extensions. A command containing a path separator is
21
+ * checked directly instead of searched.
22
+ *
23
+ * @example
24
+ * findExecutable("claude", { platform: "win32", env: { PATH: "C:\\npm", PATHEXT: ".EXE;.CMD" }, ... });
25
+ * // → "C:\\npm\\claude.cmd"
26
+ */
27
+ function findExecutable(command, lookup) {
28
+ const api = pathApi(lookup.platform);
29
+ const isWindows = lookup.platform === "win32";
30
+ const extensions = isWindows ? (readEnv(lookup.env, "PATHEXT", "win32") ?? DEFAULT_PATHEXT).split(";").map((extension) => extension.trim()).filter((extension) => extension.length > 0) : [];
31
+ const candidatesFor = (base) => {
32
+ if (!isWindows) return [base];
33
+ const hasKnownExtension = extensions.some((extension) => base.toUpperCase().endsWith(extension.toUpperCase()));
34
+ const withExtensions = extensions.map((extension) => base + extension.toLowerCase());
35
+ return hasKnownExtension ? [base, ...withExtensions] : withExtensions;
36
+ };
37
+ if ((isWindows ? /[\\/]/.test(command) : command.includes("/")) || api.isAbsolute(command)) return candidatesFor(api.resolve(command)).find((candidate) => lookup.isFile(candidate));
38
+ const directories = (readEnv(lookup.env, "PATH", lookup.platform) ?? "").split(isWindows ? ";" : ":").map((directory) => directory.trim().replace(/^"(.*)"$/, "$1")).filter((directory) => directory.length > 0);
39
+ for (const directory of directories) for (const candidate of candidatesFor(api.join(directory, command))) if (lookup.isFile(candidate)) return candidate;
40
+ }
41
+ /**
42
+ * Read the target of an npm (`cmd-shim`) or pnpm `.cmd` shim.
43
+ *
44
+ * Both generators end the shim with a line that runs the target with `%*`;
45
+ * the target is a quoted path relative to the shim's own directory
46
+ * (`"%dp0%\node_modules\pkg\cli.js"` for npm, `"%~dp0\..\pkg\cli.js"` for
47
+ * pnpm). A JavaScript target is run by Node; an `.exe`/`.com` target is run
48
+ * directly. Anything else returns `undefined` and the caller falls back to
49
+ * `cmd.exe`.
50
+ */
51
+ function parseCmdShim(content, shimPath) {
52
+ const directory = path.win32.dirname(shimPath);
53
+ const line = content.split(/\r?\n/).filter((line) => line.includes("%*")).at(-1);
54
+ if (line === void 0) return;
55
+ const raw = [...line.matchAll(/"(%~?dp0%?[\\/][^"]+)"/gi)].map((match) => match[1] ?? "").filter((target) => !/[\\/]node(\.exe)?$/i.test(target)).at(-1);
56
+ if (raw === void 0) return;
57
+ const relative = raw.replace(/^%~?dp0%?[\\/]/i, "");
58
+ const target = path.win32.resolve(directory, relative);
59
+ if (/\.(c|m)?js$/i.test(target)) return {
60
+ type: "node",
61
+ script: target
62
+ };
63
+ if (/\.(exe|com)$/i.test(target)) return {
64
+ type: "native",
65
+ file: target
66
+ };
67
+ }
68
+ /**
69
+ * Resolve a command to something `child_process.spawn` can start without a
70
+ * shell. On Windows a `.cmd`/`.bat` npm shim is unwrapped to
71
+ * `node <script>` (or its native `.exe`), so argv reaches the CLI intact
72
+ * with no `cmd.exe` parsing in between; any other batch file falls back
73
+ * to `cmd.exe`.
74
+ */
75
+ function resolveSpawnTarget(command, lookup) {
76
+ const executable = findExecutable(command, lookup);
77
+ if (executable === void 0) return;
78
+ if (lookup.platform !== "win32" || !/\.(cmd|bat)$/i.test(executable)) return {
79
+ type: "direct",
80
+ file: executable,
81
+ prefixArgs: [],
82
+ executable
83
+ };
84
+ const shim = parseCmdShim(lookup.readFile(executable) ?? "", executable);
85
+ if (shim?.type === "node") {
86
+ const bundledNode = path.win32.join(path.win32.dirname(executable), "node.exe");
87
+ return {
88
+ type: "direct",
89
+ file: lookup.isFile(bundledNode) ? bundledNode : lookup.execPath,
90
+ prefixArgs: [shim.script],
91
+ executable
92
+ };
93
+ }
94
+ if (shim?.type === "native" && lookup.isFile(shim.file)) return {
95
+ type: "direct",
96
+ file: shim.file,
97
+ prefixArgs: [],
98
+ executable
99
+ };
100
+ return {
101
+ type: "cmd",
102
+ file: executable,
103
+ executable
104
+ };
105
+ }
106
+
107
+ //#endregion
108
+ export { findExecutable, parseCmdShim, readEnv, resolveSpawnTarget };
109
+ //# sourceMappingURL=resolve-command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-command.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/spawn/resolve-command.ts"],"sourcesContent":["import path from \"node:path\";\n\n/**\n * Host facts the resolver needs, injected so every rule is a pure function\n * that tests can drive for Windows on any OS.\n */\nexport type CommandLookup = {\n platform: NodeJS.Platform;\n env: Record<string, string | undefined>;\n /** Whether a regular, runnable file exists at this absolute path. */\n isFile(filePath: string): boolean;\n /** Read a small text file (used only for npm `.cmd` shims). */\n readFile(filePath: string): string | undefined;\n /** Node binary used to run a JavaScript entry behind an npm shim. */\n execPath: string;\n};\n\n/**\n * How to start a resolved command.\n *\n * - `direct` — spawn `file` with `prefixArgs` + the CLI's argv, no shell.\n * - `cmd` — a batch file Node cannot spawn without a shell (Node refuses\n * `.cmd`/`.bat` since CVE-2024-27980); it runs through `cmd.exe` with\n * every argument quoted (see {@link buildInvocation}).\n */\nexport type SpawnTarget =\n | { type: \"direct\"; file: string; prefixArgs: string[]; executable: string }\n | { type: \"cmd\"; file: string; executable: string };\n\n/** Windows' default `PATHEXT` when the variable is unset. */\nconst DEFAULT_PATHEXT = \".COM;.EXE;.BAT;.CMD\";\n\n/** Case-insensitive read on Windows (`Path` vs `PATH`), exact elsewhere. */\nexport function readEnv(\n env: Record<string, string | undefined>,\n name: string,\n platform: NodeJS.Platform,\n): string | undefined {\n if (platform !== \"win32\") {\n return env[name];\n }\n\n const wanted = name.toUpperCase();\n\n for (const [key, value] of Object.entries(env)) {\n if (key.toUpperCase() === wanted && value !== undefined) {\n return value;\n }\n }\n\n return undefined;\n}\n\nfunction pathApi(platform: NodeJS.Platform): typeof path.posix {\n return platform === \"win32\" ? path.win32 : path.posix;\n}\n\n/**\n * Find an executable the way the platform's shell would.\n *\n * POSIX walks `PATH`. Windows walks `PATH` trying each `PATHEXT` extension\n * (`claude` → `claude.cmd`), and also accepts a name that already carries\n * one of those extensions. A command containing a path separator is\n * checked directly instead of searched.\n *\n * @example\n * findExecutable(\"claude\", { platform: \"win32\", env: { PATH: \"C:\\\\npm\", PATHEXT: \".EXE;.CMD\" }, ... });\n * // → \"C:\\\\npm\\\\claude.cmd\"\n */\nexport function findExecutable(command: string, lookup: CommandLookup): string | undefined {\n const api = pathApi(lookup.platform);\n const isWindows = lookup.platform === \"win32\";\n const extensions = isWindows\n ? (readEnv(lookup.env, \"PATHEXT\", \"win32\") ?? DEFAULT_PATHEXT)\n .split(\";\")\n .map((extension) => extension.trim())\n .filter((extension) => extension.length > 0)\n : [];\n\n const candidatesFor = (base: string): string[] => {\n if (!isWindows) {\n return [base];\n }\n\n const hasKnownExtension = extensions.some((extension) =>\n base.toUpperCase().endsWith(extension.toUpperCase()),\n );\n const withExtensions = extensions.map((extension) => base + extension.toLowerCase());\n\n return hasKnownExtension ? [base, ...withExtensions] : withExtensions;\n };\n\n const hasSeparator = isWindows ? /[\\\\/]/.test(command) : command.includes(\"/\");\n\n if (hasSeparator || api.isAbsolute(command)) {\n return candidatesFor(api.resolve(command)).find((candidate) => lookup.isFile(candidate));\n }\n\n const searchPath = readEnv(lookup.env, \"PATH\", lookup.platform) ?? \"\";\n const directories = searchPath\n .split(isWindows ? \";\" : \":\")\n .map((directory) => directory.trim().replace(/^\"(.*)\"$/, \"$1\"))\n .filter((directory) => directory.length > 0);\n\n for (const directory of directories) {\n for (const candidate of candidatesFor(api.join(directory, command))) {\n if (lookup.isFile(candidate)) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\n/** What an npm/pnpm `.cmd` shim launches. */\nexport type CmdShimTarget = { type: \"node\"; script: string } | { type: \"native\"; file: string };\n\n/**\n * Read the target of an npm (`cmd-shim`) or pnpm `.cmd` shim.\n *\n * Both generators end the shim with a line that runs the target with `%*`;\n * the target is a quoted path relative to the shim's own directory\n * (`\"%dp0%\\node_modules\\pkg\\cli.js\"` for npm, `\"%~dp0\\..\\pkg\\cli.js\"` for\n * pnpm). A JavaScript target is run by Node; an `.exe`/`.com` target is run\n * directly. Anything else returns `undefined` and the caller falls back to\n * `cmd.exe`.\n */\nexport function parseCmdShim(content: string, shimPath: string): CmdShimTarget | undefined {\n const directory = path.win32.dirname(shimPath);\n const lines = content.split(/\\r?\\n/).filter((line) => line.includes(\"%*\"));\n const line = lines.at(-1);\n\n if (line === undefined) {\n return undefined;\n }\n\n const quoted = [...line.matchAll(/\"(%~?dp0%?[\\\\/][^\"]+)\"/gi)]\n .map((match) => match[1] ?? \"\")\n .filter((target) => !/[\\\\/]node(\\.exe)?$/i.test(target));\n const raw = quoted.at(-1);\n\n if (raw === undefined) {\n return undefined;\n }\n\n const relative = raw.replace(/^%~?dp0%?[\\\\/]/i, \"\");\n const target = path.win32.resolve(directory, relative);\n\n if (/\\.(c|m)?js$/i.test(target)) {\n return { type: \"node\", script: target };\n }\n\n if (/\\.(exe|com)$/i.test(target)) {\n return { type: \"native\", file: target };\n }\n\n return undefined;\n}\n\n/**\n * Resolve a command to something `child_process.spawn` can start without a\n * shell. On Windows a `.cmd`/`.bat` npm shim is unwrapped to\n * `node <script>` (or its native `.exe`), so argv reaches the CLI intact\n * with no `cmd.exe` parsing in between; any other batch file falls back\n * to `cmd.exe`.\n */\nexport function resolveSpawnTarget(\n command: string,\n lookup: CommandLookup,\n): SpawnTarget | undefined {\n const executable = findExecutable(command, lookup);\n\n if (executable === undefined) {\n return undefined;\n }\n\n if (lookup.platform !== \"win32\" || !/\\.(cmd|bat)$/i.test(executable)) {\n return { type: \"direct\", file: executable, prefixArgs: [], executable };\n }\n\n const shim = parseCmdShim(lookup.readFile(executable) ?? \"\", executable);\n\n if (shim?.type === \"node\") {\n const bundledNode = path.win32.join(path.win32.dirname(executable), \"node.exe\");\n const node = lookup.isFile(bundledNode) ? bundledNode : lookup.execPath;\n\n return { type: \"direct\", file: node, prefixArgs: [shim.script], executable };\n }\n\n if (shim?.type === \"native\" && lookup.isFile(shim.file)) {\n return { type: \"direct\", file: shim.file, prefixArgs: [], executable };\n }\n\n return { type: \"cmd\", file: executable, executable };\n}\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ //#region ../../packages/ai-cli/src/stream/event-log.ts
2
+ /**
3
+ * An append-only event log that any number of async iterators can read.
4
+ * Each iterator starts at the first event and ends once the log is closed
5
+ * and drained, so a late consumer still sees the whole session.
6
+ */
7
+ var EventLog = class {
8
+ constructor() {
9
+ this.items = [];
10
+ this.closed = false;
11
+ this.waiters = [];
12
+ }
13
+ push(item) {
14
+ if (this.closed) return;
15
+ this.items.push(item);
16
+ this.wake();
17
+ }
18
+ close() {
19
+ this.closed = true;
20
+ this.wake();
21
+ }
22
+ get size() {
23
+ return this.items.length;
24
+ }
25
+ [Symbol.asyncIterator]() {
26
+ let index = 0;
27
+ return { next: async () => {
28
+ while (index >= this.items.length && !this.closed) await new Promise((resolve) => this.waiters.push(resolve));
29
+ if (index < this.items.length) {
30
+ const value = this.items[index];
31
+ index += 1;
32
+ return {
33
+ value,
34
+ done: false
35
+ };
36
+ }
37
+ return {
38
+ value: void 0,
39
+ done: true
40
+ };
41
+ } };
42
+ }
43
+ wake() {
44
+ const waiters = this.waiters;
45
+ this.waiters = [];
46
+ for (const waiter of waiters) waiter();
47
+ }
48
+ };
49
+
50
+ //#endregion
51
+ export { EventLog };
52
+ //# sourceMappingURL=event-log.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/stream/event-log.ts"],"sourcesContent":["/**\n * An append-only event log that any number of async iterators can read.\n * Each iterator starts at the first event and ends once the log is closed\n * and drained, so a late consumer still sees the whole session.\n */\nexport class EventLog<T> implements AsyncIterable<T> {\n private readonly items: T[] = [];\n private closed = false;\n private waiters: Array<() => void> = [];\n\n public push(item: T): void {\n if (this.closed) {\n return;\n }\n\n this.items.push(item);\n this.wake();\n }\n\n public close(): void {\n this.closed = true;\n this.wake();\n }\n\n public get size(): number {\n return this.items.length;\n }\n\n public [Symbol.asyncIterator](): AsyncIterator<T> {\n let index = 0;\n\n return {\n next: async (): Promise<IteratorResult<T>> => {\n while (index >= this.items.length && !this.closed) {\n await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n\n if (index < this.items.length) {\n const value = this.items[index] as T;\n\n index += 1;\n\n return { value, done: false };\n }\n\n return { value: undefined, done: true };\n },\n };\n }\n\n private wake(): void {\n const waiters = this.waiters;\n\n this.waiters = [];\n\n for (const waiter of waiters) {\n waiter();\n }\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,WAAb,MAAqD;;eACrB,CAAC;gBACd;iBACoB,CAAC;;CAEtC,AAAO,KAAK,MAAe;EACzB,IAAI,KAAK,QACP;EAGF,KAAK,MAAM,KAAK,IAAI;EACpB,KAAK,KAAK;CACZ;CAEA,AAAO,QAAc;EACnB,KAAK,SAAS;EACd,KAAK,KAAK;CACZ;CAEA,IAAW,OAAe;EACxB,OAAO,KAAK,MAAM;CACpB;CAEA,CAAQ,OAAO,iBAAmC;EAChD,IAAI,QAAQ;EAEZ,OAAO,EACL,MAAM,YAAwC;GAC5C,OAAO,SAAS,KAAK,MAAM,UAAU,CAAC,KAAK,QACzC,MAAM,IAAI,SAAe,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;GAGjE,IAAI,QAAQ,KAAK,MAAM,QAAQ;IAC7B,MAAM,QAAQ,KAAK,MAAM;IAEzB,SAAS;IAET,OAAO;KAAE;KAAO,MAAM;IAAM;GAC9B;GAEA,OAAO;IAAE,OAAO;IAAW,MAAM;GAAK;EACxC,EACF;CACF;CAEA,AAAQ,OAAa;EACnB,MAAM,UAAU,KAAK;EAErB,KAAK,UAAU,CAAC;EAEhB,KAAK,MAAM,UAAU,SACnB,OAAO;CAEX;AACF"}
@@ -0,0 +1,33 @@
1
+ import { CliParsedEvent } from "../contracts/events.type.mjs";
2
+ import { CliStreamParser } from "../contracts/adapter.contract.mjs";
3
+ import "../contracts/index.mjs";
4
+ //#region ../../packages/ai-cli/src/stream/json-lines.d.ts
5
+ /** A plain JSON object. */
6
+ export type JsonRecord = Record<string, unknown>;
7
+ /** `value` as a JSON object, or `undefined`. */
8
+ export declare function asRecord(value: unknown): JsonRecord | undefined;
9
+ /** `value` as an array, or an empty array. */
10
+ export declare function asArray(value: unknown): unknown[];
11
+ /** A string field, or `undefined`. */
12
+ export declare function stringField(record: JsonRecord | undefined, key: string): string | undefined;
13
+ /** A finite number field, or `undefined`. */
14
+ export declare function numberField(record: JsonRecord | undefined, key: string): number | undefined;
15
+ /** A nested object field, or `undefined`. */
16
+ export declare function recordField(record: JsonRecord | undefined, key: string): JsonRecord | undefined;
17
+ /** Tool output as text: strings verbatim, text-block arrays joined, anything else as JSON. */
18
+ export declare function outputText(value: unknown): string;
19
+ /** Parse one line as a JSON object; anything else (banners, blank lines) is `undefined`. */
20
+ export declare function parseJsonLine(line: string): JsonRecord | undefined;
21
+ /**
22
+ * Build a parser for a JSONL/NDJSON stream. Non-JSON lines (progress
23
+ * banners, warnings) are skipped; each JSON object goes to `onRecord`.
24
+ * `onEnd` may flush buffered state when stdout closes.
25
+ *
26
+ * @example
27
+ * const parser = jsonLinesParser((record) =>
28
+ * record.type === "text" ? [{ type: "message", text: String(record.text), delta: false }] : [],
29
+ * );
30
+ */
31
+ export declare function jsonLinesParser(onRecord: (record: JsonRecord) => CliParsedEvent[], onEnd?: () => CliParsedEvent[]): CliStreamParser;
32
+ //#endregion
33
+ //# sourceMappingURL=json-lines.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-lines.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/stream/json-lines.ts"],"mappings":";;;;;YAGY,aAAa;;wBAGT,SAAS,iBAAiB;;wBAO1B,QAAQ;;wBAKR,YAAY,QAAQ,wBAAwB;;wBAO5C,YAAY,QAAQ,wBAAwB;;wBAO5C,YAAY,QAAQ,wBAAwB,cAAc;;wBAK1D,WAAW;;wBAwBX,cAAc,eAAe;;;;;;;;;;;wBAwB7B,gBACd,WAAW,QAAQ,eAAe,kBAClC,cAAa,mBACZ"}
@@ -0,0 +1,65 @@
1
+ //#region ../../packages/ai-cli/src/stream/json-lines.ts
2
+ /** `value` as a JSON object, or `undefined`. */
3
+ function asRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
5
+ }
6
+ /** `value` as an array, or an empty array. */
7
+ function asArray(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+ /** A string field, or `undefined`. */
11
+ function stringField(record, key) {
12
+ const value = record?.[key];
13
+ return typeof value === "string" ? value : void 0;
14
+ }
15
+ /** A finite number field, or `undefined`. */
16
+ function numberField(record, key) {
17
+ const value = record?.[key];
18
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
19
+ }
20
+ /** A nested object field, or `undefined`. */
21
+ function recordField(record, key) {
22
+ return asRecord(record?.[key]);
23
+ }
24
+ /** Tool output as text: strings verbatim, text-block arrays joined, anything else as JSON. */
25
+ function outputText(value) {
26
+ if (typeof value === "string") return value;
27
+ if (value === void 0 || value === null) return "";
28
+ if (Array.isArray(value)) return value.map((item) => {
29
+ return stringField(asRecord(item), "text") ?? (typeof item === "string" ? item : JSON.stringify(item));
30
+ }).join("\n");
31
+ return JSON.stringify(value);
32
+ }
33
+ /** Parse one line as a JSON object; anything else (banners, blank lines) is `undefined`. */
34
+ function parseJsonLine(line) {
35
+ const trimmed = line.trim();
36
+ if (!trimmed.startsWith("{")) return;
37
+ try {
38
+ return asRecord(JSON.parse(trimmed));
39
+ } catch {
40
+ return;
41
+ }
42
+ }
43
+ /**
44
+ * Build a parser for a JSONL/NDJSON stream. Non-JSON lines (progress
45
+ * banners, warnings) are skipped; each JSON object goes to `onRecord`.
46
+ * `onEnd` may flush buffered state when stdout closes.
47
+ *
48
+ * @example
49
+ * const parser = jsonLinesParser((record) =>
50
+ * record.type === "text" ? [{ type: "message", text: String(record.text), delta: false }] : [],
51
+ * );
52
+ */
53
+ function jsonLinesParser(onRecord, onEnd = () => []) {
54
+ return {
55
+ line(line) {
56
+ const record = parseJsonLine(line);
57
+ return record === void 0 ? [] : onRecord(record);
58
+ },
59
+ end: onEnd
60
+ };
61
+ }
62
+
63
+ //#endregion
64
+ export { asArray, asRecord, jsonLinesParser, numberField, outputText, parseJsonLine, recordField, stringField };
65
+ //# sourceMappingURL=json-lines.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-lines.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/stream/json-lines.ts"],"sourcesContent":["import type { CliParsedEvent, CliStreamParser } from \"../contracts\";\n\n/** A plain JSON object. */\nexport type JsonRecord = Record<string, unknown>;\n\n/** `value` as a JSON object, or `undefined`. */\nexport function asRecord(value: unknown): JsonRecord | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as JsonRecord)\n : undefined;\n}\n\n/** `value` as an array, or an empty array. */\nexport function asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\n/** A string field, or `undefined`. */\nexport function stringField(record: JsonRecord | undefined, key: string): string | undefined {\n const value = record?.[key];\n\n return typeof value === \"string\" ? value : undefined;\n}\n\n/** A finite number field, or `undefined`. */\nexport function numberField(record: JsonRecord | undefined, key: string): number | undefined {\n const value = record?.[key];\n\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/** A nested object field, or `undefined`. */\nexport function recordField(record: JsonRecord | undefined, key: string): JsonRecord | undefined {\n return asRecord(record?.[key]);\n}\n\n/** Tool output as text: strings verbatim, text-block arrays joined, anything else as JSON. */\nexport function outputText(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (value === undefined || value === null) {\n return \"\";\n }\n\n if (Array.isArray(value)) {\n const texts = value.map((item) => {\n const block = asRecord(item);\n const text = stringField(block, \"text\");\n\n return text ?? (typeof item === \"string\" ? item : JSON.stringify(item));\n });\n\n return texts.join(\"\\n\");\n }\n\n return JSON.stringify(value);\n}\n\n/** Parse one line as a JSON object; anything else (banners, blank lines) is `undefined`. */\nexport function parseJsonLine(line: string): JsonRecord | undefined {\n const trimmed = line.trim();\n\n if (!trimmed.startsWith(\"{\")) {\n return undefined;\n }\n\n try {\n return asRecord(JSON.parse(trimmed));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build a parser for a JSONL/NDJSON stream. Non-JSON lines (progress\n * banners, warnings) are skipped; each JSON object goes to `onRecord`.\n * `onEnd` may flush buffered state when stdout closes.\n *\n * @example\n * const parser = jsonLinesParser((record) =>\n * record.type === \"text\" ? [{ type: \"message\", text: String(record.text), delta: false }] : [],\n * );\n */\nexport function jsonLinesParser(\n onRecord: (record: JsonRecord) => CliParsedEvent[],\n onEnd: () => CliParsedEvent[] = () => [],\n): CliStreamParser {\n return {\n line(line: string): CliParsedEvent[] {\n const record = parseJsonLine(line);\n\n return record === undefined ? [] : onRecord(record);\n },\n end: onEnd,\n };\n}\n"],"mappings":";;AAMA,SAAgB,SAAS,OAAwC;CAC/D,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACrE,QACD;AACN;;AAGA,SAAgB,QAAQ,OAA2B;CACjD,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;AACzC;;AAGA,SAAgB,YAAY,QAAgC,KAAiC;CAC3F,MAAM,QAAQ,SAAS;CAEvB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;;AAGA,SAAgB,YAAY,QAAgC,KAAiC;CAC3F,MAAM,QAAQ,SAAS;CAEvB,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;;AAGA,SAAgB,YAAY,QAAgC,KAAqC;CAC/F,OAAO,SAAS,SAAS,IAAI;AAC/B;;AAGA,SAAgB,WAAW,OAAwB;CACjD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI,UAAU,UAAa,UAAU,MACnC,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GAQrB,OAPc,MAAM,KAAK,SAAS;EAIhC,OAFa,YADC,SAAS,IACM,GAAG,MAEtB,MAAM,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI;CACvE,CAEW,CAAC,CAAC,KAAK,IAAI;CAGxB,OAAO,KAAK,UAAU,KAAK;AAC7B;;AAGA,SAAgB,cAAc,MAAsC;CAClE,MAAM,UAAU,KAAK,KAAK;CAE1B,IAAI,CAAC,QAAQ,WAAW,GAAG,GACzB;CAGF,IAAI;EACF,OAAO,SAAS,KAAK,MAAM,OAAO,CAAC;CACrC,QAAQ;EACN;CACF;AACF;;;;;;;;;;;AAYA,SAAgB,gBACd,UACA,cAAsC,CAAC,GACtB;CACjB,OAAO;EACL,KAAK,MAAgC;GACnC,MAAM,SAAS,cAAc,IAAI;GAEjC,OAAO,WAAW,SAAY,CAAC,IAAI,SAAS,MAAM;EACpD;EACA,KAAK;CACP;AACF"}
@@ -0,0 +1,53 @@
1
+ import { StringDecoder } from "node:string_decoder";
2
+
3
+ //#region ../../packages/ai-cli/src/stream/line-splitter.ts
4
+ /** Longest line kept in memory; a longer one is dropped (reported via `onOverflow`). */
5
+ const MAX_LINE_BYTES = 33554432;
6
+ /**
7
+ * Split a byte stream into lines. Multi-byte UTF-8 characters split across
8
+ * chunks are decoded correctly, `\r\n` and `\n` both end a line, and a final
9
+ * line without a newline is flushed by `end()`.
10
+ *
11
+ * @example
12
+ * const splitter = createLineSplitter((line) => console.log(line));
13
+ * splitter.write(Buffer.from('{"a":1}\n{"b"'));
14
+ * splitter.write(Buffer.from(":2}\n"));
15
+ * splitter.end();
16
+ */
17
+ function createLineSplitter(onLine, onOverflow = () => void 0) {
18
+ const decoder = new StringDecoder("utf8");
19
+ let pending = "";
20
+ let overflowed = false;
21
+ const consume = (text) => {
22
+ pending += text;
23
+ let newline = pending.indexOf("\n");
24
+ while (newline !== -1) {
25
+ const line = pending.slice(0, newline).replace(/\r$/, "");
26
+ pending = pending.slice(newline + 1);
27
+ if (overflowed) overflowed = false;
28
+ else onLine(line);
29
+ newline = pending.indexOf("\n");
30
+ }
31
+ if (pending.length > 33554432) {
32
+ pending = "";
33
+ if (!overflowed) {
34
+ overflowed = true;
35
+ onOverflow();
36
+ }
37
+ }
38
+ };
39
+ return {
40
+ write(chunk) {
41
+ consume(decoder.write(chunk));
42
+ },
43
+ end() {
44
+ consume(decoder.end());
45
+ if (pending.length > 0 && !overflowed) onLine(pending.replace(/\r$/, ""));
46
+ pending = "";
47
+ }
48
+ };
49
+ }
50
+
51
+ //#endregion
52
+ export { MAX_LINE_BYTES, createLineSplitter };
53
+ //# sourceMappingURL=line-splitter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"line-splitter.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/stream/line-splitter.ts"],"sourcesContent":["import { StringDecoder } from \"node:string_decoder\";\n\n/** Longest line kept in memory; a longer one is dropped (reported via `onOverflow`). */\nexport const MAX_LINE_BYTES = 32 * 1024 * 1024;\n\n/**\n * Split a byte stream into lines. Multi-byte UTF-8 characters split across\n * chunks are decoded correctly, `\\r\\n` and `\\n` both end a line, and a final\n * line without a newline is flushed by `end()`.\n *\n * @example\n * const splitter = createLineSplitter((line) => console.log(line));\n * splitter.write(Buffer.from('{\"a\":1}\\n{\"b\"'));\n * splitter.write(Buffer.from(\":2}\\n\"));\n * splitter.end();\n */\nexport function createLineSplitter(\n onLine: (line: string) => void,\n onOverflow: () => void = () => undefined,\n): { write(chunk: Buffer): void; end(): void } {\n const decoder = new StringDecoder(\"utf8\");\n let pending = \"\";\n let overflowed = false;\n\n const consume = (text: string): void => {\n pending += text;\n\n let newline = pending.indexOf(\"\\n\");\n\n while (newline !== -1) {\n const line = pending.slice(0, newline).replace(/\\r$/, \"\");\n\n pending = pending.slice(newline + 1);\n\n if (overflowed) {\n overflowed = false;\n } else {\n onLine(line);\n }\n\n newline = pending.indexOf(\"\\n\");\n }\n\n if (pending.length > MAX_LINE_BYTES) {\n pending = \"\";\n\n if (!overflowed) {\n overflowed = true;\n onOverflow();\n }\n }\n };\n\n return {\n write(chunk: Buffer): void {\n consume(decoder.write(chunk));\n },\n end(): void {\n consume(decoder.end());\n\n if (pending.length > 0 && !overflowed) {\n onLine(pending.replace(/\\r$/, \"\"));\n }\n\n pending = \"\";\n },\n };\n}\n"],"mappings":";;;;AAGA,MAAa,iBAAiB;;;;;;;;;;;;AAa9B,SAAgB,mBACd,QACA,mBAA+B,QACc;CAC7C,MAAM,UAAU,IAAI,cAAc,MAAM;CACxC,IAAI,UAAU;CACd,IAAI,aAAa;CAEjB,MAAM,WAAW,SAAuB;EACtC,WAAW;EAEX,IAAI,UAAU,QAAQ,QAAQ,IAAI;EAElC,OAAO,YAAY,IAAI;GACrB,MAAM,OAAO,QAAQ,MAAM,GAAG,OAAO,CAAC,CAAC,QAAQ,OAAO,EAAE;GAExD,UAAU,QAAQ,MAAM,UAAU,CAAC;GAEnC,IAAI,YACF,aAAa;QAEb,OAAO,IAAI;GAGb,UAAU,QAAQ,QAAQ,IAAI;EAChC;EAEA,IAAI,QAAQ,mBAAyB;GACnC,UAAU;GAEV,IAAI,CAAC,YAAY;IACf,aAAa;IACb,WAAW;GACb;EACF;CACF;CAEA,OAAO;EACL,MAAM,OAAqB;GACzB,QAAQ,QAAQ,MAAM,KAAK,CAAC;EAC9B;EACA,MAAY;GACV,QAAQ,QAAQ,IAAI,CAAC;GAErB,IAAI,QAAQ,SAAS,KAAK,CAAC,YACzB,OAAO,QAAQ,QAAQ,OAAO,EAAE,CAAC;GAGnC,UAAU;EACZ;CACF;AACF"}
@@ -0,0 +1,21 @@
1
+ //#region ../../packages/ai-cli/src/utils/toml.d.ts
2
+ /** A TOML value this writer supports. */
3
+ export type TomlValue = string | number | boolean | TomlValue[] | TomlTable;
4
+ /** A TOML table. */
5
+ export type TomlTable = {
6
+ [key: string]: TomlValue | undefined;
7
+ };
8
+ /**
9
+ * Serialize a table to TOML: scalars and arrays first, then each nested
10
+ * table as a `[dotted.header]` section. Enough for the CLI config files
11
+ * this package writes (Codex and Mistral Vibe MCP sections).
12
+ *
13
+ * @example
14
+ * toToml({ mcp_servers: { docs: { command: "npx", args: ["-y", "docs-mcp"] } } });
15
+ * // [mcp_servers.docs]
16
+ * // command = "npx"
17
+ * // args = ["-y", "docs-mcp"]
18
+ */
19
+ export declare function toToml(table: TomlTable, prefix?: string[]): string;
20
+ //#endregion
21
+ //# sourceMappingURL=toml.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toml.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/utils/toml.ts"],"mappings":";;YACY,wCAAwC,cAAc;;YAGtD;GAAe,cAAc;;;;;;;;;;;;;wBAmDzB,OAAO,OAAO,WAAW"}
@@ -0,0 +1,53 @@
1
+ //#region ../../packages/ai-cli/src/utils/toml.ts
2
+ function isTable(value) {
3
+ return typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ /** A bare key when possible, else a quoted one. */
6
+ function tomlKey(key) {
7
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);
8
+ }
9
+ /** A TOML scalar or inline value. JSON string escapes are valid TOML basic-string escapes. */
10
+ function inlineValue(value) {
11
+ if (typeof value === "string") return JSON.stringify(value);
12
+ if (typeof value === "number") {
13
+ if (!Number.isFinite(value)) throw new Error("TOML cannot encode a non-finite number");
14
+ return String(value);
15
+ }
16
+ if (typeof value === "boolean") return value ? "true" : "false";
17
+ if (Array.isArray(value)) return `[${value.map(inlineValue).join(", ")}]`;
18
+ return `{ ${Object.entries(value).filter((entry) => entry[1] !== void 0).map(([key, item]) => `${tomlKey(key)} = ${inlineValue(item)}`).join(", ")} }`;
19
+ }
20
+ /**
21
+ * Serialize a table to TOML: scalars and arrays first, then each nested
22
+ * table as a `[dotted.header]` section. Enough for the CLI config files
23
+ * this package writes (Codex and Mistral Vibe MCP sections).
24
+ *
25
+ * @example
26
+ * toToml({ mcp_servers: { docs: { command: "npx", args: ["-y", "docs-mcp"] } } });
27
+ * // [mcp_servers.docs]
28
+ * // command = "npx"
29
+ * // args = ["-y", "docs-mcp"]
30
+ */
31
+ function toToml(table, prefix = []) {
32
+ const lines = [];
33
+ const nested = [];
34
+ for (const [key, value] of Object.entries(table)) {
35
+ if (value === void 0) continue;
36
+ if (isTable(value)) {
37
+ nested.push([key, value]);
38
+ continue;
39
+ }
40
+ lines.push(`${tomlKey(key)} = ${inlineValue(value)}`);
41
+ }
42
+ const sections = [];
43
+ if (lines.length > 0) sections.push((prefix.length > 0 ? `[${prefix.map(tomlKey).join(".")}]\n` : "") + lines.join("\n"));
44
+ for (const [key, value] of nested) {
45
+ const section = toToml(value, [...prefix, key]);
46
+ if (section.length > 0) sections.push(section.trimEnd());
47
+ }
48
+ return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
49
+ }
50
+
51
+ //#endregion
52
+ export { toToml, tomlKey };
53
+ //# sourceMappingURL=toml.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toml.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/utils/toml.ts"],"sourcesContent":["/** A TOML value this writer supports. */\nexport type TomlValue = string | number | boolean | TomlValue[] | TomlTable;\n\n/** A TOML table. */\nexport type TomlTable = { [key: string]: TomlValue | undefined };\n\nfunction isTable(value: TomlValue): value is TomlTable {\n return typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** A bare key when possible, else a quoted one. */\nexport function tomlKey(key: string): string {\n return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);\n}\n\n/** A TOML scalar or inline value. JSON string escapes are valid TOML basic-string escapes. */\nfunction inlineValue(value: TomlValue): string {\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw new Error(\"TOML cannot encode a non-finite number\");\n }\n\n return String(value);\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(inlineValue).join(\", \")}]`;\n }\n\n const entries = Object.entries(value).filter(\n (entry): entry is [string, TomlValue] => entry[1] !== undefined,\n );\n\n return `{ ${entries.map(([key, item]) => `${tomlKey(key)} = ${inlineValue(item)}`).join(\", \")} }`;\n}\n\n/**\n * Serialize a table to TOML: scalars and arrays first, then each nested\n * table as a `[dotted.header]` section. Enough for the CLI config files\n * this package writes (Codex and Mistral Vibe MCP sections).\n *\n * @example\n * toToml({ mcp_servers: { docs: { command: \"npx\", args: [\"-y\", \"docs-mcp\"] } } });\n * // [mcp_servers.docs]\n * // command = \"npx\"\n * // args = [\"-y\", \"docs-mcp\"]\n */\nexport function toToml(table: TomlTable, prefix: string[] = []): string {\n const lines: string[] = [];\n const nested: Array<[string, TomlTable]> = [];\n\n for (const [key, value] of Object.entries(table)) {\n if (value === undefined) {\n continue;\n }\n\n if (isTable(value)) {\n nested.push([key, value]);\n continue;\n }\n\n lines.push(`${tomlKey(key)} = ${inlineValue(value)}`);\n }\n\n const sections: string[] = [];\n\n if (lines.length > 0) {\n sections.push(\n (prefix.length > 0 ? `[${prefix.map(tomlKey).join(\".\")}]\\n` : \"\") + lines.join(\"\\n\"),\n );\n }\n\n for (const [key, value] of nested) {\n const section = toToml(value, [...prefix, key]);\n\n if (section.length > 0) {\n sections.push(section.trimEnd());\n }\n }\n\n return sections.length > 0 ? `${sections.join(\"\\n\\n\")}\\n` : \"\";\n}\n"],"mappings":";AAMA,SAAS,QAAQ,OAAsC;CACrD,OAAO,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC1D;;AAGA,SAAgB,QAAQ,KAAqB;CAC3C,OAAO,mBAAmB,KAAK,GAAG,IAAI,MAAM,KAAK,UAAU,GAAG;AAChE;;AAGA,SAAS,YAAY,OAA0B;CAC7C,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK,UAAU,KAAK;CAG7B,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GACxB,MAAM,IAAI,MAAM,wCAAwC;EAG1D,OAAO,OAAO,KAAK;CACrB;CAEA,IAAI,OAAO,UAAU,WACnB,OAAO,QAAQ,SAAS;CAG1B,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,IAAI,MAAM,IAAI,WAAW,CAAC,CAAC,KAAK,IAAI,EAAE;CAO/C,OAAO,KAJS,OAAO,QAAQ,KAAK,CAAC,CAAC,QACnC,UAAwC,MAAM,OAAO,MAGtC,CAAC,CAAC,KAAK,CAAC,KAAK,UAAU,GAAG,QAAQ,GAAG,EAAE,KAAK,YAAY,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;AAChG;;;;;;;;;;;;AAaA,SAAgB,OAAO,OAAkB,SAAmB,CAAC,GAAW;CACtE,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAqC,CAAC;CAE5C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAChD,IAAI,UAAU,QACZ;EAGF,IAAI,QAAQ,KAAK,GAAG;GAClB,OAAO,KAAK,CAAC,KAAK,KAAK,CAAC;GACxB;EACF;EAEA,MAAM,KAAK,GAAG,QAAQ,GAAG,EAAE,KAAK,YAAY,KAAK,GAAG;CACtD;CAEA,MAAM,WAAqB,CAAC;CAE5B,IAAI,MAAM,SAAS,GACjB,SAAS,MACN,OAAO,SAAS,IAAI,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,OAAO,MAAM,MAAM,KAAK,IAAI,CACrF;CAGF,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ;EACjC,MAAM,UAAU,OAAO,OAAO,CAAC,GAAG,QAAQ,GAAG,CAAC;EAE9C,IAAI,QAAQ,SAAS,GACnB,SAAS,KAAK,QAAQ,QAAQ,CAAC;CAEnC;CAEA,OAAO,SAAS,SAAS,IAAI,GAAG,SAAS,KAAK,MAAM,EAAE,MAAM;AAC9D"}