feature-architect-agent 1.0.13 → 1.0.15
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/dist/cli/index.js +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +93 -3
- package/dist/llm/Claude.d.ts.map +1 -1
- package/dist/llm/Claude.js +42 -26
- package/dist/llm/Gemini.d.ts.map +1 -1
- package/dist/llm/Gemini.js +43 -27
- package/dist/llm/MiniMax.d.ts.map +1 -1
- package/dist/llm/MiniMax.js +9 -1
- package/dist/llm/OpenAI.d.ts.map +1 -1
- package/dist/llm/OpenAI.js +41 -25
- package/dist/llm/Opencode.d.ts.map +1 -1
- package/dist/llm/Opencode.js +9 -1
- package/dist/llm/ZAI.d.ts.map +1 -1
- package/dist/llm/ZAI.js +9 -1
- package/dist/services/ActivityLogger.d.ts +119 -0
- package/dist/services/ActivityLogger.d.ts.map +1 -0
- package/dist/services/ActivityLogger.js +514 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const program = new commander_1.Command();
|
|
|
10
10
|
program
|
|
11
11
|
.name('feature-architect')
|
|
12
12
|
.description('AI-powered feature planning agent - generate complete technical specifications')
|
|
13
|
-
.version('1.0.
|
|
13
|
+
.version('1.0.14');
|
|
14
14
|
program
|
|
15
15
|
.command('init')
|
|
16
16
|
.description('Initialize in project (analyze codebase)')
|
|
@@ -52,7 +52,7 @@ program
|
|
|
52
52
|
});
|
|
53
53
|
// Default: show help if no command
|
|
54
54
|
program.on('command:*', () => {
|
|
55
|
-
(0, logger_js_1.header)('Feature Architect Agent v1.0.
|
|
55
|
+
(0, logger_js_1.header)('Feature Architect Agent v1.0.12');
|
|
56
56
|
(0, logger_js_1.info)('AI-powered feature planning for development teams\n');
|
|
57
57
|
(0, logger_js_1.dim)('Quick start:');
|
|
58
58
|
(0, logger_js_1.dim)(' 1. feature-architect init # Analyze codebase');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAQA,wBAAsB,WAAW,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGvE"}
|
package/dist/commands/init.js
CHANGED
|
@@ -9,18 +9,27 @@ const Scanner_js_1 = require("../services/Scanner.js");
|
|
|
9
9
|
const Analyzer_js_1 = require("../services/Analyzer.js");
|
|
10
10
|
const ContextManager_js_1 = require("../services/ContextManager.js");
|
|
11
11
|
const logger_js_1 = require("../utils/logger.js");
|
|
12
|
+
const ActivityLogger_js_1 = require("../services/ActivityLogger.js");
|
|
12
13
|
async function initCommand(force = false) {
|
|
13
14
|
(0, logger_js_1.header)('🔍 Feature Architect Agent - Initialization');
|
|
15
|
+
// Log activity to dashboard
|
|
16
|
+
const logger = (0, ActivityLogger_js_1.getActivityLogger)();
|
|
17
|
+
const activityId = logger.log({
|
|
18
|
+
command: 'init',
|
|
19
|
+
status: 'pending'
|
|
20
|
+
});
|
|
14
21
|
const contextManager = new ContextManager_js_1.ContextManager();
|
|
15
22
|
// Check if already initialized
|
|
16
23
|
if (!force && await contextManager.contextExists()) {
|
|
17
24
|
const context = await contextManager.loadContext();
|
|
18
25
|
(0, logger_js_1.info)(`Already initialized on ${context?.metadata.analyzedAt}`);
|
|
19
26
|
(0, logger_js_1.info)('Use --force to re-initialize');
|
|
27
|
+
logger.updateStatus(activityId, 'completed', { note: 'Already initialized' });
|
|
20
28
|
return;
|
|
21
29
|
}
|
|
22
30
|
// Scan codebase
|
|
23
31
|
(0, logger_js_1.info)('Scanning codebase...');
|
|
32
|
+
logger.updateStatus(activityId, 'in-progress');
|
|
24
33
|
const scanner = new Scanner_js_1.ScannerService();
|
|
25
34
|
const { files, stats } = await scanner.scanWithStats();
|
|
26
35
|
(0, logger_js_1.bullet)(`Found ${stats.total} files`);
|
|
@@ -71,6 +80,13 @@ async function initCommand(force = false) {
|
|
|
71
80
|
(0, logger_js_1.info)(`Type: ${projectType}`);
|
|
72
81
|
(0, logger_js_1.info)(`Frameworks: ${context.summary.frameworks.join(', ') || 'Unknown'}`);
|
|
73
82
|
(0, logger_js_1.info)(`Patterns indexed: ${context.summary.patterns}`);
|
|
83
|
+
// Log completion
|
|
84
|
+
logger.complete(activityId, {
|
|
85
|
+
projectName,
|
|
86
|
+
projectType,
|
|
87
|
+
frameworks: context.summary.frameworks,
|
|
88
|
+
filesIndexed: stats.total
|
|
89
|
+
});
|
|
74
90
|
const contextPath = await contextManager.getContextPath();
|
|
75
91
|
(0, logger_js_1.dim)(`\nContext saved to: ${contextPath}`);
|
|
76
92
|
(0, logger_js_1.info)('\nYou can now plan features:');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AA+BA,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACjB,GACL,OAAO,CAAC,IAAI,CAAC,CAuOf"}
|
package/dist/commands/plan.js
CHANGED
|
@@ -11,8 +11,34 @@ const Planner_js_1 = require("../services/Planner.js");
|
|
|
11
11
|
const factory_js_1 = require("../llm/factory.js");
|
|
12
12
|
const logger_js_1 = require("../utils/logger.js");
|
|
13
13
|
const api_config_js_1 = require("../config/api.config.js");
|
|
14
|
+
const ActivityLogger_js_1 = require("../services/ActivityLogger.js");
|
|
15
|
+
// Get project name from current working directory
|
|
16
|
+
function getProjectName() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const folderName = path_1.default.basename(cwd);
|
|
19
|
+
// Try to read from package.json if it exists
|
|
20
|
+
try {
|
|
21
|
+
const packageJsonPath = path_1.default.join(cwd, 'package.json');
|
|
22
|
+
if (require('fs').existsSync(packageJsonPath)) {
|
|
23
|
+
const packageJson = JSON.parse(require('fs').readFileSync(packageJsonPath, 'utf-8'));
|
|
24
|
+
if (packageJson.name) {
|
|
25
|
+
// Use package.json name, but clean it up (remove @scope/ prefix)
|
|
26
|
+
return packageJson.name.replace(/^@[^/]+\//, '');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
// Ignore errors, fall back to folder name
|
|
32
|
+
}
|
|
33
|
+
return folderName;
|
|
34
|
+
}
|
|
14
35
|
async function planCommand(feature, options = {}) {
|
|
15
36
|
(0, logger_js_1.header)('🧠 Feature Architect Agent - Plan Feature');
|
|
37
|
+
// Get project name from current directory
|
|
38
|
+
const projectName = getProjectName();
|
|
39
|
+
// Initialize logger early for error tracking
|
|
40
|
+
const logger = (0, ActivityLogger_js_1.getActivityLogger)();
|
|
41
|
+
let activityId = null;
|
|
16
42
|
if (!feature) {
|
|
17
43
|
(0, logger_js_1.error)('Please provide a feature description');
|
|
18
44
|
(0, logger_js_1.info)('Usage: feature-architect plan "your feature description"');
|
|
@@ -135,25 +161,62 @@ async function planCommand(feature, options = {}) {
|
|
|
135
161
|
}
|
|
136
162
|
// Create AI provider
|
|
137
163
|
(0, logger_js_1.info)(`\nUsing AI: ${provider}${usingBuiltinKey ? ' (built-in team key)' : ''}...`);
|
|
164
|
+
// Log activity to dashboard (after provider detection)
|
|
165
|
+
activityId = logger.log({
|
|
166
|
+
command: 'plan',
|
|
167
|
+
feature,
|
|
168
|
+
provider,
|
|
169
|
+
status: 'in-progress', // Start directly as in-progress
|
|
170
|
+
metadata: {
|
|
171
|
+
startedAt: new Date().toISOString(),
|
|
172
|
+
progress: 5,
|
|
173
|
+
currentStep: 'Initializing AI provider...',
|
|
174
|
+
projectName: projectName
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
showProgressBar(5, 'Initializing AI provider...');
|
|
138
178
|
const llm = (0, factory_js_1.createProvider)({
|
|
139
179
|
provider: provider,
|
|
140
180
|
apiKey
|
|
141
181
|
});
|
|
142
182
|
const planner = new Planner_js_1.PlannerService(llm);
|
|
143
|
-
// Generate plan
|
|
144
|
-
(
|
|
183
|
+
// Generate plan with progress updates
|
|
184
|
+
updateProgress(logger, activityId, 15, 'Analyzing feature requirements...');
|
|
185
|
+
updateProgress(logger, activityId, 25, 'Loading project context...');
|
|
186
|
+
updateProgress(logger, activityId, 35, 'Preparing AI prompt...');
|
|
145
187
|
try {
|
|
188
|
+
(0, logger_js_1.info)('\n📋 Step 1/5: Analyzing feature requirements...');
|
|
189
|
+
updateProgress(logger, activityId, 15, 'Analyzing feature requirements...');
|
|
190
|
+
(0, logger_js_1.info)('📋 Step 2/5: Loading project context...');
|
|
191
|
+
updateProgress(logger, activityId, 25, 'Loading project context...');
|
|
192
|
+
(0, logger_js_1.info)('📋 Step 3/5: Preparing AI prompt...');
|
|
193
|
+
updateProgress(logger, activityId, 35, 'Preparing AI prompt...');
|
|
194
|
+
(0, logger_js_1.info)('📋 Step 4/5: Generating feature plan with AI...');
|
|
195
|
+
updateProgress(logger, activityId, 40, 'Sending request to AI provider...');
|
|
146
196
|
const plan = await planner.planFeature(feature, context, {
|
|
147
197
|
output: options.output,
|
|
148
198
|
includeDiagrams: !options.noDiagrams
|
|
149
199
|
});
|
|
200
|
+
updateProgress(logger, activityId, 70, 'AI response received, processing...');
|
|
201
|
+
(0, logger_js_1.info)('📋 Step 5/5: Writing output file...');
|
|
202
|
+
updateProgress(logger, activityId, 80, 'Writing output file...');
|
|
150
203
|
// Ensure output directory exists
|
|
151
204
|
const slug = plan.slug;
|
|
152
205
|
const outputFile = options.output || path_1.default.join('docs', 'features', `${slug}.md`);
|
|
153
206
|
await promises_1.default.mkdir(path_1.default.dirname(outputFile), { recursive: true });
|
|
154
207
|
await promises_1.default.writeFile(outputFile, plan.markdown, 'utf-8');
|
|
208
|
+
updateProgress(logger, activityId, 100, 'Complete!');
|
|
209
|
+
// Clear progress line and show success
|
|
210
|
+
process.stdout.write('\r\x1b[K');
|
|
155
211
|
(0, logger_js_1.success)('✅ Feature plan generated!');
|
|
156
212
|
(0, logger_js_1.info)(`Saved to: ${outputFile}`);
|
|
213
|
+
// Log activity as completed
|
|
214
|
+
logger.complete(activityId, {
|
|
215
|
+
outputFile,
|
|
216
|
+
slug,
|
|
217
|
+
lines: plan.markdown.split('\n').length,
|
|
218
|
+
hasDiagrams: !options.noDiagrams
|
|
219
|
+
});
|
|
157
220
|
// Show preview
|
|
158
221
|
const lines = plan.markdown.split('\n');
|
|
159
222
|
const preview = lines.slice(0, 20).join('\n');
|
|
@@ -169,10 +232,37 @@ async function planCommand(feature, options = {}) {
|
|
|
169
232
|
(0, logger_js_1.dim)(` # Or paste into ChatGPT/Cursor/Copilot`);
|
|
170
233
|
}
|
|
171
234
|
catch (err) {
|
|
172
|
-
|
|
235
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
236
|
+
process.stdout.write('\r\x1b[K'); // Clear progress line
|
|
237
|
+
(0, logger_js_1.error)(`Failed to generate plan: ${errorMsg}`);
|
|
173
238
|
(0, logger_js_1.warn)('\nTroubleshooting:');
|
|
239
|
+
// Log activity as failed (if we had created one)
|
|
240
|
+
if (activityId) {
|
|
241
|
+
logger.fail(activityId, errorMsg);
|
|
242
|
+
}
|
|
174
243
|
(0, logger_js_1.bullet)('Check your API key is set correctly');
|
|
175
244
|
(0, logger_js_1.bullet)('Ensure you have network connectivity');
|
|
176
245
|
(0, logger_js_1.bullet)('Try a shorter feature description');
|
|
177
246
|
}
|
|
178
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Update progress in dashboard and show in terminal
|
|
250
|
+
*/
|
|
251
|
+
function updateProgress(logger, activityId, progress, currentStep) {
|
|
252
|
+
// Update dashboard
|
|
253
|
+
logger.updateProgress(activityId, progress, currentStep);
|
|
254
|
+
// Show in terminal
|
|
255
|
+
showProgressBar(progress, currentStep);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Show a progress bar in the terminal with step info
|
|
259
|
+
*/
|
|
260
|
+
function showProgressBar(progress, message) {
|
|
261
|
+
const barLength = 30;
|
|
262
|
+
const filled = Math.round((progress / 100) * barLength);
|
|
263
|
+
const empty = barLength - filled;
|
|
264
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
265
|
+
const percentage = progress.toString().padStart(3, ' ');
|
|
266
|
+
// Clear line and write progress with more visible message
|
|
267
|
+
process.stdout.write(`\r\x1b[K[${bar}] ${percentage}% - ${message}`);
|
|
268
|
+
}
|
package/dist/llm/Claude.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Claude.d.ts","sourceRoot":"","sources":["../../src/llm/Claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/D,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAqC;IAKlE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Claude.d.ts","sourceRoot":"","sources":["../../src/llm/Claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/D,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAqC;IAKlE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;CAsD/E"}
|
package/dist/llm/Claude.js
CHANGED
|
@@ -11,34 +11,50 @@ class ClaudeProvider {
|
|
|
11
11
|
async generate(prompt, options = {}) {
|
|
12
12
|
const temperature = options.temperature ?? 0.7;
|
|
13
13
|
const maxTokens = options.maxTokens ?? 8000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
// Create abort controller for timeout (5 minutes)
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'x-api-key': this.apiKey,
|
|
22
|
+
'anthropic-version': '2023-06-01',
|
|
23
|
+
'content-type': 'application/json'
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
model: this.model,
|
|
27
|
+
max_tokens: maxTokens,
|
|
28
|
+
temperature,
|
|
29
|
+
messages: [
|
|
30
|
+
{
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: prompt
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}),
|
|
36
|
+
signal: controller.signal
|
|
37
|
+
});
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const error = await response.text();
|
|
41
|
+
throw new Error(`Claude API error: ${response.status} ${error}`);
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
if (data.error) {
|
|
45
|
+
throw new Error(`Claude API error: ${data.error.message}`);
|
|
46
|
+
}
|
|
47
|
+
return data.content[0].text;
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof Error) {
|
|
51
|
+
if (error.name === 'AbortError') {
|
|
52
|
+
throw new Error('Claude request timed out after 5 minutes. Please try again.');
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Claude request failed: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error('Claude request failed with unknown error');
|
|
40
57
|
}
|
|
41
|
-
return data.content[0].text;
|
|
42
58
|
}
|
|
43
59
|
}
|
|
44
60
|
exports.ClaudeProvider = ClaudeProvider;
|
package/dist/llm/Gemini.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gemini.d.ts","sourceRoot":"","sources":["../../src/llm/Gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,SAAwB;IAKnD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Gemini.d.ts","sourceRoot":"","sources":["../../src/llm/Gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,SAAwB;IAKnD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,CAAC;CA2D5G"}
|
package/dist/llm/Gemini.js
CHANGED
|
@@ -11,36 +11,52 @@ class GeminiProvider {
|
|
|
11
11
|
async generate(prompt, options = {}) {
|
|
12
12
|
const temperature = options.temperature ?? 0.7;
|
|
13
13
|
const maxTokens = options.maxTokens ?? 4000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
parts: [
|
|
23
|
-
{
|
|
24
|
-
text: prompt,
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
generationConfig: {
|
|
30
|
-
temperature,
|
|
31
|
-
maxOutputTokens: maxTokens,
|
|
14
|
+
// Create abort controller for timeout (5 minutes)
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
32
22
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
contents: [
|
|
25
|
+
{
|
|
26
|
+
parts: [
|
|
27
|
+
{
|
|
28
|
+
text: prompt,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
generationConfig: {
|
|
34
|
+
temperature,
|
|
35
|
+
maxOutputTokens: maxTokens,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
signal: controller.signal
|
|
39
|
+
});
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const error = await response.text();
|
|
43
|
+
throw new Error(`Gemini API error: ${response.status} ${error}`);
|
|
44
|
+
}
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
if (data.error) {
|
|
47
|
+
throw new Error(`Gemini API error: ${data.error.message}`);
|
|
48
|
+
}
|
|
49
|
+
return data.candidates[0].content.parts[0].text;
|
|
38
50
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof Error) {
|
|
53
|
+
if (error.name === 'AbortError') {
|
|
54
|
+
throw new Error('Gemini request timed out after 5 minutes. Please try again.');
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Gemini request failed: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
throw new Error('Gemini request failed with unknown error');
|
|
42
59
|
}
|
|
43
|
-
return data.candidates[0].content.parts[0].text;
|
|
44
60
|
}
|
|
45
61
|
}
|
|
46
62
|
exports.GeminiProvider = GeminiProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MiniMax.d.ts","sourceRoot":"","sources":["../../src/llm/MiniMax.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAgB,YAAW,WAAW;IACjD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAGtB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,aAAkB;IAQtB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"MiniMax.d.ts","sourceRoot":"","sources":["../../src/llm/MiniMax.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAgB,YAAW,WAAW;IACjD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAGtB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,aAAkB;IAQtB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;IAiElB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,SAAS,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAGhD"}
|
package/dist/llm/MiniMax.js
CHANGED
|
@@ -27,6 +27,9 @@ class MiniMaxProvider {
|
|
|
27
27
|
async generate(prompt, options = {}) {
|
|
28
28
|
const temperature = options.temperature ?? 0.7;
|
|
29
29
|
const maxTokens = options.maxTokens ?? 8000;
|
|
30
|
+
// Create abort controller for timeout (5 minutes)
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
30
33
|
try {
|
|
31
34
|
const headers = {
|
|
32
35
|
'Content-Type': 'application/json',
|
|
@@ -50,8 +53,10 @@ class MiniMaxProvider {
|
|
|
50
53
|
temperature,
|
|
51
54
|
tokens_to_generate: maxTokens,
|
|
52
55
|
stream: false
|
|
53
|
-
})
|
|
56
|
+
}),
|
|
57
|
+
signal: controller.signal
|
|
54
58
|
});
|
|
59
|
+
clearTimeout(timeout);
|
|
55
60
|
if (!response.ok) {
|
|
56
61
|
const errorText = await response.text();
|
|
57
62
|
throw new Error(`MiniMax API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
@@ -65,6 +70,9 @@ class MiniMaxProvider {
|
|
|
65
70
|
}
|
|
66
71
|
catch (error) {
|
|
67
72
|
if (error instanceof Error) {
|
|
73
|
+
if (error.name === 'AbortError') {
|
|
74
|
+
throw new Error('MiniMax request timed out after 5 minutes. Please try again.');
|
|
75
|
+
}
|
|
68
76
|
throw new Error(`MiniMax request failed: ${error.message}`);
|
|
69
77
|
}
|
|
70
78
|
throw new Error('MiniMax request failed with unknown error');
|
package/dist/llm/OpenAI.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenAI.d.ts","sourceRoot":"","sources":["../../src/llm/OpenAI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/D,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAsB;IAKnD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"OpenAI.d.ts","sourceRoot":"","sources":["../../src/llm/OpenAI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/D,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAsB;IAKnD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;CAqD/E"}
|
package/dist/llm/OpenAI.js
CHANGED
|
@@ -11,33 +11,49 @@ class OpenAIProvider {
|
|
|
11
11
|
async generate(prompt, options = {}) {
|
|
12
12
|
const temperature = options.temperature ?? 0.7;
|
|
13
13
|
const maxTokens = options.maxTokens ?? 4000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
14
|
+
// Create abort controller for timeout (5 minutes)
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
22
|
+
'Content-Type': 'application/json'
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
model: this.model,
|
|
26
|
+
messages: [
|
|
27
|
+
{
|
|
28
|
+
role: 'user',
|
|
29
|
+
content: prompt
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
temperature,
|
|
33
|
+
max_tokens: maxTokens
|
|
34
|
+
}),
|
|
35
|
+
signal: controller.signal
|
|
36
|
+
});
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const error = await response.text();
|
|
40
|
+
throw new Error(`OpenAI API error: ${response.status} ${error}`);
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
if (data.error) {
|
|
44
|
+
throw new Error(`OpenAI API error: ${data.error.message}`);
|
|
45
|
+
}
|
|
46
|
+
return data.choices[0].message.content;
|
|
35
47
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
if (error.name === 'AbortError') {
|
|
51
|
+
throw new Error('OpenAI request timed out after 5 minutes. Please try again.');
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`OpenAI request failed: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
throw new Error('OpenAI request failed with unknown error');
|
|
39
56
|
}
|
|
40
|
-
return data.choices[0].message.content;
|
|
41
57
|
}
|
|
42
58
|
}
|
|
43
59
|
exports.OpenAIProvider = OpenAIProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Opencode.d.ts","sourceRoot":"","sources":["../../src/llm/Opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAGrB,MAAM,GAAE,MAAqB,EAAE,0CAA0C;IACzE,MAAM,GAAE,cAAmB;IAOvB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Opencode.d.ts","sourceRoot":"","sources":["../../src/llm/Opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAGrB,MAAM,GAAE,MAAqB,EAAE,0CAA0C;IACzE,MAAM,GAAE,cAAmB;IAOvB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;IA0DlB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,SAAS,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAGhD"}
|
package/dist/llm/Opencode.js
CHANGED
|
@@ -40,6 +40,9 @@ class OpenCodeProvider {
|
|
|
40
40
|
async generate(prompt, options = {}) {
|
|
41
41
|
const temperature = options.temperature ?? 0.7;
|
|
42
42
|
const maxTokens = options.maxTokens ?? 8000;
|
|
43
|
+
// Create abort controller for timeout (5 minutes)
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
43
46
|
try {
|
|
44
47
|
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
45
48
|
method: 'POST',
|
|
@@ -59,8 +62,10 @@ class OpenCodeProvider {
|
|
|
59
62
|
],
|
|
60
63
|
temperature,
|
|
61
64
|
max_tokens: maxTokens
|
|
62
|
-
})
|
|
65
|
+
}),
|
|
66
|
+
signal: controller.signal
|
|
63
67
|
});
|
|
68
|
+
clearTimeout(timeout);
|
|
64
69
|
if (!response.ok) {
|
|
65
70
|
const errorText = await response.text();
|
|
66
71
|
throw new Error(`OpenCode API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
@@ -73,6 +78,9 @@ class OpenCodeProvider {
|
|
|
73
78
|
}
|
|
74
79
|
catch (error) {
|
|
75
80
|
if (error instanceof Error) {
|
|
81
|
+
if (error.name === 'AbortError') {
|
|
82
|
+
throw new Error('OpenCode request timed out after 5 minutes. Please try again.');
|
|
83
|
+
}
|
|
76
84
|
throw new Error(`OpenCode request failed: ${error.message}`);
|
|
77
85
|
}
|
|
78
86
|
throw new Error('OpenCode request failed with unknown error');
|
package/dist/llm/ZAI.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZAI.d.ts","sourceRoot":"","sources":["../../src/llm/ZAI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAY,YAAW,WAAW;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAS;gBAGtB,MAAM,GAAE,MAAW,EACnB,MAAM,GAAE,SAAc;IAWlB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ZAI.d.ts","sourceRoot":"","sources":["../../src/llm/ZAI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAY,YAAW,WAAW;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAS;gBAGtB,MAAM,GAAE,MAAW,EACnB,MAAM,GAAE,SAAc;IAWlB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACzD,OAAO,CAAC,MAAM,CAAC;IAyDlB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,SAAS,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAGhD"}
|
package/dist/llm/ZAI.js
CHANGED
|
@@ -29,6 +29,9 @@ class ZAIProvider {
|
|
|
29
29
|
async generate(prompt, options = {}) {
|
|
30
30
|
const temperature = options.temperature ?? 0.7;
|
|
31
31
|
const maxTokens = options.maxTokens ?? 8000;
|
|
32
|
+
// Create abort controller for timeout (5 minutes)
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000); // 5 minute timeout
|
|
32
35
|
try {
|
|
33
36
|
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
34
37
|
method: 'POST',
|
|
@@ -47,8 +50,10 @@ class ZAIProvider {
|
|
|
47
50
|
temperature,
|
|
48
51
|
max_tokens: maxTokens,
|
|
49
52
|
stream: false
|
|
50
|
-
})
|
|
53
|
+
}),
|
|
54
|
+
signal: controller.signal
|
|
51
55
|
});
|
|
56
|
+
clearTimeout(timeout);
|
|
52
57
|
if (!response.ok) {
|
|
53
58
|
const errorText = await response.text();
|
|
54
59
|
throw new Error(`Z AI API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
@@ -61,6 +66,9 @@ class ZAIProvider {
|
|
|
61
66
|
}
|
|
62
67
|
catch (error) {
|
|
63
68
|
if (error instanceof Error) {
|
|
69
|
+
if (error.name === 'AbortError') {
|
|
70
|
+
throw new Error('Z AI request timed out after 5 minutes. Please try again.');
|
|
71
|
+
}
|
|
64
72
|
throw new Error(`Z AI request failed: ${error.message}`);
|
|
65
73
|
}
|
|
66
74
|
throw new Error('Z AI request failed with unknown error');
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Logger for Feature Architect Agent
|
|
3
|
+
* Logs all CLI activity to ~/.feature-architect/activity.json
|
|
4
|
+
* AND sends to remote dashboard server if configured
|
|
5
|
+
*/
|
|
6
|
+
export interface ActivityEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
user: string;
|
|
10
|
+
hostname: string;
|
|
11
|
+
command: string;
|
|
12
|
+
feature?: string;
|
|
13
|
+
provider?: string;
|
|
14
|
+
status: 'pending' | 'in-progress' | 'completed' | 'failed';
|
|
15
|
+
metadata?: {
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
error?: string;
|
|
18
|
+
duration?: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface ActivityLoggerOptions {
|
|
22
|
+
maxEntries?: number;
|
|
23
|
+
activityFile?: string;
|
|
24
|
+
dashboardUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class ActivityLogger {
|
|
27
|
+
private activityFile;
|
|
28
|
+
private maxEntries;
|
|
29
|
+
private cache;
|
|
30
|
+
private lastModified;
|
|
31
|
+
private dashboardUrl;
|
|
32
|
+
private syncedActivities;
|
|
33
|
+
private pendingSyncActivities;
|
|
34
|
+
private pendingProgressUpdates;
|
|
35
|
+
private pendingActivityUpdates;
|
|
36
|
+
constructor(options?: ActivityLoggerOptions);
|
|
37
|
+
private ensureActivityFile;
|
|
38
|
+
private readActivity;
|
|
39
|
+
private writeActivity;
|
|
40
|
+
private generateId;
|
|
41
|
+
/**
|
|
42
|
+
* Log a new activity entry
|
|
43
|
+
*/
|
|
44
|
+
log(entry: Omit<ActivityEntry, 'id' | 'timestamp' | 'user' | 'hostname'>): string;
|
|
45
|
+
/**
|
|
46
|
+
* Send activity to remote dashboard server
|
|
47
|
+
*/
|
|
48
|
+
private sendToDashboard;
|
|
49
|
+
/**
|
|
50
|
+
* Update progress of an activity entry with streaming support
|
|
51
|
+
*/
|
|
52
|
+
updateProgress(id: string, progress: number, currentStep: string, output?: any, error?: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Update an existing activity entry
|
|
55
|
+
*/
|
|
56
|
+
update(id: string, updates: Partial<Omit<ActivityEntry, 'id' | 'timestamp' | 'user' | 'hostname'>>): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Update the status of an activity entry
|
|
59
|
+
*/
|
|
60
|
+
updateStatus(id: string, status: ActivityEntry['status'], metadata?: ActivityEntry['metadata']): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Mark an activity as completed
|
|
63
|
+
*/
|
|
64
|
+
complete(id: string, metadata?: ActivityEntry['metadata']): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Mark an activity as failed
|
|
67
|
+
*/
|
|
68
|
+
fail(id: string, error: string, metadata?: ActivityEntry['metadata']): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Send progress update to remote dashboard server
|
|
71
|
+
*/
|
|
72
|
+
private sendProgressToDashboard;
|
|
73
|
+
/**
|
|
74
|
+
* Flush queued progress updates for an activity after it's synced
|
|
75
|
+
*/
|
|
76
|
+
private flushPendingProgressUpdates;
|
|
77
|
+
/**
|
|
78
|
+
* Flush queued activity updates (status changes) for an activity after it's synced
|
|
79
|
+
*/
|
|
80
|
+
private flushPendingActivityUpdates;
|
|
81
|
+
/**
|
|
82
|
+
* Get all activity entries
|
|
83
|
+
*/
|
|
84
|
+
getAll(): ActivityEntry[];
|
|
85
|
+
/**
|
|
86
|
+
* Get activity entries filtered by status
|
|
87
|
+
*/
|
|
88
|
+
getByStatus(status: ActivityEntry['status']): ActivityEntry[];
|
|
89
|
+
/**
|
|
90
|
+
* Get activity entries for a specific user
|
|
91
|
+
*/
|
|
92
|
+
getByUser(user: string): ActivityEntry[];
|
|
93
|
+
/**
|
|
94
|
+
* Get recent activity entries
|
|
95
|
+
*/
|
|
96
|
+
getRecent(limit?: number): ActivityEntry[];
|
|
97
|
+
/**
|
|
98
|
+
* Clear old activity entries
|
|
99
|
+
*/
|
|
100
|
+
clearOld(days?: number): number;
|
|
101
|
+
/**
|
|
102
|
+
* Clear all activity entries
|
|
103
|
+
*/
|
|
104
|
+
clear(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Get activity statistics
|
|
107
|
+
*/
|
|
108
|
+
getStats(): {
|
|
109
|
+
total: number;
|
|
110
|
+
byStatus: Record<string, number>;
|
|
111
|
+
byCommand: Record<string, number>;
|
|
112
|
+
byFeature: Record<string, number>;
|
|
113
|
+
byProvider: Record<string, number>;
|
|
114
|
+
byUser: Record<string, number>;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export declare function getActivityLogger(options?: ActivityLoggerOptions): ActivityLogger;
|
|
118
|
+
export default getActivityLogger;
|
|
119
|
+
//# sourceMappingURL=ActivityLogger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivityLogger.d.ts","sourceRoot":"","sources":["../../src/services/ActivityLogger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Activity Logger for Feature Architect Agent
|
|
4
|
+
* Logs all CLI activity to ~/.feature-architect/activity.json
|
|
5
|
+
* AND sends to remote dashboard server if configured
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.ActivityLogger = void 0;
|
|
42
|
+
exports.getActivityLogger = getActivityLogger;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const crypto = __importStar(require("crypto"));
|
|
47
|
+
const http = __importStar(require("http"));
|
|
48
|
+
const https = __importStar(require("https"));
|
|
49
|
+
class ActivityLogger {
|
|
50
|
+
activityFile;
|
|
51
|
+
maxEntries;
|
|
52
|
+
cache = [];
|
|
53
|
+
lastModified = 0;
|
|
54
|
+
dashboardUrl;
|
|
55
|
+
// Track which activities have been created on dashboard
|
|
56
|
+
syncedActivities = new Set();
|
|
57
|
+
// Track activities pending sync to prevent duplicate POSTs
|
|
58
|
+
pendingSyncActivities = new Set();
|
|
59
|
+
// Queue progress updates for activities not yet synced
|
|
60
|
+
pendingProgressUpdates = new Map();
|
|
61
|
+
// Queue full activity updates (status changes) for activities not yet synced
|
|
62
|
+
pendingActivityUpdates = new Map();
|
|
63
|
+
constructor(options = {}) {
|
|
64
|
+
this.maxEntries = options.maxEntries || 1000;
|
|
65
|
+
this.activityFile = options.activityFile || path.join(os.homedir(), '.feature-architect', 'activity.json');
|
|
66
|
+
// Hardcoded default dashboard URL for team monitoring
|
|
67
|
+
this.dashboardUrl = options.dashboardUrl || process.env.FEATURE_ARCHITECT_DASHBOARD_URL || 'https://6301-171-61-163-152.ngrok-free.app';
|
|
68
|
+
this.ensureActivityFile();
|
|
69
|
+
}
|
|
70
|
+
ensureActivityFile() {
|
|
71
|
+
const dir = path.dirname(this.activityFile);
|
|
72
|
+
if (!fs.existsSync(dir)) {
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
if (!fs.existsSync(this.activityFile)) {
|
|
76
|
+
this.writeActivity([]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
readActivity() {
|
|
80
|
+
try {
|
|
81
|
+
const stats = fs.statSync(this.activityFile);
|
|
82
|
+
if (stats.mtimeMs > this.lastModified) {
|
|
83
|
+
const content = fs.readFileSync(this.activityFile, 'utf-8');
|
|
84
|
+
this.cache = JSON.parse(content);
|
|
85
|
+
this.lastModified = stats.mtimeMs;
|
|
86
|
+
}
|
|
87
|
+
return this.cache;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error('Error reading activity file:', error);
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
writeActivity(activity) {
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(this.activityFile, JSON.stringify(activity, null, 2));
|
|
97
|
+
this.cache = activity;
|
|
98
|
+
this.lastModified = Date.now();
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error('Error writing activity file:', error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
generateId() {
|
|
105
|
+
return Date.now().toString(36) + crypto.randomBytes(8).toString('hex').slice(0, 8);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Log a new activity entry
|
|
109
|
+
*/
|
|
110
|
+
log(entry) {
|
|
111
|
+
const activityEntry = {
|
|
112
|
+
id: this.generateId(),
|
|
113
|
+
timestamp: new Date().toISOString(),
|
|
114
|
+
user: os.userInfo().username,
|
|
115
|
+
hostname: os.hostname(),
|
|
116
|
+
...entry
|
|
117
|
+
};
|
|
118
|
+
const activity = this.readActivity();
|
|
119
|
+
activity.push(activityEntry);
|
|
120
|
+
// Keep only the last maxEntries
|
|
121
|
+
if (activity.length > this.maxEntries) {
|
|
122
|
+
activity.splice(0, activity.length - this.maxEntries);
|
|
123
|
+
}
|
|
124
|
+
this.writeActivity(activity);
|
|
125
|
+
// Send to remote dashboard if configured
|
|
126
|
+
if (this.dashboardUrl) {
|
|
127
|
+
this.sendToDashboard(activityEntry).catch(err => {
|
|
128
|
+
// Silently fail - don't block CLI if dashboard is unreachable
|
|
129
|
+
console.error('Dashboard sync failed:', err.message);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return activityEntry.id;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Send activity to remote dashboard server
|
|
136
|
+
*/
|
|
137
|
+
async sendToDashboard(entry) {
|
|
138
|
+
if (!this.dashboardUrl)
|
|
139
|
+
return;
|
|
140
|
+
const url = new URL(this.dashboardUrl);
|
|
141
|
+
const isHttps = url.protocol === 'https:';
|
|
142
|
+
const client = isHttps ? https : http;
|
|
143
|
+
// Determine if this is a new activity or an update based on sync state
|
|
144
|
+
const isNewActivity = !this.syncedActivities.has(entry.id);
|
|
145
|
+
const method = isNewActivity ? 'POST' : 'PATCH';
|
|
146
|
+
const path = isNewActivity ? '/api/activity' : `/api/activity/${entry.id}`;
|
|
147
|
+
// If this is a new activity and already pending sync, queue the update instead of skipping
|
|
148
|
+
if (isNewActivity) {
|
|
149
|
+
if (this.pendingSyncActivities.has(entry.id)) {
|
|
150
|
+
// Queue this update to send after initial sync completes
|
|
151
|
+
this.pendingActivityUpdates.set(entry.id, entry);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Mark as pending sync for new activities
|
|
155
|
+
this.pendingSyncActivities.add(entry.id);
|
|
156
|
+
}
|
|
157
|
+
// Include activityId in POST data so dashboard uses our ID
|
|
158
|
+
const postData = JSON.stringify(isNewActivity
|
|
159
|
+
? {
|
|
160
|
+
activityId: entry.id, // Send our ID so dashboard uses it
|
|
161
|
+
user: entry.user,
|
|
162
|
+
hostname: entry.hostname,
|
|
163
|
+
command: entry.command,
|
|
164
|
+
feature: entry.feature,
|
|
165
|
+
provider: entry.provider,
|
|
166
|
+
status: entry.status,
|
|
167
|
+
metadata: entry.metadata || {}
|
|
168
|
+
}
|
|
169
|
+
: {
|
|
170
|
+
status: entry.status,
|
|
171
|
+
metadata: entry.metadata || {}
|
|
172
|
+
});
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const options = {
|
|
175
|
+
hostname: url.hostname,
|
|
176
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
177
|
+
path,
|
|
178
|
+
method,
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
182
|
+
},
|
|
183
|
+
// Add timeout to prevent blocking
|
|
184
|
+
timeout: 5000
|
|
185
|
+
};
|
|
186
|
+
const req = client.request(options, (res) => {
|
|
187
|
+
let data = '';
|
|
188
|
+
res.on('data', chunk => data += chunk);
|
|
189
|
+
res.on('end', () => {
|
|
190
|
+
if (res.statusCode === 200 || res.statusCode === 201 || res.statusCode === 202) {
|
|
191
|
+
// Mark activity as synced on dashboard after successful POST
|
|
192
|
+
if (isNewActivity) {
|
|
193
|
+
this.syncedActivities.add(entry.id);
|
|
194
|
+
this.pendingSyncActivities.delete(entry.id);
|
|
195
|
+
// Flush any queued progress updates
|
|
196
|
+
this.flushPendingProgressUpdates(entry.id).catch(err => {
|
|
197
|
+
// Silently fail - don't block the main request
|
|
198
|
+
console.error('Failed to flush progress updates:', err instanceof Error ? err.message : err);
|
|
199
|
+
});
|
|
200
|
+
// Flush any queued activity updates
|
|
201
|
+
this.flushPendingActivityUpdates(entry.id).catch(err => {
|
|
202
|
+
// Silently fail - don't block the main request
|
|
203
|
+
console.error('Failed to flush activity updates:', err instanceof Error ? err.message : err);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
resolve();
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Remove from pending on error
|
|
210
|
+
if (isNewActivity) {
|
|
211
|
+
this.pendingSyncActivities.delete(entry.id);
|
|
212
|
+
}
|
|
213
|
+
reject(new Error(`Dashboard returned ${res.statusCode}`));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
req.on('error', (err) => {
|
|
218
|
+
// Remove from pending on error
|
|
219
|
+
if (isNewActivity) {
|
|
220
|
+
this.pendingSyncActivities.delete(entry.id);
|
|
221
|
+
}
|
|
222
|
+
reject(err);
|
|
223
|
+
});
|
|
224
|
+
req.on('timeout', () => {
|
|
225
|
+
// Remove from pending on timeout
|
|
226
|
+
if (isNewActivity) {
|
|
227
|
+
this.pendingSyncActivities.delete(entry.id);
|
|
228
|
+
}
|
|
229
|
+
req.destroy();
|
|
230
|
+
reject(new Error('Dashboard request timeout'));
|
|
231
|
+
});
|
|
232
|
+
req.write(postData);
|
|
233
|
+
req.end();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Update progress of an activity entry with streaming support
|
|
238
|
+
*/
|
|
239
|
+
updateProgress(id, progress, currentStep, output, error) {
|
|
240
|
+
const activity = this.readActivity();
|
|
241
|
+
const index = activity.findIndex(entry => entry.id === id);
|
|
242
|
+
if (index === -1) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
// Update progress and determine status
|
|
246
|
+
let status = activity[index].status;
|
|
247
|
+
if (progress > 0 && progress < 100) {
|
|
248
|
+
status = 'in-progress';
|
|
249
|
+
}
|
|
250
|
+
else if (progress === 100) {
|
|
251
|
+
status = 'completed';
|
|
252
|
+
}
|
|
253
|
+
activity[index] = {
|
|
254
|
+
...activity[index],
|
|
255
|
+
status,
|
|
256
|
+
metadata: {
|
|
257
|
+
...activity[index].metadata,
|
|
258
|
+
progress,
|
|
259
|
+
currentStep,
|
|
260
|
+
output,
|
|
261
|
+
error,
|
|
262
|
+
updatedAt: new Date().toISOString()
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
this.writeActivity(activity);
|
|
266
|
+
// Send progress update to dashboard if configured
|
|
267
|
+
if (this.dashboardUrl) {
|
|
268
|
+
this.sendProgressToDashboard(id, progress, currentStep, output, error).catch(err => {
|
|
269
|
+
// Silently fail - don't block CLI if dashboard is unreachable
|
|
270
|
+
console.error('Dashboard progress sync failed:', err.message);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Update an existing activity entry
|
|
277
|
+
*/
|
|
278
|
+
update(id, updates) {
|
|
279
|
+
const activity = this.readActivity();
|
|
280
|
+
const index = activity.findIndex(entry => entry.id === id);
|
|
281
|
+
if (index === -1) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
activity[index] = {
|
|
285
|
+
...activity[index],
|
|
286
|
+
...updates
|
|
287
|
+
};
|
|
288
|
+
this.writeActivity(activity);
|
|
289
|
+
// Send update to dashboard if configured
|
|
290
|
+
if (this.dashboardUrl) {
|
|
291
|
+
this.sendToDashboard(activity[index]).catch(err => {
|
|
292
|
+
// Silently fail - don't block CLI if dashboard is unreachable
|
|
293
|
+
console.error('Dashboard sync failed:', err.message);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Update the status of an activity entry
|
|
300
|
+
*/
|
|
301
|
+
updateStatus(id, status, metadata) {
|
|
302
|
+
return this.update(id, { status, metadata });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Mark an activity as completed
|
|
306
|
+
*/
|
|
307
|
+
complete(id, metadata) {
|
|
308
|
+
return this.updateStatus(id, 'completed', metadata);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Mark an activity as failed
|
|
312
|
+
*/
|
|
313
|
+
fail(id, error, metadata) {
|
|
314
|
+
return this.updateStatus(id, 'failed', { ...metadata, error });
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Send progress update to remote dashboard server
|
|
318
|
+
*/
|
|
319
|
+
async sendProgressToDashboard(id, progress, currentStep, output, error) {
|
|
320
|
+
if (!this.dashboardUrl)
|
|
321
|
+
return;
|
|
322
|
+
// Determine status based on progress
|
|
323
|
+
let status = 'pending';
|
|
324
|
+
if (progress > 0 && progress < 100) {
|
|
325
|
+
status = 'in-progress';
|
|
326
|
+
}
|
|
327
|
+
else if (progress === 100) {
|
|
328
|
+
status = 'completed';
|
|
329
|
+
}
|
|
330
|
+
else if (error) {
|
|
331
|
+
status = 'failed';
|
|
332
|
+
}
|
|
333
|
+
// If activity is not synced yet, queue the progress update
|
|
334
|
+
if (!this.syncedActivities.has(id)) {
|
|
335
|
+
if (!this.pendingProgressUpdates.has(id)) {
|
|
336
|
+
this.pendingProgressUpdates.set(id, []);
|
|
337
|
+
}
|
|
338
|
+
// Keep only the latest progress update (or last few if needed)
|
|
339
|
+
const queue = this.pendingProgressUpdates.get(id);
|
|
340
|
+
queue.push({ progress, currentStep, output, error });
|
|
341
|
+
// Limit queue size to prevent memory issues
|
|
342
|
+
if (queue.length > 10) {
|
|
343
|
+
queue.shift();
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const url = new URL(this.dashboardUrl);
|
|
348
|
+
const isHttps = url.protocol === 'https:';
|
|
349
|
+
const client = isHttps ? https : http;
|
|
350
|
+
const postData = JSON.stringify({
|
|
351
|
+
status,
|
|
352
|
+
progress,
|
|
353
|
+
currentStep,
|
|
354
|
+
output,
|
|
355
|
+
error
|
|
356
|
+
});
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const options = {
|
|
359
|
+
hostname: url.hostname,
|
|
360
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
361
|
+
path: `/api/activity/${id}/progress`,
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
366
|
+
},
|
|
367
|
+
timeout: 3000 // Shorter timeout for progress updates
|
|
368
|
+
};
|
|
369
|
+
const req = client.request(options, (res) => {
|
|
370
|
+
let data = '';
|
|
371
|
+
res.on('data', chunk => data += chunk);
|
|
372
|
+
res.on('end', () => {
|
|
373
|
+
if (res.statusCode === 200 || res.statusCode === 201) {
|
|
374
|
+
resolve();
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
reject(new Error(`Dashboard returned ${res.statusCode}`));
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
req.on('error', reject);
|
|
382
|
+
req.on('timeout', () => {
|
|
383
|
+
req.destroy();
|
|
384
|
+
reject(new Error('Dashboard progress request timeout'));
|
|
385
|
+
});
|
|
386
|
+
req.write(postData);
|
|
387
|
+
req.end();
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Flush queued progress updates for an activity after it's synced
|
|
392
|
+
*/
|
|
393
|
+
async flushPendingProgressUpdates(id) {
|
|
394
|
+
const queued = this.pendingProgressUpdates.get(id);
|
|
395
|
+
if (!queued || queued.length === 0) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
// Send each queued progress update
|
|
399
|
+
for (const update of queued) {
|
|
400
|
+
try {
|
|
401
|
+
await this.sendProgressToDashboard(id, update.progress, update.currentStep, update.output, update.error);
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
// Continue sending other updates even if one fails
|
|
405
|
+
console.error('Failed to send queued progress update:', err instanceof Error ? err.message : err);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Clear the queue after sending
|
|
409
|
+
this.pendingProgressUpdates.delete(id);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Flush queued activity updates (status changes) for an activity after it's synced
|
|
413
|
+
*/
|
|
414
|
+
async flushPendingActivityUpdates(id) {
|
|
415
|
+
const queued = this.pendingActivityUpdates.get(id);
|
|
416
|
+
if (!queued) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
// Send the queued activity update as a PATCH
|
|
421
|
+
await this.sendToDashboard(queued);
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
console.error('Failed to send queued activity update:', err instanceof Error ? err.message : err);
|
|
425
|
+
}
|
|
426
|
+
// Clear the queue after sending
|
|
427
|
+
this.pendingActivityUpdates.delete(id);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get all activity entries
|
|
431
|
+
*/
|
|
432
|
+
getAll() {
|
|
433
|
+
return this.readActivity();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get activity entries filtered by status
|
|
437
|
+
*/
|
|
438
|
+
getByStatus(status) {
|
|
439
|
+
return this.readActivity().filter(entry => entry.status === status);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get activity entries for a specific user
|
|
443
|
+
*/
|
|
444
|
+
getByUser(user) {
|
|
445
|
+
return this.readActivity().filter(entry => entry.user === user);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get recent activity entries
|
|
449
|
+
*/
|
|
450
|
+
getRecent(limit = 10) {
|
|
451
|
+
return this.readActivity().slice(-limit).reverse();
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clear old activity entries
|
|
455
|
+
*/
|
|
456
|
+
clearOld(days = 30) {
|
|
457
|
+
const cutoff = new Date();
|
|
458
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
459
|
+
let activity = this.readActivity();
|
|
460
|
+
const originalLength = activity.length;
|
|
461
|
+
activity = activity.filter(entry => new Date(entry.timestamp) > cutoff);
|
|
462
|
+
this.writeActivity(activity);
|
|
463
|
+
return originalLength - activity.length;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Clear all activity entries
|
|
467
|
+
*/
|
|
468
|
+
clear() {
|
|
469
|
+
this.writeActivity([]);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Get activity statistics
|
|
473
|
+
*/
|
|
474
|
+
getStats() {
|
|
475
|
+
const activity = this.readActivity();
|
|
476
|
+
return {
|
|
477
|
+
total: activity.length,
|
|
478
|
+
byStatus: activity.reduce((acc, entry) => {
|
|
479
|
+
acc[entry.status] = (acc[entry.status] || 0) + 1;
|
|
480
|
+
return acc;
|
|
481
|
+
}, {}),
|
|
482
|
+
byCommand: activity.reduce((acc, entry) => {
|
|
483
|
+
acc[entry.command] = (acc[entry.command] || 0) + 1;
|
|
484
|
+
return acc;
|
|
485
|
+
}, {}),
|
|
486
|
+
byFeature: activity.reduce((acc, entry) => {
|
|
487
|
+
if (entry.feature) {
|
|
488
|
+
acc[entry.feature] = (acc[entry.feature] || 0) + 1;
|
|
489
|
+
}
|
|
490
|
+
return acc;
|
|
491
|
+
}, {}),
|
|
492
|
+
byProvider: activity.reduce((acc, entry) => {
|
|
493
|
+
if (entry.provider) {
|
|
494
|
+
acc[entry.provider] = (acc[entry.provider] || 0) + 1;
|
|
495
|
+
}
|
|
496
|
+
return acc;
|
|
497
|
+
}, {}),
|
|
498
|
+
byUser: activity.reduce((acc, entry) => {
|
|
499
|
+
acc[entry.user] = (acc[entry.user] || 0) + 1;
|
|
500
|
+
return acc;
|
|
501
|
+
}, {})
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
exports.ActivityLogger = ActivityLogger;
|
|
506
|
+
// Singleton instance
|
|
507
|
+
let loggerInstance = null;
|
|
508
|
+
function getActivityLogger(options) {
|
|
509
|
+
if (!loggerInstance) {
|
|
510
|
+
loggerInstance = new ActivityLogger(options);
|
|
511
|
+
}
|
|
512
|
+
return loggerInstance;
|
|
513
|
+
}
|
|
514
|
+
exports.default = getActivityLogger;
|