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
|
@@ -1,112 +1,423 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ClaudeProvider - Anthropic Claude API
|
|
2
|
+
* ClaudeProvider - Anthropic Claude API Integration
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* for
|
|
4
|
+
* Extends AbstractAIProvider to provide Claude-specific AI capabilities
|
|
5
|
+
* with full backward compatibility for existing code.
|
|
6
6
|
*
|
|
7
7
|
* Documentation Queries:
|
|
8
8
|
* - mcp://context7/anthropic/sdk - Anthropic SDK patterns
|
|
9
9
|
* - mcp://context7/anthropic/streaming - Streaming best practices
|
|
10
10
|
* - mcp://context7/nodejs/async-generators - Async generator patterns
|
|
11
|
+
*
|
|
12
|
+
* @extends AbstractAIProvider
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Legacy usage (backward compatible)
|
|
16
|
+
* const provider = new ClaudeProvider('sk-ant-...');
|
|
17
|
+
* const result = await provider.complete('Hello');
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Enhanced usage with config
|
|
21
|
+
* const provider = new ClaudeProvider({
|
|
22
|
+
* apiKey: 'sk-ant-...',
|
|
23
|
+
* model: 'claude-sonnet-4-20250514',
|
|
24
|
+
* temperature: 0.7,
|
|
25
|
+
* maxTokens: 4096
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Using environment variable
|
|
30
|
+
* process.env.ANTHROPIC_API_KEY = 'sk-ant-...';
|
|
31
|
+
* const provider = new ClaudeProvider({});
|
|
11
32
|
*/
|
|
12
33
|
|
|
13
34
|
const Anthropic = require('@anthropic-ai/sdk');
|
|
35
|
+
const AbstractAIProvider = require('./AbstractAIProvider');
|
|
36
|
+
const AIProviderError = require('../errors/AIProviderError');
|
|
14
37
|
|
|
15
38
|
/**
|
|
16
39
|
* ClaudeProvider class for Anthropic Claude API integration
|
|
40
|
+
*
|
|
41
|
+
* @class ClaudeProvider
|
|
42
|
+
* @extends AbstractAIProvider
|
|
17
43
|
*/
|
|
18
|
-
class ClaudeProvider {
|
|
44
|
+
class ClaudeProvider extends AbstractAIProvider {
|
|
19
45
|
/**
|
|
20
46
|
* Create a new ClaudeProvider instance
|
|
21
|
-
*
|
|
22
|
-
*
|
|
47
|
+
*
|
|
48
|
+
* Supports two constructor signatures for backward compatibility:
|
|
49
|
+
* 1. new ClaudeProvider('api-key') <- Legacy
|
|
50
|
+
* 2. new ClaudeProvider({ apiKey: 'key', ...options }) <- Enhanced
|
|
51
|
+
*
|
|
52
|
+
* @param {string|Object} [config={}] - API key string or configuration object
|
|
53
|
+
* @param {string} [config.apiKey] - Anthropic API key (or use ANTHROPIC_API_KEY env var)
|
|
54
|
+
* @param {string} [config.model] - Model to use (default: claude-sonnet-4-20250514)
|
|
55
|
+
* @param {number} [config.maxTokens] - Maximum tokens (default: 4096)
|
|
56
|
+
* @param {number} [config.temperature] - Temperature (default: 0.7)
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} If API key is not provided and ANTHROPIC_API_KEY env var is not set
|
|
23
59
|
*/
|
|
24
|
-
constructor(
|
|
25
|
-
|
|
26
|
-
|
|
60
|
+
constructor(config = {}) {
|
|
61
|
+
// Handle backward compatibility: string API key as first parameter
|
|
62
|
+
if (typeof config === 'string') {
|
|
63
|
+
config = { apiKey: config };
|
|
27
64
|
}
|
|
28
65
|
|
|
29
|
-
|
|
30
|
-
|
|
66
|
+
// Call parent constructor (handles config normalization and defaults)
|
|
67
|
+
super(config);
|
|
68
|
+
|
|
69
|
+
// Validate API key is available after parent constructor
|
|
70
|
+
if (!this.apiKey) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'API key is required for ClaudeProvider. ' +
|
|
73
|
+
'Provide it via constructor or set ANTHROPIC_API_KEY environment variable.'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Initialize Anthropic client with API key
|
|
78
|
+
this.client = new Anthropic({ apiKey: this.apiKey });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================================
|
|
82
|
+
// ABSTRACT METHOD IMPLEMENTATIONS (Required by AbstractAIProvider)
|
|
83
|
+
// ============================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the default model identifier for Claude
|
|
87
|
+
*
|
|
88
|
+
* @returns {string} Default Claude model
|
|
89
|
+
*/
|
|
90
|
+
getDefaultModel() {
|
|
91
|
+
return 'claude-sonnet-4-20250514';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the environment variable name for API key
|
|
96
|
+
*
|
|
97
|
+
* @returns {string} Environment variable name
|
|
98
|
+
*/
|
|
99
|
+
getApiKeyEnvVar() {
|
|
100
|
+
return 'ANTHROPIC_API_KEY';
|
|
31
101
|
}
|
|
32
102
|
|
|
33
103
|
/**
|
|
34
104
|
* Complete a prompt synchronously (wait for full response)
|
|
105
|
+
*
|
|
106
|
+
* Uses parent's _mergeOptions to combine instance config with method options.
|
|
107
|
+
* Method-level options take precedence over instance config.
|
|
108
|
+
*
|
|
109
|
+
* Automatically applies rate limiting if configured in constructor.
|
|
110
|
+
*
|
|
35
111
|
* @param {string} prompt - The prompt to complete
|
|
36
|
-
* @param {Object} options - Optional configuration
|
|
37
|
-
* @param {string} options.model - Model to use (
|
|
38
|
-
* @param {number} options.maxTokens - Maximum tokens
|
|
112
|
+
* @param {Object} [options={}] - Optional configuration
|
|
113
|
+
* @param {string} [options.model] - Model to use (overrides instance model)
|
|
114
|
+
* @param {number} [options.maxTokens] - Maximum tokens (overrides instance maxTokens)
|
|
115
|
+
* @param {number} [options.temperature] - Temperature (overrides instance temperature)
|
|
116
|
+
*
|
|
39
117
|
* @returns {Promise<string>} The completed text
|
|
118
|
+
* @throws {AIProviderError} On API errors
|
|
40
119
|
*/
|
|
41
120
|
async complete(prompt, options = {}) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
max_tokens: options.maxTokens || 4096,
|
|
46
|
-
messages: [{ role: 'user', content: prompt }]
|
|
47
|
-
});
|
|
121
|
+
return this._withRateLimit(async () => {
|
|
122
|
+
// Merge instance config with method options
|
|
123
|
+
const finalOptions = this._mergeOptions(options);
|
|
48
124
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
125
|
+
try {
|
|
126
|
+
const response = await this.client.messages.create({
|
|
127
|
+
model: finalOptions.model,
|
|
128
|
+
max_tokens: finalOptions.maxTokens,
|
|
129
|
+
temperature: finalOptions.temperature,
|
|
130
|
+
messages: [{ role: 'user', content: prompt }]
|
|
131
|
+
});
|
|
53
132
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
133
|
+
// Extract text from response
|
|
134
|
+
if (response.content && response.content.length > 0) {
|
|
135
|
+
return response.content[0].text || '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return '';
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// Format error using parent's formatError + Claude-specific mapping
|
|
141
|
+
throw this.formatError(error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
58
144
|
}
|
|
59
145
|
|
|
60
146
|
/**
|
|
61
147
|
* Stream a prompt response (async generator for real-time feedback)
|
|
148
|
+
*
|
|
149
|
+
* Uses parent's _mergeOptions to combine instance config with method options.
|
|
150
|
+
* Method-level options take precedence over instance config.
|
|
151
|
+
*
|
|
152
|
+
* Automatically applies rate limiting if configured in constructor.
|
|
153
|
+
*
|
|
62
154
|
* @param {string} prompt - The prompt to complete
|
|
63
|
-
* @param {Object} options - Optional configuration
|
|
64
|
-
* @param {string} options.model - Model to use (
|
|
65
|
-
* @param {number} options.maxTokens - Maximum tokens
|
|
155
|
+
* @param {Object} [options={}] - Optional configuration
|
|
156
|
+
* @param {string} [options.model] - Model to use (overrides instance model)
|
|
157
|
+
* @param {number} [options.maxTokens] - Maximum tokens (overrides instance maxTokens)
|
|
158
|
+
* @param {number} [options.temperature] - Temperature (overrides instance temperature)
|
|
159
|
+
*
|
|
66
160
|
* @yields {string} Text chunks as they arrive
|
|
161
|
+
* @throws {AIProviderError} On API errors
|
|
67
162
|
*/
|
|
68
163
|
async *stream(prompt, options = {}) {
|
|
164
|
+
// Apply rate limiting before initiating stream
|
|
165
|
+
if (this.rateLimiter) {
|
|
166
|
+
await this.rateLimiter.removeTokens(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Merge instance config with method options
|
|
170
|
+
const finalOptions = this._mergeOptions(options);
|
|
171
|
+
|
|
69
172
|
try {
|
|
70
173
|
const stream = await this.client.messages.create({
|
|
71
|
-
model:
|
|
72
|
-
max_tokens:
|
|
174
|
+
model: finalOptions.model,
|
|
175
|
+
max_tokens: finalOptions.maxTokens,
|
|
176
|
+
temperature: finalOptions.temperature,
|
|
73
177
|
stream: true,
|
|
74
178
|
messages: [{ role: 'user', content: prompt }]
|
|
75
179
|
});
|
|
76
180
|
|
|
77
181
|
// Yield text deltas as they arrive
|
|
78
182
|
for await (const event of stream) {
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
183
|
+
if (
|
|
184
|
+
event.type === 'content_block_delta' &&
|
|
185
|
+
event.delta &&
|
|
186
|
+
event.delta.type === 'text_delta'
|
|
187
|
+
) {
|
|
82
188
|
yield event.delta.text;
|
|
83
189
|
}
|
|
84
190
|
}
|
|
85
191
|
} catch (error) {
|
|
86
|
-
|
|
192
|
+
// Format error using parent's formatError + Claude-specific mapping
|
|
193
|
+
throw this.formatError(error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================
|
|
198
|
+
// CAPABILITY OVERRIDES
|
|
199
|
+
// ============================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if Claude supports streaming
|
|
203
|
+
*
|
|
204
|
+
* @returns {boolean} True (Claude supports streaming)
|
|
205
|
+
*/
|
|
206
|
+
supportsStreaming() {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if Claude supports function calling
|
|
212
|
+
*
|
|
213
|
+
* @returns {boolean} True (Claude supports tools/function calling)
|
|
214
|
+
*/
|
|
215
|
+
supportsFunctionCalling() {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check if Claude supports chat format
|
|
221
|
+
*
|
|
222
|
+
* @returns {boolean} True (Claude has native chat format)
|
|
223
|
+
*/
|
|
224
|
+
supportsChat() {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if Claude supports vision/image inputs
|
|
230
|
+
*
|
|
231
|
+
* @returns {boolean} False (not implemented yet)
|
|
232
|
+
*/
|
|
233
|
+
supportsVision() {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ============================================================
|
|
238
|
+
// ENHANCED ERROR HANDLING
|
|
239
|
+
// ============================================================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Format Anthropic API errors into AIProviderError
|
|
243
|
+
*
|
|
244
|
+
* Maps Anthropic-specific error codes and HTTP statuses to AIProviderError codes.
|
|
245
|
+
* Falls back to parent's formatError for unknown errors.
|
|
246
|
+
*
|
|
247
|
+
* @param {Error} error - The error to format
|
|
248
|
+
* @returns {AIProviderError} Formatted error with appropriate code and metadata
|
|
249
|
+
*/
|
|
250
|
+
formatError(error) {
|
|
251
|
+
// Already an AIProviderError, return as-is
|
|
252
|
+
if (error instanceof AIProviderError) {
|
|
253
|
+
return error;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Map Anthropic HTTP status codes to AIProviderError codes
|
|
257
|
+
if (error.status) {
|
|
258
|
+
switch (error.status) {
|
|
259
|
+
case 401:
|
|
260
|
+
return new AIProviderError(
|
|
261
|
+
AIProviderError.INVALID_API_KEY,
|
|
262
|
+
'Invalid Anthropic API key or authentication failed',
|
|
263
|
+
true,
|
|
264
|
+
401
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
case 429:
|
|
268
|
+
return new AIProviderError(
|
|
269
|
+
AIProviderError.RATE_LIMIT,
|
|
270
|
+
'Claude API rate limit exceeded. Please retry after a delay.',
|
|
271
|
+
true,
|
|
272
|
+
429
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
case 500:
|
|
276
|
+
case 502:
|
|
277
|
+
case 503:
|
|
278
|
+
case 504:
|
|
279
|
+
return new AIProviderError(
|
|
280
|
+
AIProviderError.SERVICE_UNAVAILABLE,
|
|
281
|
+
`Claude API service temporarily unavailable (${error.status})`,
|
|
282
|
+
true,
|
|
283
|
+
error.status
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
case 400:
|
|
287
|
+
return new AIProviderError(
|
|
288
|
+
AIProviderError.INVALID_REQUEST,
|
|
289
|
+
error.message || 'Invalid request parameters',
|
|
290
|
+
true,
|
|
291
|
+
400
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
default:
|
|
295
|
+
// Unknown status code, wrap in generic error
|
|
296
|
+
return new AIProviderError(
|
|
297
|
+
'UNKNOWN_ERROR',
|
|
298
|
+
`Claude API error (${error.status}): ${error.message}`,
|
|
299
|
+
true,
|
|
300
|
+
error.status
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// No status code, check for specific error types
|
|
306
|
+
if (error.message) {
|
|
307
|
+
const lowerMessage = error.message.toLowerCase();
|
|
308
|
+
|
|
309
|
+
if (lowerMessage.includes('api key') || lowerMessage.includes('authentication')) {
|
|
310
|
+
return new AIProviderError(
|
|
311
|
+
AIProviderError.INVALID_API_KEY,
|
|
312
|
+
error.message,
|
|
313
|
+
true
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (lowerMessage.includes('rate limit')) {
|
|
318
|
+
return new AIProviderError(
|
|
319
|
+
AIProviderError.RATE_LIMIT,
|
|
320
|
+
error.message,
|
|
321
|
+
true
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (lowerMessage.includes('network') || lowerMessage.includes('connection')) {
|
|
326
|
+
return new AIProviderError(
|
|
327
|
+
AIProviderError.NETWORK_ERROR,
|
|
328
|
+
error.message,
|
|
329
|
+
true
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (lowerMessage.includes('context') || lowerMessage.includes('too long')) {
|
|
334
|
+
return new AIProviderError(
|
|
335
|
+
AIProviderError.CONTEXT_LENGTH_EXCEEDED,
|
|
336
|
+
error.message,
|
|
337
|
+
true
|
|
338
|
+
);
|
|
339
|
+
}
|
|
87
340
|
}
|
|
341
|
+
|
|
342
|
+
// Fall back to parent's error handling
|
|
343
|
+
return super.formatError(error);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============================================================
|
|
347
|
+
// ENHANCED CHAT SUPPORT
|
|
348
|
+
// ============================================================
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Chat completion with message history
|
|
352
|
+
*
|
|
353
|
+
* Overrides parent's fallback implementation to use Claude's native chat format.
|
|
354
|
+
* Converts system role to user role (Claude requirement).
|
|
355
|
+
*
|
|
356
|
+
* Automatically applies rate limiting if configured in constructor.
|
|
357
|
+
*
|
|
358
|
+
* @param {Array<{role: string, content: string}>} messages - Chat messages
|
|
359
|
+
* @param {Object} [options={}] - Optional configuration
|
|
360
|
+
*
|
|
361
|
+
* @returns {Promise<string>} Completion response
|
|
362
|
+
* @throws {AIProviderError} On API errors
|
|
363
|
+
*/
|
|
364
|
+
async chat(messages, options = {}) {
|
|
365
|
+
return this._withRateLimit(async () => {
|
|
366
|
+
// Merge instance config with method options
|
|
367
|
+
const finalOptions = this._mergeOptions(options);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// Convert system role to user (Claude doesn't support system role in messages array)
|
|
371
|
+
const claudeMessages = messages.map(msg => ({
|
|
372
|
+
role: msg.role === 'system' ? 'user' : msg.role,
|
|
373
|
+
content: msg.content
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
const response = await this.client.messages.create({
|
|
377
|
+
model: finalOptions.model,
|
|
378
|
+
max_tokens: finalOptions.maxTokens,
|
|
379
|
+
temperature: finalOptions.temperature,
|
|
380
|
+
messages: claudeMessages
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Extract text from response
|
|
384
|
+
if (response.content && response.content.length > 0) {
|
|
385
|
+
return response.content[0].text || '';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return '';
|
|
389
|
+
} catch (error) {
|
|
390
|
+
throw this.formatError(error);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
88
393
|
}
|
|
89
394
|
|
|
395
|
+
// ============================================================
|
|
396
|
+
// BACKWARD COMPATIBILITY METHODS
|
|
397
|
+
// ============================================================
|
|
398
|
+
|
|
90
399
|
/**
|
|
91
|
-
* Get the current model being used
|
|
400
|
+
* Get the current model being used (legacy method for backward compatibility)
|
|
401
|
+
*
|
|
402
|
+
* Note: This returns the default model, not the instance model.
|
|
403
|
+
* For instance model, use provider.model property.
|
|
404
|
+
*
|
|
92
405
|
* @returns {string} The default model name
|
|
406
|
+
* @deprecated Use getDefaultModel() or access provider.model instead
|
|
93
407
|
*/
|
|
94
408
|
getModel() {
|
|
95
|
-
return
|
|
409
|
+
return this.getDefaultModel();
|
|
96
410
|
}
|
|
97
411
|
|
|
98
412
|
/**
|
|
99
|
-
* Test the API connection
|
|
413
|
+
* Test the API connection (inherited from AbstractAIProvider)
|
|
414
|
+
*
|
|
415
|
+
* This method is provided by the parent class and works by making a test
|
|
416
|
+
* complete() call. Included here for documentation completeness.
|
|
417
|
+
*
|
|
100
418
|
* @returns {Promise<boolean>} True if connection is successful
|
|
101
419
|
*/
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
await this.complete('Hello');
|
|
105
|
-
return true;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
420
|
+
// testConnection() is inherited from AbstractAIProvider
|
|
110
421
|
}
|
|
111
422
|
|
|
112
423
|
module.exports = ClaudeProvider;
|