@arcteninc/core 0.0.21 → 0.0.23

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.
Files changed (62) hide show
  1. package/dist/components/ArctenAgent.d.ts +25 -0
  2. package/dist/components/ArctenAgent.d.ts.map +1 -1
  3. package/dist/components/ai-elements/prompt-input.d.ts +187 -0
  4. package/dist/components/ai-elements/prompt-input.d.ts.map +1 -0
  5. package/dist/components/ai-elements/reasoning.d.ts +17 -0
  6. package/dist/components/ai-elements/reasoning.d.ts.map +1 -0
  7. package/dist/components/ai-elements/response.d.ts +8 -0
  8. package/dist/components/ai-elements/response.d.ts.map +1 -0
  9. package/dist/components/ai-elements/shimmer.d.ts +10 -0
  10. package/dist/components/ai-elements/shimmer.d.ts.map +1 -0
  11. package/dist/components/tool-call-approval.d.ts +9 -0
  12. package/dist/components/tool-call-approval.d.ts.map +1 -0
  13. package/dist/components/tool-call-result.d.ts +8 -0
  14. package/dist/components/tool-call-result.d.ts.map +1 -0
  15. package/dist/components/ui/autotextarea.d.ts +19 -0
  16. package/dist/components/ui/autotextarea.d.ts.map +1 -0
  17. package/dist/components/ui/badge.d.ts +10 -0
  18. package/dist/components/ui/badge.d.ts.map +1 -0
  19. package/dist/components/ui/button.d.ts +14 -0
  20. package/dist/components/ui/button.d.ts.map +1 -0
  21. package/dist/components/ui/collapsible.d.ts +6 -0
  22. package/dist/components/ui/collapsible.d.ts.map +1 -0
  23. package/dist/components/ui/command.d.ts +19 -0
  24. package/dist/components/ui/command.d.ts.map +1 -0
  25. package/dist/components/ui/dialog.d.ts +16 -0
  26. package/dist/components/ui/dialog.d.ts.map +1 -0
  27. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  28. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  29. package/dist/components/ui/hover-card.d.ts +7 -0
  30. package/dist/components/ui/hover-card.d.ts.map +1 -0
  31. package/dist/components/ui/input-group.d.ts +17 -0
  32. package/dist/components/ui/input-group.d.ts.map +1 -0
  33. package/dist/components/ui/input.d.ts +4 -0
  34. package/dist/components/ui/input.d.ts.map +1 -0
  35. package/dist/components/ui/kbd.d.ts +4 -0
  36. package/dist/components/ui/kbd.d.ts.map +1 -0
  37. package/dist/components/ui/select.d.ts +16 -0
  38. package/dist/components/ui/select.d.ts.map +1 -0
  39. package/dist/components/ui/textarea.d.ts +4 -0
  40. package/dist/components/ui/textarea.d.ts.map +1 -0
  41. package/dist/components/ui/tooltip.d.ts +8 -0
  42. package/dist/components/ui/tooltip.d.ts.map +1 -0
  43. package/dist/index.cjs +20 -7
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.mjs +2361 -2537
  47. package/dist/lib/useAgent.d.ts +3 -0
  48. package/dist/lib/useAgent.d.ts.map +1 -0
  49. package/dist/lib/utils.d.ts +3 -0
  50. package/dist/lib/utils.d.ts.map +1 -0
  51. package/dist/server.d.ts +9 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/types/tool-description.d.ts +18 -0
  54. package/dist/types/tool-description.d.ts.map +1 -0
  55. package/dist/types/use-agent.d.ts +73 -0
  56. package/dist/types/use-agent.d.ts.map +1 -0
  57. package/dist/utils/extract-tool-metadata.d.ts +26 -0
  58. package/dist/utils/extract-tool-metadata.d.ts.map +1 -0
  59. package/dist/verifyToken.d.ts +19 -0
  60. package/dist/verifyToken.d.ts.map +1 -0
  61. package/package.json +7 -4
  62. package/scripts/cli-extract-types-auto.ts +718 -541
@@ -1,542 +1,719 @@
1
1
  #!/usr/bin/env bun
2
- /**
3
- * Automatically discover and extract types for tools used in ArctenAgent/useAgent
4
- * Scans the project, finds all tool usages, and generates metadata only for used tools
5
- */
6
-
7
- import * as ts from 'typescript';
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
- import { glob } from 'glob';
11
-
12
- interface TypeMetadata {
13
- type: string;
14
- description?: string;
15
- required: boolean;
16
- default?: string;
17
- values?: string[];
18
- arrayItemType?: TypeMetadata;
19
- properties?: Record<string, TypeMetadata>;
20
- }
21
-
22
- interface FunctionMetadata {
23
- name: string;
24
- description: string;
25
- parameters: Record<string, TypeMetadata>;
26
- returnType?: string;
27
- isAsync?: boolean;
28
- }
29
-
30
- interface GeneratedMetadata {
31
- generated: string;
32
- discoveredFrom: string[];
33
- functions: Record<string, FunctionMetadata>;
34
- }
35
-
36
- interface ToolUsage {
37
- toolNames: Set<string>;
38
- file: string;
39
- component: 'ArctenAgent' | 'useAgent';
40
- }
41
-
42
- /**
43
- * Find all files that use ArctenAgent or useAgent
44
- */
45
- async function findToolUsageFiles(projectRoot: string): Promise<string[]> {
46
- const pattern = path.join(projectRoot, '**/*.{ts,tsx}').replace(/\\/g, '/');
47
- const files = await glob(pattern, {
48
- ignore: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'],
49
- });
50
-
51
- console.log(`šŸ“‚ Found ${files.length} TypeScript files to scan`);
52
- return files;
53
- }
54
-
55
- /**
56
- * Extract tool names from ArctenAgent or useAgent usage
57
- */
58
- function extractToolNamesFromFile(sourceFile: ts.SourceFile): ToolUsage[] {
59
- const usages: ToolUsage[] = [];
60
-
61
- function visit(node: ts.Node) {
62
- // Check for <ArctenAgent tools={[...]} safeTools={[...]} />
63
- if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
64
- const tagName = ts.isJsxElement(node)
65
- ? node.openingElement.tagName.getText(sourceFile)
66
- : node.tagName.getText(sourceFile);
67
-
68
- if (tagName === 'ArctenAgent') {
69
- const toolNames = new Set<string>();
70
- const attributes = ts.isJsxElement(node)
71
- ? node.openingElement.attributes.properties
72
- : node.attributes.properties;
73
-
74
- for (const attr of attributes) {
75
- if (ts.isJsxAttribute(attr)) {
76
- const attrName = attr.name.getText(sourceFile);
77
- if (attrName === 'tools' || attrName === 'safeTools') {
78
- if (attr.initializer && ts.isJsxExpression(attr.initializer)) {
79
- const expr = attr.initializer.expression;
80
- if (expr && ts.isArrayLiteralExpression(expr)) {
81
- for (const element of expr.elements) {
82
- const name = element.getText(sourceFile).trim();
83
- // Remove any object property access (e.g., tools.getOrders -> getOrders)
84
- const simpleName = name.split('.').pop() || name;
85
- toolNames.add(simpleName);
86
- }
87
- }
88
- }
89
- }
90
- }
91
- }
92
-
93
- if (toolNames.size > 0) {
94
- usages.push({
95
- toolNames,
96
- file: sourceFile.fileName,
97
- component: 'ArctenAgent',
98
- });
99
- }
100
- }
101
- }
102
-
103
- // Check for useAgent({ tools: [...], safeTools: [...] })
104
- if (ts.isCallExpression(node)) {
105
- const expr = node.expression;
106
- if (ts.isIdentifier(expr) && expr.getText(sourceFile) === 'useAgent') {
107
- const toolNames = new Set<string>();
108
-
109
- if (node.arguments.length > 0) {
110
- const arg = node.arguments[0];
111
- if (ts.isObjectLiteralExpression(arg)) {
112
- for (const prop of arg.properties) {
113
- if (ts.isPropertyAssignment(prop)) {
114
- const propName = prop.name.getText(sourceFile);
115
- if (propName === 'tools' || propName === 'safeTools') {
116
- if (ts.isArrayLiteralExpression(prop.initializer)) {
117
- for (const element of prop.initializer.elements) {
118
- const name = element.getText(sourceFile).trim();
119
- const simpleName = name.split('.').pop() || name;
120
- toolNames.add(simpleName);
121
- }
122
- }
123
- }
124
- }
125
- }
126
- }
127
- }
128
-
129
- if (toolNames.size > 0) {
130
- usages.push({
131
- toolNames,
132
- file: sourceFile.fileName,
133
- component: 'useAgent',
134
- });
135
- }
136
- }
137
- }
138
-
139
- ts.forEachChild(node, visit);
140
- }
141
-
142
- visit(sourceFile);
143
- return usages;
144
- }
145
-
146
- /**
147
- * Find where a tool function is defined
148
- */
149
- function findFunctionDefinition(
150
- functionName: string,
151
- sourceFile: ts.SourceFile,
152
- program: ts.Program
153
- ): { sourceFile: ts.SourceFile; node: ts.FunctionDeclaration } | null {
154
- // First, check if it's defined in the current file
155
- let foundNode: ts.FunctionDeclaration | null = null;
156
-
157
- function visitForDefinition(node: ts.Node) {
158
- if (ts.isFunctionDeclaration(node) && node.name) {
159
- if (node.name.getText(sourceFile) === functionName) {
160
- foundNode = node;
161
- }
162
- }
163
- if (!foundNode) {
164
- ts.forEachChild(node, visitForDefinition);
165
- }
166
- }
167
-
168
- visitForDefinition(sourceFile);
169
-
170
- if (foundNode) {
171
- return { sourceFile, node: foundNode };
172
- }
173
-
174
- // If not found, check imports
175
- const imports: { name: string; from: string }[] = [];
176
-
177
- function visitForImports(node: ts.Node) {
178
- if (ts.isImportDeclaration(node)) {
179
- const moduleSpecifier = node.moduleClause;
180
- if (moduleSpecifier && ts.isImportClause(moduleSpecifier)) {
181
- const namedBindings = moduleSpecifier.namedBindings;
182
- if (namedBindings && ts.isNamedImports(namedBindings)) {
183
- for (const element of namedBindings.elements) {
184
- const importedName = element.name.getText(sourceFile);
185
- if (importedName === functionName) {
186
- const modulePath = (node.moduleSpecifier as ts.StringLiteral).text;
187
- imports.push({ name: importedName, from: modulePath });
188
- }
189
- }
190
- }
191
- }
192
- }
193
- ts.forEachChild(node, visitForImports);
194
- }
195
-
196
- visitForImports(sourceFile);
197
-
198
- // Follow imports to find definition
199
- for (const imp of imports) {
200
- const resolvedPath = resolveImportPath(imp.from, sourceFile.fileName);
201
- if (resolvedPath) {
202
- const importedSourceFile = program.getSourceFile(resolvedPath);
203
- if (importedSourceFile) {
204
- const result = findFunctionDefinition(functionName, importedSourceFile, program);
205
- if (result) {
206
- return result;
207
- }
208
- }
209
- }
210
- }
211
-
212
- return null;
213
- }
214
-
215
- /**
216
- * Resolve import path to actual file path
217
- */
218
- function resolveImportPath(importPath: string, fromFile: string): string | null {
219
- if (importPath.startsWith('.')) {
220
- // Relative import
221
- const dir = path.dirname(fromFile);
222
- const resolved = path.resolve(dir, importPath);
223
-
224
- // Try common extensions
225
- const extensions = ['.ts', '.tsx', '.js', '.jsx'];
226
- for (const ext of extensions) {
227
- const withExt = resolved + ext;
228
- if (fs.existsSync(withExt)) {
229
- return withExt;
230
- }
231
- }
232
-
233
- // Try index files
234
- for (const ext of extensions) {
235
- const indexPath = path.join(resolved, `index${ext}`);
236
- if (fs.existsSync(indexPath)) {
237
- return indexPath;
238
- }
239
- }
240
-
241
- return resolved;
242
- }
243
-
244
- // Node modules - we'll skip for now
245
- return null;
246
- }
247
-
248
- /**
249
- * Serialize a TypeScript type (same as previous implementation)
250
- */
251
- function serializeType(
252
- type: ts.Type,
253
- checker: ts.TypeChecker,
254
- visited = new Set<number>(),
255
- depth = 0
256
- ): TypeMetadata {
257
- const typeString = checker.typeToString(type);
258
-
259
- if (depth > 10) {
260
- return { type: 'any', required: true };
261
- }
262
-
263
- const typeId = (type as any).id;
264
- if (typeId !== undefined && visited.has(typeId)) {
265
- return { type: 'any', required: true };
266
- }
267
-
268
- if (type.flags & ts.TypeFlags.String) {
269
- return { type: 'string', required: true };
270
- }
271
- if (type.flags & ts.TypeFlags.Number) {
272
- return { type: 'number', required: true };
273
- }
274
- if (type.flags & ts.TypeFlags.Boolean) {
275
- return { type: 'boolean', required: true };
276
- }
277
- if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
278
- return { type: 'undefined', required: false };
279
- }
280
-
281
- if (type.isUnion()) {
282
- const types = type.types;
283
- const stringLiterals = types
284
- .filter(t => t.flags & ts.TypeFlags.StringLiteral)
285
- .map(t => (t as ts.StringLiteralType).value);
286
-
287
- if (stringLiterals.length === types.length) {
288
- return { type: 'enum', values: stringLiterals, required: true };
289
- }
290
-
291
- const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
292
- const nonUndefinedTypes = types.filter(t => !(t.flags & ts.TypeFlags.Undefined));
293
-
294
- if (hasUndefined && nonUndefinedTypes.length === 1) {
295
- const metadata = serializeType(nonUndefinedTypes[0], checker, visited, depth + 1);
296
- metadata.required = false;
297
- return metadata;
298
- }
299
-
300
- return {
301
- type: types.map(t => checker.typeToString(t)).join(' | '),
302
- required: true,
303
- };
304
- }
305
-
306
- if (checker.isArrayType(type)) {
307
- const typeArgs = (type as ts.TypeReference).typeArguments;
308
- if (typeArgs && typeArgs.length > 0) {
309
- return {
310
- type: 'array',
311
- arrayItemType: serializeType(typeArgs[0], checker, visited, depth + 1),
312
- required: true,
313
- };
314
- }
315
- return { type: 'array', required: true };
316
- }
317
-
318
- if (type.flags & ts.TypeFlags.Object) {
319
- if (typeId !== undefined) {
320
- visited.add(typeId);
321
- }
322
-
323
- const properties: Record<string, TypeMetadata> = {};
324
- const props = checker.getPropertiesOfType(type);
325
-
326
- if (props.length > 0) {
327
- for (const prop of props) {
328
- const propDeclaration = prop.valueDeclaration || prop.declarations?.[0];
329
-
330
- if (propDeclaration) {
331
- const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
332
- const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
333
-
334
- const propMetadata = serializeType(propType, checker, visited, depth + 1);
335
- propMetadata.required = !isOptional;
336
-
337
- properties[prop.name] = propMetadata;
338
- }
339
- }
340
-
341
- return { type: 'object', properties, required: true };
342
- }
343
- }
344
-
345
- return {
346
- type: typeString.length < 100 ? typeString : 'any',
347
- required: true,
348
- };
349
- }
350
-
351
- /**
352
- * Extract metadata from a function
353
- */
354
- function extractFunctionMetadata(
355
- node: ts.FunctionDeclaration,
356
- checker: ts.TypeChecker,
357
- sourceFile: ts.SourceFile
358
- ): FunctionMetadata | null {
359
- if (!node.name) {
360
- return null;
361
- }
362
-
363
- const functionName = node.name.getText(sourceFile);
364
-
365
- const jsDocTags = ts.getJSDocTags(node);
366
- const jsDocComments = ts.getJSDocCommentsAndTags(node);
367
-
368
- let description = `Execute ${functionName}`;
369
- for (const comment of jsDocComments) {
370
- if (ts.isJSDoc(comment) && comment.comment) {
371
- if (typeof comment.comment === 'string') {
372
- description = comment.comment.trim();
373
- }
374
- break;
375
- }
376
- }
377
-
378
- const paramDescriptions = new Map<string, string>();
379
- for (const tag of jsDocTags) {
380
- if (tag.tagName.text === 'param' && ts.isJSDocParameterTag(tag)) {
381
- const paramName = tag.name?.getText(sourceFile);
382
- const paramDesc = typeof tag.comment === 'string' ? tag.comment.trim() : '';
383
- if (paramName && paramDesc) {
384
- paramDescriptions.set(paramName, paramDesc);
385
- }
386
- }
387
- }
388
-
389
- const parameters: Record<string, TypeMetadata> = {};
390
- const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
391
-
392
- for (const param of node.parameters) {
393
- const paramName = param.name.getText(sourceFile);
394
- const hasDefault = param.initializer !== undefined;
395
-
396
- let typeMetadata: TypeMetadata;
397
- if (param.type) {
398
- const type = checker.getTypeFromTypeNode(param.type);
399
- typeMetadata = serializeType(type, checker);
400
- } else {
401
- typeMetadata = { type: 'any', required: true };
402
- }
403
-
404
- const isOptional = param.questionToken !== undefined || hasDefault;
405
- if (isOptional) {
406
- typeMetadata.required = false;
407
- }
408
-
409
- if (hasDefault) {
410
- typeMetadata.default = param.initializer!.getText(sourceFile);
411
- }
412
-
413
- if (paramDescriptions.has(paramName)) {
414
- typeMetadata.description = paramDescriptions.get(paramName);
415
- }
416
-
417
- parameters[paramName] = typeMetadata;
418
- }
419
-
420
- let returnType = 'any';
421
- if (node.type) {
422
- returnType = node.type.getText(sourceFile);
423
- }
424
-
425
- return {
426
- name: functionName,
427
- description,
428
- parameters,
429
- returnType,
430
- isAsync,
431
- };
432
- }
433
-
434
- /**
435
- * Main function
436
- */
437
- async function autoDiscoverAndExtract(projectRoot: string, outputPath: string) {
438
- console.log(`\nšŸ” Auto-discovering tools in: ${projectRoot}\n`);
439
-
440
- // Find all TypeScript files
441
- const files = await findToolUsageFiles(projectRoot);
442
-
443
- // Read tsconfig
444
- const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, 'tsconfig.json');
445
- let compilerOptions: ts.CompilerOptions = {
446
- target: ts.ScriptTarget.ESNext,
447
- module: ts.ModuleKind.ESNext,
448
- jsx: ts.JsxEmit.React,
449
- };
450
-
451
- if (configPath) {
452
- const config = ts.readConfigFile(configPath, ts.sys.readFile);
453
- const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
454
- compilerOptions = parsed.options;
455
- }
456
-
457
- // Create program with all files
458
- const program = ts.createProgram(files, compilerOptions);
459
- const checker = program.getTypeChecker();
460
-
461
- // Scan for tool usages
462
- const allToolUsages: ToolUsage[] = [];
463
- const allToolNames = new Set<string>();
464
-
465
- for (const file of files) {
466
- const sourceFile = program.getSourceFile(file);
467
- if (sourceFile) {
468
- const usages = extractToolNamesFromFile(sourceFile);
469
- if (usages.length > 0) {
470
- allToolUsages.push(...usages);
471
- usages.forEach(u => u.toolNames.forEach(name => allToolNames.add(name)));
472
- }
473
- }
474
- }
475
-
476
- console.log(`āœ“ Found ${allToolUsages.length} usage site(s) with ${allToolNames.size} unique tool(s)`);
477
- allToolUsages.forEach(usage => {
478
- console.log(` - ${usage.component} in ${path.relative(projectRoot, usage.file)}`);
479
- console.log(` Tools: ${Array.from(usage.toolNames).join(', ')}`);
480
- });
481
-
482
- // Extract metadata for discovered tools
483
- console.log(`\nšŸ“ Extracting types for discovered tools...\n`);
484
-
485
- const functionsMap: Record<string, FunctionMetadata> = {};
486
- const discoveredFrom: string[] = [];
487
-
488
- for (const toolName of allToolNames) {
489
- let found = false;
490
-
491
- // Search in all files for the definition
492
- for (const file of files) {
493
- const sourceFile = program.getSourceFile(file);
494
- if (sourceFile) {
495
- const result = findFunctionDefinition(toolName, sourceFile, program);
496
- if (result) {
497
- const metadata = extractFunctionMetadata(result.node, checker, result.sourceFile);
498
- if (metadata) {
499
- functionsMap[toolName] = metadata;
500
- discoveredFrom.push(path.relative(projectRoot, result.sourceFile.fileName));
501
- console.log(` āœ“ ${toolName} (from ${path.relative(projectRoot, result.sourceFile.fileName)})`);
502
- found = true;
503
- break;
504
- }
505
- }
506
- }
507
- }
508
-
509
- if (!found) {
510
- console.log(` ⚠ ${toolName} (definition not found)`);
511
- }
512
- }
513
-
514
- // Generate output
515
- const output: GeneratedMetadata = {
516
- generated: new Date().toISOString(),
517
- discoveredFrom: Array.from(new Set(discoveredFrom)),
518
- functions: functionsMap,
519
- };
520
-
521
- const outputDir = path.dirname(outputPath);
522
- if (!fs.existsSync(outputDir)) {
523
- fs.mkdirSync(outputDir, { recursive: true });
524
- }
525
-
526
- fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
527
- console.log(`\nāœ… Generated metadata for ${Object.keys(functionsMap).length} tool(s)`);
528
- console.log(`šŸ“ Output: ${outputPath}\n`);
529
- }
530
-
531
- // CLI
532
- if (import.meta.main) {
533
- const args = process.argv.slice(2);
534
- const projectRoot = args[0] ? path.resolve(args[0]) : process.cwd();
535
- const outputPath = args[1]
536
- ? path.resolve(args[1])
537
- : path.join(projectRoot, '.arcten', 'tool-types.json');
538
-
539
- autoDiscoverAndExtract(projectRoot, outputPath);
540
- }
541
-
542
- export { autoDiscoverAndExtract };
2
+ /**
3
+ * Automatically discover and extract types for tools used in ArctenAgent/useAgent
4
+ * Scans the project, finds all tool usages, and generates metadata only for used tools
5
+ */
6
+
7
+ import * as ts from 'typescript';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { glob } from 'glob';
11
+
12
+ // JSON Schema compatible format
13
+ interface JsonSchemaProperty {
14
+ type: string;
15
+ description?: string;
16
+ default?: string;
17
+ enum?: string[]; // For enum types
18
+ items?: JsonSchemaProperty; // For array types
19
+ properties?: Record<string, JsonSchemaProperty>; // For object types
20
+ required?: string[]; // For nested objects
21
+ }
22
+
23
+ interface FunctionMetadata {
24
+ name: string;
25
+ description: string;
26
+ parameters: {
27
+ type: 'object';
28
+ properties: Record<string, JsonSchemaProperty>;
29
+ required?: string[]; // Array of required parameter names
30
+ };
31
+ returnType?: string;
32
+ isAsync?: boolean;
33
+ }
34
+
35
+ interface GeneratedMetadata {
36
+ generated: string;
37
+ discoveredFrom: string[];
38
+ functions: Record<string, FunctionMetadata>;
39
+ }
40
+
41
+ interface ToolUsage {
42
+ toolNames: Set<string>;
43
+ file: string;
44
+ component: 'ArctenAgent' | 'useAgent';
45
+ }
46
+
47
+ /**
48
+ * Find all files that use ArctenAgent or useAgent
49
+ */
50
+ async function findToolUsageFiles(projectRoot: string): Promise<string[]> {
51
+ const pattern = path.join(projectRoot, '**/*.{ts,tsx}').replace(/\\/g, '/');
52
+ const files = await glob(pattern, {
53
+ ignore: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'],
54
+ });
55
+
56
+ console.log(`šŸ“‚ Found ${files.length} TypeScript files to scan`);
57
+ return files;
58
+ }
59
+
60
+ /**
61
+ * Extract tool names from an expression (handles arrays, variables, useMemo, etc.)
62
+ */
63
+ function extractToolNamesFromExpression(
64
+ expr: ts.Expression,
65
+ sourceFile: ts.SourceFile,
66
+ variableMap: Map<string, ts.Expression>
67
+ ): Set<string> {
68
+ const toolNames = new Set<string>();
69
+ const visited = new Set<ts.Node>();
70
+
71
+ function extractFromExpr(node: ts.Node): void {
72
+ if (visited.has(node)) return;
73
+ visited.add(node);
74
+
75
+ // Direct array literal: [tool1, tool2]
76
+ if (ts.isArrayLiteralExpression(node)) {
77
+ for (const element of node.elements) {
78
+ if (ts.isSpreadElement(element)) {
79
+ // Handle spread: [...someArray]
80
+ extractFromExpr(element.expression);
81
+ } else {
82
+ const name = element.getText(sourceFile).trim();
83
+ // Remove any object property access (e.g., tools.getOrders -> getOrders)
84
+ const simpleName = name.split('.').pop() || name;
85
+ if (simpleName && simpleName !== 'undefined' && simpleName !== 'null') {
86
+ toolNames.add(simpleName);
87
+ }
88
+ }
89
+ }
90
+ return;
91
+ }
92
+
93
+ // Variable reference: toolsList
94
+ if (ts.isIdentifier(node)) {
95
+ const varName = node.getText(sourceFile);
96
+ const varExpr = variableMap.get(varName);
97
+ if (varExpr) {
98
+ extractFromExpr(varExpr);
99
+ }
100
+ return;
101
+ }
102
+
103
+ // Property access: wrappedTools.getRAGInfo
104
+ if (ts.isPropertyAccessExpression(node)) {
105
+ const propName = node.name.getText(sourceFile);
106
+ toolNames.add(propName);
107
+ return;
108
+ }
109
+
110
+ // useMemo(() => [...], [])
111
+ if (ts.isCallExpression(node)) {
112
+ const callExpr = node.expression;
113
+ if (ts.isIdentifier(callExpr)) {
114
+ const funcName = callExpr.getText(sourceFile);
115
+ if (funcName === 'useMemo' && node.arguments.length > 0) {
116
+ const firstArg = node.arguments[0];
117
+ // Arrow function: () => [...]
118
+ if (ts.isArrowFunction(firstArg) && firstArg.body) {
119
+ if (ts.isBlock(firstArg.body)) {
120
+ // Block body: () => { return [...] }
121
+ for (const stmt of firstArg.body.statements) {
122
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
123
+ extractFromExpr(stmt.expression);
124
+ }
125
+ }
126
+ } else {
127
+ // Expression body: () => [...]
128
+ extractFromExpr(firstArg.body);
129
+ }
130
+ }
131
+ // Regular function call: useMemo(fn, [])
132
+ else if (ts.isFunctionExpression(firstArg) || ts.isArrowFunction(firstArg)) {
133
+ // Can't easily extract from function body without more analysis
134
+ }
135
+ }
136
+ }
137
+ return;
138
+ }
139
+
140
+ // Parenthesized: (toolsList)
141
+ if (ts.isParenthesizedExpression(node)) {
142
+ extractFromExpr(node.expression);
143
+ return;
144
+ }
145
+ }
146
+
147
+ extractFromExpr(expr);
148
+ return toolNames;
149
+ }
150
+
151
+ /**
152
+ * Build a map of variable declarations in the file
153
+ */
154
+ function buildVariableMap(sourceFile: ts.SourceFile): Map<string, ts.Expression> {
155
+ const variableMap = new Map<string, ts.Expression>();
156
+
157
+ function visit(node: ts.Node) {
158
+ // const toolsList = [...] or const { tools } = something
159
+ if (ts.isVariableStatement(node)) {
160
+ for (const declaration of node.declarationList.declarations) {
161
+ if (ts.isIdentifier(declaration.name)) {
162
+ const varName = declaration.name.getText(sourceFile);
163
+ if (declaration.initializer) {
164
+ variableMap.set(varName, declaration.initializer);
165
+ }
166
+ }
167
+ // Handle destructuring: const { tools } = something
168
+ else if (ts.isObjectBindingPattern(declaration.name)) {
169
+ // For now, skip destructuring - can be enhanced later
170
+ }
171
+ }
172
+ }
173
+
174
+ ts.forEachChild(node, visit);
175
+ }
176
+
177
+ visit(sourceFile);
178
+ return variableMap;
179
+ }
180
+
181
+ /**
182
+ * Extract tool names from ArctenAgent or useAgent usage
183
+ */
184
+ function extractToolNamesFromFile(
185
+ sourceFile: ts.SourceFile,
186
+ program?: ts.Program
187
+ ): ToolUsage[] {
188
+ const usages: ToolUsage[] = [];
189
+ const variableMap = buildVariableMap(sourceFile);
190
+
191
+ function visit(node: ts.Node) {
192
+ // Check for <ArctenAgent tools={[...]} safeTools={[...]} />
193
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
194
+ const tagName = ts.isJsxElement(node)
195
+ ? node.openingElement.tagName.getText(sourceFile)
196
+ : node.tagName.getText(sourceFile);
197
+
198
+ if (tagName === 'ArctenAgent') {
199
+ const toolNames = new Set<string>();
200
+ const attributes = ts.isJsxElement(node)
201
+ ? node.openingElement.attributes.properties
202
+ : node.attributes.properties;
203
+
204
+ for (const attr of attributes) {
205
+ if (ts.isJsxAttribute(attr)) {
206
+ const attrName = attr.name.getText(sourceFile);
207
+ if (attrName === 'tools' || attrName === 'safeTools') {
208
+ if (attr.initializer && ts.isJsxExpression(attr.initializer)) {
209
+ const expr = attr.initializer.expression;
210
+ if (expr) {
211
+ const extracted = extractToolNamesFromExpression(expr, sourceFile, variableMap);
212
+ extracted.forEach(name => toolNames.add(name));
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ if (toolNames.size > 0) {
220
+ usages.push({
221
+ toolNames,
222
+ file: sourceFile.fileName,
223
+ component: 'ArctenAgent',
224
+ });
225
+ }
226
+ }
227
+ }
228
+
229
+ // Check for useAgent({ tools: [...], safeTools: [...] })
230
+ if (ts.isCallExpression(node)) {
231
+ const expr = node.expression;
232
+ if (ts.isIdentifier(expr) && expr.getText(sourceFile) === 'useAgent') {
233
+ const toolNames = new Set<string>();
234
+
235
+ if (node.arguments.length > 0) {
236
+ const arg = node.arguments[0];
237
+ if (ts.isObjectLiteralExpression(arg)) {
238
+ for (const prop of arg.properties) {
239
+ if (ts.isPropertyAssignment(prop)) {
240
+ const propName = prop.name.getText(sourceFile);
241
+ if (propName === 'tools' || propName === 'safeTools') {
242
+ const extracted = extractToolNamesFromExpression(
243
+ prop.initializer,
244
+ sourceFile,
245
+ variableMap
246
+ );
247
+ extracted.forEach(name => toolNames.add(name));
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ if (toolNames.size > 0) {
255
+ usages.push({
256
+ toolNames,
257
+ file: sourceFile.fileName,
258
+ component: 'useAgent',
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ ts.forEachChild(node, visit);
265
+ }
266
+
267
+ visit(sourceFile);
268
+ return usages;
269
+ }
270
+
271
+ /**
272
+ * Find where a tool function is defined
273
+ */
274
+ function findFunctionDefinition(
275
+ functionName: string,
276
+ sourceFile: ts.SourceFile,
277
+ program: ts.Program
278
+ ): { sourceFile: ts.SourceFile; node: ts.FunctionDeclaration } | null {
279
+ // First, check if it's defined in the current file
280
+ let foundNode: ts.FunctionDeclaration | null = null;
281
+
282
+ function visitForDefinition(node: ts.Node) {
283
+ if (ts.isFunctionDeclaration(node) && node.name) {
284
+ if (node.name.getText(sourceFile) === functionName) {
285
+ foundNode = node;
286
+ }
287
+ }
288
+ if (!foundNode) {
289
+ ts.forEachChild(node, visitForDefinition);
290
+ }
291
+ }
292
+
293
+ visitForDefinition(sourceFile);
294
+
295
+ if (foundNode) {
296
+ return { sourceFile, node: foundNode };
297
+ }
298
+
299
+ // If not found, check imports
300
+ const imports: { name: string; from: string }[] = [];
301
+
302
+ function visitForImports(node: ts.Node) {
303
+ if (ts.isImportDeclaration(node)) {
304
+ const moduleSpecifier = node.moduleClause;
305
+ if (moduleSpecifier && ts.isImportClause(moduleSpecifier)) {
306
+ const namedBindings = moduleSpecifier.namedBindings;
307
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
308
+ for (const element of namedBindings.elements) {
309
+ const importedName = element.name.getText(sourceFile);
310
+ if (importedName === functionName) {
311
+ const modulePath = (node.moduleSpecifier as ts.StringLiteral).text;
312
+ imports.push({ name: importedName, from: modulePath });
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ ts.forEachChild(node, visitForImports);
319
+ }
320
+
321
+ visitForImports(sourceFile);
322
+
323
+ // Follow imports to find definition
324
+ for (const imp of imports) {
325
+ const resolvedPath = resolveImportPath(imp.from, sourceFile.fileName);
326
+ if (resolvedPath) {
327
+ const importedSourceFile = program.getSourceFile(resolvedPath);
328
+ if (importedSourceFile) {
329
+ const result = findFunctionDefinition(functionName, importedSourceFile, program);
330
+ if (result) {
331
+ return result;
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ return null;
338
+ }
339
+
340
+ /**
341
+ * Resolve import path to actual file path
342
+ */
343
+ function resolveImportPath(importPath: string, fromFile: string): string | null {
344
+ if (importPath.startsWith('.')) {
345
+ // Relative import
346
+ const dir = path.dirname(fromFile);
347
+ const resolved = path.resolve(dir, importPath);
348
+
349
+ // Try common extensions
350
+ const extensions = ['.ts', '.tsx', '.js', '.jsx'];
351
+ for (const ext of extensions) {
352
+ const withExt = resolved + ext;
353
+ if (fs.existsSync(withExt)) {
354
+ return withExt;
355
+ }
356
+ }
357
+
358
+ // Try index files
359
+ for (const ext of extensions) {
360
+ const indexPath = path.join(resolved, `index${ext}`);
361
+ if (fs.existsSync(indexPath)) {
362
+ return indexPath;
363
+ }
364
+ }
365
+
366
+ return resolved;
367
+ }
368
+
369
+ // Node modules - we'll skip for now
370
+ return null;
371
+ }
372
+
373
+ /**
374
+ * Serialize a TypeScript type to JSON Schema format
375
+ * Returns: { isOptional, schema }
376
+ */
377
+ function serializeType(
378
+ type: ts.Type,
379
+ checker: ts.TypeChecker,
380
+ visited = new Set<number>(),
381
+ depth = 0
382
+ ): { isOptional: boolean; schema: JsonSchemaProperty } {
383
+ const typeString = checker.typeToString(type);
384
+
385
+ if (depth > 10) {
386
+ return { isOptional: false, schema: { type: 'any' } };
387
+ }
388
+
389
+ const typeId = (type as any).id;
390
+ if (typeId !== undefined && visited.has(typeId)) {
391
+ return { isOptional: false, schema: { type: 'any' } };
392
+ }
393
+
394
+ // Handle primitives
395
+ if (type.flags & ts.TypeFlags.String) {
396
+ return { isOptional: false, schema: { type: 'string' } };
397
+ }
398
+ if (type.flags & ts.TypeFlags.Number) {
399
+ return { isOptional: false, schema: { type: 'number' } };
400
+ }
401
+ if (type.flags & ts.TypeFlags.Boolean) {
402
+ return { isOptional: false, schema: { type: 'boolean' } };
403
+ }
404
+ if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
405
+ return { isOptional: true, schema: { type: 'null' } };
406
+ }
407
+
408
+ // Handle union types
409
+ if (type.isUnion()) {
410
+ const types = type.types;
411
+
412
+ // Check if it's a string literal union (enum)
413
+ const stringLiterals = types
414
+ .filter(t => t.flags & ts.TypeFlags.StringLiteral)
415
+ .map(t => (t as ts.StringLiteralType).value);
416
+
417
+ if (stringLiterals.length === types.length) {
418
+ return {
419
+ isOptional: false,
420
+ schema: { type: 'string', enum: stringLiterals }
421
+ };
422
+ }
423
+
424
+ // Check for optional (T | undefined)
425
+ const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
426
+ const nonUndefinedTypes = types.filter(t => !(t.flags & ts.TypeFlags.Undefined));
427
+
428
+ if (hasUndefined && nonUndefinedTypes.length === 1) {
429
+ const result = serializeType(nonUndefinedTypes[0], checker, visited, depth + 1);
430
+ return { isOptional: true, schema: result.schema };
431
+ }
432
+
433
+ // Generic union - just use string representation
434
+ return {
435
+ isOptional: false,
436
+ schema: { type: types.map(t => checker.typeToString(t)).join(' | ') }
437
+ };
438
+ }
439
+
440
+ // Handle arrays
441
+ if (checker.isArrayType(type)) {
442
+ const typeArgs = (type as ts.TypeReference).typeArguments;
443
+ if (typeArgs && typeArgs.length > 0) {
444
+ const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1);
445
+ return {
446
+ isOptional: false,
447
+ schema: { type: 'array', items: itemResult.schema }
448
+ };
449
+ }
450
+ return { isOptional: false, schema: { type: 'array' } };
451
+ }
452
+
453
+ // Handle objects
454
+ if (type.flags & ts.TypeFlags.Object) {
455
+ if (typeId !== undefined) {
456
+ visited.add(typeId);
457
+ }
458
+
459
+ const properties: Record<string, JsonSchemaProperty> = {};
460
+ const required: string[] = [];
461
+ const props = checker.getPropertiesOfType(type);
462
+
463
+ if (props.length > 0) {
464
+ for (const prop of props) {
465
+ const propDeclaration = prop.valueDeclaration || prop.declarations?.[0];
466
+
467
+ if (propDeclaration) {
468
+ const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
469
+ const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
470
+
471
+ const propResult = serializeType(propType, checker, visited, depth + 1);
472
+ properties[prop.name] = propResult.schema;
473
+
474
+ // Track required properties
475
+ if (!isOptional && !propResult.isOptional) {
476
+ required.push(prop.name);
477
+ }
478
+ }
479
+ }
480
+
481
+ const schema: JsonSchemaProperty = {
482
+ type: 'object',
483
+ properties
484
+ };
485
+
486
+ if (required.length > 0) {
487
+ schema.required = required;
488
+ }
489
+
490
+ return { isOptional: false, schema };
491
+ }
492
+ }
493
+
494
+ // Fallback
495
+ return {
496
+ isOptional: false,
497
+ schema: { type: typeString.length < 100 ? typeString : 'any' }
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Extract metadata from a function
503
+ */
504
+ function extractFunctionMetadata(
505
+ node: ts.FunctionDeclaration,
506
+ checker: ts.TypeChecker,
507
+ sourceFile: ts.SourceFile
508
+ ): FunctionMetadata | null {
509
+ if (!node.name) {
510
+ return null;
511
+ }
512
+
513
+ const functionName = node.name.getText(sourceFile);
514
+
515
+ const jsDocTags = ts.getJSDocTags(node);
516
+ const jsDocComments = ts.getJSDocCommentsAndTags(node);
517
+
518
+ let description = `Execute ${functionName}`;
519
+ for (const comment of jsDocComments) {
520
+ if (ts.isJSDoc(comment) && comment.comment) {
521
+ if (typeof comment.comment === 'string') {
522
+ description = comment.comment.trim();
523
+ }
524
+ break;
525
+ }
526
+ }
527
+
528
+ const paramDescriptions = new Map<string, string>();
529
+ for (const tag of jsDocTags) {
530
+ if (tag.tagName.text === 'param' && ts.isJSDocParameterTag(tag)) {
531
+ const paramName = tag.name?.getText(sourceFile);
532
+ const paramDesc = typeof tag.comment === 'string' ? tag.comment.trim() : '';
533
+ if (paramName && paramDesc) {
534
+ paramDescriptions.set(paramName, paramDesc);
535
+ }
536
+ }
537
+ }
538
+
539
+ const properties: Record<string, JsonSchemaProperty> = {};
540
+ const required: string[] = [];
541
+ const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
542
+
543
+ for (const param of node.parameters) {
544
+ const paramName = param.name.getText(sourceFile);
545
+ const hasDefault = param.initializer !== undefined;
546
+
547
+ let propSchema: JsonSchemaProperty;
548
+ let isOptional = false;
549
+
550
+ if (param.type) {
551
+ const type = checker.getTypeFromTypeNode(param.type);
552
+ const result = serializeType(type, checker);
553
+ propSchema = result.schema;
554
+ isOptional = result.isOptional;
555
+ } else {
556
+ propSchema = { type: 'any' };
557
+ }
558
+
559
+ // Check if parameter has default value or question mark
560
+ const hasQuestionMark = param.questionToken !== undefined;
561
+ if (hasDefault || hasQuestionMark) {
562
+ isOptional = true;
563
+ }
564
+
565
+ // Add default value if present
566
+ if (hasDefault) {
567
+ propSchema.default = param.initializer!.getText(sourceFile);
568
+ }
569
+
570
+ // Add description from JSDoc
571
+ if (paramDescriptions.has(paramName)) {
572
+ propSchema.description = paramDescriptions.get(paramName);
573
+ }
574
+
575
+ properties[paramName] = propSchema;
576
+
577
+ // Track required params
578
+ if (!isOptional) {
579
+ required.push(paramName);
580
+ }
581
+ }
582
+
583
+ let returnType = 'any';
584
+ if (node.type) {
585
+ returnType = node.type.getText(sourceFile);
586
+ }
587
+
588
+ return {
589
+ name: functionName,
590
+ description,
591
+ parameters: {
592
+ type: 'object',
593
+ properties,
594
+ required: required.length > 0 ? required : undefined
595
+ },
596
+ returnType,
597
+ isAsync,
598
+ };
599
+ }
600
+
601
+ /**
602
+ * Main function
603
+ */
604
+ async function autoDiscoverAndExtract(projectRoot: string, outputPath: string) {
605
+ console.log(`\nšŸ” Auto-discovering tools in: ${projectRoot}\n`);
606
+
607
+ // Find all TypeScript files
608
+ const files = await findToolUsageFiles(projectRoot);
609
+
610
+ // Read tsconfig
611
+ const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, 'tsconfig.json');
612
+ let compilerOptions: ts.CompilerOptions = {
613
+ target: ts.ScriptTarget.ESNext,
614
+ module: ts.ModuleKind.ESNext,
615
+ jsx: ts.JsxEmit.React,
616
+ };
617
+
618
+ if (configPath) {
619
+ const config = ts.readConfigFile(configPath, ts.sys.readFile);
620
+ const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
621
+ compilerOptions = parsed.options;
622
+ }
623
+
624
+ // Create program with all files
625
+ const program = ts.createProgram(files, compilerOptions);
626
+ const checker = program.getTypeChecker();
627
+
628
+ // Scan for tool usages
629
+ const allToolUsages: ToolUsage[] = [];
630
+ const allToolNames = new Set<string>();
631
+
632
+ for (const file of files) {
633
+ const sourceFile = program.getSourceFile(file);
634
+ if (sourceFile) {
635
+ const usages = extractToolNamesFromFile(sourceFile, program);
636
+ if (usages.length > 0) {
637
+ allToolUsages.push(...usages);
638
+ usages.forEach(u => u.toolNames.forEach(name => allToolNames.add(name)));
639
+ }
640
+ }
641
+ }
642
+
643
+ console.log(`āœ“ Found ${allToolUsages.length} usage site(s) with ${allToolNames.size} unique tool(s)`);
644
+ allToolUsages.forEach(usage => {
645
+ console.log(` - ${usage.component} in ${path.relative(projectRoot, usage.file)}`);
646
+ console.log(` Tools: ${Array.from(usage.toolNames).join(', ')}`);
647
+ });
648
+
649
+ // Extract metadata for discovered tools
650
+ console.log(`\nšŸ“ Extracting types for discovered tools...\n`);
651
+
652
+ const functionsMap: Record<string, FunctionMetadata> = {};
653
+ const discoveredFrom: string[] = [];
654
+
655
+ for (const toolName of allToolNames) {
656
+ let found = false;
657
+
658
+ // Search in all files for the definition
659
+ for (const file of files) {
660
+ const sourceFile = program.getSourceFile(file);
661
+ if (sourceFile) {
662
+ const result = findFunctionDefinition(toolName, sourceFile, program);
663
+ if (result) {
664
+ const metadata = extractFunctionMetadata(result.node, checker, result.sourceFile);
665
+ if (metadata) {
666
+ functionsMap[toolName] = metadata;
667
+ discoveredFrom.push(path.relative(projectRoot, result.sourceFile.fileName));
668
+ console.log(` āœ“ ${toolName} (from ${path.relative(projectRoot, result.sourceFile.fileName)})`);
669
+ found = true;
670
+ break;
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ if (!found) {
677
+ console.log(` ⚠ ${toolName} (definition not found)`);
678
+ }
679
+ }
680
+
681
+ // Generate output
682
+ const output: GeneratedMetadata = {
683
+ generated: new Date().toISOString(),
684
+ discoveredFrom: Array.from(new Set(discoveredFrom)),
685
+ functions: functionsMap,
686
+ };
687
+
688
+ const outputDir = path.dirname(outputPath);
689
+ if (!fs.existsSync(outputDir)) {
690
+ fs.mkdirSync(outputDir, { recursive: true });
691
+ }
692
+
693
+ // Generate TypeScript module instead of JSON for easier bundler integration
694
+ const tsContent = `// Auto-generated by arcten-extract-types
695
+ // Do not edit this file manually
696
+
697
+ export const toolMetadata = ${JSON.stringify(output, null, 2)} as const;
698
+ `;
699
+
700
+ fs.writeFileSync(outputPath, tsContent);
701
+ console.log(`\nāœ… Generated metadata for ${Object.keys(functionsMap).length} tool(s)`);
702
+ console.log(`šŸ“ Output: ${outputPath}`);
703
+ console.log(`\nšŸ’” Usage in your component:`);
704
+ console.log(` import { toolMetadata } from './.arcten/tool-metadata';`);
705
+ console.log(` useAgent({ tools: [...], toolMetadata: toolMetadata.functions })\n`);
706
+ }
707
+
708
+ // CLI
709
+ if (import.meta.main) {
710
+ const args = process.argv.slice(2);
711
+ const projectRoot = args[0] ? path.resolve(args[0]) : process.cwd();
712
+ const outputPath = args[1]
713
+ ? path.resolve(args[1])
714
+ : path.join(projectRoot, '.arcten', 'tool-metadata.ts');
715
+
716
+ autoDiscoverAndExtract(projectRoot, outputPath);
717
+ }
718
+
719
+ export { autoDiscoverAndExtract };