protoagent 0.0.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.
@@ -0,0 +1,208 @@
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 } from './providers.js';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ /**
11
+ * Mask API key for display (show first 8 and last 4 characters)
12
+ */
13
+ function maskApiKey(apiKey) {
14
+ if (apiKey.length <= 12) {
15
+ return '*'.repeat(apiKey.length);
16
+ }
17
+ const start = apiKey.substring(0, 8);
18
+ const end = apiKey.substring(apiKey.length - 4);
19
+ const middle = '*'.repeat(apiKey.length - 12);
20
+ return `${start}${middle}${end}`;
21
+ }
22
+ /**
23
+ * Show current configuration (without exposing sensitive data)
24
+ */
25
+ export async function showCurrentConfig() {
26
+ try {
27
+ const configExists = await hasConfig();
28
+ if (!configExists) {
29
+ console.log('โŒ No configuration found. Run ProtoAgent to set up.');
30
+ return;
31
+ }
32
+ const config = await loadConfig();
33
+ const configDir = getConfigDirectory();
34
+ console.log('\n๐Ÿ“‹ Current ProtoAgent Configuration:');
35
+ console.log('โ”€'.repeat(40));
36
+ console.log(`Provider: ${config.provider}`);
37
+ console.log(`Model: ${config.model}`);
38
+ // Show the appropriate API key based on provider
39
+ if (config.provider === 'openai' && config.credentials.OPENAI_API_KEY) {
40
+ console.log(`API Key: ${maskApiKey(config.credentials.OPENAI_API_KEY)}`);
41
+ }
42
+ else if (config.provider === 'gemini' && config.credentials.GEMINI_API_KEY) {
43
+ console.log(`API Key: ${maskApiKey(config.credentials.GEMINI_API_KEY)}`);
44
+ }
45
+ else if (config.provider === 'cerebras' && config.credentials.CEREBRAS_API_KEY) {
46
+ console.log(`API Key: ${maskApiKey(config.credentials.CEREBRAS_API_KEY)}`);
47
+ }
48
+ console.log(`Config Location: ${configDir}/config.json`);
49
+ console.log('โ”€'.repeat(40));
50
+ }
51
+ catch (error) {
52
+ console.error(`โŒ Error reading configuration: ${error.message}`);
53
+ }
54
+ }
55
+ /**
56
+ * Update API key
57
+ */
58
+ export async function updateApiKey() {
59
+ try {
60
+ const configExists = await hasConfig();
61
+ if (!configExists) {
62
+ console.log('โŒ No configuration found. Run ProtoAgent to set up first.');
63
+ return;
64
+ }
65
+ const config = await loadConfig();
66
+ console.log(`\n๐Ÿ”‘ Update ${config.provider.toUpperCase()} API Key`);
67
+ // Show current API key based on provider
68
+ let currentKey = '';
69
+ let helpUrl = '';
70
+ let keyPrefix = '';
71
+ if (config.provider === 'openai') {
72
+ currentKey = config.credentials.OPENAI_API_KEY || '';
73
+ helpUrl = 'https://platform.openai.com/api-keys';
74
+ keyPrefix = 'sk-';
75
+ }
76
+ else if (config.provider === 'gemini') {
77
+ currentKey = config.credentials.GEMINI_API_KEY || '';
78
+ helpUrl = 'https://aistudio.google.com/app/apikey';
79
+ keyPrefix = '';
80
+ }
81
+ else if (config.provider === 'cerebras') {
82
+ currentKey = config.credentials.CEREBRAS_API_KEY || '';
83
+ helpUrl = 'https://cloud.cerebras.ai/platform';
84
+ keyPrefix = '';
85
+ }
86
+ console.log(`Current API Key: ${maskApiKey(currentKey)}`);
87
+ console.log(`Get your API key from: ${helpUrl}\n`);
88
+ const { newApiKey } = await inquirer.prompt([
89
+ {
90
+ type: 'password',
91
+ name: 'newApiKey',
92
+ message: `Enter your new ${config.provider.toUpperCase()} API key:`,
93
+ mask: '*',
94
+ validate: (input) => {
95
+ if (!input || input.trim().length === 0) {
96
+ return 'API key is required';
97
+ }
98
+ if (keyPrefix && !input.trim().startsWith(keyPrefix)) {
99
+ return `${config.provider.toUpperCase()} API keys should start with "${keyPrefix}"`;
100
+ }
101
+ return true;
102
+ }
103
+ }
104
+ ]);
105
+ // Update configuration based on provider
106
+ if (config.provider === 'openai') {
107
+ config.credentials.OPENAI_API_KEY = newApiKey.trim();
108
+ }
109
+ else if (config.provider === 'gemini') {
110
+ config.credentials.GEMINI_API_KEY = newApiKey.trim();
111
+ }
112
+ else if (config.provider === 'cerebras') {
113
+ config.credentials.CEREBRAS_API_KEY = newApiKey.trim();
114
+ }
115
+ await saveConfig(config);
116
+ console.log('\nโœ… API key updated successfully!');
117
+ console.log(`New API Key: ${maskApiKey(newApiKey)}`);
118
+ }
119
+ catch (error) {
120
+ console.error(`โŒ Error updating API key: ${error.message}`);
121
+ }
122
+ }
123
+ /**
124
+ * Update model
125
+ */
126
+ export async function updateModel() {
127
+ try {
128
+ const configExists = await hasConfig();
129
+ if (!configExists) {
130
+ console.log('โŒ No configuration found. Run ProtoAgent to set up first.');
131
+ return;
132
+ }
133
+ const config = await loadConfig();
134
+ console.log(`\n๐Ÿค– Update ${config.provider.toUpperCase()} Model`);
135
+ console.log(`Current Model: ${config.model}\n`);
136
+ // Get available models based on provider
137
+ let availableModels = [];
138
+ if (config.provider === 'openai') {
139
+ availableModels = openaiProvider.models;
140
+ }
141
+ else if (config.provider === 'gemini') {
142
+ availableModels = geminiProvider.models;
143
+ }
144
+ else if (config.provider === 'cerebras') {
145
+ availableModels = cerebrasProvider.models;
146
+ }
147
+ const { newModel } = await inquirer.prompt([
148
+ {
149
+ type: 'list',
150
+ name: 'newModel',
151
+ message: `Select a new ${config.provider.toUpperCase()} model:`,
152
+ choices: availableModels.map(model => ({
153
+ name: model === config.model ? `${model} (current)` : model,
154
+ value: model
155
+ })),
156
+ default: config.model
157
+ }
158
+ ]);
159
+ if (newModel === config.model) {
160
+ console.log('โœจ No change needed - same model selected.');
161
+ return;
162
+ }
163
+ // Update configuration
164
+ const previousModel = config.model;
165
+ config.model = newModel;
166
+ await saveConfig(config);
167
+ console.log('\nโœ… Model updated successfully!');
168
+ console.log(`Previous Model: ${previousModel}`);
169
+ console.log(`New Model: ${newModel}`);
170
+ }
171
+ catch (error) {
172
+ console.error(`โŒ Error updating model: ${error.message}`);
173
+ }
174
+ }
175
+ /**
176
+ * Reset configuration (full reconfiguration)
177
+ */
178
+ export async function resetConfiguration() {
179
+ try {
180
+ const configExists = await hasConfig();
181
+ if (configExists) {
182
+ console.log('\nโš ๏ธ Reset Configuration');
183
+ console.log('This will delete your current configuration and set up ProtoAgent from scratch.');
184
+ const { confirm } = await inquirer.prompt([
185
+ {
186
+ type: 'confirm',
187
+ name: 'confirm',
188
+ message: 'Are you sure you want to reset your configuration?',
189
+ default: false
190
+ }
191
+ ]);
192
+ if (!confirm) {
193
+ console.log('Configuration reset cancelled.');
194
+ return;
195
+ }
196
+ // Delete existing configuration
197
+ const configPath = path.join(getConfigDirectory(), 'config.json');
198
+ await fs.unlink(configPath);
199
+ console.log('โœ… Existing configuration deleted.');
200
+ }
201
+ // Run setup again
202
+ console.log('\n๐Ÿ”„ Starting fresh configuration setup...');
203
+ await setupConfig();
204
+ }
205
+ catch (error) {
206
+ console.error(`โŒ Error resetting configuration: ${error.message}`);
207
+ }
208
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Configuration manager for ProtoAgent
3
+ */
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import { geminiProvider, openaiProvider, cerebrasProvider } from './providers.js';
8
+ /**
9
+ * Get the config directory path
10
+ */
11
+ function getConfigDir() {
12
+ const homeDir = os.homedir();
13
+ // Use XDG Base Directory specification on Unix-like systems
14
+ if (process.platform === 'win32') {
15
+ return path.join(homeDir, 'AppData', 'Local', 'protoagent');
16
+ }
17
+ else {
18
+ return path.join(homeDir, '.local', 'share', 'protoagent');
19
+ }
20
+ }
21
+ /**
22
+ * Get the config file path
23
+ */
24
+ function getConfigPath() {
25
+ return path.join(getConfigDir(), 'config.json');
26
+ }
27
+ /**
28
+ * Ensure config directory exists
29
+ */
30
+ async function ensureConfigDir() {
31
+ const configDir = getConfigDir();
32
+ try {
33
+ await fs.access(configDir);
34
+ }
35
+ catch {
36
+ await fs.mkdir(configDir, { recursive: true });
37
+ }
38
+ }
39
+ /**
40
+ * Check if configuration exists
41
+ */
42
+ export async function hasConfig() {
43
+ try {
44
+ await fs.access(getConfigPath());
45
+ return true;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * Load user configuration
53
+ */
54
+ export async function loadConfig() {
55
+ try {
56
+ const configPath = getConfigPath();
57
+ const configData = await fs.readFile(configPath, 'utf-8');
58
+ return JSON.parse(configData);
59
+ }
60
+ catch (error) {
61
+ throw new Error(`Failed to load configuration: ${error.message}`);
62
+ }
63
+ }
64
+ /**
65
+ * Save user configuration
66
+ */
67
+ export async function saveConfig(config) {
68
+ try {
69
+ await ensureConfigDir();
70
+ const configPath = getConfigPath();
71
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
72
+ }
73
+ catch (error) {
74
+ throw new Error(`Failed to save configuration: ${error.message}`);
75
+ }
76
+ }
77
+ /**
78
+ * Validate configuration
79
+ */
80
+ export function validateConfig(config) {
81
+ if (!config || typeof config !== 'object') {
82
+ return false;
83
+ }
84
+ const { provider, model, credentials } = config;
85
+ if (!model || !credentials || !(credentials.OPENAI_API_KEY || credentials.GEMINI_API_KEY || credentials.CEREBRAS_API_KEY)) {
86
+ return false;
87
+ }
88
+ if (provider === 'gemini') {
89
+ // Check if model is available for Gemini
90
+ if (!geminiProvider.models.includes(model)) {
91
+ return false;
92
+ }
93
+ // Additional model validation for Gemini can be added here
94
+ return true;
95
+ }
96
+ if (provider === 'openai') {
97
+ // Check if model is available for OpenAI
98
+ if (!openaiProvider.models.includes(model)) {
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ if (provider === 'cerebras') {
104
+ // Check if model is available for Cerebras
105
+ if (!cerebrasProvider.models.includes(model)) {
106
+ return false;
107
+ }
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ /**
113
+ * Get configuration directory path for display
114
+ */
115
+ export function getConfigDirectory() {
116
+ return getConfigDir();
117
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * MCP management commands for ProtoAgent CLI
3
+ */
4
+ import inquirer from 'inquirer';
5
+ import { MCPManager } from './mcp-manager.js';
6
+ const mcpManager = new MCPManager();
7
+ /**
8
+ * Show current MCP configuration and status
9
+ */
10
+ export async function showMCPStatus() {
11
+ try {
12
+ await mcpManager.loadConfig();
13
+ console.log('\n๐Ÿ”Œ MCP Configuration & Status:');
14
+ console.log('โ”€'.repeat(50));
15
+ const servers = mcpManager.listServers();
16
+ const connectionStatus = mcpManager.getConnectionStatus();
17
+ if (Object.keys(servers).length === 0) {
18
+ console.log('No MCP servers configured');
19
+ return;
20
+ }
21
+ for (const [name, config] of Object.entries(servers)) {
22
+ const status = connectionStatus[name];
23
+ const statusIcon = status === true ? '๐ŸŸข' : status === false ? '๐Ÿ”ด' : 'โšช';
24
+ const typeLabel = config.isBuiltIn ? '(built-in)' : '(custom)';
25
+ console.log(`${statusIcon} ${name} ${typeLabel}`);
26
+ console.log(` Description: ${config.description}`);
27
+ console.log(` Command: ${config.command} ${(config.args || []).join(' ')}`);
28
+ console.log(` Enabled: ${config.enabled ? 'Yes' : 'No'}`);
29
+ console.log('');
30
+ }
31
+ // Show available tools if connected
32
+ const tools = await mcpManager.getAllTools();
33
+ if (tools.length > 0) {
34
+ console.log('๐Ÿ› ๏ธ Available Tools:');
35
+ for (const tool of tools) {
36
+ console.log(` โ€ข ${tool.server}:${tool.name} - ${tool.description || 'No description'}`);
37
+ }
38
+ }
39
+ console.log('โ”€'.repeat(50));
40
+ }
41
+ catch (error) {
42
+ console.error(`โŒ Error showing MCP status: ${error.message}`);
43
+ }
44
+ }
45
+ /**
46
+ * Connect to MCP servers
47
+ */
48
+ export async function connectMCPServers() {
49
+ try {
50
+ await mcpManager.loadConfig();
51
+ await mcpManager.connectToAllServers();
52
+ }
53
+ catch (error) {
54
+ console.error(`โŒ Error connecting to MCP servers: ${error.message}`);
55
+ }
56
+ }
57
+ /**
58
+ * Add a custom MCP server
59
+ */
60
+ export async function addCustomMCPServer() {
61
+ try {
62
+ console.log('\n๐Ÿ”ง Add Custom MCP Server');
63
+ console.log('Configure a new MCP server for ProtoAgent\n');
64
+ const serverConfig = await inquirer.prompt([
65
+ {
66
+ type: 'input',
67
+ name: 'name',
68
+ message: 'Server name (unique identifier):',
69
+ validate: (input) => {
70
+ if (!input || input.trim().length === 0) {
71
+ return 'Server name is required';
72
+ }
73
+ if (!/^[a-zA-Z0-9\-_]+$/.test(input.trim())) {
74
+ return 'Server name can only contain letters, numbers, hyphens, and underscores';
75
+ }
76
+ return true;
77
+ }
78
+ },
79
+ {
80
+ type: 'input',
81
+ name: 'description',
82
+ message: 'Description:',
83
+ validate: (input) => input.trim().length > 0 ? true : 'Description is required'
84
+ },
85
+ {
86
+ type: 'input',
87
+ name: 'command',
88
+ message: 'Command to run the server:',
89
+ validate: (input) => input.trim().length > 0 ? true : 'Command is required'
90
+ },
91
+ {
92
+ type: 'input',
93
+ name: 'args',
94
+ message: 'Arguments (space-separated, optional):',
95
+ default: ''
96
+ },
97
+ {
98
+ type: 'confirm',
99
+ name: 'enabled',
100
+ message: 'Enable this server?',
101
+ default: true
102
+ }
103
+ ]);
104
+ const newServer = {
105
+ name: serverConfig.name.trim(),
106
+ description: serverConfig.description.trim(),
107
+ command: serverConfig.command.trim(),
108
+ args: serverConfig.args.trim() ? serverConfig.args.trim().split(' ') : [],
109
+ enabled: serverConfig.enabled,
110
+ isBuiltIn: false
111
+ };
112
+ await mcpManager.loadConfig();
113
+ await mcpManager.addCustomServer(newServer);
114
+ console.log(`\nโœ… Custom MCP server '${newServer.name}' added successfully!`);
115
+ if (newServer.enabled) {
116
+ const { connectNow } = await inquirer.prompt([
117
+ {
118
+ type: 'confirm',
119
+ name: 'connectNow',
120
+ message: 'Connect to this server now?',
121
+ default: true
122
+ }
123
+ ]);
124
+ if (connectNow) {
125
+ const connection = await mcpManager.connectToServer(newServer.name);
126
+ if (connection) {
127
+ console.log(`๐ŸŽ‰ Successfully connected to ${newServer.name}!`);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ catch (error) {
133
+ console.error(`โŒ Error adding custom MCP server: ${error.message}`);
134
+ }
135
+ }
136
+ /**
137
+ * Remove a custom MCP server
138
+ */
139
+ export async function removeCustomMCPServer() {
140
+ try {
141
+ await mcpManager.loadConfig();
142
+ const servers = mcpManager.listServers();
143
+ const customServers = Object.entries(servers).filter(([_, config]) => !config.isBuiltIn);
144
+ if (customServers.length === 0) {
145
+ console.log('๐Ÿ“ญ No custom MCP servers configured');
146
+ return;
147
+ }
148
+ const { serverToRemove } = await inquirer.prompt([
149
+ {
150
+ type: 'list',
151
+ name: 'serverToRemove',
152
+ message: 'Select server to remove:',
153
+ choices: customServers.map(([name, config]) => ({
154
+ name: `${name} - ${config.description}`,
155
+ value: name
156
+ }))
157
+ }
158
+ ]);
159
+ const { confirm } = await inquirer.prompt([
160
+ {
161
+ type: 'confirm',
162
+ name: 'confirm',
163
+ message: `Are you sure you want to remove '${serverToRemove}'?`,
164
+ default: false
165
+ }
166
+ ]);
167
+ if (confirm) {
168
+ await mcpManager.removeCustomServer(serverToRemove);
169
+ console.log(`โœ… Custom MCP server '${serverToRemove}' removed successfully!`);
170
+ }
171
+ else {
172
+ console.log('Operation cancelled.');
173
+ }
174
+ }
175
+ catch (error) {
176
+ console.error(`โŒ Error removing custom MCP server: ${error.message}`);
177
+ }
178
+ }
179
+ /**
180
+ * Toggle MCP server enabled/disabled status
181
+ */
182
+ export async function toggleMCPServer() {
183
+ try {
184
+ await mcpManager.loadConfig();
185
+ const servers = mcpManager.listServers();
186
+ if (Object.keys(servers).length === 0) {
187
+ console.log('๐Ÿ“ญ No MCP servers configured');
188
+ return;
189
+ }
190
+ const { serverToToggle } = await inquirer.prompt([
191
+ {
192
+ type: 'list',
193
+ name: 'serverToToggle',
194
+ message: 'Select server to enable/disable:',
195
+ choices: Object.entries(servers).map(([name, config]) => ({
196
+ name: `${name} - ${config.enabled ? '๐ŸŸข Enabled' : '๐Ÿ”ด Disabled'} - ${config.description}`,
197
+ value: name
198
+ }))
199
+ }
200
+ ]);
201
+ const currentConfig = servers[serverToToggle];
202
+ const newStatus = !currentConfig.enabled;
203
+ // Update the configuration
204
+ await mcpManager.loadConfig();
205
+ const updatedServers = mcpManager.listServers();
206
+ updatedServers[serverToToggle].enabled = newStatus;
207
+ // Save the updated configuration
208
+ const updatedConfig = {
209
+ servers: updatedServers,
210
+ globalTimeout: 30000,
211
+ maxConcurrentConnections: 5,
212
+ retryAttempts: 3
213
+ };
214
+ const mcpManagerForSave = new MCPManager(updatedConfig);
215
+ await mcpManagerForSave.saveConfig();
216
+ console.log(`โœ… Server '${serverToToggle}' ${newStatus ? 'enabled' : 'disabled'}`);
217
+ }
218
+ catch (error) {
219
+ console.error(`โŒ Error toggling MCP server: ${error.message}`);
220
+ }
221
+ }
222
+ /**
223
+ * Test connection to a specific MCP server
224
+ */
225
+ export async function testMCPServer() {
226
+ try {
227
+ await mcpManager.loadConfig();
228
+ const servers = mcpManager.listServers();
229
+ const enabledServers = Object.entries(servers).filter(([_, config]) => config.enabled);
230
+ if (enabledServers.length === 0) {
231
+ console.log('๐Ÿ“ญ No enabled MCP servers to test');
232
+ return;
233
+ }
234
+ const { serverToTest } = await inquirer.prompt([
235
+ {
236
+ type: 'list',
237
+ name: 'serverToTest',
238
+ message: 'Select server to test:',
239
+ choices: enabledServers.map(([name, config]) => ({
240
+ name: `${name} - ${config.description}`,
241
+ value: name
242
+ }))
243
+ }
244
+ ]);
245
+ console.log(`๐Ÿงช Testing connection to ${serverToTest}...`);
246
+ const connection = await mcpManager.connectToServer(serverToTest);
247
+ if (connection) {
248
+ console.log(`โœ… Successfully connected to ${serverToTest}!`);
249
+ console.log(`๐Ÿ› ๏ธ Available tools: ${connection.capabilities.join(', ')}`);
250
+ // Clean up test connection
251
+ await mcpManager.disconnectServer(serverToTest);
252
+ }
253
+ else {
254
+ console.log(`โŒ Failed to connect to ${serverToTest}`);
255
+ }
256
+ }
257
+ catch (error) {
258
+ console.error(`โŒ Error testing MCP server: ${error.message}`);
259
+ }
260
+ }
261
+ /**
262
+ * Get the MCP manager instance (for use in main application)
263
+ */
264
+ export function getMCPManager() {
265
+ return mcpManager;
266
+ }