krims-code-cli 1.5.2__py3-none-any.whl
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.
- krims_code_cli-1.5.2.dist-info/METADATA +336 -0
- krims_code_cli-1.5.2.dist-info/RECORD +43 -0
- krims_code_cli-1.5.2.dist-info/WHEEL +5 -0
- krims_code_cli-1.5.2.dist-info/entry_points.txt +2 -0
- krims_code_cli-1.5.2.dist-info/licenses/LICENSE +21 -0
- krims_code_cli-1.5.2.dist-info/top_level.txt +1 -0
- krims_pip/__init__.py +1 -0
- krims_pip/cli.py +50 -0
- krims_pip/node_project/bin/krims-code.js +10 -0
- krims_pip/node_project/package-lock.json +793 -0
- krims_pip/node_project/package.json +46 -0
- krims_pip/node_project/src/agent.js +415 -0
- krims_pip/node_project/src/ai/fallback.js +239 -0
- krims_pip/node_project/src/ai/google.js +87 -0
- krims_pip/node_project/src/ai/providers.js +203 -0
- krims_pip/node_project/src/ai/router.js +146 -0
- krims_pip/node_project/src/ai/search.js +94 -0
- krims_pip/node_project/src/ai/telemetry.js +125 -0
- krims_pip/node_project/src/ai/tokens.js +101 -0
- krims_pip/node_project/src/ai/universal.js +507 -0
- krims_pip/node_project/src/ai/xai.js +50 -0
- krims_pip/node_project/src/chat.js +2543 -0
- krims_pip/node_project/src/cli.js +632 -0
- krims_pip/node_project/src/commands/commit.js +157 -0
- krims_pip/node_project/src/commands/dashboard.js +59 -0
- krims_pip/node_project/src/commands/github.js +349 -0
- krims_pip/node_project/src/commands/index.js +67 -0
- krims_pip/node_project/src/commands/status.js +66 -0
- krims_pip/node_project/src/commands/teamwork-preview.js +326 -0
- krims_pip/node_project/src/commands/theme.js +42 -0
- krims_pip/node_project/src/commands/themes.js +30 -0
- krims_pip/node_project/src/config.js +400 -0
- krims_pip/node_project/src/dashboard.js +112 -0
- krims_pip/node_project/src/file-parser.js +164 -0
- krims_pip/node_project/src/git.js +41 -0
- krims_pip/node_project/src/mic.js +220 -0
- krims_pip/node_project/src/modes.js +92 -0
- krims_pip/node_project/src/telemetry-server.js +855 -0
- krims_pip/node_project/src/ui/banner.js +204 -0
- krims_pip/node_project/src/ui/dashboard.html +834 -0
- krims_pip/node_project/src/ui/spinner.js +43 -0
- krims_pip/node_project/src/ui/theme.js +624 -0
- krims_pip/node_project/src/updater.js +196 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// Krims Code AI CLI — Main CLI Logic & Command Routing
|
|
3
|
+
// Universal AI Gateway — Supports 13+ providers
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { Marked } from "marked";
|
|
12
|
+
import { markedTerminal } from "marked-terminal";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
colors,
|
|
16
|
+
label,
|
|
17
|
+
separator,
|
|
18
|
+
keyValue,
|
|
19
|
+
clearStreamedText,
|
|
20
|
+
StreamFilter,
|
|
21
|
+
stripCodeFences,
|
|
22
|
+
setTheme,
|
|
23
|
+
} from "./ui/theme.js";
|
|
24
|
+
import { createSpinner } from "./ui/spinner.js";
|
|
25
|
+
import { routePrompt } from "./ai/router.js";
|
|
26
|
+
import { PROVIDERS, getProvidersByTier, getActiveProviders } from "./ai/providers.js";
|
|
27
|
+
import { startChat } from "./chat.js";
|
|
28
|
+
import { parseFile, formatContext } from "./file-parser.js";
|
|
29
|
+
import { MODES, DEFAULT_MODE, getModeByName } from "./modes.js";
|
|
30
|
+
import {
|
|
31
|
+
getAIConfig,
|
|
32
|
+
setConfigValue,
|
|
33
|
+
getConfigValue,
|
|
34
|
+
listConfig,
|
|
35
|
+
resetConfig,
|
|
36
|
+
deleteConfigValue,
|
|
37
|
+
getConfigPath,
|
|
38
|
+
configExists,
|
|
39
|
+
isValidConfigKey,
|
|
40
|
+
} from "./config.js";
|
|
41
|
+
import { registry } from "./commands/index.js";
|
|
42
|
+
|
|
43
|
+
// Configure marked dynamically for terminal output
|
|
44
|
+
const getMarked = () => new Marked(markedTerminal({
|
|
45
|
+
reflowText: true,
|
|
46
|
+
width: process.stdout.columns ? Math.max(20, process.stdout.columns - 4) : 80,
|
|
47
|
+
showSectionPrefix: false,
|
|
48
|
+
code: (c) => colors.orange(c),
|
|
49
|
+
codespan: (c) => colors.accent3(c),
|
|
50
|
+
heading: (h) => colors.accent.bold(h),
|
|
51
|
+
strong: (s) => colors.magenta.bold(s),
|
|
52
|
+
em: chalk.italic,
|
|
53
|
+
hr: (h) => colors.dim(h),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
57
|
+
const __dirname = dirname(__filename);
|
|
58
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
59
|
+
const VERSION = pkg.version;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets up and runs the Krims Code CLI.
|
|
63
|
+
* @param {string[]} argv - Process arguments
|
|
64
|
+
*/
|
|
65
|
+
export async function createCLI(argv) {
|
|
66
|
+
const program = new Command();
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.name("krims-code")
|
|
70
|
+
.description("Krims Code AI — Universal AI Gateway CLI\n Supports 13+ AI providers • Free & paid models • Local fallbacks")
|
|
71
|
+
.version(VERSION, "-v, --version");
|
|
72
|
+
|
|
73
|
+
// ── Chat Command ────────────────────────────────────────
|
|
74
|
+
program
|
|
75
|
+
.command("chat")
|
|
76
|
+
.description("Start an interactive chat session")
|
|
77
|
+
.option("-m, --mode <mode>", `Reasoning mode (${Object.keys(MODES).filter(m => m !== "claude-code").join(", ")})`, DEFAULT_MODE)
|
|
78
|
+
.option("-p, --provider <provider>", "Preferred AI provider (openai, groq, google, etc.)")
|
|
79
|
+
.action(async (opts) => {
|
|
80
|
+
await startChat({ mode: opts.mode, preferredProvider: opts.provider });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ── Ask Command ─────────────────────────────────────────
|
|
84
|
+
program
|
|
85
|
+
.command("ask <prompt...>")
|
|
86
|
+
.description("Send a single prompt and get a response")
|
|
87
|
+
.option("-m, --mode <mode>", "Reasoning mode", DEFAULT_MODE)
|
|
88
|
+
.option("-f, --file <path>", "Attach a file for context")
|
|
89
|
+
.option("-p, --provider <provider>", "Preferred AI provider")
|
|
90
|
+
.option("--model <model>", "Override the AI model")
|
|
91
|
+
.option("--raw", "Output raw text without formatting")
|
|
92
|
+
.action(async (promptParts, opts) => {
|
|
93
|
+
const prompt = promptParts.join(" ");
|
|
94
|
+
await handleAsk(prompt, opts);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ── Config Command ──────────────────────────────────────
|
|
98
|
+
const configCmd = program
|
|
99
|
+
.command("config")
|
|
100
|
+
.description("Manage API keys and settings");
|
|
101
|
+
|
|
102
|
+
configCmd
|
|
103
|
+
.command("set <key> <value>")
|
|
104
|
+
.description("Set a config value (e.g., GROQ_API_KEY, OPENAI_API_KEY)")
|
|
105
|
+
.action(async (key, value) => {
|
|
106
|
+
await handleConfigSet(key, value);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
configCmd
|
|
110
|
+
.command("get <key>")
|
|
111
|
+
.description("Get a configuration value")
|
|
112
|
+
.action(async (key) => {
|
|
113
|
+
await handleConfigGet(key);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
configCmd
|
|
117
|
+
.command("list")
|
|
118
|
+
.description("List all configuration (keys masked)")
|
|
119
|
+
.action(async () => {
|
|
120
|
+
await handleConfigList();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
configCmd
|
|
124
|
+
.command("delete <key>")
|
|
125
|
+
.description("Delete a configuration key")
|
|
126
|
+
.action(async (key) => {
|
|
127
|
+
await handleConfigDelete(key);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
configCmd
|
|
131
|
+
.command("reset")
|
|
132
|
+
.description("Delete all configuration")
|
|
133
|
+
.action(async () => {
|
|
134
|
+
await handleConfigReset();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
configCmd
|
|
138
|
+
.command("path")
|
|
139
|
+
.description("Show config file location")
|
|
140
|
+
.action(() => {
|
|
141
|
+
console.log("\n" + label.config + " " + colors.text(getConfigPath()) + "\n");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ── Providers Command ───────────────────────────────────
|
|
145
|
+
program
|
|
146
|
+
.command("providers")
|
|
147
|
+
.description("List all supported AI providers and their status")
|
|
148
|
+
.option("--free", "Show only free-tier providers")
|
|
149
|
+
.action(async (opts) => {
|
|
150
|
+
await handleProviders(opts);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ── Models Command ──────────────────────────────────────
|
|
154
|
+
program
|
|
155
|
+
.command("models [provider]")
|
|
156
|
+
.description("List available models for a provider")
|
|
157
|
+
.action((provider) => {
|
|
158
|
+
handleModels(provider);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── Modes Command ───────────────────────────────────────
|
|
162
|
+
program
|
|
163
|
+
.command("modes")
|
|
164
|
+
.description("List all reasoning modes")
|
|
165
|
+
.action(() => {
|
|
166
|
+
handleModes();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── Setup Command ───────────────────────────────────────
|
|
170
|
+
program
|
|
171
|
+
.command("setup")
|
|
172
|
+
.description("Interactive guided setup for API keys")
|
|
173
|
+
.action(async () => {
|
|
174
|
+
await handleSetup();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Load registry commands and register them dynamically
|
|
178
|
+
await registry.load();
|
|
179
|
+
const commands = registry.getAll();
|
|
180
|
+
for (const cmd of commands) {
|
|
181
|
+
const signature = cmd.signature || cmd.name;
|
|
182
|
+
let cmdObj = program.command(signature);
|
|
183
|
+
|
|
184
|
+
if (cmd.description) {
|
|
185
|
+
cmdObj.description(cmd.description);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (cmd.aliases && Array.isArray(cmd.aliases)) {
|
|
189
|
+
for (const alias of cmd.aliases) {
|
|
190
|
+
cmdObj.alias(alias);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (cmd.options && Array.isArray(cmd.options)) {
|
|
195
|
+
for (const opt of cmd.options) {
|
|
196
|
+
if (opt.defaultValue !== undefined) {
|
|
197
|
+
cmdObj.option(opt.flags, opt.description, opt.defaultValue);
|
|
198
|
+
} else {
|
|
199
|
+
cmdObj.option(opt.flags, opt.description);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
cmdObj.action(async (...args) => {
|
|
205
|
+
const opts = cmdObj.opts();
|
|
206
|
+
const cliArgs = args.filter(a => typeof a === 'string' || Array.isArray(a));
|
|
207
|
+
await cmd.executeCLI(cliArgs, opts);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Default: Show help ──────────────────────────────────
|
|
212
|
+
program.action(() => {
|
|
213
|
+
showMiniBanner();
|
|
214
|
+
program.help();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await program.parseAsync(argv);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ═══════════════════════════════════════════════════════════
|
|
221
|
+
// COMMAND HANDLERS
|
|
222
|
+
// ═══════════════════════════════════════════════════════════
|
|
223
|
+
|
|
224
|
+
async function handleAsk(prompt, opts) {
|
|
225
|
+
const mode = getModeByName(opts.mode) || MODES[DEFAULT_MODE];
|
|
226
|
+
const aiConfig = await getAIConfig();
|
|
227
|
+
|
|
228
|
+
// Set theme from configuration
|
|
229
|
+
const theme = aiConfig.THEME || "cyberpunk";
|
|
230
|
+
setTheme(theme);
|
|
231
|
+
|
|
232
|
+
// Override model if specified
|
|
233
|
+
if (opts.model) {
|
|
234
|
+
// Set it for all providers
|
|
235
|
+
for (const p of Object.values(PROVIDERS)) {
|
|
236
|
+
aiConfig[`${p.key.replace("_API_KEY", "")}_MODEL`] = opts.model;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Attach file context if specified
|
|
241
|
+
let fullPrompt = prompt;
|
|
242
|
+
if (opts.file) {
|
|
243
|
+
try {
|
|
244
|
+
const fileData = await parseFile(opts.file);
|
|
245
|
+
fullPrompt = formatContext(fileData) + "\n\n" + prompt;
|
|
246
|
+
console.log(label.file + " " + colors.accent(`Attached: ${fileData.name}`) + colors.dim(` (${formatBytes(fileData.size)})`));
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.log(label.error + " " + colors.danger(err.message));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!opts.raw) {
|
|
254
|
+
console.log(label.mode + " " + colors.muted(`${mode.label} • ${mode.layer}`));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const queryStartTime = Date.now();
|
|
258
|
+
let firstTokenTime = 0;
|
|
259
|
+
const spinner = createSpinner(colors.muted("Routing through failover mesh..."));
|
|
260
|
+
spinner.start();
|
|
261
|
+
|
|
262
|
+
let hasStartedStreaming = false;
|
|
263
|
+
let streamedText = "";
|
|
264
|
+
const filter = !opts.raw ? new StreamFilter(process.stdout.write.bind(process.stdout)) : null;
|
|
265
|
+
const onToken = (token) => {
|
|
266
|
+
if (!hasStartedStreaming) {
|
|
267
|
+
hasStartedStreaming = true;
|
|
268
|
+
firstTokenTime = Date.now();
|
|
269
|
+
spinner.stop();
|
|
270
|
+
}
|
|
271
|
+
if (filter) {
|
|
272
|
+
filter.write(token);
|
|
273
|
+
} else {
|
|
274
|
+
process.stdout.write(token);
|
|
275
|
+
}
|
|
276
|
+
streamedText += token;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const result = await routePrompt(fullPrompt, mode.systemPrompt, aiConfig, onToken);
|
|
281
|
+
spinner.stop();
|
|
282
|
+
if (filter) {
|
|
283
|
+
filter.flush();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (opts.raw) {
|
|
287
|
+
if (!hasStartedStreaming) {
|
|
288
|
+
console.log(result.text);
|
|
289
|
+
} else {
|
|
290
|
+
if (!result.text.endsWith("\n")) {
|
|
291
|
+
console.log("");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
if (hasStartedStreaming) {
|
|
296
|
+
clearStreamedText(filter ? filter.filteredText : streamedText);
|
|
297
|
+
}
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log(label.krims + " " + colors.dim(`via ${result.provider}${result.model ? ` (${result.model})` : ""} • Node ${result.node}`));
|
|
300
|
+
console.log(separator("─"));
|
|
301
|
+
console.log("");
|
|
302
|
+
|
|
303
|
+
if (result.provider === "local" || result.provider === "offline-fallback") {
|
|
304
|
+
console.log(colors.text(" " + result.text.split("\n").join("\n ")));
|
|
305
|
+
} else {
|
|
306
|
+
let displayText = result.text;
|
|
307
|
+
const cleanedText = displayText.replace(/\[WRITE_FILE:\s*([^\n\]]+)\][\s\S]*?\[END_WRITE\]/g, (match, p1) => {
|
|
308
|
+
return `\n\n${colors.brand("⚡ [File creation request: " + p1 + "]")}\n\n`;
|
|
309
|
+
});
|
|
310
|
+
const rendered = getMarked().parse(cleanedText);
|
|
311
|
+
console.log(rendered);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const elapsedSec = ((Date.now() - queryStartTime) / 1000).toFixed(1);
|
|
315
|
+
let speedText = "";
|
|
316
|
+
if (firstTokenTime > 0) {
|
|
317
|
+
const streamElapsed = (Date.now() - firstTokenTime) / 1000;
|
|
318
|
+
if (streamElapsed > 0.05) {
|
|
319
|
+
const estimatedTokens = Math.max(1, Math.round(streamedText.length / 4));
|
|
320
|
+
const tps = (estimatedTokens / streamElapsed).toFixed(1);
|
|
321
|
+
speedText = ` • ${tps} tok/s`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log(separator("─"));
|
|
326
|
+
console.log(
|
|
327
|
+
" " + colors.dim(`Node ${result.node} • ${result.provider}`) +
|
|
328
|
+
(result.model ? colors.dim(` • ${result.model}`) : "") +
|
|
329
|
+
colors.dim(` • ${elapsedSec}s${speedText}`)
|
|
330
|
+
);
|
|
331
|
+
console.log("");
|
|
332
|
+
|
|
333
|
+
// Parse file write blocks
|
|
334
|
+
const writeRegex = /\[WRITE_FILE:\s*([^\n\]]+)\]\n([\s\S]*?)\n\[END_WRITE\]/g;
|
|
335
|
+
let match;
|
|
336
|
+
const fileWrites = [];
|
|
337
|
+
while ((match = writeRegex.exec(result.text)) !== null) {
|
|
338
|
+
fileWrites.push({ path: match[1].trim(), content: stripCodeFences(match[2]) });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (fileWrites.length > 0) {
|
|
342
|
+
const { resolve, dirname } = await import("node:path");
|
|
343
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
344
|
+
|
|
345
|
+
for (const fileWrite of fileWrites) {
|
|
346
|
+
const finalPath = resolve(fileWrite.path);
|
|
347
|
+
console.log("");
|
|
348
|
+
console.log(label.system + " " + colors.warning(`Auto-Writing File: ${colors.accent(finalPath)} (${fileWrite.content.length} bytes)`));
|
|
349
|
+
try {
|
|
350
|
+
const dir = dirname(finalPath);
|
|
351
|
+
await mkdir(dir, { recursive: true });
|
|
352
|
+
await writeFile(finalPath, fileWrite.content, "utf-8");
|
|
353
|
+
console.log(" " + colors.success(`✓ File created successfully!\n`));
|
|
354
|
+
} catch (err) {
|
|
355
|
+
console.log(" " + colors.danger(`✗ Write failed: ${err.message}\n`));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
spinner.fail("Request failed");
|
|
362
|
+
console.error(label.error + " " + colors.danger(err.message));
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function handleConfigSet(key, value) {
|
|
368
|
+
const normalizedKey = key.toUpperCase();
|
|
369
|
+
|
|
370
|
+
if (!isValidConfigKey(normalizedKey)) {
|
|
371
|
+
console.log("\n" + label.config + " " + colors.warning(`Unknown key format: "${normalizedKey}"`));
|
|
372
|
+
console.log(" " + colors.muted("Use format: PROVIDER_API_KEY or PROVIDER_MODEL"));
|
|
373
|
+
console.log(" " + colors.muted("Example: GROQ_API_KEY, OPENAI_API_KEY, GOOGLE_MODEL\n"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await setConfigValue(normalizedKey, value);
|
|
378
|
+
const maskedValue = normalizedKey.includes("KEY") && value.length > 8
|
|
379
|
+
? value.slice(0, 6) + "•••" + value.slice(-3)
|
|
380
|
+
: value;
|
|
381
|
+
console.log("\n" + label.config + " " + colors.success(`✓ Set ${normalizedKey} = ${maskedValue}`));
|
|
382
|
+
console.log(" " + colors.muted(`Saved to ${getConfigPath()}`) + "\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function handleConfigGet(key) {
|
|
386
|
+
const normalizedKey = key.toUpperCase();
|
|
387
|
+
const value = await getConfigValue(normalizedKey);
|
|
388
|
+
|
|
389
|
+
if (value === undefined) {
|
|
390
|
+
console.log("\n" + label.config + " " + colors.muted(`"${normalizedKey}" is not set.\n`));
|
|
391
|
+
} else {
|
|
392
|
+
const masked = normalizedKey.includes("KEY") && typeof value === "string" && value.length > 8
|
|
393
|
+
? value.slice(0, 6) + "•••" + value.slice(-3)
|
|
394
|
+
: value;
|
|
395
|
+
console.log("\n" + label.config + " " + keyValue(normalizedKey, masked) + "\n");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function handleConfigList() {
|
|
400
|
+
const exists = await configExists();
|
|
401
|
+
if (!exists) {
|
|
402
|
+
console.log("\n" + label.config + " " + colors.muted("No config file found."));
|
|
403
|
+
console.log(" " + colors.muted("Run ") + colors.accent("krims-code setup") + colors.muted(" for guided setup.\n"));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const config = await listConfig();
|
|
408
|
+
const keys = Object.keys(config);
|
|
409
|
+
|
|
410
|
+
if (keys.length === 0) {
|
|
411
|
+
console.log("\n" + label.config + " " + colors.muted("Config file is empty.\n"));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
console.log("");
|
|
416
|
+
console.log(colors.brand(" ◈ CONFIGURATION"));
|
|
417
|
+
console.log(separator("─"));
|
|
418
|
+
for (const [k, v] of Object.entries(config)) {
|
|
419
|
+
console.log(keyValue(" " + k, v));
|
|
420
|
+
}
|
|
421
|
+
console.log("");
|
|
422
|
+
console.log(" " + colors.dim(`Location: ${getConfigPath()}`) + "\n");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function handleConfigDelete(key) {
|
|
426
|
+
const normalizedKey = key.toUpperCase();
|
|
427
|
+
await deleteConfigValue(normalizedKey);
|
|
428
|
+
console.log("\n" + label.config + " " + colors.success(`✓ Deleted "${normalizedKey}"`) + "\n");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function handleConfigReset() {
|
|
432
|
+
await resetConfig();
|
|
433
|
+
console.log("\n" + label.config + " " + colors.success("✓ All configuration cleared.") + "\n");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function handleProviders(opts) {
|
|
437
|
+
const aiConfig = await getAIConfig();
|
|
438
|
+
const active = getActiveProviders(aiConfig);
|
|
439
|
+
const activeIds = new Set(active.map((a) => a.id));
|
|
440
|
+
|
|
441
|
+
console.log("");
|
|
442
|
+
console.log(colors.brand(" ⚡ SUPPORTED AI PROVIDERS"));
|
|
443
|
+
console.log(separator("─"));
|
|
444
|
+
|
|
445
|
+
const tiers = getProvidersByTier();
|
|
446
|
+
const sections = [
|
|
447
|
+
{ label: "🆓 FREE TIER", providers: tiers.free, color: "#67ffb0" },
|
|
448
|
+
{ label: "🔓 FREE + PAID", providers: tiers["free+paid"], color: "#ffb900" },
|
|
449
|
+
{ label: "💎 PAID", providers: tiers.paid, color: "#6ce8ff" },
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
for (const section of sections) {
|
|
453
|
+
if (opts.free && section.label === "💎 PAID") continue;
|
|
454
|
+
|
|
455
|
+
console.log("");
|
|
456
|
+
console.log(" " + chalk.hex(section.color).bold(section.label));
|
|
457
|
+
console.log("");
|
|
458
|
+
|
|
459
|
+
for (const p of section.providers) {
|
|
460
|
+
const status = activeIds.has(p.id)
|
|
461
|
+
? colors.success(" ✓ ACTIVE")
|
|
462
|
+
: colors.dim(" ○ Not configured");
|
|
463
|
+
const name = chalk.hex(section.color).bold(p.name.padEnd(18));
|
|
464
|
+
console.log(` ${name} ${status}`);
|
|
465
|
+
console.log(` ${"".padEnd(18)} ${colors.muted(p.description)}`);
|
|
466
|
+
console.log(` ${"".padEnd(18)} ${colors.dim("Key: ")}${colors.accent(p.key)} ${colors.dim("Model: ")}${colors.text(p.defaultModel)}`);
|
|
467
|
+
console.log("");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
console.log(separator("─"));
|
|
472
|
+
console.log(" " + colors.muted("Configure: ") + colors.accent("krims-code config set <KEY_NAME> <your-key>"));
|
|
473
|
+
console.log(" " + colors.muted("Quick setup: ") + colors.accent("krims-code setup"));
|
|
474
|
+
console.log("");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function handleModels(providerName) {
|
|
478
|
+
if (!providerName) {
|
|
479
|
+
console.log("");
|
|
480
|
+
console.log(colors.brand(" ◈ MODELS BY PROVIDER"));
|
|
481
|
+
console.log(separator("─"));
|
|
482
|
+
|
|
483
|
+
for (const [id, p] of Object.entries(PROVIDERS)) {
|
|
484
|
+
console.log("");
|
|
485
|
+
console.log(" " + colors.accent(p.name));
|
|
486
|
+
for (const m of p.models) {
|
|
487
|
+
const isDefault = m === p.defaultModel;
|
|
488
|
+
console.log(" " + (isDefault ? colors.accent3(" ★ " + m) : colors.muted(" " + m)));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
console.log("");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const key = providerName.toLowerCase();
|
|
496
|
+
const provider = PROVIDERS[key];
|
|
497
|
+
|
|
498
|
+
if (!provider) {
|
|
499
|
+
console.log("\n" + label.error + " " + colors.danger(`Unknown provider: "${providerName}"`));
|
|
500
|
+
console.log(" " + colors.muted("Available: " + Object.keys(PROVIDERS).join(", ")) + "\n");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
console.log("");
|
|
505
|
+
console.log(colors.brand(` ◈ ${provider.name} MODELS`));
|
|
506
|
+
console.log(separator("─"));
|
|
507
|
+
for (const m of provider.models) {
|
|
508
|
+
const isDefault = m === provider.defaultModel;
|
|
509
|
+
console.log(" " + (isDefault ? colors.accent3("★ " + m + " (default)") : colors.text(" " + m)));
|
|
510
|
+
}
|
|
511
|
+
console.log("");
|
|
512
|
+
console.log(" " + colors.muted("Override: ") + colors.accent(`krims-code ask --model ${provider.models[0]} "prompt"`) + "\n");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function handleModes() {
|
|
516
|
+
console.log("");
|
|
517
|
+
console.log(colors.brand(" ◈ KRIMS CODE REASONING MODES"));
|
|
518
|
+
console.log(separator("─"));
|
|
519
|
+
console.log("");
|
|
520
|
+
|
|
521
|
+
for (const mode of Object.values(MODES)) {
|
|
522
|
+
const badge = chalk.hex("#6ce8ff").bold(`[${mode.label}]`);
|
|
523
|
+
console.log(` ${badge} ${colors.dim(mode.layer)}`);
|
|
524
|
+
console.log(" " + colors.text(mode.description));
|
|
525
|
+
|
|
526
|
+
const sig = mode.signal;
|
|
527
|
+
const bar = (val) => {
|
|
528
|
+
const filled = Math.round(val / 10);
|
|
529
|
+
return chalk.hex("#6ce8ff")("█".repeat(filled)) + chalk.hex("#1a2a3a")("░".repeat(10 - filled)) + colors.dim(` ${val}%`);
|
|
530
|
+
};
|
|
531
|
+
console.log(
|
|
532
|
+
" " + colors.dim("RSN ") + bar(sig.reasoning) +
|
|
533
|
+
" " + colors.dim("CLR ") + bar(sig.clarity)
|
|
534
|
+
);
|
|
535
|
+
console.log(
|
|
536
|
+
" " + colors.dim("SIQ ") + bar(sig.systemIQ) +
|
|
537
|
+
" " + colors.dim("DLV ") + bar(sig.delivery)
|
|
538
|
+
);
|
|
539
|
+
console.log("");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function handleSetup() {
|
|
544
|
+
const { createInterface } = await import("node:readline");
|
|
545
|
+
|
|
546
|
+
console.log("");
|
|
547
|
+
console.log(colors.brand(" ⚡ KRIMS CODE SETUP WIZARD"));
|
|
548
|
+
console.log(separator("─"));
|
|
549
|
+
console.log("");
|
|
550
|
+
console.log(colors.text(" Configure your AI providers. Press Enter to skip any provider."));
|
|
551
|
+
console.log(colors.muted(" Keys are stored locally at: " + getConfigPath()));
|
|
552
|
+
console.log("");
|
|
553
|
+
|
|
554
|
+
const rl = createInterface({
|
|
555
|
+
input: process.stdin,
|
|
556
|
+
output: process.stdout,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const ask = (question) => new Promise((resolve) => {
|
|
560
|
+
rl.question(" " + colors.accent("? ") + colors.text(question) + " ", (answer) => {
|
|
561
|
+
resolve(answer.trim());
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Recommended free providers first
|
|
566
|
+
const setupOrder = [
|
|
567
|
+
{ id: "groq", hint: "Get free key at https://console.groq.com" },
|
|
568
|
+
{ id: "google", hint: "Get free key at https://aistudio.google.com/apikey" },
|
|
569
|
+
{ id: "openrouter", hint: "Free models at https://openrouter.ai/keys" },
|
|
570
|
+
{ id: "together", hint: "Free credits at https://api.together.xyz" },
|
|
571
|
+
{ id: "cerebras", hint: "Free tier at https://cloud.cerebras.ai" },
|
|
572
|
+
{ id: "cohere", hint: "Free dev key at https://dashboard.cohere.com" },
|
|
573
|
+
{ id: "openai", hint: "Paid — https://platform.openai.com/api-keys" },
|
|
574
|
+
{ id: "anthropic", hint: "Paid — https://console.anthropic.com" },
|
|
575
|
+
{ id: "xai", hint: "Paid — https://console.x.ai" },
|
|
576
|
+
{ id: "mistral", hint: "https://console.mistral.ai" },
|
|
577
|
+
{ id: "deepseek", hint: "https://platform.deepseek.com" },
|
|
578
|
+
{ id: "perplexity", hint: "https://www.perplexity.ai/settings/api" },
|
|
579
|
+
{ id: "fireworks", hint: "https://fireworks.ai/api-keys" },
|
|
580
|
+
];
|
|
581
|
+
|
|
582
|
+
let configured = 0;
|
|
583
|
+
|
|
584
|
+
for (const { id, hint } of setupOrder) {
|
|
585
|
+
const provider = PROVIDERS[id];
|
|
586
|
+
if (!provider) continue;
|
|
587
|
+
|
|
588
|
+
const tierBadge = provider.tier === "free"
|
|
589
|
+
? chalk.hex("#67ffb0")("[FREE]")
|
|
590
|
+
: provider.tier === "free+paid"
|
|
591
|
+
? chalk.hex("#ffb900")("[FREE+PAID]")
|
|
592
|
+
: chalk.hex("#6ce8ff")("[PAID]");
|
|
593
|
+
|
|
594
|
+
console.log(` ${tierBadge} ${colors.text.bold(provider.name)} — ${colors.dim(provider.description)}`);
|
|
595
|
+
console.log(" " + colors.dim(hint));
|
|
596
|
+
|
|
597
|
+
const key = await ask(`${provider.key}:`);
|
|
598
|
+
if (key) {
|
|
599
|
+
await setConfigValue(provider.key, key);
|
|
600
|
+
console.log(" " + colors.success("✓ Saved!") + "\n");
|
|
601
|
+
configured++;
|
|
602
|
+
} else {
|
|
603
|
+
console.log(" " + colors.dim("Skipped") + "\n");
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
rl.close();
|
|
608
|
+
|
|
609
|
+
console.log(separator("─", 62));
|
|
610
|
+
if (configured > 0) {
|
|
611
|
+
console.log("\n " + colors.success(`✓ Setup complete! ${configured} provider(s) configured.`));
|
|
612
|
+
console.log(" " + colors.muted("Start chatting: ") + colors.accent("krims-code chat"));
|
|
613
|
+
console.log(" " + colors.muted("Quick query: ") + colors.accent('krims-code ask "Hello!"'));
|
|
614
|
+
} else {
|
|
615
|
+
console.log("\n " + colors.warning("No providers configured. Krims Code will use Krylo fallback mode."));
|
|
616
|
+
console.log(" " + colors.muted("Run ") + colors.accent("krims-code setup") + colors.muted(" again anytime."));
|
|
617
|
+
}
|
|
618
|
+
console.log("");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function showMiniBanner() {
|
|
622
|
+
console.log("");
|
|
623
|
+
console.log(colors.brand(" ⚡ Krims Code AI v110") + colors.dim(" — Universal AI Gateway"));
|
|
624
|
+
console.log(separator("─"));
|
|
625
|
+
console.log("");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function formatBytes(bytes) {
|
|
629
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
630
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
631
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
632
|
+
}
|