krims-code-cli 1.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- krims_code_cli-1.5.2.dist-info/METADATA +336 -0
- krims_code_cli-1.5.2.dist-info/RECORD +43 -0
- krims_code_cli-1.5.2.dist-info/WHEEL +5 -0
- krims_code_cli-1.5.2.dist-info/entry_points.txt +2 -0
- krims_code_cli-1.5.2.dist-info/licenses/LICENSE +21 -0
- krims_code_cli-1.5.2.dist-info/top_level.txt +1 -0
- krims_pip/__init__.py +1 -0
- krims_pip/cli.py +50 -0
- krims_pip/node_project/bin/krims-code.js +10 -0
- krims_pip/node_project/package-lock.json +793 -0
- krims_pip/node_project/package.json +46 -0
- krims_pip/node_project/src/agent.js +415 -0
- krims_pip/node_project/src/ai/fallback.js +239 -0
- krims_pip/node_project/src/ai/google.js +87 -0
- krims_pip/node_project/src/ai/providers.js +203 -0
- krims_pip/node_project/src/ai/router.js +146 -0
- krims_pip/node_project/src/ai/search.js +94 -0
- krims_pip/node_project/src/ai/telemetry.js +125 -0
- krims_pip/node_project/src/ai/tokens.js +101 -0
- krims_pip/node_project/src/ai/universal.js +507 -0
- krims_pip/node_project/src/ai/xai.js +50 -0
- krims_pip/node_project/src/chat.js +2543 -0
- krims_pip/node_project/src/cli.js +632 -0
- krims_pip/node_project/src/commands/commit.js +157 -0
- krims_pip/node_project/src/commands/dashboard.js +59 -0
- krims_pip/node_project/src/commands/github.js +349 -0
- krims_pip/node_project/src/commands/index.js +67 -0
- krims_pip/node_project/src/commands/status.js +66 -0
- krims_pip/node_project/src/commands/teamwork-preview.js +326 -0
- krims_pip/node_project/src/commands/theme.js +42 -0
- krims_pip/node_project/src/commands/themes.js +30 -0
- krims_pip/node_project/src/config.js +400 -0
- krims_pip/node_project/src/dashboard.js +112 -0
- krims_pip/node_project/src/file-parser.js +164 -0
- krims_pip/node_project/src/git.js +41 -0
- krims_pip/node_project/src/mic.js +220 -0
- krims_pip/node_project/src/modes.js +92 -0
- krims_pip/node_project/src/telemetry-server.js +855 -0
- krims_pip/node_project/src/ui/banner.js +204 -0
- krims_pip/node_project/src/ui/dashboard.html +834 -0
- krims_pip/node_project/src/ui/spinner.js +43 -0
- krims_pip/node_project/src/ui/theme.js +624 -0
- krims_pip/node_project/src/updater.js +196 -0
|
@@ -0,0 +1,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
|
+
|