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,46 @@
1
+ {
2
+ "name": "@krishivpb60/krims-code-cli",
3
+ "version": "1.5.2",
4
+ "description": "Krims Code AI — A cyberpunk command-line AI assistant with multi-mode reasoning, 12-node failover mesh, file context injection, and offline fallbacks.",
5
+ "main": "src/cli.js",
6
+ "bin": {
7
+ "krims-code": "bin/krims-code.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "start": "node bin/krims-code.js",
12
+ "test": "node --test"
13
+ },
14
+ "keywords": [
15
+ "ai",
16
+ "cli",
17
+ "gemini",
18
+ "grok",
19
+ "xai",
20
+ "chatbot",
21
+ "Krims Code",
22
+ "cyberpunk",
23
+ "terminal",
24
+ "assistant"
25
+ ],
26
+ "author": "Krishiv PB <krylobloxyt@gmail.com> (https://github.com/Krylo-60)",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Krylo-60/krims-code-cli.git"
31
+ },
32
+ "homepage": "https://github.com/Krylo-60/krims-code-cli#readme",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.1.0",
42
+ "marked": "^14.0.0",
43
+ "marked-terminal": "^7.2.0",
44
+ "ora": "^8.1.0"
45
+ }
46
+ }
@@ -0,0 +1,415 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Krims Code AI CLI — Agent Autopilot Engine
3
+ // Safe Command Execution, File Sandbox, DuckDuckGo Search
4
+ // ═══════════════════════════════════════════════════════════
5
+
6
+ import { resolve, relative, isAbsolute, dirname } from "node:path";
7
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
8
+ import { exec } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ import chalk from "chalk";
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ export const AGENT_INSTRUCTIONS = `
15
+ SYSTEM AGENT CAPABILITIES:
16
+ You can interact with the user's terminal and files by outputting special command blocks in your responses. You can output multiple command blocks in one turn. The CLI will execute them, show you the result, and let you continue.
17
+ Supported commands:
18
+ 1. To read a file: [READ_FILE: path/to/file.ext]
19
+ 2. To write/create a file:
20
+ [WRITE_FILE: path/to/file.ext]
21
+ <content>
22
+ [END_WRITE]
23
+ 3. To run a terminal command: [RUN_COMMAND: your command here]
24
+ 4. To search the web: [SEARCH_WEB: search query here]
25
+
26
+ Rules:
27
+ - Before running modifying commands or reading private files, check the user's permission level.
28
+ - Always output clean, direct commands.
29
+ - Do not explain these command blocks; just output them when you need to perform the action.
30
+ - Never output or format responses using local output prefixes like '🤖 [LOCAL MATH SOLVER]' or offline fallback banners. Respond naturally as the assistant.
31
+ `;
32
+
33
+ export function isSafeCommand(cmd) {
34
+ const safePatterns = [
35
+ /^git\s+(status|diff|log|branch|show)/i,
36
+ /^(ls|dir|pwd)(\s+|$)/i,
37
+ /^(cat|type|head|tail)(\s+|$)/i,
38
+ /^(npm|yarn|pnpm)\s+test(\s+|$)/i,
39
+ /^(node|npm|git|yarn|pnpm|python\d*|py|pip)\s+(--version|-v)(\s+|$)/i,
40
+ /^py\s+-[0-9a-zA-Z]+(\s+|$)/i, // matches 'py -0p', 'py -0'
41
+ /^py\s+-\d+(\.\d+)*\s+(--version|-v)(\s+|$)/i, // matches 'py -3.12 --version'
42
+ ];
43
+ return safePatterns.some((pattern) => pattern.test(cmd.trim()));
44
+ }
45
+
46
+ /**
47
+ * Checks if a target path is inside the current working directory.
48
+ * @param {string} path
49
+ * @returns {boolean}
50
+ */
51
+ export function isInsideWorkspace(path) {
52
+ const ws = resolve(process.cwd());
53
+ const target = resolve(path);
54
+ const rel = relative(ws, target);
55
+ return !rel.startsWith("..") && !isAbsolute(rel);
56
+ }
57
+
58
+ /**
59
+ * Free web search via DuckDuckGo HTML scraping.
60
+ * @param {string} query
61
+ * @returns {Promise<Array>} List of search results
62
+ */
63
+ export async function searchDuckDuckGo(query) {
64
+ try {
65
+ const response = await fetch(
66
+ `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
67
+ {
68
+ headers: {
69
+ "User-Agent":
70
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
71
+ },
72
+ }
73
+ );
74
+ if (!response.ok) {
75
+ throw new Error(`DuckDuckGo returned status ${response.status}`);
76
+ }
77
+ const html = await response.text();
78
+
79
+ const results = [];
80
+ const matches = [...html.matchAll(/<a class="result__a"[^>]* href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g)];
81
+ const snippets = [...html.matchAll(/<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g)];
82
+
83
+ for (let i = 0; i < Math.min(5, matches.length); i++) {
84
+ let url = matches[i][1];
85
+ const title = matches[i][2].replace(/<[^>]*>/g, "").trim();
86
+ const snippet = snippets[i]
87
+ ? snippets[i][1].replace(/<[^>]*>/g, "").trim()
88
+ : "";
89
+
90
+ if (url.includes("uddg=")) {
91
+ const urlMatch = url.match(/uddg=([^&]+)/);
92
+ if (urlMatch) {
93
+ url = decodeURIComponent(urlMatch[1]);
94
+ }
95
+ }
96
+ if (url.startsWith("//")) {
97
+ url = "https:" + url;
98
+ }
99
+
100
+ results.push({ title, url, snippet });
101
+ }
102
+ return results;
103
+ } catch (err) {
104
+ throw new Error(`DuckDuckGo search failed: ${err.message}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Sequentially parses and executes all agent tool blocks in the text.
110
+ * @param {string} text - AI response
111
+ * @param {object} aiConfig - Flat config object
112
+ * @param {object} rl - Readline interface
113
+ * @returns {Promise<Array>} List of execution results
114
+ */
115
+ export async function processAgentBlocks(text, aiConfig, rl) {
116
+ let index = 0;
117
+ const results = [];
118
+
119
+ while (true) {
120
+ const runMatch = text.indexOf("[RUN_COMMAND:", index);
121
+ const readMatch = text.indexOf("[READ_FILE:", index);
122
+ const searchMatch = text.indexOf("[SEARCH_WEB:", index);
123
+ const writeMatch = text.indexOf("[WRITE_FILE:", index);
124
+
125
+ const matches = [
126
+ { type: "RUN_COMMAND", pos: runMatch },
127
+ { type: "READ_FILE", pos: readMatch },
128
+ { type: "SEARCH_WEB", pos: searchMatch },
129
+ { type: "WRITE_FILE", pos: writeMatch },
130
+ ].filter((m) => m.pos !== -1);
131
+
132
+ if (matches.length === 0) break;
133
+
134
+ // Process the earliest tag in the text
135
+ matches.sort((a, b) => a.pos - b.pos);
136
+ const nextTool = matches[0];
137
+
138
+ if (nextTool.type === "WRITE_FILE") {
139
+ const startTag = "[WRITE_FILE:";
140
+ const startIdx = nextTool.pos;
141
+ const endTagIdx = text.indexOf("]", startIdx);
142
+ if (endTagIdx === -1) {
143
+ index = startIdx + startTag.length;
144
+ continue;
145
+ }
146
+ const filePath = text.substring(startIdx + startTag.length, endTagIdx).trim();
147
+ const endWriteIdx = text.indexOf("[END_WRITE]", endTagIdx);
148
+ if (endWriteIdx === -1) {
149
+ index = endTagIdx + 1;
150
+ continue;
151
+ }
152
+ const content = text.substring(endTagIdx + 1, endWriteIdx);
153
+
154
+ const result = await executeTool("WRITE_FILE", filePath, content, aiConfig, rl);
155
+ results.push(result);
156
+ index = endWriteIdx + "[END_WRITE]".length;
157
+ } else {
158
+ const tag = `[${nextTool.type}:`;
159
+ const startIdx = nextTool.pos;
160
+ const endIdx = text.indexOf("]", startIdx);
161
+ if (endIdx === -1) {
162
+ index = startIdx + tag.length;
163
+ continue;
164
+ }
165
+ const arg = text.substring(startIdx + tag.length, endIdx).trim();
166
+
167
+ const result = await executeTool(nextTool.type, arg, "", aiConfig, rl);
168
+ results.push(result);
169
+ index = endIdx + 1;
170
+ }
171
+ }
172
+
173
+ return results;
174
+ }
175
+
176
+ /**
177
+ * Executes a single tool based on autopilot config settings.
178
+ * @param {string} type - Tool type
179
+ * @param {string} arg - Tool argument
180
+ * @param {string} content - Write file content (if applicable)
181
+ * @param {object} aiConfig - Flat config object
182
+ * @param {object} rl - Readline interface
183
+ */
184
+ export async function executeTool(type, arg, content, aiConfig, rl) {
185
+ const autopilot = (aiConfig.AUTOPILOT || "off").toLowerCase();
186
+
187
+ if (type === "READ_FILE") {
188
+ const filePath = arg;
189
+ const inside = isInsideWorkspace(filePath);
190
+
191
+ let allowed = false;
192
+ if (
193
+ autopilot === "machine" ||
194
+ autopilot === "workspace" ||
195
+ (autopilot === "safe" && inside)
196
+ ) {
197
+ allowed = true;
198
+ } else {
199
+ rl.pause();
200
+ const answer = await new Promise((resolve) => {
201
+ rl.question(
202
+ chalk.yellow(
203
+ `\n⚠️ AI wants to read file: "${filePath}". Allow? [y/N]: `
204
+ ),
205
+ resolve
206
+ );
207
+ });
208
+ rl.resume();
209
+ allowed =
210
+ answer.toLowerCase().trim() === "y" ||
211
+ answer.toLowerCase().trim() === "yes";
212
+ }
213
+
214
+ if (!allowed) {
215
+ return {
216
+ tool: "READ_FILE",
217
+ arg,
218
+ success: false,
219
+ error: "Access denied by user.",
220
+ };
221
+ }
222
+
223
+ try {
224
+ const fileContent = readFileSync(resolve(filePath), "utf-8");
225
+ return {
226
+ tool: "READ_FILE",
227
+ arg,
228
+ success: true,
229
+ content: fileContent,
230
+ };
231
+ } catch (err) {
232
+ return {
233
+ tool: "READ_FILE",
234
+ arg,
235
+ success: false,
236
+ error: err.message,
237
+ };
238
+ }
239
+ }
240
+
241
+ if (type === "WRITE_FILE") {
242
+ const filePath = arg;
243
+ const inside = isInsideWorkspace(filePath);
244
+
245
+ let allowed = false;
246
+ if (autopilot === "machine" || (autopilot === "workspace" && inside)) {
247
+ allowed = true;
248
+ } else {
249
+ rl.pause();
250
+ const answer = await new Promise((resolve) => {
251
+ rl.question(
252
+ chalk.yellow(
253
+ `\n⚠️ AI wants to write file: "${filePath}". Allow? [y/N]: `
254
+ ),
255
+ resolve
256
+ );
257
+ });
258
+ rl.resume();
259
+ allowed =
260
+ answer.toLowerCase().trim() === "y" ||
261
+ answer.toLowerCase().trim() === "yes";
262
+ }
263
+
264
+ if (!allowed) {
265
+ return {
266
+ tool: "WRITE_FILE",
267
+ arg,
268
+ success: false,
269
+ error: "Access denied by user.",
270
+ };
271
+ }
272
+
273
+ try {
274
+ const fullPath = resolve(filePath);
275
+ mkdirSync(dirname(fullPath), { recursive: true });
276
+ writeFileSync(fullPath, content, "utf-8");
277
+ return {
278
+ tool: "WRITE_FILE",
279
+ arg,
280
+ success: true,
281
+ message: "File written successfully.",
282
+ };
283
+ } catch (err) {
284
+ return {
285
+ tool: "WRITE_FILE",
286
+ arg,
287
+ success: false,
288
+ error: err.message,
289
+ };
290
+ }
291
+ }
292
+
293
+ if (type === "RUN_COMMAND") {
294
+ const command = arg;
295
+ const safe = isSafeCommand(command);
296
+
297
+ let allowed = false;
298
+ if (
299
+ autopilot === "machine" ||
300
+ (autopilot === "safe" && safe) ||
301
+ (autopilot === "workspace" && safe)
302
+ ) {
303
+ allowed = true;
304
+ } else {
305
+ rl.pause();
306
+ const answer = await new Promise((resolve) => {
307
+ rl.question(
308
+ chalk.yellow(
309
+ `\n⚠️ AI wants to run terminal command: "${command}". Allow? [y/N/always]: `
310
+ ),
311
+ resolve
312
+ );
313
+ });
314
+ rl.resume();
315
+
316
+ const cleanAnswer = answer.toLowerCase().trim();
317
+ if (cleanAnswer === "always") {
318
+ const { setConfigValue } = await import("./config.js");
319
+ await setConfigValue("AUTOPILOT", "safe");
320
+ aiConfig.AUTOPILOT = "safe";
321
+ console.log(chalk.green(`\n✓ Autopilot enabled (Safe mode).`));
322
+ allowed = true;
323
+ } else {
324
+ allowed = cleanAnswer === "y" || cleanAnswer === "yes";
325
+ }
326
+ }
327
+
328
+ if (!allowed) {
329
+ return {
330
+ tool: "RUN_COMMAND",
331
+ arg,
332
+ success: false,
333
+ error: "Execution denied by user.",
334
+ };
335
+ }
336
+
337
+ console.log(chalk.cyan(`\n⚡ Running command: ${command}`));
338
+ try {
339
+ const { stdout, stderr } = await execAsync(command);
340
+ return {
341
+ tool: "RUN_COMMAND",
342
+ arg,
343
+ success: true,
344
+ stdout,
345
+ stderr,
346
+ };
347
+ } catch (err) {
348
+ return {
349
+ tool: "RUN_COMMAND",
350
+ arg,
351
+ success: false,
352
+ error: err.message,
353
+ stdout: err.stdout,
354
+ stderr: err.stderr,
355
+ };
356
+ }
357
+ }
358
+
359
+ if (type === "SEARCH_WEB") {
360
+ const query = arg;
361
+
362
+ let allowed = false;
363
+ if (autopilot !== "off") {
364
+ allowed = true;
365
+ } else {
366
+ rl.pause();
367
+ const answer = await new Promise((resolve) => {
368
+ rl.question(
369
+ chalk.yellow(
370
+ `\n⚠️ AI wants to search the web for: "${query}". Allow? [y/N]: `
371
+ ),
372
+ resolve
373
+ );
374
+ });
375
+ rl.resume();
376
+ allowed =
377
+ answer.toLowerCase().trim() === "y" ||
378
+ answer.toLowerCase().trim() === "yes";
379
+ }
380
+
381
+ if (!allowed) {
382
+ return {
383
+ tool: "SEARCH_WEB",
384
+ arg,
385
+ success: false,
386
+ error: "Search denied by user.",
387
+ };
388
+ }
389
+
390
+ console.log(chalk.cyan(`\n🔍 Searching web: "${query}"`));
391
+ try {
392
+ const results = await searchDuckDuckGo(query);
393
+ return {
394
+ tool: "SEARCH_WEB",
395
+ arg,
396
+ success: true,
397
+ results,
398
+ };
399
+ } catch (err) {
400
+ return {
401
+ tool: "SEARCH_WEB",
402
+ arg,
403
+ success: false,
404
+ error: err.message,
405
+ };
406
+ }
407
+ }
408
+
409
+ return {
410
+ tool: type,
411
+ arg,
412
+ success: false,
413
+ error: "Unknown tool type.",
414
+ };
415
+ }
@@ -0,0 +1,239 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Krims Code AI CLI — Local Fallback Engine
3
+ // Math Solver & Offline Fallback
4
+ // ═══════════════════════════════════════════════════════════
5
+
6
+ /**
7
+ * Detects if a prompt is a pure mathematical expression.
8
+ * Supports basic operators, parentheses, standard math functions, and constants.
9
+ * @param {string} prompt - The user prompt
10
+ * @returns {string|null} The cleaned expression or null
11
+ */
12
+ export function detectMathExpression(prompt) {
13
+ const clean = prompt.replace(/\s+/g, "").toLowerCase();
14
+
15
+ // Check if it's a word-based status query
16
+ if (clean === "status" || clean === "hud") return null;
17
+
18
+ // Strip allowed function and constant words
19
+ const structure = clean.replace(/sin|cos|tan|log|ln|sqrt|pi|e|abs/g, "");
20
+
21
+ // Must be composed of valid math characters, AND contain either a math operator or an active function call
22
+ if (/^[0-9+\-*/().%^]+$/.test(structure)) {
23
+ const hasOperator = /[+\-*/%^]/.test(structure);
24
+ const hasFunction = /(sin|cos|tan|log|ln|sqrt|abs)\(/.test(clean);
25
+ if (hasOperator || hasFunction) {
26
+ return clean;
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * Safely evaluates a mathematical expression locally.
34
+ * Supports trig, square root, natural/base-10 logs, absolute values, pi, and e.
35
+ * @param {string} expression - A sanitized math expression
36
+ * @returns {{ text: string, type: string }|null}
37
+ */
38
+ export function solveMath(expression) {
39
+ if (!expression) return null;
40
+ try {
41
+ const clean = expression.replace(/\s+/g, "").toLowerCase();
42
+
43
+ // Validate character structure
44
+ const structure = clean.replace(/sin|cos|tan|log|ln|sqrt|pi|e|abs/g, "");
45
+ if (!/^[0-9+\-*/().%^]+$/.test(structure)) return null;
46
+
47
+ // Convert terms to JavaScript Math equivalents
48
+ let jsExpr = clean
49
+ .replace(/\^/g, "**")
50
+ .replace(/sin\(/g, "Math.sin(")
51
+ .replace(/cos\(/g, "Math.cos(")
52
+ .replace(/tan\(/g, "Math.tan(")
53
+ .replace(/log\(/g, "Math.log10(")
54
+ .replace(/ln\(/g, "Math.log(")
55
+ .replace(/sqrt\(/g, "Math.sqrt(")
56
+ .replace(/abs\(/g, "Math.abs(")
57
+ .replace(/\bpi\b/g, "Math.PI")
58
+ .replace(/\be\b/g, "Math.E");
59
+
60
+ const result = Function(`"use strict"; return (${jsExpr})`)();
61
+ if (typeof result === "number" && !isNaN(result) && isFinite(result)) {
62
+ return {
63
+ text: [
64
+ "🤖 [LOCAL MATH SOLVER]",
65
+ ` Expression: ${expression}`,
66
+ ` Result: ${Number(result.toFixed(6))}`,
67
+ ].join("\n"),
68
+ type: "local-math",
69
+ };
70
+ }
71
+ } catch {
72
+ // Not a valid expression
73
+ }
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Starts a mainframe security bypass mini-game.
79
+ * @returns {{ text: string, type: string }}
80
+ */
81
+ export function runMainframeHack() {
82
+ return {
83
+ text: [
84
+ "⚡ [LOCAL TERMINAL SECURITY BYPASS GAME]",
85
+ " MAINFRAME HACK PROTOCOL LOADED.",
86
+ " ────────────────────────────────────────",
87
+ " Objective: Bypass security by guessing the 4-digit PIN (digits 0-9).",
88
+ " For each guess, you will get feedback:",
89
+ " • 'Hit' - correct digit in correct position.",
90
+ " • 'Close' - correct digit but in wrong position.",
91
+ " You have 6 attempts before security lock-out.",
92
+ " ",
93
+ " Type `/guess <number>` to input breach code (e.g. /guess 2941)",
94
+ " To abort, type `/abort`.",
95
+ " ────────────────────────────────────────",
96
+ ].join("\n"),
97
+ type: "mainframe-game",
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Generates a local offline/error reply when no AI keys are configured or fail.
103
+ * Implements the Krylo companion bot fallback speaking clean English.
104
+ * @param {string} prompt - The user prompt
105
+ * @param {string[]} [errors] - Optional error messages from failed provider nodes
106
+ * @returns {{ text: string, type: string }}
107
+ */
108
+ export function generateOfflineReply(prompt, errors = []) {
109
+ const clean = (prompt || "").trim().toLowerCase();
110
+ let header = "🤖 [KRYLO COMPANION - OFFLINE MODE]";
111
+
112
+ // Default Error/Quota Fallback
113
+ if (errors && errors.length > 0) {
114
+ const isRateLimited = errors.some((e) => {
115
+ const lower = e.toLowerCase();
116
+ return (
117
+ lower.includes("quota") ||
118
+ lower.includes("rate limit") ||
119
+ lower.includes("rate-limit") ||
120
+ lower.includes("429") ||
121
+ lower.includes("resource_exhausted") ||
122
+ lower.includes("limit exceeded")
123
+ );
124
+ });
125
+
126
+ const lines = [
127
+ header,
128
+ "⚠️ All configured AI provider nodes failed to respond.",
129
+ ""
130
+ ];
131
+ if (isRateLimited) {
132
+ lines.push("💡 [Rate Limit / Quota Exceeded]: One or more provider nodes hit their API usage limits.");
133
+ }
134
+ lines.push(
135
+ "Errors encountered:",
136
+ ...errors.map((e) => `• ${e}`),
137
+ "",
138
+ "Please check your API keys, network connection, or rate limits."
139
+ );
140
+
141
+ return {
142
+ text: lines.join("\n"),
143
+ type: "offline-error"
144
+ };
145
+ }
146
+
147
+ let warning = "\n💡 Note: Running locally to conserve your API quota.";
148
+
149
+ // Greetings
150
+ if (/^(hello|hi|hey|greetings|yo|sup)\b/.test(clean)) {
151
+ return {
152
+ text: [
153
+ header,
154
+ warning,
155
+ "",
156
+ "Hello! I am Krylo, your offline companion for Krims Code CLI.",
157
+ "I can help you with local math calculations (e.g. 2 + 2), status checks, or run simple hacking games.",
158
+ "To converse with an advanced LLM, please configure an API key using:",
159
+ " krims-code config set GOOGLE_API_KEY <your_key_here>"
160
+ ].join("\n"),
161
+ type: "krylo-local"
162
+ };
163
+ }
164
+
165
+ // Help / Commands
166
+ if (clean.includes("help") || clean.includes("command") || clean.includes("shortcut")) {
167
+ return {
168
+ text: [
169
+ header,
170
+ warning,
171
+ "",
172
+ "💡 [Krims Code CLI QUICK CHEAT SHEET]",
173
+ " • /mode <name> - Switch reasoning modes (synthesis, research, architect, titan)",
174
+ " • /attach <file> - Attach a file for context (supports autocomplete)",
175
+ " • /git - Launch interactive Git TUI & file stager checkbox menu",
176
+ " • /dashboard - Launch local web telemetry dashboard HUD",
177
+ " • /autopilot debug - Run autonomous debug loop to fix test failures",
178
+ " • /copy - Copy the last response to clipboard",
179
+ " • /export - Export conversation history to Markdown"
180
+ ].join("\n"),
181
+ type: "krylo-local"
182
+ };
183
+ }
184
+
185
+ // Diagnostics / Status
186
+ if (clean.includes("status") || clean.includes("hud") || clean.includes("cpu") || clean.includes("ping") || clean.includes("diagnostics")) {
187
+ return {
188
+ text: [
189
+ header,
190
+ warning,
191
+ "",
192
+ "📊 [SYSTEM DIAGNOSTIC READOUT]",
193
+ " • CPU Core Load: Nominal / Optimized",
194
+ " • Memory Usage: Active & stable",
195
+ " • Network Link: Standby / Offline local mesh active",
196
+ " • Failover Mesh: Ready to route prompts"
197
+ ].join("\n"),
198
+ type: "krylo-local"
199
+ };
200
+ }
201
+
202
+ // Code / Programming
203
+ if (clean.includes("code") || clean.includes("write") || clean.includes("program") || clean.includes("javascript") || clean.includes("python")) {
204
+ return {
205
+ text: [
206
+ header,
207
+ warning,
208
+ "",
209
+ "💻 [LOCAL CODE REFERENCE]",
210
+ "Since I am running offline, I cannot write complex code for you. Here is a basic template:",
211
+ "",
212
+ "```javascript",
213
+ "// Node.js entry template",
214
+ "import fs from 'node:fs/promises';",
215
+ "",
216
+ "async function main() {",
217
+ " console.log('Krims Code offline node initialized.');",
218
+ "}",
219
+ "main();",
220
+ "```",
221
+ "",
222
+ "Configure an API key to have Krims Code write, refactor, or debug code on your behalf!"
223
+ ].join("\n"),
224
+ type: "krylo-local"
225
+ };
226
+ }
227
+
228
+ return {
229
+ text: [
230
+ header,
231
+ warning,
232
+ "",
233
+ "No active API keys configured. Please set GOOGLE_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY in your config to start chatting.",
234
+ "Example: krims-code config set GOOGLE_API_KEY <your-key>"
235
+ ].join("\n"),
236
+ type: "offline-error"
237
+ };
238
+ }
239
+