rafaygen-cli 1.3.2 → 1.3.3
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.
- package/bin/rgcli.js +302 -29
- package/package.json +2 -2
- package/src/agent.js +1297 -191
- package/src/auth.js +446 -105
- package/src/executor.js +715 -65
- package/src/state.js +254 -10
- package/src/ui.js +419 -51
package/src/agent.js
CHANGED
|
@@ -1,277 +1,1383 @@
|
|
|
1
|
-
import { getToken, getApiUrl, getModel, setModel } from "./auth.js";
|
|
2
|
-
import { executeAction } from "./executor.js";
|
|
3
|
-
import { printError, printStep, printSuccess } from "./ui.js";
|
|
4
|
-
import { getSessionState, updateSessionState } from "./state.js";
|
|
5
1
|
import fs from "fs";
|
|
6
2
|
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import { getToken, getApiUrl, getModel, setModel } from "./auth.js";
|
|
7
|
+
import { executeAction } from "./executor.js";
|
|
8
|
+
import {
|
|
9
|
+
printError,
|
|
10
|
+
printStep,
|
|
11
|
+
printSuccess,
|
|
12
|
+
renderBox,
|
|
13
|
+
renderCodeBox,
|
|
14
|
+
printAsciiLogo,
|
|
15
|
+
printRandomWelcome,
|
|
16
|
+
} from "./ui.js";
|
|
17
|
+
import {
|
|
18
|
+
getSessionState,
|
|
19
|
+
updateSessionState,
|
|
20
|
+
addToHistory,
|
|
21
|
+
saveSession,
|
|
22
|
+
loadSession,
|
|
23
|
+
listSessions,
|
|
24
|
+
clearHistory,
|
|
25
|
+
} from "./state.js";
|
|
7
26
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
/* ─── helpers that may not be in auth.js yet ─── */
|
|
28
|
+
function getSkillsDir() {
|
|
29
|
+
const dir = path.join(os.homedir(), ".rgcli", "skills");
|
|
30
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getMcpConfigPath() {
|
|
35
|
+
const dir = path.join(os.homedir(), ".rgcli");
|
|
36
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
return path.join(dir, "mcp.json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ─── UI helpers not yet in ui.js ─── */
|
|
41
|
+
async function _chalk() {
|
|
42
|
+
return (await import("chalk")).default;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printWarning(msg) {
|
|
46
|
+
const c = _chalkSync();
|
|
47
|
+
console.log(c.yellow.bold("\n⚠ Warning: ") + c.yellow(msg) + "\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printInfo(msg) {
|
|
51
|
+
const c = _chalkSync();
|
|
52
|
+
console.log(c.blueBright.bold("\nℹ ") + c.blueBright(msg) + "\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printModelBadge(model) {
|
|
56
|
+
const c = _chalkSync();
|
|
57
|
+
console.log(
|
|
58
|
+
c.bgMagenta.white.bold(` MODEL `) +
|
|
59
|
+
" " +
|
|
60
|
+
c.magentaBright.bold(model) +
|
|
61
|
+
"\n"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function printSessionStatus(state) {
|
|
66
|
+
const c = _chalkSync();
|
|
67
|
+
const lines = [
|
|
68
|
+
`${c.bold("Session ID:")} ${state.sessionId}`,
|
|
69
|
+
`${c.bold("Sandbox:")} ${state.sandboxMode}`,
|
|
70
|
+
`${c.bold("Approvals:")} ${state.approvalMode}`,
|
|
71
|
+
`${c.bold("Model:")} ${getModel()}`,
|
|
72
|
+
`${c.bold("Reasoning:")} ${state.reasoningEffort}`,
|
|
73
|
+
`${c.bold("Compact:")} ${state.compactMode ? "ON" : "OFF"}`,
|
|
74
|
+
`${c.bold("CWD:")} ${state.cwd}`,
|
|
75
|
+
`${c.bold("Attached:")} ${state.attachedFiles.size} file(s)`,
|
|
76
|
+
`${c.bold("Active Skill:")} ${state.activeSkill || "none"}`,
|
|
77
|
+
`${c.bold("History:")} ${state.conversationHistory.length} messages`,
|
|
78
|
+
`${c.bold("Image:")} ${state.imageAttached || "none"}`,
|
|
79
|
+
];
|
|
80
|
+
renderBox(" Session Status ", lines.join("\n"), "cyan");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printAgentThinking() {
|
|
84
|
+
const c = _chalkSync();
|
|
85
|
+
console.log(c.gray(" 🧠 Agent is thinking..."));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printToolExecution(tool) {
|
|
89
|
+
const c = _chalkSync();
|
|
90
|
+
console.log(c.yellow(` 🔧 Executing tool: ${tool}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderMarkdown(text) {
|
|
94
|
+
const c = _chalkSync();
|
|
95
|
+
if (!text) return;
|
|
96
|
+
// Basic markdown rendering: headers, bold, inline code, code blocks
|
|
97
|
+
const lines = text.split("\n");
|
|
98
|
+
const output = [];
|
|
99
|
+
let inCodeBlock = false;
|
|
100
|
+
let codeBuffer = [];
|
|
101
|
+
let codeLang = "";
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (line.startsWith("```") && !inCodeBlock) {
|
|
105
|
+
inCodeBlock = true;
|
|
106
|
+
codeLang = line.slice(3).trim();
|
|
107
|
+
codeBuffer = [];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (line.startsWith("```") && inCodeBlock) {
|
|
111
|
+
inCodeBlock = false;
|
|
112
|
+
try {
|
|
113
|
+
const { highlight } = await import("cli-highlight");
|
|
114
|
+
output.push(
|
|
115
|
+
highlight(codeBuffer.join("\n"), {
|
|
116
|
+
language: codeLang || "plaintext",
|
|
117
|
+
ignoreIllegals: true,
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
} catch {
|
|
121
|
+
output.push(c.green(codeBuffer.join("\n")));
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (inCodeBlock) {
|
|
126
|
+
codeBuffer.push(line);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let processed = line;
|
|
131
|
+
// Headers
|
|
132
|
+
if (processed.startsWith("### "))
|
|
133
|
+
processed = c.cyan.bold(processed.slice(4));
|
|
134
|
+
else if (processed.startsWith("## "))
|
|
135
|
+
processed = c.cyan.bold.underline(processed.slice(3));
|
|
136
|
+
else if (processed.startsWith("# "))
|
|
137
|
+
processed = c.cyan.bold.underline(processed.slice(2));
|
|
138
|
+
// Bold
|
|
139
|
+
processed = processed.replace(
|
|
140
|
+
/\*\*(.+?)\*\*/g,
|
|
141
|
+
(_, m) => c.bold(m)
|
|
142
|
+
);
|
|
143
|
+
// Italic
|
|
144
|
+
processed = processed.replace(
|
|
145
|
+
/\*(.+?)\*/g,
|
|
146
|
+
(_, m) => c.italic(m)
|
|
147
|
+
);
|
|
148
|
+
// Inline code
|
|
149
|
+
processed = processed.replace(
|
|
150
|
+
/`([^`]+)`/g,
|
|
151
|
+
(_, m) => c.bgGray.white(` ${m} `)
|
|
152
|
+
);
|
|
153
|
+
// Bullet points
|
|
154
|
+
if (processed.startsWith("- "))
|
|
155
|
+
processed = c.green(" • ") + processed.slice(2);
|
|
156
|
+
if (/^\d+\.\s/.test(processed))
|
|
157
|
+
processed = c.green(" " + processed);
|
|
158
|
+
|
|
159
|
+
output.push(processed);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (inCodeBlock && codeBuffer.length) {
|
|
163
|
+
output.push(c.green(codeBuffer.join("\n")));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(output.join("\n"));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Synchronous chalk import (cached after first dynamic import)
|
|
170
|
+
let _chalkCache = null;
|
|
171
|
+
function _chalkSync() {
|
|
172
|
+
if (!_chalkCache) {
|
|
173
|
+
// chalk 5.x is ESM-only but we already import it at top-level transitively via ui.js
|
|
174
|
+
// Fallback: use a basic wrapper
|
|
175
|
+
try {
|
|
176
|
+
// attempt require for chalk 4.x compat
|
|
177
|
+
_chalkCache = require("chalk");
|
|
178
|
+
} catch {
|
|
179
|
+
// provide a pass-through if chalk not yet loaded
|
|
180
|
+
const identity = (s) => s;
|
|
181
|
+
const handler = {
|
|
182
|
+
get(_, prop) {
|
|
183
|
+
if (prop === "bold" || prop === "italic" || prop === "underline")
|
|
184
|
+
return new Proxy(identity, handler);
|
|
185
|
+
if (typeof identity[prop] === "function") return identity[prop];
|
|
186
|
+
return new Proxy(identity, handler);
|
|
187
|
+
},
|
|
188
|
+
apply(target, _, args) {
|
|
189
|
+
return args[0];
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
_chalkCache = new Proxy(identity, handler);
|
|
22
193
|
}
|
|
23
194
|
}
|
|
24
|
-
return
|
|
195
|
+
return _chalkCache;
|
|
25
196
|
}
|
|
26
197
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
198
|
+
// Pre-load chalk
|
|
199
|
+
(async () => {
|
|
200
|
+
try {
|
|
201
|
+
_chalkCache = (await import("chalk")).default;
|
|
202
|
+
} catch {}
|
|
203
|
+
})();
|
|
204
|
+
|
|
205
|
+
/* ─── Binary / skip detection ─── */
|
|
206
|
+
const BINARY_EXTS = new Set([
|
|
207
|
+
"png","jpg","jpeg","gif","bmp","ico","webp","svg","mp3","mp4","avi",
|
|
208
|
+
"mov","mkv","zip","tar","gz","rar","7z","exe","dll","so","dylib",
|
|
209
|
+
"bin","dat","pdf","doc","docx","xls","xlsx","ppt","pptx","woff",
|
|
210
|
+
"woff2","ttf","eot","class","o","pyc","pyo",
|
|
211
|
+
]);
|
|
212
|
+
const SKIP_DIRS = new Set([
|
|
213
|
+
"node_modules",".git",".next","dist","build","__pycache__",".cache",
|
|
214
|
+
".vscode",".idea","coverage","vendor","target",
|
|
215
|
+
]);
|
|
31
216
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
217
|
+
function isBinary(filePath) {
|
|
218
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
219
|
+
return BINARY_EXTS.has(ext);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ═══════════════════════════════════════════
|
|
223
|
+
extractFileContext(input)
|
|
224
|
+
═══════════════════════════════════════════ */
|
|
225
|
+
export function extractFileContext(input) {
|
|
226
|
+
const regex = /"([^"]+)"|'([^']+)'|(\S+)/g;
|
|
227
|
+
let match;
|
|
228
|
+
const paths = [];
|
|
229
|
+
const nonPaths = [];
|
|
230
|
+
|
|
231
|
+
while ((match = regex.exec(input)) !== null) {
|
|
232
|
+
const token = match[1] || match[2] || match[3];
|
|
233
|
+
const resolved = path.isAbsolute(token)
|
|
234
|
+
? token
|
|
235
|
+
: path.resolve(process.cwd(), token);
|
|
236
|
+
if (fs.existsSync(resolved)) {
|
|
237
|
+
paths.push(resolved);
|
|
238
|
+
} else {
|
|
239
|
+
nonPaths.push(match[0]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const extractedContext = [];
|
|
244
|
+
const visited = new Set();
|
|
245
|
+
|
|
246
|
+
function readDir(dir, depth = 0) {
|
|
247
|
+
if (depth > 4 || visited.size >= 30) return;
|
|
248
|
+
let entries;
|
|
249
|
+
try {
|
|
250
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
251
|
+
} catch {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
for (const entry of entries) {
|
|
255
|
+
if (visited.size >= 30) break;
|
|
256
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
257
|
+
const full = path.join(dir, entry.name);
|
|
258
|
+
if (entry.isDirectory()) {
|
|
259
|
+
readDir(full, depth + 1);
|
|
260
|
+
} else if (entry.isFile()) {
|
|
261
|
+
if (visited.has(full)) continue;
|
|
262
|
+
visited.add(full);
|
|
263
|
+
if (isBinary(full)) {
|
|
264
|
+
extractedContext.push({
|
|
265
|
+
path: full,
|
|
266
|
+
type: "binary",
|
|
267
|
+
content: `[Binary file: ${entry.name}]`,
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
try {
|
|
271
|
+
const raw = fs.readFileSync(full, "utf-8");
|
|
272
|
+
extractedContext.push({
|
|
273
|
+
path: full,
|
|
274
|
+
type: "text",
|
|
275
|
+
content: raw.slice(0, 10000),
|
|
276
|
+
});
|
|
277
|
+
} catch {
|
|
278
|
+
extractedContext.push({
|
|
279
|
+
path: full,
|
|
280
|
+
type: "error",
|
|
281
|
+
content: `[Could not read: ${full}]`,
|
|
282
|
+
});
|
|
52
283
|
}
|
|
53
284
|
}
|
|
54
|
-
} catch (e) {
|
|
55
|
-
// ignore
|
|
56
285
|
}
|
|
57
286
|
}
|
|
58
|
-
remainingWords.push(word);
|
|
59
287
|
}
|
|
60
288
|
|
|
61
|
-
|
|
289
|
+
for (const p of paths) {
|
|
290
|
+
const stat = fs.statSync(p);
|
|
291
|
+
if (stat.isDirectory()) {
|
|
292
|
+
readDir(p);
|
|
293
|
+
} else if (stat.isFile()) {
|
|
294
|
+
visited.add(p);
|
|
295
|
+
if (isBinary(p)) {
|
|
296
|
+
extractedContext.push({
|
|
297
|
+
path: p,
|
|
298
|
+
type: "binary",
|
|
299
|
+
content: `[Binary file: ${path.basename(p)}]`,
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
try {
|
|
303
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
304
|
+
extractedContext.push({
|
|
305
|
+
path: p,
|
|
306
|
+
type: "text",
|
|
307
|
+
content: raw.slice(0, 10000),
|
|
308
|
+
});
|
|
309
|
+
} catch {
|
|
310
|
+
extractedContext.push({
|
|
311
|
+
path: p,
|
|
312
|
+
type: "error",
|
|
313
|
+
content: `[Could not read: ${p}]`,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
cleanPrompt: nonPaths.join(" "),
|
|
322
|
+
extractedContext,
|
|
323
|
+
};
|
|
62
324
|
}
|
|
63
325
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (fs.existsSync(
|
|
70
|
-
|
|
326
|
+
/* ═══════════════════════════════════════════
|
|
327
|
+
loadSkillContext(skillName)
|
|
328
|
+
═══════════════════════════════════════════ */
|
|
329
|
+
export function loadSkillContext(skillName) {
|
|
330
|
+
const promptPath = path.join(getSkillsDir(), skillName, "prompt.md");
|
|
331
|
+
if (fs.existsSync(promptPath)) {
|
|
332
|
+
try {
|
|
333
|
+
return fs.readFileSync(promptPath, "utf-8");
|
|
334
|
+
} catch {
|
|
335
|
+
return `You are an AI assistant specialized in "${skillName}". Follow best practices and produce clean, production-ready output.`;
|
|
336
|
+
}
|
|
71
337
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
338
|
+
// dynamic generic prompt
|
|
339
|
+
return [
|
|
340
|
+
`You are an AI coding agent with the "${skillName}" skill activated.`,
|
|
341
|
+
`Focus all responses on the domain of "${skillName}".`,
|
|
342
|
+
`Provide expert-level guidance, code, and explanations.`,
|
|
343
|
+
`Always produce complete, working implementations.`,
|
|
344
|
+
].join("\n");
|
|
75
345
|
}
|
|
76
346
|
|
|
347
|
+
/* ═══════════════════════════════════════════
|
|
348
|
+
askAgent(promptText, extraContext)
|
|
349
|
+
═══════════════════════════════════════════ */
|
|
77
350
|
export async function askAgent(promptText, extraContext = "") {
|
|
351
|
+
const chalk = (await import("chalk")).default;
|
|
352
|
+
const ora = (await import("ora")).default;
|
|
353
|
+
|
|
78
354
|
const token = getToken();
|
|
79
355
|
if (!token) {
|
|
80
|
-
|
|
356
|
+
printError(
|
|
357
|
+
"Not authenticated. Run " +
|
|
358
|
+
chalk.cyan("rg login") +
|
|
359
|
+
" first."
|
|
360
|
+
);
|
|
361
|
+
return null;
|
|
81
362
|
}
|
|
82
363
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
364
|
+
// Build final prompt
|
|
365
|
+
const state = getSessionState();
|
|
366
|
+
const parts = [promptText];
|
|
367
|
+
if (extraContext) parts.push(`\n---\nContext:\n${extraContext}`);
|
|
368
|
+
|
|
369
|
+
// Attach file context from state
|
|
370
|
+
if (state.attachedFiles.size > 0) {
|
|
371
|
+
const fileCtx = [];
|
|
372
|
+
for (const fp of state.attachedFiles) {
|
|
373
|
+
if (fs.existsSync(fp)) {
|
|
374
|
+
if (isBinary(fp)) {
|
|
375
|
+
fileCtx.push(`[Binary: ${path.basename(fp)}]`);
|
|
376
|
+
} else {
|
|
377
|
+
try {
|
|
378
|
+
const c = fs.readFileSync(fp, "utf-8").slice(0, 10000);
|
|
379
|
+
fileCtx.push(`--- ${fp} ---\n${c}\n---`);
|
|
380
|
+
} catch {
|
|
381
|
+
fileCtx.push(`[Unreadable: ${fp}]`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (fileCtx.length) {
|
|
387
|
+
parts.push(`\n---\nAttached Files:\n${fileCtx.join("\n\n")}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Active skill context
|
|
392
|
+
if (state.activeSkill) {
|
|
393
|
+
const skillCtx = loadSkillContext(state.activeSkill);
|
|
394
|
+
parts.push(`\n---\nActive Skill (${state.activeSkill}):\n${skillCtx}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Image context
|
|
398
|
+
if (state.imageAttached) {
|
|
399
|
+
parts.push(`\n---\n[Image attached: ${state.imageAttached}]`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Reasoning effort
|
|
403
|
+
if (state.reasoningEffort && state.reasoningEffort !== "medium") {
|
|
404
|
+
parts.push(`\n[Reasoning effort: ${state.reasoningEffort}]`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const finalPrompt = parts.join("\n");
|
|
408
|
+
|
|
409
|
+
const spinner = ora({ text: "Thinking...", spinner: "dots12" }).start();
|
|
90
410
|
|
|
91
411
|
try {
|
|
92
|
-
const finalPrompt = extraContext ? `[CONTEXT: ${extraContext}]\n${promptText}` : promptText;
|
|
93
|
-
|
|
94
|
-
// We pass the token via Bearer, and Google-Access-Token header for OAuth natively
|
|
95
412
|
const headers = {
|
|
96
413
|
"Content-Type": "application/json",
|
|
97
|
-
|
|
414
|
+
Authorization: `Bearer ${token}`,
|
|
98
415
|
};
|
|
99
416
|
|
|
100
|
-
//
|
|
417
|
+
// Google access token detection (OAuth tokens are long)
|
|
101
418
|
if (token.length > 100) {
|
|
102
419
|
headers["Google-Access-Token"] = token;
|
|
103
420
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
headers["X-Model-Override"] = modelPref;
|
|
421
|
+
|
|
422
|
+
const model = getModel();
|
|
423
|
+
if (model && model !== "default") {
|
|
424
|
+
headers["X-Model-Override"] = model;
|
|
109
425
|
}
|
|
110
426
|
|
|
111
|
-
const
|
|
427
|
+
const apiUrl = getApiUrl();
|
|
428
|
+
const resp = await fetch(apiUrl, {
|
|
112
429
|
method: "POST",
|
|
113
430
|
headers,
|
|
114
431
|
body: JSON.stringify({ prompt: finalPrompt }),
|
|
115
432
|
});
|
|
116
433
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
434
|
+
const rawText = await resp.text();
|
|
435
|
+
|
|
436
|
+
// Check for HTML error pages
|
|
437
|
+
if (rawText.toLowerCase().includes("<html")) {
|
|
438
|
+
spinner.stop();
|
|
439
|
+
printError(
|
|
440
|
+
"Endpoint not available. The server returned an HTML page instead of JSON."
|
|
441
|
+
);
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let data;
|
|
446
|
+
try {
|
|
447
|
+
data = JSON.parse(rawText);
|
|
448
|
+
} catch {
|
|
449
|
+
spinner.stop();
|
|
450
|
+
printError("Invalid response from backend. Could not parse JSON.");
|
|
451
|
+
if (rawText.length < 500) {
|
|
452
|
+
console.log(_chalkSync().gray(`Raw response: ${rawText.slice(0, 300)}`));
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!resp.ok) {
|
|
458
|
+
spinner.stop();
|
|
459
|
+
const errMsg =
|
|
460
|
+
data.error || data.message || data.msg || `HTTP ${resp.status}`;
|
|
461
|
+
printError(`Backend error: ${errMsg}`);
|
|
462
|
+
return null;
|
|
120
463
|
}
|
|
121
464
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
console.log(
|
|
465
|
+
spinner.stop();
|
|
466
|
+
|
|
467
|
+
// Render the response message
|
|
468
|
+
const message = data.message || data.response || data.text || data.content || "";
|
|
469
|
+
if (message) {
|
|
470
|
+
console.log("");
|
|
471
|
+
renderMarkdown(message);
|
|
472
|
+
console.log("");
|
|
128
473
|
}
|
|
129
474
|
|
|
130
|
-
|
|
475
|
+
// Execute any actions the agent returned
|
|
476
|
+
if (Array.isArray(data.actions)) {
|
|
131
477
|
for (const action of data.actions) {
|
|
132
478
|
await executeAction(action);
|
|
133
479
|
}
|
|
134
|
-
printSuccess("All actions executed successfully!");
|
|
135
480
|
}
|
|
136
481
|
|
|
137
|
-
|
|
482
|
+
// Save to conversation history
|
|
483
|
+
addToHistory("user", promptText);
|
|
484
|
+
addToHistory("assistant", message);
|
|
485
|
+
|
|
486
|
+
return data;
|
|
487
|
+
} catch (err) {
|
|
138
488
|
spinner.stop();
|
|
139
|
-
|
|
489
|
+
if (err.code === "ECONNREFUSED") {
|
|
490
|
+
printError(
|
|
491
|
+
"Cannot connect to the RafayGen backend. Is the server running?"
|
|
492
|
+
);
|
|
493
|
+
} else if (err.code === "ENOTFOUND") {
|
|
494
|
+
printError("DNS resolution failed. Check your API URL.");
|
|
495
|
+
} else {
|
|
496
|
+
printError(`Request failed: ${err.message}`);
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
140
499
|
}
|
|
141
500
|
}
|
|
142
501
|
|
|
502
|
+
/* ═══════════════════════════════════════════
|
|
503
|
+
MCP helpers
|
|
504
|
+
═══════════════════════════════════════════ */
|
|
505
|
+
function loadMcpConfig() {
|
|
506
|
+
const p = getMcpConfigPath();
|
|
507
|
+
if (fs.existsSync(p)) {
|
|
508
|
+
try {
|
|
509
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
510
|
+
} catch {
|
|
511
|
+
return { servers: [] };
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { servers: [] };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function saveMcpConfig(config) {
|
|
518
|
+
const p = getMcpConfigPath();
|
|
519
|
+
fs.writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* ═══════════════════════════════════════════
|
|
523
|
+
startInteractiveLoop()
|
|
524
|
+
═══════════════════════════════════════════ */
|
|
143
525
|
export async function startInteractiveLoop() {
|
|
144
|
-
const inquirer = (await import("inquirer")).default;
|
|
145
526
|
const chalk = (await import("chalk")).default;
|
|
527
|
+
const inquirer = (await import("inquirer")).default;
|
|
528
|
+
_chalkCache = chalk;
|
|
146
529
|
|
|
147
|
-
|
|
530
|
+
// ── Welcome ──
|
|
531
|
+
printAsciiLogo();
|
|
532
|
+
printRandomWelcome();
|
|
533
|
+
const state = getSessionState();
|
|
534
|
+
console.log(
|
|
535
|
+
chalk.gray(
|
|
536
|
+
` Session: ${state.sessionId.slice(0, 8)}... | Model: ${getModel()} | Sandbox: ${state.sandboxMode}`
|
|
537
|
+
)
|
|
538
|
+
);
|
|
539
|
+
console.log(
|
|
540
|
+
chalk.gray(` Type ${chalk.white("/help")} for commands, or just start chatting.\n`)
|
|
541
|
+
);
|
|
148
542
|
|
|
543
|
+
// ── REPL ──
|
|
149
544
|
while (true) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
545
|
+
let input;
|
|
546
|
+
try {
|
|
547
|
+
const answers = await inquirer.prompt([
|
|
548
|
+
{
|
|
549
|
+
type: "input",
|
|
550
|
+
name: "input",
|
|
551
|
+
message: chalk.greenBright("❯"),
|
|
552
|
+
prefix: "",
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
input = (answers.input || "").trim();
|
|
556
|
+
} catch {
|
|
557
|
+
// Ctrl+C or EOF
|
|
558
|
+
saveSession();
|
|
559
|
+
console.log(chalk.gray("\nSession saved. Goodbye!\n"));
|
|
560
|
+
process.exit(0);
|
|
561
|
+
}
|
|
158
562
|
|
|
159
|
-
const input = promptText.trim();
|
|
160
563
|
if (!input) continue;
|
|
161
564
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
565
|
+
/* ─────────────────────────────────────
|
|
566
|
+
SLASH COMMANDS
|
|
567
|
+
───────────────────────────────────── */
|
|
568
|
+
|
|
569
|
+
// /exit, /quit
|
|
570
|
+
if (input === "/exit" || input === "/quit") {
|
|
571
|
+
saveSession();
|
|
572
|
+
console.log(chalk.cyan("\n 💾 Session saved. See you next time!\n"));
|
|
165
573
|
process.exit(0);
|
|
166
|
-
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// /clear
|
|
577
|
+
if (input === "/clear") {
|
|
167
578
|
console.clear();
|
|
168
579
|
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
skills
|
|
191
|
-
|
|
192
|
-
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// / (command palette)
|
|
583
|
+
if (input === "/") {
|
|
584
|
+
const commands = [
|
|
585
|
+
{ name: "📋 /help — Show all commands", value: "/help" },
|
|
586
|
+
{ name: "🤖 /models — Switch AI model", value: "/models" },
|
|
587
|
+
{ name: "📊 /status — Show session status", value: "/status" },
|
|
588
|
+
{ name: "📝 /history — View conversation history", value: "/history" },
|
|
589
|
+
{ name: "🔀 /fork — Fork current session", value: "/fork" },
|
|
590
|
+
{ name: "⏪ /resume — Resume a saved session", value: "/resume" },
|
|
591
|
+
{ name: "📦 /compact — Toggle compact mode", value: "/compact" },
|
|
592
|
+
{ name: "🔍 /review — Review code in CWD", value: "/review" },
|
|
593
|
+
{ name: "📄 /diff — Show git diff", value: "/diff" },
|
|
594
|
+
{ name: "📎 /attach — Attach file/folder", value: "/attach" },
|
|
595
|
+
{ name: "🗑 /detach — Clear attachments", value: "/detach" },
|
|
596
|
+
{ name: "🌐 /web — Web search", value: "/web" },
|
|
597
|
+
{ name: "🖼 /vision — Attach image", value: "/vision" },
|
|
598
|
+
{ name: "🔎 /search — Search in CWD", value: "/search" },
|
|
599
|
+
{ name: "🔒 /sandbox — Set sandbox mode", value: "/sandbox" },
|
|
600
|
+
{ name: "✅ /approvals — Set approval mode", value: "/approvals" },
|
|
601
|
+
{ name: "🧩 /skills — List skills", value: "/skills" },
|
|
602
|
+
{ name: "🔌 /mcp — MCP servers", value: "/mcp" },
|
|
603
|
+
{ name: "🧠 /reasoning — Set reasoning effort", value: "/reasoning" },
|
|
604
|
+
{ name: "🚪 /exit — Save & exit", value: "/exit" },
|
|
605
|
+
];
|
|
606
|
+
const { cmd } = await inquirer.prompt([
|
|
607
|
+
{
|
|
608
|
+
type: "list",
|
|
609
|
+
name: "cmd",
|
|
610
|
+
message: "Command Palette",
|
|
611
|
+
choices: commands,
|
|
612
|
+
pageSize: 20,
|
|
613
|
+
},
|
|
614
|
+
]);
|
|
615
|
+
// Re-process the selected command
|
|
616
|
+
input = cmd;
|
|
617
|
+
if (input === "/exit") {
|
|
618
|
+
saveSession();
|
|
619
|
+
console.log(chalk.cyan("\n 💾 Session saved. See you next time!\n"));
|
|
620
|
+
process.exit(0);
|
|
193
621
|
}
|
|
194
|
-
|
|
622
|
+
// fall through to handle below
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// /help
|
|
626
|
+
if (input === "/help") {
|
|
627
|
+
const helpText = [
|
|
628
|
+
chalk.cyan.bold.underline(" RafayGen CLI — Command Reference\n"),
|
|
629
|
+
chalk.yellow.bold(" Basic:"),
|
|
630
|
+
` ${chalk.white("/help")} Show this help`,
|
|
631
|
+
` ${chalk.white("/clear")} Clear console`,
|
|
632
|
+
` ${chalk.white("/exit, /quit")} Save session & exit`,
|
|
633
|
+
"",
|
|
634
|
+
chalk.yellow.bold(" Model Management:"),
|
|
635
|
+
` ${chalk.white("/models, /model")} Pick an AI model`,
|
|
636
|
+
"",
|
|
637
|
+
chalk.yellow.bold(" Session Management:"),
|
|
638
|
+
` ${chalk.white("/status")} Show full session state`,
|
|
639
|
+
` ${chalk.white("/compact")} Toggle compact output mode`,
|
|
640
|
+
` ${chalk.white("/fork")} Fork session with same history`,
|
|
641
|
+
` ${chalk.white("/resume")} Resume a saved session`,
|
|
642
|
+
` ${chalk.white("/history")} Show last 10 conversation turns`,
|
|
643
|
+
"",
|
|
644
|
+
chalk.yellow.bold(" Code Workflows:"),
|
|
645
|
+
` ${chalk.white("/review")} Ask agent to review code in CWD`,
|
|
646
|
+
` ${chalk.white("/diff")} Show git diff of CWD`,
|
|
647
|
+
` ${chalk.white("/attach <path>")} Attach file or folder to context`,
|
|
648
|
+
` ${chalk.white("/detach")} Clear all attached files`,
|
|
649
|
+
"",
|
|
650
|
+
chalk.yellow.bold(" File Operations:"),
|
|
651
|
+
` ${chalk.white("/web <query>")} Force web search context`,
|
|
652
|
+
` ${chalk.white("/vision <path>")} Attach image to context`,
|
|
653
|
+
` ${chalk.white("/search <query>")} Grep search in CWD`,
|
|
654
|
+
"",
|
|
655
|
+
chalk.yellow.bold(" Sandbox & Approvals:"),
|
|
656
|
+
` ${chalk.white("/sandbox [mode]")} Set sandbox (read-only|workspace-write|danger-full-access)`,
|
|
657
|
+
` ${chalk.white("/approvals [mode]")} Set approval (suggest|auto-edit|full-auto|never)`,
|
|
658
|
+
"",
|
|
659
|
+
chalk.yellow.bold(" Skills:"),
|
|
660
|
+
` ${chalk.white("/skills")} List installed & built-in skills`,
|
|
661
|
+
` ${chalk.white("/skill install <name>")} Install a new skill`,
|
|
662
|
+
` ${chalk.white("/skill remove <name>")} Remove a skill`,
|
|
663
|
+
` ${chalk.white("/skill enable <name>")} Set active skill`,
|
|
664
|
+
` ${chalk.white("/skill disable <name>")} Deactivate skill`,
|
|
665
|
+
` ${chalk.white("$<skill> <prompt>")} Run prompt with skill context`,
|
|
666
|
+
"",
|
|
667
|
+
chalk.yellow.bold(" MCP (Model Context Protocol):"),
|
|
668
|
+
` ${chalk.white("/mcp")} List MCP servers`,
|
|
669
|
+
` ${chalk.white("/mcp add")} Add an MCP server`,
|
|
670
|
+
` ${chalk.white("/mcp remove <name>")} Remove an MCP server`,
|
|
671
|
+
` ${chalk.white("/mcp inspect <name>")} Inspect MCP server config`,
|
|
672
|
+
"",
|
|
673
|
+
chalk.yellow.bold(" Agent Reasoning:"),
|
|
674
|
+
` ${chalk.white("/reasoning <level>")} Set reasoning effort (low|medium|high)`,
|
|
675
|
+
"",
|
|
676
|
+
chalk.yellow.bold(" Tips:"),
|
|
677
|
+
` ${chalk.gray("•")} Drag & drop files into the prompt to auto-attach context`,
|
|
678
|
+
` ${chalk.gray("•")} Type ${chalk.white("/")} alone to open the command palette`,
|
|
679
|
+
"",
|
|
680
|
+
].join("\n");
|
|
681
|
+
console.log(helpText);
|
|
195
682
|
continue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// /models, /model
|
|
686
|
+
if (input === "/models" || input === "/model") {
|
|
687
|
+
const modelChoices = [
|
|
688
|
+
{ name: "Google Gemini (Default)", value: "default" },
|
|
689
|
+
{ name: "Groq — Llama 3.1 70B", value: "groq-llama-3.1-70b" },
|
|
690
|
+
{ name: "Mistral — Large", value: "mistral-large" },
|
|
691
|
+
{ name: "DeepSeek — Coder", value: "deepseek-coder" },
|
|
692
|
+
{ name: "Qwen — Max", value: "qwen-max" },
|
|
693
|
+
{ name: "Ollama — Cloud", value: "ollama-cloud" },
|
|
694
|
+
{ name: "MuleRouter — Auto", value: "mulerouter-auto" },
|
|
695
|
+
{ name: "HuggingFace — Zephyr", value: "huggingface-zephyr" },
|
|
696
|
+
];
|
|
697
|
+
const { selectedModel } = await inquirer.prompt([
|
|
698
|
+
{
|
|
699
|
+
type: "list",
|
|
700
|
+
name: "selectedModel",
|
|
701
|
+
message: "Select an AI model:",
|
|
702
|
+
choices: modelChoices,
|
|
703
|
+
},
|
|
704
|
+
]);
|
|
705
|
+
setModel(selectedModel);
|
|
706
|
+
printModelBadge(
|
|
707
|
+
modelChoices.find((m) => m.value === selectedModel)?.name ||
|
|
708
|
+
selectedModel
|
|
709
|
+
);
|
|
710
|
+
printSuccess(`Model switched to ${selectedModel}`);
|
|
201
711
|
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// /status
|
|
715
|
+
if (input === "/status") {
|
|
716
|
+
printSessionStatus(getSessionState());
|
|
206
717
|
continue;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
console.log("");
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// /compact
|
|
721
|
+
if (input === "/compact") {
|
|
722
|
+
const cur = getSessionState();
|
|
723
|
+
updateSessionState({ compactMode: !cur.compactMode });
|
|
724
|
+
const newVal = getSessionState().compactMode;
|
|
725
|
+
printSuccess(`Compact mode ${newVal ? "ENABLED" : "DISABLED"}`);
|
|
216
726
|
continue;
|
|
217
|
-
}
|
|
218
|
-
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// /fork
|
|
730
|
+
if (input === "/fork") {
|
|
731
|
+
saveSession();
|
|
732
|
+
const oldId = getSessionState().sessionId;
|
|
733
|
+
const newId = crypto.randomUUID();
|
|
734
|
+
updateSessionState({ sessionId: newId });
|
|
735
|
+
saveSession();
|
|
736
|
+
console.log(chalk.green(`\n 🔀 Session forked!`));
|
|
737
|
+
console.log(chalk.gray(` Old: ${oldId}`));
|
|
738
|
+
console.log(chalk.cyan(` New: ${newId}\n`));
|
|
219
739
|
continue;
|
|
220
|
-
}
|
|
221
|
-
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// /resume
|
|
743
|
+
if (input === "/resume") {
|
|
744
|
+
const sessions = listSessions();
|
|
745
|
+
if (sessions.length === 0) {
|
|
746
|
+
printWarning("No saved sessions found.");
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const choices = sessions.map((s) => ({
|
|
750
|
+
name: `${s.sessionId.slice(0, 8)}... | ${s.messageCount} msgs | ${s.savedAt || "unknown"}`,
|
|
751
|
+
value: s.sessionId,
|
|
752
|
+
}));
|
|
753
|
+
const { sessionId } = await inquirer.prompt([
|
|
222
754
|
{
|
|
223
755
|
type: "list",
|
|
224
|
-
name: "
|
|
225
|
-
message: "Select
|
|
226
|
-
choices
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{ name: "Mistral (Large)", value: "mistral" },
|
|
230
|
-
{ name: "DeepSeek (Coder)", value: "deepseek" },
|
|
231
|
-
{ name: "Qwen (Max)", value: "qwen" },
|
|
232
|
-
{ name: "Ollama (Cloud)", value: "ollama" },
|
|
233
|
-
{ name: "MuleRouter (Auto)", value: "mulerouter" }
|
|
234
|
-
]
|
|
235
|
-
}
|
|
756
|
+
name: "sessionId",
|
|
757
|
+
message: "Select a session to resume:",
|
|
758
|
+
choices,
|
|
759
|
+
pageSize: 15,
|
|
760
|
+
},
|
|
236
761
|
]);
|
|
237
|
-
|
|
238
|
-
|
|
762
|
+
if (loadSession(sessionId)) {
|
|
763
|
+
printSuccess(`Resumed session ${sessionId.slice(0, 8)}...`);
|
|
764
|
+
printSessionStatus(getSessionState());
|
|
765
|
+
} else {
|
|
766
|
+
printError(`Failed to load session ${sessionId}`);
|
|
767
|
+
}
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// /history
|
|
772
|
+
if (input === "/history") {
|
|
773
|
+
const hist = getSessionState().conversationHistory;
|
|
774
|
+
if (hist.length === 0) {
|
|
775
|
+
printInfo("No conversation history yet.");
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const last10 = hist.slice(-10);
|
|
779
|
+
console.log(chalk.cyan.bold("\n 📝 Conversation History (last 10):\n"));
|
|
780
|
+
for (const entry of last10) {
|
|
781
|
+
const role =
|
|
782
|
+
entry.role === "user"
|
|
783
|
+
? chalk.greenBright.bold(" YOU: ")
|
|
784
|
+
: entry.role === "assistant"
|
|
785
|
+
? chalk.magentaBright.bold(" AI: ")
|
|
786
|
+
: chalk.gray.bold(" SYS: ");
|
|
787
|
+
const ts = entry.timestamp
|
|
788
|
+
? chalk.gray(` [${new Date(entry.timestamp).toLocaleTimeString()}]`)
|
|
789
|
+
: "";
|
|
790
|
+
const content =
|
|
791
|
+
entry.content.length > 200
|
|
792
|
+
? entry.content.slice(0, 200) + "..."
|
|
793
|
+
: entry.content;
|
|
794
|
+
console.log(role + content + ts);
|
|
795
|
+
console.log(chalk.gray(" " + "─".repeat(60)));
|
|
796
|
+
}
|
|
797
|
+
console.log("");
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// /review
|
|
802
|
+
if (input === "/review") {
|
|
803
|
+
const cwd = getSessionState().cwd;
|
|
804
|
+
printStep(`Scanning ${cwd} for code review...`);
|
|
805
|
+
const { extractedContext } = extractFileContext(cwd);
|
|
806
|
+
const contextStr = extractedContext
|
|
807
|
+
.filter((f) => f.type === "text")
|
|
808
|
+
.map((f) => `--- ${f.path} ---\n${f.content}`)
|
|
809
|
+
.join("\n\n");
|
|
810
|
+
if (!contextStr) {
|
|
811
|
+
printWarning("No readable files found in current directory.");
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
await askAgent(
|
|
815
|
+
"Please review the following code. Identify bugs, security issues, performance problems, and suggest improvements.",
|
|
816
|
+
contextStr.slice(0, 50000)
|
|
817
|
+
);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// /diff
|
|
822
|
+
if (input === "/diff") {
|
|
823
|
+
const cwd = getSessionState().cwd;
|
|
824
|
+
try {
|
|
825
|
+
const diffOutput = execSync("git diff", {
|
|
826
|
+
cwd,
|
|
827
|
+
encoding: "utf-8",
|
|
828
|
+
timeout: 10000,
|
|
829
|
+
});
|
|
830
|
+
if (diffOutput.trim()) {
|
|
831
|
+
renderBox(" Git Diff ", diffOutput, "yellow");
|
|
832
|
+
} else {
|
|
833
|
+
printInfo("No uncommitted changes found.");
|
|
834
|
+
}
|
|
835
|
+
} catch (err) {
|
|
836
|
+
if (err.message.includes("not a git repository")) {
|
|
837
|
+
printError("Not a git repository.");
|
|
838
|
+
} else {
|
|
839
|
+
printError(`git diff failed: ${err.message}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// /attach <path>
|
|
846
|
+
if (input.startsWith("/attach")) {
|
|
847
|
+
const arg = input.slice(7).trim();
|
|
848
|
+
if (!arg) {
|
|
849
|
+
const { filePath } = await inquirer.prompt([
|
|
850
|
+
{
|
|
851
|
+
type: "input",
|
|
852
|
+
name: "filePath",
|
|
853
|
+
message: "Path to attach:",
|
|
854
|
+
},
|
|
855
|
+
]);
|
|
856
|
+
if (filePath.trim()) {
|
|
857
|
+
const resolved = path.isAbsolute(filePath.trim())
|
|
858
|
+
? filePath.trim()
|
|
859
|
+
: path.resolve(process.cwd(), filePath.trim());
|
|
860
|
+
if (fs.existsSync(resolved)) {
|
|
861
|
+
const cur = getSessionState();
|
|
862
|
+
cur.attachedFiles.add(resolved);
|
|
863
|
+
updateSessionState({ attachedFiles: cur.attachedFiles });
|
|
864
|
+
printSuccess(`Attached: ${resolved}`);
|
|
865
|
+
} else {
|
|
866
|
+
printError(`Path not found: ${filePath.trim()}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
const resolved = path.isAbsolute(arg)
|
|
871
|
+
? arg
|
|
872
|
+
: path.resolve(process.cwd(), arg);
|
|
873
|
+
if (fs.existsSync(resolved)) {
|
|
874
|
+
const cur = getSessionState();
|
|
875
|
+
cur.attachedFiles.add(resolved);
|
|
876
|
+
updateSessionState({ attachedFiles: cur.attachedFiles });
|
|
877
|
+
printSuccess(`Attached: ${resolved}`);
|
|
878
|
+
} else {
|
|
879
|
+
printError(`Path not found: ${arg}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// /detach
|
|
886
|
+
if (input === "/detach") {
|
|
887
|
+
updateSessionState({ attachedFiles: new Set() });
|
|
888
|
+
printSuccess("All attached files cleared.");
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// /web <query>
|
|
893
|
+
if (input.startsWith("/web")) {
|
|
894
|
+
const query = input.slice(4).trim();
|
|
895
|
+
if (!query) {
|
|
896
|
+
printWarning("Usage: /web <search query>");
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
await askAgent(
|
|
900
|
+
`Perform a web search for: "${query}" and summarize the results.`,
|
|
901
|
+
`[Web search context requested for: ${query}]`
|
|
902
|
+
);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// /vision <path>
|
|
907
|
+
if (input.startsWith("/vision")) {
|
|
908
|
+
const imgPath = input.slice(7).trim();
|
|
909
|
+
if (!imgPath) {
|
|
910
|
+
printWarning("Usage: /vision <image-path>");
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const resolved = path.isAbsolute(imgPath)
|
|
914
|
+
? imgPath
|
|
915
|
+
: path.resolve(process.cwd(), imgPath);
|
|
916
|
+
if (!fs.existsSync(resolved)) {
|
|
917
|
+
printError(`Image not found: ${resolved}`);
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
updateSessionState({ imageAttached: resolved });
|
|
921
|
+
printSuccess(`Image attached: ${resolved}`);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// /search <query>
|
|
926
|
+
if (input.startsWith("/search")) {
|
|
927
|
+
const query = input.slice(7).trim();
|
|
928
|
+
if (!query) {
|
|
929
|
+
printWarning("Usage: /search <query>");
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
const cwd = getSessionState().cwd;
|
|
933
|
+
try {
|
|
934
|
+
let result;
|
|
935
|
+
try {
|
|
936
|
+
result = execSync(
|
|
937
|
+
`grep -rnI --include="*.{js,ts,py,json,md,jsx,tsx,css,html,yaml,yml,toml,go,rs,java,c,cpp,h}" "${query}" .`,
|
|
938
|
+
{ cwd, encoding: "utf-8", timeout: 15000 }
|
|
939
|
+
);
|
|
940
|
+
} catch {
|
|
941
|
+
// fallback to simple grep
|
|
942
|
+
result = execSync(`grep -rnI "${query}" . 2>/dev/null | head -50`, {
|
|
943
|
+
cwd,
|
|
944
|
+
encoding: "utf-8",
|
|
945
|
+
timeout: 15000,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if (result.trim()) {
|
|
949
|
+
renderBox(` Search: "${query}" `, result.trim().slice(0, 5000), "green");
|
|
950
|
+
} else {
|
|
951
|
+
printInfo(`No results found for "${query}".`);
|
|
952
|
+
}
|
|
953
|
+
} catch {
|
|
954
|
+
printInfo(`No results found for "${query}".`);
|
|
955
|
+
}
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// /sandbox [mode]
|
|
960
|
+
if (input.startsWith("/sandbox")) {
|
|
961
|
+
const arg = input.slice(8).trim();
|
|
962
|
+
const validModes = ["read-only", "workspace-write", "danger-full-access"];
|
|
963
|
+
if (arg && validModes.includes(arg)) {
|
|
964
|
+
updateSessionState({ sandboxMode: arg });
|
|
965
|
+
printSuccess(`Sandbox mode set to: ${arg}`);
|
|
966
|
+
} else {
|
|
967
|
+
const { mode } = await inquirer.prompt([
|
|
968
|
+
{
|
|
969
|
+
type: "list",
|
|
970
|
+
name: "mode",
|
|
971
|
+
message: "Select sandbox mode:",
|
|
972
|
+
choices: [
|
|
973
|
+
{ name: "🔒 Read-Only — No file writes allowed", value: "read-only" },
|
|
974
|
+
{ name: "📝 Workspace Write — Write to project files", value: "workspace-write" },
|
|
975
|
+
{ name: "⚠️ Danger Full Access — Full system access", value: "danger-full-access" },
|
|
976
|
+
],
|
|
977
|
+
},
|
|
978
|
+
]);
|
|
979
|
+
updateSessionState({ sandboxMode: mode });
|
|
980
|
+
printSuccess(`Sandbox mode set to: ${mode}`);
|
|
981
|
+
}
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// /approvals [mode]
|
|
986
|
+
if (input.startsWith("/approvals")) {
|
|
987
|
+
const arg = input.slice(10).trim();
|
|
988
|
+
const validModes = ["suggest", "auto-edit", "full-auto", "never"];
|
|
989
|
+
if (arg && validModes.includes(arg)) {
|
|
990
|
+
updateSessionState({ approvalMode: arg });
|
|
991
|
+
printSuccess(`Approval mode set to: ${arg}`);
|
|
992
|
+
} else {
|
|
993
|
+
const { mode } = await inquirer.prompt([
|
|
994
|
+
{
|
|
995
|
+
type: "list",
|
|
996
|
+
name: "mode",
|
|
997
|
+
message: "Select approval mode:",
|
|
998
|
+
choices: [
|
|
999
|
+
{ name: "💬 Suggest — Show diffs, ask before writing", value: "suggest" },
|
|
1000
|
+
{ name: "✏️ Auto-Edit — Auto-apply edits, ask for commands", value: "auto-edit" },
|
|
1001
|
+
{ name: "🚀 Full-Auto — Apply everything automatically", value: "full-auto" },
|
|
1002
|
+
{ name: "🚫 Never — Block all actions", value: "never" },
|
|
1003
|
+
],
|
|
1004
|
+
},
|
|
1005
|
+
]);
|
|
1006
|
+
updateSessionState({ approvalMode: mode });
|
|
1007
|
+
printSuccess(`Approval mode set to: ${mode}`);
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// /skills
|
|
1013
|
+
if (input === "/skills") {
|
|
1014
|
+
const skillsDir = getSkillsDir();
|
|
1015
|
+
let installed = [];
|
|
1016
|
+
try {
|
|
1017
|
+
installed = fs
|
|
1018
|
+
.readdirSync(skillsDir, { withFileTypes: true })
|
|
1019
|
+
.filter((d) => d.isDirectory())
|
|
1020
|
+
.map((d) => d.name);
|
|
1021
|
+
} catch {}
|
|
1022
|
+
|
|
1023
|
+
const builtIn = [
|
|
1024
|
+
"code-review",
|
|
1025
|
+
"refactor",
|
|
1026
|
+
"test-gen",
|
|
1027
|
+
"docs-gen",
|
|
1028
|
+
"debug",
|
|
1029
|
+
"optimize",
|
|
1030
|
+
"security-audit",
|
|
1031
|
+
"api-design",
|
|
1032
|
+
"database-schema",
|
|
1033
|
+
"devops",
|
|
1034
|
+
];
|
|
1035
|
+
|
|
1036
|
+
console.log(chalk.cyan.bold("\n 🧩 Installed Skills:\n"));
|
|
1037
|
+
if (installed.length === 0) {
|
|
1038
|
+
console.log(chalk.gray(" (none installed)\n"));
|
|
1039
|
+
} else {
|
|
1040
|
+
for (const s of installed) {
|
|
1041
|
+
const active =
|
|
1042
|
+
getSessionState().activeSkill === s ? chalk.green(" ● ACTIVE") : "";
|
|
1043
|
+
const hasPrompt = fs.existsSync(
|
|
1044
|
+
path.join(skillsDir, s, "prompt.md")
|
|
1045
|
+
);
|
|
1046
|
+
console.log(
|
|
1047
|
+
` ${chalk.white("•")} ${chalk.bold(s)}${active} ${
|
|
1048
|
+
hasPrompt ? chalk.gray("[prompt.md ✓]") : chalk.yellow("[no prompt]")
|
|
1049
|
+
}`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
console.log("");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
console.log(chalk.cyan.bold(" 📦 Built-in Skills:\n"));
|
|
1056
|
+
for (const s of builtIn) {
|
|
1057
|
+
console.log(` ${chalk.white("•")} ${chalk.gray(s)}`);
|
|
1058
|
+
}
|
|
1059
|
+
console.log(
|
|
1060
|
+
chalk.gray(
|
|
1061
|
+
`\n Use ${chalk.white("$skillname <prompt>")} to invoke a skill.\n`
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
239
1064
|
continue;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// /skill install|remove|enable|disable <name>
|
|
1068
|
+
if (input.startsWith("/skill ")) {
|
|
1069
|
+
const parts = input.slice(7).trim().split(/\s+/);
|
|
1070
|
+
const action = parts[0];
|
|
1071
|
+
const name = parts.slice(1).join(" ");
|
|
1072
|
+
|
|
1073
|
+
if (action === "install") {
|
|
1074
|
+
if (!name) {
|
|
1075
|
+
printWarning("Usage: /skill install <name>");
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
const skillDir = path.join(getSkillsDir(), name);
|
|
1079
|
+
if (fs.existsSync(skillDir)) {
|
|
1080
|
+
printWarning(`Skill "${name}" already exists.`);
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
1084
|
+
// Create prompt.md
|
|
1085
|
+
const promptContent = [
|
|
1086
|
+
`# ${name} Skill`,
|
|
1087
|
+
"",
|
|
1088
|
+
`You are an expert assistant specialized in "${name}".`,
|
|
1089
|
+
"",
|
|
1090
|
+
"## Instructions",
|
|
1091
|
+
"",
|
|
1092
|
+
`- Focus all responses on the "${name}" domain.`,
|
|
1093
|
+
"- Provide complete, working implementations.",
|
|
1094
|
+
"- Follow best practices and industry standards.",
|
|
1095
|
+
"- Include error handling and edge cases.",
|
|
1096
|
+
"- Add clear documentation and comments.",
|
|
1097
|
+
"",
|
|
1098
|
+
"## Output Format",
|
|
1099
|
+
"",
|
|
1100
|
+
"- Provide code in fenced code blocks with language tags.",
|
|
1101
|
+
"- Explain your reasoning before showing code.",
|
|
1102
|
+
"- Highlight any assumptions or trade-offs.",
|
|
1103
|
+
"",
|
|
1104
|
+
].join("\n");
|
|
1105
|
+
fs.writeFileSync(path.join(skillDir, "prompt.md"), promptContent, "utf-8");
|
|
1106
|
+
|
|
1107
|
+
// Create config.json
|
|
1108
|
+
const configContent = {
|
|
1109
|
+
name,
|
|
1110
|
+
version: "1.0.0",
|
|
1111
|
+
description: `Custom skill for ${name}`,
|
|
1112
|
+
author: os.userInfo().username,
|
|
1113
|
+
createdAt: new Date().toISOString(),
|
|
1114
|
+
tags: [name],
|
|
1115
|
+
};
|
|
1116
|
+
fs.writeFileSync(
|
|
1117
|
+
path.join(skillDir, "config.json"),
|
|
1118
|
+
JSON.stringify(configContent, null, 2),
|
|
1119
|
+
"utf-8"
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
printSuccess(
|
|
1123
|
+
`Skill "${name}" installed at ${skillDir}\n Edit ${path.join(skillDir, "prompt.md")} to customize.`
|
|
1124
|
+
);
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (action === "remove") {
|
|
1129
|
+
if (!name) {
|
|
1130
|
+
printWarning("Usage: /skill remove <name>");
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
const skillDir = path.join(getSkillsDir(), name);
|
|
1134
|
+
if (!fs.existsSync(skillDir)) {
|
|
1135
|
+
printError(`Skill "${name}" not found.`);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
1139
|
+
if (getSessionState().activeSkill === name) {
|
|
1140
|
+
updateSessionState({ activeSkill: null });
|
|
1141
|
+
}
|
|
1142
|
+
printSuccess(`Skill "${name}" removed.`);
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (action === "enable") {
|
|
1147
|
+
if (!name) {
|
|
1148
|
+
printWarning("Usage: /skill enable <name>");
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
updateSessionState({ activeSkill: name });
|
|
1152
|
+
printSuccess(`Skill "${name}" enabled as active skill.`);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (action === "disable") {
|
|
1157
|
+
if (!name) {
|
|
1158
|
+
printWarning("Usage: /skill disable <name>");
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (getSessionState().activeSkill === name) {
|
|
1162
|
+
updateSessionState({ activeSkill: null });
|
|
1163
|
+
printSuccess(`Skill "${name}" disabled.`);
|
|
1164
|
+
} else {
|
|
1165
|
+
printInfo(`Skill "${name}" was not active.`);
|
|
1166
|
+
}
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
printWarning(
|
|
1171
|
+
"Unknown skill command. Use: install, remove, enable, disable"
|
|
1172
|
+
);
|
|
244
1173
|
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// /mcp
|
|
1177
|
+
if (input === "/mcp" || input === "/mcp list") {
|
|
1178
|
+
const config = loadMcpConfig();
|
|
1179
|
+
const servers = config.servers || [];
|
|
1180
|
+
if (servers.length === 0) {
|
|
1181
|
+
printInfo("No MCP servers configured.");
|
|
1182
|
+
console.log(
|
|
1183
|
+
chalk.gray(` Use ${chalk.white("/mcp add")} to add one.\n`)
|
|
1184
|
+
);
|
|
1185
|
+
} else {
|
|
1186
|
+
console.log(chalk.cyan.bold("\n 🔌 MCP Servers:\n"));
|
|
1187
|
+
for (const s of servers) {
|
|
1188
|
+
console.log(
|
|
1189
|
+
` ${chalk.white("•")} ${chalk.bold(s.name)} — ${chalk.gray(s.url)} ${
|
|
1190
|
+
s.auth ? chalk.yellow("[auth]") : chalk.gray("[no auth]")
|
|
1191
|
+
}`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
console.log("");
|
|
1195
|
+
}
|
|
249
1196
|
continue;
|
|
250
1197
|
}
|
|
251
1198
|
|
|
252
|
-
//
|
|
253
|
-
|
|
1199
|
+
// /mcp add
|
|
1200
|
+
if (input === "/mcp add") {
|
|
1201
|
+
const answers = await inquirer.prompt([
|
|
1202
|
+
{
|
|
1203
|
+
type: "input",
|
|
1204
|
+
name: "name",
|
|
1205
|
+
message: "Server name:",
|
|
1206
|
+
validate: (v) => (v.trim() ? true : "Name is required"),
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
type: "input",
|
|
1210
|
+
name: "url",
|
|
1211
|
+
message: "Server URL:",
|
|
1212
|
+
validate: (v) => (v.trim() ? true : "URL is required"),
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
type: "input",
|
|
1216
|
+
name: "auth",
|
|
1217
|
+
message: "Auth token (leave empty for none):",
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
type: "input",
|
|
1221
|
+
name: "description",
|
|
1222
|
+
message: "Description (optional):",
|
|
1223
|
+
},
|
|
1224
|
+
]);
|
|
1225
|
+
const config = loadMcpConfig();
|
|
1226
|
+
config.servers = config.servers || [];
|
|
1227
|
+
config.servers.push({
|
|
1228
|
+
name: answers.name.trim(),
|
|
1229
|
+
url: answers.url.trim(),
|
|
1230
|
+
auth: answers.auth.trim() || null,
|
|
1231
|
+
description: answers.description.trim() || "",
|
|
1232
|
+
addedAt: new Date().toISOString(),
|
|
1233
|
+
});
|
|
1234
|
+
saveMcpConfig(config);
|
|
1235
|
+
printSuccess(`MCP server "${answers.name.trim()}" added.`);
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
254
1238
|
|
|
255
|
-
|
|
256
|
-
|
|
1239
|
+
// /mcp remove <name>
|
|
1240
|
+
if (input.startsWith("/mcp remove ")) {
|
|
1241
|
+
const name = input.slice(12).trim();
|
|
1242
|
+
if (!name) {
|
|
1243
|
+
printWarning("Usage: /mcp remove <name>");
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const config = loadMcpConfig();
|
|
1247
|
+
const before = (config.servers || []).length;
|
|
1248
|
+
config.servers = (config.servers || []).filter(
|
|
1249
|
+
(s) => s.name.toLowerCase() !== name.toLowerCase()
|
|
1250
|
+
);
|
|
1251
|
+
if (config.servers.length < before) {
|
|
1252
|
+
saveMcpConfig(config);
|
|
1253
|
+
printSuccess(`MCP server "${name}" removed.`);
|
|
1254
|
+
} else {
|
|
1255
|
+
printError(`MCP server "${name}" not found.`);
|
|
1256
|
+
}
|
|
1257
|
+
continue;
|
|
257
1258
|
}
|
|
258
1259
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1260
|
+
// /mcp inspect <name>
|
|
1261
|
+
if (input.startsWith("/mcp inspect ")) {
|
|
1262
|
+
const name = input.slice(13).trim();
|
|
1263
|
+
if (!name) {
|
|
1264
|
+
printWarning("Usage: /mcp inspect <name>");
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
const config = loadMcpConfig();
|
|
1268
|
+
const server = (config.servers || []).find(
|
|
1269
|
+
(s) => s.name.toLowerCase() === name.toLowerCase()
|
|
1270
|
+
);
|
|
1271
|
+
if (server) {
|
|
1272
|
+
renderBox(
|
|
1273
|
+
` MCP: ${server.name} `,
|
|
1274
|
+
[
|
|
1275
|
+
`Name: ${server.name}`,
|
|
1276
|
+
`URL: ${server.url}`,
|
|
1277
|
+
`Auth: ${server.auth ? "••••••" + server.auth.slice(-4) : "none"}`,
|
|
1278
|
+
`Description: ${server.description || "(none)"}`,
|
|
1279
|
+
`Added: ${server.addedAt || "unknown"}`,
|
|
1280
|
+
].join("\n"),
|
|
1281
|
+
"magenta"
|
|
1282
|
+
);
|
|
1283
|
+
} else {
|
|
1284
|
+
printError(`MCP server "${name}" not found.`);
|
|
1285
|
+
}
|
|
1286
|
+
continue;
|
|
268
1287
|
}
|
|
269
1288
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
1289
|
+
// /reasoning <level>
|
|
1290
|
+
if (input.startsWith("/reasoning")) {
|
|
1291
|
+
const level = input.slice(10).trim().toLowerCase();
|
|
1292
|
+
const validLevels = ["low", "medium", "high"];
|
|
1293
|
+
if (level && validLevels.includes(level)) {
|
|
1294
|
+
updateSessionState({ reasoningEffort: level });
|
|
1295
|
+
printSuccess(`Reasoning effort set to: ${level}`);
|
|
1296
|
+
} else {
|
|
1297
|
+
const { selected } = await inquirer.prompt([
|
|
1298
|
+
{
|
|
1299
|
+
type: "list",
|
|
1300
|
+
name: "selected",
|
|
1301
|
+
message: "Select reasoning effort:",
|
|
1302
|
+
choices: [
|
|
1303
|
+
{ name: "🟢 Low — Fast, concise answers", value: "low" },
|
|
1304
|
+
{ name: "🟡 Medium — Balanced (default)", value: "medium" },
|
|
1305
|
+
{ name: "🔴 High — Deep analysis, slower", value: "high" },
|
|
1306
|
+
],
|
|
1307
|
+
default: getSessionState().reasoningEffort,
|
|
1308
|
+
},
|
|
1309
|
+
]);
|
|
1310
|
+
updateSessionState({ reasoningEffort: selected });
|
|
1311
|
+
printSuccess(`Reasoning effort set to: ${selected}`);
|
|
1312
|
+
}
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/* ─────────────────────────────────────
|
|
1317
|
+
$ SKILL EXECUTION
|
|
1318
|
+
───────────────────────────────────── */
|
|
1319
|
+
if (input.startsWith("$")) {
|
|
1320
|
+
const withoutDollar = input.slice(1);
|
|
1321
|
+
const spaceIdx = withoutDollar.indexOf(" ");
|
|
1322
|
+
let skillName, skillPrompt;
|
|
1323
|
+
if (spaceIdx === -1) {
|
|
1324
|
+
skillName = withoutDollar.trim();
|
|
1325
|
+
skillPrompt = "";
|
|
1326
|
+
} else {
|
|
1327
|
+
skillName = withoutDollar.slice(0, spaceIdx).trim();
|
|
1328
|
+
skillPrompt = withoutDollar.slice(spaceIdx + 1).trim();
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
if (!skillName) {
|
|
1332
|
+
printWarning("Usage: $<skillname> <prompt>");
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const skillCtx = loadSkillContext(skillName);
|
|
1337
|
+
printStep(`Invoking skill: ${skillName}`);
|
|
1338
|
+
|
|
1339
|
+
if (!skillPrompt) {
|
|
1340
|
+
const { prompt } = await inquirer.prompt([
|
|
1341
|
+
{
|
|
1342
|
+
type: "input",
|
|
1343
|
+
name: "prompt",
|
|
1344
|
+
message: `[${skillName}] Enter your prompt:`,
|
|
1345
|
+
},
|
|
1346
|
+
]);
|
|
1347
|
+
skillPrompt = prompt;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
if (skillPrompt.trim()) {
|
|
1351
|
+
await askAgent(skillPrompt, `Skill Context (${skillName}):\n${skillCtx}`);
|
|
1352
|
+
}
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/* ─────────────────────────────────────
|
|
1357
|
+
AUTO FILE CONTEXT DETECTION
|
|
1358
|
+
───────────────────────────────────── */
|
|
1359
|
+
let finalInput = input;
|
|
1360
|
+
let extraCtx = "";
|
|
1361
|
+
|
|
1362
|
+
// detect potential file paths in the input
|
|
1363
|
+
const { cleanPrompt, extractedContext } = extractFileContext(input);
|
|
1364
|
+
if (extractedContext.length > 0) {
|
|
1365
|
+
const fileContextStr = extractedContext
|
|
1366
|
+
.map((f) => {
|
|
1367
|
+
if (f.type === "text") return `--- ${f.path} ---\n${f.content}`;
|
|
1368
|
+
return `[${f.type}: ${f.path}]`;
|
|
1369
|
+
})
|
|
1370
|
+
.join("\n\n");
|
|
1371
|
+
extraCtx = fileContextStr;
|
|
1372
|
+
// Only use cleanPrompt if we actually found files and there's remaining text
|
|
1373
|
+
if (cleanPrompt.trim()) {
|
|
1374
|
+
finalInput = cleanPrompt.trim();
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
273
1377
|
|
|
274
|
-
|
|
275
|
-
|
|
1378
|
+
/* ─────────────────────────────────────
|
|
1379
|
+
DEFAULT: Send to Agent
|
|
1380
|
+
───────────────────────────────────── */
|
|
1381
|
+
await askAgent(finalInput, extraCtx);
|
|
276
1382
|
}
|
|
277
1383
|
}
|