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 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.11');
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.11');
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":"AAOA,wBAAsB,WAAW,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EvE"}
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"}
@@ -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":"AAQA,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,CA2Kf"}
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"}
@@ -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
- (0, logger_js_1.info)('Generating feature plan...\n');
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
- (0, logger_js_1.error)(`Failed to generate plan: ${err instanceof Error ? err.message : String(err)}`);
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
+ }
@@ -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;CAqC/E"}
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"}
@@ -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
- const response = await fetch('https://api.anthropic.com/v1/messages', {
15
- method: 'POST',
16
- headers: {
17
- 'x-api-key': this.apiKey,
18
- 'anthropic-version': '2023-06-01',
19
- 'content-type': 'application/json'
20
- },
21
- body: JSON.stringify({
22
- model: this.model,
23
- max_tokens: maxTokens,
24
- temperature,
25
- messages: [
26
- {
27
- role: 'user',
28
- content: prompt
29
- }
30
- ]
31
- })
32
- });
33
- if (!response.ok) {
34
- const error = await response.text();
35
- throw new Error(`Claude API error: ${response.status} ${error}`);
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
- const data = await response.json();
38
- if (data.error) {
39
- throw new Error(`Claude API error: ${data.error.message}`);
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;
@@ -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;CA0C5G"}
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"}
@@ -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
- const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`, {
15
- method: 'POST',
16
- headers: {
17
- 'Content-Type': 'application/json',
18
- },
19
- body: JSON.stringify({
20
- contents: [
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
- if (!response.ok) {
36
- const error = await response.text();
37
- throw new Error(`Gemini API error: ${response.status} ${error}`);
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
- const data = await response.json();
40
- if (data.error) {
41
- throw new Error(`Gemini API error: ${data.error.message}`);
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;IAuDlB;;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"}
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"}
@@ -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');
@@ -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;CAoC/E"}
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"}
@@ -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
- const response = await fetch('https://api.openai.com/v1/chat/completions', {
15
- method: 'POST',
16
- headers: {
17
- 'Authorization': `Bearer ${this.apiKey}`,
18
- 'Content-Type': 'application/json'
19
- },
20
- body: JSON.stringify({
21
- model: this.model,
22
- messages: [
23
- {
24
- role: 'user',
25
- content: prompt
26
- }
27
- ],
28
- temperature,
29
- max_tokens: maxTokens
30
- })
31
- });
32
- if (!response.ok) {
33
- const error = await response.text();
34
- throw new Error(`OpenAI API error: ${response.status} ${error}`);
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
- const data = await response.json();
37
- if (data.error) {
38
- throw new Error(`OpenAI API error: ${data.error.message}`);
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;IAgDlB;;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"}
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"}
@@ -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');
@@ -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;IA+ClB;;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"}
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feature-architect-agent",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "AI-powered feature planning agent - generates complete technical specifications",
5
5
  "main": "dist/index.js",
6
6
  "bin": {