genesis-ai-cli 14.3.0 → 14.4.0

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.
@@ -298,7 +298,12 @@ class AgenticToolExecutor {
298
298
  }
299
299
  // Read file with line numbers
300
300
  async readFile(args) {
301
- const filePath = path.isAbsolute(args.file_path) ? args.file_path : path.join(this.cwd, args.file_path);
301
+ // Accept both file_path and path for flexibility
302
+ const inputPath = args.file_path || args.path;
303
+ if (!inputPath) {
304
+ return 'Error: file_path is required';
305
+ }
306
+ const filePath = path.isAbsolute(inputPath) ? inputPath : path.join(this.cwd, inputPath);
302
307
  if (!fs.existsSync(filePath)) {
303
308
  return `File not found: ${filePath}`;
304
309
  }
@@ -317,7 +322,11 @@ class AgenticToolExecutor {
317
322
  }
318
323
  // Write file
319
324
  async writeFile(args) {
320
- const filePath = path.isAbsolute(args.file_path) ? args.file_path : path.join(this.cwd, args.file_path);
325
+ const inputPath = args.file_path || args.path;
326
+ if (!inputPath) {
327
+ return 'Error: file_path is required';
328
+ }
329
+ const filePath = path.isAbsolute(inputPath) ? inputPath : path.join(this.cwd, inputPath);
321
330
  // Ensure directory exists
322
331
  const dir = path.dirname(filePath);
323
332
  if (!fs.existsSync(dir)) {
@@ -328,7 +337,11 @@ class AgenticToolExecutor {
328
337
  }
329
338
  // Edit file with string replacement
330
339
  async editFile(args) {
331
- const filePath = path.isAbsolute(args.file_path) ? args.file_path : path.join(this.cwd, args.file_path);
340
+ const inputPath = args.file_path || args.path;
341
+ if (!inputPath) {
342
+ return 'Error: file_path is required';
343
+ }
344
+ const filePath = path.isAbsolute(inputPath) ? inputPath : path.join(this.cwd, inputPath);
332
345
  if (!fs.existsSync(filePath)) {
333
346
  return `File not found: ${filePath}`;
334
347
  }
@@ -677,8 +690,17 @@ ${colorize('Commands:', 'cyan')}
677
690
  const toolMatch = content.match(/```tool\s*\n([\s\S]*?)\n```/);
678
691
  if (toolMatch) {
679
692
  try {
680
- const toolCall = JSON.parse(toolMatch[1]);
693
+ const rawJson = toolMatch[1].trim();
694
+ const toolCall = JSON.parse(rawJson);
695
+ // Ensure arguments exists
696
+ if (!toolCall.arguments) {
697
+ toolCall.arguments = {};
698
+ }
681
699
  console.log((0, ui_js_1.muted)(`\n ⚙ ${toolCall.name}...`));
700
+ // Debug: show what arguments were parsed
701
+ if (Object.keys(toolCall.arguments).length === 0) {
702
+ console.log((0, ui_js_1.muted)(` ⚠ No arguments parsed. Raw JSON: ${rawJson.slice(0, 200)}`));
703
+ }
682
704
  const result = await this.executor.execute({
683
705
  id: `tool-${Date.now()}`,
684
706
  name: toolCall.name,
package/dist/src/index.js CHANGED
@@ -225,7 +225,13 @@ async function cmdPipeline(specOrFile, options) {
225
225
  console.error(c(`Error: Spec file not found: ${specOrFile}`, 'red'));
226
226
  process.exit(1);
227
227
  }
228
- spec = JSON.parse(fs.readFileSync(specOrFile, 'utf-8'));
228
+ try {
229
+ spec = JSON.parse(fs.readFileSync(specOrFile, 'utf-8'));
230
+ }
231
+ catch (err) {
232
+ console.error(c(`Error: Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`, 'red'));
233
+ process.exit(1);
234
+ }
229
235
  }
230
236
  else {
231
237
  spec = specOrFile;
@@ -296,7 +302,14 @@ async function cmdDesign(specFile) {
296
302
  console.error(c(`Error: Spec file not found: ${specFile}`, 'red'));
297
303
  process.exit(1);
298
304
  }
299
- const spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
305
+ let spec;
306
+ try {
307
+ spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
308
+ }
309
+ catch (err) {
310
+ console.error(c(`Error: Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`, 'red'));
311
+ process.exit(1);
312
+ }
300
313
  console.log(c(`\nDesigning architecture for: ${spec.name}\n`, 'bold'));
301
314
  const orchestrator = (0, orchestrator_js_1.createOrchestrator)({ verbose: true });
302
315
  const prompt = orchestrator.buildArchitecturePrompt(spec, {
@@ -319,7 +332,14 @@ async function cmdGenerate(specFile) {
319
332
  console.error(c(`Error: Spec file not found: ${specFile}`, 'red'));
320
333
  process.exit(1);
321
334
  }
322
- const spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
335
+ let spec;
336
+ try {
337
+ spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
338
+ }
339
+ catch (err) {
340
+ console.error(c(`Error: Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`, 'red'));
341
+ process.exit(1);
342
+ }
323
343
  console.log(c(`\nGenerating code for: ${spec.name}\n`, 'bold'));
324
344
  const orchestrator = (0, orchestrator_js_1.createOrchestrator)({ verbose: true });
325
345
  const prompt = orchestrator.buildCodePrompt(spec, { components: [], relations: [], invariants: [], operations: [], events: [] }, 'typescript');
@@ -337,7 +357,14 @@ async function cmdVisualize(specFile) {
337
357
  console.error(c(`Error: Spec file not found: ${specFile}`, 'red'));
338
358
  process.exit(1);
339
359
  }
340
- const spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
360
+ let spec;
361
+ try {
362
+ spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
363
+ }
364
+ catch (err) {
365
+ console.error(c(`Error: Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`, 'red'));
366
+ process.exit(1);
367
+ }
341
368
  console.log(c(`\nCreating visuals for: ${spec.name}\n`, 'bold'));
342
369
  const orchestrator = (0, orchestrator_js_1.createOrchestrator)({ verbose: true });
343
370
  const visualTypes = ['architecture', 'concept', 'logo'];
@@ -355,7 +382,14 @@ async function cmdPublish(specFile) {
355
382
  console.error(c(`Error: Spec file not found: ${specFile}`, 'red'));
356
383
  process.exit(1);
357
384
  }
358
- const spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
385
+ let spec;
386
+ try {
387
+ spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
388
+ }
389
+ catch (err) {
390
+ console.error(c(`Error: Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`, 'red'));
391
+ process.exit(1);
392
+ }
359
393
  console.log(c(`\nPublishing: ${spec.name}\n`, 'bold'));
360
394
  console.log(c('Steps:', 'cyan'));
361
395
  console.log(' 1. Create GitHub repository');
@@ -1805,7 +1839,14 @@ async function cmdAgentic(subcommand, options) {
1805
1839
  // ============================================================================
1806
1840
  async function cmdInstall(options) {
1807
1841
  const { execSync, spawn } = await import('child_process');
1808
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
1842
+ let pkg;
1843
+ try {
1844
+ pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
1845
+ }
1846
+ catch (err) {
1847
+ console.error(c('Error: Could not read package.json', 'red'));
1848
+ process.exit(1);
1849
+ }
1809
1850
  const version = pkg.version;
1810
1851
  console.log(c(`\n=== Genesis Installation (v${version}) ===\n`, 'bold'));
1811
1852
  // Check current installation
@@ -101,6 +101,8 @@ export declare class HybridRouter {
101
101
  private localBridge;
102
102
  private cloudBridge;
103
103
  private hardwareProfile;
104
+ private ollamaAvailableCache;
105
+ private static readonly OLLAMA_CACHE_TTL_MS;
104
106
  constructor(config?: Partial<RouterConfig>);
105
107
  /**
106
108
  * Get detected hardware profile
@@ -116,6 +118,10 @@ export declare class HybridRouter {
116
118
  execute(prompt: string, systemPrompt?: string): Promise<LLMResponse & {
117
119
  routingDecision: RoutingDecision;
118
120
  }>;
121
+ /**
122
+ * Check if Ollama is available
123
+ * v14.4.0: Results cached for 5 seconds to avoid repeated network calls
124
+ */
119
125
  private isOllamaAvailable;
120
126
  private getBridge;
121
127
  private updateAverage;
@@ -298,6 +298,9 @@ class HybridRouter {
298
298
  localBridge = null;
299
299
  cloudBridge = null;
300
300
  hardwareProfile;
301
+ // v14.4.0: Cache Ollama availability to avoid repeated network calls
302
+ ollamaAvailableCache = null;
303
+ static OLLAMA_CACHE_TTL_MS = 5000; // 5 seconds
301
304
  constructor(config) {
302
305
  // Detect hardware and auto-configure
303
306
  this.hardwareProfile = detectHardware();
@@ -445,14 +448,27 @@ class HybridRouter {
445
448
  // ==========================================================================
446
449
  // Helpers
447
450
  // ==========================================================================
451
+ /**
452
+ * Check if Ollama is available
453
+ * v14.4.0: Results cached for 5 seconds to avoid repeated network calls
454
+ */
448
455
  async isOllamaAvailable() {
456
+ const now = Date.now();
457
+ // Return cached result if fresh
458
+ if (this.ollamaAvailableCache &&
459
+ (now - this.ollamaAvailableCache.timestamp) < HybridRouter.OLLAMA_CACHE_TTL_MS) {
460
+ return this.ollamaAvailableCache.result;
461
+ }
449
462
  try {
450
463
  const response = await fetch('http://localhost:11434/api/tags', {
451
464
  signal: AbortSignal.timeout(1000),
452
465
  });
453
- return response.ok;
466
+ const result = response.ok;
467
+ this.ollamaAvailableCache = { result, timestamp: now };
468
+ return result;
454
469
  }
455
470
  catch {
471
+ this.ollamaAvailableCache = { result: false, timestamp: now };
456
472
  return false;
457
473
  }
458
474
  }
@@ -29,6 +29,8 @@ export interface VectorDocument {
29
29
  createdAt: Date;
30
30
  /** Last updated timestamp */
31
31
  updatedAt: Date;
32
+ /** v14.4.0: Last accessed timestamp for LRU eviction */
33
+ accessedAt: Date;
32
34
  /** Optional namespace/collection */
33
35
  namespace?: string;
34
36
  }
@@ -95,6 +97,7 @@ export declare class VectorStore {
95
97
  delete(id: string): boolean;
96
98
  /**
97
99
  * Get a document by ID
100
+ * v14.4.0: Updates accessedAt for LRU tracking
98
101
  */
99
102
  get(id: string): VectorDocument | undefined;
100
103
  /**
@@ -119,8 +122,9 @@ export declare class VectorStore {
119
122
  filter?: Record<string, unknown>;
120
123
  }): Promise<VectorSearchResult[]>;
121
124
  /**
122
- * Maintain size limits by removing oldest documents when over limit
125
+ * Maintain size limits using LRU eviction
123
126
  * v9.4.0: Prevents unbounded memory growth
127
+ * v14.4.0: Changed to LRU (least recently used) instead of FIFO
124
128
  */
125
129
  private maintainSize;
126
130
  /**
@@ -87,13 +87,15 @@ class VectorStore {
87
87
  async add(id, text, metadata = {}, namespace) {
88
88
  // Generate embedding
89
89
  const result = await this.embeddings.embed(text);
90
+ const now = new Date();
90
91
  const doc = {
91
92
  id,
92
93
  text,
93
94
  vector: result.vector,
94
95
  metadata,
95
- createdAt: new Date(),
96
- updatedAt: new Date(),
96
+ createdAt: now,
97
+ updatedAt: now,
98
+ accessedAt: now,
97
99
  namespace: namespace || this.config.defaultNamespace,
98
100
  };
99
101
  this.documents.set(id, doc);
@@ -113,6 +115,7 @@ class VectorStore {
113
115
  const texts = items.map(i => i.text);
114
116
  const embedResults = await this.embeddings.embedBatch(texts);
115
117
  const docs = [];
118
+ const now = new Date();
116
119
  for (let i = 0; i < items.length; i++) {
117
120
  const item = items[i];
118
121
  const doc = {
@@ -120,8 +123,9 @@ class VectorStore {
120
123
  text: item.text,
121
124
  vector: embedResults[i].vector,
122
125
  metadata: item.metadata || {},
123
- createdAt: new Date(),
124
- updatedAt: new Date(),
126
+ createdAt: now,
127
+ updatedAt: now,
128
+ accessedAt: now,
125
129
  namespace: item.namespace || this.config.defaultNamespace,
126
130
  };
127
131
  this.documents.set(item.id, doc);
@@ -148,12 +152,14 @@ class VectorStore {
148
152
  const result = await this.embeddings.embed(text);
149
153
  vector = result.vector;
150
154
  }
155
+ const now = new Date();
151
156
  const updated = {
152
157
  ...existing,
153
158
  text: text || existing.text,
154
159
  vector,
155
160
  metadata: metadata ? { ...existing.metadata, ...metadata } : existing.metadata,
156
- updatedAt: new Date(),
161
+ updatedAt: now,
162
+ accessedAt: now,
157
163
  };
158
164
  this.documents.set(id, updated);
159
165
  this.dirty = true;
@@ -178,9 +184,15 @@ class VectorStore {
178
184
  }
179
185
  /**
180
186
  * Get a document by ID
187
+ * v14.4.0: Updates accessedAt for LRU tracking
181
188
  */
182
189
  get(id) {
183
- return this.documents.get(id);
190
+ const doc = this.documents.get(id);
191
+ if (doc) {
192
+ // Update access time for LRU
193
+ doc.accessedAt = new Date();
194
+ }
195
+ return doc;
184
196
  }
185
197
  /**
186
198
  * Check if document exists
@@ -262,17 +274,18 @@ class VectorStore {
262
274
  // Size Management (v9.4.0)
263
275
  // ============================================================================
264
276
  /**
265
- * Maintain size limits by removing oldest documents when over limit
277
+ * Maintain size limits using LRU eviction
266
278
  * v9.4.0: Prevents unbounded memory growth
279
+ * v14.4.0: Changed to LRU (least recently used) instead of FIFO
267
280
  */
268
281
  maintainSize() {
269
282
  const maxDocs = this.config.maxDocuments || 50000;
270
283
  if (this.documents.size <= maxDocs)
271
284
  return;
272
- // Convert to array and sort by creation date (oldest first)
285
+ // Convert to array and sort by access date (least recently accessed first)
273
286
  const sorted = Array.from(this.documents.values())
274
- .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
275
- // Remove oldest documents until under limit
287
+ .sort((a, b) => a.accessedAt.getTime() - b.accessedAt.getTime());
288
+ // Remove least recently accessed documents until under limit
276
289
  const toRemove = sorted.slice(0, this.documents.size - maxDocs);
277
290
  for (const doc of toRemove) {
278
291
  this.documents.delete(doc.id);
@@ -348,12 +361,13 @@ class VectorStore {
348
361
  if (!this.config.persistPath)
349
362
  return;
350
363
  const data = {
351
- version: '1.0',
364
+ version: '1.1', // v14.4.0: Added accessedAt for LRU
352
365
  documents: Array.from(this.documents.entries()).map(([id, doc]) => ({
353
366
  ...doc,
354
367
  id,
355
368
  createdAt: doc.createdAt.toISOString(),
356
369
  updatedAt: doc.updatedAt.toISOString(),
370
+ accessedAt: doc.accessedAt.toISOString(),
357
371
  })),
358
372
  savedAt: new Date().toISOString(),
359
373
  };
@@ -377,6 +391,7 @@ class VectorStore {
377
391
  const data = JSON.parse(content);
378
392
  this.documents.clear();
379
393
  for (const doc of data.documents) {
394
+ const accessedAt = doc.accessedAt;
380
395
  this.documents.set(doc.id, {
381
396
  id: doc.id,
382
397
  text: doc.text,
@@ -384,6 +399,7 @@ class VectorStore {
384
399
  metadata: doc.metadata,
385
400
  createdAt: new Date(doc.createdAt),
386
401
  updatedAt: new Date(doc.updatedAt),
402
+ accessedAt: accessedAt ? new Date(accessedAt) : new Date(doc.updatedAt),
387
403
  namespace: doc.namespace,
388
404
  });
389
405
  }
@@ -77,9 +77,13 @@ class HybridStreamExecutor {
77
77
  try {
78
78
  // Get API key from environment (HybridStreamOptions doesn't include apiKey)
79
79
  const apiKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || '';
80
- // Create LLM bridge
80
+ // Create LLM bridge - validate provider is supported
81
+ const validProviders = ['ollama', 'openai', 'anthropic'];
82
+ const provider = validProviders.includes(this.options.provider)
83
+ ? this.options.provider
84
+ : 'anthropic'; // Default to anthropic if unknown provider
81
85
  const bridge = new index_js_1.LLMBridge({
82
- provider: this.options.provider,
86
+ provider,
83
87
  model: this.options.model,
84
88
  apiKey,
85
89
  temperature: this.options.temperature || 0.7,
@@ -376,44 +380,72 @@ class HybridStreamExecutor {
376
380
  // ==========================================================================
377
381
  // Event Creation
378
382
  // ==========================================================================
379
- createEvent(type, data) {
383
+ createEvent(type, data = {}) {
380
384
  const baseEvent = {
381
385
  id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
382
386
  timestamp: new Date().toISOString(),
383
387
  };
384
388
  switch (type) {
385
389
  case 'token':
386
- return { ...baseEvent, type: 'token', content: data.token };
390
+ return { ...baseEvent, type: 'token', content: String(data.token || '') };
387
391
  case 'tool_start':
388
- return { ...baseEvent, type: 'tool_start', ...data };
392
+ return {
393
+ ...baseEvent,
394
+ type: 'tool_start',
395
+ toolCallId: String(data.toolCallId || ''),
396
+ name: String(data.name || ''),
397
+ args: data.args || {}
398
+ };
389
399
  case 'tool_result':
390
- return { ...baseEvent, type: 'tool_result', ...data };
400
+ return {
401
+ ...baseEvent,
402
+ type: 'tool_result',
403
+ toolCallId: String(data.toolCallId || ''),
404
+ content: data.content,
405
+ success: Boolean(data.success),
406
+ duration: Number(data.duration || 0),
407
+ error: data.error ? String(data.error) : undefined
408
+ };
391
409
  case 'thinking_start':
392
410
  return { ...baseEvent, type: 'thinking_start' };
393
411
  case 'thinking_token':
394
- return { ...baseEvent, type: 'thinking_token', content: data.content };
412
+ return { ...baseEvent, type: 'thinking_token', content: String(data.content || '') };
395
413
  case 'thinking_end':
396
414
  return { ...baseEvent, type: 'thinking_end' };
397
415
  case 'metadata':
398
- return { ...baseEvent, type: 'metadata', provider: this.options.provider, model: this.options.model, ...data };
416
+ return {
417
+ ...baseEvent,
418
+ type: 'metadata',
419
+ provider: this.options.provider,
420
+ model: this.options.model,
421
+ usage: data.usage,
422
+ cost: data.cost
423
+ };
399
424
  case 'error':
400
425
  return {
401
426
  ...baseEvent,
402
427
  type: 'error',
403
428
  code: 'EXECUTION_ERROR',
404
- message: data.error,
405
- retryable: data.recoverable,
429
+ message: String(data.error || 'Unknown error'),
430
+ retryable: Boolean(data.recoverable),
406
431
  };
407
432
  case 'done':
408
433
  return {
409
434
  ...baseEvent,
410
435
  type: 'done',
411
- content: data.finalContent,
436
+ content: String(data.finalContent || ''),
412
437
  reason: 'stop',
413
438
  metrics: data.metrics,
414
439
  };
415
440
  default:
416
- return baseEvent;
441
+ // For unknown event types, return error event
442
+ return {
443
+ ...baseEvent,
444
+ type: 'error',
445
+ code: 'UNKNOWN_EVENT',
446
+ message: `Unknown event type: ${type}`,
447
+ retryable: false
448
+ };
417
449
  }
418
450
  }
419
451
  // ==========================================================================
@@ -519,11 +551,21 @@ async function hybridStreamCollect(options) {
519
551
  metrics = event.metrics;
520
552
  }
521
553
  }
554
+ // Get final metrics from done event if available
555
+ if (!metrics && events.length > 0) {
556
+ const lastEvent = events[events.length - 1];
557
+ if (lastEvent.type === 'done') {
558
+ metrics = lastEvent.metrics;
559
+ }
560
+ }
522
561
  return {
523
562
  content,
524
563
  events,
525
- metrics: metrics || events[events.length - 1]?.type === 'done'
526
- ? events[events.length - 1].metrics
527
- : {},
564
+ metrics: metrics || {
565
+ tokensPerSecond: 0, totalTokens: 0, inputTokens: 0, outputTokens: 0,
566
+ thinkingTokens: 0, cost: 0, confidence: 0, toolCallCount: 0,
567
+ toolLatencyTotal: 0, toolLatencyAverage: 0, modelUpgrades: 0,
568
+ startTime: new Date().toISOString(), elapsed: 0, state: 'completed', retries: 0
569
+ },
528
570
  };
529
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-ai-cli",
3
- "version": "14.3.0",
3
+ "version": "14.4.0",
4
4
  "description": "Fully Autonomous AI System with RSI (Recursive Self-Improvement) - Self-funding, Self-deploying, Production Memory, A2A Protocol & Governance",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",