@arcteninc/core 0.0.133 → 0.0.135

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcteninc/core",
3
- "version": "0.0.133",
3
+ "version": "0.0.135",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -1566,4 +1566,12 @@ if (import.meta.main) {
1566
1566
  autoDiscoverAndExtract(projectRoot, outputPath);
1567
1567
  }
1568
1568
 
1569
- export { autoDiscoverAndExtract };
1569
+ export {
1570
+ autoDiscoverAndExtract,
1571
+ findToolUsageFiles,
1572
+ extractToolNamesFromFile,
1573
+ findFunctionDefinition,
1574
+ extractFunctionMetadata,
1575
+ };
1576
+
1577
+ export type { FunctionMetadata, ToolUsage };
@@ -14,6 +14,7 @@ import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  import { glob } from 'glob';
16
16
  import * as readline from 'readline';
17
+ import { autoDiscoverAndExtract } from './cli-extract-types-auto.ts';
17
18
 
18
19
  // Types
19
20
  interface FunctionMetadata {
@@ -197,6 +198,19 @@ async function scanCodebase(): Promise<CodebaseContext> {
197
198
  // Build file tree
198
199
  const fileTree = buildFileTree(projectRoot);
199
200
 
201
+ // Debug: log context size
202
+ const contextStr = JSON.stringify({
203
+ fileTree,
204
+ framework,
205
+ dependencies,
206
+ functions,
207
+ stats: {
208
+ totalFunctions: functions.length,
209
+ skippedPaths: EXCLUDE_PATHS,
210
+ },
211
+ });
212
+ console.log(`šŸ“Š Context size: ${(contextStr.length / 1024).toFixed(2)} KB (~${Math.round(contextStr.length / 4)} tokens)`);
213
+
200
214
  return {
201
215
  fileTree,
202
216
  framework,
@@ -252,65 +266,58 @@ function getDependencies(projectRoot: string): Record<string, string> {
252
266
  return { ...packageJson.dependencies };
253
267
  }
254
268
 
255
- // Helper: Scan for functions
269
+ // Helper: Scan for functions (reuses existing autoDiscoverAndExtract)
256
270
  async function scanFunctions(projectRoot: string): Promise<FunctionMetadata[]> {
257
- const pattern = path.join(projectRoot, '**/*.{ts,tsx,js,jsx}').replace(/\\/g, '/');
258
- const files = await glob(pattern, {
259
- ignore: EXCLUDE_PATHS.map(p => `**/${p}/**`),
260
- });
271
+ console.log('šŸ” Using TypeScript AST to scan for exported functions...');
261
272
 
262
- const functions: FunctionMetadata[] = [];
273
+ // Generate metadata file using existing extraction logic
274
+ const outputPath = path.join(projectRoot, '.arcten', 'tool-metadata.ts');
263
275
 
264
- // Simple regex-based extraction (can be enhanced with AST parsing)
265
- for (const file of files) {
266
- const content = fs.readFileSync(file, 'utf-8');
267
-
268
- // Match exported functions
269
- const functionRegex = /export\s+(async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g;
270
- let match;
271
-
272
- while ((match = functionRegex.exec(content)) !== null) {
273
- const [, isAsync, name, params] = match;
274
- functions.push({
275
- name,
276
- file: path.relative(projectRoot, file),
277
- params: parseParams(params),
278
- jsDoc: extractJsDoc(content, match.index),
279
- sourceCode: extractFunctionSource(content, match.index),
280
- });
281
- }
276
+ // Ensure .arcten directory exists
277
+ const arctenDir = path.dirname(outputPath);
278
+ if (!fs.existsSync(arctenDir)) {
279
+ fs.mkdirSync(arctenDir, { recursive: true });
282
280
  }
283
281
 
284
- return functions;
285
- }
282
+ // Run the auto-discovery
283
+ await autoDiscoverAndExtract(projectRoot, outputPath);
286
284
 
287
- // Helper: Parse function parameters
288
- function parseParams(paramsStr: string): any {
289
- if (!paramsStr.trim()) return [];
285
+ // Read the generated metadata
286
+ const content = fs.readFileSync(outputPath, 'utf-8');
287
+ const metadataMatch = content.match(/export const toolMetadata = ([\s\S]+) as const;/);
290
288
 
291
- return paramsStr.split(',').map(p => {
292
- const [name, type] = p.trim().split(':');
293
- return { name: name?.trim(), type: type?.trim() };
294
- });
295
- }
289
+ if (!metadataMatch) {
290
+ return [];
291
+ }
296
292
 
297
- // Helper: Extract JSDoc comment
298
- function extractJsDoc(content: string, index: number): string | undefined {
299
- const beforeFunction = content.substring(0, index);
300
- const jsDocMatch = beforeFunction.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
301
- return jsDocMatch ? jsDocMatch[1].trim() : undefined;
302
- }
293
+ const metadata = JSON.parse(metadataMatch[1]);
294
+ const functions: FunctionMetadata[] = [];
295
+
296
+ // Convert to wizard format
297
+ for (const [name, fn] of Object.entries(metadata.functions as any)) {
298
+ // Find which file this function was discovered from
299
+ const fileIndex = metadata.discoveredFrom.findIndex((f: string) =>
300
+ content.includes(name)
301
+ );
302
+
303
+ functions.push({
304
+ name,
305
+ file: metadata.discoveredFrom[fileIndex] || metadata.discoveredFrom[0] || 'unknown',
306
+ params: Object.entries(fn.parameters?.properties || {}).map(([paramName, prop]: [string, any]) => ({
307
+ name: paramName,
308
+ type: prop.type || 'any',
309
+ })),
310
+ returnType: fn.returnType,
311
+ jsDoc: fn.description,
312
+ sourceCode: `${name}(${Object.keys(fn.parameters?.properties || {}).join(', ')})`,
313
+ });
314
+ }
303
315
 
304
- // Helper: Extract function source (first 1000 chars)
305
- function extractFunctionSource(content: string, index: number): string {
306
- const remaining = content.substring(index);
307
- const endMatch = remaining.match(/\n\}/);
308
- const endIndex = endMatch ? endMatch.index! + 2 : Math.min(1000, remaining.length);
309
- return remaining.substring(0, endIndex);
316
+ return functions;
310
317
  }
311
318
 
312
- // Helper: Build file tree
313
- function buildFileTree(projectRoot: string, depth: number = 0, maxDepth: number = 4): string {
319
+ // Helper: Build file tree (limited depth to reduce tokens)
320
+ function buildFileTree(projectRoot: string, depth: number = 0, maxDepth: number = 2): string {
314
321
  if (depth >= maxDepth) return '';
315
322
 
316
323
  const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
@@ -332,6 +339,53 @@ function buildFileTree(projectRoot: string, depth: number = 0, maxDepth: number
332
339
  return tree;
333
340
  }
334
341
 
342
+ // Step 2.5: Handle missing descriptions
343
+ async function handleMissingDescriptions(
344
+ rl: readline.Interface,
345
+ functions: FunctionMetadata[]
346
+ ): Promise<FunctionMetadata[]> {
347
+ // Find functions with generic "Execute X" descriptions
348
+ const missingDescriptions = functions.filter(fn =>
349
+ !fn.jsDoc || fn.jsDoc.startsWith('Execute ')
350
+ );
351
+
352
+ if (missingDescriptions.length === 0) {
353
+ return functions;
354
+ }
355
+
356
+ console.log(`\nāš ļø ${missingDescriptions.length} function${missingDescriptions.length > 1 ? 's are' : ' is'} missing JSDoc descriptions\n`);
357
+ console.log('How would you like to handle these?');
358
+ console.log(' 1. Edit descriptions now (one by one)');
359
+ console.log(' 2. Skip - I\'ll add JSDoc comments manually');
360
+ console.log(' 3. Skip - I\'ll edit in dashboard later');
361
+
362
+ const choice = await ask(rl, '\n? Choose option (1-3): ');
363
+
364
+ if (choice === '1') {
365
+ // Edit descriptions one by one
366
+ console.log('');
367
+ for (const fn of missingDescriptions) {
368
+ console.log(`šŸ“ ${fn.name} (${fn.file})`);
369
+ console.log(` Current: ${fn.jsDoc || `Execute ${fn.name}`}\n`);
370
+
371
+ const newDesc = await ask(rl, ' Enter description (or press Enter to skip): ');
372
+
373
+ if (newDesc.trim()) {
374
+ fn.jsDoc = newDesc.trim();
375
+ console.log(` āœ“ Updated description for ${fn.name}\n`);
376
+ } else {
377
+ console.log(` ā­ļø Skipped ${fn.name}\n`);
378
+ }
379
+ }
380
+ console.log('āœ… Description editing complete\n');
381
+ } else {
382
+ console.log('\nā­ļø Skipping description editing\n');
383
+ console.log('šŸ’” You can add JSDoc comments to your code or edit descriptions in the dashboard.');
384
+ }
385
+
386
+ return functions;
387
+ }
388
+
335
389
  // Step 3: Get user intent
336
390
  async function getUserIntent(rl: readline.Interface): Promise<string> {
337
391
  console.log('\nšŸ’­ Step 3: Define your agent\'s purpose\n');
@@ -352,6 +406,8 @@ async function getUserIntent(rl: readline.Interface): Promise<string> {
352
406
 
353
407
  // Helper: Get JWT token from API key
354
408
  async function getAuthToken(apiKey: string, serverUrl: string): Promise<string> {
409
+ console.log(`šŸ”‘ Getting auth token from ${serverUrl}/token...`);
410
+
355
411
  try {
356
412
  const response = await fetch(`${serverUrl}/token`, {
357
413
  method: 'POST',
@@ -361,13 +417,23 @@ async function getAuthToken(apiKey: string, serverUrl: string): Promise<string>
361
417
  body: JSON.stringify({ apiKey }),
362
418
  });
363
419
 
420
+ console.log(`šŸ”‘ Token response status: ${response.status}`);
421
+
364
422
  if (!response.ok) {
365
423
  const error = await response.json();
424
+ console.error(`šŸ”‘ Token error:`, error);
366
425
  throw new Error(error.error || 'Failed to authenticate');
367
426
  }
368
427
 
369
428
  const data = await response.json();
370
- return data.token;
429
+ const token = data.clientToken || data.token; // Server returns "clientToken"
430
+ console.log(`šŸ”‘ Got token: ${token ? token.substring(0, 20) + '...' : 'NO TOKEN'}`);
431
+
432
+ if (!token) {
433
+ throw new Error('No token received from server');
434
+ }
435
+
436
+ return token;
371
437
  } catch (error: any) {
372
438
  if (error.cause?.code === 'ENOTFOUND' || error.cause?.code === 'ECONNREFUSED' || error.message.includes('fetch')) {
373
439
  console.error(`\nāŒ Unable to connect to: ${serverUrl}`);
@@ -453,6 +519,8 @@ async function runAnalysis(
453
519
 
454
520
  // Get JWT token from API key
455
521
  const token = await getAuthToken(apiKey, serverUrl);
522
+ console.log(`šŸ“” Using token for analysis: ${token ? token.substring(0, 20) + '...' : 'NO TOKEN'}`);
523
+ console.log(`šŸ“” Calling: ${serverUrl}/tools/analyze`);
456
524
 
457
525
  const response = await fetch(`${serverUrl}/tools/analyze`, {
458
526
  method: 'POST',
@@ -463,15 +531,26 @@ async function runAnalysis(
463
531
  body: JSON.stringify({ context, userIntent }),
464
532
  });
465
533
 
534
+ console.log(`šŸ“” Analysis response status: ${response.status}`);
535
+
466
536
  if (!response.ok) {
467
537
  const error = await response.json();
538
+ console.error(`šŸ“” Analysis error response:`, error);
468
539
  throw new Error(error.error || 'Analysis failed');
469
540
  }
470
541
 
471
542
  const result = await response.json();
543
+ console.log(`šŸ“” Analysis result:`, JSON.stringify(result).substring(0, 200));
544
+
545
+ if (!result || !result.success) {
546
+ throw new Error(result?.error || 'Analysis failed - invalid response');
547
+ }
548
+
549
+ const tokensUsed = result.tokensUsed || 0;
550
+ const actualCost = result.stats?.actualCost || 0;
472
551
 
473
- console.log(`āœ“ Analysis complete - ${result.tokensUsed.toLocaleString()} tokens used`);
474
- console.log(` Actual cost: $${result.stats.actualCost.toFixed(2)}`);
552
+ console.log(`āœ“ Analysis complete - ${tokensUsed.toLocaleString()} tokens used`);
553
+ console.log(` Actual cost: $${actualCost.toFixed(2)}`);
475
554
 
476
555
  return result;
477
556
  }
@@ -479,9 +558,10 @@ async function runAnalysis(
479
558
  // Step 7: Sync to dashboard
480
559
  async function syncToDashboard(
481
560
  apiKey: string,
482
- convexUrl: string,
561
+ serverUrl: string,
483
562
  projectId: string,
484
- tools: ToolRecommendation[]
563
+ tools: ToolRecommendation[],
564
+ agents?: AgentConfig[]
485
565
  ): Promise<void> {
486
566
  console.log('\nā˜ļø Step 7: Syncing to dashboard...\n');
487
567
 
@@ -498,41 +578,58 @@ async function syncToDashboard(
498
578
  sensitiveParams: tool.sensitiveParams || [],
499
579
  }));
500
580
 
581
+ // Convert agents to the format expected by bulkUpsert
582
+ const agentsData = agents?.map(agent => ({
583
+ name: agent.name,
584
+ systemPrompt: agent.systemPrompt,
585
+ enabledTools: agent.enabledTools,
586
+ description: agent.description || `Agent: ${agent.name}`,
587
+ })) || [];
588
+
501
589
  try {
502
- // Call Convex bulkUpsert mutation via HTTP API
503
- const response = await fetch(`${convexUrl}/api/mutation`, {
590
+ // Get JWT token
591
+ const token = await getAuthToken(apiKey, serverUrl);
592
+
593
+ // Call server endpoint which will handle Convex mutation
594
+ const response = await fetch(`${serverUrl}/tools/sync`, {
504
595
  method: 'POST',
505
596
  headers: {
506
597
  'Content-Type': 'application/json',
507
- 'Authorization': `Bearer ${apiKey}`,
598
+ 'Authorization': `Bearer ${token}`,
508
599
  },
509
600
  body: JSON.stringify({
510
- path: 'tools:bulkUpsert',
511
- args: { projectId, tools: toolsData },
512
- format: 'json',
601
+ projectId,
602
+ tools: toolsData,
603
+ agents: agentsData,
513
604
  }),
514
605
  });
515
606
 
516
607
  if (!response.ok) {
517
608
  const errorText = await response.text().catch(() => 'Unknown error');
518
- throw new Error(`Failed to sync tools: ${response.status} ${errorText}`);
609
+ throw new Error(`Failed to sync: ${response.status} ${errorText}`);
519
610
  }
520
611
 
521
612
  const result = await response.json();
522
613
 
523
614
  if (!result.success) {
524
- throw new Error(result.error || 'Failed to sync tools to dashboard');
615
+ throw new Error(result.error || 'Failed to sync to dashboard');
525
616
  }
526
617
 
527
- console.log(`āœ… Synced ${tools.length} tools to dashboard`);
618
+ console.log(`āœ… Synced ${tools.length} tools${agents && agents.length > 0 ? ` and ${agents.length} agents` : ''} to dashboard`);
528
619
 
529
620
  if (result.upserted) {
530
621
  const created = result.upserted.filter((t: any) => t.created).length;
531
622
  const updated = result.upserted.filter((t: any) => !t.created).length;
532
- console.log(` šŸ“ Created: ${created} | Updated: ${updated}`);
623
+ console.log(` šŸ“ Tools - Created: ${created} | Updated: ${updated}`);
624
+ }
625
+
626
+ if (result.agents && agents && agents.length > 0) {
627
+ const agentCreated = result.agents.filter((a: any) => a.created).length;
628
+ const agentUpdated = result.agents.filter((a: any) => !a.created).length;
629
+ console.log(` šŸ“ Agents - Created: ${agentCreated} | Updated: ${agentUpdated}`);
533
630
  }
534
631
  } catch (error: any) {
535
- console.error(`āŒ Failed to sync tools: ${error.message}`);
632
+ console.error(`āŒ Failed to sync: ${error.message}`);
536
633
  throw error;
537
634
  }
538
635
  }
@@ -699,7 +796,6 @@ async function main() {
699
796
 
700
797
  const rl = createPrompt();
701
798
  const serverUrl = process.env.ARCTEN_SERVER_URL || 'https://api.arcten.com';
702
- const convexUrl = process.env.CONVEX_URL || 'https://convex.arcten.com';
703
799
 
704
800
  try {
705
801
  // Check for existing setup
@@ -731,6 +827,11 @@ async function main() {
731
827
  // Step 2: Scan codebase
732
828
  const context = await scanCodebase();
733
829
 
830
+ // Step 2.5: Handle missing descriptions (only in full wizard mode, not sync/allow-all)
831
+ if (!skipAnalysis && !allowAll) {
832
+ context.functions = await handleMissingDescriptions(rl, context.functions);
833
+ }
834
+
734
835
  let analysis: AnalysisResult | null = null;
735
836
 
736
837
  if (allowAll) {
@@ -749,7 +850,7 @@ async function main() {
749
850
  sensitiveParams: [],
750
851
  }));
751
852
 
752
- await syncToDashboard(apiKey, convexUrl, projectId, allTools);
853
+ await syncToDashboard(apiKey, serverUrl, projectId, allTools);
753
854
  await generateLocalConfig(projectId, 'default');
754
855
 
755
856
  console.log('\nāœ… Setup complete!\n');
@@ -770,7 +871,7 @@ async function main() {
770
871
  sensitiveParams: [],
771
872
  }));
772
873
 
773
- await syncToDashboard(apiKey, convexUrl, projectId, basicTools);
874
+ await syncToDashboard(apiKey, serverUrl, projectId, basicTools);
774
875
  await generateLocalConfig(projectId, 'default');
775
876
 
776
877
  console.log('\nāœ… Sync complete!\n');
@@ -798,7 +899,7 @@ async function main() {
798
899
  console.log(` Placements suggested: ${analysis.suggestedPlacements.length}`);
799
900
 
800
901
  // Step 7: Sync to dashboard
801
- await syncToDashboard(apiKey, convexUrl, projectId, analysis.recommendations);
902
+ await syncToDashboard(apiKey, serverUrl, projectId, analysis.recommendations, analysis.suggestedAgents);
802
903
 
803
904
  // Step 7.5: Display placement suggestions
804
905
  await displayPlacementSuggestions(analysis.suggestedPlacements);
@@ -816,7 +917,7 @@ async function main() {
816
917
  sensitiveParams: [],
817
918
  }));
818
919
 
819
- await syncToDashboard(apiKey, convexUrl, projectId, basicTools);
920
+ await syncToDashboard(apiKey, serverUrl, projectId, basicTools);
820
921
  await generateLocalConfig(projectId, 'default');
821
922
  }
822
923