prab-cli 1.0.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.
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChatHandler = void 0;
4
+ const messages_1 = require("@langchain/core/messages");
5
+ const context_1 = require("./context");
6
+ const ui_1 = require("./ui");
7
+ const tracker_1 = require("./tracker");
8
+ /**
9
+ * Handles chat interactions with AI, tool calling, and context management
10
+ */
11
+ class ChatHandler {
12
+ constructor(toolRegistry, toolExecutor, modelProvider, initialContext) {
13
+ this.messages = [];
14
+ this.contextMessage = '';
15
+ this.toolRegistry = toolRegistry;
16
+ this.toolExecutor = toolExecutor;
17
+ this.modelProvider = modelProvider;
18
+ this.contextMessage = initialContext;
19
+ // Initialize with system message
20
+ this.initializeSystemMessage();
21
+ }
22
+ /**
23
+ * Initialize system message with tool descriptions and context
24
+ */
25
+ initializeSystemMessage() {
26
+ const toolDescriptions = this.toolRegistry.getToolDescriptions();
27
+ const systemPrompt = `You are an expert AI coding assistant with access to powerful tools for file operations, shell commands, and git management.
28
+
29
+ Available Tools:
30
+ ${toolDescriptions}
31
+
32
+ Guidelines:
33
+ - Use tools proactively to read, edit, and create files when the user asks for code changes
34
+ - For multi-step tasks, use the manage_todos tool to track progress
35
+ - Always use read_file before editing files to understand the current content
36
+ - Use glob and grep to explore unfamiliar codebases
37
+ - Provide clear explanations of what you're doing
38
+ - Ask for clarification if requirements are ambiguous
39
+
40
+ ${this.contextMessage}
41
+
42
+ When you need to perform file operations, use the appropriate tools rather than just suggesting changes.`;
43
+ this.messages.push(new messages_1.SystemMessage(systemPrompt));
44
+ }
45
+ /**
46
+ * Process user input and generate response with tool support
47
+ */
48
+ async processUserInput(input) {
49
+ const startTime = Date.now();
50
+ // Log prompt received
51
+ tracker_1.tracker.promptReceived(input);
52
+ try {
53
+ // Auto-attach file context if files are mentioned
54
+ const attachedFiles = await this.attachMentionedFiles(input);
55
+ let finalInput = input;
56
+ if (attachedFiles.length > 0) {
57
+ ui_1.log.info(`Attached context for: ${attachedFiles.join(', ')}`);
58
+ tracker_1.tracker.contextAttached(attachedFiles);
59
+ }
60
+ // Add user message
61
+ this.messages.push(new messages_1.HumanMessage(finalInput));
62
+ // Get tools as LangChain tools
63
+ const tools = this.toolRegistry.getAllAsLangChainTools();
64
+ // Main response loop (handles tool calls)
65
+ let continueLoop = true;
66
+ let iterationCount = 0;
67
+ const maxIterations = 10; // Prevent infinite loops
68
+ while (continueLoop && iterationCount < maxIterations) {
69
+ iterationCount++;
70
+ tracker_1.tracker.iteration(iterationCount, 'Starting API call');
71
+ // Log API request
72
+ tracker_1.tracker.apiRequest(this.modelProvider.modelId, this.messages.length, tools.length);
73
+ const apiStartTime = Date.now();
74
+ // Stream response from model
75
+ const stream = this.modelProvider.streamChat(this.messages, tools);
76
+ let fullResponse = '';
77
+ let toolCalls = [];
78
+ const formatter = new ui_1.StreamFormatter();
79
+ process.stdout.write('\n');
80
+ try {
81
+ for await (const chunk of stream) {
82
+ // Handle text content
83
+ if (chunk.content && typeof chunk.content === 'string') {
84
+ fullResponse += chunk.content;
85
+ // Format and output the chunk with syntax highlighting
86
+ const formatted = formatter.processChunk(chunk.content);
87
+ if (formatted) {
88
+ process.stdout.write(formatted);
89
+ }
90
+ }
91
+ // Handle tool calls
92
+ if (chunk.tool_calls && chunk.tool_calls.length > 0) {
93
+ toolCalls = chunk.tool_calls;
94
+ }
95
+ }
96
+ // Flush any remaining content in the formatter
97
+ const remaining = formatter.flush();
98
+ if (remaining) {
99
+ process.stdout.write(remaining);
100
+ }
101
+ // Log API response
102
+ const apiDuration = Date.now() - apiStartTime;
103
+ tracker_1.tracker.apiResponse(fullResponse.length > 0, toolCalls.length > 0, toolCalls.length, apiDuration);
104
+ }
105
+ catch (apiError) {
106
+ tracker_1.tracker.apiError(apiError.message, { stack: apiError.stack });
107
+ throw apiError;
108
+ }
109
+ process.stdout.write('\n\n');
110
+ // Log AI response if there's content
111
+ if (fullResponse.length > 0) {
112
+ tracker_1.tracker.aiResponse(fullResponse);
113
+ }
114
+ // If we have tool calls, execute them
115
+ if (toolCalls.length > 0) {
116
+ // Log AI's tool decision
117
+ tracker_1.tracker.aiToolDecision(toolCalls.map(tc => ({
118
+ name: tc.name,
119
+ args: tc.args || {}
120
+ })));
121
+ // Add AI message with tool calls
122
+ this.messages.push(new messages_1.AIMessage({
123
+ content: fullResponse,
124
+ tool_calls: toolCalls
125
+ }));
126
+ // Execute tools
127
+ const results = await this.executeToolCalls(toolCalls);
128
+ // Add tool results as messages
129
+ for (let i = 0; i < toolCalls.length; i++) {
130
+ const toolCall = toolCalls[i];
131
+ const result = results[i];
132
+ this.messages.push(new messages_1.ToolMessage({
133
+ content: result.success ? result.output : `Error: ${result.error}`,
134
+ tool_call_id: toolCall.id,
135
+ name: toolCall.name
136
+ }));
137
+ }
138
+ tracker_1.tracker.iteration(iterationCount, 'Tool execution complete, continuing loop');
139
+ // Continue loop to get AI's response after tool execution
140
+ continue;
141
+ }
142
+ else {
143
+ // No tool calls, add final AI message and end loop
144
+ this.messages.push(new messages_1.AIMessage(fullResponse));
145
+ continueLoop = false;
146
+ tracker_1.tracker.iteration(iterationCount, 'No tool calls, ending loop');
147
+ }
148
+ }
149
+ if (iterationCount >= maxIterations) {
150
+ ui_1.log.warning('Maximum iteration limit reached. Ending conversation turn.');
151
+ tracker_1.tracker.warn('Max iterations reached', { iterations: iterationCount });
152
+ }
153
+ // Log success
154
+ const duration = Date.now() - startTime;
155
+ tracker_1.tracker.promptComplete(input, duration, iterationCount);
156
+ }
157
+ catch (error) {
158
+ ui_1.log.error(`Error: ${error.message}`);
159
+ tracker_1.tracker.promptFailed(input, error.message);
160
+ tracker_1.tracker.error('Chat processing failed', error);
161
+ }
162
+ }
163
+ /**
164
+ * Execute tool calls from AI
165
+ */
166
+ async executeToolCalls(toolCalls) {
167
+ const formattedCalls = toolCalls.map(tc => ({
168
+ id: tc.id || `call-${Date.now()}`,
169
+ name: tc.name,
170
+ args: tc.args || {}
171
+ }));
172
+ return await this.toolExecutor.executeMultiple(formattedCalls);
173
+ }
174
+ /**
175
+ * Auto-attach file context for mentioned files
176
+ */
177
+ async attachMentionedFiles(input) {
178
+ const allFiles = await (0, context_1.getFileTree)();
179
+ const attached = [];
180
+ for (const file of allFiles) {
181
+ const base = file.split('/').pop() || '';
182
+ const isBaseMatch = base.length > 2 && input.includes(base);
183
+ const isPathMatch = input.includes(file);
184
+ if (isPathMatch || isBaseMatch) {
185
+ const content = (0, context_1.getFileContent)(file);
186
+ if (content) {
187
+ // Add file content to the messages as context
188
+ attached.push(file);
189
+ }
190
+ }
191
+ }
192
+ return attached;
193
+ }
194
+ /**
195
+ * Clear chat history but keep system message
196
+ */
197
+ clearHistory() {
198
+ const systemMsg = this.messages[0];
199
+ this.messages = [systemMsg];
200
+ tracker_1.tracker.debug('Chat history cleared');
201
+ }
202
+ /**
203
+ * Get message count
204
+ */
205
+ getMessageCount() {
206
+ return this.messages.length;
207
+ }
208
+ /**
209
+ * Update context (e.g., if file tree changes)
210
+ */
211
+ updateContext(newContext) {
212
+ this.contextMessage = newContext;
213
+ // Re-initialize system message with new context
214
+ const systemMsg = this.messages[0];
215
+ this.messages[0] = systemMsg; // Keep the same message for now
216
+ // In a more sophisticated implementation, we'd rebuild the system message
217
+ }
218
+ }
219
+ exports.ChatHandler = ChatHandler;
@@ -0,0 +1,156 @@
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.clearSessionData = exports.setSessionData = exports.getSessionData = exports.setPreference = exports.getPreferences = exports.setActiveModel = exports.getModelConfig = exports.clearApiKey = exports.setApiKey = exports.getApiKey = exports.getConfig = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const registry_1 = require("./models/registry");
11
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.config', 'groq-cli-tool');
12
+ const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
13
+ const ensureConfigDir = () => {
14
+ if (!fs_1.default.existsSync(CONFIG_DIR)) {
15
+ fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
16
+ }
17
+ };
18
+ const readConfig = () => {
19
+ try {
20
+ if (!fs_1.default.existsSync(CONFIG_FILE))
21
+ return {};
22
+ return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf-8'));
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ };
28
+ const writeConfig = (data) => {
29
+ ensureConfigDir();
30
+ fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
31
+ };
32
+ /**
33
+ * Get full config with defaults
34
+ */
35
+ const getConfig = () => {
36
+ const config = readConfig();
37
+ // Handle legacy config format (old apiKey -> new apiKeys.groq)
38
+ if ('apiKey' in config && typeof config.apiKey === 'string') {
39
+ const legacyKey = config.apiKey;
40
+ delete config.apiKey;
41
+ config.apiKeys = { groq: legacyKey };
42
+ writeConfig(config);
43
+ }
44
+ return {
45
+ apiKeys: config.apiKeys || { groq: '' },
46
+ activeModel: config.activeModel || (0, registry_1.getDefaultModel)(),
47
+ preferences: {
48
+ temperature: config.preferences?.temperature ?? 0.7,
49
+ autoConfirm: config.preferences?.autoConfirm ?? false,
50
+ safeMode: config.preferences?.safeMode ?? true,
51
+ maxTokens: config.preferences?.maxTokens
52
+ },
53
+ session: config.session || { todos: [] }
54
+ };
55
+ };
56
+ exports.getConfig = getConfig;
57
+ /**
58
+ * Get API key (backward compatible)
59
+ */
60
+ const getApiKey = () => {
61
+ const config = (0, exports.getConfig)();
62
+ return config.apiKeys.groq || '';
63
+ };
64
+ exports.getApiKey = getApiKey;
65
+ /**
66
+ * Set API key (backward compatible)
67
+ */
68
+ const setApiKey = (key) => {
69
+ const config = readConfig();
70
+ if (!config.apiKeys) {
71
+ config.apiKeys = { groq: '' };
72
+ }
73
+ config.apiKeys.groq = key;
74
+ writeConfig(config);
75
+ };
76
+ exports.setApiKey = setApiKey;
77
+ /**
78
+ * Clear API key (backward compatible)
79
+ */
80
+ const clearApiKey = () => {
81
+ const config = readConfig();
82
+ if (config.apiKeys) {
83
+ config.apiKeys.groq = '';
84
+ }
85
+ writeConfig(config);
86
+ };
87
+ exports.clearApiKey = clearApiKey;
88
+ /**
89
+ * Get active model configuration
90
+ */
91
+ const getModelConfig = () => {
92
+ const config = (0, exports.getConfig)();
93
+ return {
94
+ modelId: config.activeModel,
95
+ temperature: config.preferences.temperature
96
+ };
97
+ };
98
+ exports.getModelConfig = getModelConfig;
99
+ /**
100
+ * Set active model
101
+ */
102
+ const setActiveModel = (modelId) => {
103
+ const config = readConfig();
104
+ config.activeModel = modelId;
105
+ writeConfig(config);
106
+ };
107
+ exports.setActiveModel = setActiveModel;
108
+ /**
109
+ * Get user preferences
110
+ */
111
+ const getPreferences = () => {
112
+ return (0, exports.getConfig)().preferences;
113
+ };
114
+ exports.getPreferences = getPreferences;
115
+ /**
116
+ * Set a preference
117
+ */
118
+ const setPreference = (key, value) => {
119
+ const config = readConfig();
120
+ if (!config.preferences) {
121
+ config.preferences = {
122
+ temperature: 0.7,
123
+ autoConfirm: false,
124
+ safeMode: true
125
+ };
126
+ }
127
+ config.preferences[key] = value;
128
+ writeConfig(config);
129
+ };
130
+ exports.setPreference = setPreference;
131
+ /**
132
+ * Get session data (todos, etc.)
133
+ */
134
+ const getSessionData = () => {
135
+ const config = (0, exports.getConfig)();
136
+ return config.session || { todos: [] };
137
+ };
138
+ exports.getSessionData = getSessionData;
139
+ /**
140
+ * Set session data
141
+ */
142
+ const setSessionData = (data) => {
143
+ const config = readConfig();
144
+ config.session = data;
145
+ writeConfig(config);
146
+ };
147
+ exports.setSessionData = setSessionData;
148
+ /**
149
+ * Clear session data
150
+ */
151
+ const clearSessionData = () => {
152
+ const config = readConfig();
153
+ config.session = { todos: [] };
154
+ writeConfig(config);
155
+ };
156
+ exports.clearSessionData = clearSessionData;
@@ -0,0 +1,44 @@
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.writeFile = exports.getFileContent = exports.getFileTree = exports.isGitRepo = void 0;
7
+ const simple_git_1 = __importDefault(require("simple-git"));
8
+ const glob_1 = require("glob");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const git = (0, simple_git_1.default)();
11
+ const isGitRepo = async () => {
12
+ try {
13
+ return await git.checkIsRepo();
14
+ }
15
+ catch (e) {
16
+ return false;
17
+ }
18
+ };
19
+ exports.isGitRepo = isGitRepo;
20
+ const getFileTree = async (cwd = process.cwd()) => {
21
+ // Ignore node_modules, .git, dist, etc.
22
+ const ignore = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.env', '**/*.lock'];
23
+ const files = await (0, glob_1.glob)('**/*', { cwd, ignore, nodir: true });
24
+ return files;
25
+ };
26
+ exports.getFileTree = getFileTree;
27
+ const getFileContent = (filePath) => {
28
+ try {
29
+ return fs_1.default.readFileSync(filePath, 'utf-8');
30
+ }
31
+ catch (e) {
32
+ return '';
33
+ }
34
+ };
35
+ exports.getFileContent = getFileContent;
36
+ const writeFile = (filePath, content) => {
37
+ try {
38
+ fs_1.default.writeFileSync(filePath, content, 'utf-8');
39
+ }
40
+ catch (e) {
41
+ throw e;
42
+ }
43
+ };
44
+ exports.writeFile = writeFile;
@@ -0,0 +1,53 @@
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.fetchGroqModels = fetchGroqModels;
7
+ exports.groupModelsByOwner = groupModelsByOwner;
8
+ exports.formatContextWindow = formatContextWindow;
9
+ const groq_sdk_1 = __importDefault(require("groq-sdk"));
10
+ /**
11
+ * Fetch available models from Groq API
12
+ */
13
+ async function fetchGroqModels(apiKey) {
14
+ try {
15
+ const groq = new groq_sdk_1.default({ apiKey });
16
+ const response = await groq.models.list();
17
+ // Filter and sort models
18
+ const models = response.data
19
+ .filter(m => m.active !== false)
20
+ .sort((a, b) => a.id.localeCompare(b.id));
21
+ return models;
22
+ }
23
+ catch (error) {
24
+ console.error('Failed to fetch models:', error.message);
25
+ return [];
26
+ }
27
+ }
28
+ /**
29
+ * Group models by provider/owner
30
+ */
31
+ function groupModelsByOwner(models) {
32
+ const groups = new Map();
33
+ for (const model of models) {
34
+ const owner = model.owned_by || 'Other';
35
+ if (!groups.has(owner)) {
36
+ groups.set(owner, []);
37
+ }
38
+ groups.get(owner).push(model);
39
+ }
40
+ return groups;
41
+ }
42
+ /**
43
+ * Format context window size
44
+ */
45
+ function formatContextWindow(tokens) {
46
+ if (tokens >= 1000000) {
47
+ return `${(tokens / 1000000).toFixed(0)}M`;
48
+ }
49
+ if (tokens >= 1000) {
50
+ return `${(tokens / 1000).toFixed(0)}K`;
51
+ }
52
+ return `${tokens}`;
53
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamChat = exports.initGroq = void 0;
4
+ const groq_1 = require("@langchain/groq");
5
+ const messages_1 = require("@langchain/core/messages");
6
+ const config_1 = require("./config");
7
+ let model = null;
8
+ const initGroq = () => {
9
+ const apiKey = (0, config_1.getApiKey)();
10
+ if (!apiKey) {
11
+ throw new Error('API Key not found');
12
+ }
13
+ model = new groq_1.ChatGroq({
14
+ apiKey,
15
+ model: 'llama-3.3-70b-versatile',
16
+ temperature: 0.7
17
+ });
18
+ };
19
+ exports.initGroq = initGroq;
20
+ const streamChat = async (messages) => {
21
+ if (!model)
22
+ (0, exports.initGroq)();
23
+ const langChainMessages = messages.map(m => {
24
+ if (m.role === 'system')
25
+ return new messages_1.SystemMessage(m.content);
26
+ if (m.role === 'user')
27
+ return new messages_1.HumanMessage(m.content);
28
+ return new messages_1.AIMessage(m.content);
29
+ });
30
+ const stream = await model.stream(langChainMessages);
31
+ return stream;
32
+ };
33
+ exports.streamChat = streamChat;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GroqProvider = void 0;
4
+ const groq_1 = require("@langchain/groq");
5
+ const provider_1 = require("./provider");
6
+ /**
7
+ * Groq model provider implementation
8
+ * Supports multiple Groq models with function calling
9
+ */
10
+ class GroqProvider extends provider_1.ModelProvider {
11
+ constructor(modelId = 'llama-3.3-70b-versatile', temperature = 0.7) {
12
+ super();
13
+ this.name = 'groq';
14
+ this.supportsFunctionCalling = true;
15
+ this.model = null;
16
+ this.apiKey = '';
17
+ this.temperature = 0.7;
18
+ this.modelId = modelId;
19
+ this.temperature = temperature;
20
+ }
21
+ /**
22
+ * Initialize the Groq model
23
+ */
24
+ initialize(apiKey, modelId) {
25
+ this.apiKey = apiKey;
26
+ if (modelId) {
27
+ this.modelId = modelId;
28
+ }
29
+ this.model = new groq_1.ChatGroq({
30
+ apiKey: this.apiKey,
31
+ model: this.modelId,
32
+ temperature: this.temperature
33
+ });
34
+ }
35
+ /**
36
+ * Stream chat with optional tool support
37
+ */
38
+ async *streamChat(messages, tools) {
39
+ if (!this.model) {
40
+ throw new Error('Model not initialized. Call initialize() first.');
41
+ }
42
+ // Bind tools if provided
43
+ const modelToUse = tools && tools.length > 0
44
+ ? this.model.bindTools(tools)
45
+ : this.model;
46
+ // Stream the response
47
+ const stream = await modelToUse.stream(messages);
48
+ for await (const chunk of stream) {
49
+ yield chunk;
50
+ }
51
+ }
52
+ /**
53
+ * Get model information
54
+ */
55
+ getInfo() {
56
+ return {
57
+ id: this.modelId,
58
+ provider: 'groq',
59
+ capabilities: ['chat', 'streaming', 'function_calling'],
60
+ description: `Groq model: ${this.modelId}`
61
+ };
62
+ }
63
+ /**
64
+ * Update model configuration
65
+ */
66
+ setModel(modelId) {
67
+ this.modelId = modelId;
68
+ if (this.apiKey) {
69
+ this.initialize(this.apiKey, modelId);
70
+ }
71
+ }
72
+ /**
73
+ * Update temperature
74
+ */
75
+ setTemperature(temperature) {
76
+ this.temperature = temperature;
77
+ if (this.apiKey) {
78
+ this.initialize(this.apiKey, this.modelId);
79
+ }
80
+ }
81
+ }
82
+ exports.GroqProvider = GroqProvider;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModelProvider = void 0;
4
+ /**
5
+ * Abstract base class for model providers
6
+ * Implementations handle specific AI service integrations
7
+ */
8
+ class ModelProvider {
9
+ }
10
+ exports.ModelProvider = ModelProvider;