kairn-cli 1.9.0 → 1.10.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.
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import chalk14 from "chalk";
4
4
 
5
5
  // src/commands/init.ts
6
6
  import { Command } from "commander";
7
- import { password, select } from "@inquirer/prompts";
7
+ import { input, password, select } from "@inquirer/prompts";
8
8
  import chalk3 from "chalk";
9
9
  import Anthropic from "@anthropic-ai/sdk";
10
10
  import OpenAI from "openai";
@@ -62,27 +62,158 @@ async function saveConfig(config) {
62
62
  await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
63
63
  }
64
64
 
65
+ // src/providers.ts
66
+ var PROVIDER_CONFIGS = {
67
+ anthropic: {
68
+ name: "Anthropic",
69
+ verifyModel: "claude-haiku-4-5-20251001",
70
+ cheapModel: "claude-haiku-4-5-20251001"
71
+ },
72
+ openai: {
73
+ name: "OpenAI",
74
+ verifyModel: "gpt-4.1-nano",
75
+ cheapModel: "gpt-4.1-nano"
76
+ },
77
+ google: {
78
+ name: "Google Gemini",
79
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
80
+ verifyModel: "gemini-2.5-flash",
81
+ cheapModel: "gemini-2.5-flash"
82
+ },
83
+ xai: {
84
+ name: "xAI (Grok)",
85
+ baseURL: "https://api.x.ai/v1",
86
+ verifyModel: "grok-4-1-fast-non-reasoning",
87
+ cheapModel: "grok-4-1-fast-non-reasoning"
88
+ },
89
+ deepseek: {
90
+ name: "DeepSeek",
91
+ baseURL: "https://api.deepseek.com",
92
+ verifyModel: "deepseek-chat",
93
+ cheapModel: "deepseek-chat"
94
+ },
95
+ mistral: {
96
+ name: "Mistral",
97
+ baseURL: "https://api.mistral.ai/v1",
98
+ verifyModel: "mistral-small-latest",
99
+ cheapModel: "mistral-small-latest"
100
+ },
101
+ groq: {
102
+ name: "Groq (open-source models)",
103
+ baseURL: "https://api.groq.com/openai/v1",
104
+ verifyModel: "meta-llama/llama-4-scout-17b-16e-instruct",
105
+ cheapModel: "meta-llama/llama-4-scout-17b-16e-instruct"
106
+ }
107
+ };
108
+ var PROVIDER_MODELS = {
109
+ anthropic: [
110
+ { name: "Claude Sonnet 4.6 (recommended)", value: "claude-sonnet-4-6" },
111
+ { name: "Claude Opus 4.6 (highest quality)", value: "claude-opus-4-6" },
112
+ { name: "Claude Haiku 4.5 (fastest, cheapest)", value: "claude-haiku-4-5-20251001" }
113
+ ],
114
+ openai: [
115
+ { name: "GPT-4.1 (recommended \u2014 smartest non-reasoning)", value: "gpt-4.1" },
116
+ { name: "GPT-4.1 mini (faster, cheaper)", value: "gpt-4.1-mini" },
117
+ { name: "o4-mini (reasoning, cost-efficient)", value: "o4-mini" },
118
+ { name: "GPT-5 mini (frontier)", value: "gpt-5-mini" }
119
+ ],
120
+ google: [
121
+ { name: "Gemini 2.5 Flash (recommended \u2014 best value)", value: "gemini-2.5-flash" },
122
+ { name: "Gemini 3 Flash (newest frontier)", value: "gemini-3-flash" },
123
+ { name: "Gemini 2.5 Pro (highest quality)", value: "gemini-2.5-pro" },
124
+ { name: "Gemini 3.1 Pro Preview (most advanced)", value: "gemini-3.1-pro-preview" }
125
+ ],
126
+ xai: [
127
+ { name: "Grok 4.1 Fast (recommended \u2014 $0.20/M, very fast)", value: "grok-4-1-fast-non-reasoning" },
128
+ { name: "Grok 4.20 (frontier quality, 2M context)", value: "grok-4.20-0309-non-reasoning" }
129
+ ],
130
+ deepseek: [
131
+ { name: "DeepSeek V3.2 Chat (recommended \u2014 cheapest good model)", value: "deepseek-chat" },
132
+ { name: "DeepSeek V3.2 Reasoner (with chain-of-thought)", value: "deepseek-reasoner" }
133
+ ],
134
+ mistral: [
135
+ { name: "Mistral Large 3 (recommended \u2014 open-weight flagship)", value: "mistral-large-latest" },
136
+ { name: "Codestral (code-optimized, 256K context)", value: "codestral-latest" },
137
+ { name: "Mistral Small 4 (cheapest)", value: "mistral-small-latest" }
138
+ ],
139
+ groq: [
140
+ { name: "Llama 4 Maverick (recommended \u2014 free, fast)", value: "meta-llama/llama-4-maverick-17b-128e-instruct" },
141
+ { name: "Llama 4 Scout (free, fast)", value: "meta-llama/llama-4-scout-17b-16e-instruct" },
142
+ { name: "DeepSeek R1 70B (free reasoning)", value: "deepseek-r1-distill-llama-70b" },
143
+ { name: "Qwen 3 32B (free, multilingual)", value: "qwen/qwen3-32b" }
144
+ ]
145
+ };
146
+ var PROVIDER_CHOICES = [
147
+ { name: "Anthropic (Claude) \u2014 recommended", value: "anthropic" },
148
+ { name: "OpenAI (GPT)", value: "openai" },
149
+ { name: "Google (Gemini)", value: "google" },
150
+ { name: "xAI (Grok)", value: "xai" },
151
+ { name: "DeepSeek \u2014 cheapest", value: "deepseek" },
152
+ { name: "Mistral \u2014 open-weight", value: "mistral" },
153
+ { name: "Groq \u2014 free tier, open-source models", value: "groq" },
154
+ { name: "Other (OpenAI-compatible endpoint)", value: "other" }
155
+ ];
156
+ function getProviderName(provider) {
157
+ if (provider === "other") return "Custom endpoint";
158
+ return PROVIDER_CONFIGS[provider].name;
159
+ }
160
+ function getBaseURL(provider, customBaseURL) {
161
+ if (provider === "other") return customBaseURL;
162
+ return PROVIDER_CONFIGS[provider]?.baseURL;
163
+ }
164
+ function getCheapModel(provider, fallbackModel) {
165
+ if (provider === "other") return fallbackModel;
166
+ return PROVIDER_CONFIGS[provider].cheapModel;
167
+ }
168
+ function getVerifyModel(provider, fallbackModel) {
169
+ if (provider === "other") return fallbackModel;
170
+ return PROVIDER_CONFIGS[provider].verifyModel;
171
+ }
172
+
65
173
  // src/ui.ts
66
174
  import chalk from "chalk";
67
175
  var maroon = chalk.rgb(139, 0, 0);
68
- var warm = chalk.rgb(212, 165, 116);
176
+ var darkMaroon = chalk.rgb(100, 0, 0);
177
+ var warmStone = chalk.rgb(212, 165, 116);
178
+ var lightStone = chalk.rgb(220, 190, 160);
179
+ var dimStone = chalk.rgb(140, 100, 70);
69
180
  var ui = {
70
- // Brand
181
+ // Brand colors
71
182
  brand: (text) => maroon.bold(text),
72
- accent: (text) => warm(text),
73
- // Headers
74
- header: (text) => {
75
- const line = "\u2500".repeat(50);
183
+ accent: (text) => warmStone(text),
184
+ // Logos and banners
185
+ fullBanner: (subtitle) => {
186
+ const KAIRN_WORDMARK2 = [
187
+ maroon("\u2588\u2588\u2557 \u2588\u2588\u2557") + " " + maroon("\u2588\u2588\u2588\u2588\u2588\u2557 ") + " " + maroon("\u2588\u2588\u2557") + " " + maroon("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + " " + maroon("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557"),
188
+ maroon("\u2588\u2588\u2551 \u2588\u2588\u2554\u255D") + " " + maroon("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + " " + maroon("\u2588\u2588\u2551") + " " + maroon("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + " " + maroon("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551"),
189
+ warmStone("\u2588\u2588\u2588\u2588\u2588\u2554\u255D ") + " " + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551") + " " + warmStone("\u2588\u2588\u2551") + " " + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + " " + warmStone("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551"),
190
+ warmStone("\u2588\u2588\u2554\u2550\u2588\u2588\u2557 ") + " " + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551") + " " + warmStone("\u2588\u2588\u2551") + " " + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + " " + warmStone("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551"),
191
+ lightStone("\u2588\u2588\u2551 \u2588\u2588\u2557") + " " + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + " " + lightStone("\u2588\u2588\u2551") + " " + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + " " + lightStone("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551"),
192
+ lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + " " + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + " " + lightStone("\u255A\u2550\u255D") + " " + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + " " + lightStone("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D")
193
+ ];
194
+ console.log("");
195
+ for (const line of KAIRN_WORDMARK2) {
196
+ console.log(" " + line);
197
+ }
198
+ if (subtitle) {
199
+ console.log(dimStone(` ${subtitle}`));
200
+ }
201
+ console.log("");
202
+ },
203
+ compactBanner: (subtitle) => {
204
+ const line = maroon("\u2501").repeat(52);
205
+ console.log(` ${line}`);
206
+ console.log(` ${maroon(" \u25C6")} ${chalk.bold.rgb(139, 0, 0)("KAIRN")}` + (subtitle ? ` ${dimStone("\u2014 " + subtitle)}` : ""));
207
+ console.log(` ${line}`);
208
+ },
209
+ // Section headers
210
+ section: (title) => {
211
+ const len = chalk.dim(title).length;
212
+ const line = "\u2501".repeat(Math.max(0, 48 - len));
76
213
  return `
77
- ${maroon("\u250C" + line + "\u2510")}
78
- ${maroon("\u2502")} ${maroon.bold(text.padEnd(49))}${maroon("\u2502")}
79
- ${maroon("\u2514" + line + "\u2518")}
80
- `;
214
+ ${warmStone("\u2501\u2501")} ${chalk.bold(title)} ${chalk.dim(warmStone(line))}`;
81
215
  },
82
- // Sections
83
- section: (title) => `
84
- ${warm("\u2501\u2501")} ${chalk.bold(title)} ${warm("\u2501".repeat(Math.max(0, 44 - title.length)))}`,
85
- // Status
216
+ // Status messages
86
217
  success: (text) => chalk.green(` \u2713 ${text}`),
87
218
  warn: (text) => chalk.yellow(` \u26A0 ${text}`),
88
219
  error: (text) => chalk.red(` \u2717 ${text}`),
@@ -92,65 +223,68 @@ var ui = {
92
223
  // File list
93
224
  file: (path15) => chalk.dim(` ${path15}`),
94
225
  // Tool display
95
- tool: (name, reason) => ` ${warm("\u25CF")} ${chalk.bold(name)}
226
+ tool: (name, reason) => ` ${warmStone("\u25CF")} ${chalk.bold(name)}
96
227
  ${chalk.dim(reason)}`,
97
228
  // Divider
98
229
  divider: () => chalk.dim(` ${"\u2500".repeat(50)}`),
99
230
  // Command suggestion
100
231
  cmd: (command) => ` ${chalk.bold.white("$ " + command)}`,
101
- // Env var setup
102
- envVar: (name, desc, url) => {
103
- let out = ` ${chalk.bold(`export ${name}=`)}${chalk.dim('"your-key-here"')}
104
- `;
105
- out += chalk.dim(` ${desc}`);
232
+ // Env var setup with signupUrl
233
+ envVarPrompt: (name, desc, url) => {
234
+ let out = ` ${chalk.bold(name)}${chalk.dim(` (${desc})`)}`;
106
235
  if (url) out += `
107
- ${chalk.dim("Get one at:")} ${warm(url)}`;
236
+ ${chalk.dim("Get one at:")} ${warmStone(url)}`;
108
237
  return out;
109
238
  },
110
- // Clarification question display
111
- question: (q, suggestion) => ` ${warm("?")} ${chalk.bold(q)}
112
- ${chalk.dim(`suggested: ${suggestion}`)}`,
113
- // Branded error box
239
+ // Clarification question
240
+ question: (q, suggestion) => {
241
+ let msg = ` ${warmStone("?")} ${chalk.bold(q)}`;
242
+ if (suggestion) {
243
+ msg += `
244
+ ${chalk.dim(`(suggested: ${suggestion})`)}`;
245
+ }
246
+ return msg;
247
+ },
248
+ // Error box for compile failures
114
249
  errorBox: (title, message) => {
115
250
  const line = "\u2500".repeat(50);
116
- return `
117
- ${chalk.red("\u250C" + line + "\u2510")}
118
- ${chalk.red("\u2502")} ${chalk.red.bold(title.padEnd(49))}${chalk.red("\u2502")}
119
- ${chalk.red("\u2514" + line + "\u2518")}
120
-
121
- ${chalk.red("\u2717")} ${message}
122
- `;
251
+ return chalk.red(`
252
+ \u250C${line}\u2510
253
+ \u2502 ${title.padEnd(49)}\u2502
254
+ \u2502 ${message.padEnd(49)}\u2502
255
+ \u2514${line}\u2518
256
+ `);
123
257
  }
124
258
  };
125
259
 
126
260
  // src/logo.ts
127
261
  import chalk2 from "chalk";
128
262
  var maroon2 = chalk2.rgb(139, 0, 0);
129
- var darkMaroon = chalk2.rgb(100, 0, 0);
130
- var warmStone = chalk2.rgb(180, 120, 80);
131
- var lightStone = chalk2.rgb(212, 165, 116);
132
- var dimStone = chalk2.rgb(140, 100, 70);
263
+ var darkMaroon2 = chalk2.rgb(100, 0, 0);
264
+ var warmStone2 = chalk2.rgb(180, 120, 80);
265
+ var lightStone2 = chalk2.rgb(212, 165, 116);
266
+ var dimStone2 = chalk2.rgb(140, 100, 70);
133
267
  var KAIRN_WORDMARK = [
134
- maroon2("\u2588\u2588\u2557 \u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon(" ") + maroon2("\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557"),
135
- maroon2("\u2588\u2588\u2551 \u2588\u2588\u2554\u255D") + darkMaroon(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2551") + darkMaroon(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon(" ") + maroon2("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551"),
136
- warmStone("\u2588\u2588\u2588\u2588\u2588\u2554\u255D ") + dimStone(" ") + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551"),
137
- warmStone("\u2588\u2588\u2554\u2550\u2588\u2588\u2557 ") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2551") + dimStone(" ") + warmStone("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + dimStone(" ") + warmStone("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551"),
138
- lightStone("\u2588\u2588\u2551 \u2588\u2588\u2557") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone(" ") + lightStone("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551"),
139
- lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone(" ") + lightStone("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D")
268
+ maroon2("\u2588\u2588\u2557 \u2588\u2588\u2557") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2557") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557"),
269
+ maroon2("\u2588\u2588\u2551 \u2588\u2588\u2554\u255D") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2551") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + darkMaroon2(" ") + maroon2("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551"),
270
+ warmStone2("\u2588\u2588\u2588\u2588\u2588\u2554\u255D ") + dimStone2(" ") + warmStone2("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551") + dimStone2(" ") + warmStone2("\u2588\u2588\u2551") + dimStone2(" ") + warmStone2("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + dimStone2(" ") + warmStone2("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551"),
271
+ warmStone2("\u2588\u2588\u2554\u2550\u2588\u2588\u2557 ") + dimStone2(" ") + warmStone2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551") + dimStone2(" ") + warmStone2("\u2588\u2588\u2551") + dimStone2(" ") + warmStone2("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + dimStone2(" ") + warmStone2("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551"),
272
+ lightStone2("\u2588\u2588\u2551 \u2588\u2588\u2557") + dimStone2(" ") + lightStone2("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone2(" ") + lightStone2("\u2588\u2588\u2551") + dimStone2(" ") + lightStone2("\u2588\u2588\u2551 \u2588\u2588\u2551") + dimStone2(" ") + lightStone2("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551"),
273
+ lightStone2("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone2(" ") + lightStone2("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone2(" ") + lightStone2("\u255A\u2550\u255D") + dimStone2(" ") + lightStone2("\u255A\u2550\u255D \u255A\u2550\u255D") + dimStone2(" ") + lightStone2("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D")
140
274
  ];
141
275
  var CAIRN_ART = [
142
- dimStone(" \u28C0\u28C0\u28C0 "),
143
- warmStone(" \u28F4\u28FF\u28FF\u28FF\u28E6 "),
144
- warmStone(" \u2819\u283F\u283F\u280B "),
145
- dimStone(" \u28C0\u28E4\u28E4\u28E4\u28E4\u28C0 "),
146
- lightStone(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
147
- lightStone(" \u2819\u283B\u283F\u283F\u283F\u281F\u280B "),
148
- dimStone(" \u28C0\u28E4\u28E4\u28F6\u28F6\u28F6\u28F6\u28E4\u28E4\u28C0 "),
149
- warmStone(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
150
- warmStone(" \u2819\u283B\u283F\u283F\u283F\u283F\u283F\u283F\u281F\u280B "),
151
- dimStone(" \u28C0\u28E4\u28F6\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F6\u28F6\u28E4\u28C0 "),
152
- lightStone(" \u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF "),
153
- dimStone(" \u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809 ")
276
+ dimStone2(" \u28C0\u28C0\u28C0 "),
277
+ warmStone2(" \u28F4\u28FF\u28FF\u28FF\u28E6 "),
278
+ warmStone2(" \u2819\u283F\u283F\u280B "),
279
+ dimStone2(" \u28C0\u28E4\u28E4\u28E4\u28E4\u28C0 "),
280
+ lightStone2(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
281
+ lightStone2(" \u2819\u283B\u283F\u283F\u283F\u281F\u280B "),
282
+ dimStone2(" \u28C0\u28E4\u28E4\u28F6\u28F6\u28F6\u28F6\u28E4\u28E4\u28C0 "),
283
+ warmStone2(" \u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6 "),
284
+ warmStone2(" \u2819\u283B\u283F\u283F\u283F\u283F\u283F\u283F\u281F\u280B "),
285
+ dimStone2(" \u28C0\u28E4\u28F6\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F6\u28F6\u28E4\u28C0 "),
286
+ lightStone2(" \u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF "),
287
+ dimStone2(" \u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809\u2809 ")
154
288
  ];
155
289
  function printFullBanner(subtitle) {
156
290
  console.log("");
@@ -158,7 +292,7 @@ function printFullBanner(subtitle) {
158
292
  console.log(" " + line);
159
293
  }
160
294
  if (subtitle) {
161
- console.log(dimStone(` ${subtitle}`));
295
+ console.log(dimStone2(` ${subtitle}`));
162
296
  }
163
297
  console.log("");
164
298
  }
@@ -166,7 +300,7 @@ function printCompactBanner() {
166
300
  const line = maroon2("\u2501").repeat(50);
167
301
  console.log(`
168
302
  ${line}`);
169
- console.log(` ${maroon2(" \u25C6")} ${chalk2.bold.rgb(139, 0, 0)("KAIRN")} ${dimStone("\u2014 Agent Environment Compiler")}`);
303
+ console.log(` ${maroon2(" \u25C6")} ${chalk2.bold.rgb(139, 0, 0)("KAIRN")} ${dimStone2("\u2014 Agent Environment Compiler")}`);
170
304
  console.log(` ${line}
171
305
  `);
172
306
  }
@@ -208,62 +342,28 @@ async function installSeedTemplates() {
208
342
  console.log(ui.success(`${installed} template${installed === 1 ? "" : "s"} installed`));
209
343
  }
210
344
  }
211
- var PROVIDER_MODELS = {
212
- anthropic: {
213
- name: "Anthropic",
214
- models: [
215
- { name: "Claude Sonnet 4.6 (recommended \u2014 fast, smart)", value: "claude-sonnet-4-6" },
216
- { name: "Claude Opus 4.6 (highest quality)", value: "claude-opus-4-6" },
217
- { name: "Claude Haiku 4.5 (fastest, cheapest)", value: "claude-haiku-4-5-20251001" }
218
- ]
219
- },
220
- openai: {
221
- name: "OpenAI",
222
- models: [
223
- { name: "GPT-4o (recommended)", value: "gpt-4o" },
224
- { name: "GPT-4o mini (faster, cheaper)", value: "gpt-4o-mini" },
225
- { name: "o3 (reasoning)", value: "o3" }
226
- ]
227
- },
228
- google: {
229
- name: "Google Gemini",
230
- models: [
231
- { name: "Gemini 2.5 Flash (recommended)", value: "gemini-2.5-flash-preview-05-20" },
232
- { name: "Gemini 2.5 Pro (highest quality)", value: "gemini-2.5-pro-preview-05-06" }
233
- ]
234
- }
235
- };
236
- async function verifyKey(provider, apiKey, model) {
345
+ async function verifyKey(provider, apiKey, baseURL, model) {
237
346
  try {
238
347
  if (provider === "anthropic") {
239
- const client = new Anthropic({ apiKey });
240
- await client.messages.create({
241
- model: "claude-haiku-4-5-20251001",
242
- max_tokens: 10,
243
- messages: [{ role: "user", content: "ping" }]
244
- });
245
- return true;
246
- } else if (provider === "openai") {
247
- const client = new OpenAI({ apiKey });
248
- await client.chat.completions.create({
249
- model: "gpt-4o-mini",
250
- max_tokens: 10,
251
- messages: [{ role: "user", content: "ping" }]
252
- });
253
- return true;
254
- } else if (provider === "google") {
255
- const client = new OpenAI({
256
- apiKey,
257
- baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/"
258
- });
259
- await client.chat.completions.create({
260
- model: "gemini-2.5-flash-preview-05-20",
348
+ const client2 = new Anthropic({ apiKey });
349
+ await client2.messages.create({
350
+ model: getVerifyModel(provider, model || "claude-haiku-4-5-20251001"),
261
351
  max_tokens: 10,
262
352
  messages: [{ role: "user", content: "ping" }]
263
353
  });
264
354
  return true;
265
355
  }
266
- return false;
356
+ const verifyModel = provider === "other" ? model || "test" : getVerifyModel(provider, model || "");
357
+ const resolvedBaseURL = getBaseURL(provider, baseURL);
358
+ const clientOptions = { apiKey };
359
+ if (resolvedBaseURL) clientOptions.baseURL = resolvedBaseURL;
360
+ const client = new OpenAI(clientOptions);
361
+ await client.chat.completions.create({
362
+ model: verifyModel,
363
+ max_tokens: 10,
364
+ messages: [{ role: "user", content: "ping" }]
365
+ });
366
+ return true;
267
367
  } catch {
268
368
  return false;
269
369
  }
@@ -285,42 +385,52 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
285
385
  }
286
386
  const provider = await select({
287
387
  message: "LLM provider",
288
- choices: [
289
- { name: "Anthropic (Claude) \u2014 recommended", value: "anthropic" },
290
- { name: "OpenAI (GPT)", value: "openai" },
291
- { name: "Google (Gemini)", value: "google" }
292
- ]
293
- });
294
- const providerInfo = PROVIDER_MODELS[provider];
295
- const model = await select({
296
- message: "Compilation model",
297
- choices: providerInfo.models
388
+ choices: PROVIDER_CHOICES
298
389
  });
390
+ let model;
391
+ let baseURL;
392
+ let providerDisplayName;
393
+ if (provider === "other") {
394
+ providerDisplayName = "Custom endpoint";
395
+ baseURL = await input({ message: "Base URL" });
396
+ model = await input({ message: "Model name" });
397
+ } else {
398
+ providerDisplayName = getProviderName(provider);
399
+ model = await select({
400
+ message: "Compilation model",
401
+ choices: PROVIDER_MODELS[provider]
402
+ });
403
+ }
299
404
  const apiKey = await password({
300
- message: `${providerInfo.name} API key`,
405
+ message: `${providerDisplayName} API key${provider === "other" ? " (Enter to skip)" : ""}`,
301
406
  mask: "*"
302
407
  });
303
- if (!apiKey) {
408
+ if (!apiKey && provider !== "other") {
304
409
  console.log(ui.error("No API key provided. Aborting."));
305
410
  process.exit(1);
306
411
  }
307
- console.log(chalk3.dim("\n Verifying API key..."));
308
- const valid = await verifyKey(provider, apiKey, model);
309
- if (!valid) {
310
- console.log(ui.error("Invalid API key. Check your key and try again."));
311
- process.exit(1);
412
+ if (apiKey) {
413
+ console.log(chalk3.dim("\n Verifying API key..."));
414
+ const valid = await verifyKey(provider, apiKey, baseURL, model);
415
+ if (!valid) {
416
+ console.log(ui.error("Invalid API key. Check your key and try again."));
417
+ process.exit(1);
418
+ }
419
+ console.log(ui.success("API key verified"));
420
+ } else {
421
+ console.log(ui.warn("No API key \u2014 skipping verification"));
312
422
  }
313
- console.log(ui.success("API key verified"));
314
423
  const config = {
315
424
  provider,
316
- api_key: apiKey,
425
+ api_key: apiKey || "",
317
426
  model,
427
+ ...baseURL ? { base_url: baseURL } : {},
318
428
  default_runtime: "claude-code",
319
429
  created_at: (/* @__PURE__ */ new Date()).toISOString()
320
430
  };
321
431
  await saveConfig(config);
322
432
  console.log(ui.success(`Config saved to ${chalk3.dim(getConfigPath())}`));
323
- console.log(ui.kv("Provider", providerInfo.name));
433
+ console.log(ui.kv("Provider", providerDisplayName));
324
434
  console.log(ui.kv("Model", model));
325
435
  await installSeedTemplates();
326
436
  const hasClaude = detectClaudeCode();
@@ -338,7 +448,7 @@ var initCommand = new Command("init").description("Set up Kairn with your API ke
338
448
 
339
449
  // src/commands/describe.ts
340
450
  import { Command as Command2 } from "commander";
341
- import { input, confirm, select as select2 } from "@inquirer/prompts";
451
+ import { input as input2, confirm, select as select2 } from "@inquirer/prompts";
342
452
  import chalk5 from "chalk";
343
453
  import ora from "ora";
344
454
 
@@ -819,10 +929,11 @@ function classifyError(err, provider) {
819
929
  return `${provider} API error: ${msg}`;
820
930
  }
821
931
  async function callLLM(config, userMessage) {
932
+ const providerName = getProviderName(config.provider);
822
933
  if (config.provider === "anthropic") {
823
- const client = new Anthropic2({ apiKey: config.api_key });
934
+ const client2 = new Anthropic2({ apiKey: config.api_key });
824
935
  try {
825
- const response = await client.messages.create({
936
+ const response = await client2.messages.create({
826
937
  model: config.model,
827
938
  max_tokens: 8192,
828
939
  system: SYSTEM_PROMPT,
@@ -833,35 +944,31 @@ async function callLLM(config, userMessage) {
833
944
  throw new Error("No text response from compiler LLM");
834
945
  }
835
946
  return textBlock.text;
836
- } catch (err) {
837
- throw new Error(classifyError(err, "Anthropic"));
838
- }
839
- } else if (config.provider === "openai" || config.provider === "google") {
840
- const providerName = config.provider === "google" ? "Google" : "OpenAI";
841
- const clientOptions = { apiKey: config.api_key };
842
- if (config.provider === "google") {
843
- clientOptions.baseURL = "https://generativelanguage.googleapis.com/v1beta/openai/";
844
- }
845
- const client = new OpenAI2(clientOptions);
846
- try {
847
- const response = await client.chat.completions.create({
848
- model: config.model,
849
- max_tokens: 8192,
850
- messages: [
851
- { role: "system", content: SYSTEM_PROMPT },
852
- { role: "user", content: userMessage }
853
- ]
854
- });
855
- const text = response.choices[0]?.message?.content;
856
- if (!text) {
857
- throw new Error("No text response from compiler LLM");
858
- }
859
- return text;
860
947
  } catch (err) {
861
948
  throw new Error(classifyError(err, providerName));
862
949
  }
863
950
  }
864
- throw new Error(`Unsupported provider: ${config.provider}. Run \`kairn init\` to reconfigure.`);
951
+ const resolvedBaseURL = getBaseURL(config.provider, config.base_url);
952
+ const clientOptions = { apiKey: config.api_key };
953
+ if (resolvedBaseURL) clientOptions.baseURL = resolvedBaseURL;
954
+ const client = new OpenAI2(clientOptions);
955
+ try {
956
+ const response = await client.chat.completions.create({
957
+ model: config.model,
958
+ max_tokens: 8192,
959
+ messages: [
960
+ { role: "system", content: SYSTEM_PROMPT },
961
+ { role: "user", content: userMessage }
962
+ ]
963
+ });
964
+ const text = response.choices[0]?.message?.content;
965
+ if (!text) {
966
+ throw new Error("No text response from compiler LLM");
967
+ }
968
+ return text;
969
+ } catch (err) {
970
+ throw new Error(classifyError(err, providerName));
971
+ }
865
972
  }
866
973
  function validateSpec(spec, onProgress) {
867
974
  const warnings = [];
@@ -913,9 +1020,7 @@ async function generateClarifications(intent, onProgress) {
913
1020
  }
914
1021
  onProgress?.("Analyzing your request...");
915
1022
  const clarificationConfig = { ...config };
916
- if (config.provider === "anthropic") {
917
- clarificationConfig.model = "claude-haiku-4-5-20251001";
918
- }
1023
+ clarificationConfig.model = getCheapModel(config.provider, config.model);
919
1024
  const response = await callLLM(clarificationConfig, CLARIFICATION_PROMPT + "\n\nUser description: " + intent);
920
1025
  try {
921
1026
  let cleaned = response.trim();
@@ -1627,7 +1732,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1627
1732
  );
1628
1733
  process.exit(1);
1629
1734
  }
1630
- const intentRaw = intentArg || await input({
1735
+ const intentRaw = intentArg || await input2({
1631
1736
  message: "What do you want your agent to do?"
1632
1737
  });
1633
1738
  if (!intentRaw.trim()) {
@@ -1647,7 +1752,7 @@ var describeCommand = new Command2("describe").description("Describe your workfl
1647
1752
  if (clarifications.length > 0) {
1648
1753
  const answers = [];
1649
1754
  for (const c of clarifications) {
1650
- const answer = await input({
1755
+ const answer = await input2({
1651
1756
  message: c.question,
1652
1757
  default: c.suggestion
1653
1758
  });
@@ -2593,7 +2698,7 @@ var doctorCommand = new Command7("doctor").description(
2593
2698
  // src/commands/registry.ts
2594
2699
  import { Command as Command8 } from "commander";
2595
2700
  import chalk11 from "chalk";
2596
- import { input as input2, select as select3 } from "@inquirer/prompts";
2701
+ import { input as input3, select as select3 } from "@inquirer/prompts";
2597
2702
  var listCommand2 = new Command8("list").description("List tools in the registry").option("--category <cat>", "Filter by category").option("--user-only", "Show only user-defined tools").action(async (options) => {
2598
2703
  printCompactBanner();
2599
2704
  let all;
@@ -2653,7 +2758,7 @@ var listCommand2 = new Command8("list").description("List tools in the registry"
2653
2758
  var addCommand = new Command8("add").description("Add a tool to the user registry").action(async () => {
2654
2759
  let id;
2655
2760
  try {
2656
- id = await input2({
2761
+ id = await input3({
2657
2762
  message: "Tool ID (kebab-case)",
2658
2763
  validate: (v) => {
2659
2764
  if (!v) return "ID is required";
@@ -2661,8 +2766,8 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2661
2766
  return true;
2662
2767
  }
2663
2768
  });
2664
- const name = await input2({ message: "Display name" });
2665
- const description = await input2({ message: "Description" });
2769
+ const name = await input3({ message: "Display name" });
2770
+ const description = await input3({ message: "Description" });
2666
2771
  const category = await select3({
2667
2772
  message: "Category",
2668
2773
  choices: [
@@ -2706,8 +2811,8 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2706
2811
  if (auth === "api_key" || auth === "connection_string") {
2707
2812
  let addMore = true;
2708
2813
  while (addMore) {
2709
- const varName = await input2({ message: "Env var name" });
2710
- const varDesc = await input2({ message: "Env var description" });
2814
+ const varName = await input3({ message: "Env var name" });
2815
+ const varDesc = await input3({ message: "Env var description" });
2711
2816
  env_vars.push({ name: varName, description: varDesc });
2712
2817
  const another = await select3({
2713
2818
  message: "Add another env var?",
@@ -2719,14 +2824,14 @@ var addCommand = new Command8("add").description("Add a tool to the user registr
2719
2824
  addMore = another;
2720
2825
  }
2721
2826
  }
2722
- const signup_url_raw = await input2({ message: "Signup URL (optional, press enter to skip)" });
2827
+ const signup_url_raw = await input3({ message: "Signup URL (optional, press enter to skip)" });
2723
2828
  const signup_url = signup_url_raw.trim() || void 0;
2724
- const best_for_raw = await input2({ message: "Best-for tags, comma-separated" });
2829
+ const best_for_raw = await input3({ message: "Best-for tags, comma-separated" });
2725
2830
  const best_for = best_for_raw.split(",").map((s) => s.trim()).filter(Boolean);
2726
2831
  const install = {};
2727
2832
  if (type === "mcp_server") {
2728
- const command = await input2({ message: "MCP command" });
2729
- const args_raw = await input2({ message: "MCP args, comma-separated (leave blank for none)" });
2833
+ const command = await input3({ message: "MCP command" });
2834
+ const args_raw = await input3({ message: "MCP args, comma-separated (leave blank for none)" });
2730
2835
  const args = args_raw.split(",").map((s) => s.trim()).filter(Boolean);
2731
2836
  install.mcp_config = { command, args };
2732
2837
  }