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,2543 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Krims Code AI CLI — Interactive Chat Loop
3
+ // Universal AI Gateway & Cyberpunk Command Center
4
+ // ═══════════════════════════════════════════════════════════
5
+
6
+ import { createInterface } from "node:readline";
7
+ import { writeFile } from "node:fs/promises";
8
+ import { readdirSync, existsSync, statSync } from "node:fs";
9
+ import { resolve, join, sep } from "node:path";
10
+ import { exec } from "node:child_process";
11
+ import chalk from "chalk";
12
+ import { Marked } from "marked";
13
+ import { markedTerminal } from "marked-terminal";
14
+
15
+ import {
16
+ colors,
17
+ label,
18
+ separator,
19
+ keyValue,
20
+ bullet,
21
+ modeBadge,
22
+ clearStreamedText,
23
+ StreamFilter,
24
+ stripCodeFences,
25
+ getActiveTheme,
26
+ setTheme,
27
+ getThemesList,
28
+ interactiveMenu,
29
+ getIcon,
30
+ highlightCode
31
+ } from "./ui/theme.js";
32
+ import { createSpinner } from "./ui/spinner.js";
33
+ import { showBanner } from "./ui/banner.js";
34
+ import { routePrompt } from "./ai/router.js";
35
+ import { getActiveProviders } from "./ai/providers.js";
36
+ import {
37
+ getAIConfig,
38
+ loadHistory,
39
+ saveHistory,
40
+ clearHistory,
41
+ setConfigValue,
42
+ listSessions,
43
+ switchSession,
44
+ startNewSession
45
+ } from "./config.js";
46
+ import { MODES, DEFAULT_MODE, getModeByName } from "./modes.js";
47
+ import { parseFile, formatContext } from "./file-parser.js";
48
+ import { runMainframeHack } from "./ai/fallback.js";
49
+ import { AGENT_INSTRUCTIONS } from "./agent.js";
50
+ import { checkForUpdates } from "./updater.js";
51
+ import { getSessionTokenStats, getBreakdownByModel, resetSessionTokenStats } from "./ai/tokens.js";
52
+ import { getGitDiff } from "./git.js";
53
+ import { registry } from "./commands/index.js";
54
+
55
+
56
+
57
+ // Configure marked dynamically for terminal output
58
+ const getMarked = () => new Marked(markedTerminal({
59
+ reflowText: true,
60
+ width: process.stdout.columns ? Math.max(20, process.stdout.columns - 4) : 80,
61
+ showSectionPrefix: false,
62
+ code: (c, lang) => highlightCode(c, lang),
63
+ codespan: (c) => colors.accent3(c),
64
+ heading: (h) => colors.accent.bold(h),
65
+ strong: (s) => colors.magenta.bold(s),
66
+ em: chalk.italic,
67
+ hr: (h) => colors.dim(h),
68
+ }));
69
+
70
+ /**
71
+ * Starts the interactive krims-code chat session.
72
+ * @param {{ mode?: string, preferredProvider?: string }} [options={}]
73
+ */
74
+ export async function startChat(options = {}) {
75
+ // Load AI config
76
+ const aiConfig = await getAIConfig();
77
+
78
+ // Load registry commands
79
+ await registry.load();
80
+
81
+ // Run update check
82
+ await checkForUpdates();
83
+
84
+ // Reset token stats for the new session
85
+ resetSessionTokenStats();
86
+
87
+
88
+ // Set theme from configuration
89
+ const theme = aiConfig.THEME || "cyberpunk";
90
+ setTheme(theme);
91
+
92
+ let currentMode = getModeByName(options.mode) || getModeByName(aiConfig.DEFAULT_MODE) || MODES[DEFAULT_MODE];
93
+ let attachedFiles = [];
94
+
95
+ // Persistent history loader
96
+ const history = await loadHistory();
97
+
98
+ // Mini-game state
99
+ const game = {
100
+ active: false,
101
+ code: "",
102
+ attempts: 0,
103
+ maxAttempts: 6,
104
+ };
105
+
106
+ // Show banner
107
+ showBanner(currentMode.name);
108
+
109
+ // Active providers diagnostic check
110
+ const active = getActiveProviders(aiConfig);
111
+ if (active.length === 0) {
112
+ console.log(
113
+ "\n" + label.system + " " +
114
+ colors.warning("No API keys configured. Using local fallback solvers.") + "\n" +
115
+ " " + colors.muted("Run ") + colors.accent("krims-code setup") +
116
+ colors.muted(" to configure providers (free options available!).\n")
117
+ );
118
+ } else {
119
+ const providerNames = active.map((a) => a.provider.name);
120
+ const unique = [...new Set(providerNames)];
121
+ console.log(
122
+ label.mesh + " " +
123
+ colors.accent("Failover mesh online: ") +
124
+ colors.text(unique.join(" → ")) +
125
+ colors.muted(" → Offline fallback")
126
+ );
127
+ console.log(
128
+ " " + colors.dim(`${active.length} node(s) active across ${unique.length} provider(s)`) + "\n"
129
+ );
130
+ }
131
+
132
+ // Display loaded history message if any
133
+ if (history.length > 0) {
134
+ console.log(
135
+ " " + label.info + " " +
136
+ colors.muted(`Restored ${Math.floor(history.length / 2)} message exchanges from persistent logs.`) + "\n"
137
+ );
138
+ }
139
+
140
+ // Completer: handles commands & dynamic local file path autocomplete
141
+ const completer = (line) => {
142
+ const builtIn = [
143
+ "/help", "/mode", "/modes", "/attach", "/files", "/clear",
144
+ "/providers", "/export", "/copy", "/exit", "/quit",
145
+ "/history-clear", "/game", "/abort", "/cmd", "/write",
146
+ "/run", "/history", "/autopilot", "/tokens", "/update",
147
+ "/review", "/diagnose", "/goal", "/explain", "/refactor", "/bug", "/doc", "/translate",
148
+ "/search", "/git", "/cd", "/mic"
149
+ ];
150
+ for (const registryCmd of registry.getAll()) {
151
+ builtIn.push(`/${registryCmd.name}`);
152
+ if (registryCmd.aliases) {
153
+ for (const alias of registryCmd.aliases) {
154
+ builtIn.push(`/${alias}`);
155
+ }
156
+ }
157
+ }
158
+ const customCmds = aiConfig.CUSTOM_COMMANDS || {};
159
+ const commands = [...builtIn, ...Object.keys(customCmds)];
160
+
161
+ // File path autocompletion on /attach or /cd
162
+ if (line.startsWith("/attach ") || line.startsWith("/cd ")) {
163
+ const isCd = line.startsWith("/cd ");
164
+ const prefix = isCd ? "/cd " : "/attach ";
165
+ const query = line.slice(prefix.length);
166
+ const lastSlash = Math.max(query.lastIndexOf("/"), query.lastIndexOf("\\"));
167
+ let searchDir = ".";
168
+ let searchPrefix = query;
169
+
170
+ if (lastSlash !== -1) {
171
+ searchDir = query.slice(0, lastSlash);
172
+ if (searchDir === "") {
173
+ searchDir = sep;
174
+ }
175
+ searchPrefix = query.slice(lastSlash + 1);
176
+ }
177
+
178
+ try {
179
+ const resolved = resolve(searchDir);
180
+ if (existsSync(resolved) && statSync(resolved).isDirectory()) {
181
+ const files = readdirSync(resolved);
182
+ const hits = files
183
+ .filter((f) => f.toLowerCase().startsWith(searchPrefix.toLowerCase()) && !f.startsWith("."))
184
+ .map((f) => {
185
+ const fullPath = searchDir === "." || searchDir === sep ? f : join(searchDir, f);
186
+ const fullResolved = resolve(fullPath);
187
+ const isDir = statSync(fullResolved).isDirectory();
188
+ if (isCd && !isDir) return null;
189
+ return `${prefix}${fullPath}${isDir ? "/" : ""}`;
190
+ })
191
+ .filter(Boolean);
192
+ return [hits.length ? hits : [], line];
193
+ }
194
+ } catch (e) {
195
+ // Fallback silently on fs errors
196
+ }
197
+ return [[], line];
198
+ }
199
+
200
+ // Sub-arguments autocomplete on /mode
201
+ if (line.startsWith("/mode ")) {
202
+ const query = line.slice(6).toLowerCase();
203
+ const modesList = Object.keys(MODES);
204
+ const hits = modesList
205
+ .filter((m) => m.startsWith(query))
206
+ .map((m) => `/mode ${m}`);
207
+ return [hits.length ? hits : [], line];
208
+ }
209
+
210
+ // Sub-arguments autocomplete on /theme
211
+ if (line.startsWith("/theme ")) {
212
+ const query = line.slice(7).toLowerCase();
213
+ const themesList = getThemesList();
214
+ const hits = themesList
215
+ .filter((t) => t.startsWith(query))
216
+ .map((t) => `/theme ${t}`);
217
+ return [hits.length ? hits : [], line];
218
+ }
219
+
220
+ // Sub-arguments autocomplete on /cmd
221
+ if (line.startsWith("/cmd ")) {
222
+ const query = line.slice(5).toLowerCase();
223
+ const subcmds = ["list", "add", "remove"];
224
+ const hits = subcmds
225
+ .filter((s) => s.startsWith(query))
226
+ .map((s) => `/cmd ${s}`);
227
+ return [hits.length ? hits : [], line];
228
+ }
229
+
230
+ const hits = commands.filter((c) => c.startsWith(line));
231
+ return [hits.length ? hits : [], line];
232
+ };
233
+
234
+ // Create readline interface
235
+ const rl = createInterface({
236
+ input: process.stdin,
237
+ output: process.stdout,
238
+ prompt: colors.accent(" ❯ "),
239
+ terminal: true,
240
+ completer
241
+ });
242
+
243
+ // Load persistent history entries directly into the shell up/down array
244
+ if (history.length > 0) {
245
+ const userQueries = history
246
+ .filter((h) => h.role === "user")
247
+ .map((h) => h.content);
248
+ // Readline history is structured newest first (index 0)
249
+ rl.history = [...new Set(userQueries)].reverse();
250
+ }
251
+
252
+ // ── AI Execution Helper ──────────────────────────────────
253
+ async function executeAIQuery(promptText, originalInput = promptText) {
254
+ // ── Build Prompt with Context ─────────────────────────
255
+ let fullPrompt = promptText;
256
+ if (attachedFiles.length > 0) {
257
+ const contexts = attachedFiles.map((f) => formatContext(f)).join("\n\n");
258
+ fullPrompt = `${contexts}\n\n${promptText}`;
259
+ }
260
+
261
+ // Append AGENT_INSTRUCTIONS to mode systemPrompt
262
+ const systemPrompt = currentMode.systemPrompt + "\n" + AGENT_INSTRUCTIONS;
263
+
264
+ let loopCount = 0;
265
+ const MAX_LOOPS = 5;
266
+ let currentQueryPrompt = fullPrompt;
267
+ let aiResponseText = "";
268
+ let lastResult = null;
269
+
270
+ try {
271
+ while (loopCount < MAX_LOOPS) {
272
+ const queryStartTime = Date.now();
273
+ let firstTokenTime = 0;
274
+
275
+ if (loopCount > 0) {
276
+ console.log(colors.accent(`\n🤖 [Krims Code Autopilot Mode - Iteration ${loopCount + 1}/${MAX_LOOPS}]`));
277
+ }
278
+
279
+ const spinner = createSpinner(
280
+ colors.muted(loopCount === 0 ? `Routing through mesh ${currentMode.label}...` : `Agent executing tasks...`)
281
+ );
282
+ spinner.start();
283
+
284
+ let hasStartedStreaming = false;
285
+ let streamedText = "";
286
+ const filter = new StreamFilter(process.stdout.write.bind(process.stdout));
287
+ const onToken = (token) => {
288
+ if (!hasStartedStreaming) {
289
+ hasStartedStreaming = true;
290
+ firstTokenTime = Date.now();
291
+ spinner.stop();
292
+ }
293
+ filter.write(token);
294
+ streamedText += token;
295
+ };
296
+
297
+ const result = await routePrompt(currentQueryPrompt, systemPrompt, aiConfig, onToken, history);
298
+ spinner.stop();
299
+ filter.flush();
300
+
301
+ aiResponseText = result.text;
302
+ lastResult = result;
303
+
304
+ if (hasStartedStreaming) {
305
+ clearStreamedText(filter.filteredText);
306
+ }
307
+
308
+ // Display response
309
+ console.log("");
310
+ console.log(label.krims + " " + providerBadge(result));
311
+ console.log(separator("─"));
312
+ console.log("");
313
+
314
+ if (result.provider === "local" || result.provider === "offline-fallback") {
315
+ console.log(colors.text(" " + result.text.split("\n").join("\n ")));
316
+ } else {
317
+ let displayText = result.text;
318
+ const rendered = getMarked().parse(displayText);
319
+ console.log(rendered);
320
+ }
321
+
322
+ const elapsedSec = ((Date.now() - queryStartTime) / 1000).toFixed(1);
323
+ let speedText = "";
324
+ if (firstTokenTime > 0) {
325
+ const streamElapsed = (Date.now() - firstTokenTime) / 1000;
326
+ if (streamElapsed > 0.05) {
327
+ const estimatedTokens = Math.max(1, Math.round(streamedText.length / 4));
328
+ const tps = (estimatedTokens / streamElapsed).toFixed(1);
329
+ speedText = ` • ${tps} tok/s`;
330
+ }
331
+ }
332
+
333
+ const showTokens = aiConfig.SHOW_TOKENS !== "false";
334
+ let tokensText = "";
335
+ if (showTokens && result.usage) {
336
+ const { promptTokens, completionTokens } = result.usage;
337
+ tokensText = ` • ${promptTokens.toLocaleString()} in / ${completionTokens.toLocaleString()} out tokens`;
338
+ }
339
+
340
+ console.log(separator("─"));
341
+ console.log(
342
+ " " + colors.dim(`Node ${result.node} • ${result.provider}`) +
343
+ (result.model ? colors.dim(` • ${result.model}`) : "") +
344
+ colors.dim(` • ${elapsedSec}s${speedText}`) +
345
+ colors.dim(tokensText) +
346
+ colors.dim(` • ${Math.floor(history.length / 2)} exchanges`)
347
+ );
348
+ console.log("");
349
+
350
+ // Process any agent tools output by the AI
351
+ const { processAgentBlocks } = await import("./agent.js");
352
+ const toolResults = await processAgentBlocks(aiResponseText, aiConfig, rl);
353
+
354
+ if (toolResults.length === 0) {
355
+ // No tools executed, end loop
356
+ break;
357
+ }
358
+
359
+ // Store this turn in history so AI knows what happened
360
+ history.push({ role: "user", content: currentQueryPrompt, timestamp: new Date() });
361
+ history.push({
362
+ role: "assistant",
363
+ content: aiResponseText,
364
+ provider: result.provider,
365
+ model: result.model,
366
+ node: result.node,
367
+ timestamp: new Date(),
368
+ });
369
+ await saveHistory(history, currentMode.name);
370
+
371
+ // Format tool outputs as next prompt
372
+ let formattedResults = "### Agent Tool Outputs:\n";
373
+ for (const tr of toolResults) {
374
+ if (tr.success) {
375
+ if (tr.tool === "RUN_COMMAND") {
376
+ formattedResults += `\n- RUN_COMMAND "${tr.arg}" succeeded. Output:\n\`\`\`\n${tr.stdout || ""}${tr.stderr || ""}\n\`\`\`;`;
377
+ } else if (tr.tool === "READ_FILE") {
378
+ formattedResults += `\n- READ_FILE "${tr.arg}" succeeded. File Content:\n\`\`\`\n${tr.content}\n\`\`\`;`;
379
+ } else if (tr.tool === "WRITE_FILE") {
380
+ formattedResults += `\n- WRITE_FILE "${tr.arg}" succeeded.`;
381
+ } else if (tr.tool === "SEARCH_WEB") {
382
+ if (tr.results && tr.results.length > 0) {
383
+ const resultsList = tr.results.map((r, i) => `${i+1}. [${r.title}](${r.url})\n ${r.snippet}`).join("\n");
384
+ formattedResults += `\n- SEARCH_WEB "${tr.arg}" succeeded. Results:\n${resultsList}`;
385
+ } else {
386
+ formattedResults += `\n- SEARCH_WEB "${tr.arg}" succeeded. No search results were found.`;
387
+ }
388
+ }
389
+ } else {
390
+ formattedResults += `\n- ${tr.tool} "${tr.arg}" failed: ${tr.error}`;
391
+ }
392
+ }
393
+ formattedResults += "\n\nPlease continue and finalize your task or perform next steps.";
394
+
395
+ currentQueryPrompt = formattedResults;
396
+ loopCount++;
397
+ }
398
+
399
+ // Store final state in history
400
+ if (loopCount > 0) {
401
+ // Just save to disk to persist
402
+ await saveHistory(history, currentMode.name);
403
+ } else {
404
+ // Standard single-turn save
405
+ history.push({ role: "user", content: originalInput, timestamp: new Date() });
406
+ history.push({
407
+ role: "assistant",
408
+ content: aiResponseText,
409
+ provider: lastResult.provider,
410
+ model: lastResult.model,
411
+ node: lastResult.node,
412
+ timestamp: new Date(),
413
+ });
414
+ await saveHistory(history, currentMode.name);
415
+ }
416
+
417
+ } catch (err) {
418
+ console.log("\n" + label.error + " " + colors.danger(err.message) + "\n");
419
+ }
420
+
421
+ // Sync shell's recall history list
422
+ const userQueries = history
423
+ .filter((h) => h.role === "user")
424
+ .map((h) => h.content);
425
+ rl.history = [...new Set(userQueries)].reverse();
426
+ }
427
+
428
+ rl.prompt();
429
+
430
+ rl.on("line", async (line) => {
431
+ const input = line.trim();
432
+ if (!input) {
433
+ rl.prompt();
434
+ return;
435
+ }
436
+
437
+ // ── Handle Game Input ──────────────────────────────────
438
+ if (game.active && !input.startsWith("/")) {
439
+ handleGuess(input, game);
440
+ rl.prompt();
441
+ return;
442
+ }
443
+
444
+ // ── Handle Slash Commands ──────────────────────────────
445
+ if (input.startsWith("/")) {
446
+ const [cmd, ...args] = input.split(/\s+/);
447
+ const builtInList = [
448
+ "/", "/help", "/mode", "/modes", "/attach", "/files", "/clear",
449
+ "/providers", "/export", "/copy", "/exit", "/quit",
450
+ "/history-clear", "/game", "/abort", "/cmd",
451
+ "/guess", "/write", "/run", "/history", "/autopilot", "/tokens",
452
+ "/update", "/review", "/diagnose", "/goal", "/explain", "/refactor", "/bug", "/doc",
453
+ "/translate", "/search", "/git", "/cd", "/mic"
454
+ ];
455
+ for (const registryCmd of registry.getAll()) {
456
+ builtInList.push(`/${registryCmd.name}`);
457
+ if (registryCmd.aliases) {
458
+ for (const alias of registryCmd.aliases) {
459
+ builtInList.push(`/${alias}`);
460
+ }
461
+ }
462
+ }
463
+
464
+ const customCmds = aiConfig.CUSTOM_COMMANDS || {};
465
+
466
+ if (!builtInList.includes(cmd.toLowerCase()) && customCmds[cmd]) {
467
+ const template = customCmds[cmd];
468
+ const userArg = args.join(" ");
469
+ const rewrittenPrompt = template + (userArg ? " " + userArg : "");
470
+
471
+ console.log("\n" + label.system + " " + colors.accent(`Executing custom command: `) + colors.text(cmd));
472
+ console.log(" " + colors.muted("Prompt: ") + colors.text(rewrittenPrompt) + "\n");
473
+
474
+ await executeAIQuery(rewrittenPrompt, input);
475
+ rl.prompt();
476
+ return;
477
+ }
478
+
479
+ const handled = await handleCommand(input, {
480
+ currentMode,
481
+ attachedFiles,
482
+ history,
483
+ aiConfig,
484
+ game,
485
+ setMode: (mode) => { currentMode = mode; },
486
+ addFile: (file) => { attachedFiles.push(file); },
487
+ clearFiles: () => { attachedFiles = []; },
488
+ rl,
489
+ });
490
+ if (handled !== "exit") {
491
+ rl.prompt();
492
+ }
493
+ return;
494
+ }
495
+
496
+ await executeAIQuery(input);
497
+ rl.prompt();
498
+ });
499
+
500
+ rl.on("close", () => {
501
+ console.log("\n" + label.system + " " + colors.muted("Session terminated. Stay cyberpunk. ⚡\n"));
502
+ process.exit(0);
503
+ });
504
+ }
505
+
506
+ /**
507
+ * Handles slash commands in the chat.
508
+ */
509
+ async function handleCommand(input, ctx) {
510
+ const [cmd, ...args] = input.split(/\s+/);
511
+
512
+ const slashName = cmd.startsWith("/") ? cmd.slice(1).toLowerCase() : cmd.toLowerCase();
513
+ const registeredCmd = registry.get(slashName);
514
+ if (registeredCmd) {
515
+ const result = await registeredCmd.executeChat(args, ctx);
516
+ if (result === "exit") {
517
+ return "exit";
518
+ }
519
+ return;
520
+ }
521
+
522
+ switch (cmd.toLowerCase()) {
523
+ case "/":
524
+ case "/help":
525
+ showHelp(ctx.aiConfig);
526
+ break;
527
+
528
+ case "/mode":
529
+ handleModeSwitch(args, ctx);
530
+ break;
531
+
532
+ case "/modes":
533
+ showModes();
534
+ break;
535
+
536
+ case "/attach":
537
+ await handleAttach(args, ctx);
538
+ break;
539
+
540
+ case "/files":
541
+ showAttachedFiles(ctx.attachedFiles);
542
+ break;
543
+
544
+ case "/clear":
545
+ // Actual screen clear & scrollback reset
546
+ process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
547
+ showBanner(ctx.currentMode.name);
548
+ break;
549
+
550
+ case "/cd":
551
+ await handleCd(args, ctx);
552
+ break;
553
+
554
+ case "/export":
555
+ await handleExport(ctx.history);
556
+ break;
557
+
558
+ case "/status":
559
+ // handled dynamically
560
+ break;
561
+
562
+ case "/providers":
563
+ showActiveProviders(ctx.aiConfig);
564
+ break;
565
+
566
+ case "/update":
567
+ console.log("\n" + label.system + " " + colors.muted("Checking registry for updates..."));
568
+ await checkForUpdates(true);
569
+ console.log("");
570
+ break;
571
+
572
+ case "/review":
573
+ await handleReviewCommand(ctx);
574
+ break;
575
+
576
+ case "/diagnose":
577
+ await handleDiagnoseCommand(args, ctx);
578
+ break;
579
+
580
+ case "/goal":
581
+ await handleGoalCommand(args, ctx);
582
+ break;
583
+
584
+ case "/explain":
585
+ case "/refactor":
586
+ case "/bug":
587
+ case "/doc":
588
+ case "/translate":
589
+ await handleFileAICommand(cmd, args, ctx);
590
+ break;
591
+
592
+ case "/search":
593
+ await handleSearchCommand(args, ctx);
594
+ break;
595
+
596
+ case "/theme":
597
+ // handled dynamically
598
+ break;
599
+
600
+ case "/themes":
601
+ // handled dynamically
602
+ break;
603
+
604
+ case "/history-clear":
605
+ await handleHistoryClear(ctx.history, ctx.rl);
606
+ break;
607
+
608
+ case "/game":
609
+ handleGameStart(ctx.game);
610
+ break;
611
+
612
+ case "/abort":
613
+ handleGameAbort(ctx.game);
614
+ break;
615
+
616
+ case "/guess":
617
+ if (ctx.game.active) {
618
+ handleGuess(args[0] || "", ctx.game);
619
+ } else {
620
+ console.log("\n" + label.system + " " + colors.warning("Game is not active. Type /game to start.\n"));
621
+ }
622
+ break;
623
+
624
+ case "/copy":
625
+ await handleCopy(ctx.history);
626
+ break;
627
+
628
+ case "/cmd":
629
+ await handleCustomCommands(args, ctx);
630
+ break;
631
+
632
+ case "/write":
633
+ await handleWriteFile(args, ctx);
634
+ break;
635
+
636
+ case "/commit":
637
+ // handled dynamically
638
+ break;
639
+
640
+ case "/run":
641
+ await handleRunCommand(args, ctx);
642
+ break;
643
+
644
+ case "/history":
645
+ await handleHistorySwitch(ctx);
646
+ break;
647
+
648
+ case "/autopilot":
649
+ await handleAutopilotSwitch(args, ctx);
650
+ break;
651
+
652
+ case "/git":
653
+ await handleGitTUI(ctx);
654
+ break;
655
+
656
+ case "/dashboard":
657
+ // handled dynamically
658
+ break;
659
+
660
+ case "/mic":
661
+ await handleMicInput(ctx);
662
+ break;
663
+
664
+ case "/tokens":
665
+ await handleTokensDisplay(ctx);
666
+ break;
667
+
668
+ case "/exit":
669
+ case "/quit":
670
+ ctx.rl.close();
671
+ return "exit";
672
+
673
+ default:
674
+ console.log("\n" + label.system + " " + colors.warning(`Unknown command: ${cmd}. Type /help for available commands.\n`));
675
+ }
676
+ }
677
+
678
+ // ── Command Handlers ────────────────────────────────────────
679
+
680
+ function showHelp(aiConfig) {
681
+ console.log("");
682
+ console.log(colors.brand(" ⚡ KRIMS CODE CLI COMMANDS"));
683
+ console.log(separator("─"));
684
+ console.log("");
685
+ console.log(keyValue("/", "Show this help menu"));
686
+ console.log(keyValue("/help", "Show this help menu"));
687
+ console.log(keyValue("/mode <name>", "Switch mode (" + Object.keys(MODES).join(", ") + ")"));
688
+ console.log(keyValue("/modes", "List all modes with signal metrics"));
689
+ console.log(keyValue("/theme <name>", "Switch visual theme (cyberpunk, matrix, synthwave, crimson)"));
690
+ console.log(keyValue("/themes", "List available visual themes"));
691
+ console.log(keyValue("/attach <path>", "Attach a file for context (supports Tab path autocomplete!)"));
692
+ console.log(keyValue("/files", "List attached files"));
693
+ console.log(keyValue("/cd <path>", "Change current working directory of this session (supports Tab path autocomplete!)"));
694
+ console.log(keyValue("/clear", "Clear terminal screen and reprint banner"));
695
+ console.log(keyValue("/providers", "Show active AI providers"));
696
+ console.log(keyValue("/export", "Export conversation to file"));
697
+ console.log(keyValue("/history", "List, switch, and resume past interactive chat sessions"));
698
+ console.log(keyValue("/history-clear", "Clear saved persistent chat history"));
699
+ console.log(keyValue("/autopilot <mode|debug [cmd]>", "View/switch autopilot level (off, safe, workspace, machine) or run autonomous debug loop"));
700
+ console.log(keyValue("/git", "Launch interactive Git branch tree, history, and file staging TUI"));
701
+ console.log(keyValue("/dashboard", "Spawn web-based local cyberpunk telemetry dashboard companion"));
702
+ console.log(keyValue("/mic", "Record audio voice input from microphone and transcribe to text"));
703
+ console.log(keyValue("/tokens", "View detailed session token usage and exchanges telemetry"));
704
+ console.log(keyValue("/update", "Force check for updates and update Krims Code CLI manually"));
705
+ console.log(keyValue("/game", "Start the local mainframe hacking mini-game"));
706
+ console.log(keyValue("/copy", "Copy the last assistant response to clipboard"));
707
+ console.log(keyValue("/cmd <list|add|remove>", "Manage custom command shortcuts"));
708
+ console.log(keyValue("/write <filename>", "Extract last code block and save to file"));
709
+ console.log(keyValue("/commit", "Generate conventional commit message and commit changes"));
710
+ console.log(keyValue("/run <command>", "Execute a shell command interactively"));
711
+ console.log(keyValue("/review", "Run git diff and stream an AI code review"));
712
+ console.log(keyValue("/diagnose [cmd]", "Run build/tests and AI-debug any errors"));
713
+ console.log(keyValue("/goal <task>", "Run an autonomous feedback loop to achieve a specific goal"));
714
+ console.log(keyValue("/explain <file>", "AI-explain the design and logic of a file"));
715
+ console.log(keyValue("/refactor <file>", "AI-refactor the code of a target file"));
716
+ console.log(keyValue("/bug <file>", "AI-audit a file to find potential logic bugs"));
717
+ console.log(keyValue("/doc <file>", "AI-generate documentation/docstrings for a file"));
718
+ console.log(keyValue("/translate <file> <lang>", "AI-translate code of a file to another language"));
719
+ console.log(keyValue("/search <query>", "Find matches in code files (use --ai for semantic search)"));
720
+ console.log(keyValue("/exit", "End session"));
721
+
722
+ if (aiConfig && aiConfig.CUSTOM_COMMANDS) {
723
+ const custom = aiConfig.CUSTOM_COMMANDS;
724
+ const entries = Object.entries(custom);
725
+ if (entries.length > 0) {
726
+ console.log("");
727
+ console.log(colors.brand(" ⚡ CUSTOM SHORTCUTS"));
728
+ console.log(separator("─"));
729
+ for (const [cmd, template] of entries) {
730
+ console.log(keyValue(cmd, `Shortcut for: "${template}"`));
731
+ }
732
+ }
733
+ }
734
+ console.log("");
735
+ }
736
+
737
+ function handleModeSwitch(args, ctx) {
738
+ const modeName = args[0];
739
+ if (!modeName) {
740
+ console.log("\n" + label.mode + " " + colors.warning("Usage: /mode <" + Object.keys(MODES).join("|") + ">\n"));
741
+ return;
742
+ }
743
+
744
+ const newMode = getModeByName(modeName);
745
+ if (!newMode) {
746
+ console.log("\n" + label.mode + " " + colors.danger(`Unknown mode: "${modeName}".`) + " " + colors.muted("Available: " + Object.keys(MODES).join(", ") + "\n"));
747
+ return;
748
+ }
749
+
750
+ ctx.setMode(newMode);
751
+ console.log("\n" + label.mode + " " + colors.accent("Switched to ") + modeBadge(newMode.name));
752
+ console.log(" " + colors.muted(newMode.description) + "\n");
753
+
754
+ const sig = newMode.signal;
755
+ console.log(" " + signalBar("Reasoning", sig.reasoning));
756
+ console.log(" " + signalBar("Clarity", sig.clarity));
757
+ console.log(" " + signalBar("System IQ", sig.systemIQ));
758
+ console.log(" " + signalBar("Delivery", sig.delivery));
759
+ console.log("");
760
+ }
761
+
762
+ function showModes() {
763
+ console.log("");
764
+ console.log(colors.brand(" ◈ AVAILABLE REASONING MODES"));
765
+ console.log(separator("─"));
766
+ console.log("");
767
+
768
+ for (const mode of Object.values(MODES)) {
769
+ console.log(" " + modeBadge(mode.name) + " " + colors.muted(`(${mode.layer})`));
770
+ console.log(" " + colors.text(mode.description));
771
+ const sig = mode.signal;
772
+ console.log(" " + signalBar("RSN", sig.reasoning) + " " + signalBar("CLR", sig.clarity) + " " + signalBar("SIQ", sig.systemIQ) + " " + signalBar("DLV", sig.delivery));
773
+ console.log("");
774
+ }
775
+ }
776
+
777
+ async function handleCd(args, ctx) {
778
+ const { homedir } = await import("node:os");
779
+ if (args.length === 0) {
780
+ try {
781
+ const home = homedir();
782
+ process.chdir(home);
783
+ console.log("\n" + label.system + " " + colors.success(`Changed directory to: `) + colors.text(home) + "\n");
784
+ } catch (err) {
785
+ console.log("\n" + label.error + " " + colors.danger(`Failed to change directory: ${err.message}`) + "\n");
786
+ }
787
+ return;
788
+ }
789
+
790
+ const targetPath = args.join(" ").trim();
791
+ const resolvedPath = resolve(targetPath);
792
+
793
+ try {
794
+ if (!existsSync(resolvedPath)) {
795
+ console.log("\n" + label.error + " " + colors.danger(`Directory does not exist: ${targetPath}`) + "\n");
796
+ return;
797
+ }
798
+
799
+ const stat = statSync(resolvedPath);
800
+ if (!stat.isDirectory()) {
801
+ console.log("\n" + label.error + " " + colors.danger(`Path is not a directory: ${targetPath}`) + "\n");
802
+ return;
803
+ }
804
+
805
+ process.chdir(resolvedPath);
806
+ console.log("\n" + label.system + " " + colors.success(`Changed directory to: `) + colors.text(resolvedPath) + "\n");
807
+ } catch (err) {
808
+ console.log("\n" + label.error + " " + colors.danger(`Failed to change directory: ${err.message}`) + "\n");
809
+ }
810
+ }
811
+
812
+ async function handleAttach(args, ctx) {
813
+ const filePath = args.join(" ").trim();
814
+ if (!filePath) {
815
+ const { scanWorkspaceFiles } = await import("./file-parser.js");
816
+ const { interactiveCheckbox } = await import("./ui/theme.js");
817
+
818
+ const workspaceFiles = scanWorkspaceFiles(process.cwd());
819
+ if (workspaceFiles.length === 0) {
820
+ console.log("\n" + label.file + " " + colors.muted("No supported files found in this workspace.\n"));
821
+ return;
822
+ }
823
+
824
+ ctx.rl.pause();
825
+ const selected = await interactiveCheckbox(
826
+ "Attach files (Arrow Keys to navigate, Space to toggle, Enter to confirm, Esc/q to cancel):\n",
827
+ workspaceFiles,
828
+ ctx.attachedFiles.map(f => f.relativePath || f.name)
829
+ );
830
+ ctx.rl.resume();
831
+
832
+ if (selected === null) {
833
+ console.log("\n" + label.file + " " + colors.muted("Selection canceled.\n"));
834
+ return;
835
+ }
836
+
837
+ ctx.clearFiles();
838
+ if (selected.length === 0) {
839
+ console.log("\n" + label.file + " " + colors.success("Cleared all attachments.\n"));
840
+ return;
841
+ }
842
+
843
+ let successCount = 0;
844
+ for (const file of selected) {
845
+ try {
846
+ const fileData = await parseFile(file);
847
+ fileData.relativePath = file;
848
+ ctx.addFile(fileData);
849
+ successCount++;
850
+ } catch (err) {
851
+ console.log(label.error + " " + colors.danger(`Failed to attach ${file}: ${err.message}`));
852
+ }
853
+ }
854
+ console.log("\n" + label.file + " " + colors.success(`Successfully attached ${successCount} file(s).\n`));
855
+ return;
856
+ }
857
+
858
+ try {
859
+ const fileData = await parseFile(filePath);
860
+ ctx.addFile(fileData);
861
+ console.log("\n" + label.file + " " + colors.success(`Attached: ${fileData.name}`));
862
+ console.log(" " + colors.muted(`${formatBytes(fileData.size)} • ${fileData.extension} • ${ctx.attachedFiles.length} file(s) loaded\n`));
863
+ } catch (err) {
864
+ console.log("\n" + label.error + " " + colors.danger(err.message) + "\n");
865
+ }
866
+ }
867
+
868
+ function showAttachedFiles(files) {
869
+ if (files.length === 0) {
870
+ console.log("\n" + label.file + " " + colors.muted("No files attached. Use /attach <path> to add context.\n"));
871
+ return;
872
+ }
873
+
874
+ console.log("");
875
+ console.log(label.file + " " + colors.accent(`${files.length} file(s) attached:`));
876
+ for (const f of files) {
877
+ console.log(bullet(`${f.name} (${formatBytes(f.size)}, ${f.extension})`));
878
+ }
879
+ console.log("");
880
+ }
881
+
882
+ async function handleExport(history) {
883
+ if (history.length === 0) {
884
+ console.log("\n" + label.system + " " + colors.muted("No conversation to export.\n"));
885
+ return;
886
+ }
887
+
888
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
889
+ const filename = `krims-chat-${timestamp}.md`;
890
+ const filepath = resolve(filename);
891
+
892
+ let content = `# Krims Code AI Chat Export\n*Exported at ${new Date().toLocaleString()}*\n\n---\n\n`;
893
+
894
+ for (const entry of history) {
895
+ if (entry.role === "user") {
896
+ content += `## 👤 You\n${entry.content}\n\n`;
897
+ } else {
898
+ content += `## 🤖 Krims Code (${entry.provider || "unknown"})\n${entry.content}\n\n---\n\n`;
899
+ }
900
+ }
901
+
902
+ try {
903
+ await writeFile(filepath, content, "utf-8");
904
+ console.log("\n" + label.system + " " + colors.success(`Exported to: ${filepath}\n`));
905
+ } catch (err) {
906
+ console.log("\n" + label.error + " " + colors.danger(`Export failed: ${err.message}\n`));
907
+ }
908
+ }
909
+
910
+ function showStatus_unused(ctx) {
911
+ const active = getActiveProviders(ctx.aiConfig);
912
+
913
+ console.log("");
914
+ console.log(colors.brand(" ◈ SESSION STATUS"));
915
+ console.log(separator("─"));
916
+ console.log(keyValue(" Theme", getActiveTheme().toUpperCase()));
917
+ console.log(keyValue(" Mode", ctx.currentMode.label));
918
+ console.log(keyValue(" Layer", ctx.currentMode.layer));
919
+ console.log(keyValue(" Exchanges", String(Math.floor(ctx.history.length / 2))));
920
+ console.log(keyValue(" Files", String(ctx.attachedFiles.length)));
921
+ console.log(keyValue(" Providers", String(active.length)));
922
+ console.log("");
923
+ }
924
+
925
+ function showActiveProviders(aiConfig) {
926
+ const active = getActiveProviders(aiConfig);
927
+
928
+ console.log("");
929
+ console.log(colors.brand(" ◈ ACTIVE PROVIDERS"));
930
+ console.log(separator("─"));
931
+
932
+ if (active.length === 0) {
933
+ console.log(" " + colors.warning("No providers. Run `krims-code setup` to configure.") + "\n");
934
+ return;
935
+ }
936
+
937
+ for (const { provider } of active) {
938
+ console.log(" " + colors.success("✓ ") + colors.text(provider.name) + colors.dim(` • ${provider.defaultModel}`));
939
+ }
940
+ console.log(" " + colors.success("✓ ") + colors.text("Offline Fallback") + colors.dim(" • Local fallback"));
941
+ console.log(" " + colors.success("✓ ") + colors.text("Math Solver") + colors.dim(" • Local"));
942
+ console.log("");
943
+ }
944
+
945
+ async function handleThemeSwitch_unused(args) {
946
+ const themeName = args[0];
947
+ if (!themeName) {
948
+ console.log("\n" + label.system + " " + colors.warning("Usage: /theme <theme-name>. Type /themes to list themes.\n"));
949
+ return;
950
+ }
951
+
952
+ const success = setTheme(themeName);
953
+ if (success) {
954
+ await setConfigValue("THEME", themeName.toLowerCase().trim());
955
+ console.log("\n" + label.system + " " + colors.success(`✓ Theme switched to ${themeName.toUpperCase()}`));
956
+ console.log(" " + colors.muted("Visual grid modulates synchronized.\n"));
957
+ } else {
958
+ console.log("\n" + label.system + " " + colors.danger(`Unknown theme: "${themeName}".`) + " " + colors.muted(`Available: ${getThemesList().join(", ")}\n`));
959
+ }
960
+ }
961
+
962
+ function showThemesList_unused() {
963
+ console.log("");
964
+ console.log(colors.brand(" ◈ AVAILABLE COLOR THEMES"));
965
+ console.log(separator("─"));
966
+ const active = getActiveTheme();
967
+ for (const t of getThemesList()) {
968
+ const activeText = t === active ? colors.success("★ ACTIVE") : "";
969
+ console.log(bullet(t.toUpperCase().padEnd(14) + activeText));
970
+ }
971
+ console.log("");
972
+ }
973
+
974
+ async function handleHistoryClear(history, rl) {
975
+ await clearHistory();
976
+ history.length = 0;
977
+ if (rl) rl.history = [];
978
+ console.log("\n" + label.system + " " + colors.success("✓ Persistent chat history and prompt history cleared.\n"));
979
+ }
980
+
981
+ async function handleAutopilotSwitch(args, ctx) {
982
+ const setting = args[0]?.toLowerCase().trim();
983
+ if (setting === "debug") {
984
+ await handleAutopilotDebug(args.slice(1).join(" "), ctx);
985
+ return;
986
+ }
987
+ if (!setting) {
988
+ const current = (ctx.aiConfig.AUTOPILOT || "off").toUpperCase();
989
+ console.log("\n" + label.system + " " + colors.brand("🤖 AUTOPILOT AGENT CONFIGURATION"));
990
+ console.log(separator("─"));
991
+ console.log(keyValue(" Current Setting", current));
992
+ console.log("");
993
+ console.log(" " + colors.muted("Available Modes:"));
994
+ console.log(" • " + colors.accent("off") + colors.text(" - Always ask user for confirmation before executing any actions."));
995
+ console.log(" • " + colors.accent("safe") + colors.text(" - Run read-only/safe terminal commands and searches automatically."));
996
+ console.log(" • " + colors.accent("workspace") + colors.text(" - Run any actions automatically if they stay inside the workspace."));
997
+ console.log(" • " + colors.accent("machine") + colors.text(" - Complete autopilot. Run any action automatically (Full access)."));
998
+ console.log("");
999
+ console.log(" " + colors.muted("To change setting: ") + colors.accent("/autopilot <mode>") + "\n");
1000
+ return;
1001
+ }
1002
+
1003
+ const valid = ["off", "safe", "workspace", "machine"];
1004
+ if (!valid.includes(setting)) {
1005
+ console.log("\n" + label.system + " " + colors.danger(`ERROR: Unknown autopilot mode "${setting}".`) + " " + colors.muted("Choose from: off, safe, workspace, machine.\n"));
1006
+ return;
1007
+ }
1008
+
1009
+ await setConfigValue("AUTOPILOT", setting);
1010
+ ctx.aiConfig.AUTOPILOT = setting;
1011
+ console.log("\n" + label.system + " " + colors.success(`✓ Autopilot setting updated to ${setting.toUpperCase()} successfully.\n`));
1012
+ }
1013
+
1014
+ async function handleHistorySwitch(ctx) {
1015
+ const sessions = listSessions();
1016
+ if (sessions.length === 0) {
1017
+ console.log("\n" + label.system + " " + colors.muted("No past chat sessions found.\n"));
1018
+ return;
1019
+ }
1020
+
1021
+ const items = sessions.map((s) => {
1022
+ const dateStr = new Date(s.timestamp).toLocaleString();
1023
+ const count = s.messages.length;
1024
+ const exchanges = Math.floor(count / 2);
1025
+ // Find first user query preview
1026
+ const firstQuery = s.messages.find((m) => m.role === "user")?.content || "Empty conversation";
1027
+ const preview = firstQuery.length > 50 ? firstQuery.slice(0, 47) + "..." : firstQuery;
1028
+ const modeBadgeText = `[${s.mode}]`;
1029
+ return `${colors.dim(dateStr)} ${colors.brand(modeBadgeText.padEnd(12))} ${colors.muted(exchanges + " exch")} • ${colors.text(preview)}`;
1030
+ });
1031
+
1032
+ // Add an option to start a new session
1033
+ items.push(colors.accent("➕ Start a new chat session"));
1034
+
1035
+ ctx.rl.pause();
1036
+ const selectedIndex = await interactiveMenu(
1037
+ "Select a past chat session to resume (Arrow Keys to navigate, Enter to select, Esc/q to cancel):\n",
1038
+ items
1039
+ );
1040
+ ctx.rl.resume();
1041
+
1042
+ if (selectedIndex === null) {
1043
+ console.log("\n" + label.system + " " + colors.muted("Selection canceled.\n"));
1044
+ return;
1045
+ }
1046
+
1047
+ // Clear screen and load the selected session
1048
+ process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
1049
+
1050
+ if (selectedIndex === sessions.length) {
1051
+ // Start new session
1052
+ const newSessionFile = startNewSession();
1053
+ ctx.history.length = 0;
1054
+ showBanner(ctx.currentMode.name);
1055
+ console.log("\n" + label.system + " " + colors.success("Started a new chat session.\n"));
1056
+ } else {
1057
+ const selectedSession = sessions[selectedIndex];
1058
+ switchSession(selectedSession.file);
1059
+
1060
+ // Load history
1061
+ const loadedHistory = await loadHistory();
1062
+ ctx.history.length = 0;
1063
+ for (const msg of loadedHistory) {
1064
+ ctx.history.push(msg);
1065
+ }
1066
+
1067
+ showBanner(ctx.currentMode.name);
1068
+ console.log("\n" + label.system + " " + colors.success(`✓ Switched to chat session from ${new Date(selectedSession.timestamp).toLocaleString()}`));
1069
+ console.log(" " + colors.muted(`Restored ${Math.floor(ctx.history.length / 2)} message exchanges.\n`));
1070
+ }
1071
+
1072
+ // Sync shell's recall history list
1073
+ const userQueries = ctx.history
1074
+ .filter((h) => h.role === "user")
1075
+ .map((h) => h.content);
1076
+ ctx.rl.history = [...new Set(userQueries)].reverse();
1077
+ }
1078
+
1079
+ function handleGameStart(game) {
1080
+ if (game.active) {
1081
+ console.log("\n" + label.system + " " + colors.warning("Mainframe breach is already in progress. Type /abort to cancel.\n"));
1082
+ return;
1083
+ }
1084
+
1085
+ // Set up game
1086
+ game.active = true;
1087
+ game.attempts = 0;
1088
+
1089
+ // Generate random 4-digit code
1090
+ const code = Array.from({ length: 4 }, () => Math.floor(Math.random() * 10)).join("");
1091
+ game.code = code;
1092
+
1093
+ const rules = runMainframeHack();
1094
+ console.log("\n" + rules.text + "\n");
1095
+ }
1096
+
1097
+ function handleGameAbort(game) {
1098
+ if (!game.active) {
1099
+ console.log("\n" + label.system + " " + colors.warning("No security breach in progress.\n"));
1100
+ return;
1101
+ }
1102
+ game.active = false;
1103
+ console.log("\n" + label.system + " " + colors.warning("Breach protocol aborted. Connection terminated.\n"));
1104
+ }
1105
+
1106
+ function handleGuess(input, game) {
1107
+ const guess = input.trim();
1108
+ if (!/^\d{4}$/.test(guess)) {
1109
+ console.log("\n" + label.error + " " + colors.danger("BREACH ERROR: Code must be exactly 4 digits (0-9).") + "\n");
1110
+ return;
1111
+ }
1112
+
1113
+ game.attempts++;
1114
+
1115
+ const codeArr = game.code.split("");
1116
+ const guessArr = guess.split("");
1117
+
1118
+ let hits = 0;
1119
+ let closes = 0;
1120
+
1121
+ const codeUsed = [false, false, false, false];
1122
+ const guessUsed = [false, false, false, false];
1123
+
1124
+ // First pass: Hits
1125
+ for (let i = 0; i < 4; i++) {
1126
+ if (guessArr[i] === codeArr[i]) {
1127
+ hits++;
1128
+ codeUsed[i] = true;
1129
+ guessUsed[i] = true;
1130
+ }
1131
+ }
1132
+
1133
+ // Second pass: Closes
1134
+ for (let i = 0; i < 4; i++) {
1135
+ if (guessUsed[i]) continue;
1136
+ for (let j = 0; j < 4; j++) {
1137
+ if (codeUsed[j]) continue;
1138
+ if (guessArr[i] === codeArr[j]) {
1139
+ closes++;
1140
+ codeUsed[j] = true;
1141
+ break;
1142
+ }
1143
+ }
1144
+ }
1145
+
1146
+ console.log("");
1147
+ console.log(colors.magenta(` [BREACH ATTEMPT #${game.attempts} / ${game.maxAttempts}]`));
1148
+ console.log(colors.text(` BREACH INPUT: ${guess.split("").join(" ")}`));
1149
+ console.log(colors.success(` HITS (Pos): ${"█ ".repeat(hits)}${"░ ".repeat(4 - hits)} (${hits})`));
1150
+ console.log(colors.warning(` CLOSE (Val): ${"█ ".repeat(closes)}${"░ ".repeat(4 - closes)} (${closes})`));
1151
+ console.log("");
1152
+
1153
+ if (hits === 4) {
1154
+ console.log(label.system + " " + colors.success("MAINFRAME BYPASSED! Access granted. Decryption complete. 🔓\n"));
1155
+ game.active = false;
1156
+ } else if (game.attempts >= game.maxAttempts) {
1157
+ console.log(label.error + " " + colors.danger("SECURITY SHUTDOWN! Mainframe locked out. Intrusion logged. 🔒"));
1158
+ console.log(" Intrusion PIN was: " + colors.accent(game.code) + "\n");
1159
+ game.active = false;
1160
+ } else {
1161
+ console.log(colors.muted(" Recalibrating security bypass codes...") + "\n");
1162
+ }
1163
+ }
1164
+
1165
+ async function handleCopy(history) {
1166
+ const lastResponse = [...history].reverse().find((h) => h.role === "assistant");
1167
+ if (!lastResponse) {
1168
+ console.log("\n" + label.system + " " + colors.muted("No response to copy yet.\n"));
1169
+ return;
1170
+ }
1171
+
1172
+ try {
1173
+ await copyToClipboard(lastResponse.content);
1174
+ console.log("\n" + label.system + " " + colors.success("✓ Last response copied to OS Clipboard successfully!\n"));
1175
+ } catch (err) {
1176
+ console.log("\n" + label.system + " " + colors.muted("Unable to copy automatically. Displaying content below:"));
1177
+ console.log(colors.text(lastResponse.content.slice(0, 800)));
1178
+ if (lastResponse.content.length > 800) {
1179
+ console.log(colors.dim(" [... truncated, use /export to save full conversation]"));
1180
+ }
1181
+ console.log("");
1182
+ }
1183
+ }
1184
+
1185
+ function copyToClipboard(text) {
1186
+ return new Promise((resolve, reject) => {
1187
+ let command;
1188
+ if (process.platform === "win32") {
1189
+ command = "clip";
1190
+ } else if (process.platform === "darwin") {
1191
+ command = "pbcopy";
1192
+ } else {
1193
+ command = "xclip -selection clipboard || xsel -ib";
1194
+ }
1195
+
1196
+ try {
1197
+ const child = exec(command, (err) => {
1198
+ if (err) reject(err);
1199
+ else resolve();
1200
+ });
1201
+ child.stdin.write(text);
1202
+ child.stdin.end();
1203
+ } catch (e) {
1204
+ reject(e);
1205
+ }
1206
+ });
1207
+ }
1208
+
1209
+ // ── Box / Badges / Theme helpers ─────────────────────────────
1210
+
1211
+ function providerBadge(result) {
1212
+ const badges = {
1213
+ "groq": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Groq "),
1214
+ "together ai": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Together "),
1215
+ "cerebras": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Cerebras "),
1216
+ "openai": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" OpenAI "),
1217
+ "google": chalk.bgHex("#1a1a2a").hex("#2d7dff")(" Gemini "),
1218
+ "anthropic": chalk.bgHex("#2a1a2a").hex("#b06cff")(" Claude "),
1219
+ "xai": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Grok "),
1220
+ "mistral ai": chalk.bgHex("#1a1a2a").hex("#ffb900")(" Mistral "),
1221
+ "openrouter": chalk.bgHex("#1a1a2a").hex("#6ce8ff")(" OpenRouter "),
1222
+ "cohere": chalk.bgHex("#1a2a2a").hex("#6ce8ff")(" Cohere "),
1223
+ "deepseek": chalk.bgHex("#1a1a2a").hex("#2d7dff")(" DeepSeek "),
1224
+ "perplexity": chalk.bgHex("#1a2a2a").hex("#6ce8ff")(" Perplexity "),
1225
+ "fireworks ai": chalk.bgHex("#2a1a1a").hex("#ff6b8d")(" Fireworks "),
1226
+ "local": chalk.bgHex("#1a2a1a").hex("#67ffb0")(" Math Solver "),
1227
+ "offline-fallback": chalk.bgHex("#0c1825").hex("#6ce8ff")(" Krylo "),
1228
+ };
1229
+
1230
+ const badge = badges[result.provider] || colors.muted(` ${result.provider} `);
1231
+ return badge + colors.dim(` Node ${result.node}`);
1232
+ }
1233
+
1234
+ function signalBar(name, value) {
1235
+ const filled = Math.round(value / 10);
1236
+ const empty = 10 - filled;
1237
+ const bar = colors.accent("█".repeat(filled)) + colors.dim("░".repeat(empty));
1238
+ return `${colors.muted(name.padEnd(10))} ${bar} ${colors.muted(value + "%")}`;
1239
+ }
1240
+
1241
+ function formatBytes(bytes) {
1242
+ if (bytes < 1024) return `${bytes}B`;
1243
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1244
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1245
+ }
1246
+
1247
+ /**
1248
+ * Handles the management of custom slash command shortcuts.
1249
+ */
1250
+ async function handleCustomCommands(args, ctx) {
1251
+ const sub = args[0]?.toLowerCase();
1252
+
1253
+ if (sub === "list") {
1254
+ const custom = ctx.aiConfig.CUSTOM_COMMANDS || {};
1255
+ const entries = Object.entries(custom);
1256
+
1257
+ console.log("");
1258
+ console.log(colors.brand(" ⚡ CUSTOM SHORTCUT COMMANDS"));
1259
+ console.log(separator("─"));
1260
+
1261
+ if (entries.length === 0) {
1262
+ console.log(" " + colors.muted("No custom commands registered."));
1263
+ console.log(" " + colors.muted("Create one: ") + colors.accent("/cmd add /explain \"Explain this code:\"") + "\n");
1264
+ return;
1265
+ }
1266
+
1267
+ for (const [cmd, template] of entries) {
1268
+ console.log(` ${colors.accent(cmd.padEnd(16))} ${colors.text(template)}`);
1269
+ }
1270
+ console.log("");
1271
+ return;
1272
+ }
1273
+
1274
+ if (sub === "add") {
1275
+ const name = args[1];
1276
+ const template = args.slice(2).join(" ");
1277
+
1278
+ if (!name || !template) {
1279
+ console.log("\n" + label.system + " " + colors.warning("Usage: /cmd add <name> <template>"));
1280
+ console.log(" " + colors.muted("Example: /cmd add /explain \"Explain this code in detail:\"") + "\n");
1281
+ return;
1282
+ }
1283
+
1284
+ if (!name.startsWith("/")) {
1285
+ console.log("\n" + label.system + " " + colors.danger("ERROR: Command name must start with a slash '/' (e.g. /explain)") + "\n");
1286
+ return;
1287
+ }
1288
+
1289
+ const builtIn = [
1290
+ "/help", "/mode", "/modes", "/attach", "/files", "/clear",
1291
+ "/providers", "/export", "/status", "/copy", "/exit", "/quit",
1292
+ "/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/guess", "/tokens"
1293
+ ];
1294
+
1295
+ if (builtIn.includes(name.toLowerCase())) {
1296
+ console.log("\n" + label.system + " " + colors.danger(`ERROR: Cannot override system command "${name}"`) + "\n");
1297
+ return;
1298
+ }
1299
+
1300
+ const custom = ctx.aiConfig.CUSTOM_COMMANDS || {};
1301
+ custom[name] = template;
1302
+
1303
+ await setConfigValue("CUSTOM_COMMANDS", custom);
1304
+ ctx.aiConfig.CUSTOM_COMMANDS = custom; // sync context
1305
+
1306
+ console.log("\n" + label.system + " " + colors.success(`✓ Command registered successfully!`));
1307
+ console.log(` ${colors.accent(name)} ➔ "${template}"\n`);
1308
+ return;
1309
+ }
1310
+
1311
+ if (sub === "remove") {
1312
+ const name = args[1];
1313
+ if (!name) {
1314
+ console.log("\n" + label.system + " " + colors.warning("Usage: /cmd remove <name>") + "\n");
1315
+ return;
1316
+ }
1317
+
1318
+ const custom = ctx.aiConfig.CUSTOM_COMMANDS || {};
1319
+ if (!custom[name]) {
1320
+ console.log("\n" + label.system + " " + colors.warning(`No custom command named "${name}" exists.`) + "\n");
1321
+ return;
1322
+ }
1323
+
1324
+ delete custom[name];
1325
+ await setConfigValue("CUSTOM_COMMANDS", custom);
1326
+ ctx.aiConfig.CUSTOM_COMMANDS = custom; // sync context
1327
+
1328
+ console.log("\n" + label.system + " " + colors.success(`✓ Removed custom command: "${name}"\n`));
1329
+ return;
1330
+ }
1331
+
1332
+ console.log("\n" + label.system + " " + colors.warning("Usage: /cmd <list|add|remove> [args]"));
1333
+ console.log(" " + colors.muted("Type /help for help or /cmd list to see existing shortcuts.\n"));
1334
+ }
1335
+
1336
+ /**
1337
+ * Extracts all code blocks from a markdown string.
1338
+ */
1339
+ function extractCodeBlocks(markdown) {
1340
+ const regex = /```[\w-]*\n([\s\S]*?)\n```/g;
1341
+ const blocks = [];
1342
+ let match;
1343
+ while ((match = regex.exec(markdown)) !== null) {
1344
+ blocks.push(match[1]);
1345
+ }
1346
+ return blocks;
1347
+ }
1348
+
1349
+ /**
1350
+ * Manual file writing command. Extracts the last code block of the previous
1351
+ * assistant response and writes it to a file.
1352
+ */
1353
+ async function handleWriteFile(args, ctx) {
1354
+ const filename = args.join(" ");
1355
+ if (!filename) {
1356
+ console.log("\n" + label.system + " " + colors.warning("Usage: /write <filename>") + "\n");
1357
+ return;
1358
+ }
1359
+
1360
+ const lastResponse = [...ctx.history].reverse().find((h) => h.role === "assistant");
1361
+ if (!lastResponse) {
1362
+ console.log("\n" + label.system + " " + colors.muted("No assistant response available to write.\n"));
1363
+ return;
1364
+ }
1365
+
1366
+ const codeBlocks = extractCodeBlocks(lastResponse.content);
1367
+ if (codeBlocks.length === 0) {
1368
+ console.log("\n" + label.system + " " + colors.warning("No code blocks found in the last response.\n"));
1369
+ return;
1370
+ }
1371
+
1372
+ const blockContent = codeBlocks[codeBlocks.length - 1];
1373
+ const filepath = resolve(filename);
1374
+
1375
+ try {
1376
+ const { dirname } = await import("node:path");
1377
+ const { mkdir } = await import("node:fs/promises");
1378
+ const dir = dirname(filepath);
1379
+ await mkdir(dir, { recursive: true });
1380
+ await writeFile(filepath, blockContent, "utf-8");
1381
+ console.log("\n" + label.system + " " + colors.success(`✓ Code block successfully written to: ${filepath}\n`));
1382
+ } catch (err) {
1383
+ console.log("\n" + label.error + " " + colors.danger(`Write failed: ${err.message}\n`));
1384
+ }
1385
+ }
1386
+
1387
+ /**
1388
+ * Interactive git commit command inside chat loop.
1389
+ */
1390
+ async function handleCommitInsideChat_unused(ctx) {
1391
+ const { getGitDiff, runGitCommit } = await import("./git.js");
1392
+ const { exec } = await import("node:child_process");
1393
+ const { promisify } = await import("node:util");
1394
+ const execAsync = promisify(exec);
1395
+
1396
+ try {
1397
+ const { diff, isStaged } = await getGitDiff();
1398
+ if (!diff) {
1399
+ console.log("\n" + label.system + " " + colors.warning("No staged or unstaged changes detected. Stage your files using 'git add' first.\n"));
1400
+ return;
1401
+ }
1402
+
1403
+ if (!isStaged) {
1404
+ ctx.rl.pause();
1405
+ const stageAnswer = await new Promise((resolve) => {
1406
+ ctx.rl.question(colors.warning("\nNo staged changes found. Do you want to stage all changes automatically? [y/N]: "), resolve);
1407
+ });
1408
+ ctx.rl.resume();
1409
+
1410
+ if (stageAnswer.toLowerCase().trim() === "y" || stageAnswer.toLowerCase().trim() === "yes") {
1411
+ await execAsync("git add .");
1412
+ console.log(label.system + " " + colors.success("Staged all changes successfully."));
1413
+ } else {
1414
+ console.log("\n" + label.system + " " + colors.muted("Aborted. Please stage files using 'git add' first.\n"));
1415
+ return;
1416
+ }
1417
+ }
1418
+
1419
+ console.log("\n" + label.system + " " + colors.brand("Reading git diff and generating conventional commit message..."));
1420
+ console.log("");
1421
+
1422
+ const systemPrompt = "You are an expert developer assistant. Generate a concise, clear, and professional conventional commit message (e.g., 'feat: add login page', 'fix: resolve buffer overflow') based on the provided git diff. Output ONLY the commit message itself on a single line, with absolutely no backticks, markdown, explanations, prefix, or introductory text.";
1423
+ const userPrompt = `Here is the git diff:\n\n${diff}`;
1424
+
1425
+ let firstToken = true;
1426
+ let commitMessage = "";
1427
+ const onToken = (token) => {
1428
+ if (firstToken) {
1429
+ firstToken = false;
1430
+ process.stdout.write(label.krims + " Suggested Commit Message: " + colors.success(token));
1431
+ } else {
1432
+ process.stdout.write(colors.success(token));
1433
+ }
1434
+ commitMessage += token;
1435
+ };
1436
+
1437
+ const result = await routePrompt(userPrompt, systemPrompt, ctx.aiConfig, onToken);
1438
+ console.log("\n");
1439
+
1440
+ const cleanMessage = result.text.trim().replace(/^`+|`+$/g, ""); // strip quotes/backticks
1441
+
1442
+ ctx.rl.pause();
1443
+ const answer = await new Promise((resolve) => {
1444
+ ctx.rl.question(colors.muted("Commit with this message? [Y/n]: "), resolve);
1445
+ });
1446
+ ctx.rl.resume();
1447
+
1448
+ if (answer.toLowerCase().trim() === "n" || answer.toLowerCase().trim() === "no") {
1449
+ console.log("\n" + label.system + " " + colors.muted("Commit aborted.\n"));
1450
+ return;
1451
+ }
1452
+
1453
+ console.log("\n" + label.system + " " + colors.brand("Executing git commit..."));
1454
+ const output = await runGitCommit(cleanMessage);
1455
+ console.log("\n" + colors.success(output) + "\n");
1456
+
1457
+ } catch (err) {
1458
+ console.log("\n" + label.error + " " + colors.danger(err.message) + "\n");
1459
+ }
1460
+ }
1461
+
1462
+ /**
1463
+ * Sandboxed interactive shell command execution.
1464
+ */
1465
+ async function handleRunCommand(args, ctx) {
1466
+ const command = args.join(" ").trim();
1467
+ if (!command) {
1468
+ console.log("\n" + label.system + " " + colors.warning("Usage: /run <command>\n"));
1469
+ return;
1470
+ }
1471
+
1472
+ const { spawn } = await import("node:child_process");
1473
+
1474
+ console.log("\n" + label.system + " " + colors.brand(`Running command: ${command}`));
1475
+ console.log(separator("─") + "\n");
1476
+
1477
+ ctx.rl.pause();
1478
+
1479
+ return new Promise((resolve) => {
1480
+ const isWindows = process.platform === "win32";
1481
+ const shell = isWindows ? "cmd.exe" : "/bin/sh";
1482
+ const shellArgs = isWindows ? ["/c", command] : ["-c", command];
1483
+
1484
+ const child = spawn(shell, shellArgs, { stdio: "inherit" });
1485
+
1486
+ child.on("close", (code) => {
1487
+ ctx.rl.resume();
1488
+ console.log("\n" + separator("─"));
1489
+ if (code === 0) {
1490
+ console.log(label.system + " " + colors.success(`✓ Command exited successfully (code 0).\n`));
1491
+ } else {
1492
+ console.log(label.system + " " + colors.danger(`✗ Command failed with exit status ${code}.\n`));
1493
+ }
1494
+ resolve();
1495
+ });
1496
+
1497
+ child.on("error", (err) => {
1498
+ ctx.rl.resume();
1499
+ console.log("\n" + label.error + " " + colors.danger(`Failed to start command: ${err.message}\n`));
1500
+ resolve();
1501
+ });
1502
+ });
1503
+ }
1504
+
1505
+ /**
1506
+ * Interactive display of session token usage statistics.
1507
+ */
1508
+ async function handleTokensDisplay(ctx) {
1509
+ const stats = getSessionTokenStats();
1510
+ const breakdown = getBreakdownByModel();
1511
+
1512
+ console.log("\n" + separator("━"));
1513
+ console.log(colors.accent.bold(" ★ KRIMS CODE SESSION TOKEN TELEMETRY ★"));
1514
+ console.log(separator("─"));
1515
+
1516
+ const models = Object.keys(breakdown);
1517
+ if (models.length === 0) {
1518
+ console.log(colors.muted(" No queries executed in this session yet."));
1519
+ } else {
1520
+ // Print header
1521
+ console.log(
1522
+ colors.brand(" " + "Model".padEnd(35) + "Prompt".padStart(10) + "Completion".padStart(12) + "Total".padStart(10))
1523
+ );
1524
+ console.log(colors.dim(" " + "─".repeat(67)));
1525
+ for (const [model, data] of Object.entries(breakdown)) {
1526
+ const truncatedModel = model.length > 33 ? model.slice(0, 30) + "..." : model;
1527
+ console.log(
1528
+ " " + colors.text(truncatedModel.padEnd(35)) +
1529
+ colors.brand(data.prompt.toLocaleString().padStart(10)) +
1530
+ colors.brand(data.completion.toLocaleString().padStart(12)) +
1531
+ colors.accent.bold(data.total.toLocaleString().padStart(10))
1532
+ );
1533
+ }
1534
+ }
1535
+
1536
+ console.log(separator("─"));
1537
+ console.log(" " + colors.accent("Total Exchanges:") + colors.text(` ${stats.exchanges}`));
1538
+ console.log(" " + colors.accent("Total Tokens:") + colors.text(` Prompt: ${stats.prompt.toLocaleString()} | Completion: ${stats.completion.toLocaleString()} | Sum: `) + colors.brand.bold(stats.total.toLocaleString()));
1539
+ console.log(separator("━") + "\n");
1540
+ }
1541
+
1542
+ /**
1543
+ * Streams an AI query prompt and prints telemetry details at the end.
1544
+ */
1545
+ async function executeAISpecialCommand(prompt, specialLabel, ctx) {
1546
+ const systemPrompt = ctx.currentMode.systemPrompt + "\n" + AGENT_INSTRUCTIONS;
1547
+ let hasStarted = false;
1548
+ let responseText = "";
1549
+ const queryStartTime = Date.now();
1550
+ let firstTokenTime = 0;
1551
+
1552
+ const onToken = (token) => {
1553
+ if (!hasStarted) {
1554
+ hasStarted = true;
1555
+ firstTokenTime = Date.now();
1556
+ process.stdout.write("\n" + label.krims + " " + colors.accent(specialLabel) + "\n" + separator("─") + "\n\n");
1557
+ }
1558
+ process.stdout.write(colors.success(token));
1559
+ responseText += token;
1560
+ };
1561
+
1562
+ const result = await routePrompt(prompt, systemPrompt, ctx.aiConfig, onToken);
1563
+ console.log("\n");
1564
+
1565
+ const elapsedSec = ((Date.now() - queryStartTime) / 1000).toFixed(1);
1566
+ let speedText = "";
1567
+ if (firstTokenTime > 0) {
1568
+ const streamElapsed = (Date.now() - firstTokenTime) / 1000;
1569
+ if (streamElapsed > 0.05) {
1570
+ const estimatedTokens = Math.max(1, Math.round(responseText.length / 4));
1571
+ const tps = (estimatedTokens / streamElapsed).toFixed(1);
1572
+ speedText = ` • ${tps} tok/s`;
1573
+ }
1574
+ }
1575
+
1576
+ const showTokens = ctx.aiConfig.SHOW_TOKENS !== "false";
1577
+ let tokensText = "";
1578
+ if (showTokens && result.usage) {
1579
+ const { promptTokens, completionTokens } = result.usage;
1580
+ tokensText = ` • ${promptTokens.toLocaleString()} in / ${completionTokens.toLocaleString()} out tokens`;
1581
+ }
1582
+
1583
+ console.log(separator("─"));
1584
+ console.log(
1585
+ " " + colors.dim(`Node ${result.node} • ${result.provider}`) +
1586
+ (result.model ? colors.dim(` • ${result.model}`) : "") +
1587
+ colors.dim(` • ${elapsedSec}s${speedText}`) +
1588
+ colors.dim(tokensText)
1589
+ );
1590
+ console.log("");
1591
+ }
1592
+
1593
+ /**
1594
+ * Handler for the /review command (git diff analysis).
1595
+ */
1596
+ async function handleReviewCommand(ctx) {
1597
+ console.log("\n" + label.system + " " + colors.muted("Running git diff to fetch repository changes..."));
1598
+ try {
1599
+ const { diff, isStaged } = await getGitDiff();
1600
+ if (!diff) {
1601
+ console.log(label.system + " " + colors.success("✓ No changes detected in the repository to review.\n"));
1602
+ return;
1603
+ }
1604
+
1605
+ const specialLabel = `Reviewing ${isStaged ? "staged" : "unstaged"} changes...`;
1606
+ const prompt = `Review the following git diff. Identify potential bugs, logical issues, security concerns, performance problems, and recommend optimization or code cleanup. Keep it concise, practical, and highly technical:\n\n\`\`\`diff\n${diff}\n\`\`\``;
1607
+
1608
+ await executeAISpecialCommand(prompt, specialLabel, ctx);
1609
+ } catch (err) {
1610
+ console.log(label.system + " " + colors.danger(`Error: ${err.message}\n`));
1611
+ }
1612
+ }
1613
+
1614
+ /**
1615
+ * Handler for the /diagnose command (build & test diagnostics execution).
1616
+ */
1617
+ async function handleDiagnoseCommand(args, ctx) {
1618
+ const defaultCmd = ctx.aiConfig.DIAGNOSE_CMD || "npm test";
1619
+ const cmdToRun = args.join(" ").trim() || defaultCmd;
1620
+
1621
+ console.log("\n" + label.system + " " + colors.muted(`Running diagnostics command: "${cmdToRun}"...`));
1622
+
1623
+ const spinner = createSpinner("Executing diagnostics").start();
1624
+ try {
1625
+ const { exec } = await import("node:child_process");
1626
+ const { promisify } = await import("node:util");
1627
+ const execAsync = promisify(exec);
1628
+ await execAsync(cmdToRun);
1629
+ spinner.succeed("Diagnostics complete!");
1630
+ console.log("\n" + label.system + " " + colors.success("✓ Diagnostics clean! Build and tests passed successfully.\n"));
1631
+ } catch (err) {
1632
+ spinner.fail("Diagnostics failed!");
1633
+
1634
+ const output = (err.stdout || "") + "\n" + (err.stderr || "");
1635
+ console.log("\n" + label.system + " " + colors.warning(`Diagnostics returned exit code ${err.code}.`));
1636
+ console.log(colors.muted("Analyzing compiler/test output logs...\n"));
1637
+
1638
+ const prompt = `The diagnostics command "${cmdToRun}" failed with exit code ${err.code}. Analyze the following stdout and stderr logs to determine the root cause, identify the files/lines causing the failure, and provide a step-by-step resolution and debugging plan:\n\n\`\`\`\n${output.slice(0, 15000)}\n\`\`\``;
1639
+
1640
+ await executeAISpecialCommand(prompt, "Analyzing diagnostics logs...", ctx);
1641
+ }
1642
+ }
1643
+
1644
+ /**
1645
+ * Handler for file analysis commands: /explain, /refactor, /bug, /doc, /translate.
1646
+ */
1647
+ async function handleFileAICommand(cmdName, args, ctx) {
1648
+ const filePath = args[0];
1649
+ if (!filePath) {
1650
+ console.log("\n" + label.system + " " + colors.warning(`Usage: ${cmdName} <file_path>\n`));
1651
+ return;
1652
+ }
1653
+
1654
+ // Resolve path
1655
+ const resolvedPath = resolve(process.cwd(), filePath);
1656
+
1657
+ // Verify path is inside the workspace
1658
+ const { isInsideWorkspace } = await import("./agent.js");
1659
+ if (!isInsideWorkspace(resolvedPath)) {
1660
+ console.log("\n" + label.system + " " + colors.danger("Error: Path is outside the current workspace sandbox.\n"));
1661
+ return;
1662
+ }
1663
+
1664
+ if (!existsSync(resolvedPath)) {
1665
+ console.log("\n" + label.system + " " + colors.danger(`Error: File does not exist at "${filePath}"\n`));
1666
+ return;
1667
+ }
1668
+
1669
+ const stat = statSync(resolvedPath);
1670
+ if (stat.isDirectory()) {
1671
+ console.log("\n" + label.system + " " + colors.danger(`Error: "${filePath}" is a directory. File path required.\n`));
1672
+ return;
1673
+ }
1674
+
1675
+ if (stat.size > 150 * 1024) { // 150KB limit
1676
+ console.log("\n" + label.system + " " + colors.warning(`Warning: File "${filePath}" is too large (${Math.round(stat.size / 1024)}KB). Limits are 150KB to protect context limit.\n`));
1677
+ return;
1678
+ }
1679
+
1680
+ // Read file content
1681
+ let content;
1682
+ try {
1683
+ const { parseFile } = await import("./file-parser.js");
1684
+ const parsed = await parseFile(resolvedPath);
1685
+ content = parsed.content;
1686
+ } catch (err) {
1687
+ console.log("\n" + label.system + " " + colors.danger(`Error parsing file: ${err.message}\n`));
1688
+ return;
1689
+ }
1690
+
1691
+ let prompt = "";
1692
+ let labelText = "";
1693
+
1694
+ switch (cmdName.toLowerCase()) {
1695
+ case "/explain":
1696
+ labelText = `Explaining ${filePath}...`;
1697
+ prompt = `Explain the architecture, design patterns, logic flow, and purpose of the following code. Be clear, technical, and structured:\n\n\`\`\`\n${content}\n\`\`\``;
1698
+ break;
1699
+ case "/refactor":
1700
+ labelText = `Refactoring ${filePath}...`;
1701
+ prompt = `Suggest refactoring improvements for the following code. Focus on clean code design principles, optimization, readability, reducing complexity, and fixing potential logic bugs. Return both the refactored code block and explanations:\n\n\`\`\`\n${content}\n\`\`\``;
1702
+ break;
1703
+ case "/bug":
1704
+ labelText = `Auditing bugs in ${filePath}...`;
1705
+ prompt = `Perform a thorough static analysis and code review of the following code. Identify potential logical bugs, race conditions, edge case failures, performance bottlenecks, and security hazards. Suggest fixes:\n\n\`\`\`\n${content}\n\`\`\``;
1706
+ break;
1707
+ case "/doc":
1708
+ labelText = `Generating documentation for ${filePath}...`;
1709
+ prompt = `Generate comprehensive API documentation, JSDoc/docstrings, and comments for the following code. Ensure code parameters, return values, and types are documented:\n\n\`\`\`\n${content}\n\`\`\``;
1710
+ break;
1711
+ case "/translate":
1712
+ const targetLang = args[1];
1713
+ if (!targetLang) {
1714
+ console.log("\n" + label.system + " " + colors.warning(`Usage: /translate <file_path> <target_language>\n`));
1715
+ return;
1716
+ }
1717
+ labelText = `Translating ${filePath} to ${targetLang}...`;
1718
+ prompt = `Translate the following code into ${targetLang}. Return a clean, syntactically correct, and beautifully structured code block of the translated code:\n\n\`\`\`\n${content}\n\`\`\``;
1719
+ break;
1720
+ }
1721
+
1722
+ try {
1723
+ await executeAISpecialCommand(prompt, labelText, ctx);
1724
+ } catch (err) {
1725
+ console.log("\n" + label.system + " " + colors.danger(`Error: ${err.message}\n`));
1726
+ }
1727
+ }
1728
+
1729
+ /**
1730
+ * Handler for the /search command (workspace file crawler and AI semantic finder).
1731
+ */
1732
+ async function handleSearchCommand(args, ctx) {
1733
+ const isAi = args[0] === "--ai";
1734
+ const queryArgs = isAi ? args.slice(1) : args;
1735
+ const query = queryArgs.join(" ").trim();
1736
+
1737
+ if (!query) {
1738
+ console.log("\n" + label.system + " " + colors.warning("Usage: /search [--ai] <query_string>\n"));
1739
+ return;
1740
+ }
1741
+
1742
+ const { workspaceSearch, crawlDirectory } = await import("./search.js");
1743
+
1744
+ if (isAi) {
1745
+ console.log("\n" + label.system + " " + colors.muted("Scanning workspace project tree for semantic search..."));
1746
+ const files = crawlDirectory(process.cwd());
1747
+ const { relative } = await import("node:path");
1748
+ const relativePaths = files.map((f) => relative(process.cwd(), f).replace(/\\/g, "/"));
1749
+
1750
+ // Construct semantic prompt
1751
+ const prompt = `Here is the directory structure / file listing of the current workspace:\n\n${relativePaths.slice(0, 100).join("\n")}\n\nBased on this file listing, identify and explain where the following logic or system is implemented, listing the relevant files: ${query}`;
1752
+
1753
+ await executeAISpecialCommand(prompt, `Semantic search: "${query}"`, ctx);
1754
+ return;
1755
+ }
1756
+
1757
+ console.log("\n" + label.system + " " + colors.muted(`Searching workspace for "${query}"...`));
1758
+ const results = workspaceSearch(query);
1759
+
1760
+ if (results.length === 0) {
1761
+ console.log("\n" + label.system + " " + colors.warning(`✓ No matches found for "${query}" in workspace.\n`));
1762
+ return;
1763
+ }
1764
+
1765
+ console.log("\n" + separator("━"));
1766
+ console.log(colors.accent.bold(` ★ WORKSPACE SEARCH RESULTS FOR "${query.toUpperCase()}" ★`));
1767
+ console.log(separator("─"));
1768
+
1769
+ // Print header
1770
+ console.log(
1771
+ colors.brand(" " + "File Path".padEnd(45) + "Line".padStart(6) + " " + "Preview")
1772
+ );
1773
+ console.log(colors.dim(" " + "─".repeat(80)));
1774
+
1775
+ // Display matches (limit to top 50 to prevent terminal overflow)
1776
+ const displayLimit = 50;
1777
+ const visibleResults = results.slice(0, displayLimit);
1778
+
1779
+ for (const match of visibleResults) {
1780
+ const truncatedPath = match.relativePath.length > 43 ? "..." + match.relativePath.slice(-40) : match.relativePath;
1781
+ const truncatedLine = match.lineContent.length > 50 ? match.lineContent.slice(0, 47) + "..." : match.lineContent;
1782
+ console.log(
1783
+ " " + colors.text(truncatedPath.padEnd(45)) +
1784
+ colors.brand(match.lineNumber.toString().padStart(6)) +
1785
+ " " + colors.muted(truncatedLine)
1786
+ );
1787
+ }
1788
+
1789
+ console.log(separator("─"));
1790
+ if (results.length > displayLimit) {
1791
+ console.log(" " + colors.warning(`⚠ Showing first ${displayLimit} of ${results.length} total matches.`));
1792
+ } else {
1793
+ console.log(" " + colors.success(`✓ Found ${results.length} matches across the workspace.`));
1794
+ }
1795
+ console.log(separator("━") + "\n");
1796
+ }
1797
+
1798
+ /**
1799
+ * Handler for the /goal command (autonomous goal execution loop).
1800
+ */
1801
+ export async function handleGoalCommand(args, ctx) {
1802
+ const goal = args.join(" ").trim();
1803
+ if (!goal) {
1804
+ console.log("\n" + label.system + " " + colors.warning("Usage: /goal <high-level goal statement>\n"));
1805
+ console.log(" " + colors.muted("Example: ") + colors.accent("/goal implement user login page") + "\n");
1806
+ return;
1807
+ }
1808
+
1809
+ console.log("\n" + label.system + " " + colors.brand("🤖 STARTING AUTONOMOUS GOAL SOLVER"));
1810
+ console.log(separator("─"));
1811
+ console.log(keyValue(" Target Goal", goal));
1812
+ console.log("");
1813
+
1814
+ let iteration = 1;
1815
+ const maxIterations = 5;
1816
+
1817
+ let currentPrompt = `
1818
+ The user has set the following goal for you to achieve in the workspace:
1819
+ "${goal}"
1820
+
1821
+ Please analyze this goal, inspect the workspace files as needed, planning and executing step-by-step changes.
1822
+ Once you have fully completed and verified the goal, end your final response with the exact marker [GOAL_ACHIEVED] to stop the loop.
1823
+ If you hit an unrecoverable roadblock and cannot achieve the goal, output [GOAL_FAILED].
1824
+ `;
1825
+
1826
+ const goalSystemPrompt = `
1827
+ You are Krims Code Autopilot in Autonomous Goal Solver Mode.
1828
+ The user has set the following goal: "${goal}"
1829
+
1830
+ Your task is to take any actions necessary to achieve this goal. You have full access to workspace tools:
1831
+ - Read files: [READ_FILE: path/to/file]
1832
+ - Write files:
1833
+ [WRITE_FILE: path/to/file]
1834
+ <new file content>
1835
+ [END_WRITE]
1836
+ - Search the web: [SEARCH_WEB: query]
1837
+ - Run commands: [RUN_COMMAND: command]
1838
+
1839
+ Rules:
1840
+ - Plan carefully and execute actions incrementally.
1841
+ - After each action, the environment will run the tool and return the results to you.
1842
+ - Once you believe the goal has been fully achieved and verified (e.g., via running tests or inspecting files), output the exact text [GOAL_ACHIEVED] at the end of your response to signal completion.
1843
+ - If you cannot complete the goal or run into an unrecoverable error, output [GOAL_FAILED] with a summary of what went wrong.
1844
+ `;
1845
+
1846
+ while (iteration <= maxIterations) {
1847
+ console.log(colors.accent(`\n🤖 [Autopilot Goal Solver - Iteration ${iteration}/${maxIterations}]`));
1848
+
1849
+ const spinner = createSpinner(colors.muted(`Krims Code planning & executing next steps...`));
1850
+ spinner.start();
1851
+
1852
+ let streamedText = "";
1853
+ let hasStartedStreaming = false;
1854
+ const filter = new StreamFilter(process.stdout.write.bind(process.stdout));
1855
+ const onToken = (token) => {
1856
+ if (!hasStartedStreaming) {
1857
+ hasStartedStreaming = true;
1858
+ spinner.stop();
1859
+ }
1860
+ filter.write(token);
1861
+ streamedText += token;
1862
+ };
1863
+
1864
+ let result;
1865
+ try {
1866
+ result = await routePrompt(currentPrompt, goalSystemPrompt, ctx.aiConfig, onToken, ctx.history);
1867
+ spinner.stop();
1868
+ filter.flush();
1869
+ } catch (routeErr) {
1870
+ spinner.stop();
1871
+ console.log("\n" + label.error + " " + colors.danger(`AI Routing Failed: ${routeErr.message}`));
1872
+ break;
1873
+ }
1874
+
1875
+ if (hasStartedStreaming) {
1876
+ clearStreamedText(filter.filteredText);
1877
+ }
1878
+
1879
+ console.log("");
1880
+ console.log(label.krims + " " + providerBadge(result));
1881
+ console.log(separator("─"));
1882
+ console.log("");
1883
+
1884
+ const rendered = getMarked().parse(result.text);
1885
+ console.log(rendered);
1886
+ console.log(separator("─"));
1887
+
1888
+ ctx.history.push({ role: "user", content: currentPrompt, timestamp: new Date() });
1889
+ ctx.history.push({
1890
+ role: "assistant",
1891
+ content: result.text,
1892
+ provider: result.provider,
1893
+ model: result.model,
1894
+ node: result.node,
1895
+ timestamp: new Date(),
1896
+ });
1897
+ await saveHistory(ctx.history, ctx.currentMode.name);
1898
+
1899
+ if (result.text.includes("[GOAL_ACHIEVED]")) {
1900
+ console.log("\n" + label.system + " " + colors.success(`✓ Goal successfully achieved and verified!\n`));
1901
+ break;
1902
+ }
1903
+
1904
+ if (result.text.includes("[GOAL_FAILED]")) {
1905
+ console.log("\n" + label.system + " " + colors.danger(`❌ Autopilot reported goal failure.\n`));
1906
+ break;
1907
+ }
1908
+
1909
+ const { processAgentBlocks } = await import("./agent.js");
1910
+ const toolResults = await processAgentBlocks(result.text, ctx.aiConfig, ctx.rl);
1911
+
1912
+ let toolOutputs = "### Agent Tool Outputs:\n";
1913
+ if (toolResults.length === 0) {
1914
+ toolOutputs += "\n(No tool actions were executed in the last iteration. Please execute a tool or finish the task.)";
1915
+ } else {
1916
+ for (const tr of toolResults) {
1917
+ if (tr.success) {
1918
+ if (tr.tool === "READ_FILE") {
1919
+ toolOutputs += `\n- READ_FILE "${tr.arg}" succeeded. Content:\n\`\`\`\n${tr.content}\n\`\`\`;`;
1920
+ } else if (tr.tool === "WRITE_FILE") {
1921
+ toolOutputs += `\n- WRITE_FILE "${tr.arg}" succeeded.`;
1922
+ } else if (tr.tool === "RUN_COMMAND") {
1923
+ toolOutputs += `\n- RUN_COMMAND "${tr.arg}" succeeded. Output:\n\`\`\`\nSTDOUT:\n${tr.stdout}\nSTDERR:\n${tr.stderr}\n\`\`\`;`;
1924
+ } else if (tr.tool === "SEARCH_WEB") {
1925
+ if (tr.results && tr.results.length > 0) {
1926
+ const list = tr.results.map((r, i) => `${i+1}. [${r.title}](${r.url})\n ${r.snippet}`).join("\n");
1927
+ toolOutputs += `\n- SEARCH_WEB "${tr.arg}" succeeded. Results:\n${list}`;
1928
+ } else {
1929
+ toolOutputs += `\n- SEARCH_WEB "${tr.arg}" succeeded. No search results were found.`;
1930
+ }
1931
+ }
1932
+ } else {
1933
+ toolOutputs += `\n- ${tr.tool} "${tr.arg}" failed: ${tr.error}`;
1934
+ }
1935
+ }
1936
+ }
1937
+
1938
+ currentPrompt = `
1939
+ ${toolOutputs}
1940
+
1941
+ Please analyze the results of your actions and proceed with the next step to achieve the goal: "${goal}".
1942
+ Once you believe the goal has been fully achieved and verified, end your final response with [GOAL_ACHIEVED] to stop the loop.
1943
+ If you cannot achieve the goal, output [GOAL_FAILED].
1944
+ `;
1945
+ iteration++;
1946
+ }
1947
+
1948
+ if (iteration > maxIterations) {
1949
+ console.log("\n" + label.system + " " + colors.warning(`⚠️ Max autonomous goal solver iterations (${maxIterations}) reached.\n`));
1950
+ }
1951
+ }
1952
+
1953
+ /**
1954
+ * Runs an autonomous, self-correcting debug/test feedback loop.
1955
+ */
1956
+ export async function handleAutopilotDebug(cmdArg, ctx) {
1957
+ const { exec } = await import("node:child_process");
1958
+ const { promisify } = await import("node:util");
1959
+ const execAsync = promisify(exec);
1960
+
1961
+ const testCmd = cmdArg.trim() || ctx.aiConfig.DIAGNOSE_CMD || "npm test";
1962
+
1963
+ console.log("\n" + label.system + " " + colors.brand("🤖 AUTOPILOT AUTONOMOUS DEBUG LOOP"));
1964
+ console.log(separator("─"));
1965
+ console.log(keyValue(" Diagnostic Command", testCmd));
1966
+ console.log("");
1967
+
1968
+ console.log(colors.cyan(`⚡ Running initial diagnostics: ${testCmd}`));
1969
+
1970
+ let stdout = "";
1971
+ let stderr = "";
1972
+ let passed = false;
1973
+ let runErr = null;
1974
+
1975
+ try {
1976
+ const res = await execAsync(testCmd);
1977
+ stdout = res.stdout;
1978
+ stderr = res.stderr;
1979
+ passed = true;
1980
+ } catch (err) {
1981
+ stdout = err.stdout || "";
1982
+ stderr = err.stderr || "";
1983
+ runErr = err;
1984
+ passed = false;
1985
+ }
1986
+
1987
+ if (passed) {
1988
+ console.log("\n" + label.system + " " + colors.success(`✓ Diagnostics passed successfully on the first run!\n`));
1989
+ return;
1990
+ }
1991
+
1992
+ console.log("\n" + label.system + " " + colors.danger(`❌ Initial run failed. Starting self-correcting debug loop...\n`));
1993
+
1994
+ let iteration = 1;
1995
+ const maxIterations = 3;
1996
+ let currentPrompt = `
1997
+ The test/build command "${testCmd}" failed.
1998
+ Here is the execution output:
1999
+ --- STDOUT ---
2000
+ ${stdout}
2001
+ --- STDERR ---
2002
+ ${stderr}
2003
+ --- ERROR ---
2004
+ ${runErr ? runErr.message : ""}
2005
+
2006
+ Please inspect the logs. If you need to read any files first to locate the bug, use the [READ_FILE: path] tool. If you know how to fix it, write the corrected files using [WRITE_FILE: path]...[END_WRITE].
2007
+ After you output your edits or read operations, we will apply them and re-run the command.
2008
+ `;
2009
+
2010
+ const debugSystemPrompt = `
2011
+ You are Krims Code Autopilot in Autonomous Debug Mode.
2012
+ A terminal command failed. Your goal is to analyze the error logs, read relevant source files to find the bug, write fixes to those files, and make sure the diagnostics pass.
2013
+ You can read files using: [READ_FILE: path/to/file]
2014
+ You can write files using:
2015
+ [WRITE_FILE: path/to/file]
2016
+ <new file content>
2017
+ [END_WRITE]
2018
+
2019
+ Rules:
2020
+ - You must identify the root cause of the error.
2021
+ - First, read the relevant file(s) that might have caused the error.
2022
+ - Then, output the corrected file content.
2023
+ - Do not run any command blocks yourself. The environment will automatically re-run the test command for you after you output your modifications.
2024
+ - Keep your changes minimal and target only the bug.
2025
+ `;
2026
+
2027
+ while (iteration <= maxIterations) {
2028
+ console.log(colors.accent(`\n🤖 [Autopilot Debug - Iteration ${iteration}/${maxIterations}]`));
2029
+
2030
+ const spinner = createSpinner(colors.muted(`Krims Code analyzing diagnostics & planning fixes...`));
2031
+ spinner.start();
2032
+
2033
+ let streamedText = "";
2034
+ let hasStartedStreaming = false;
2035
+ const filter = new StreamFilter(process.stdout.write.bind(process.stdout));
2036
+ const onToken = (token) => {
2037
+ if (!hasStartedStreaming) {
2038
+ hasStartedStreaming = true;
2039
+ spinner.stop();
2040
+ }
2041
+ filter.write(token);
2042
+ streamedText += token;
2043
+ };
2044
+
2045
+ let result;
2046
+ try {
2047
+ result = await routePrompt(currentPrompt, debugSystemPrompt, ctx.aiConfig, onToken, ctx.history);
2048
+ spinner.stop();
2049
+ filter.flush();
2050
+ } catch (routeErr) {
2051
+ spinner.stop();
2052
+ console.log("\n" + label.error + " " + colors.danger(`AI Routing Failed: ${routeErr.message}`));
2053
+ break;
2054
+ }
2055
+
2056
+ if (hasStartedStreaming) {
2057
+ clearStreamedText(filter.filteredText);
2058
+ }
2059
+
2060
+ console.log("");
2061
+ console.log(label.krims + " " + providerBadge(result));
2062
+ console.log(separator("─"));
2063
+ console.log("");
2064
+
2065
+ const rendered = getMarked().parse(result.text);
2066
+ console.log(rendered);
2067
+ console.log(separator("─"));
2068
+
2069
+ ctx.history.push({ role: "user", content: currentPrompt, timestamp: new Date() });
2070
+ ctx.history.push({
2071
+ role: "assistant",
2072
+ content: result.text,
2073
+ provider: result.provider,
2074
+ model: result.model,
2075
+ node: result.node,
2076
+ timestamp: new Date(),
2077
+ });
2078
+ await saveHistory(ctx.history, ctx.currentMode.name);
2079
+
2080
+ const { processAgentBlocks } = await import("./agent.js");
2081
+ const toolResults = await processAgentBlocks(result.text, ctx.aiConfig, ctx.rl);
2082
+
2083
+ console.log(colors.cyan(`\n⚡ Re-running diagnostic command (Attempt ${iteration}/${maxIterations}): ${testCmd}`));
2084
+
2085
+ let testStdout = "";
2086
+ let testStderr = "";
2087
+ let testPassed = false;
2088
+ let testRunErr = null;
2089
+
2090
+ try {
2091
+ const res = await execAsync(testCmd);
2092
+ testStdout = res.stdout;
2093
+ testStderr = res.stderr;
2094
+ testPassed = true;
2095
+ } catch (err) {
2096
+ testStdout = err.stdout || "";
2097
+ testStderr = err.stderr || "";
2098
+ testRunErr = err;
2099
+ testPassed = false;
2100
+ }
2101
+
2102
+ if (testPassed) {
2103
+ console.log("\n" + label.system + " " + colors.success(`✓ Diagnostics passed successfully after autopilot debug corrections!\n`));
2104
+ break;
2105
+ } else {
2106
+ console.log("\n" + label.system + " " + colors.danger(`❌ Diagnostic check still failing (Attempt ${iteration}/${maxIterations}).`));
2107
+
2108
+ let toolOutputs = "### Agent Tool Outputs:\n";
2109
+ for (const tr of toolResults) {
2110
+ if (tr.success) {
2111
+ if (tr.tool === "READ_FILE") {
2112
+ toolOutputs += `\n- READ_FILE "${tr.arg}" succeeded. Content:\n\`\`\`\n${tr.content}\n\`\`\`;`;
2113
+ } else if (tr.tool === "WRITE_FILE") {
2114
+ toolOutputs += `\n- WRITE_FILE "${tr.arg}" succeeded.`;
2115
+ } else if (tr.tool === "SEARCH_WEB") {
2116
+ const list = tr.results.map((r, i) => `${i+1}. [${r.title}](${r.url})\n ${r.snippet}`).join("\n");
2117
+ toolOutputs += `\n- SEARCH_WEB "${tr.arg}" succeeded. Results:\n${list}`;
2118
+ }
2119
+ } else {
2120
+ toolOutputs += `\n- ${tr.tool} "${tr.arg}" failed: ${tr.error}`;
2121
+ }
2122
+ }
2123
+
2124
+ currentPrompt = `
2125
+ ${toolOutputs}
2126
+
2127
+ The test/build command "${testCmd}" is still failing.
2128
+ Here is the new execution output:
2129
+ --- STDOUT ---
2130
+ ${testStdout}
2131
+ --- STDERR ---
2132
+ ${testStderr}
2133
+ --- ERROR ---
2134
+ ${testRunErr ? testRunErr.message : ""}
2135
+
2136
+ Please analyze the remaining issues, read any other files you need, and apply further fixes.
2137
+ `;
2138
+ iteration++;
2139
+ }
2140
+ }
2141
+
2142
+ if (iteration > maxIterations) {
2143
+ console.log("\n" + label.system + " " + colors.warning(`⚠️ Max autopilot debug iterations reached. Review the diagnostics manually.\n`));
2144
+ }
2145
+ }
2146
+
2147
+ /**
2148
+ * Renders the custom interactive Git TUI file stager and branch tree.
2149
+ */
2150
+ export async function handleGitTUI(ctx) {
2151
+ const { execSync } = await import("node:child_process");
2152
+
2153
+ try {
2154
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
2155
+ } catch (e) {
2156
+ console.log("\n" + label.error + " " + colors.danger("Not a git repository (or git is not installed).\n"));
2157
+ return;
2158
+ }
2159
+
2160
+ const stdin = process.stdin;
2161
+ const stdout = process.stdout;
2162
+ const wasRaw = stdin.isRaw;
2163
+
2164
+ stdin.setRawMode(true);
2165
+ stdin.resume();
2166
+ stdin.setEncoding("utf8");
2167
+ stdout.write("\x1b[?25l"); // Hide cursor
2168
+
2169
+ let files = getGitStatusFiles();
2170
+ let activeIndex = 0;
2171
+ let renderedLines = 0;
2172
+
2173
+ function getGitStatusFiles() {
2174
+ try {
2175
+ const out = execSync("git status --porcelain", { encoding: "utf8" }).trim();
2176
+ if (!out) return [];
2177
+ return out.split("\n").map(line => {
2178
+ const status = line.slice(0, 2);
2179
+ const file = line.slice(3).trim();
2180
+ const isStaged = status[0] !== " " && status[0] !== "?";
2181
+ const isUnstaged = status[1] !== " " || status[0] === "?";
2182
+ return {
2183
+ path: file,
2184
+ status,
2185
+ staged: isStaged,
2186
+ unstaged: isUnstaged
2187
+ };
2188
+ });
2189
+ } catch (e) {
2190
+ return [];
2191
+ }
2192
+ }
2193
+
2194
+ function render() {
2195
+ if (renderedLines > 0) {
2196
+ stdout.write(`\x1b[${renderedLines}A\x1b[J`);
2197
+ }
2198
+
2199
+ let lines = [];
2200
+ lines.push(colors.brand("🌿 KRIMS CODE INTERACTIVE GIT TUI"));
2201
+ lines.push(separator("─"));
2202
+
2203
+ let branchGraph = "";
2204
+ try {
2205
+ branchGraph = execSync("git log --graph --oneline --decorate -n 6", { encoding: "utf8" }).trim();
2206
+ } catch (e) {
2207
+ branchGraph = " No git history found.";
2208
+ }
2209
+
2210
+ lines.push(colors.accent("Commit Graph & History:"));
2211
+ if (branchGraph) {
2212
+ lines.push(branchGraph.split("\n").map(l => " " + colors.muted(l)).join("\n"));
2213
+ }
2214
+ lines.push(separator("─"));
2215
+
2216
+ lines.push(colors.accent("Modified Files:"));
2217
+
2218
+ if (files.length === 0) {
2219
+ lines.push(colors.success(" Clean working directory. Nothing to stage/commit."));
2220
+ } else {
2221
+ files.forEach((file, index) => {
2222
+ const isActive = index === activeIndex;
2223
+ const pointer = isActive ? colors.accent("❯ ") : " ";
2224
+ const checkbox = file.staged ? colors.success("[⬢] ") : colors.muted("[⬡] ");
2225
+
2226
+ let statusColor = colors.text;
2227
+ if (file.status[0] === "?" || file.status[1] === "?") {
2228
+ statusColor = colors.warning;
2229
+ } else if (file.staged && !file.unstaged) {
2230
+ statusColor = colors.success;
2231
+ } else if (file.unstaged) {
2232
+ statusColor = colors.danger;
2233
+ }
2234
+
2235
+ const pathText = isActive ? colors.brand(file.path) : statusColor(file.path);
2236
+ const statusText = colors.dim(`(${file.status})`);
2237
+ lines.push(pointer + checkbox + pathText + " " + statusText);
2238
+ });
2239
+ }
2240
+
2241
+ lines.push(separator("─"));
2242
+ lines.push(colors.muted("Hotkeys: [Space] Stage/Unstage | [D] Discard | [C] Commit | [P] Push | [Q/Esc] Quit"));
2243
+
2244
+ const outputStr = lines.join("\n") + "\n";
2245
+ stdout.write(outputStr);
2246
+ renderedLines = lines.length;
2247
+ }
2248
+
2249
+ render();
2250
+
2251
+ return new Promise((resolve) => {
2252
+ async function handleKey(key) {
2253
+ console.log("TUI_KEY:", JSON.stringify(key));
2254
+ if (key === "\u0003" || key === "q" || key === "Q" || key === "\u001b") {
2255
+ cleanup();
2256
+ resolve();
2257
+ return;
2258
+ }
2259
+
2260
+ if (key === "\u001b[A") { // Up Arrow
2261
+ if (files.length > 0) {
2262
+ activeIndex = (activeIndex - 1 + files.length) % files.length;
2263
+ render();
2264
+ }
2265
+ return;
2266
+ }
2267
+ if (key === "\u001b[B") { // Down Arrow
2268
+ if (files.length > 0) {
2269
+ activeIndex = (activeIndex + 1) % files.length;
2270
+ render();
2271
+ }
2272
+ return;
2273
+ }
2274
+
2275
+ if (key === " ") { // Stage/Unstage
2276
+ if (files.length > 0) {
2277
+ const file = files[activeIndex];
2278
+ try {
2279
+ if (file.staged) {
2280
+ execSync(`git restore --staged "${file.path}"`);
2281
+ } else {
2282
+ execSync(`git add "${file.path}"`);
2283
+ }
2284
+ } catch (err) {
2285
+ // Ignore
2286
+ }
2287
+ files = getGitStatusFiles();
2288
+ if (activeIndex >= files.length) {
2289
+ activeIndex = Math.max(0, files.length - 1);
2290
+ }
2291
+ render();
2292
+ }
2293
+ return;
2294
+ }
2295
+
2296
+ if (key === "d" || key === "D") { // Discard
2297
+ if (files.length > 0) {
2298
+ const file = files[activeIndex];
2299
+ try {
2300
+ if (file.status[0] === "?" || file.status[1] === "?") {
2301
+ const fs = await import("node:fs");
2302
+ fs.rmSync(file.path, { force: true });
2303
+ } else {
2304
+ execSync(`git restore "${file.path}"`);
2305
+ }
2306
+ } catch (err) {
2307
+ // Ignore
2308
+ }
2309
+ files = getGitStatusFiles();
2310
+ if (activeIndex >= files.length) {
2311
+ activeIndex = Math.max(0, files.length - 1);
2312
+ }
2313
+ render();
2314
+ }
2315
+ return;
2316
+ }
2317
+
2318
+ if (key === "c" || key === "C") { // Commit
2319
+ cleanup();
2320
+
2321
+ const hasStaged = files.some(f => f.staged);
2322
+ if (!hasStaged) {
2323
+ console.log("\n" + label.warning + " " + colors.warning("No staged changes to commit. Stage some files first!\n"));
2324
+ await new Promise(r => setTimeout(r, 1500));
2325
+
2326
+ stdin.setRawMode(true);
2327
+ stdin.resume();
2328
+ stdout.write("\x1b[?25l");
2329
+ renderedLines = 0;
2330
+ files = getGitStatusFiles();
2331
+ render();
2332
+ return;
2333
+ }
2334
+
2335
+ ctx.rl.pause();
2336
+ const commitMsg = await new Promise((resMsg) => {
2337
+ ctx.rl.question(
2338
+ colors.accent("\n💬 Enter commit message: "),
2339
+ resMsg
2340
+ );
2341
+ });
2342
+ ctx.rl.resume();
2343
+
2344
+ if (commitMsg.trim()) {
2345
+ try {
2346
+ execSync(`git commit -m "${commitMsg.trim()}"`);
2347
+ console.log("\n" + label.system + " " + colors.success("✓ Changes committed successfully!\n"));
2348
+ } catch (err) {
2349
+ console.log("\n" + label.error + " " + colors.danger("Failed to commit changes: " + err.message + "\n"));
2350
+ }
2351
+ } else {
2352
+ console.log("\n" + label.warning + " " + colors.muted("Commit aborted (empty message).\n"));
2353
+ }
2354
+
2355
+ await new Promise(r => setTimeout(r, 1500));
2356
+
2357
+ stdin.setRawMode(true);
2358
+ stdin.resume();
2359
+ stdout.write("\x1b[?25l");
2360
+ renderedLines = 0;
2361
+ files = getGitStatusFiles();
2362
+ activeIndex = 0;
2363
+ render();
2364
+ return;
2365
+ }
2366
+
2367
+ if (key === "p" || key === "P") { // Push
2368
+ cleanup();
2369
+ console.log("\n" + label.system + " " + colors.brand("🚀 Pushing changes to remote branch..."));
2370
+ try {
2371
+ const out = execSync("git push", { encoding: "utf8" });
2372
+ console.log(colors.muted(out));
2373
+ console.log("\n" + label.system + " " + colors.success("✓ Push completed successfully!\n"));
2374
+ } catch (err) {
2375
+ console.log("\n" + label.error + " " + colors.danger("Failed to push changes: " + err.message + "\n"));
2376
+ }
2377
+
2378
+ await new Promise(r => setTimeout(r, 2000));
2379
+
2380
+ stdin.setRawMode(true);
2381
+ stdin.resume();
2382
+ stdout.write("\x1b[?25l");
2383
+ renderedLines = 0;
2384
+ files = getGitStatusFiles();
2385
+ render();
2386
+ return;
2387
+ }
2388
+ }
2389
+
2390
+ function cleanup() {
2391
+ stdin.removeListener("data", handleKey);
2392
+ stdin.setRawMode(wasRaw);
2393
+ stdin.pause();
2394
+ stdout.write("\x1b[?25h"); // Show cursor
2395
+ }
2396
+
2397
+ stdin.on("data", handleKey);
2398
+ });
2399
+ }
2400
+
2401
+ /**
2402
+ * Handles spawning and launching the local telemetry dashboard.
2403
+ */
2404
+ export async function handleDashboardCommand_unused(ctx) {
2405
+ const { startDashboardServer } = await import("./dashboard.js");
2406
+ const { exec } = await import("node:child_process");
2407
+
2408
+ try {
2409
+ const { port } = await startDashboardServer();
2410
+ console.log("\n" + label.system + " " + colors.brand("📊 KRIMS CODE WEB TELEMETRY DASHBOARD"));
2411
+ console.log(separator("─"));
2412
+ console.log(keyValue(" Status", colors.success("ONLINE")));
2413
+ console.log(keyValue(" Local URL", `http://localhost:${port}`));
2414
+ console.log("");
2415
+ console.log(" " + colors.muted("Launching browser companion automatically..."));
2416
+
2417
+ let startCmd = `start http://localhost:${port}`;
2418
+ if (process.platform === "darwin") {
2419
+ startCmd = `open http://localhost:${port}`;
2420
+ } else if (process.platform === "linux") {
2421
+ startCmd = `xdg-open http://localhost:${port}`;
2422
+ }
2423
+ exec(startCmd);
2424
+ console.log("\n" + label.system + " " + colors.success("✓ Dashboard launched. Press Ctrl+C in this session to stop dashboard at exit.\n"));
2425
+ } catch (err) {
2426
+ console.log("\n" + label.error + " " + colors.danger("Failed to start dashboard server: " + err.message + "\n"));
2427
+ }
2428
+ }
2429
+
2430
+ /**
2431
+ * Handles recording audio voice from microphone and transcribing to text input.
2432
+ */
2433
+ export async function handleMicInput(ctx) {
2434
+ const { startRecording, transcribeAudioFile } = await import("./mic.js");
2435
+ const { join } = await import("node:path");
2436
+ const { tmpdir } = await import("node:os");
2437
+ const fs = await import("node:fs");
2438
+
2439
+ const apiKeyExists = ctx.aiConfig.GOOGLE_API_KEY || ctx.aiConfig.GROQ_API_KEY || ctx.aiConfig.OPENAI_API_KEY;
2440
+ if (!apiKeyExists) {
2441
+ console.log("\n" + label.error + " " + colors.danger("No API keys found for speech-to-text. Please configure GOOGLE_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY.\n"));
2442
+ return;
2443
+ }
2444
+
2445
+ const wavPath = join(tmpdir(), `krims_mic_${Date.now()}.wav`);
2446
+ let handle;
2447
+
2448
+ try {
2449
+ handle = await startRecording(wavPath);
2450
+ } catch (err) {
2451
+ console.log("\n" + label.error + " " + colors.danger(`Failed to start recording: ${err.message}\n`));
2452
+ return;
2453
+ }
2454
+
2455
+ console.log("\n" + label.system + " " + colors.brand(getIcon("mic", ctx.aiConfig) + "AUDIO VOICE INPUT"));
2456
+ console.log(separator("─"));
2457
+ console.log(colors.accent(" Recording started..."));
2458
+ console.log(" " + colors.muted("Speak into your microphone."));
2459
+ console.log(" " + colors.brand("Press [Enter] to STOP and transcribe..."));
2460
+ console.log(separator("─"));
2461
+
2462
+ ctx.rl.pause();
2463
+
2464
+ const stdin = process.stdin;
2465
+ const wasRaw = stdin.isRaw;
2466
+ const isTTY = typeof stdin.setRawMode === "function";
2467
+
2468
+ if (isTTY) {
2469
+ stdin.setRawMode(true);
2470
+ }
2471
+ stdin.resume();
2472
+ stdin.setEncoding("utf8");
2473
+
2474
+ let aborted = false;
2475
+ await new Promise((resolve) => {
2476
+ function onData(chunk) {
2477
+ if (!isTTY) {
2478
+ if (chunk.includes("\n") || chunk.includes("\r")) {
2479
+ stdin.removeListener("data", onData);
2480
+ resolve();
2481
+ }
2482
+ return;
2483
+ }
2484
+ if (chunk === "\u0003") {
2485
+ aborted = true;
2486
+ stdin.removeListener("data", onData);
2487
+ resolve();
2488
+ return;
2489
+ }
2490
+ if (chunk === "\r" || chunk === "\n" || chunk === "\r\n") {
2491
+ stdin.removeListener("data", onData);
2492
+ resolve();
2493
+ }
2494
+ }
2495
+ stdin.on("data", onData);
2496
+ });
2497
+
2498
+ if (isTTY) {
2499
+ stdin.setRawMode(wasRaw);
2500
+ }
2501
+ ctx.rl.resume();
2502
+
2503
+ if (aborted) {
2504
+ console.log("\n" + label.system + " " + colors.warning("Recording aborted by user.\n"));
2505
+ try {
2506
+ await handle.stop();
2507
+ if (fs.existsSync(wavPath)) { fs.unlinkSync(wavPath); }
2508
+ } catch (e) {}
2509
+ return;
2510
+ }
2511
+
2512
+ console.log("");
2513
+ const spinner = createSpinner("transcribe");
2514
+ spinner.start("Stopping recording and transcribing...");
2515
+
2516
+ try {
2517
+ await handle.stop();
2518
+ const text = await transcribeAudioFile(wavPath, ctx.aiConfig);
2519
+ spinner.stop();
2520
+
2521
+ if (fs.existsSync(wavPath)) {
2522
+ try { fs.unlinkSync(wavPath); } catch (e) {}
2523
+ }
2524
+
2525
+ if (!text.trim()) {
2526
+ console.log("\n" + label.system + " " + colors.warning("No speech detected or transcription was empty.\n"));
2527
+ return;
2528
+ }
2529
+
2530
+ console.log("\n" + label.system + " " + colors.success("✓ Transcribed text:"));
2531
+ console.log(" " + colors.text(`"${text}"`));
2532
+ console.log("");
2533
+
2534
+ ctx.rl.write(text);
2535
+ } catch (err) {
2536
+ spinner.stop();
2537
+ if (fs.existsSync(wavPath)) {
2538
+ try { fs.unlinkSync(wavPath); } catch (e) {}
2539
+ }
2540
+ console.log("\n" + label.error + " " + colors.danger(`Transcription failed: ${err.message}\n`));
2541
+ }
2542
+ }
2543
+