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.
Files changed (43) hide show
  1. krims_code_cli-1.5.2.dist-info/METADATA +336 -0
  2. krims_code_cli-1.5.2.dist-info/RECORD +43 -0
  3. krims_code_cli-1.5.2.dist-info/WHEEL +5 -0
  4. krims_code_cli-1.5.2.dist-info/entry_points.txt +2 -0
  5. krims_code_cli-1.5.2.dist-info/licenses/LICENSE +21 -0
  6. krims_code_cli-1.5.2.dist-info/top_level.txt +1 -0
  7. krims_pip/__init__.py +1 -0
  8. krims_pip/cli.py +50 -0
  9. krims_pip/node_project/bin/krims-code.js +10 -0
  10. krims_pip/node_project/package-lock.json +793 -0
  11. krims_pip/node_project/package.json +46 -0
  12. krims_pip/node_project/src/agent.js +415 -0
  13. krims_pip/node_project/src/ai/fallback.js +239 -0
  14. krims_pip/node_project/src/ai/google.js +87 -0
  15. krims_pip/node_project/src/ai/providers.js +203 -0
  16. krims_pip/node_project/src/ai/router.js +146 -0
  17. krims_pip/node_project/src/ai/search.js +94 -0
  18. krims_pip/node_project/src/ai/telemetry.js +125 -0
  19. krims_pip/node_project/src/ai/tokens.js +101 -0
  20. krims_pip/node_project/src/ai/universal.js +507 -0
  21. krims_pip/node_project/src/ai/xai.js +50 -0
  22. krims_pip/node_project/src/chat.js +2543 -0
  23. krims_pip/node_project/src/cli.js +632 -0
  24. krims_pip/node_project/src/commands/commit.js +157 -0
  25. krims_pip/node_project/src/commands/dashboard.js +59 -0
  26. krims_pip/node_project/src/commands/github.js +349 -0
  27. krims_pip/node_project/src/commands/index.js +67 -0
  28. krims_pip/node_project/src/commands/status.js +66 -0
  29. krims_pip/node_project/src/commands/teamwork-preview.js +326 -0
  30. krims_pip/node_project/src/commands/theme.js +42 -0
  31. krims_pip/node_project/src/commands/themes.js +30 -0
  32. krims_pip/node_project/src/config.js +400 -0
  33. krims_pip/node_project/src/dashboard.js +112 -0
  34. krims_pip/node_project/src/file-parser.js +164 -0
  35. krims_pip/node_project/src/git.js +41 -0
  36. krims_pip/node_project/src/mic.js +220 -0
  37. krims_pip/node_project/src/modes.js +92 -0
  38. krims_pip/node_project/src/telemetry-server.js +855 -0
  39. krims_pip/node_project/src/ui/banner.js +204 -0
  40. krims_pip/node_project/src/ui/dashboard.html +834 -0
  41. krims_pip/node_project/src/ui/spinner.js +43 -0
  42. krims_pip/node_project/src/ui/theme.js +624 -0
  43. 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
+ }