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,43 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Krims Code AI CLI — Spinner Helpers
3
+ // ═══════════════════════════════════════════════════════════
4
+
5
+ import ora from "ora";
6
+
7
+ /**
8
+ * Creates a styled spinner with the Krims Code theme.
9
+ * @param {string} text - Spinner label text
10
+ * @returns {object} An ora spinner instance
11
+ */
12
+ export function createSpinner(text) {
13
+ return ora({
14
+ text,
15
+ spinner: {
16
+ interval: 80,
17
+ frames: ["▖", "▘", "▝", "▗"],
18
+ },
19
+ color: "cyan",
20
+ discardStdin: false,
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Wraps an async function with a loading spinner.
26
+ * Shows the spinner while the function runs and reports success/failure.
27
+ * @param {string} text - The loading message
28
+ * @param {Function} asyncFn - The async function to execute
29
+ * @returns {Promise<*>} The result of the async function
30
+ */
31
+ export async function withSpinner(text, asyncFn) {
32
+ const spinner = createSpinner(text);
33
+ spinner.start();
34
+
35
+ try {
36
+ const result = await asyncFn();
37
+ spinner.succeed();
38
+ return result;
39
+ } catch (err) {
40
+ spinner.fail(err.message || "Operation failed");
41
+ throw err;
42
+ }
43
+ }
@@ -0,0 +1,624 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Krims Code AI CLI — Dynamic Theme-Aware Formatting Utilities
3
+ // ═══════════════════════════════════════════════════════════
4
+
5
+ import chalk from "chalk";
6
+
7
+ // ── Theme Definitions ─────────────────────────────────────
8
+ export const THEMES = {
9
+ cyberpunk: {
10
+ accent: "#00f0ff", // Neon cyan
11
+ accent2: "#bd93f9", // Neon purple/magenta
12
+ accent3: "#50fa7b", // Neon green
13
+ danger: "#ff5555", // Neon red
14
+ warning: "#ffb86c", // Neon orange
15
+ muted: "#6272a4", // Comment gray
16
+ text: "#f8f8f2", // Crisp white-yellow
17
+ dim: "#44475a", // Border gray
18
+ brand: "#00f0ff", // Neon cyan
19
+ success: "#50fa7b", // Neon green
20
+ error: "#ff5555",
21
+ magenta: "#ff79c6",
22
+ orange: "#ffb86c",
23
+ },
24
+ matrix: {
25
+ accent: "#50fa7b", // Lime green
26
+ accent2: "#00ff00", // Matrix green
27
+ accent3: "#8efb50", // Light lime
28
+ danger: "#ff5555",
29
+ warning: "#ffb86c",
30
+ muted: "#1f5f1f", // Dark forest green
31
+ text: "#aaffaa", // Soft green text
32
+ dim: "#103f10", // Dark green border
33
+ brand: "#50fa7b",
34
+ success: "#50fa7b",
35
+ error: "#ff5555",
36
+ magenta: "#50fa7b",
37
+ orange: "#8efb50",
38
+ },
39
+ synthwave: {
40
+ accent: "#ff79c6", // Neon pink
41
+ accent2: "#ffb86c", // Neon orange
42
+ accent3: "#bd93f9", // Neon purple
43
+ danger: "#ff5555",
44
+ warning: "#ffb86c",
45
+ muted: "#6f2a8a", // Dark purple
46
+ text: "#ffe8f5", // Soft pinkish white
47
+ dim: "#3e104f", // Deep purple border
48
+ brand: "#ff79c6",
49
+ success: "#bd93f9",
50
+ error: "#ff5555",
51
+ magenta: "#ff79c6",
52
+ orange: "#ffb86c",
53
+ },
54
+ crimson: {
55
+ accent: "#ff5555", // Neon red
56
+ accent2: "#ffb86c", // Gold/orange
57
+ accent3: "#f1fa8c", // Neon yellow
58
+ danger: "#ff5555",
59
+ warning: "#ffb86c",
60
+ muted: "#662222", // Muted red-gray
61
+ text: "#ffffff", // White text
62
+ dim: "#331111", // Dark red border
63
+ brand: "#ff5555",
64
+ success: "#f1fa8c",
65
+ error: "#ff5555",
66
+ magenta: "#ff5555",
67
+ orange: "#ffb86c",
68
+ }
69
+ };
70
+
71
+ let activeThemeName = "cyberpunk";
72
+
73
+ // ── Dynamic Colors Resolver ───────────────────────────────
74
+ export const colors = new Proxy({}, {
75
+ get(target, prop) {
76
+ const theme = THEMES[activeThemeName] || THEMES.cyberpunk;
77
+ const colorHex = theme[prop] || "#ffffff";
78
+ const isBold = prop === "brand" || prop === "success" || prop === "error";
79
+
80
+ let fn = chalk.hex(colorHex);
81
+ if (isBold) {
82
+ fn = fn.bold;
83
+ }
84
+ return fn;
85
+ }
86
+ });
87
+
88
+ // ── Labels ────────────────────────────────────────────────
89
+ export const label = {
90
+ get system() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent).bold(" SYSTEM "); },
91
+ get user() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent3).bold(" YOU "); },
92
+ get krims() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent).bold(" KRIMS "); },
93
+ get error() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#2a0a14" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].danger).bold(" ERROR "); },
94
+ get info() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent2).bold(" INFO "); },
95
+ get config() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].warning).bold(" CONFIG "); },
96
+ get math() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent3).bold(" MATH "); },
97
+ get krylo() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent).bold(" KRYLO "); },
98
+ get mode() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent2).bold(" MODE "); },
99
+ get mesh() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].accent).bold(" MESH "); },
100
+ get file() { return chalk.bgHex(activeThemeName === "cyberpunk" ? "#0c1825" : THEMES[activeThemeName].dim).hex(THEMES[activeThemeName].warning).bold(" FILE "); },
101
+ };
102
+
103
+ // ── Formatting Helpers ───────────────────────────────────
104
+ export function separator(char = "─", length) {
105
+ const width = process.stdout.columns || 80;
106
+ const targetLength = length !== undefined ? length : Math.max(10, width - 4);
107
+ return colors.dim(char.repeat(targetLength));
108
+ }
109
+
110
+ export function heading(text) {
111
+ return colors.brand(`\n ${text}\n`) + separator();
112
+ }
113
+
114
+ export function keyValue(key, value) {
115
+ return ` ${colors.muted(key + ":")} ${colors.text(value)}`;
116
+ }
117
+
118
+ export function bullet(text) {
119
+ return ` ${colors.accent("›")} ${colors.text(text)}`;
120
+ }
121
+
122
+ export function modeBadge(mode) {
123
+ const theme = THEMES[activeThemeName] || THEMES.cyberpunk;
124
+ const badges = {
125
+ synthesis: chalk.bgHex(activeThemeName === "cyberpunk" ? "#1a3a2a" : theme.dim).hex(theme.accent3).bold(" SYNTHESIS "),
126
+ research: chalk.bgHex(activeThemeName === "cyberpunk" ? "#1a2a3a" : theme.dim).hex(theme.accent2).bold(" RESEARCH "),
127
+ architect: chalk.bgHex(activeThemeName === "cyberpunk" ? "#2a1a3a" : theme.dim).hex(theme.magenta).bold(" ARCHITECT "),
128
+ titan: chalk.bgHex(activeThemeName === "cyberpunk" ? "#1a2a3a" : theme.dim).hex(theme.accent).bold(" TITAN FUSION "),
129
+ };
130
+ return badges[mode?.toLowerCase()] || badges.titan;
131
+ }
132
+
133
+ /**
134
+ * Backs up the cursor and clears terminal lines printed during real-time streaming
135
+ * so they can be replaced by the final formatted response.
136
+ * @param {string} text - The raw streamed text that was printed
137
+ */
138
+ export function clearStreamedText(text) {
139
+ if (!process.stdout.isTTY) return;
140
+ const width = process.stdout.columns || 80;
141
+ const lines = text.split("\n");
142
+ let lineCount = 0;
143
+ for (const line of lines) {
144
+ lineCount += Math.max(1, Math.ceil(line.length / width));
145
+ }
146
+ if (lineCount > 0) {
147
+ process.stdout.write(`\x1b[${lineCount}A\x1b[J`);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Filter stream tokens on the fly to suppress file write blocks
153
+ * and print a cleaner placeholder badge in real-time instead of raw code.
154
+ */
155
+ export class StreamFilter {
156
+ constructor(writeFn) {
157
+ this.writeFn = writeFn;
158
+ this.buffer = "";
159
+ this.cursor = 0;
160
+ this.state = "NORMAL"; // "NORMAL", "COLLECTING_FILENAME", "SUPPRESSING"
161
+ this.filenameBuffer = "";
162
+ this.filteredText = "";
163
+ }
164
+
165
+ _write(text) {
166
+ this.writeFn(text);
167
+ this.filteredText += text;
168
+ }
169
+
170
+ write(token) {
171
+ this.buffer += token;
172
+ this.process();
173
+ }
174
+
175
+ process() {
176
+ const writeTag = "[WRITE_FILE:";
177
+ const endTag = "[END_WRITE]";
178
+
179
+ while (this.cursor < this.buffer.length) {
180
+ if (this.state === "NORMAL") {
181
+ const nextIndex = this.buffer.indexOf(writeTag, this.cursor);
182
+ if (nextIndex !== -1) {
183
+ if (nextIndex > this.cursor) {
184
+ this._write(this.buffer.slice(this.cursor, nextIndex));
185
+ }
186
+ this.cursor = nextIndex + writeTag.length;
187
+ this.state = "COLLECTING_FILENAME";
188
+ this.filenameBuffer = "";
189
+ } else {
190
+ // Check for partial match of writeTag at the end of the buffer
191
+ let partialMatchLength = 0;
192
+ for (let i = 1; i < writeTag.length; i++) {
193
+ const part = writeTag.slice(0, i);
194
+ if (this.buffer.endsWith(part)) {
195
+ partialMatchLength = i;
196
+ break;
197
+ }
198
+ }
199
+ const safeEnd = this.buffer.length - partialMatchLength;
200
+ if (safeEnd > this.cursor) {
201
+ this._write(this.buffer.slice(this.cursor, safeEnd));
202
+ this.cursor = safeEnd;
203
+ }
204
+ break; // Wait for more tokens
205
+ }
206
+ } else if (this.state === "COLLECTING_FILENAME") {
207
+ const closeIndex = this.buffer.indexOf("]", this.cursor);
208
+ if (closeIndex !== -1) {
209
+ this.filenameBuffer += this.buffer.slice(this.cursor, closeIndex);
210
+ const filename = this.filenameBuffer.trim();
211
+ this._write(`\n\n${colors.brand("⚡ [File creation request: " + filename + "]")}\n\n`);
212
+ this.cursor = closeIndex + 1;
213
+ this.state = "SUPPRESSING";
214
+ } else {
215
+ this.filenameBuffer += this.buffer.slice(this.cursor);
216
+ this.cursor = this.buffer.length;
217
+ break; // Wait for more tokens
218
+ }
219
+ } else if (this.state === "SUPPRESSING") {
220
+ const nextIndex = this.buffer.indexOf(endTag, this.cursor);
221
+ if (nextIndex !== -1) {
222
+ this.cursor = nextIndex + endTag.length;
223
+ this.state = "NORMAL";
224
+ } else {
225
+ // Check for partial match of endTag at the end of the buffer
226
+ let partialMatchLength = 0;
227
+ for (let i = 1; i < endTag.length; i++) {
228
+ const part = endTag.slice(0, i);
229
+ if (this.buffer.endsWith(part)) {
230
+ partialMatchLength = i;
231
+ break;
232
+ }
233
+ }
234
+ const safeEnd = this.buffer.length - partialMatchLength;
235
+ if (safeEnd > this.cursor) {
236
+ this.cursor = safeEnd;
237
+ }
238
+ break; // Wait for more tokens
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ flush() {
245
+ if (this.state === "NORMAL" && this.cursor < this.buffer.length) {
246
+ this._write(this.buffer.slice(this.cursor));
247
+ this.cursor = this.buffer.length;
248
+ }
249
+ }
250
+ }
251
+
252
+ // ── Theme State Management ────────────────────────────────
253
+
254
+ export function getActiveTheme() {
255
+ return activeThemeName;
256
+ }
257
+
258
+ export function setTheme(themeName) {
259
+ if (!themeName) return false;
260
+ const name = themeName.toLowerCase().trim();
261
+ if (THEMES[name]) {
262
+ activeThemeName = name;
263
+ return true;
264
+ }
265
+ return false;
266
+ }
267
+
268
+ export function getThemesList() {
269
+ return Object.keys(THEMES);
270
+ }
271
+
272
+ export function stripCodeFences(content) {
273
+ let cleaned = content.trim();
274
+ if (cleaned.startsWith("```")) {
275
+ const firstNewline = cleaned.indexOf("\n");
276
+ if (firstNewline !== -1) {
277
+ cleaned = cleaned.slice(firstNewline + 1);
278
+ } else {
279
+ cleaned = cleaned.slice(3);
280
+ }
281
+ if (cleaned.endsWith("```")) {
282
+ cleaned = cleaned.slice(0, -3);
283
+ }
284
+ }
285
+ return cleaned.trim();
286
+ }
287
+
288
+ /**
289
+ * Interactive checkbox selector inside terminal using raw stdin. Renders scrollable pagination.
290
+ * Arrow Up/Down to navigate, Space to toggle, Enter to confirm, Esc/q to abort.
291
+ */
292
+ export async function interactiveCheckbox(headerText, items, preselected = []) {
293
+ if (items.length === 0) return [];
294
+
295
+ const stdin = process.stdin;
296
+ const stdout = process.stdout;
297
+
298
+ const wasRaw = stdin.isRaw;
299
+ stdin.setRawMode(true);
300
+ stdin.resume();
301
+ stdin.setEncoding("utf8");
302
+
303
+ stdout.write("\x1b[?25l"); // Hide cursor
304
+
305
+ let activeIndex = 0;
306
+ const selected = new Set(preselected.map(item => items.indexOf(item)).filter(i => i !== -1));
307
+
308
+ const PAGE_SIZE = 10;
309
+ let startRow = 0;
310
+ let renderedLines = 0;
311
+
312
+ function render() {
313
+ if (renderedLines > 0) {
314
+ stdout.write(`\x1b[${renderedLines}A\x1b[J`);
315
+ }
316
+
317
+ let lines = [];
318
+ lines.push(colors.brand(headerText));
319
+
320
+ if (activeIndex < startRow) {
321
+ startRow = activeIndex;
322
+ } else if (activeIndex >= startRow + PAGE_SIZE) {
323
+ startRow = activeIndex - PAGE_SIZE + 1;
324
+ }
325
+
326
+ const visibleEnd = Math.min(items.length, startRow + PAGE_SIZE);
327
+ for (let i = startRow; i < visibleEnd; i++) {
328
+ const isActive = i === activeIndex;
329
+ const isSelected = selected.has(i);
330
+
331
+ const pointer = isActive ? colors.accent("❯ ") : " ";
332
+ const checkbox = isSelected
333
+ ? colors.success("[⬢] ")
334
+ : colors.muted("[⬡] ");
335
+
336
+ const itemText = isActive
337
+ ? colors.brand(items[i])
338
+ : colors.text(items[i]);
339
+
340
+ lines.push(pointer + checkbox + itemText);
341
+ }
342
+
343
+ if (items.length > PAGE_SIZE) {
344
+ lines.push(colors.dim(` (Arrow Keys, Page ${Math.floor(startRow/PAGE_SIZE) + 1}/${Math.ceil(items.length/PAGE_SIZE)})`));
345
+ }
346
+
347
+ const outputStr = lines.join("\n") + "\n";
348
+ stdout.write(outputStr);
349
+ renderedLines = lines.length;
350
+ }
351
+
352
+ render();
353
+
354
+ return new Promise((resolve) => {
355
+ function handleKey(key) {
356
+ if (key === "\u0003" || key === "\u001b" || key === "q") { // Ctrl+C, Esc, q
357
+ cleanup();
358
+ resolve(null);
359
+ return;
360
+ }
361
+
362
+ if (key === "\r" || key === "\n") { // Enter
363
+ cleanup();
364
+ resolve([...selected].map(i => items[i]));
365
+ return;
366
+ }
367
+
368
+ if (key === " ") { // Spacebar
369
+ if (selected.has(activeIndex)) {
370
+ selected.delete(activeIndex);
371
+ } else {
372
+ selected.add(activeIndex);
373
+ }
374
+ render();
375
+ return;
376
+ }
377
+
378
+ if (key === "\u001b[A") { // Up Arrow
379
+ activeIndex = (activeIndex - 1 + items.length) % items.length;
380
+ render();
381
+ } else if (key === "\u001b[B") { // Down Arrow
382
+ activeIndex = (activeIndex + 1) % items.length;
383
+ render();
384
+ }
385
+ }
386
+
387
+ function cleanup() {
388
+ stdin.removeListener("data", handleKey);
389
+ stdin.setRawMode(wasRaw);
390
+ stdin.pause();
391
+ stdout.write("\x1b[?25h"); // Show cursor
392
+ }
393
+
394
+ stdin.on("data", handleKey);
395
+ });
396
+ }
397
+
398
+ /**
399
+ * Interactive single-select menu selector inside terminal. Renders scrollable pagination.
400
+ * Arrow Up/Down to navigate, Enter to select, Esc/q to abort.
401
+ */
402
+ export async function interactiveMenu(headerText, items) {
403
+ if (items.length === 0) return null;
404
+
405
+ const stdin = process.stdin;
406
+ const stdout = process.stdout;
407
+
408
+ const wasRaw = stdin.isRaw;
409
+ stdin.setRawMode(true);
410
+ stdin.resume();
411
+ stdin.setEncoding("utf8");
412
+
413
+ stdout.write("\x1b[?25l"); // Hide cursor
414
+
415
+ let activeIndex = 0;
416
+ const PAGE_SIZE = 10;
417
+ let startRow = 0;
418
+ let renderedLines = 0;
419
+
420
+ function render() {
421
+ if (renderedLines > 0) {
422
+ stdout.write(`\x1b[${renderedLines}A\x1b[J`);
423
+ }
424
+
425
+ let lines = [];
426
+ lines.push(colors.brand(headerText));
427
+
428
+ if (activeIndex < startRow) {
429
+ startRow = activeIndex;
430
+ } else if (activeIndex >= startRow + PAGE_SIZE) {
431
+ startRow = activeIndex - PAGE_SIZE + 1;
432
+ }
433
+
434
+ const visibleEnd = Math.min(items.length, startRow + PAGE_SIZE);
435
+ for (let i = startRow; i < visibleEnd; i++) {
436
+ const isActive = i === activeIndex;
437
+ const pointer = isActive ? colors.accent("❯ ") : " ";
438
+ const itemText = isActive
439
+ ? colors.success(items[i])
440
+ : colors.text(items[i]);
441
+
442
+ lines.push(pointer + itemText);
443
+ }
444
+
445
+ if (items.length > PAGE_SIZE) {
446
+ lines.push(colors.dim(` (Page ${Math.floor(startRow/PAGE_SIZE) + 1}/${Math.ceil(items.length/PAGE_SIZE)})`));
447
+ }
448
+
449
+ const outputStr = lines.join("\n") + "\n";
450
+ stdout.write(outputStr);
451
+ renderedLines = lines.length;
452
+ }
453
+
454
+ render();
455
+
456
+ return new Promise((resolve) => {
457
+ function handleKey(key) {
458
+ if (key === "\u0003" || key === "\u001b" || key === "q") { // Ctrl+C, Esc, q
459
+ cleanup();
460
+ resolve(null);
461
+ return;
462
+ }
463
+
464
+ if (key === "\r" || key === "\n") { // Enter
465
+ cleanup();
466
+ resolve(activeIndex);
467
+ return;
468
+ }
469
+
470
+ if (key === "\u001b[A") { // Up Arrow
471
+ activeIndex = (activeIndex - 1 + items.length) % items.length;
472
+ render();
473
+ } else if (key === "\u001b[B") { // Down Arrow
474
+ activeIndex = (activeIndex + 1) % items.length;
475
+ render();
476
+ }
477
+ }
478
+
479
+ function cleanup() {
480
+ stdin.removeListener("data", handleKey);
481
+ stdin.setRawMode(wasRaw);
482
+ stdin.pause();
483
+ stdout.write("\x1b[?25h"); // Show cursor
484
+ }
485
+
486
+ stdin.on("data", handleKey);
487
+ });
488
+ }
489
+
490
+ /**
491
+ * Returns either a premium Nerd Font glyph or a standard Unicode emoji fallback
492
+ * depending on whether NERD_FONTS is enabled in the configuration.
493
+ * @param {string} name - Icon name
494
+ * @param {object} config - Active configuration object
495
+ * @returns {string}
496
+ */
497
+ export function getIcon(name, config) {
498
+ const useNerd = config?.NERD_FONTS === true || config?.NERD_FONTS === "true";
499
+
500
+ const icons = {
501
+ workspace: useNerd ? "\uf07c " : "📂 ",
502
+ mode: useNerd ? "\uf0e0 " : "🧠 ", // brain / envelope-like modes icon
503
+ network: useNerd ? "\uf6ff " : "🟢 ", // network icon
504
+ engine: useNerd ? "\uf013 " : "⚙️ ", // gear icon
505
+ package: useNerd ? "\uf1b2 " : "📦 ", // package icon
506
+ mic: useNerd ? "\uf130 " : "🎤 ", // microphone icon
507
+ git: useNerd ? "\uf113 " : "🌿 ", // git/leaf icon
508
+ dashboard: useNerd ? "\uf201 " : "📊 ", // chart icon
509
+ bolt: useNerd ? "\uf0e7 " : "⚡ ", // lightning bolt icon
510
+ };
511
+
512
+ return icons[name] || "";
513
+ }
514
+
515
+ /**
516
+ * Zero-dependency dynamic theme-aware code block syntax highlighting.
517
+ * @param {string} code - The raw code to highlight
518
+ * @param {string} lang - The code block language (e.g. javascript, python, etc.)
519
+ * @returns {string} Colored code for terminal output
520
+ */
521
+ export function highlightCode(code, lang) {
522
+ if (!lang) return colors.orange(code);
523
+ const l = lang.toLowerCase().trim();
524
+
525
+ // Theme-aware colors from the active theme proxy
526
+ const kwColor = colors.magenta; // keywords
527
+ const strColor = colors.success; // strings
528
+ const commentColor = colors.muted; // comments
529
+ const numColor = colors.accent2; // numbers
530
+ const fnColor = colors.accent; // function calls
531
+ const opColor = colors.warning; // operators
532
+
533
+ if (l === "javascript" || l === "js" || l === "typescript" || l === "ts" || l === "json") {
534
+ let result = code;
535
+
536
+ // Comments
537
+ const comments = [];
538
+ result = result.replace(/(\/\*[\s\S]*?\*\/|\/\/.*)/g, (match) => {
539
+ comments.push(commentColor(match));
540
+ return `__COMMENT_PLACEHOLDER_${comments.length - 1}__`;
541
+ });
542
+
543
+ // Strings
544
+ const strings = [];
545
+ result = result.replace(/(["'`])(?:\\.|[^\\])*?\1/g, (match) => {
546
+ strings.push(strColor(match));
547
+ return `__STRING_PLACEHOLDER_${strings.length - 1}__`;
548
+ });
549
+
550
+ // Keywords
551
+ const keywords = /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|default|import|export|from|class|extends|new|this|async|await|try|catch|finally|throw|typeof|instanceof|in|of|null|undefined|true|false|void|delete|debugger)\b/g;
552
+ result = result.replace(keywords, (m) => kwColor(m));
553
+
554
+ // Numbers
555
+ result = result.replace(/\b(\d+(?:\.\d+)?)\b/g, (m) => numColor(m));
556
+
557
+ // Function calls
558
+ result = result.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)(?=\s*\()/g, (m) => fnColor(m));
559
+
560
+ // Operators
561
+ result = result.replace(/([+\-*/%=<>!&|^~]+)/g, (m) => opColor(m));
562
+
563
+ // Restore strings and comments
564
+ result = result.replace(/__STRING_PLACEHOLDER_(\d+)__/g, (_, idx) => strings[parseInt(idx, 10)]);
565
+ result = result.replace(/__COMMENT_PLACEHOLDER_(\d+)__/g, (_, idx) => comments[parseInt(idx, 10)]);
566
+
567
+ return result;
568
+ }
569
+
570
+ if (l === "python" || l === "py") {
571
+ let result = code;
572
+
573
+ const comments = [];
574
+ result = result.replace(/(#.*)/g, (match) => {
575
+ comments.push(commentColor(match));
576
+ return `__COMMENT_PLACEHOLDER_${comments.length - 1}__`;
577
+ });
578
+
579
+ const strings = [];
580
+ result = result.replace(/(["'])(?:\\.|[^\\])*?\1/g, (match) => {
581
+ strings.push(strColor(match));
582
+ return `__STRING_PLACEHOLDER_${strings.length - 1}__`;
583
+ });
584
+
585
+ const keywords = /\b(def|class|return|if|elif|else|for|while|break|continue|import|from|as|in|is|not|and|or|try|except|finally|raise|assert|with|lambda|pass|global|nonlocal|None|True|False)\b/g;
586
+ result = result.replace(keywords, (m) => kwColor(m));
587
+
588
+ result = result.replace(/\b(\d+(?:\.\d+)?)\b/g, (m) => numColor(m));
589
+ result = result.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)(?=\s*\()/g, (m) => fnColor(m));
590
+
591
+ result = result.replace(/__STRING_PLACEHOLDER_(\d+)__/g, (_, idx) => strings[parseInt(idx, 10)]);
592
+ result = result.replace(/__COMMENT_PLACEHOLDER_(\d+)__/g, (_, idx) => comments[parseInt(idx, 10)]);
593
+
594
+ return result;
595
+ }
596
+
597
+ if (l === "html" || l === "xml" || l === "svg") {
598
+ let result = code;
599
+ result = result.replace(/(<\/?[a-zA-Z0-9:-]+)/g, (m) => kwColor(m));
600
+ result = result.replace(/(\/?>)/g, (m) => kwColor(m));
601
+ result = result.replace(/(="[^"]*")/g, (match) => "=" + strColor(match.slice(1)));
602
+ return result;
603
+ }
604
+
605
+ if (l === "css") {
606
+ let result = code;
607
+ result = result.replace(/([a-zA-Z0-9_#-]+)\s*:/g, (m, p1) => fnColor(p1) + ":");
608
+ result = result.replace(/:\s*([^;]+);/g, (match, p1) => ": " + numColor(p1) + ";");
609
+ result = result.replace(/(\.[a-zA-Z0-9_-]+)/g, (m) => kwColor(m));
610
+ result = result.replace(/(#[a-zA-Z0-9_-]+)/g, (m) => opColor(m));
611
+ return result;
612
+ }
613
+
614
+ if (l === "bash" || l === "sh" || l === "cmd" || l === "powershell" || l === "ps1") {
615
+ let result = code;
616
+ result = result.replace(/(#.*)/g, (m) => commentColor(m));
617
+ const keywords = /\b(echo|cd|ls|dir|pwd|git|npm|node|pip|python|mkdir|rm|cp|mv|cat|grep|tail|head)\b/g;
618
+ result = result.replace(keywords, (m) => kwColor(m));
619
+ return result;
620
+ }
621
+
622
+ return colors.orange(code);
623
+ }
624
+