prab-cli 1.1.0 → 1.2.1
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/dist/index.js +163 -3
- package/dist/lib/chat-handler.js +108 -23
- package/dist/lib/config.js +72 -13
- package/dist/lib/context.js +7 -12
- package/dist/lib/groq-models.js +3 -3
- package/dist/lib/groq.js +6 -6
- package/dist/lib/safety.js +29 -34
- package/dist/lib/slash-commands.js +6 -0
- package/dist/lib/tracker.js +74 -72
- package/dist/lib/ui.js +289 -109
- package/package.json +25 -3
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const groq_models_1 = require("./lib/groq-models");
|
|
|
60
60
|
// Import chat handler and safety
|
|
61
61
|
const chat_handler_1 = require("./lib/chat-handler");
|
|
62
62
|
const safety_1 = require("./lib/safety");
|
|
63
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
63
64
|
const tracker_1 = require("./lib/tracker");
|
|
64
65
|
// Cache for available models
|
|
65
66
|
let cachedModels = [];
|
|
@@ -194,8 +195,9 @@ program.action(async () => {
|
|
|
194
195
|
tracker_1.tracker.modelInit(modelConfig.modelId, "groq", false, e.message);
|
|
195
196
|
process.exit(1);
|
|
196
197
|
}
|
|
197
|
-
// Display banner
|
|
198
|
-
(0,
|
|
198
|
+
// Display banner with customization
|
|
199
|
+
const customization = (0, config_1.getCustomization)();
|
|
200
|
+
(0, ui_1.banner)(modelConfig.modelId, toolRegistry.count(), customization);
|
|
199
201
|
// Context Gathering
|
|
200
202
|
const spinner = (0, ora_1.default)("Checking context...").start();
|
|
201
203
|
const isRepo = await (0, context_1.isGitRepo)();
|
|
@@ -363,6 +365,81 @@ program.action(async () => {
|
|
|
363
365
|
ui_1.log.success("API Key updated.");
|
|
364
366
|
break;
|
|
365
367
|
}
|
|
368
|
+
case "settings": {
|
|
369
|
+
console.log("\n┌─────────────────────────────────────┐");
|
|
370
|
+
console.log("│ CUSTOMIZATION │");
|
|
371
|
+
console.log("└─────────────────────────────────────┘\n");
|
|
372
|
+
const currentCustomization = (0, config_1.getCustomization)();
|
|
373
|
+
console.log(chalk_1.default.gray(` Current CLI Name: ${chalk_1.default.cyan(currentCustomization.cliName)}`));
|
|
374
|
+
console.log(chalk_1.default.gray(` Current User: ${chalk_1.default.cyan(currentCustomization.userName || "(not set)")}`));
|
|
375
|
+
console.log(chalk_1.default.gray(` Current Theme: ${chalk_1.default.cyan(currentCustomization.theme)}`));
|
|
376
|
+
console.log("");
|
|
377
|
+
try {
|
|
378
|
+
const settingChoice = await (0, select_1.default)({
|
|
379
|
+
message: "What would you like to customize?",
|
|
380
|
+
choices: [
|
|
381
|
+
{ name: "Change CLI Name (banner text)", value: "cli-name" },
|
|
382
|
+
{ name: "Set Your Name (greeting)", value: "user-name" },
|
|
383
|
+
{
|
|
384
|
+
name: "Change Theme (default, minimal, colorful)",
|
|
385
|
+
value: "theme",
|
|
386
|
+
},
|
|
387
|
+
{ name: "Reset to Defaults", value: "reset" },
|
|
388
|
+
{ name: "Cancel", value: "cancel" },
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
if (settingChoice === "cli-name") {
|
|
392
|
+
const { newName } = await inquirer_1.default.prompt([
|
|
393
|
+
{
|
|
394
|
+
type: "input",
|
|
395
|
+
name: "newName",
|
|
396
|
+
message: "Enter new CLI name (e.g., 'My CLI', 'Dev Tool'):",
|
|
397
|
+
default: currentCustomization.cliName,
|
|
398
|
+
},
|
|
399
|
+
]);
|
|
400
|
+
if (newName && newName.trim()) {
|
|
401
|
+
(0, config_1.setCliName)(newName.trim());
|
|
402
|
+
ui_1.log.success(`CLI name changed to: ${newName.trim()}`);
|
|
403
|
+
ui_1.log.info("Restart the CLI to see the new banner.");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else if (settingChoice === "user-name") {
|
|
407
|
+
const { newUserName } = await inquirer_1.default.prompt([
|
|
408
|
+
{
|
|
409
|
+
type: "input",
|
|
410
|
+
name: "newUserName",
|
|
411
|
+
message: "Enter your name (for greeting):",
|
|
412
|
+
default: currentCustomization.userName || "",
|
|
413
|
+
},
|
|
414
|
+
]);
|
|
415
|
+
if (newUserName && newUserName.trim()) {
|
|
416
|
+
(0, config_1.setUserName)(newUserName.trim());
|
|
417
|
+
ui_1.log.success(`Welcome message will now greet: ${newUserName.trim()}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else if (settingChoice === "theme") {
|
|
421
|
+
const themeChoice = await (0, select_1.default)({
|
|
422
|
+
message: "Select a theme:",
|
|
423
|
+
choices: [
|
|
424
|
+
{ name: "Default (Cyan)", value: "default" },
|
|
425
|
+
{ name: "Minimal (White)", value: "minimal" },
|
|
426
|
+
{ name: "Colorful (Magenta)", value: "colorful" },
|
|
427
|
+
],
|
|
428
|
+
});
|
|
429
|
+
(0, config_1.setTheme)(themeChoice);
|
|
430
|
+
ui_1.log.success(`Theme changed to: ${themeChoice}`);
|
|
431
|
+
ui_1.log.info("Restart the CLI to see the new theme.");
|
|
432
|
+
}
|
|
433
|
+
else if (settingChoice === "reset") {
|
|
434
|
+
(0, config_1.resetCustomization)();
|
|
435
|
+
ui_1.log.success("Customization reset to defaults.");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// User cancelled
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
366
443
|
case "exit": {
|
|
367
444
|
process.exit(0);
|
|
368
445
|
}
|
|
@@ -370,7 +447,90 @@ program.action(async () => {
|
|
|
370
447
|
continue;
|
|
371
448
|
}
|
|
372
449
|
// Process user input with chat handler
|
|
373
|
-
await chatHandler.processUserInput(userInput);
|
|
450
|
+
const result = await chatHandler.processUserInput(userInput);
|
|
451
|
+
// Handle model errors - offer to switch models
|
|
452
|
+
if (!result.success && result.isModelError) {
|
|
453
|
+
console.log("");
|
|
454
|
+
console.log(chalk_1.default.yellow("┌─────────────────────────────────────────────────────┐"));
|
|
455
|
+
console.log(chalk_1.default.yellow("│") +
|
|
456
|
+
chalk_1.default.red.bold(" Model Error Detected ") +
|
|
457
|
+
chalk_1.default.yellow("│"));
|
|
458
|
+
console.log(chalk_1.default.yellow("└─────────────────────────────────────────────────────┘"));
|
|
459
|
+
const errorMessages = {
|
|
460
|
+
rate_limit: "Rate limit exceeded. The model is receiving too many requests.",
|
|
461
|
+
model_unavailable: "Model is currently unavailable or overloaded.",
|
|
462
|
+
auth_error: "Authentication error. Please check your API key.",
|
|
463
|
+
unknown: "An error occurred with the current model.",
|
|
464
|
+
};
|
|
465
|
+
console.log(chalk_1.default.dim(` ${errorMessages[result.errorType || "unknown"]}`));
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log(chalk_1.default.cyan(" Would you like to switch to a different model?"));
|
|
468
|
+
console.log("");
|
|
469
|
+
// Fetch models if not cached
|
|
470
|
+
if (cachedModels.length === 0) {
|
|
471
|
+
const spinner = (0, ora_1.default)("Fetching available models...").start();
|
|
472
|
+
cachedModels = await (0, groq_models_1.fetchGroqModels)(apiKey);
|
|
473
|
+
spinner.succeed(`Found ${cachedModels.length} models`);
|
|
474
|
+
}
|
|
475
|
+
// Filter out the current model and build choices
|
|
476
|
+
const currentModel = modelProvider.modelId;
|
|
477
|
+
const availableModels = cachedModels.filter((m) => m.id !== currentModel);
|
|
478
|
+
const grouped = (0, groq_models_1.groupModelsByOwner)(availableModels);
|
|
479
|
+
const modelChoices = [];
|
|
480
|
+
// Add "Cancel" option first
|
|
481
|
+
modelChoices.push({
|
|
482
|
+
name: chalk_1.default.dim(" Cancel (keep current model)"),
|
|
483
|
+
value: "__cancel__",
|
|
484
|
+
});
|
|
485
|
+
modelChoices.push(new select_1.Separator("─── Available Models ───"));
|
|
486
|
+
for (const [owner, models] of grouped) {
|
|
487
|
+
modelChoices.push(new select_1.Separator(`─── ${owner} ───`));
|
|
488
|
+
for (const model of models) {
|
|
489
|
+
const ctx = (0, groq_models_1.formatContextWindow)(model.context_window);
|
|
490
|
+
modelChoices.push({
|
|
491
|
+
name: ` ${model.id} (${ctx} ctx)`,
|
|
492
|
+
value: model.id,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
const selectedModel = await (0, select_1.default)({
|
|
498
|
+
message: "Select a model to switch to:",
|
|
499
|
+
choices: modelChoices,
|
|
500
|
+
pageSize: 12,
|
|
501
|
+
loop: false,
|
|
502
|
+
});
|
|
503
|
+
if (selectedModel && selectedModel !== "__cancel__") {
|
|
504
|
+
const oldModel = currentModel;
|
|
505
|
+
(0, config_1.setActiveModel)(selectedModel);
|
|
506
|
+
modelProvider.setModel(selectedModel);
|
|
507
|
+
chatHandler.updateModelProvider(modelProvider);
|
|
508
|
+
ui_1.log.success(`Switched to model: ${selectedModel}`);
|
|
509
|
+
tracker_1.tracker.modelSwitch(oldModel, selectedModel, true);
|
|
510
|
+
// Ask if user wants to retry the last message
|
|
511
|
+
console.log("");
|
|
512
|
+
const { retry } = await inquirer_1.default.prompt([
|
|
513
|
+
{
|
|
514
|
+
type: "confirm",
|
|
515
|
+
name: "retry",
|
|
516
|
+
message: "Retry your last message with the new model?",
|
|
517
|
+
default: true,
|
|
518
|
+
},
|
|
519
|
+
]);
|
|
520
|
+
if (retry) {
|
|
521
|
+
console.log(chalk_1.default.dim("Retrying with new model..."));
|
|
522
|
+
await chatHandler.processUserInput(userInput);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
ui_1.log.info("Keeping current model. You can try again or switch models with /model");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
// User cancelled with Ctrl+C
|
|
531
|
+
ui_1.log.info("Model switch cancelled.");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
374
534
|
}
|
|
375
535
|
});
|
|
376
536
|
program.parse(process.argv);
|
package/dist/lib/chat-handler.js
CHANGED
|
@@ -11,7 +11,7 @@ const tracker_1 = require("./tracker");
|
|
|
11
11
|
class ChatHandler {
|
|
12
12
|
constructor(toolRegistry, toolExecutor, modelProvider, initialContext) {
|
|
13
13
|
this.messages = [];
|
|
14
|
-
this.contextMessage =
|
|
14
|
+
this.contextMessage = "";
|
|
15
15
|
this.usage = {
|
|
16
16
|
promptTokens: 0,
|
|
17
17
|
completionTokens: 0,
|
|
@@ -58,9 +58,9 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
58
58
|
try {
|
|
59
59
|
// Auto-attach file context if files are mentioned
|
|
60
60
|
const attachedFiles = await this.attachMentionedFiles(input);
|
|
61
|
-
|
|
61
|
+
const finalInput = input;
|
|
62
62
|
if (attachedFiles.length > 0) {
|
|
63
|
-
ui_1.log.info(`Attached context for: ${attachedFiles.join(
|
|
63
|
+
ui_1.log.info(`Attached context for: ${attachedFiles.join(", ")}`);
|
|
64
64
|
tracker_1.tracker.contextAttached(attachedFiles);
|
|
65
65
|
}
|
|
66
66
|
// Add user message
|
|
@@ -73,20 +73,20 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
73
73
|
const maxIterations = 10; // Prevent infinite loops
|
|
74
74
|
while (continueLoop && iterationCount < maxIterations) {
|
|
75
75
|
iterationCount++;
|
|
76
|
-
tracker_1.tracker.iteration(iterationCount,
|
|
76
|
+
tracker_1.tracker.iteration(iterationCount, "Starting API call");
|
|
77
77
|
// Log API request
|
|
78
78
|
tracker_1.tracker.apiRequest(this.modelProvider.modelId, this.messages.length, tools.length);
|
|
79
79
|
const apiStartTime = Date.now();
|
|
80
80
|
// Stream response from model
|
|
81
81
|
const stream = this.modelProvider.streamChat(this.messages, tools);
|
|
82
|
-
let fullResponse =
|
|
82
|
+
let fullResponse = "";
|
|
83
83
|
let toolCalls = [];
|
|
84
84
|
const formatter = new ui_1.StreamFormatter();
|
|
85
|
-
process.stdout.write(
|
|
85
|
+
process.stdout.write("\n");
|
|
86
86
|
try {
|
|
87
87
|
for await (const chunk of stream) {
|
|
88
88
|
// Handle text content
|
|
89
|
-
if (chunk.content && typeof chunk.content ===
|
|
89
|
+
if (chunk.content && typeof chunk.content === "string") {
|
|
90
90
|
fullResponse += chunk.content;
|
|
91
91
|
// Format and output the chunk with syntax highlighting
|
|
92
92
|
const formatted = formatter.processChunk(chunk.content);
|
|
@@ -120,7 +120,7 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
120
120
|
tracker_1.tracker.apiError(apiError.message, { stack: apiError.stack });
|
|
121
121
|
throw apiError;
|
|
122
122
|
}
|
|
123
|
-
process.stdout.write(
|
|
123
|
+
process.stdout.write("\n\n");
|
|
124
124
|
// Log AI response if there's content
|
|
125
125
|
if (fullResponse.length > 0) {
|
|
126
126
|
tracker_1.tracker.aiResponse(fullResponse);
|
|
@@ -128,14 +128,14 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
128
128
|
// If we have tool calls, execute them
|
|
129
129
|
if (toolCalls.length > 0) {
|
|
130
130
|
// Log AI's tool decision
|
|
131
|
-
tracker_1.tracker.aiToolDecision(toolCalls.map(tc => ({
|
|
131
|
+
tracker_1.tracker.aiToolDecision(toolCalls.map((tc) => ({
|
|
132
132
|
name: tc.name,
|
|
133
|
-
args: tc.args || {}
|
|
133
|
+
args: tc.args || {},
|
|
134
134
|
})));
|
|
135
135
|
// Add AI message with tool calls
|
|
136
136
|
this.messages.push(new messages_1.AIMessage({
|
|
137
137
|
content: fullResponse,
|
|
138
|
-
tool_calls: toolCalls
|
|
138
|
+
tool_calls: toolCalls,
|
|
139
139
|
}));
|
|
140
140
|
// Execute tools
|
|
141
141
|
const results = await this.executeToolCalls(toolCalls);
|
|
@@ -146,10 +146,10 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
146
146
|
this.messages.push(new messages_1.ToolMessage({
|
|
147
147
|
content: result.success ? result.output : `Error: ${result.error}`,
|
|
148
148
|
tool_call_id: toolCall.id,
|
|
149
|
-
name: toolCall.name
|
|
149
|
+
name: toolCall.name,
|
|
150
150
|
}));
|
|
151
151
|
}
|
|
152
|
-
tracker_1.tracker.iteration(iterationCount,
|
|
152
|
+
tracker_1.tracker.iteration(iterationCount, "Tool execution complete, continuing loop");
|
|
153
153
|
// Continue loop to get AI's response after tool execution
|
|
154
154
|
continue;
|
|
155
155
|
}
|
|
@@ -157,31 +157,98 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
157
157
|
// No tool calls, add final AI message and end loop
|
|
158
158
|
this.messages.push(new messages_1.AIMessage(fullResponse));
|
|
159
159
|
continueLoop = false;
|
|
160
|
-
tracker_1.tracker.iteration(iterationCount,
|
|
160
|
+
tracker_1.tracker.iteration(iterationCount, "No tool calls, ending loop");
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
if (iterationCount >= maxIterations) {
|
|
164
|
-
ui_1.log.warning(
|
|
165
|
-
tracker_1.tracker.warn(
|
|
164
|
+
ui_1.log.warning("Maximum iteration limit reached. Ending conversation turn.");
|
|
165
|
+
tracker_1.tracker.warn("Max iterations reached", { iterations: iterationCount });
|
|
166
166
|
}
|
|
167
167
|
// Log success
|
|
168
168
|
const duration = Date.now() - startTime;
|
|
169
169
|
tracker_1.tracker.promptComplete(input, duration, iterationCount);
|
|
170
|
+
return { success: true };
|
|
170
171
|
}
|
|
171
172
|
catch (error) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
tracker_1.tracker.
|
|
173
|
+
const errorMessage = error.message || "Unknown error";
|
|
174
|
+
ui_1.log.error(`Error: ${errorMessage}`);
|
|
175
|
+
tracker_1.tracker.promptFailed(input, errorMessage);
|
|
176
|
+
tracker_1.tracker.error("Chat processing failed", error);
|
|
177
|
+
// Detect model-specific errors
|
|
178
|
+
const isModelError = this.isModelError(errorMessage);
|
|
179
|
+
const errorType = this.classifyError(errorMessage);
|
|
180
|
+
if (isModelError) {
|
|
181
|
+
// Remove the user message so they can retry with a different model
|
|
182
|
+
this.messages.pop();
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
error: errorMessage,
|
|
187
|
+
isModelError,
|
|
188
|
+
errorType,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if error is related to the model (rate limit, unavailable, etc.)
|
|
194
|
+
*/
|
|
195
|
+
isModelError(errorMessage) {
|
|
196
|
+
const modelErrorPatterns = [
|
|
197
|
+
"rate limit",
|
|
198
|
+
"rate_limit",
|
|
199
|
+
"quota exceeded",
|
|
200
|
+
"model not found",
|
|
201
|
+
"model is not available",
|
|
202
|
+
"model_not_available",
|
|
203
|
+
"overloaded",
|
|
204
|
+
"capacity",
|
|
205
|
+
"too many requests",
|
|
206
|
+
"429",
|
|
207
|
+
"503",
|
|
208
|
+
"502",
|
|
209
|
+
"service unavailable",
|
|
210
|
+
"timeout",
|
|
211
|
+
"context length",
|
|
212
|
+
"maximum context",
|
|
213
|
+
"token limit",
|
|
214
|
+
];
|
|
215
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
216
|
+
return modelErrorPatterns.some((pattern) => lowerMessage.includes(pattern));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Classify the type of error
|
|
220
|
+
*/
|
|
221
|
+
classifyError(errorMessage) {
|
|
222
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
223
|
+
if (lowerMessage.includes("rate limit") ||
|
|
224
|
+
lowerMessage.includes("rate_limit") ||
|
|
225
|
+
lowerMessage.includes("too many requests") ||
|
|
226
|
+
lowerMessage.includes("429")) {
|
|
227
|
+
return "rate_limit";
|
|
228
|
+
}
|
|
229
|
+
if (lowerMessage.includes("model not found") ||
|
|
230
|
+
lowerMessage.includes("not available") ||
|
|
231
|
+
lowerMessage.includes("overloaded") ||
|
|
232
|
+
lowerMessage.includes("503") ||
|
|
233
|
+
lowerMessage.includes("502")) {
|
|
234
|
+
return "model_unavailable";
|
|
235
|
+
}
|
|
236
|
+
if (lowerMessage.includes("auth") ||
|
|
237
|
+
lowerMessage.includes("api key") ||
|
|
238
|
+
lowerMessage.includes("unauthorized") ||
|
|
239
|
+
lowerMessage.includes("401")) {
|
|
240
|
+
return "auth_error";
|
|
175
241
|
}
|
|
242
|
+
return "unknown";
|
|
176
243
|
}
|
|
177
244
|
/**
|
|
178
245
|
* Execute tool calls from AI
|
|
179
246
|
*/
|
|
180
247
|
async executeToolCalls(toolCalls) {
|
|
181
|
-
const formattedCalls = toolCalls.map(tc => ({
|
|
248
|
+
const formattedCalls = toolCalls.map((tc) => ({
|
|
182
249
|
id: tc.id || `call-${Date.now()}`,
|
|
183
250
|
name: tc.name,
|
|
184
|
-
args: tc.args || {}
|
|
251
|
+
args: tc.args || {},
|
|
185
252
|
}));
|
|
186
253
|
return await this.toolExecutor.executeMultiple(formattedCalls);
|
|
187
254
|
}
|
|
@@ -192,7 +259,7 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
192
259
|
const allFiles = await (0, context_1.getFileTree)();
|
|
193
260
|
const attached = [];
|
|
194
261
|
for (const file of allFiles) {
|
|
195
|
-
const base = file.split(
|
|
262
|
+
const base = file.split("/").pop() || "";
|
|
196
263
|
const isBaseMatch = base.length > 2 && input.includes(base);
|
|
197
264
|
const isPathMatch = input.includes(file);
|
|
198
265
|
if (isPathMatch || isBaseMatch) {
|
|
@@ -211,7 +278,7 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
211
278
|
clearHistory() {
|
|
212
279
|
const systemMsg = this.messages[0];
|
|
213
280
|
this.messages = [systemMsg];
|
|
214
|
-
tracker_1.tracker.debug(
|
|
281
|
+
tracker_1.tracker.debug("Chat history cleared");
|
|
215
282
|
}
|
|
216
283
|
/**
|
|
217
284
|
* Get message count
|
|
@@ -235,5 +302,23 @@ When you need to perform file operations, use the appropriate tools rather than
|
|
|
235
302
|
this.messages[0] = systemMsg; // Keep the same message for now
|
|
236
303
|
// In a more sophisticated implementation, we'd rebuild the system message
|
|
237
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Update the model provider (for switching models)
|
|
307
|
+
*/
|
|
308
|
+
updateModelProvider(modelProvider) {
|
|
309
|
+
this.modelProvider = modelProvider;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the last user input (for retry after model switch)
|
|
313
|
+
*/
|
|
314
|
+
getLastUserInput() {
|
|
315
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
316
|
+
const msg = this.messages[i];
|
|
317
|
+
if (msg instanceof messages_1.HumanMessage) {
|
|
318
|
+
return msg.content;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
238
323
|
}
|
|
239
324
|
exports.ChatHandler = ChatHandler;
|
package/dist/lib/config.js
CHANGED
|
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.clearSessionData = exports.setSessionData = exports.getSessionData = exports.setPreference = exports.getPreferences = exports.setActiveModel = exports.getModelConfig = exports.clearApiKey = exports.setApiKey = exports.getApiKey = exports.getConfig = void 0;
|
|
6
|
+
exports.resetCustomization = exports.setTheme = exports.setUserName = exports.setCliName = exports.getCustomization = exports.clearSessionData = exports.setSessionData = exports.getSessionData = exports.setPreference = exports.getPreferences = exports.setActiveModel = exports.getModelConfig = exports.clearApiKey = exports.setApiKey = exports.getApiKey = exports.getConfig = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const registry_1 = require("./models/registry");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const DEFAULT_CLI_NAME = "Prab CLI";
|
|
12
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".config", "groq-cli-tool");
|
|
13
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
|
|
13
14
|
const ensureConfigDir = () => {
|
|
14
15
|
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
15
16
|
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -19,7 +20,7 @@ const readConfig = () => {
|
|
|
19
20
|
try {
|
|
20
21
|
if (!fs_1.default.existsSync(CONFIG_FILE))
|
|
21
22
|
return {};
|
|
22
|
-
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE,
|
|
23
|
+
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
|
|
23
24
|
}
|
|
24
25
|
catch {
|
|
25
26
|
return {};
|
|
@@ -35,22 +36,27 @@ const writeConfig = (data) => {
|
|
|
35
36
|
const getConfig = () => {
|
|
36
37
|
const config = readConfig();
|
|
37
38
|
// Handle legacy config format (old apiKey -> new apiKeys.groq)
|
|
38
|
-
if (
|
|
39
|
+
if ("apiKey" in config && typeof config.apiKey === "string") {
|
|
39
40
|
const legacyKey = config.apiKey;
|
|
40
41
|
delete config.apiKey;
|
|
41
42
|
config.apiKeys = { groq: legacyKey };
|
|
42
43
|
writeConfig(config);
|
|
43
44
|
}
|
|
44
45
|
return {
|
|
45
|
-
apiKeys: config.apiKeys || { groq:
|
|
46
|
+
apiKeys: config.apiKeys || { groq: "" },
|
|
46
47
|
activeModel: config.activeModel || (0, registry_1.getDefaultModel)(),
|
|
47
48
|
preferences: {
|
|
48
49
|
temperature: config.preferences?.temperature ?? 0.7,
|
|
49
50
|
autoConfirm: config.preferences?.autoConfirm ?? false,
|
|
50
51
|
safeMode: config.preferences?.safeMode ?? true,
|
|
51
|
-
maxTokens: config.preferences?.maxTokens
|
|
52
|
+
maxTokens: config.preferences?.maxTokens,
|
|
52
53
|
},
|
|
53
|
-
|
|
54
|
+
customization: {
|
|
55
|
+
cliName: config.customization?.cliName || DEFAULT_CLI_NAME,
|
|
56
|
+
userName: config.customization?.userName,
|
|
57
|
+
theme: config.customization?.theme || "default",
|
|
58
|
+
},
|
|
59
|
+
session: config.session || { todos: [] },
|
|
54
60
|
};
|
|
55
61
|
};
|
|
56
62
|
exports.getConfig = getConfig;
|
|
@@ -59,7 +65,7 @@ exports.getConfig = getConfig;
|
|
|
59
65
|
*/
|
|
60
66
|
const getApiKey = () => {
|
|
61
67
|
const config = (0, exports.getConfig)();
|
|
62
|
-
return config.apiKeys.groq ||
|
|
68
|
+
return config.apiKeys.groq || "";
|
|
63
69
|
};
|
|
64
70
|
exports.getApiKey = getApiKey;
|
|
65
71
|
/**
|
|
@@ -68,7 +74,7 @@ exports.getApiKey = getApiKey;
|
|
|
68
74
|
const setApiKey = (key) => {
|
|
69
75
|
const config = readConfig();
|
|
70
76
|
if (!config.apiKeys) {
|
|
71
|
-
config.apiKeys = { groq:
|
|
77
|
+
config.apiKeys = { groq: "" };
|
|
72
78
|
}
|
|
73
79
|
config.apiKeys.groq = key;
|
|
74
80
|
writeConfig(config);
|
|
@@ -80,7 +86,7 @@ exports.setApiKey = setApiKey;
|
|
|
80
86
|
const clearApiKey = () => {
|
|
81
87
|
const config = readConfig();
|
|
82
88
|
if (config.apiKeys) {
|
|
83
|
-
config.apiKeys.groq =
|
|
89
|
+
config.apiKeys.groq = "";
|
|
84
90
|
}
|
|
85
91
|
writeConfig(config);
|
|
86
92
|
};
|
|
@@ -92,7 +98,7 @@ const getModelConfig = () => {
|
|
|
92
98
|
const config = (0, exports.getConfig)();
|
|
93
99
|
return {
|
|
94
100
|
modelId: config.activeModel,
|
|
95
|
-
temperature: config.preferences.temperature
|
|
101
|
+
temperature: config.preferences.temperature,
|
|
96
102
|
};
|
|
97
103
|
};
|
|
98
104
|
exports.getModelConfig = getModelConfig;
|
|
@@ -121,7 +127,7 @@ const setPreference = (key, value) => {
|
|
|
121
127
|
config.preferences = {
|
|
122
128
|
temperature: 0.7,
|
|
123
129
|
autoConfirm: false,
|
|
124
|
-
safeMode: true
|
|
130
|
+
safeMode: true,
|
|
125
131
|
};
|
|
126
132
|
}
|
|
127
133
|
config.preferences[key] = value;
|
|
@@ -154,3 +160,56 @@ const clearSessionData = () => {
|
|
|
154
160
|
writeConfig(config);
|
|
155
161
|
};
|
|
156
162
|
exports.clearSessionData = clearSessionData;
|
|
163
|
+
/**
|
|
164
|
+
* Get customization settings
|
|
165
|
+
*/
|
|
166
|
+
const getCustomization = () => {
|
|
167
|
+
const config = (0, exports.getConfig)();
|
|
168
|
+
return config.customization;
|
|
169
|
+
};
|
|
170
|
+
exports.getCustomization = getCustomization;
|
|
171
|
+
/**
|
|
172
|
+
* Set CLI name
|
|
173
|
+
*/
|
|
174
|
+
const setCliName = (name) => {
|
|
175
|
+
const config = readConfig();
|
|
176
|
+
if (!config.customization) {
|
|
177
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
178
|
+
}
|
|
179
|
+
config.customization.cliName = name;
|
|
180
|
+
writeConfig(config);
|
|
181
|
+
};
|
|
182
|
+
exports.setCliName = setCliName;
|
|
183
|
+
/**
|
|
184
|
+
* Set user name
|
|
185
|
+
*/
|
|
186
|
+
const setUserName = (name) => {
|
|
187
|
+
const config = readConfig();
|
|
188
|
+
if (!config.customization) {
|
|
189
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
190
|
+
}
|
|
191
|
+
config.customization.userName = name;
|
|
192
|
+
writeConfig(config);
|
|
193
|
+
};
|
|
194
|
+
exports.setUserName = setUserName;
|
|
195
|
+
/**
|
|
196
|
+
* Set theme
|
|
197
|
+
*/
|
|
198
|
+
const setTheme = (theme) => {
|
|
199
|
+
const config = readConfig();
|
|
200
|
+
if (!config.customization) {
|
|
201
|
+
config.customization = { cliName: DEFAULT_CLI_NAME };
|
|
202
|
+
}
|
|
203
|
+
config.customization.theme = theme;
|
|
204
|
+
writeConfig(config);
|
|
205
|
+
};
|
|
206
|
+
exports.setTheme = setTheme;
|
|
207
|
+
/**
|
|
208
|
+
* Reset customization to defaults
|
|
209
|
+
*/
|
|
210
|
+
const resetCustomization = () => {
|
|
211
|
+
const config = readConfig();
|
|
212
|
+
config.customization = { cliName: DEFAULT_CLI_NAME, theme: "default" };
|
|
213
|
+
writeConfig(config);
|
|
214
|
+
};
|
|
215
|
+
exports.resetCustomization = resetCustomization;
|
package/dist/lib/context.js
CHANGED
|
@@ -12,33 +12,28 @@ const isGitRepo = async () => {
|
|
|
12
12
|
try {
|
|
13
13
|
return await git.checkIsRepo();
|
|
14
14
|
}
|
|
15
|
-
catch
|
|
15
|
+
catch {
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
exports.isGitRepo = isGitRepo;
|
|
20
20
|
const getFileTree = async (cwd = process.cwd()) => {
|
|
21
21
|
// Ignore node_modules, .git, dist, etc.
|
|
22
|
-
const ignore = [
|
|
23
|
-
const files = await (0, glob_1.glob)(
|
|
22
|
+
const ignore = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.env", "**/*.lock"];
|
|
23
|
+
const files = await (0, glob_1.glob)("**/*", { cwd, ignore, nodir: true });
|
|
24
24
|
return files;
|
|
25
25
|
};
|
|
26
26
|
exports.getFileTree = getFileTree;
|
|
27
27
|
const getFileContent = (filePath) => {
|
|
28
28
|
try {
|
|
29
|
-
return fs_1.default.readFileSync(filePath,
|
|
29
|
+
return fs_1.default.readFileSync(filePath, "utf-8");
|
|
30
30
|
}
|
|
31
|
-
catch
|
|
32
|
-
return
|
|
31
|
+
catch {
|
|
32
|
+
return "";
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
exports.getFileContent = getFileContent;
|
|
36
36
|
const writeFile = (filePath, content) => {
|
|
37
|
-
|
|
38
|
-
fs_1.default.writeFileSync(filePath, content, 'utf-8');
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
throw e;
|
|
42
|
-
}
|
|
37
|
+
fs_1.default.writeFileSync(filePath, content, "utf-8");
|
|
43
38
|
};
|
|
44
39
|
exports.writeFile = writeFile;
|
package/dist/lib/groq-models.js
CHANGED
|
@@ -16,12 +16,12 @@ async function fetchGroqModels(apiKey) {
|
|
|
16
16
|
const response = await groq.models.list();
|
|
17
17
|
// Filter and sort models
|
|
18
18
|
const models = response.data
|
|
19
|
-
.filter(m => m.active !== false)
|
|
19
|
+
.filter((m) => m.active !== false)
|
|
20
20
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
21
21
|
return models;
|
|
22
22
|
}
|
|
23
23
|
catch (error) {
|
|
24
|
-
console.error(
|
|
24
|
+
console.error("Failed to fetch models:", error.message);
|
|
25
25
|
return [];
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -31,7 +31,7 @@ async function fetchGroqModels(apiKey) {
|
|
|
31
31
|
function groupModelsByOwner(models) {
|
|
32
32
|
const groups = new Map();
|
|
33
33
|
for (const model of models) {
|
|
34
|
-
const owner = model.owned_by ||
|
|
34
|
+
const owner = model.owned_by || "Other";
|
|
35
35
|
if (!groups.has(owner)) {
|
|
36
36
|
groups.set(owner, []);
|
|
37
37
|
}
|