protoagent 0.0.5 → 0.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.
Files changed (58) hide show
  1. package/README.md +99 -19
  2. package/dist/App.js +602 -0
  3. package/dist/agentic-loop.js +492 -525
  4. package/dist/cli.js +39 -0
  5. package/dist/components/CollapsibleBox.js +26 -0
  6. package/dist/components/ConfigDialog.js +40 -0
  7. package/dist/components/ConsolidatedToolMessage.js +41 -0
  8. package/dist/components/FormattedMessage.js +93 -0
  9. package/dist/components/Table.js +275 -0
  10. package/dist/config.js +171 -0
  11. package/dist/mcp.js +170 -0
  12. package/dist/providers.js +137 -0
  13. package/dist/sessions.js +161 -0
  14. package/dist/skills.js +229 -0
  15. package/dist/sub-agent.js +103 -0
  16. package/dist/system-prompt.js +131 -0
  17. package/dist/tools/bash.js +178 -0
  18. package/dist/tools/edit-file.js +65 -171
  19. package/dist/tools/index.js +79 -134
  20. package/dist/tools/list-directory.js +20 -73
  21. package/dist/tools/read-file.js +57 -101
  22. package/dist/tools/search-files.js +74 -162
  23. package/dist/tools/todo.js +57 -140
  24. package/dist/tools/webfetch.js +310 -0
  25. package/dist/tools/write-file.js +44 -135
  26. package/dist/utils/approval.js +69 -0
  27. package/dist/utils/compactor.js +87 -0
  28. package/dist/utils/cost-tracker.js +26 -81
  29. package/dist/utils/format-message.js +26 -0
  30. package/dist/utils/logger.js +101 -307
  31. package/dist/utils/path-validation.js +74 -0
  32. package/package.json +45 -51
  33. package/LICENSE +0 -21
  34. package/dist/config/client.js +0 -315
  35. package/dist/config/commands.js +0 -223
  36. package/dist/config/manager.js +0 -117
  37. package/dist/config/mcp-commands.js +0 -266
  38. package/dist/config/mcp-manager.js +0 -240
  39. package/dist/config/mcp-types.js +0 -28
  40. package/dist/config/providers.js +0 -229
  41. package/dist/config/setup.js +0 -209
  42. package/dist/config/system-prompt.js +0 -397
  43. package/dist/config/types.js +0 -4
  44. package/dist/index.js +0 -229
  45. package/dist/tools/create-directory.js +0 -76
  46. package/dist/tools/directory-operations.js +0 -195
  47. package/dist/tools/file-operations.js +0 -211
  48. package/dist/tools/run-shell-command.js +0 -746
  49. package/dist/tools/search-operations.js +0 -179
  50. package/dist/tools/shell-operations.js +0 -342
  51. package/dist/tools/task-complete.js +0 -26
  52. package/dist/tools/view-directory-tree.js +0 -125
  53. package/dist/tools.js +0 -2
  54. package/dist/utils/conversation-compactor.js +0 -140
  55. package/dist/utils/enhanced-prompt.js +0 -23
  56. package/dist/utils/file-operations-approval.js +0 -373
  57. package/dist/utils/interrupt-handler.js +0 -127
  58. package/dist/utils/user-cancellation.js +0 -34
package/package.json CHANGED
@@ -1,61 +1,55 @@
1
1
  {
2
2
  "name": "protoagent",
3
- "version": "0.0.5",
4
- "description": "Interactive AI coding agent CLI with file system capabilities and shell command execution",
3
+ "version": "0.1.0",
5
4
  "type": "module",
6
- "main": "dist/index.js",
5
+ "files": [
6
+ "dist",
7
+ "README.md"
8
+ ],
7
9
  "bin": {
8
- "protoagent": "dist/index.js"
10
+ "protoagent": "./dist/cli.js"
9
11
  },
10
12
  "scripts": {
11
- "build": "tsc",
12
- "dev": "tsx src/index.ts",
13
- "start": "node dist/index.js",
14
- "prepublishOnly": "npm run build",
15
- "prepack": "npm run build"
16
- },
17
- "keywords": [
18
- "cli",
19
- "ai",
20
- "coding-assistant",
21
- "agent",
22
- "coding",
23
- "typescript",
24
- "interactive",
25
- "openai",
26
- "file-system",
27
- "shell-commands",
28
- "developer-tools"
29
- ],
30
- "author": "Thomas Gauvin",
31
- "license": "MIT",
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/thomasgauvin/protoagent.git"
35
- },
36
- "bugs": {
37
- "url": "https://github.com/thomasgauvin/protoagent/issues"
38
- },
39
- "homepage": "https://github.com/thomasgauvin/protoagent#readme",
40
- "devDependencies": {
41
- "@types/inquirer": "^9.0.9",
42
- "@types/node": "^20.0.0",
43
- "dotenv": "^17.2.1",
44
- "tsx": "^4.7.0",
45
- "typescript": "^5.0.0"
13
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
14
+ "dev": "tsx src/cli.tsx",
15
+ "test": "node --test --import tsx tests/**/*.test.{ts,tsx}",
16
+ "build": "npm run clean && tsc && node -e \"const fs=require('node:fs'); if (fs.existsSync('dist/cli.js')) fs.chmodSync('dist/cli.js', 0o755)\"",
17
+ "build:watch": "tsc --watch",
18
+ "prepack": "npm run build",
19
+ "docs:dev": "vitepress dev docs",
20
+ "docs:build": "vitepress build docs",
21
+ "docs:preview": "vitepress preview docs"
46
22
  },
23
+ "author": "",
24
+ "license": "ISC",
47
25
  "dependencies": {
48
- "commander": "^11.0.0",
49
- "inquirer": "^9.3.7",
50
- "openai": "^5.11.0",
51
- "zod": "^3.25.76"
52
- },
53
- "engines": {
54
- "node": ">=16.0.0"
26
+ "@inkjs/ui": "^2.0.0",
27
+ "@modelcontextprotocol/sdk": "^1.27.1",
28
+ "commander": "^14.0.1",
29
+ "he": "^1.2.0",
30
+ "html-to-text": "^9.0.5",
31
+ "ink": "^6.7.0",
32
+ "ink-big-text": "^2.0.0",
33
+ "openai": "^5.23.1",
34
+ "react": "^19.1.1",
35
+ "turndown": "^7.2.2",
36
+ "yaml": "^2.8.2",
37
+ "yoga-layout": "^3.2.1"
55
38
  },
56
- "files": [
57
- "dist/**/*",
58
- "README.md",
59
- "LICENSE"
60
- ]
39
+ "devDependencies": {
40
+ "@eslint/js": "^9.36.0",
41
+ "@tailwindcss/postcss": "^4.1.18",
42
+ "@types/he": "^1.2.3",
43
+ "@types/html-to-text": "^9.0.4",
44
+ "@types/node": "^24.5.2",
45
+ "@types/react": "^19.1.15",
46
+ "@types/turndown": "^5.0.6",
47
+ "eslint": "^9.36.0",
48
+ "ink-testing-library": "^4.0.0",
49
+ "tailwindcss": "^4.0.0",
50
+ "tsx": "^4.20.6",
51
+ "typescript": "^5.9.2",
52
+ "typescript-eslint": "^8.44.1",
53
+ "vitepress": "^1.6.4"
54
+ }
61
55
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Thomas Gauvin
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,315 +0,0 @@
1
- /**
2
- * OpenAI client manager for ProtoAgent
3
- */
4
- import OpenAI from 'openai';
5
- import { geminiProvider, cerebrasProvider, anthropicProvider, getModelConfig } from './providers.js';
6
- import { estimateTokens, getContextInfo } from '../utils/cost-tracker.js';
7
- import { logger } from '../utils/logger.js';
8
- /**
9
- * Create OpenAI client from configuration
10
- */
11
- export function createOpenAIClient(config) {
12
- logger.debug('🤖 Creating OpenAI client', {
13
- component: 'OpenAIClient',
14
- provider: config.provider,
15
- model: config.model
16
- });
17
- if (config.provider === 'openai') {
18
- logger.debug('🔑 Using OpenAI provider', { component: 'OpenAIClient' });
19
- return new OpenAI({
20
- apiKey: config.credentials.OPENAI_API_KEY
21
- });
22
- }
23
- if (config.provider === 'gemini') {
24
- logger.debug('🔑 Using Gemini provider', {
25
- component: 'OpenAIClient',
26
- baseURL: geminiProvider.baseURL
27
- });
28
- return new OpenAI({
29
- apiKey: config.credentials.GEMINI_API_KEY,
30
- baseURL: geminiProvider.baseURL
31
- });
32
- }
33
- if (config.provider === 'cerebras') {
34
- logger.debug('🔑 Using Cerebras provider', {
35
- component: 'OpenAIClient',
36
- baseURL: cerebrasProvider.baseURL
37
- });
38
- return new OpenAI({
39
- apiKey: config.credentials.CEREBRAS_API_KEY,
40
- baseURL: cerebrasProvider.baseURL
41
- });
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
- }
53
- logger.error('❌ Unknown provider', {
54
- component: 'OpenAIClient',
55
- provider: config.provider
56
- });
57
- throw new Error(`Unknown provider: ${config.provider}`);
58
- }
59
- /**
60
- * Sleep for a given number of milliseconds
61
- */
62
- function sleep(ms) {
63
- return new Promise(resolve => setTimeout(resolve, ms));
64
- }
65
- /**
66
- * Check if an error is retryable
67
- */
68
- function isRetryableError(error) {
69
- logger.debug('🔍 Checking if error is retryable', {
70
- component: 'OpenAIClient',
71
- status: error?.status,
72
- code: error?.code
73
- });
74
- // Check for rate limiting (429)
75
- if (error?.status === 429) {
76
- logger.debug('⏳ Error is rate limit (429)', { component: 'OpenAIClient' });
77
- return true;
78
- }
79
- // Check for server errors (5xx)
80
- if (error?.status >= 500 && error?.status < 600) {
81
- logger.debug('🔧 Error is server error (5xx)', {
82
- component: 'OpenAIClient',
83
- status: error.status
84
- });
85
- return true;
86
- }
87
- // Check for network/connection errors
88
- if (error?.code === 'ECONNRESET' ||
89
- error?.code === 'ENOTFOUND' ||
90
- error?.code === 'ECONNREFUSED' ||
91
- error?.message?.includes('network') ||
92
- error?.message?.includes('timeout')) {
93
- logger.debug('🌐 Error is network related', {
94
- component: 'OpenAIClient',
95
- code: error.code,
96
- message: error.message
97
- });
98
- return true;
99
- }
100
- logger.debug('❌ Error is not retryable', { component: 'OpenAIClient' });
101
- return false;
102
- }
103
- /**
104
- * Get appropriate delay for rate limiting based on error
105
- */
106
- function getRateLimitDelay(error, attempt) {
107
- logger.debug('⏱️ Calculating rate limit delay', {
108
- component: 'OpenAIClient',
109
- attempt,
110
- retryAfterHeader: error?.headers?.['retry-after']
111
- });
112
- // Check for Retry-After header (OpenAI provides this for rate limits)
113
- if (error?.headers?.['retry-after']) {
114
- const retryAfter = parseInt(error.headers['retry-after'], 10);
115
- if (!isNaN(retryAfter)) {
116
- const delayMs = retryAfter * 1000;
117
- logger.debug('📨 Using Retry-After header delay', {
118
- component: 'OpenAIClient',
119
- retryAfter,
120
- delayMs
121
- });
122
- return delayMs; // Convert to milliseconds
123
- }
124
- }
125
- // Default exponential backoff for rate limits: 2^attempt seconds (min 2s, max 60s)
126
- const baseDelay = Math.min(Math.pow(2, attempt) * 1000, 60000);
127
- const jitter = Math.random() * 1000; // Add jitter to prevent thundering herd
128
- const finalDelay = Math.max(baseDelay + jitter, 2000); // Minimum 2 seconds
129
- logger.debug('⚡ Using exponential backoff delay', {
130
- component: 'OpenAIClient',
131
- attempt,
132
- baseDelay,
133
- jitter,
134
- finalDelay
135
- });
136
- return finalDelay;
137
- }
138
- /**
139
- * Get delay for general retryable errors
140
- */
141
- function getRetryDelay(attempt) {
142
- // Exponential backoff: 1, 2, 4 seconds with jitter
143
- const baseDelay = Math.pow(2, attempt - 1) * 1000;
144
- const jitter = Math.random() * 500;
145
- const finalDelay = Math.min(baseDelay + jitter, 4000); // Max 4 seconds
146
- logger.debug('🔄 Calculating retry delay', {
147
- component: 'OpenAIClient',
148
- attempt,
149
- baseDelay,
150
- jitter,
151
- finalDelay
152
- });
153
- return finalDelay;
154
- }
155
- /**
156
- * Create chat completion with OpenAI with retry logic and cost tracking
157
- */
158
- export async function createChatCompletion(client, params, config, messages) {
159
- const maxRetries = 3;
160
- let lastError;
161
- logger.debug('🚀 Starting chat completion request', {
162
- component: 'OpenAIClient',
163
- provider: config.provider,
164
- model: config.model,
165
- messageCount: messages.length,
166
- maxRetries
167
- });
168
- // Get model configuration for cost tracking
169
- const modelConfig = getModelConfig(config.provider, config.model);
170
- // Estimate input tokens for cost calculation
171
- const estimatedInputTokens = messages.reduce((total, msg) => {
172
- if ('content' in msg && msg.content && typeof msg.content === 'string') {
173
- return total + estimateTokens(msg.content);
174
- }
175
- return total;
176
- }, 0);
177
- logger.debug('📊 Token estimation complete', {
178
- component: 'OpenAIClient',
179
- estimatedInputTokens,
180
- hasModelConfig: !!modelConfig
181
- });
182
- // Log context information before making the request
183
- if (modelConfig) {
184
- const contextInfo = getContextInfo(messages, modelConfig);
185
- logger.consoleLog(`📊 Context: ${contextInfo.currentTokens}/${contextInfo.maxTokens} tokens (${contextInfo.utilizationPercentage.toFixed(1)}%)`, {
186
- component: 'OpenAIClient',
187
- ...contextInfo
188
- });
189
- if (contextInfo.needsCompaction) {
190
- logger.consoleLog(`⚠️ Context approaching limit - automatic compaction will trigger soon`, {
191
- component: 'OpenAIClient',
192
- needsCompaction: true
193
- });
194
- }
195
- }
196
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
197
- logger.debug(`🎯 Attempt ${attempt}/${maxRetries}`, {
198
- component: 'OpenAIClient',
199
- attempt,
200
- maxRetries
201
- });
202
- try {
203
- logger.debug('📡 Making API request', {
204
- component: 'OpenAIClient',
205
- stream: params.stream || true,
206
- includeUsage: true
207
- });
208
- const stream = await client.chat.completions.create({
209
- ...params,
210
- stream: true,
211
- stream_options: { include_usage: true }
212
- });
213
- logger.debug('✅ API request successful', {
214
- component: 'OpenAIClient',
215
- attempt,
216
- estimatedInputTokens
217
- });
218
- return { stream, estimatedInputTokens };
219
- }
220
- catch (error) {
221
- lastError = error;
222
- logger.debug('❌ API request failed', {
223
- component: 'OpenAIClient',
224
- attempt,
225
- errorStatus: error?.status,
226
- errorCode: error?.code,
227
- errorMessage: error?.message
228
- });
229
- // Handle rate limiting (429) specially
230
- if (error?.status === 429) {
231
- const delay = getRateLimitDelay(error, attempt);
232
- const seconds = Math.round(delay / 1000);
233
- if (attempt < maxRetries) {
234
- logger.consoleLog(`\n⏳ Rate limited by API. Waiting ${seconds} seconds before retry (attempt ${attempt}/${maxRetries})...`, {
235
- component: 'OpenAIClient',
236
- attempt,
237
- maxRetries,
238
- delayMs: delay
239
- });
240
- await sleep(delay);
241
- continue;
242
- }
243
- else {
244
- logger.consoleLog('\n❌ Rate limit exceeded. Maximum retries reached.', {
245
- component: 'OpenAIClient',
246
- maxRetries
247
- });
248
- logger.consoleLog('💡 Tip: Consider upgrading your API plan or waiting before making more requests.');
249
- break;
250
- }
251
- }
252
- // Handle other retryable errors
253
- if (isRetryableError(error) && attempt < maxRetries) {
254
- const delay = getRetryDelay(attempt);
255
- const seconds = Math.round(delay / 1000);
256
- logger.consoleLog(`\n⚠️ API error (${error?.status || error?.code || 'unknown'}). Retrying in ${seconds} seconds (attempt ${attempt}/${maxRetries})...`, {
257
- component: 'OpenAIClient',
258
- attempt,
259
- maxRetries,
260
- errorType: error?.status || error?.code || 'unknown',
261
- delayMs: delay
262
- });
263
- await sleep(delay);
264
- continue;
265
- }
266
- // For non-retryable errors or max retries reached, break immediately
267
- logger.error('💥 Breaking retry loop', {
268
- component: 'OpenAIClient',
269
- attempt,
270
- maxRetries,
271
- isRetryable: isRetryableError(error)
272
- });
273
- break;
274
- }
275
- }
276
- // If we get here, all retries failed
277
- logger.consoleLog('\n❌ API request failed after all retry attempts.', {
278
- component: 'OpenAIClient',
279
- maxRetries,
280
- lastErrorStatus: lastError?.status,
281
- lastErrorCode: lastError?.code
282
- });
283
- // Provide user-friendly error messages
284
- if (lastError?.status === 401) {
285
- logger.consoleLog('💡 This looks like an authentication error. Check your API key configuration.');
286
- logger.consoleLog(' Run: protoagent config --update-key');
287
- logger.error('🔐 Authentication error detected', { component: 'OpenAIClient' });
288
- }
289
- else if (lastError?.status === 429) {
290
- logger.consoleLog('💡 Rate limit exceeded. Try again later or upgrade your API plan.');
291
- logger.error('⏳ Rate limit error detected', { component: 'OpenAIClient' });
292
- }
293
- else if (lastError?.status === 403) {
294
- logger.consoleLog('💡 Access forbidden. Check your API key permissions and billing status.');
295
- logger.error('🚫 Forbidden access error detected', { component: 'OpenAIClient' });
296
- }
297
- else if (lastError?.status >= 500) {
298
- logger.consoleLog('💡 Server error. The API service may be temporarily unavailable.');
299
- logger.error('🔧 Server error detected', {
300
- component: 'OpenAIClient',
301
- status: lastError.status
302
- });
303
- }
304
- else if (lastError?.code === 'ENOTFOUND' || lastError?.message?.includes('network')) {
305
- logger.consoleLog('💡 Network connection error. Check your internet connection.');
306
- logger.error('🌐 Network error detected', {
307
- component: 'OpenAIClient',
308
- code: lastError?.code
309
- });
310
- }
311
- // Suggest graceful shutdown
312
- logger.consoleLog('\n🚪 ProtoAgent will now exit. Please resolve the issue and try again.');
313
- logger.error('🚪 Exiting due to API failure', { component: 'OpenAIClient' });
314
- process.exit(1);
315
- }
@@ -1,223 +0,0 @@
1
- /**
2
- * Configuration management commands for ProtoAgent CLI
3
- */
4
- import inquirer from 'inquirer';
5
- import { hasConfig, loadConfig, saveConfig, getConfigDirectory } from './manager.js';
6
- import { setupConfig } from './setup.js';
7
- import { openaiProvider, geminiProvider, cerebrasProvider, anthropicProvider } from './providers.js';
8
- import fs from 'fs/promises';
9
- import path from 'path';
10
- import { logger } from '../utils/logger.js';
11
- /**
12
- * Mask API key for display (show first 8 and last 4 characters)
13
- */
14
- function maskApiKey(apiKey) {
15
- if (apiKey.length <= 12) {
16
- return '*'.repeat(apiKey.length);
17
- }
18
- const start = apiKey.substring(0, 8);
19
- const end = apiKey.substring(apiKey.length - 4);
20
- const middle = '*'.repeat(apiKey.length - 12);
21
- return `${start}${middle}${end}`;
22
- }
23
- /**
24
- * Show current configuration (without exposing sensitive data)
25
- */
26
- export async function showCurrentConfig() {
27
- try {
28
- const configExists = await hasConfig();
29
- if (!configExists) {
30
- logger.consoleLog('❌ No configuration found. Run ProtoAgent to set up.');
31
- return;
32
- }
33
- const config = await loadConfig();
34
- const configDir = getConfigDirectory();
35
- logger.consoleLog('\n📋 Current ProtoAgent Configuration:');
36
- logger.consoleLog('─'.repeat(40));
37
- logger.consoleLog(`Provider: ${config.provider}`);
38
- logger.consoleLog(`Model: ${config.model}`);
39
- // Show the appropriate API key based on provider
40
- if (config.provider === 'openai' && config.credentials.OPENAI_API_KEY) {
41
- logger.consoleLog(`API Key: ${maskApiKey(config.credentials.OPENAI_API_KEY)}`);
42
- }
43
- else if (config.provider === 'gemini' && config.credentials.GEMINI_API_KEY) {
44
- logger.consoleLog(`API Key: ${maskApiKey(config.credentials.GEMINI_API_KEY)}`);
45
- }
46
- else if (config.provider === 'cerebras' && config.credentials.CEREBRAS_API_KEY) {
47
- logger.consoleLog(`API Key: ${maskApiKey(config.credentials.CEREBRAS_API_KEY)}`);
48
- }
49
- else if (config.provider === 'anthropic' && config.credentials.ANTHROPIC_API_KEY) {
50
- logger.consoleLog(`API Key: ${maskApiKey(config.credentials.ANTHROPIC_API_KEY)}`);
51
- }
52
- logger.consoleLog(`Config Location: ${configDir}/config.json`);
53
- logger.consoleLog('─'.repeat(40));
54
- }
55
- catch (error) {
56
- console.error(`❌ Error reading configuration: ${error.message}`);
57
- }
58
- }
59
- /**
60
- * Update API key
61
- */
62
- export async function updateApiKey() {
63
- try {
64
- const configExists = await hasConfig();
65
- if (!configExists) {
66
- logger.consoleLog('❌ No configuration found. Run ProtoAgent to set up first.');
67
- return;
68
- }
69
- const config = await loadConfig();
70
- logger.consoleLog(`\n🔑 Update ${config.provider.toUpperCase()} API Key`);
71
- // Show current API key based on provider
72
- let currentKey = '';
73
- let helpUrl = '';
74
- let keyPrefix = '';
75
- if (config.provider === 'openai') {
76
- currentKey = config.credentials.OPENAI_API_KEY || '';
77
- helpUrl = 'https://platform.openai.com/api-keys';
78
- keyPrefix = 'sk-';
79
- }
80
- else if (config.provider === 'gemini') {
81
- currentKey = config.credentials.GEMINI_API_KEY || '';
82
- helpUrl = 'https://aistudio.google.com/app/apikey';
83
- keyPrefix = '';
84
- }
85
- else if (config.provider === 'cerebras') {
86
- currentKey = config.credentials.CEREBRAS_API_KEY || '';
87
- helpUrl = 'https://cloud.cerebras.ai/platform';
88
- keyPrefix = '';
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
- }
95
- logger.consoleLog(`Current API Key: ${maskApiKey(currentKey)}`);
96
- logger.consoleLog(`Get your API key from: ${helpUrl}\n`);
97
- const { newApiKey } = await inquirer.prompt([
98
- {
99
- type: 'password',
100
- name: 'newApiKey',
101
- message: `Enter your new ${config.provider.toUpperCase()} API key:`,
102
- mask: '*',
103
- validate: (input) => {
104
- if (!input || input.trim().length === 0) {
105
- return 'API key is required';
106
- }
107
- if (keyPrefix && !input.trim().startsWith(keyPrefix)) {
108
- return `${config.provider.toUpperCase()} API keys should start with "${keyPrefix}"`;
109
- }
110
- return true;
111
- }
112
- }
113
- ]);
114
- // Update configuration based on provider
115
- if (config.provider === 'openai') {
116
- config.credentials.OPENAI_API_KEY = newApiKey.trim();
117
- }
118
- else if (config.provider === 'gemini') {
119
- config.credentials.GEMINI_API_KEY = newApiKey.trim();
120
- }
121
- else if (config.provider === 'cerebras') {
122
- config.credentials.CEREBRAS_API_KEY = newApiKey.trim();
123
- }
124
- else if (config.provider === 'anthropic') {
125
- config.credentials.ANTHROPIC_API_KEY = newApiKey.trim();
126
- }
127
- await saveConfig(config);
128
- logger.consoleLog('\n✅ API key updated successfully!');
129
- logger.consoleLog(`New API Key: ${maskApiKey(newApiKey)}`);
130
- }
131
- catch (error) {
132
- console.error(`❌ Error updating API key: ${error.message}`);
133
- }
134
- }
135
- /**
136
- * Update model
137
- */
138
- export async function updateModel() {
139
- try {
140
- const configExists = await hasConfig();
141
- if (!configExists) {
142
- logger.consoleLog('❌ No configuration found. Run ProtoAgent to set up first.');
143
- return;
144
- }
145
- const config = await loadConfig();
146
- logger.consoleLog(`\n🤖 Update ${config.provider.toUpperCase()} Model`);
147
- logger.consoleLog(`Current Model: ${config.model}\n`);
148
- // Get available models based on provider
149
- let availableModels = [];
150
- if (config.provider === 'openai') {
151
- availableModels = openaiProvider.models;
152
- }
153
- else if (config.provider === 'gemini') {
154
- availableModels = geminiProvider.models;
155
- }
156
- else if (config.provider === 'cerebras') {
157
- availableModels = cerebrasProvider.models;
158
- }
159
- else if (config.provider === 'anthropic') {
160
- availableModels = anthropicProvider.models;
161
- }
162
- const { newModel } = await inquirer.prompt([
163
- {
164
- type: 'list',
165
- name: 'newModel',
166
- message: `Select a new ${config.provider.toUpperCase()} model:`,
167
- choices: availableModels.map(model => ({
168
- name: model === config.model ? `${model} (current)` : model,
169
- value: model
170
- })),
171
- default: config.model
172
- }
173
- ]);
174
- if (newModel === config.model) {
175
- logger.consoleLog('✨ No change needed - same model selected.');
176
- return;
177
- }
178
- // Update configuration
179
- const previousModel = config.model;
180
- config.model = newModel;
181
- await saveConfig(config);
182
- logger.consoleLog('\n✅ Model updated successfully!');
183
- logger.consoleLog(`Previous Model: ${previousModel}`);
184
- logger.consoleLog(`New Model: ${newModel}`);
185
- }
186
- catch (error) {
187
- console.error(`❌ Error updating model: ${error.message}`);
188
- }
189
- }
190
- /**
191
- * Reset configuration (full reconfiguration)
192
- */
193
- export async function resetConfiguration() {
194
- try {
195
- const configExists = await hasConfig();
196
- if (configExists) {
197
- logger.consoleLog('\n⚠️ Reset Configuration');
198
- logger.consoleLog('This will delete your current configuration and set up ProtoAgent from scratch.');
199
- const { confirm } = await inquirer.prompt([
200
- {
201
- type: 'confirm',
202
- name: 'confirm',
203
- message: 'Are you sure you want to reset your configuration?',
204
- default: false
205
- }
206
- ]);
207
- if (!confirm) {
208
- logger.consoleLog('Configuration reset cancelled.');
209
- return;
210
- }
211
- // Delete existing configuration
212
- const configPath = path.join(getConfigDirectory(), 'config.json');
213
- await fs.unlink(configPath);
214
- logger.consoleLog('✅ Existing configuration deleted.');
215
- }
216
- // Run setup again
217
- logger.consoleLog('\n🔄 Starting fresh configuration setup...');
218
- await setupConfig();
219
- }
220
- catch (error) {
221
- console.error(`❌ Error resetting configuration: ${error.message}`);
222
- }
223
- }