prab-cli 1.0.0 → 1.1.0

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 CHANGED
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
38
  };
@@ -10,6 +43,8 @@ const config_1 = require("./lib/config");
10
43
  const context_1 = require("./lib/context");
11
44
  const ui_1 = require("./lib/ui");
12
45
  const config_2 = require("./lib/config");
46
+ const slash_commands_1 = require("./lib/slash-commands");
47
+ const select_1 = __importStar(require("@inquirer/select"));
13
48
  const ora_1 = __importDefault(require("ora"));
14
49
  // Import tool system
15
50
  const base_1 = require("./lib/tools/base");
@@ -176,7 +211,7 @@ program.action(async () => {
176
211
  }
177
212
  // Initialize chat handler
178
213
  const chatHandler = new chat_handler_1.ChatHandler(toolRegistry, toolExecutor, modelProvider, contextMessage);
179
- ui_1.log.info('Type "/" for commands menu, or start chatting!');
214
+ ui_1.log.info('Type "/" for commands (searchable), or start chatting!');
180
215
  // Display any existing todos
181
216
  const session = (0, config_2.getSessionData)();
182
217
  if (session.todos && session.todos.length > 0) {
@@ -191,180 +226,147 @@ program.action(async () => {
191
226
  message: ">",
192
227
  },
193
228
  ]);
194
- // Handle Slash Commands Menu
195
- if (userInput.trim() === "/") {
229
+ // Handle Slash Commands
230
+ if (userInput.trim() === "/" || userInput.trim().startsWith("/")) {
196
231
  const currentModel = modelProvider.modelId;
197
- console.log("\n┌────────────────────────────────────────┐");
198
- console.log("│ AVAILABLE COMMANDS │");
199
- console.log("└────────────────────────────────────────┘\n");
200
- const { action } = await inquirer_1.default.prompt([
201
- {
202
- type: "list",
203
- name: "action",
204
- message: "Choose an option:",
205
- choices: [
206
- "1. Select Model",
207
- "2. Usage",
208
- "3. Tools",
209
- "4. Todos",
210
- "5. Clear Todos",
211
- "6. Context",
212
- "7. Clear History",
213
- "8. API Key",
214
- "9. Exit",
215
- ],
216
- },
217
- ]);
218
- // Select Model - Toggle between different models
219
- if (action === "1. Select Model") {
220
- // Fetch models from Groq API if not cached
221
- if (cachedModels.length === 0) {
222
- const spinner = (0, ora_1.default)("Fetching available models from Groq...").start();
223
- cachedModels = await (0, groq_models_1.fetchGroqModels)(apiKey);
224
- spinner.succeed(`Found ${cachedModels.length} models`);
225
- }
226
- // Group models by owner
227
- const grouped = (0, groq_models_1.groupModelsByOwner)(cachedModels);
228
- const modelChoices = [];
229
- // Build choices grouped by owner
230
- for (const [owner, models] of grouped) {
231
- modelChoices.push(new inquirer_1.default.Separator(`─── ${owner} ───`));
232
- for (const model of models) {
233
- const isCurrent = model.id === currentModel;
234
- const ctx = (0, groq_models_1.formatContextWindow)(model.context_window);
235
- modelChoices.push({
236
- name: `${isCurrent ? "✓ " : " "}${model.id} (${ctx} ctx)`,
237
- value: model.id,
238
- short: model.id,
232
+ // Show the searchable slash command menu
233
+ const action = await (0, slash_commands_1.showSlashCommandMenu)();
234
+ if (!action) {
235
+ // User cancelled
236
+ continue;
237
+ }
238
+ // Execute the selected command
239
+ switch (action) {
240
+ case "model": {
241
+ // Fetch models from Groq API if not cached
242
+ if (cachedModels.length === 0) {
243
+ const spinner = (0, ora_1.default)("Fetching available models from Groq...").start();
244
+ cachedModels = await (0, groq_models_1.fetchGroqModels)(apiKey);
245
+ spinner.succeed(`Found ${cachedModels.length} models`);
246
+ }
247
+ // Group models by owner
248
+ const grouped = (0, groq_models_1.groupModelsByOwner)(cachedModels);
249
+ const modelChoices = [];
250
+ // Build choices grouped by owner
251
+ for (const [owner, models] of grouped) {
252
+ modelChoices.push(new select_1.Separator(`─── ${owner} ───`));
253
+ for (const model of models) {
254
+ const isCurrent = model.id === currentModel;
255
+ const ctx = (0, groq_models_1.formatContextWindow)(model.context_window);
256
+ modelChoices.push({
257
+ name: `${isCurrent ? "✓ " : " "}${model.id} (${ctx} ctx)`,
258
+ value: model.id,
259
+ });
260
+ }
261
+ }
262
+ try {
263
+ const selectedModel = await (0, select_1.default)({
264
+ message: `Select a model (current: ${currentModel}):`,
265
+ choices: modelChoices,
266
+ pageSize: 15,
267
+ loop: false,
239
268
  });
269
+ if (selectedModel && selectedModel !== currentModel) {
270
+ const oldModel = currentModel;
271
+ (0, config_1.setActiveModel)(selectedModel);
272
+ modelProvider.setModel(selectedModel);
273
+ ui_1.log.success(`Switched to model: ${selectedModel}`);
274
+ tracker_1.tracker.modelSwitch(oldModel, selectedModel, true);
275
+ }
276
+ else if (selectedModel === currentModel) {
277
+ ui_1.log.info(`Already using ${selectedModel}`);
278
+ }
279
+ }
280
+ catch (e) {
281
+ // User cancelled with Ctrl+C
240
282
  }
283
+ break;
241
284
  }
242
- const { selectedModel } = await inquirer_1.default.prompt([
243
- {
244
- type: "list",
245
- name: "selectedModel",
246
- message: `Select a model (current: ${currentModel}):`,
247
- choices: modelChoices,
248
- pageSize: 15,
249
- loop: false,
250
- },
251
- ]);
252
- if (selectedModel && selectedModel !== currentModel) {
253
- const oldModel = currentModel;
254
- (0, config_1.setActiveModel)(selectedModel);
255
- modelProvider.setModel(selectedModel);
256
- ui_1.log.success(`Switched to model: ${selectedModel}`);
257
- tracker_1.tracker.modelSwitch(oldModel, selectedModel, true);
285
+ case "usage": {
286
+ console.log("\n┌─────────────────────────────────────┐");
287
+ console.log("│ MODEL INFO │");
288
+ console.log("└─────────────────────────────────────┘\n");
289
+ console.log(` Current Model: ${currentModel}`);
290
+ // Find model in cache and show details
291
+ if (cachedModels.length === 0) {
292
+ const spinner = (0, ora_1.default)("Fetching model info...").start();
293
+ cachedModels = await (0, groq_models_1.fetchGroqModels)(apiKey);
294
+ spinner.stop();
295
+ }
296
+ const modelInfo = cachedModels.find((m) => m.id === currentModel);
297
+ if (modelInfo) {
298
+ console.log(` Provider: ${modelInfo.owned_by}`);
299
+ console.log(` Context: ${(0, groq_models_1.formatContextWindow)(modelInfo.context_window)} tokens`);
300
+ console.log(` Status: ${modelInfo.active ? "Active" : "Inactive"}`);
301
+ }
302
+ console.log("\n┌─────────────────────────────────────┐");
303
+ console.log("│ TOKEN CONSUMPTION │");
304
+ console.log("└─────────────────────────────────────┘\n");
305
+ const usageStats = chatHandler.getUsageStats();
306
+ console.log(` Prompt Tokens: ${usageStats.promptTokens.toLocaleString()}`);
307
+ console.log(` Completion Tokens: ${usageStats.completionTokens.toLocaleString()}`);
308
+ console.log(` Total Tokens: ${usageStats.totalTokens.toLocaleString()}`);
309
+ console.log(` API Requests: ${usageStats.requestCount}`);
310
+ console.log("\n┌─────────────────────────────────────┐");
311
+ console.log("│ SESSION STATS │");
312
+ console.log("└─────────────────────────────────────┘\n");
313
+ console.log(` Messages: ${chatHandler.getMessageCount()}`);
314
+ console.log(` Tools: ${toolRegistry.count()} available`);
315
+ console.log(` Session ID: ${tracker_1.tracker.getSessionId()}`);
316
+ console.log("");
317
+ break;
258
318
  }
259
- else if (selectedModel === currentModel) {
260
- ui_1.log.info(`Already using ${selectedModel}`);
319
+ case "tools": {
320
+ console.log("\n┌─────────────────────────────────────┐");
321
+ console.log("│ AVAILABLE TOOLS │");
322
+ console.log("└─────────────────────────────────────┘\n");
323
+ console.log(toolRegistry.getToolDescriptions());
324
+ console.log("");
325
+ break;
261
326
  }
262
- continue;
263
- }
264
- // Usage - Show model usage and stats
265
- if (action === "2. Usage") {
266
- console.log("\n┌─────────────────────────────────────┐");
267
- console.log("│ MODEL USAGE │");
268
- console.log("└─────────────────────────────────────┘\n");
269
- console.log(` Current Model: ${currentModel}`);
270
- // Find model in cache and show details
271
- if (cachedModels.length === 0) {
272
- const spinner = (0, ora_1.default)("Fetching model info...").start();
273
- cachedModels = await (0, groq_models_1.fetchGroqModels)(apiKey);
274
- spinner.stop();
327
+ case "todos": {
328
+ const session = (0, config_2.getSessionData)();
329
+ (0, ui_1.showTodoList)(session.todos);
330
+ break;
275
331
  }
276
- const modelInfo = cachedModels.find((m) => m.id === currentModel);
277
- if (modelInfo) {
278
- console.log(` Provider: ${modelInfo.owned_by}`);
279
- console.log(` Context: ${(0, groq_models_1.formatContextWindow)(modelInfo.context_window)} tokens`);
280
- console.log(` Status: ${modelInfo.active ? "Active" : "Inactive"}`);
332
+ case "clear-todos": {
333
+ (0, config_1.clearSessionData)();
334
+ ui_1.log.success("Todos cleared.");
335
+ break;
336
+ }
337
+ case "context": {
338
+ console.log("\n┌─────────────────────────────────────┐");
339
+ console.log("│ FILE CONTEXT │");
340
+ console.log("└─────────────────────────────────────┘\n");
341
+ console.log(contextMessage || " No context loaded.");
342
+ console.log("");
343
+ break;
344
+ }
345
+ case "clear": {
346
+ chatHandler.clearHistory();
347
+ ui_1.log.success("Chat history cleared.");
348
+ break;
349
+ }
350
+ case "api-key": {
351
+ const { key } = await inquirer_1.default.prompt([
352
+ {
353
+ type: "password",
354
+ name: "key",
355
+ message: "Enter new API Key:",
356
+ mask: "*",
357
+ },
358
+ ]);
359
+ (0, config_1.setApiKey)(key.trim());
360
+ apiKey = key.trim();
361
+ modelProvider.initialize(key.trim(), modelConfig.modelId);
362
+ cachedModels = []; // Clear model cache
363
+ ui_1.log.success("API Key updated.");
364
+ break;
365
+ }
366
+ case "exit": {
367
+ process.exit(0);
281
368
  }
282
- console.log("\n┌─────────────────────────────────────┐");
283
- console.log("│ SESSION STATS │");
284
- console.log("└─────────────────────────────────────┘\n");
285
- console.log(` Messages: ${chatHandler.getMessageCount()}`);
286
- console.log(` Tools: ${toolRegistry.count()} available`);
287
- console.log(` Session ID: ${tracker_1.tracker.getSessionId()}`);
288
- console.log("");
289
- continue;
290
- }
291
- if (action === "3. Tools") {
292
- console.log("\n┌─────────────────────────────────────┐");
293
- console.log("│ AVAILABLE TOOLS │");
294
- console.log("└─────────────────────────────────────┘\n");
295
- console.log(toolRegistry.getToolDescriptions());
296
- console.log("");
297
- continue;
298
- }
299
- if (action === "4. Todos") {
300
- const session = (0, config_2.getSessionData)();
301
- (0, ui_1.showTodoList)(session.todos);
302
- continue;
303
- }
304
- if (action === "5. Clear Todos") {
305
- (0, config_1.clearSessionData)();
306
- ui_1.log.success("Todos cleared.");
307
- continue;
308
- }
309
- if (action === "6. Context") {
310
- console.log("\n┌─────────────────────────────────────┐");
311
- console.log("│ FILE CONTEXT │");
312
- console.log("└─────────────────────────────────────┘\n");
313
- console.log(contextMessage || " No context loaded.");
314
- console.log("");
315
- continue;
316
- }
317
- if (action === "7. Clear History") {
318
- chatHandler.clearHistory();
319
- ui_1.log.success("Chat history cleared.");
320
- continue;
321
- }
322
- if (action === "8. API Key") {
323
- const { key } = await inquirer_1.default.prompt([
324
- {
325
- type: "password",
326
- name: "key",
327
- message: "Enter new API Key:",
328
- mask: "*",
329
- },
330
- ]);
331
- (0, config_1.setApiKey)(key.trim());
332
- apiKey = key.trim();
333
- modelProvider.initialize(key.trim(), modelConfig.modelId);
334
- cachedModels = []; // Clear model cache
335
- ui_1.log.success("API Key updated.");
336
- continue;
337
- }
338
- if (action === "9. Exit")
339
- process.exit(0);
340
- continue;
341
- }
342
- // Handle direct slash commands
343
- if (userInput.startsWith("/")) {
344
- const cmd = userInput.trim().toLowerCase();
345
- if (cmd === "/exit" || cmd === "/quit")
346
- process.exit(0);
347
- if (cmd === "/clear") {
348
- chatHandler.clearHistory();
349
- ui_1.log.success("History cleared.");
350
- continue;
351
- }
352
- if (cmd === "/context") {
353
- ui_1.log.info(contextMessage || "No context.");
354
- continue;
355
- }
356
- if (cmd === "/tools") {
357
- console.log("\n📦 Available Tools:\n");
358
- console.log(toolRegistry.getToolDescriptions());
359
- console.log("");
360
- continue;
361
- }
362
- if (cmd === "/todos") {
363
- const session = (0, config_2.getSessionData)();
364
- (0, ui_1.showTodoList)(session.todos);
365
- continue;
366
369
  }
367
- ui_1.log.warning(`Unknown command: ${cmd}. Type "/" for menu.`);
368
370
  continue;
369
371
  }
370
372
  // Process user input with chat handler
@@ -12,6 +12,12 @@ class ChatHandler {
12
12
  constructor(toolRegistry, toolExecutor, modelProvider, initialContext) {
13
13
  this.messages = [];
14
14
  this.contextMessage = '';
15
+ this.usage = {
16
+ promptTokens: 0,
17
+ completionTokens: 0,
18
+ totalTokens: 0,
19
+ requestCount: 0,
20
+ };
15
21
  this.toolRegistry = toolRegistry;
16
22
  this.toolExecutor = toolExecutor;
17
23
  this.modelProvider = modelProvider;
@@ -92,7 +98,15 @@ When you need to perform file operations, use the appropriate tools rather than
92
98
  if (chunk.tool_calls && chunk.tool_calls.length > 0) {
93
99
  toolCalls = chunk.tool_calls;
94
100
  }
101
+ // Capture usage metadata from the chunk
102
+ if (chunk.usage_metadata) {
103
+ this.usage.promptTokens += chunk.usage_metadata.input_tokens || 0;
104
+ this.usage.completionTokens += chunk.usage_metadata.output_tokens || 0;
105
+ this.usage.totalTokens += chunk.usage_metadata.total_tokens || 0;
106
+ }
95
107
  }
108
+ // Increment request count
109
+ this.usage.requestCount++;
96
110
  // Flush any remaining content in the formatter
97
111
  const remaining = formatter.flush();
98
112
  if (remaining) {
@@ -205,6 +219,12 @@ When you need to perform file operations, use the appropriate tools rather than
205
219
  getMessageCount() {
206
220
  return this.messages.length;
207
221
  }
222
+ /**
223
+ * Get usage statistics
224
+ */
225
+ getUsageStats() {
226
+ return { ...this.usage };
227
+ }
208
228
  /**
209
229
  * Update context (e.g., if file tree changes)
210
230
  */
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SLASH_COMMANDS = void 0;
7
+ exports.showSlashCommandMenu = showSlashCommandMenu;
8
+ const search_1 = __importDefault(require("@inquirer/search"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ exports.SLASH_COMMANDS = [
11
+ {
12
+ name: "/model",
13
+ description: "Switch between available AI models",
14
+ shortcut: "m",
15
+ action: "model",
16
+ },
17
+ {
18
+ name: "/usage",
19
+ description: "Show model info and session statistics",
20
+ shortcut: "u",
21
+ action: "usage",
22
+ },
23
+ {
24
+ name: "/tools",
25
+ description: "List all available tools",
26
+ shortcut: "t",
27
+ action: "tools",
28
+ },
29
+ {
30
+ name: "/todos",
31
+ description: "Display current todo list",
32
+ action: "todos",
33
+ },
34
+ {
35
+ name: "/clear-todos",
36
+ description: "Clear all todos",
37
+ action: "clear-todos",
38
+ },
39
+ {
40
+ name: "/context",
41
+ description: "Show file context from current directory",
42
+ shortcut: "c",
43
+ action: "context",
44
+ },
45
+ {
46
+ name: "/clear",
47
+ description: "Clear chat history",
48
+ action: "clear",
49
+ },
50
+ {
51
+ name: "/api-key",
52
+ description: "Update your Groq API key",
53
+ action: "api-key",
54
+ },
55
+ {
56
+ name: "/exit",
57
+ description: "Exit the application",
58
+ shortcut: "q",
59
+ action: "exit",
60
+ },
61
+ ];
62
+ function formatCommandChoice(cmd) {
63
+ const shortcut = cmd.shortcut ? chalk_1.default.dim(` (${cmd.shortcut})`) : "";
64
+ return `${chalk_1.default.cyan(cmd.name)}${shortcut} ${chalk_1.default.dim("─")} ${cmd.description}`;
65
+ }
66
+ async function showSlashCommandMenu() {
67
+ try {
68
+ const answer = await (0, search_1.default)({
69
+ message: "Command",
70
+ source: async (input) => {
71
+ const searchTerm = (input || "").toLowerCase().replace(/^\//, "");
72
+ return exports.SLASH_COMMANDS.filter((cmd) => {
73
+ if (!searchTerm)
74
+ return true;
75
+ return (cmd.name.toLowerCase().includes(searchTerm) ||
76
+ cmd.description.toLowerCase().includes(searchTerm) ||
77
+ cmd.shortcut?.toLowerCase() === searchTerm);
78
+ }).map((cmd) => ({
79
+ name: formatCommandChoice(cmd),
80
+ value: cmd.action,
81
+ description: cmd.description,
82
+ }));
83
+ },
84
+ });
85
+ return answer;
86
+ }
87
+ catch (error) {
88
+ // User cancelled (Ctrl+C)
89
+ if (error.message?.includes("cancelled") || error.name === "ExitPromptError") {
90
+ return null;
91
+ }
92
+ throw error;
93
+ }
94
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prab-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI-powered coding assistant for your terminal. Built with Groq's lightning-fast LLMs, featuring autonomous tool execution, syntax-highlighted output, and git integration.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -49,6 +49,8 @@
49
49
  "LICENSE"
50
50
  ],
51
51
  "dependencies": {
52
+ "@inquirer/search": "^4.1.0",
53
+ "@inquirer/select": "^5.0.4",
52
54
  "@langchain/core": "^1.1.11",
53
55
  "@langchain/groq": "^1.0.2",
54
56
  "@langchain/openai": "^1.2.2",