protoagent 0.0.3 → 0.0.4

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.
@@ -5,6 +5,7 @@ import { getModelConfig } from './config/providers.js';
5
5
  import { estimateTokens, createUsageInfo, logUsageInfo, getContextInfo } from './utils/cost-tracker.js';
6
6
  import { checkAndCompactIfNeeded } from './utils/conversation-compactor.js';
7
7
  import { logger } from './utils/logger.js';
8
+ import { interruptHandler, UserInterruptError } from './utils/interrupt-handler.js';
8
9
  // Create system message for ProtoAgent dynamically
9
10
  async function createSystemMessage() {
10
11
  const systemPrompt = await generateSystemPrompt();
@@ -70,7 +71,13 @@ export class AgenticLoop {
70
71
  role: 'user',
71
72
  content: userInput
72
73
  });
73
- logger.consoleLog('šŸ¤” Thinking...');
74
+ // Show a subtle hint about Q key on first use (only if this is the first interaction)
75
+ if (this.messages.length === 2) { // System message + first user message
76
+ logger.consoleLog('šŸ¤” Thinking... (press Q to pause)');
77
+ }
78
+ else {
79
+ logger.consoleLog('šŸ¤” Thinking...');
80
+ }
74
81
  logger.info('šŸ¤” AI thinking process started');
75
82
  logger.debug('🧠 Starting thinking process', {
76
83
  component: 'AgenticLoop',
@@ -143,84 +150,103 @@ export class AgenticLoop {
143
150
  let hasToolCalls = false;
144
151
  let actualUsage = undefined;
145
152
  let chunkCount = 0;
146
- for await (const chunk of stream) {
147
- chunkCount++;
148
- const delta = chunk.choices[0]?.delta;
149
- logger.trace('šŸ“¦ Processing chunk', {
150
- component: 'AgenticLoop',
151
- chunkNumber: chunkCount,
152
- hasContent: !!delta?.content,
153
- hasToolCalls: !!delta?.tool_calls,
154
- finishReason: chunk.choices[0]?.finish_reason
155
- });
156
- if (chunk.usage) {
157
- actualUsage = chunk.usage;
158
- logger.trace('šŸ“Š Received usage data', {
159
- component: 'AgenticLoop',
160
- promptTokens: chunk.usage.prompt_tokens,
161
- completionTokens: chunk.usage.completion_tokens,
162
- totalTokens: chunk.usage.total_tokens
163
- });
164
- }
165
- if (delta?.content) {
166
- streamedContent += delta.content;
167
- assistantMessage.content = streamedContent;
168
- // Stream content to user in real-time if no tool calls are being made
169
- if (this.options.streamOutput && !hasToolCalls && !delta.tool_calls) {
170
- if (streamedContent === delta.content) {
171
- // First content chunk
172
- process.stdout.write('\nšŸ¤– ProtoAgent: ');
173
- }
174
- process.stdout.write(delta.content);
153
+ // Start listening for 'Q' key interrupts during streaming
154
+ interruptHandler.startListening();
155
+ try {
156
+ for await (const chunk of stream) {
157
+ // Check for user interrupt
158
+ if (interruptHandler.isInterrupted()) {
159
+ logger.debug('User requested pause during streaming', { component: 'AgenticLoop' });
160
+ break;
175
161
  }
176
- }
177
- if (delta?.tool_calls) {
178
- hasToolCalls = true;
179
- logger.trace('šŸ”§ Tool calls detected in delta', {
162
+ chunkCount++;
163
+ const delta = chunk.choices[0]?.delta;
164
+ logger.trace('šŸ“¦ Processing chunk', {
180
165
  component: 'AgenticLoop',
181
- toolCallsCount: delta.tool_calls.length
166
+ chunkNumber: chunkCount,
167
+ hasContent: !!delta?.content,
168
+ hasToolCalls: !!delta?.tool_calls,
169
+ finishReason: chunk.choices[0]?.finish_reason
182
170
  });
183
- // Initialize tool_calls array if not exists
184
- if (!assistantMessage.tool_calls) {
185
- assistantMessage.tool_calls = [];
186
- }
187
- // Handle tool calls in streaming
188
- for (const toolCallDelta of delta.tool_calls) {
189
- const index = toolCallDelta.index || 0;
190
- logger.trace('šŸ› ļø Processing tool call delta', {
171
+ if (chunk.usage) {
172
+ actualUsage = chunk.usage;
173
+ logger.trace('šŸ“Š Received usage data', {
191
174
  component: 'AgenticLoop',
192
- index,
193
- hasId: !!toolCallDelta.id,
194
- hasName: !!toolCallDelta.function?.name,
195
- hasArgs: !!toolCallDelta.function?.arguments
175
+ promptTokens: chunk.usage.prompt_tokens,
176
+ completionTokens: chunk.usage.completion_tokens,
177
+ totalTokens: chunk.usage.total_tokens
196
178
  });
197
- // Ensure we have an entry at this index
198
- if (!assistantMessage.tool_calls[index]) {
199
- assistantMessage.tool_calls[index] = {
200
- id: '',
201
- type: 'function',
202
- function: { name: '', arguments: '' }
203
- };
204
- }
205
- if (toolCallDelta.id) {
206
- assistantMessage.tool_calls[index].id = toolCallDelta.id;
179
+ }
180
+ if (delta?.content) {
181
+ streamedContent += delta.content;
182
+ assistantMessage.content = streamedContent;
183
+ // Stream content to user in real-time if no tool calls are being made
184
+ if (this.options.streamOutput && !hasToolCalls && !delta.tool_calls) {
185
+ if (streamedContent === delta.content) {
186
+ // First content chunk
187
+ process.stdout.write('\nšŸ¤– ProtoAgent: ');
188
+ }
189
+ process.stdout.write(delta.content);
207
190
  }
208
- if (toolCallDelta.function?.name) {
209
- assistantMessage.tool_calls[index].function.name += toolCallDelta.function.name;
191
+ }
192
+ if (delta?.tool_calls) {
193
+ hasToolCalls = true;
194
+ logger.trace('šŸ”§ Tool calls detected in delta', {
195
+ component: 'AgenticLoop',
196
+ toolCallsCount: delta.tool_calls.length
197
+ });
198
+ // Initialize tool_calls array if not exists
199
+ if (!assistantMessage.tool_calls) {
200
+ assistantMessage.tool_calls = [];
210
201
  }
211
- if (toolCallDelta.function?.arguments) {
212
- assistantMessage.tool_calls[index].function.arguments += toolCallDelta.function.arguments;
202
+ // Handle tool calls in streaming
203
+ for (const toolCallDelta of delta.tool_calls) {
204
+ const index = toolCallDelta.index || 0;
205
+ logger.trace('šŸ› ļø Processing tool call delta', {
206
+ component: 'AgenticLoop',
207
+ index,
208
+ hasId: !!toolCallDelta.id,
209
+ hasName: !!toolCallDelta.function?.name,
210
+ hasArgs: !!toolCallDelta.function?.arguments
211
+ });
212
+ // Ensure we have an entry at this index
213
+ if (!assistantMessage.tool_calls[index]) {
214
+ assistantMessage.tool_calls[index] = {
215
+ id: '',
216
+ type: 'function',
217
+ function: { name: '', arguments: '' }
218
+ };
219
+ }
220
+ if (toolCallDelta.id) {
221
+ assistantMessage.tool_calls[index].id = toolCallDelta.id;
222
+ }
223
+ if (toolCallDelta.function?.name) {
224
+ assistantMessage.tool_calls[index].function.name += toolCallDelta.function.name;
225
+ }
226
+ if (toolCallDelta.function?.arguments) {
227
+ assistantMessage.tool_calls[index].function.arguments += toolCallDelta.function.arguments;
228
+ }
213
229
  }
214
230
  }
215
231
  }
232
+ logger.debug('āœ… Stream processing complete', {
233
+ component: 'AgenticLoop',
234
+ totalChunks: chunkCount,
235
+ finalContentLength: streamedContent.length,
236
+ finalToolCallsCount: assistantMessage.tool_calls?.length || 0,
237
+ hasActualUsage: !!actualUsage
238
+ });
239
+ }
240
+ finally {
241
+ // Always stop listening for interrupts when streaming ends
242
+ interruptHandler.stopListening();
243
+ }
244
+ // Check if streaming was interrupted
245
+ if (interruptHandler.isInterrupted()) {
246
+ logger.debug('User paused during streaming', { component: 'AgenticLoop' });
247
+ interruptHandler.reset();
248
+ throw new UserInterruptError('User paused during streaming');
216
249
  }
217
- logger.debug('āœ… Stream processing complete', {
218
- component: 'AgenticLoop',
219
- totalChunks: chunkCount,
220
- finalContentLength: streamedContent.length,
221
- finalToolCallsCount: assistantMessage.tool_calls?.length || 0,
222
- hasActualUsage: !!actualUsage
223
- });
224
250
  const message = assistantMessage;
225
251
  // DEBUG: Log the complete AI response
226
252
  logger.debug('šŸ¤– AI Response received', {
@@ -299,65 +325,79 @@ export class AgenticLoop {
299
325
  logger.consoleLog(`šŸ”§ Using ${message.tool_calls.length} tool(s)...`);
300
326
  logger.info(`šŸ”§ Tool usage message displayed: ${message.tool_calls.length} tools`);
301
327
  // Execute each tool call
302
- for (const toolCall of message.tool_calls) {
303
- const { name, arguments: args } = toolCall.function;
304
- logger.debug('šŸ› ļø Executing tool', {
305
- component: 'AgenticLoop',
306
- toolName: name,
307
- toolId: toolCall.id,
308
- argsLength: args.length
309
- });
310
- logger.consoleLog(`šŸ› ļø ${name}`);
311
- logger.info(`šŸ› ļø Tool execution message displayed: ${name}`);
312
- try {
313
- const toolArgs = JSON.parse(args);
314
- logger.debug('šŸ“‹ Tool arguments parsed', {
315
- component: 'AgenticLoop',
316
- toolName: name,
317
- parsedArgs: Object.keys(toolArgs)
318
- });
319
- const startTime = Date.now();
320
- const result = await handleToolCall(name, toolArgs);
321
- const executionTime = Date.now() - startTime;
322
- logger.debug('āœ… Tool execution successful', {
328
+ // Start listening for interrupts during tool execution
329
+ interruptHandler.startListening();
330
+ try {
331
+ for (const toolCall of message.tool_calls) {
332
+ // Check for user interrupt before each tool
333
+ if (interruptHandler.isInterrupted()) {
334
+ logger.debug('User requested pause during tool execution', { component: 'AgenticLoop' });
335
+ interruptHandler.reset();
336
+ throw new UserInterruptError('User paused during tool execution');
337
+ }
338
+ const { name, arguments: args } = toolCall.function;
339
+ logger.debug('šŸ› ļø Executing tool', {
323
340
  component: 'AgenticLoop',
324
341
  toolName: name,
325
- executionTime,
326
- resultLength: result.length
327
- });
328
- // Add tool result to conversation
329
- this.addMessage({
330
- role: 'tool',
331
- tool_call_id: toolCall.id,
332
- content: result
342
+ toolId: toolCall.id,
343
+ argsLength: args.length
333
344
  });
334
- // Show abbreviated result to user
335
- const lines = result.split('\n');
336
- if (lines.length > 10) {
337
- logger.consoleLog(` āœ… ${lines.slice(0, 3).join('\n ')}\n ... (${lines.length - 6} more lines) ...\n ${lines.slice(-3).join('\n ')}`);
338
- logger.info(` āœ… Tool result displayed (abbreviated, ${lines.length} lines)`);
345
+ logger.consoleLog(`šŸ› ļø ${name}`);
346
+ logger.info(`šŸ› ļø Tool execution message displayed: ${name}`);
347
+ try {
348
+ const toolArgs = JSON.parse(args);
349
+ logger.debug('šŸ“‹ Tool arguments parsed', {
350
+ component: 'AgenticLoop',
351
+ toolName: name,
352
+ parsedArgs: Object.keys(toolArgs)
353
+ });
354
+ const startTime = Date.now();
355
+ const result = await handleToolCall(name, toolArgs);
356
+ const executionTime = Date.now() - startTime;
357
+ logger.debug('āœ… Tool execution successful', {
358
+ component: 'AgenticLoop',
359
+ toolName: name,
360
+ executionTime,
361
+ resultLength: result.length
362
+ });
363
+ // Add tool result to conversation
364
+ this.addMessage({
365
+ role: 'tool',
366
+ tool_call_id: toolCall.id,
367
+ content: result
368
+ });
369
+ // Show abbreviated result to user
370
+ const lines = result.split('\n');
371
+ if (lines.length > 10) {
372
+ logger.consoleLog(` āœ… ${lines.slice(0, 3).join('\n ')}\n ... (${lines.length - 6} more lines) ...\n ${lines.slice(-3).join('\n ')}`);
373
+ logger.info(` āœ… Tool result displayed (abbreviated, ${lines.length} lines)`);
374
+ }
375
+ else {
376
+ logger.consoleLog(` āœ… ${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
377
+ logger.info(` āœ… Tool result displayed (full, ${result.length} chars)`);
378
+ }
339
379
  }
340
- else {
341
- logger.consoleLog(` āœ… ${result.slice(0, 200)}${result.length > 200 ? '...' : ''}`);
342
- logger.info(` āœ… Tool result displayed (full, ${result.length} chars)`);
380
+ catch (error) {
381
+ const errorMessage = error instanceof Error ? error.message : String(error);
382
+ logger.error('āŒ Tool execution failed', {
383
+ component: 'AgenticLoop',
384
+ toolName: name,
385
+ error: errorMessage
386
+ });
387
+ logger.consoleLog(` āŒ Error: ${errorMessage}`);
388
+ logger.error(` āŒ Tool error message displayed: ${errorMessage}`);
389
+ // Add error result to conversation
390
+ this.addMessage({
391
+ role: 'tool',
392
+ tool_call_id: toolCall.id,
393
+ content: `Error: ${errorMessage}`
394
+ });
343
395
  }
344
396
  }
345
- catch (error) {
346
- const errorMessage = error instanceof Error ? error.message : String(error);
347
- logger.error('āŒ Tool execution failed', {
348
- component: 'AgenticLoop',
349
- toolName: name,
350
- error: errorMessage
351
- });
352
- logger.consoleLog(` āŒ Error: ${errorMessage}`);
353
- logger.error(` āŒ Tool error message displayed: ${errorMessage}`);
354
- // Add error result to conversation
355
- this.addMessage({
356
- role: 'tool',
357
- tool_call_id: toolCall.id,
358
- content: `Error: ${errorMessage}`
359
- });
360
- }
397
+ }
398
+ finally {
399
+ // Always stop listening for interrupts when tool execution ends
400
+ interruptHandler.stopListening();
361
401
  }
362
402
  // Continue the loop to let AI process tool results
363
403
  logger.trace('šŸ”„ Continuing agentic loop for AI to process tool results', {
@@ -426,6 +466,15 @@ export class AgenticLoop {
426
466
  }
427
467
  }
428
468
  catch (apiError) {
469
+ // Check if this is a user interrupt first
470
+ if (apiError instanceof UserInterruptError) {
471
+ logger.warn('šŸ›‘ User pressed Q - interrupt during processing', {
472
+ component: 'AgenticLoop',
473
+ reason: apiError.message
474
+ });
475
+ console.log('\n'); // Just a clean newline
476
+ return;
477
+ }
429
478
  // Handle API errors that weren't caught by the retry logic
430
479
  logger.trace('āŒ API Error occurred - stopping agentic loop', {
431
480
  component: 'AgenticLoop',
@@ -474,6 +523,15 @@ export class AgenticLoop {
474
523
  }
475
524
  }
476
525
  catch (error) {
526
+ // Check if this is a user interrupt
527
+ if (error instanceof UserInterruptError) {
528
+ logger.debug('šŸ›‘ User interrupt detected - returning control to user', {
529
+ component: 'AgenticLoop',
530
+ reason: error.message
531
+ });
532
+ logger.consoleLog('\n');
533
+ return;
534
+ }
477
535
  // Handle general processing errors
478
536
  console.error('\nāŒ Error during processing:', error?.message || 'Unknown error');
479
537
  logger.error('\nāŒ General processing error displayed to user', {
@@ -2,7 +2,7 @@
2
2
  * OpenAI client manager for ProtoAgent
3
3
  */
4
4
  import OpenAI from 'openai';
5
- import { geminiProvider, cerebrasProvider, getModelConfig } from './providers.js';
5
+ import { geminiProvider, cerebrasProvider, anthropicProvider, getModelConfig } from './providers.js';
6
6
  import { estimateTokens, getContextInfo } from '../utils/cost-tracker.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  /**
@@ -40,6 +40,16 @@ export function createOpenAIClient(config) {
40
40
  baseURL: cerebrasProvider.baseURL
41
41
  });
42
42
  }
43
+ if (config.provider === 'anthropic') {
44
+ logger.debug('šŸ”‘ Using Anthropic provider', {
45
+ component: 'OpenAIClient',
46
+ baseURL: anthropicProvider.baseURL
47
+ });
48
+ return new OpenAI({
49
+ apiKey: config.credentials.ANTHROPIC_API_KEY,
50
+ baseURL: anthropicProvider.baseURL
51
+ });
52
+ }
43
53
  logger.error('āŒ Unknown provider', {
44
54
  component: 'OpenAIClient',
45
55
  provider: config.provider
@@ -4,7 +4,7 @@
4
4
  import inquirer from 'inquirer';
5
5
  import { hasConfig, loadConfig, saveConfig, getConfigDirectory } from './manager.js';
6
6
  import { setupConfig } from './setup.js';
7
- import { openaiProvider, geminiProvider, cerebrasProvider } from './providers.js';
7
+ import { openaiProvider, geminiProvider, cerebrasProvider, anthropicProvider } from './providers.js';
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import { logger } from '../utils/logger.js';
@@ -46,6 +46,9 @@ export async function showCurrentConfig() {
46
46
  else if (config.provider === 'cerebras' && config.credentials.CEREBRAS_API_KEY) {
47
47
  logger.consoleLog(`API Key: ${maskApiKey(config.credentials.CEREBRAS_API_KEY)}`);
48
48
  }
49
+ else if (config.provider === 'anthropic' && config.credentials.ANTHROPIC_API_KEY) {
50
+ logger.consoleLog(`API Key: ${maskApiKey(config.credentials.ANTHROPIC_API_KEY)}`);
51
+ }
49
52
  logger.consoleLog(`Config Location: ${configDir}/config.json`);
50
53
  logger.consoleLog('─'.repeat(40));
51
54
  }
@@ -84,6 +87,11 @@ export async function updateApiKey() {
84
87
  helpUrl = 'https://cloud.cerebras.ai/platform';
85
88
  keyPrefix = '';
86
89
  }
90
+ else if (config.provider === 'anthropic') {
91
+ currentKey = config.credentials.ANTHROPIC_API_KEY || '';
92
+ helpUrl = 'https://console.anthropic.com/settings/keys';
93
+ keyPrefix = 'sk-';
94
+ }
87
95
  logger.consoleLog(`Current API Key: ${maskApiKey(currentKey)}`);
88
96
  logger.consoleLog(`Get your API key from: ${helpUrl}\n`);
89
97
  const { newApiKey } = await inquirer.prompt([
@@ -113,6 +121,9 @@ export async function updateApiKey() {
113
121
  else if (config.provider === 'cerebras') {
114
122
  config.credentials.CEREBRAS_API_KEY = newApiKey.trim();
115
123
  }
124
+ else if (config.provider === 'anthropic') {
125
+ config.credentials.ANTHROPIC_API_KEY = newApiKey.trim();
126
+ }
116
127
  await saveConfig(config);
117
128
  logger.consoleLog('\nāœ… API key updated successfully!');
118
129
  logger.consoleLog(`New API Key: ${maskApiKey(newApiKey)}`);
@@ -145,6 +156,9 @@ export async function updateModel() {
145
156
  else if (config.provider === 'cerebras') {
146
157
  availableModels = cerebrasProvider.models;
147
158
  }
159
+ else if (config.provider === 'anthropic') {
160
+ availableModels = anthropicProvider.models;
161
+ }
148
162
  const { newModel } = await inquirer.prompt([
149
163
  {
150
164
  type: 'list',
@@ -153,6 +153,63 @@ export const cerebrasProvider = {
153
153
  },
154
154
  baseURL: 'https://api.cerebras.ai/v1'
155
155
  };
156
+ export const anthropicModels = {
157
+ models: [
158
+ 'claude-3-5-sonnet-4-20250126',
159
+ 'claude-3-5-sonnet-20241022',
160
+ 'claude-3-5-haiku-20241022',
161
+ 'claude-3-opus-20240229',
162
+ 'claude-3-sonnet-20240229',
163
+ 'claude-3-haiku-20240307'
164
+ ],
165
+ defaultModel: 'claude-3-5-sonnet-4-20250126'
166
+ };
167
+ export const anthropicModelConfigs = {
168
+ 'claude-3-5-sonnet-4-20250126': {
169
+ name: 'Claude 3.5 Sonnet 4',
170
+ contextWindow: 200000,
171
+ pricing: { inputTokens: 0.000003, outputTokens: 0.000015 } // $3.00/$15.00 per 1M tokens
172
+ },
173
+ 'claude-3-5-sonnet-20241022': {
174
+ name: 'Claude 3.5 Sonnet',
175
+ contextWindow: 200000,
176
+ pricing: { inputTokens: 0.000003, outputTokens: 0.000015 } // $3.00/$15.00 per 1M tokens
177
+ },
178
+ 'claude-3-5-haiku-20241022': {
179
+ name: 'Claude 3.5 Haiku',
180
+ contextWindow: 200000,
181
+ pricing: { inputTokens: 0.0000008, outputTokens: 0.000004 } // $0.80/$4.00 per 1M tokens
182
+ },
183
+ 'claude-3-opus-20240229': {
184
+ name: 'Claude 3 Opus',
185
+ contextWindow: 200000,
186
+ pricing: { inputTokens: 0.000015, outputTokens: 0.000075 } // $15.00/$75.00 per 1M tokens
187
+ },
188
+ 'claude-3-sonnet-20240229': {
189
+ name: 'Claude 3 Sonnet',
190
+ contextWindow: 200000,
191
+ pricing: { inputTokens: 0.000003, outputTokens: 0.000015 } // $3.00/$15.00 per 1M tokens
192
+ },
193
+ 'claude-3-haiku-20240307': {
194
+ name: 'Claude 3 Haiku',
195
+ contextWindow: 200000,
196
+ pricing: { inputTokens: 0.00000025, outputTokens: 0.00000125 } // $0.25/$1.25 per 1M tokens
197
+ }
198
+ };
199
+ export const anthropicProvider = {
200
+ id: 'anthropic',
201
+ name: 'Anthropic',
202
+ models: anthropicModels.models,
203
+ defaultModel: anthropicModels.defaultModel,
204
+ modelConfigs: anthropicModelConfigs,
205
+ auth: {
206
+ type: 'api_key',
207
+ envVar: 'ANTHROPIC_API_KEY',
208
+ label: 'Anthropic API Key',
209
+ helpUrl: 'https://console.anthropic.com/settings/keys'
210
+ },
211
+ baseURL: 'https://api.anthropic.com/v1'
212
+ };
156
213
  /**
157
214
  * Get model configuration for any provider
158
215
  */
@@ -164,6 +221,8 @@ export function getModelConfig(provider, model) {
164
221
  return geminiModelConfigs[model] || null;
165
222
  case 'cerebras':
166
223
  return cerebrasModelConfigs[model] || null;
224
+ case 'anthropic':
225
+ return anthropicModelConfigs[model] || null;
167
226
  default:
168
227
  return null;
169
228
  }
@@ -2,7 +2,7 @@
2
2
  * Interactive configuration setup for ProtoAgent
3
3
  */
4
4
  import inquirer from 'inquirer';
5
- import { openaiProvider, geminiProvider, cerebrasProvider } from './providers.js';
5
+ import { openaiProvider, geminiProvider, cerebrasProvider, anthropicProvider } from './providers.js';
6
6
  import { saveConfig, getConfigDirectory } from './manager.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  /**
@@ -18,6 +18,11 @@ export async function setupConfig() {
18
18
  name: model === openaiProvider.defaultModel ? `${model} (recommended)` : model,
19
19
  value: { provider: 'openai', model }
20
20
  })),
21
+ new inquirer.Separator('--- Anthropic Models ---'),
22
+ ...anthropicProvider.models.map(model => ({
23
+ name: model === anthropicProvider.defaultModel ? `${model} (recommended)` : model,
24
+ value: { provider: 'anthropic', model }
25
+ })),
21
26
  new inquirer.Separator('--- Gemini Models ---'),
22
27
  ...geminiProvider.models.map(model => ({
23
28
  name: model === geminiProvider.defaultModel ? `${model} (recommended)` : model,
@@ -45,7 +50,8 @@ export async function setupConfig() {
45
50
  let credentials = {
46
51
  OPENAI_API_KEY: '',
47
52
  GEMINI_API_KEY: '',
48
- CEREBRAS_API_KEY: ''
53
+ CEREBRAS_API_KEY: '',
54
+ ANTHROPIC_API_KEY: ''
49
55
  };
50
56
  if (provider === 'openai') {
51
57
  logger.consoleLog(`\nšŸ“ ${openaiProvider.auth.label} Setup`);
@@ -71,6 +77,30 @@ export async function setupConfig() {
71
77
  apiKey = response.apiKey;
72
78
  credentials.OPENAI_API_KEY = apiKey;
73
79
  }
80
+ else if (provider === 'anthropic') {
81
+ logger.consoleLog(`\nšŸ“ ${anthropicProvider.auth.label} Setup`);
82
+ logger.consoleLog("You'll need an API key from Anthropic.");
83
+ logger.consoleLog(`Get your API key from: ${anthropicProvider.auth.helpUrl}`);
84
+ const response = await inquirer.prompt([
85
+ {
86
+ type: 'password',
87
+ name: 'apiKey',
88
+ message: `Enter your ${anthropicProvider.auth.label}:`,
89
+ mask: '*',
90
+ validate: (input) => {
91
+ if (!input || input.trim().length === 0) {
92
+ return 'API key is required';
93
+ }
94
+ if (!input.trim().startsWith('sk-')) {
95
+ return 'Anthropic API keys should start with "sk-"';
96
+ }
97
+ return true;
98
+ }
99
+ }
100
+ ]);
101
+ apiKey = response.apiKey;
102
+ credentials.ANTHROPIC_API_KEY = apiKey;
103
+ }
74
104
  else if (provider === 'gemini') {
75
105
  logger.consoleLog(`\nšŸ“ ${geminiProvider.auth.label} Setup`);
76
106
  logger.consoleLog("You'll need an API key from Gemini.");
@@ -121,6 +151,9 @@ export async function setupConfig() {
121
151
  if (provider === 'openai') {
122
152
  providerName = openaiProvider.name;
123
153
  }
154
+ else if (provider === 'anthropic') {
155
+ providerName = anthropicProvider.name;
156
+ }
124
157
  else if (provider === 'gemini') {
125
158
  providerName = geminiProvider.name;
126
159
  }