@warlock.js/ai-cli 0.0.0-stage → 5.31.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 +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +69 -2
  4. package/cjs/index.cjs +4576 -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 +320 -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 +244 -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 +199 -0
  45. package/esm/adapters/grok.mjs.map +1 -0
  46. package/esm/adapters/index.d.mts +16 -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 +252 -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 +250 -0
  67. package/esm/adapters/reasonix.mjs.map +1 -0
  68. package/esm/adapters/shared.d.mts +78 -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 +40 -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 +53 -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 +154 -0
  81. package/esm/contracts/adapter.contract.d.mts.map +1 -0
  82. package/esm/contracts/events.type.d.mts +111 -0
  83. package/esm/contracts/events.type.d.mts.map +1 -0
  84. package/esm/contracts/index.d.mts +4 -0
  85. package/esm/contracts/mcp.type.d.mts +29 -0
  86. package/esm/contracts/mcp.type.d.mts.map +1 -0
  87. package/esm/contracts/options.type.d.mts +87 -0
  88. package/esm/contracts/options.type.d.mts.map +1 -0
  89. package/esm/errors.d.mts +46 -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 +31 -0
  94. package/esm/index.mjs +28 -0
  95. package/esm/model/cli-model.d.mts +48 -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 +86 -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 +35 -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 +73 -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 +22 -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,48 @@
1
+ import { CliUsage } from "../contracts/events.type.mjs";
2
+ import { CliRunOptions } from "../contracts/options.type.mjs";
3
+ import { CliAgent } from "../cli-agent.mjs";
4
+ import { Message, ModelContract, ModelPricing, Usage } from "@warlock.js/ai";
5
+
6
+ //#region ../../packages/ai-cli/src/model/cli-model.d.ts
7
+ /** Configuration of a CLI-backed model. Run options apply to every call. */
8
+ type CliModelConfig = CliRunOptions & {
9
+ /** Model name reported to the agent, reports and observers. Default: `model`, else the adapter name. */name?: string; /** Per-million-token pricing, so the agent computes `Usage.cost` (and USD budgets apply). */
10
+ pricing?: ModelPricing;
11
+ };
12
+ /**
13
+ * Render an agent conversation into one CLI prompt. System messages become
14
+ * a leading instruction block; a single user turn is passed as-is; a longer
15
+ * history is rendered as a labelled transcript ending with the latest turn.
16
+ *
17
+ * @example
18
+ * renderPrompt([{ role: "system", content: "Be brief." }, { role: "user", content: "Hi" }], "codex");
19
+ * // "Be brief.\n\nHi"
20
+ */
21
+ declare function renderPrompt(messages: Message[], adapter: string): string;
22
+ /** Map CLI usage to the framework `Usage` (zeros when the CLI reported none). */
23
+ declare function toUsage(usage: CliUsage | undefined): Usage;
24
+ /**
25
+ * A `ModelContract` backed by a CLI agent, so `ai.agent({ model })` — and
26
+ * its budget, guardrail, observer and fallback middleware — can drive a
27
+ * coding CLI.
28
+ *
29
+ * Each call is one fresh CLI session: the conversation is rendered into a
30
+ * single prompt (see {@link renderPrompt}) and the session's final answer
31
+ * becomes the response. The CLI runs its own tools; framework tools cannot
32
+ * be handed to it this way (give the CLI MCP servers with `mcp` instead),
33
+ * so a call carrying `tools` is refused. `stream()` yields the final answer
34
+ * once the session ends — read `session.events` from `agent.run()` for live
35
+ * progress.
36
+ *
37
+ * @example
38
+ * const coder = cli.codex({ cwd: "/srv/repo" });
39
+ * const reviewer = ai.agent({
40
+ * model: cliModel(coder, { name: "codex" }),
41
+ * middleware: [budget({ maxTokens: 200_000 })],
42
+ * });
43
+ * const { text } = await reviewer.execute("Review the last commit.");
44
+ */
45
+ declare function cliModel(agent: CliAgent, config?: CliModelConfig): ModelContract;
46
+ //#endregion
47
+ export { CliModelConfig, cliModel, renderPrompt, toUsage };
48
+ //# sourceMappingURL=cli-model.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-model.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/model/cli-model.ts"],"mappings":";;;;;;;KAeY,cAAA,GAAiB,aAAA;0GAE3B,IAAA,WAFU;EAIV,OAAA,GAAU,YAAY;AAAA;;;;;;;AAAA;AAgCxB;;iBAAgB,YAAA,CAAa,QAAA,EAAU,OAAO,IAAI,OAAA;;iBA2BlC,OAAA,CAAQ,KAAA,EAAO,QAAA,eAAuB,KAAK;;;;AA3BM;AA2BjE;;;;;;;;AAA2D;AA2C3D;;;;;;;;iBAAgB,QAAA,CAAS,KAAA,EAAO,QAAA,EAAU,MAAA,GAAQ,cAAA,GAAsB,aAAA"}
@@ -0,0 +1,133 @@
1
+ import { CliError } from "../errors.mjs";
2
+
3
+ //#region ../../packages/ai-cli/src/model/cli-model.ts
4
+ /** The text of one message; CLI models accept text only. */
5
+ function messageText(message, adapter) {
6
+ if (typeof message.content === "string") return message.content;
7
+ return message.content.map((part) => {
8
+ if (part.type !== "text") throw new CliError(`${adapter}: CLI models accept text only; a "${part.type}" attachment cannot be passed to the CLI.`, {
9
+ type: "unsupported-option",
10
+ adapter
11
+ });
12
+ return part.text;
13
+ }).join("\n");
14
+ }
15
+ /**
16
+ * Render an agent conversation into one CLI prompt. System messages become
17
+ * a leading instruction block; a single user turn is passed as-is; a longer
18
+ * history is rendered as a labelled transcript ending with the latest turn.
19
+ *
20
+ * @example
21
+ * renderPrompt([{ role: "system", content: "Be brief." }, { role: "user", content: "Hi" }], "codex");
22
+ * // "Be brief.\n\nHi"
23
+ */
24
+ function renderPrompt(messages, adapter) {
25
+ const system = messages.filter((message) => message.role === "system").map((message) => messageText(message, adapter).trim()).filter((text) => text.length > 0);
26
+ const turns = messages.filter((message) => message.role !== "system");
27
+ const only = turns.length === 1 ? turns[0] : void 0;
28
+ const body = only !== void 0 && only.role === "user" ? messageText(only, adapter) : turns.map((message) => {
29
+ return `${message.role === "user" ? "User" : message.role === "assistant" ? "Assistant" : `Tool result${message.toolCallId ? ` (${message.toolCallId})` : ""}`}:\n${messageText(message, adapter)}`;
30
+ }).join("\n\n");
31
+ return [...system, body].filter((part) => part.length > 0).join("\n\n");
32
+ }
33
+ /** Map CLI usage to the framework `Usage` (zeros when the CLI reported none). */
34
+ function toUsage(usage) {
35
+ if (usage === void 0) return {
36
+ input: 0,
37
+ output: 0,
38
+ total: 0
39
+ };
40
+ const mapped = {
41
+ input: usage.input,
42
+ output: usage.output,
43
+ total: usage.total
44
+ };
45
+ if (usage.cachedTokens !== void 0) mapped.cachedTokens = usage.cachedTokens;
46
+ if (usage.cacheWriteTokens !== void 0) mapped.cacheWriteTokens = usage.cacheWriteTokens;
47
+ if (usage.reasoningTokens !== void 0) mapped.reasoningTokens = usage.reasoningTokens;
48
+ return mapped;
49
+ }
50
+ function abortError(message) {
51
+ const error = new Error(message);
52
+ error.name = "AbortError";
53
+ return error;
54
+ }
55
+ /**
56
+ * A `ModelContract` backed by a CLI agent, so `ai.agent({ model })` — and
57
+ * its budget, guardrail, observer and fallback middleware — can drive a
58
+ * coding CLI.
59
+ *
60
+ * Each call is one fresh CLI session: the conversation is rendered into a
61
+ * single prompt (see {@link renderPrompt}) and the session's final answer
62
+ * becomes the response. The CLI runs its own tools; framework tools cannot
63
+ * be handed to it this way (give the CLI MCP servers with `mcp` instead),
64
+ * so a call carrying `tools` is refused. `stream()` yields the final answer
65
+ * once the session ends — read `session.events` from `agent.run()` for live
66
+ * progress.
67
+ *
68
+ * @example
69
+ * const coder = cli.codex({ cwd: "/srv/repo" });
70
+ * const reviewer = ai.agent({
71
+ * model: cliModel(coder, { name: "codex" }),
72
+ * middleware: [budget({ maxTokens: 200_000 })],
73
+ * });
74
+ * const { text } = await reviewer.execute("Review the last commit.");
75
+ */
76
+ function cliModel(agent, config = {}) {
77
+ const { adapter } = agent;
78
+ const capabilities = { reasoning: adapter.effort === true };
79
+ const execute = async (messages, options = {}) => {
80
+ if (options.tools !== void 0 && options.tools.length > 0) throw new CliError(`${adapter.title}: a CLI model runs the CLI's own tools; pass framework tools to the CLI as MCP servers (\`mcp\`) instead of agent \`tools\`.`, {
81
+ type: "unsupported-option",
82
+ adapter: adapter.name
83
+ });
84
+ const { name: _name, pricing: _pricing, ...runDefaults } = config;
85
+ const effort = options.reasoning?.effort;
86
+ const result = await (await agent.run(renderPrompt(messages, adapter.name), {
87
+ ...runDefaults,
88
+ signal: options.signal ?? runDefaults.signal,
89
+ effort: adapter.effort === true && effort !== void 0 ? effort : runDefaults.effort
90
+ })).done();
91
+ if (result.status === "cancelled") throw abortError(result.error ?? `${adapter.name} run was cancelled`);
92
+ if (result.status !== "completed") throw new CliError(`${adapter.title}: ${result.error ?? `run ${result.status}`}`, {
93
+ type: "run-failed",
94
+ adapter: adapter.name,
95
+ context: {
96
+ status: result.status,
97
+ exitCode: result.exitCode,
98
+ sessionRef: result.sessionRef
99
+ }
100
+ });
101
+ return result;
102
+ };
103
+ return {
104
+ name: config.name ?? config.model ?? agent.options.model ?? adapter.name,
105
+ provider: adapter.name,
106
+ capabilities,
107
+ pricing: config.pricing,
108
+ async complete(messages, options) {
109
+ const result = await execute(messages, options);
110
+ return {
111
+ content: result.text,
112
+ finishReason: "stop",
113
+ usage: toUsage(result.usage)
114
+ };
115
+ },
116
+ async *stream(messages, options) {
117
+ const result = await execute(messages, options);
118
+ if (result.text.length > 0) yield {
119
+ type: "delta",
120
+ content: result.text
121
+ };
122
+ yield {
123
+ type: "done",
124
+ finishReason: "stop",
125
+ usage: toUsage(result.usage)
126
+ };
127
+ }
128
+ };
129
+ }
130
+
131
+ //#endregion
132
+ export { cliModel, renderPrompt, toUsage };
133
+ //# sourceMappingURL=cli-model.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-model.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/model/cli-model.ts"],"sourcesContent":["import type {\n Message,\n ModelCallOptions,\n ModelCapabilities,\n ModelContract,\n ModelPricing,\n ModelResponse,\n ModelStreamChunk,\n Usage,\n} from \"@warlock.js/ai\";\nimport type { CliAgent } from \"../cli-agent\";\nimport type { CliResult, CliRunOptions, CliUsage } from \"../contracts\";\nimport { CliError } from \"../errors\";\n\n/** Configuration of a CLI-backed model. Run options apply to every call. */\nexport type CliModelConfig = CliRunOptions & {\n /** Model name reported to the agent, reports and observers. Default: `model`, else the adapter name. */\n name?: string;\n /** Per-million-token pricing, so the agent computes `Usage.cost` (and USD budgets apply). */\n pricing?: ModelPricing;\n};\n\n/** The text of one message; CLI models accept text only. */\nfunction messageText(message: Message, adapter: string): string {\n if (typeof message.content === \"string\") {\n return message.content;\n }\n\n return message.content\n .map((part) => {\n if (part.type !== \"text\") {\n throw new CliError(\n `${adapter}: CLI models accept text only; a \"${part.type}\" attachment cannot be passed to the CLI.`,\n { type: \"unsupported-option\", adapter },\n );\n }\n\n return part.text;\n })\n .join(\"\\n\");\n}\n\n/**\n * Render an agent conversation into one CLI prompt. System messages become\n * a leading instruction block; a single user turn is passed as-is; a longer\n * history is rendered as a labelled transcript ending with the latest turn.\n *\n * @example\n * renderPrompt([{ role: \"system\", content: \"Be brief.\" }, { role: \"user\", content: \"Hi\" }], \"codex\");\n * // \"Be brief.\\n\\nHi\"\n */\nexport function renderPrompt(messages: Message[], adapter: string): string {\n const system = messages\n .filter((message) => message.role === \"system\")\n .map((message) => messageText(message, adapter).trim())\n .filter((text) => text.length > 0);\n const turns = messages.filter((message) => message.role !== \"system\");\n const only = turns.length === 1 ? turns[0] : undefined;\n const body =\n only !== undefined && only.role === \"user\"\n ? messageText(only, adapter)\n : turns\n .map((message) => {\n const label =\n message.role === \"user\"\n ? \"User\"\n : message.role === \"assistant\"\n ? \"Assistant\"\n : `Tool result${message.toolCallId ? ` (${message.toolCallId})` : \"\"}`;\n\n return `${label}:\\n${messageText(message, adapter)}`;\n })\n .join(\"\\n\\n\");\n\n return [...system, body].filter((part) => part.length > 0).join(\"\\n\\n\");\n}\n\n/** Map CLI usage to the framework `Usage` (zeros when the CLI reported none). */\nexport function toUsage(usage: CliUsage | undefined): Usage {\n if (usage === undefined) {\n return { input: 0, output: 0, total: 0 };\n }\n\n const mapped: Usage = { input: usage.input, output: usage.output, total: usage.total };\n\n if (usage.cachedTokens !== undefined) mapped.cachedTokens = usage.cachedTokens;\n if (usage.cacheWriteTokens !== undefined) mapped.cacheWriteTokens = usage.cacheWriteTokens;\n if (usage.reasoningTokens !== undefined) mapped.reasoningTokens = usage.reasoningTokens;\n\n return mapped;\n}\n\nfunction abortError(message: string): Error {\n const error = new Error(message);\n\n error.name = \"AbortError\";\n\n return error;\n}\n\n/**\n * A `ModelContract` backed by a CLI agent, so `ai.agent({ model })` — and\n * its budget, guardrail, observer and fallback middleware — can drive a\n * coding CLI.\n *\n * Each call is one fresh CLI session: the conversation is rendered into a\n * single prompt (see {@link renderPrompt}) and the session's final answer\n * becomes the response. The CLI runs its own tools; framework tools cannot\n * be handed to it this way (give the CLI MCP servers with `mcp` instead),\n * so a call carrying `tools` is refused. `stream()` yields the final answer\n * once the session ends — read `session.events` from `agent.run()` for live\n * progress.\n *\n * @example\n * const coder = cli.codex({ cwd: \"/srv/repo\" });\n * const reviewer = ai.agent({\n * model: cliModel(coder, { name: \"codex\" }),\n * middleware: [budget({ maxTokens: 200_000 })],\n * });\n * const { text } = await reviewer.execute(\"Review the last commit.\");\n */\nexport function cliModel(agent: CliAgent, config: CliModelConfig = {}): ModelContract {\n const { adapter } = agent;\n const capabilities: ModelCapabilities = { reasoning: adapter.effort === true };\n\n const execute = async (\n messages: Message[],\n options: ModelCallOptions = {},\n ): Promise<CliResult> => {\n if (options.tools !== undefined && options.tools.length > 0) {\n throw new CliError(\n `${adapter.title}: a CLI model runs the CLI's own tools; pass framework tools to the CLI as MCP servers (\\`mcp\\`) instead of agent \\`tools\\`.`,\n { type: \"unsupported-option\", adapter: adapter.name },\n );\n }\n\n const { name: _name, pricing: _pricing, ...runDefaults } = config;\n const effort = options.reasoning?.effort;\n const session = await agent.run(renderPrompt(messages, adapter.name), {\n ...runDefaults,\n signal: options.signal ?? runDefaults.signal,\n effort: adapter.effort === true && effort !== undefined ? effort : runDefaults.effort,\n });\n const result = await session.done();\n\n if (result.status === \"cancelled\") {\n throw abortError(result.error ?? `${adapter.name} run was cancelled`);\n }\n\n if (result.status !== \"completed\") {\n throw new CliError(`${adapter.title}: ${result.error ?? `run ${result.status}`}`, {\n type: \"run-failed\",\n adapter: adapter.name,\n context: {\n status: result.status,\n exitCode: result.exitCode,\n sessionRef: result.sessionRef,\n },\n });\n }\n\n return result;\n };\n\n return {\n name: config.name ?? config.model ?? agent.options.model ?? adapter.name,\n provider: adapter.name,\n capabilities,\n pricing: config.pricing,\n async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n const result = await execute(messages, options);\n\n return { content: result.text, finishReason: \"stop\", usage: toUsage(result.usage) };\n },\n async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n const result = await execute(messages, options);\n\n if (result.text.length > 0) {\n yield { type: \"delta\", content: result.text };\n }\n\n yield { type: \"done\", finishReason: \"stop\", usage: toUsage(result.usage) };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ //#region ../../packages/ai-cli/src/probe/probe.d.ts
2
+ /**
3
+ * Whether a help screen lists a flag or subcommand. Flags (`--json`, `-p`)
4
+ * must appear as a whole token — `--json` does not match `--json-schema`;
5
+ * a bare word (`exec`) is matched as a whole word.
6
+ *
7
+ * @example
8
+ * helpLists(" -p, --print Print response", "--print"); // true
9
+ * helpLists(" --output-format <format>", "--output"); // false
10
+ */
11
+ declare function helpLists(help: string, flag: string): boolean;
12
+ /** The first version-looking token of `--version` output, else its first line. */
13
+ declare function parseVersion(output: string): string;
14
+ /** Flags a plan requires that the help screen does not list. */
15
+ declare function missingFlags(help: string, required: readonly string[]): string[];
16
+ //#endregion
17
+ export { helpLists, missingFlags, parseVersion };
18
+ //# sourceMappingURL=probe.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.mts","names":[],"sources":["../../../../../../../../packages/ai-cli/src/probe/probe.ts"],"mappings":";;;;AA0BoD;AAapD;;;;AAA2C;iBAb3B,SAAA,CAAU,IAAA,UAAc,IAAY;;iBAapC,YAAA,CAAa,MAAc;;iBAa3B,YAAA,CAAa,IAAA,UAAc,QAA2B"}
@@ -0,0 +1,148 @@
1
+ import { CliError } from "../errors.mjs";
2
+ import { killProcessTree } from "../spawn/process-tree.mjs";
3
+ import { buildInvocation } from "../spawn/invocation.mjs";
4
+ import { spawn } from "node:child_process";
5
+
6
+ //#region ../../packages/ai-cli/src/probe/probe.ts
7
+ /** How long a single `--help`/`--version` call may take. */
8
+ const PROBE_TIMEOUT_MS = 3e4;
9
+ /** Output cap per probe call; help screens are a few KiB. */
10
+ const PROBE_OUTPUT_LIMIT = 1024 * 1024;
11
+ function escapeRegExp(text) {
12
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
+ }
14
+ /**
15
+ * Whether a help screen lists a flag or subcommand. Flags (`--json`, `-p`)
16
+ * must appear as a whole token — `--json` does not match `--json-schema`;
17
+ * a bare word (`exec`) is matched as a whole word.
18
+ *
19
+ * @example
20
+ * helpLists(" -p, --print Print response", "--print"); // true
21
+ * helpLists(" --output-format <format>", "--output"); // false
22
+ */
23
+ function helpLists(help, flag) {
24
+ const escaped = escapeRegExp(flag);
25
+ if (flag.startsWith("-")) return new RegExp(`(^|[\\s,\\[(|/"'\`])${escaped}(?=$|[\\s,=\\])|<\\[.:;"'\`])`, "m").test(help);
26
+ return new RegExp(`(^|[^\\w-])${escaped}(?=$|[^\\w-])`, "m").test(help);
27
+ }
28
+ /** The first version-looking token of `--version` output, else its first line. */
29
+ function parseVersion(output) {
30
+ const match = /\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?/.exec(output);
31
+ if (match) return match[0];
32
+ const firstLine = output.trim().split(/\r?\n/)[0]?.trim();
33
+ return firstLine && firstLine.length > 0 ? firstLine : "unknown";
34
+ }
35
+ /** Flags a plan requires that the help screen does not list. */
36
+ function missingFlags(help, required) {
37
+ return required.filter((flag) => !helpLists(help, flag));
38
+ }
39
+ /** Run a probe invocation and capture stdout + stderr (help is printed to either). */
40
+ function capture(input) {
41
+ return new Promise((resolve) => {
42
+ const invocation = buildInvocation(input.target, input.args, input.comspec);
43
+ if ("refused" in invocation) {
44
+ resolve({
45
+ output: "",
46
+ exitCode: null,
47
+ error: new Error(invocation.refused)
48
+ });
49
+ return;
50
+ }
51
+ const child = spawn(invocation.file, invocation.args, {
52
+ cwd: input.cwd,
53
+ env: input.env,
54
+ shell: false,
55
+ windowsHide: true,
56
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments,
57
+ detached: input.platform !== "win32",
58
+ stdio: [
59
+ "ignore",
60
+ "pipe",
61
+ "pipe"
62
+ ]
63
+ });
64
+ const chunks = [];
65
+ let size = 0;
66
+ let settled = false;
67
+ const collect = (chunk) => {
68
+ if (size < PROBE_OUTPUT_LIMIT) {
69
+ chunks.push(chunk);
70
+ size += chunk.length;
71
+ }
72
+ };
73
+ const timer = setTimeout(() => {
74
+ killProcessTree(child, input.platform, 0);
75
+ }, PROBE_TIMEOUT_MS);
76
+ const finish = (exitCode, error) => {
77
+ if (settled) return;
78
+ settled = true;
79
+ clearTimeout(timer);
80
+ resolve({
81
+ output: Buffer.concat(chunks).toString("utf8"),
82
+ exitCode,
83
+ error
84
+ });
85
+ };
86
+ child.stdout?.on("data", collect);
87
+ child.stderr?.on("data", collect);
88
+ child.on("error", (error) => finish(null, error));
89
+ child.on("close", (code) => finish(code));
90
+ });
91
+ }
92
+ /**
93
+ * Read the CLI's version and help screens. A help invocation that cannot
94
+ * run at all (spawn error, no output) fails the probe; a non-zero exit with
95
+ * output is accepted, because several CLIs exit 1 after printing help.
96
+ */
97
+ async function probeCli(input) {
98
+ const { adapter } = input;
99
+ const versionRun = await capture({
100
+ ...input,
101
+ args: adapter.probe.version
102
+ });
103
+ if (versionRun.error !== void 0) throw new CliError(`${adapter.title}: could not run "${input.target.executable} ${adapter.probe.version.join(" ")}": ${versionRun.error.message}`, {
104
+ type: "probe-failed",
105
+ adapter: adapter.name,
106
+ executable: input.target.executable,
107
+ cause: versionRun.error
108
+ });
109
+ const version = parseVersion(versionRun.output);
110
+ const helps = [];
111
+ for (const args of adapter.probe.help) {
112
+ const helpRun = await capture({
113
+ ...input,
114
+ args
115
+ });
116
+ if (helpRun.error !== void 0 || helpRun.output.trim().length === 0) throw new CliError(`${adapter.title} ${version}: "${input.target.executable} ${args.join(" ")}" printed no help` + (helpRun.error ? ` (${helpRun.error.message})` : "") + "; the capability probe cannot verify the flags this adapter needs.", {
117
+ type: "probe-failed",
118
+ adapter: adapter.name,
119
+ version,
120
+ executable: input.target.executable,
121
+ cause: helpRun.error
122
+ });
123
+ helps.push(helpRun.output);
124
+ }
125
+ return {
126
+ executable: input.target.executable,
127
+ version,
128
+ help: helps.join("\n")
129
+ };
130
+ }
131
+ /** Throw a `capability` error naming the first required flag the help does not list. */
132
+ function assertCapabilities(adapter, probe, required) {
133
+ const missing = missingFlags(probe.help, required);
134
+ const flag = missing[0];
135
+ if (flag === void 0) return;
136
+ const helpCommands = adapter.probe.help.map((args) => `${adapter.command} ${args.join(" ")}`).join(", ");
137
+ throw new CliError(`${adapter.title} ${probe.version} (${probe.executable}) does not support ${missing.join(", ")}: not listed in "${helpCommands}". This adapter was verified against ${adapter.title} ${adapter.verification.version}; install a compatible version or drop the option that needs ${flag}.`, {
138
+ type: "capability",
139
+ adapter: adapter.name,
140
+ flag,
141
+ version: probe.version,
142
+ executable: probe.executable
143
+ });
144
+ }
145
+
146
+ //#endregion
147
+ export { assertCapabilities, helpLists, missingFlags, parseVersion, probeCli };
148
+ //# sourceMappingURL=probe.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/probe/probe.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport type { CliAdapter, CliProbeResult } from \"../contracts\";\nimport { CliError } from \"../errors\";\nimport { killProcessTree } from \"../spawn/process-tree\";\nimport { buildInvocation } from \"../spawn/invocation\";\nimport type { SpawnTarget } from \"../spawn/resolve-command\";\n\n/** How long a single `--help`/`--version` call may take. */\nexport const PROBE_TIMEOUT_MS = 30_000;\n\n/** Output cap per probe call; help screens are a few KiB. */\nconst PROBE_OUTPUT_LIMIT = 1024 * 1024;\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Whether a help screen lists a flag or subcommand. Flags (`--json`, `-p`)\n * must appear as a whole token — `--json` does not match `--json-schema`;\n * a bare word (`exec`) is matched as a whole word.\n *\n * @example\n * helpLists(\" -p, --print Print response\", \"--print\"); // true\n * helpLists(\" --output-format <format>\", \"--output\"); // false\n */\nexport function helpLists(help: string, flag: string): boolean {\n const escaped = escapeRegExp(flag);\n\n if (flag.startsWith(\"-\")) {\n return new RegExp(`(^|[\\\\s,\\\\[(|/\"'\\`])${escaped}(?=$|[\\\\s,=\\\\])|<\\\\[.:;\"'\\`])`, \"m\").test(\n help,\n );\n }\n\n return new RegExp(`(^|[^\\\\w-])${escaped}(?=$|[^\\\\w-])`, \"m\").test(help);\n}\n\n/** The first version-looking token of `--version` output, else its first line. */\nexport function parseVersion(output: string): string {\n const match = /\\d+\\.\\d+(?:\\.\\d+)?(?:[-+][0-9A-Za-z.-]+)?/.exec(output);\n\n if (match) {\n return match[0];\n }\n\n const firstLine = output.trim().split(/\\r?\\n/)[0]?.trim();\n\n return firstLine && firstLine.length > 0 ? firstLine : \"unknown\";\n}\n\n/** Flags a plan requires that the help screen does not list. */\nexport function missingFlags(help: string, required: readonly string[]): string[] {\n return required.filter((flag) => !helpLists(help, flag));\n}\n\ntype CaptureInput = {\n target: SpawnTarget;\n args: string[];\n env: Record<string, string>;\n cwd: string;\n comspec: string;\n platform: NodeJS.Platform;\n};\n\n/** Run a probe invocation and capture stdout + stderr (help is printed to either). */\nfunction capture(\n input: CaptureInput,\n): Promise<{ output: string; exitCode: number | null; error?: Error }> {\n return new Promise((resolve) => {\n const invocation = buildInvocation(input.target, input.args, input.comspec);\n\n if (\"refused\" in invocation) {\n resolve({ output: \"\", exitCode: null, error: new Error(invocation.refused) });\n\n return;\n }\n\n const child = spawn(invocation.file, invocation.args, {\n cwd: input.cwd,\n env: input.env,\n shell: false,\n windowsHide: true,\n windowsVerbatimArguments: invocation.windowsVerbatimArguments,\n detached: input.platform !== \"win32\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const chunks: Buffer[] = [];\n let size = 0;\n let settled = false;\n\n const collect = (chunk: Buffer): void => {\n if (size < PROBE_OUTPUT_LIMIT) {\n chunks.push(chunk);\n size += chunk.length;\n }\n };\n\n const timer = setTimeout(() => {\n killProcessTree(child, input.platform, 0);\n }, PROBE_TIMEOUT_MS);\n\n const finish = (exitCode: number | null, error?: Error): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n clearTimeout(timer);\n resolve({ output: Buffer.concat(chunks).toString(\"utf8\"), exitCode, error });\n };\n\n child.stdout?.on(\"data\", collect);\n child.stderr?.on(\"data\", collect);\n child.on(\"error\", (error) => finish(null, error));\n child.on(\"close\", (code) => finish(code));\n });\n}\n\nexport type ProbeInput = Omit<CaptureInput, \"args\"> & { adapter: CliAdapter };\n\n/**\n * Read the CLI's version and help screens. A help invocation that cannot\n * run at all (spawn error, no output) fails the probe; a non-zero exit with\n * output is accepted, because several CLIs exit 1 after printing help.\n */\nexport async function probeCli(input: ProbeInput): Promise<CliProbeResult> {\n const { adapter } = input;\n const versionRun = await capture({ ...input, args: adapter.probe.version });\n\n if (versionRun.error !== undefined) {\n throw new CliError(\n `${adapter.title}: could not run \"${input.target.executable} ${adapter.probe.version.join(\" \")}\": ${versionRun.error.message}`,\n {\n type: \"probe-failed\",\n adapter: adapter.name,\n executable: input.target.executable,\n cause: versionRun.error,\n },\n );\n }\n\n const version = parseVersion(versionRun.output);\n const helps: string[] = [];\n\n for (const args of adapter.probe.help) {\n const helpRun = await capture({ ...input, args });\n\n if (helpRun.error !== undefined || helpRun.output.trim().length === 0) {\n throw new CliError(\n `${adapter.title} ${version}: \"${input.target.executable} ${args.join(\" \")}\" printed no help` +\n (helpRun.error ? ` (${helpRun.error.message})` : \"\") +\n \"; the capability probe cannot verify the flags this adapter needs.\",\n {\n type: \"probe-failed\",\n adapter: adapter.name,\n version,\n executable: input.target.executable,\n cause: helpRun.error,\n },\n );\n }\n\n helps.push(helpRun.output);\n }\n\n return { executable: input.target.executable, version, help: helps.join(\"\\n\") };\n}\n\n/** Throw a `capability` error naming the first required flag the help does not list. */\nexport function assertCapabilities(\n adapter: CliAdapter,\n probe: CliProbeResult,\n required: readonly string[],\n): void {\n const missing = missingFlags(probe.help, required);\n const flag = missing[0];\n\n if (flag === undefined) {\n return;\n }\n\n const helpCommands = adapter.probe.help\n .map((args) => `${adapter.command} ${args.join(\" \")}`)\n .join(\", \");\n\n throw new CliError(\n `${adapter.title} ${probe.version} (${probe.executable}) does not support ${missing.join(\", \")}: ` +\n `not listed in \"${helpCommands}\". This adapter was verified against ${adapter.title} ` +\n `${adapter.verification.version}; install a compatible version or drop the option that needs ${flag}.`,\n {\n type: \"capability\",\n adapter: adapter.name,\n flag,\n version: probe.version,\n executable: probe.executable,\n },\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ //#region ../../packages/ai-cli/src/session/aggregate.ts
2
+ function sum(a, b) {
3
+ return a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
4
+ }
5
+ /** Add two usages field by field; optional fields stay absent unless either side has them. */
6
+ function addUsage(left, right) {
7
+ if (left === void 0) return { ...right };
8
+ const merged = {
9
+ input: left.input + right.input,
10
+ output: left.output + right.output,
11
+ total: left.total + right.total
12
+ };
13
+ const cachedTokens = sum(left.cachedTokens, right.cachedTokens);
14
+ const cacheWriteTokens = sum(left.cacheWriteTokens, right.cacheWriteTokens);
15
+ const reasoningTokens = sum(left.reasoningTokens, right.reasoningTokens);
16
+ const costUSD = sum(left.costUSD, right.costUSD);
17
+ if (cachedTokens !== void 0) merged.cachedTokens = cachedTokens;
18
+ if (cacheWriteTokens !== void 0) merged.cacheWriteTokens = cacheWriteTokens;
19
+ if (reasoningTokens !== void 0) merged.reasoningTokens = reasoningTokens;
20
+ if (costUSD !== void 0) merged.costUSD = costUSD;
21
+ return merged;
22
+ }
23
+ /**
24
+ * Folds parsed events into the pieces of a `CliResult`: the session ref,
25
+ * usage, every complete message, and the final answer.
26
+ *
27
+ * Final-answer rule: the last `final: true` message (the vendor's own
28
+ * result record) wins; otherwise the newest assistant text — a complete
29
+ * message, or the deltas streamed since the last message or tool activity.
30
+ */
31
+ var ResultAggregator = class {
32
+ constructor(resume) {
33
+ this.messages = [];
34
+ this.deltaBuffer = "";
35
+ this.sessionRef = resume;
36
+ }
37
+ add(event) {
38
+ switch (event.type) {
39
+ case "session.started":
40
+ if (event.sessionRef) this.sessionRef = event.sessionRef;
41
+ break;
42
+ case "message":
43
+ if (event.delta) {
44
+ this.deltaBuffer += event.text;
45
+ break;
46
+ }
47
+ if (event.final) {
48
+ this.finalText = event.text;
49
+ break;
50
+ }
51
+ this.messages.push(event.text);
52
+ this.lastCompleteText = event.text;
53
+ this.deltaBuffer = "";
54
+ break;
55
+ case "tool.call":
56
+ case "tool.result":
57
+ this.flushDeltas();
58
+ break;
59
+ case "usage":
60
+ this.usage = event.cumulative ? { ...event.usage } : addUsage(this.usage, event.usage);
61
+ break;
62
+ case "error":
63
+ if (event.fatal && this.fatalError === void 0) this.fatalError = event.message;
64
+ break;
65
+ }
66
+ }
67
+ /** Close the stream: deltas never followed by a complete message count as one. */
68
+ finish() {
69
+ this.flushDeltas();
70
+ }
71
+ get text() {
72
+ if (this.finalText !== void 0) return this.finalText;
73
+ return this.deltaBuffer.length > 0 ? this.deltaBuffer : this.lastCompleteText ?? "";
74
+ }
75
+ flushDeltas() {
76
+ if (this.deltaBuffer.trim().length > 0) {
77
+ this.messages.push(this.deltaBuffer);
78
+ this.lastCompleteText = this.deltaBuffer;
79
+ }
80
+ this.deltaBuffer = "";
81
+ }
82
+ };
83
+
84
+ //#endregion
85
+ export { ResultAggregator };
86
+ //# sourceMappingURL=aggregate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/session/aggregate.ts"],"sourcesContent":["import type { CliParsedEvent, CliUsage } from \"../contracts\";\n\nfunction sum(a: number | undefined, b: number | undefined): number | undefined {\n return a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);\n}\n\n/** Add two usages field by field; optional fields stay absent unless either side has them. */\nexport function addUsage(left: CliUsage | undefined, right: CliUsage): CliUsage {\n if (left === undefined) {\n return { ...right };\n }\n\n const merged: CliUsage = {\n input: left.input + right.input,\n output: left.output + right.output,\n total: left.total + right.total,\n };\n\n const cachedTokens = sum(left.cachedTokens, right.cachedTokens);\n const cacheWriteTokens = sum(left.cacheWriteTokens, right.cacheWriteTokens);\n const reasoningTokens = sum(left.reasoningTokens, right.reasoningTokens);\n const costUSD = sum(left.costUSD, right.costUSD);\n\n if (cachedTokens !== undefined) merged.cachedTokens = cachedTokens;\n if (cacheWriteTokens !== undefined) merged.cacheWriteTokens = cacheWriteTokens;\n if (reasoningTokens !== undefined) merged.reasoningTokens = reasoningTokens;\n if (costUSD !== undefined) merged.costUSD = costUSD;\n\n return merged;\n}\n\n/**\n * Folds parsed events into the pieces of a `CliResult`: the session ref,\n * usage, every complete message, and the final answer.\n *\n * Final-answer rule: the last `final: true` message (the vendor's own\n * result record) wins; otherwise the newest assistant text — a complete\n * message, or the deltas streamed since the last message or tool activity.\n */\nexport class ResultAggregator {\n public sessionRef?: string;\n public usage?: CliUsage;\n public readonly messages: string[] = [];\n public fatalError?: string;\n private finalText?: string;\n private deltaBuffer = \"\";\n private lastCompleteText?: string;\n\n public constructor(resume?: string) {\n this.sessionRef = resume;\n }\n\n public add(event: CliParsedEvent): void {\n switch (event.type) {\n case \"session.started\":\n if (event.sessionRef) {\n this.sessionRef = event.sessionRef;\n }\n break;\n case \"message\":\n if (event.delta) {\n this.deltaBuffer += event.text;\n break;\n }\n\n if (event.final) {\n this.finalText = event.text;\n break;\n }\n\n this.messages.push(event.text);\n this.lastCompleteText = event.text;\n this.deltaBuffer = \"\";\n break;\n case \"tool.call\":\n case \"tool.result\":\n this.flushDeltas();\n break;\n case \"usage\":\n this.usage = event.cumulative ? { ...event.usage } : addUsage(this.usage, event.usage);\n break;\n case \"error\":\n if (event.fatal && this.fatalError === undefined) {\n this.fatalError = event.message;\n }\n break;\n }\n }\n\n /** Close the stream: deltas never followed by a complete message count as one. */\n public finish(): void {\n this.flushDeltas();\n }\n\n public get text(): string {\n if (this.finalText !== undefined) {\n return this.finalText;\n }\n\n return this.deltaBuffer.length > 0 ? this.deltaBuffer : (this.lastCompleteText ?? \"\");\n }\n\n private flushDeltas(): void {\n if (this.deltaBuffer.trim().length > 0) {\n this.messages.push(this.deltaBuffer);\n this.lastCompleteText = this.deltaBuffer;\n }\n\n this.deltaBuffer = \"\";\n }\n}\n"],"mappings":";AAEA,SAAS,IAAI,GAAuB,GAA2C;CAC7E,OAAO,MAAM,UAAa,MAAM,SAAY,UAAa,KAAK,MAAM,KAAK;AAC3E;;AAGA,SAAgB,SAAS,MAA4B,OAA2B;CAC9E,IAAI,SAAS,QACX,OAAO,EAAE,GAAG,MAAM;CAGpB,MAAM,SAAmB;EACvB,OAAO,KAAK,QAAQ,MAAM;EAC1B,QAAQ,KAAK,SAAS,MAAM;EAC5B,OAAO,KAAK,QAAQ,MAAM;CAC5B;CAEA,MAAM,eAAe,IAAI,KAAK,cAAc,MAAM,YAAY;CAC9D,MAAM,mBAAmB,IAAI,KAAK,kBAAkB,MAAM,gBAAgB;CAC1E,MAAM,kBAAkB,IAAI,KAAK,iBAAiB,MAAM,eAAe;CACvE,MAAM,UAAU,IAAI,KAAK,SAAS,MAAM,OAAO;CAE/C,IAAI,iBAAiB,QAAW,OAAO,eAAe;CACtD,IAAI,qBAAqB,QAAW,OAAO,mBAAmB;CAC9D,IAAI,oBAAoB,QAAW,OAAO,kBAAkB;CAC5D,IAAI,YAAY,QAAW,OAAO,UAAU;CAE5C,OAAO;AACT;;;;;;;;;AAUA,IAAa,mBAAb,MAA8B;CAS5B,AAAO,YAAY,QAAiB;kBANC,CAAC;qBAGhB;EAIpB,KAAK,aAAa;CACpB;CAEA,AAAO,IAAI,OAA6B;EACtC,QAAQ,MAAM,MAAd;GACE,KAAK;IACH,IAAI,MAAM,YACR,KAAK,aAAa,MAAM;IAE1B;GACF,KAAK;IACH,IAAI,MAAM,OAAO;KACf,KAAK,eAAe,MAAM;KAC1B;IACF;IAEA,IAAI,MAAM,OAAO;KACf,KAAK,YAAY,MAAM;KACvB;IACF;IAEA,KAAK,SAAS,KAAK,MAAM,IAAI;IAC7B,KAAK,mBAAmB,MAAM;IAC9B,KAAK,cAAc;IACnB;GACF,KAAK;GACL,KAAK;IACH,KAAK,YAAY;IACjB;GACF,KAAK;IACH,KAAK,QAAQ,MAAM,aAAa,EAAE,GAAG,MAAM,MAAM,IAAI,SAAS,KAAK,OAAO,MAAM,KAAK;IACrF;GACF,KAAK;IACH,IAAI,MAAM,SAAS,KAAK,eAAe,QACrC,KAAK,aAAa,MAAM;IAE1B;EACJ;CACF;;CAGA,AAAO,SAAe;EACpB,KAAK,YAAY;CACnB;CAEA,IAAW,OAAe;EACxB,IAAI,KAAK,cAAc,QACrB,OAAO,KAAK;EAGd,OAAO,KAAK,YAAY,SAAS,IAAI,KAAK,cAAe,KAAK,oBAAoB;CACpF;CAEA,AAAQ,cAAoB;EAC1B,IAAI,KAAK,YAAY,KAAK,CAAC,CAAC,SAAS,GAAG;GACtC,KAAK,SAAS,KAAK,KAAK,WAAW;GACnC,KAAK,mBAAmB,KAAK;EAC/B;EAEA,KAAK,cAAc;CACrB;AACF"}
@@ -0,0 +1,71 @@
1
+ //#region ../../packages/ai-cli/src/session/environment.ts
2
+ /**
3
+ * Variables a CLI needs to start at all: executable lookup, home and temp
4
+ * directories, locale, TLS/proxy settings and the Windows system set.
5
+ * Passed through even with `inheritEnv: false`.
6
+ */
7
+ const MINIMAL_ENV = [
8
+ "PATH",
9
+ "PATHEXT",
10
+ "HOME",
11
+ "USERPROFILE",
12
+ "HOMEDRIVE",
13
+ "HOMEPATH",
14
+ "APPDATA",
15
+ "LOCALAPPDATA",
16
+ "SYSTEMROOT",
17
+ "SYSTEMDRIVE",
18
+ "WINDIR",
19
+ "COMSPEC",
20
+ "TEMP",
21
+ "TMP",
22
+ "TMPDIR",
23
+ "LANG",
24
+ "LC_ALL",
25
+ "LC_CTYPE",
26
+ "TERM",
27
+ "SHELL",
28
+ "USER",
29
+ "LOGNAME",
30
+ "XDG_RUNTIME_DIR",
31
+ "HTTP_PROXY",
32
+ "HTTPS_PROXY",
33
+ "NO_PROXY",
34
+ "http_proxy",
35
+ "https_proxy",
36
+ "no_proxy",
37
+ "NODE_EXTRA_CA_CERTS",
38
+ "SSL_CERT_FILE",
39
+ "SSL_CERT_DIR"
40
+ ];
41
+ /**
42
+ * Build a CLI's environment. Precedence, lowest first: inherited variables
43
+ * (minus the adapter's managed home variables), the adapter plan, the API
44
+ * key, then the caller's `env`. Windows variable names compare
45
+ * case-insensitively.
46
+ */
47
+ function buildEnvironment(input) {
48
+ const normalize = (name) => input.platform === "win32" ? name.toUpperCase() : name;
49
+ const managed = new Set(input.managed.map(normalize));
50
+ const allowed = input.inherit === true ? void 0 : new Set([...MINIMAL_ENV, ...input.inherit === false ? [] : input.inherit].map(normalize));
51
+ const environment = {};
52
+ for (const [name, value] of Object.entries(input.source)) {
53
+ if (value === void 0 || managed.has(normalize(name))) continue;
54
+ if (allowed !== void 0 && !allowed.has(normalize(name))) continue;
55
+ environment[name] = value;
56
+ }
57
+ const assign = (name, value) => {
58
+ if (input.platform === "win32") {
59
+ for (const existing of Object.keys(environment)) if (existing.toUpperCase() === name.toUpperCase()) delete environment[existing];
60
+ }
61
+ environment[name] = value;
62
+ };
63
+ for (const [name, value] of Object.entries(input.plan ?? {})) assign(name, value);
64
+ if (input.apiKey !== void 0) assign(input.apiKey.name, input.apiKey.value);
65
+ for (const [name, value] of Object.entries(input.user ?? {})) assign(name, value);
66
+ return environment;
67
+ }
68
+
69
+ //#endregion
70
+ export { buildEnvironment };
71
+ //# sourceMappingURL=environment.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.mjs","names":[],"sources":["../../../../../../../../packages/ai-cli/src/session/environment.ts"],"sourcesContent":["/**\n * Variables a CLI needs to start at all: executable lookup, home and temp\n * directories, locale, TLS/proxy settings and the Windows system set.\n * Passed through even with `inheritEnv: false`.\n */\nexport const MINIMAL_ENV = [\n \"PATH\",\n \"PATHEXT\",\n \"HOME\",\n \"USERPROFILE\",\n \"HOMEDRIVE\",\n \"HOMEPATH\",\n \"APPDATA\",\n \"LOCALAPPDATA\",\n \"SYSTEMROOT\",\n \"SYSTEMDRIVE\",\n \"WINDIR\",\n \"COMSPEC\",\n \"TEMP\",\n \"TMP\",\n \"TMPDIR\",\n \"LANG\",\n \"LC_ALL\",\n \"LC_CTYPE\",\n \"TERM\",\n \"SHELL\",\n \"USER\",\n \"LOGNAME\",\n \"XDG_RUNTIME_DIR\",\n \"HTTP_PROXY\",\n \"HTTPS_PROXY\",\n \"NO_PROXY\",\n \"http_proxy\",\n \"https_proxy\",\n \"no_proxy\",\n \"NODE_EXTRA_CA_CERTS\",\n \"SSL_CERT_FILE\",\n \"SSL_CERT_DIR\",\n] as const;\n\nexport type BuildEnvironmentInput = {\n source: Record<string, string | undefined>;\n inherit: boolean | readonly string[];\n /** Variables the adapter manages (config home); never inherited from `source`. */\n managed: readonly string[];\n /** Adapter plan variables (config home, …). */\n plan?: Record<string, string>;\n /** Caller variables; win over everything else. */\n user?: Record<string, string>;\n /** API key and the variable it goes to. */\n apiKey?: { name: string; value: string };\n platform: NodeJS.Platform;\n};\n\n/**\n * Build a CLI's environment. Precedence, lowest first: inherited variables\n * (minus the adapter's managed home variables), the adapter plan, the API\n * key, then the caller's `env`. Windows variable names compare\n * case-insensitively.\n */\nexport function buildEnvironment(input: BuildEnvironmentInput): Record<string, string> {\n const normalize = (name: string): string =>\n input.platform === \"win32\" ? name.toUpperCase() : name;\n const managed = new Set(input.managed.map(normalize));\n const allowed =\n input.inherit === true\n ? undefined\n : new Set([...MINIMAL_ENV, ...(input.inherit === false ? [] : input.inherit)].map(normalize));\n\n const environment: Record<string, string> = {};\n\n for (const [name, value] of Object.entries(input.source)) {\n if (value === undefined || managed.has(normalize(name))) {\n continue;\n }\n\n if (allowed !== undefined && !allowed.has(normalize(name))) {\n continue;\n }\n\n environment[name] = value;\n }\n\n const assign = (name: string, value: string): void => {\n if (input.platform === \"win32\") {\n for (const existing of Object.keys(environment)) {\n if (existing.toUpperCase() === name.toUpperCase()) {\n delete environment[existing];\n }\n }\n }\n\n environment[name] = value;\n };\n\n for (const [name, value] of Object.entries(input.plan ?? {})) {\n assign(name, value);\n }\n\n if (input.apiKey !== undefined) {\n assign(input.apiKey.name, input.apiKey.value);\n }\n\n for (const [name, value] of Object.entries(input.user ?? {})) {\n assign(name, value);\n }\n\n return environment;\n}\n"],"mappings":";;;;;;AAKA,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;AAsBA,SAAgB,iBAAiB,OAAsD;CACrF,MAAM,aAAa,SACjB,MAAM,aAAa,UAAU,KAAK,YAAY,IAAI;CACpD,MAAM,UAAU,IAAI,IAAI,MAAM,QAAQ,IAAI,SAAS,CAAC;CACpD,MAAM,UACJ,MAAM,YAAY,OACd,SACA,IAAI,IAAI,CAAC,GAAG,aAAa,GAAI,MAAM,YAAY,QAAQ,CAAC,IAAI,MAAM,OAAQ,CAAC,CAAC,IAAI,SAAS,CAAC;CAEhG,MAAM,cAAsC,CAAC;CAE7C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,MAAM,GAAG;EACxD,IAAI,UAAU,UAAa,QAAQ,IAAI,UAAU,IAAI,CAAC,GACpD;EAGF,IAAI,YAAY,UAAa,CAAC,QAAQ,IAAI,UAAU,IAAI,CAAC,GACvD;EAGF,YAAY,QAAQ;CACtB;CAEA,MAAM,UAAU,MAAc,UAAwB;EACpD,IAAI,MAAM,aAAa,SACrB;QAAK,MAAM,YAAY,OAAO,KAAK,WAAW,GAC5C,IAAI,SAAS,YAAY,MAAM,KAAK,YAAY,GAC9C,OAAO,YAAY;EAEvB;EAGF,YAAY,QAAQ;CACtB;CAEA,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,QAAQ,CAAC,CAAC,GACzD,OAAO,MAAM,KAAK;CAGpB,IAAI,MAAM,WAAW,QACnB,OAAO,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK;CAG9C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,QAAQ,CAAC,CAAC,GACzD,OAAO,MAAM,KAAK;CAGpB,OAAO;AACT"}
@@ -0,0 +1,74 @@
1
+ import path from "node:path";
2
+ import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+
5
+ //#region ../../packages/ai-cli/src/session/run-directory.ts
6
+ /**
7
+ * Create the run's private directory and resolve its config home.
8
+ *
9
+ * The run directory is `mkdtemp`'d under the OS temp dir with mode 0700, so
10
+ * other local users cannot read the MCP config (and the tokens in it)
11
+ * written there. A `"private"` home is a `home/` folder inside it.
12
+ */
13
+ async function createRunDirectories(home, cwd) {
14
+ const runDir = await mkdtemp(path.join(tmpdir(), "warlock-cli-"));
15
+ await chmod(runDir, 448);
16
+ const dispose = async () => {
17
+ await rm(runDir, {
18
+ recursive: true,
19
+ force: true
20
+ });
21
+ };
22
+ if (home === "inherit") return {
23
+ runDir,
24
+ homeIsPrivate: false,
25
+ dispose
26
+ };
27
+ if (home === "private") {
28
+ const privateHome = path.join(runDir, "home");
29
+ await mkdir(privateHome, { mode: 448 });
30
+ return {
31
+ runDir,
32
+ home: privateHome,
33
+ homeIsPrivate: true,
34
+ dispose
35
+ };
36
+ }
37
+ const ownedHome = path.resolve(cwd, home);
38
+ await mkdir(ownedHome, {
39
+ recursive: true,
40
+ mode: 448
41
+ });
42
+ return {
43
+ runDir,
44
+ home: ownedHome,
45
+ homeIsPrivate: false,
46
+ dispose
47
+ };
48
+ }
49
+ /**
50
+ * Write launch files with owner-only permissions (0600; parent directories
51
+ * 0700). Only paths inside the run directory or a private home are
52
+ * accepted, so an adapter can never overwrite a file in a caller-owned
53
+ * home or elsewhere on disk.
54
+ */
55
+ async function writeLaunchFiles(files, directories) {
56
+ const roots = [directories.runDir];
57
+ for (const file of files) {
58
+ const target = path.resolve(file.path);
59
+ if (!roots.some((root) => {
60
+ const relative = path.relative(root, target);
61
+ return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
62
+ })) throw new Error(`Refusing to write ${target}: launch files must live inside the run directory.`);
63
+ await mkdir(path.dirname(target), {
64
+ recursive: true,
65
+ mode: 448
66
+ });
67
+ await writeFile(target, file.content, { mode: 384 });
68
+ await chmod(target, 384);
69
+ }
70
+ }
71
+
72
+ //#endregion
73
+ export { createRunDirectories, writeLaunchFiles };
74
+ //# sourceMappingURL=run-directory.mjs.map