claude-autopm 1.31.0 → 2.1.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.
- package/README.md +57 -5
- package/autopm/.claude/mcp/test-server.md +10 -0
- package/bin/autopm-poc.js +176 -44
- package/bin/autopm.js +97 -179
- package/lib/ai-providers/AbstractAIProvider.js +524 -0
- package/lib/ai-providers/ClaudeProvider.js +359 -48
- package/lib/ai-providers/TemplateProvider.js +432 -0
- package/lib/cli/commands/agent.js +206 -0
- package/lib/cli/commands/config.js +488 -0
- package/lib/cli/commands/prd.js +345 -0
- package/lib/cli/commands/task.js +206 -0
- package/lib/config/ConfigManager.js +531 -0
- package/lib/errors/AIProviderError.js +164 -0
- package/lib/services/AgentService.js +557 -0
- package/lib/services/EpicService.js +609 -0
- package/lib/services/PRDService.js +928 -103
- package/lib/services/TaskService.js +760 -0
- package/lib/services/interfaces.js +753 -0
- package/lib/utils/CircuitBreaker.js +165 -0
- package/lib/utils/Encryption.js +201 -0
- package/lib/utils/RateLimiter.js +241 -0
- package/lib/utils/ServiceFactory.js +165 -0
- package/package.json +6 -5
- package/scripts/config/get.js +108 -0
- package/scripts/config/init.js +100 -0
- package/scripts/config/list-providers.js +93 -0
- package/scripts/config/set-api-key.js +107 -0
- package/scripts/config/set-provider.js +201 -0
- package/scripts/config/set.js +139 -0
- package/scripts/config/show.js +181 -0
- package/autopm/.claude/.env +0 -158
- package/autopm/.claude/settings.local.json +0 -9
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TemplateProvider - No-AI Fallback Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides template-based text generation without actual AI calls.
|
|
5
|
+
* Useful for:
|
|
6
|
+
* - No API key scenarios
|
|
7
|
+
* - Offline mode
|
|
8
|
+
* - Testing without API costs
|
|
9
|
+
* - Deterministic output for CI/CD
|
|
10
|
+
* - Development without rate limits
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Simple variable substitution: {{variable}}
|
|
14
|
+
* - Nested property access: {{object.property}}
|
|
15
|
+
* - Conditionals: {{#if condition}}...{{/if}}
|
|
16
|
+
* - Loops: {{#each array}}...{{/each}}
|
|
17
|
+
* - Simulated streaming support
|
|
18
|
+
*
|
|
19
|
+
* @extends AbstractAIProvider
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const AbstractAIProvider = require('./AbstractAIProvider');
|
|
23
|
+
const AIProviderError = require('../errors/AIProviderError');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default templates for common use cases
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_TEMPLATES = {
|
|
29
|
+
default: 'Response: {{prompt}}',
|
|
30
|
+
|
|
31
|
+
prd_parse: `
|
|
32
|
+
PRD Analysis: {{prdTitle}}
|
|
33
|
+
|
|
34
|
+
Key Features:
|
|
35
|
+
{{#each features}}
|
|
36
|
+
- {{name}}: {{description}}
|
|
37
|
+
{{/each}}
|
|
38
|
+
|
|
39
|
+
Technical Approach:
|
|
40
|
+
{{technicalApproach}}
|
|
41
|
+
|
|
42
|
+
Estimated Effort: {{effortEstimate}}
|
|
43
|
+
`.trim(),
|
|
44
|
+
|
|
45
|
+
epic_decompose: `
|
|
46
|
+
Epic: {{epicName}}
|
|
47
|
+
|
|
48
|
+
Description: {{description}}
|
|
49
|
+
|
|
50
|
+
Tasks:
|
|
51
|
+
{{#each tasks}}
|
|
52
|
+
{{index}}. {{name}} ({{effort}})
|
|
53
|
+
{{#if dependencies}}Dependencies: {{dependencies}}{{/if}}
|
|
54
|
+
{{/each}}
|
|
55
|
+
|
|
56
|
+
Total Effort: {{totalEffort}}
|
|
57
|
+
`.trim(),
|
|
58
|
+
|
|
59
|
+
task_generate: `
|
|
60
|
+
Task: {{taskTitle}}
|
|
61
|
+
|
|
62
|
+
Description: {{description}}
|
|
63
|
+
|
|
64
|
+
Acceptance Criteria:
|
|
65
|
+
{{#each criteria}}
|
|
66
|
+
- {{.}}
|
|
67
|
+
{{/each}}
|
|
68
|
+
|
|
69
|
+
Effort: {{effort}}
|
|
70
|
+
Priority: {{priority}}
|
|
71
|
+
`.trim()
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
class TemplateProvider extends AbstractAIProvider {
|
|
75
|
+
/**
|
|
76
|
+
* Create a new TemplateProvider
|
|
77
|
+
*
|
|
78
|
+
* @param {Object} config - Configuration options
|
|
79
|
+
* @param {Object} config.templates - Custom templates
|
|
80
|
+
* @param {string} config.templates.default - Default template
|
|
81
|
+
* @param {Object} config.templates.* - Named templates
|
|
82
|
+
*/
|
|
83
|
+
constructor(config = {}) {
|
|
84
|
+
super(config);
|
|
85
|
+
this.templates = config.templates || {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the default model identifier
|
|
90
|
+
*
|
|
91
|
+
* @returns {string} Model name
|
|
92
|
+
*/
|
|
93
|
+
getDefaultModel() {
|
|
94
|
+
return 'template-v1';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the environment variable name for API key
|
|
99
|
+
* (Not required for template provider)
|
|
100
|
+
*
|
|
101
|
+
* @returns {string} Environment variable name
|
|
102
|
+
*/
|
|
103
|
+
getApiKeyEnvVar() {
|
|
104
|
+
return 'TEMPLATE_PROVIDER_API_KEY';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generate text using template rendering
|
|
109
|
+
*
|
|
110
|
+
* @param {string} prompt - Input prompt (available as {{prompt}} in template)
|
|
111
|
+
* @param {Object} options - Generation options
|
|
112
|
+
* @param {string} options.template - Template string to use
|
|
113
|
+
* @param {Object} options.data - Data for template substitution
|
|
114
|
+
* @returns {Promise<string>} Rendered template
|
|
115
|
+
*/
|
|
116
|
+
async complete(prompt, options = {}) {
|
|
117
|
+
try {
|
|
118
|
+
const template = options.template || this.templates.default || DEFAULT_TEMPLATES.default;
|
|
119
|
+
const data = options.data || { prompt };
|
|
120
|
+
|
|
121
|
+
// Ensure prompt is in data
|
|
122
|
+
if (!data.prompt) {
|
|
123
|
+
data.prompt = prompt;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.render(template, data);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw this.formatError(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate streaming text by yielding words incrementally
|
|
134
|
+
* Simulates streaming behavior without actual AI
|
|
135
|
+
*
|
|
136
|
+
* @param {string} prompt - Input prompt
|
|
137
|
+
* @param {Object} options - Generation options
|
|
138
|
+
* @yields {string} Word chunks with spaces
|
|
139
|
+
*/
|
|
140
|
+
async *stream(prompt, options = {}) {
|
|
141
|
+
try {
|
|
142
|
+
const result = await this.complete(prompt, options);
|
|
143
|
+
|
|
144
|
+
// Simulate streaming by yielding word by word
|
|
145
|
+
const words = result.split(' ');
|
|
146
|
+
for (const word of words) {
|
|
147
|
+
if (word) {
|
|
148
|
+
yield word + ' ';
|
|
149
|
+
await this._delay(10); // Small delay to simulate streaming
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw this.formatError(error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if streaming is supported
|
|
159
|
+
*
|
|
160
|
+
* @returns {boolean} True (simulated streaming supported)
|
|
161
|
+
*/
|
|
162
|
+
supportsStreaming() {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if function calling is supported
|
|
168
|
+
*
|
|
169
|
+
* @returns {boolean} False (not supported)
|
|
170
|
+
*/
|
|
171
|
+
supportsFunctionCalling() {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if chat mode is supported
|
|
177
|
+
*
|
|
178
|
+
* @returns {boolean} False (basic templates only)
|
|
179
|
+
*/
|
|
180
|
+
supportsChat() {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if vision/image analysis is supported
|
|
186
|
+
*
|
|
187
|
+
* @returns {boolean} False (not supported)
|
|
188
|
+
*/
|
|
189
|
+
supportsVision() {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate provider configuration
|
|
195
|
+
* Always returns true as no API key needed
|
|
196
|
+
*
|
|
197
|
+
* @returns {Promise<boolean>} True
|
|
198
|
+
*/
|
|
199
|
+
async validate() {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Test connection to provider
|
|
205
|
+
* Always returns true as no external connection needed
|
|
206
|
+
*
|
|
207
|
+
* @returns {Promise<boolean>} True
|
|
208
|
+
*/
|
|
209
|
+
async testConnection() {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Render a template with data
|
|
215
|
+
* Processes in order: variables → nested properties → conditionals → loops
|
|
216
|
+
*
|
|
217
|
+
* @param {string} template - Template string
|
|
218
|
+
* @param {Object} data - Data for substitution
|
|
219
|
+
* @returns {string} Rendered result
|
|
220
|
+
*/
|
|
221
|
+
render(template, data) {
|
|
222
|
+
if (!template) return '';
|
|
223
|
+
if (!data) data = {};
|
|
224
|
+
|
|
225
|
+
let result = String(template);
|
|
226
|
+
|
|
227
|
+
// Process in specific order for correct nesting
|
|
228
|
+
// 1. Process loops first (they may contain conditionals and variables)
|
|
229
|
+
result = this._processLoops(result, data);
|
|
230
|
+
|
|
231
|
+
// 2. Process conditionals (they may contain variables)
|
|
232
|
+
result = this._processConditionals(result, data);
|
|
233
|
+
|
|
234
|
+
// 3. Process nested properties (more specific than simple variables)
|
|
235
|
+
result = this._replaceNestedProperties(result, data);
|
|
236
|
+
|
|
237
|
+
// 4. Process simple variables last (catch remaining)
|
|
238
|
+
result = this._replaceVariables(result, data);
|
|
239
|
+
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Replace simple variables like {{variable}}
|
|
245
|
+
*
|
|
246
|
+
* @private
|
|
247
|
+
* @param {string} template - Template string
|
|
248
|
+
* @param {Object} data - Data object
|
|
249
|
+
* @returns {string} Template with variables replaced
|
|
250
|
+
*/
|
|
251
|
+
_replaceVariables(template, data) {
|
|
252
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
253
|
+
return data[key] !== undefined ? String(data[key]) : match;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Replace nested properties like {{object.property}}
|
|
259
|
+
*
|
|
260
|
+
* @private
|
|
261
|
+
* @param {string} template - Template string
|
|
262
|
+
* @param {Object} data - Data object
|
|
263
|
+
* @returns {string} Template with nested properties replaced
|
|
264
|
+
*/
|
|
265
|
+
_replaceNestedProperties(template, data) {
|
|
266
|
+
return template.replace(/\{\{([\w.]+)\}\}/g, (match, path) => {
|
|
267
|
+
// Skip if it's a simple variable (no dots)
|
|
268
|
+
if (!path.includes('.')) {
|
|
269
|
+
return match;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const value = this._getNestedProperty(data, path);
|
|
273
|
+
return value !== undefined ? String(value) : match;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get nested property from object using dot notation
|
|
279
|
+
*
|
|
280
|
+
* @private
|
|
281
|
+
* @param {Object} obj - Object to traverse
|
|
282
|
+
* @param {string} path - Dot-separated path
|
|
283
|
+
* @returns {*} Property value or undefined
|
|
284
|
+
*/
|
|
285
|
+
_getNestedProperty(obj, path) {
|
|
286
|
+
return path.split('.').reduce((current, key) =>
|
|
287
|
+
current?.[key], obj
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Process conditional blocks {{#if condition}}...{{/if}}
|
|
293
|
+
*
|
|
294
|
+
* @private
|
|
295
|
+
* @param {string} template - Template string
|
|
296
|
+
* @param {Object} data - Data object
|
|
297
|
+
* @returns {string} Template with conditionals processed
|
|
298
|
+
*/
|
|
299
|
+
_processConditionals(template, data) {
|
|
300
|
+
// Process nested conditionals by repeatedly applying the regex
|
|
301
|
+
// Use non-greedy matching and process innermost first
|
|
302
|
+
let result = template;
|
|
303
|
+
let previousResult;
|
|
304
|
+
let iterations = 0;
|
|
305
|
+
const maxIterations = 100; // Prevent infinite loops
|
|
306
|
+
|
|
307
|
+
do {
|
|
308
|
+
previousResult = result;
|
|
309
|
+
iterations++;
|
|
310
|
+
|
|
311
|
+
result = result.replace(
|
|
312
|
+
/\{\{#if\s+([\w.]+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
313
|
+
(match, conditionPath, content) => {
|
|
314
|
+
const condition = this._getNestedProperty(data, conditionPath) || data[conditionPath];
|
|
315
|
+
|
|
316
|
+
// Check truthiness (like JavaScript)
|
|
317
|
+
return condition ? content : '';
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
if (iterations >= maxIterations) {
|
|
322
|
+
break; // Safety exit
|
|
323
|
+
}
|
|
324
|
+
} while (result !== previousResult); // Continue until no more changes
|
|
325
|
+
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Process loop blocks {{#each array}}...{{/each}}
|
|
331
|
+
*
|
|
332
|
+
* @private
|
|
333
|
+
* @param {string} template - Template string
|
|
334
|
+
* @param {Object} data - Data object
|
|
335
|
+
* @returns {string} Template with loops processed
|
|
336
|
+
*/
|
|
337
|
+
_processLoops(template, data, depth = 0) {
|
|
338
|
+
// Prevent infinite recursion
|
|
339
|
+
if (depth > 10) {
|
|
340
|
+
return template;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Process loops with recursion for nested loops
|
|
344
|
+
const result = template.replace(
|
|
345
|
+
/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
|
|
346
|
+
(match, arrayName, itemTemplate) => {
|
|
347
|
+
const array = data[arrayName];
|
|
348
|
+
|
|
349
|
+
if (!Array.isArray(array)) {
|
|
350
|
+
return '';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return array.map(item => {
|
|
354
|
+
// If item is primitive, wrap in object with 'item' key
|
|
355
|
+
const itemData = typeof item === 'object' && item !== null
|
|
356
|
+
? item
|
|
357
|
+
: { item };
|
|
358
|
+
|
|
359
|
+
// Merge with parent data for access to outer variables
|
|
360
|
+
const mergedData = { ...data, ...itemData };
|
|
361
|
+
|
|
362
|
+
// Process the item template
|
|
363
|
+
let itemResult = itemTemplate;
|
|
364
|
+
|
|
365
|
+
// First replace {{.}} with the actual item value for primitives
|
|
366
|
+
if (typeof item !== 'object' || item === null) {
|
|
367
|
+
itemResult = itemResult.replace(/\{\{\.\}\}/g, String(item));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Recursively process nested loops with merged data FIRST
|
|
371
|
+
if (itemResult.includes('{{#each')) {
|
|
372
|
+
itemResult = this._processLoops(itemResult, mergedData, depth + 1);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Then replace variables (these should not interfere with processed loops)
|
|
376
|
+
itemResult = itemResult.replace(/\{\{([\w.]+)\}\}/g, (m, path) => {
|
|
377
|
+
// Skip if it looks like a template tag
|
|
378
|
+
if (path === 'each' || path === 'if' || path === '/each' || path === '/if') {
|
|
379
|
+
return m;
|
|
380
|
+
}
|
|
381
|
+
const value = this._getNestedProperty(mergedData, path) || mergedData[path];
|
|
382
|
+
return value !== undefined ? String(value) : m;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Process conditionals within loop
|
|
386
|
+
itemResult = itemResult.replace(
|
|
387
|
+
/\{\{#if\s+([\w.]+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
388
|
+
(m, condPath, content) => {
|
|
389
|
+
const cond = this._getNestedProperty(mergedData, condPath) || mergedData[condPath];
|
|
390
|
+
return cond ? content : '';
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return itemResult;
|
|
395
|
+
}).join('');
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Helper to delay execution (for simulated streaming)
|
|
404
|
+
*
|
|
405
|
+
* @private
|
|
406
|
+
* @param {number} ms - Milliseconds to delay
|
|
407
|
+
* @returns {Promise} Resolves after delay
|
|
408
|
+
*/
|
|
409
|
+
_delay(ms) {
|
|
410
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Format error as AIProviderError
|
|
415
|
+
*
|
|
416
|
+
* @param {Error} error - Original error
|
|
417
|
+
* @returns {AIProviderError} Formatted error
|
|
418
|
+
*/
|
|
419
|
+
formatError(error) {
|
|
420
|
+
if (error instanceof AIProviderError) {
|
|
421
|
+
return error;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return new AIProviderError(
|
|
425
|
+
'TEMPLATE_ERROR',
|
|
426
|
+
`Template rendering error: ${error.message}`,
|
|
427
|
+
true // isOperational
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
module.exports = TemplateProvider;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Agent Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides agent management commands for listing, searching, and invoking agents.
|
|
5
|
+
* Implements subcommands for list, search, and invoke operations.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/agent
|
|
8
|
+
* @requires ../../services/AgentService
|
|
9
|
+
* @requires ora
|
|
10
|
+
* @requires chalk
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const AgentService = require('../../services/AgentService');
|
|
14
|
+
const ora = require('ora');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List all available agents
|
|
19
|
+
* @param {Object} argv - Command arguments
|
|
20
|
+
*/
|
|
21
|
+
async function agentList(argv) {
|
|
22
|
+
const spinner = ora('Loading agents...').start();
|
|
23
|
+
const agentService = new AgentService();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const agents = await agentService.listAgents();
|
|
27
|
+
|
|
28
|
+
spinner.succeed(chalk.green(`Found ${agents.length} agents`));
|
|
29
|
+
|
|
30
|
+
// Display agent list
|
|
31
|
+
if (agents.length > 0) {
|
|
32
|
+
console.log(chalk.green(`\n${agents.length} agents available:`));
|
|
33
|
+
agents.forEach(agent => {
|
|
34
|
+
console.log(` - ${agent.name}: ${agent.title} [${agent.category}]`);
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
console.log(chalk.yellow('\nNo agents found.'));
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail(chalk.red('Failed to list agents'));
|
|
41
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Search agents by keyword
|
|
47
|
+
* @param {Object} argv - Command arguments
|
|
48
|
+
*/
|
|
49
|
+
async function agentSearch(argv) {
|
|
50
|
+
const spinner = ora(`Searching agents for: ${argv.query}`).start();
|
|
51
|
+
const agentService = new AgentService();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const agents = await agentService.searchAgents(argv.query);
|
|
55
|
+
|
|
56
|
+
spinner.succeed(chalk.green(`Found ${agents.length} agent${agents.length === 1 ? '' : 's'}`));
|
|
57
|
+
|
|
58
|
+
// Display search results
|
|
59
|
+
if (agents.length > 0) {
|
|
60
|
+
console.log(chalk.green(`\n${agents.length} agent${agents.length === 1 ? '' : 's'} matching '${argv.query}':`));
|
|
61
|
+
agents.forEach(agent => {
|
|
62
|
+
console.log(` - ${agent.name}: ${agent.title}`);
|
|
63
|
+
if (agent.specialization) {
|
|
64
|
+
console.log(` ${agent.specialization}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
console.log(chalk.yellow(`\nNo agents found matching '${argv.query}'.`));
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
spinner.fail(chalk.red('Failed to search agents'));
|
|
72
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Invoke an agent with a task
|
|
78
|
+
* @param {Object} argv - Command arguments
|
|
79
|
+
*/
|
|
80
|
+
async function agentInvoke(argv) {
|
|
81
|
+
const spinner = ora(`Invoking agent: ${argv.name}`).start();
|
|
82
|
+
const agentService = new AgentService();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (argv.stream) {
|
|
86
|
+
// Streaming mode
|
|
87
|
+
spinner.text = 'Streaming agent response...';
|
|
88
|
+
|
|
89
|
+
for await (const chunk of agentService.invokeStream(argv.name, argv.task, {})) {
|
|
90
|
+
process.stdout.write(chunk);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
spinner.succeed(chalk.green('Agent invoked successfully'));
|
|
94
|
+
} else {
|
|
95
|
+
// Non-streaming mode
|
|
96
|
+
const result = await agentService.invoke(argv.name, argv.task, {});
|
|
97
|
+
|
|
98
|
+
spinner.succeed(chalk.green('Agent invoked successfully'));
|
|
99
|
+
|
|
100
|
+
console.log(chalk.green('\nAgent Response:'));
|
|
101
|
+
console.log(result);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
spinner.fail(chalk.red('Failed to invoke agent'));
|
|
105
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Main command handler
|
|
111
|
+
* @param {Object} argv - Command arguments
|
|
112
|
+
*/
|
|
113
|
+
async function handler(argv) {
|
|
114
|
+
// Validate action
|
|
115
|
+
const validActions = ['list', 'search', 'invoke'];
|
|
116
|
+
|
|
117
|
+
if (!validActions.includes(argv.action)) {
|
|
118
|
+
console.error(chalk.red(`\nError: Unknown action: ${argv.action}`));
|
|
119
|
+
console.error(chalk.yellow(`Valid actions: ${validActions.join(', ')}`));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Route to appropriate handler
|
|
124
|
+
try {
|
|
125
|
+
switch (argv.action) {
|
|
126
|
+
case 'list':
|
|
127
|
+
await agentList(argv);
|
|
128
|
+
break;
|
|
129
|
+
case 'search':
|
|
130
|
+
await agentSearch(argv);
|
|
131
|
+
break;
|
|
132
|
+
case 'invoke':
|
|
133
|
+
await agentInvoke(argv);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// Global error handler for unexpected errors
|
|
138
|
+
console.error(chalk.red(`\nUnexpected error: ${error.message}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Command builder - registers all subcommands
|
|
144
|
+
* @param {Object} yargs - Yargs instance
|
|
145
|
+
* @returns {Object} Configured yargs instance
|
|
146
|
+
*/
|
|
147
|
+
function builder(yargs) {
|
|
148
|
+
return yargs
|
|
149
|
+
.command(
|
|
150
|
+
'list',
|
|
151
|
+
'List all available agents',
|
|
152
|
+
(yargs) => {
|
|
153
|
+
return yargs;
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
.command(
|
|
157
|
+
'search <query>',
|
|
158
|
+
'Search agents by keyword',
|
|
159
|
+
(yargs) => {
|
|
160
|
+
return yargs
|
|
161
|
+
.positional('query', {
|
|
162
|
+
describe: 'Search query',
|
|
163
|
+
type: 'string'
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
.command(
|
|
168
|
+
'invoke <name>',
|
|
169
|
+
'Invoke an agent with a task',
|
|
170
|
+
(yargs) => {
|
|
171
|
+
return yargs
|
|
172
|
+
.positional('name', {
|
|
173
|
+
describe: 'Agent name',
|
|
174
|
+
type: 'string'
|
|
175
|
+
})
|
|
176
|
+
.option('task', {
|
|
177
|
+
describe: 'Task description',
|
|
178
|
+
type: 'string',
|
|
179
|
+
demandOption: true
|
|
180
|
+
})
|
|
181
|
+
.option('stream', {
|
|
182
|
+
describe: 'Use streaming mode',
|
|
183
|
+
type: 'boolean',
|
|
184
|
+
default: false
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
.demandCommand(1, 'You must specify an agent action')
|
|
189
|
+
.strictCommands()
|
|
190
|
+
.help();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Command export
|
|
195
|
+
*/
|
|
196
|
+
module.exports = {
|
|
197
|
+
command: 'agent <action>',
|
|
198
|
+
describe: 'Manage agents',
|
|
199
|
+
builder,
|
|
200
|
+
handler,
|
|
201
|
+
handlers: {
|
|
202
|
+
list: agentList,
|
|
203
|
+
search: agentSearch,
|
|
204
|
+
invoke: agentInvoke
|
|
205
|
+
}
|
|
206
|
+
};
|