@soederpop/luca 0.0.28 → 0.0.30

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 (51) hide show
  1. package/commands/try-all-challenges.ts +1 -1
  2. package/docs/TABLE-OF-CONTENTS.md +0 -3
  3. package/docs/examples/structured-output-with-assistants.md +144 -0
  4. package/docs/tutorials/20-browser-esm.md +234 -0
  5. package/package.json +1 -1
  6. package/src/agi/container.server.ts +4 -0
  7. package/src/agi/features/assistant.ts +132 -2
  8. package/src/agi/features/browser-use.ts +623 -0
  9. package/src/agi/features/conversation.ts +135 -45
  10. package/src/agi/lib/interceptor-chain.ts +79 -0
  11. package/src/bootstrap/generated.ts +381 -308
  12. package/src/cli/build-info.ts +2 -2
  13. package/src/clients/rest.ts +7 -7
  14. package/src/commands/chat.ts +22 -0
  15. package/src/commands/describe.ts +67 -2
  16. package/src/commands/prompt.ts +23 -3
  17. package/src/container.ts +411 -113
  18. package/src/helper.ts +189 -5
  19. package/src/introspection/generated.agi.ts +17664 -11568
  20. package/src/introspection/generated.node.ts +4891 -1860
  21. package/src/introspection/generated.web.ts +379 -291
  22. package/src/introspection/index.ts +7 -0
  23. package/src/introspection/scan.ts +224 -7
  24. package/src/node/container.ts +31 -10
  25. package/src/node/features/content-db.ts +7 -7
  26. package/src/node/features/disk-cache.ts +11 -11
  27. package/src/node/features/esbuild.ts +3 -3
  28. package/src/node/features/file-manager.ts +37 -16
  29. package/src/node/features/fs.ts +64 -25
  30. package/src/node/features/git.ts +10 -10
  31. package/src/node/features/helpers.ts +25 -18
  32. package/src/node/features/ink.ts +13 -13
  33. package/src/node/features/ipc-socket.ts +8 -8
  34. package/src/node/features/networking.ts +3 -3
  35. package/src/node/features/os.ts +7 -7
  36. package/src/node/features/package-finder.ts +15 -15
  37. package/src/node/features/proc.ts +1 -1
  38. package/src/node/features/ui.ts +13 -13
  39. package/src/node/features/vm.ts +4 -4
  40. package/src/scaffolds/generated.ts +1 -1
  41. package/src/servers/express.ts +6 -6
  42. package/src/servers/mcp.ts +4 -4
  43. package/src/servers/socket.ts +6 -6
  44. package/test/interceptor-chain.test.ts +61 -0
  45. package/docs/apis/features/node/window-manager.md +0 -445
  46. package/docs/examples/window-manager-layouts.md +0 -180
  47. package/docs/examples/window-manager.md +0 -125
  48. package/docs/window-manager-fix.md +0 -249
  49. package/scripts/test-window-manager-lifecycle.ts +0 -86
  50. package/scripts/test-window-manager.ts +0 -43
  51. package/src/node/features/window-manager.ts +0 -1603
@@ -75,6 +75,11 @@ export type HelperIntrospection = {
75
75
  envVars?: string[]
76
76
  // class-level @example blocks from JSDoc
77
77
  examples?: ExampleIntrospection[]
78
+ // referenced type definitions resolved from parameter and return types
79
+ types?: Record<string, {
80
+ description: string;
81
+ properties: Record<string, { type: string; description: string; optional?: boolean }>
82
+ }>
78
83
  }
79
84
 
80
85
  export type RegistryIntrospection = {
@@ -203,6 +208,7 @@ export function setBuildTimeData(key: string, data: HelperIntrospection) {
203
208
  getters: data.getters || existing?.getters || {},
204
209
  envVars: existing?.envVars || data.envVars || [],
205
210
  examples: data.examples || existing?.examples,
211
+ types: { ...(existing?.types || {}), ...(data.types || {}) },
206
212
  })
207
213
  }
208
214
 
@@ -259,6 +265,7 @@ export function interceptRegistration(registry: any, helperConstructor: any) {
259
265
  ? helperConstructor.envVars
260
266
  : (existing?.envVars || []),
261
267
  examples: existing?.examples,
268
+ types: existing?.types,
262
269
  }
263
270
 
264
271
  // Always populate state and options from Zod schemas at runtime
@@ -189,6 +189,7 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
189
189
  const getters = this.extractGetters(classNode, sourceFile);
190
190
  const events = this.extractEvents(classNode, sourceFile);
191
191
  const examples = this.extractJSDocExamples(classNode);
192
+ const types = this.collectReferencedTypes(methods, getters, sourceFile);
192
193
 
193
194
  // state and options are derived at runtime from Zod schemas
194
195
  // via interceptRegistration — no need to extract them at build time
@@ -204,6 +205,7 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
204
205
  options: {},
205
206
  envVars: [],
206
207
  ...(examples.length > 0 ? { examples } : {}),
208
+ ...(Object.keys(types).length > 0 ? { types } : {}),
207
209
  };
208
210
  }
209
211
 
@@ -230,7 +232,7 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
230
232
  * These are framework internals (class references, utility objects) not useful in inspection.
231
233
  */
232
234
  private static CONTAINER_SKIP_GETTERS = new Set([
233
- 'Feature', 'Helper', 'State', 'z', 'features',
235
+ 'Feature', 'Helper', 'State', 'z',
234
236
  ]);
235
237
 
236
238
  private extendsContainer(classNode: ts.ClassDeclaration): boolean {
@@ -298,6 +300,9 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
298
300
  // Skip methods starting with _ (internal methods like _hide)
299
301
  if (methodName.startsWith('_')) continue;
300
302
 
303
+ // Skip methods tagged with @internal
304
+ if (this.hasJSDocTag(member, 'internal')) continue;
305
+
301
306
  const description = this.extractJSDocDescription(member) || '';
302
307
  const parameters = this.extractParameters(member);
303
308
  const required = this.extractRequiredParameters(member);
@@ -327,6 +332,9 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
327
332
  // Skip container framework getters
328
333
  if (IntrospectionScannerFeature.CONTAINER_SKIP_GETTERS.has(getterName)) continue;
329
334
 
335
+ // Skip getters tagged with @internal
336
+ if (this.hasJSDocTag(member, 'internal')) continue;
337
+
330
338
  // Skip private getters unless includePrivate is true
331
339
  const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
332
340
  if (isPrivate && !this.options.includePrivate) continue;
@@ -336,7 +344,7 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
336
344
  if (isStatic) continue;
337
345
 
338
346
  const description = this.extractJSDocDescriptionFromAccessor(member) || '';
339
- const returns = member.type ? member.type.getText() : 'any';
347
+ const returns = this.resolveReturnType(member, member.type, 'any');
340
348
  const examples = this.extractJSDocExamples(member);
341
349
 
342
350
  getters[getterName] = {
@@ -486,6 +494,9 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
486
494
  const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
487
495
  if (isStatic) continue;
488
496
 
497
+ // Skip methods tagged with @internal
498
+ if (this.hasJSDocTag(member, 'internal')) continue;
499
+
489
500
  const description = this.extractJSDocDescription(member) || '';
490
501
  const parameters = this.extractParameters(member);
491
502
  const required = this.extractRequiredParameters(member);
@@ -539,8 +550,11 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
539
550
  const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
540
551
  if (isStatic) continue;
541
552
 
553
+ // Skip getters tagged with @internal
554
+ if (this.hasJSDocTag(member, 'internal')) continue;
555
+
542
556
  const description = this.extractJSDocDescriptionFromAccessor(member) || '';
543
- const returns = member.type ? member.type.getText() : 'any';
557
+ const returns = this.resolveReturnType(member, member.type, 'any');
544
558
  const examples = this.extractJSDocExamples(member);
545
559
 
546
560
  getters[getterName] = {
@@ -554,6 +568,57 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
554
568
  return getters;
555
569
  }
556
570
 
571
+ /**
572
+ * Extract a return type from a node, preferring explicit TS annotation,
573
+ * falling back to JSDoc @returns {Type}, then to the given default.
574
+ */
575
+ private resolveReturnType(node: ts.Node, typeNode: ts.TypeNode | undefined, fallback: string): string {
576
+ if (typeNode) return typeNode.getText();
577
+
578
+ const sourceFile = node.getSourceFile();
579
+ const fullText = sourceFile.getFullText();
580
+ const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
581
+
582
+ if (ranges && ranges.length > 0) {
583
+ for (let i = ranges.length - 1; i >= 0; i--) {
584
+ const range = ranges[i];
585
+ if (!range) continue;
586
+ const commentText = fullText.substring(range.pos, range.end);
587
+ if (!commentText.startsWith('/**')) continue;
588
+
589
+ const match = commentText.match(/@returns?\s*\{([^}]+)\}/);
590
+ if (match) return match[1].trim();
591
+ }
592
+ }
593
+
594
+ return fallback;
595
+ }
596
+
597
+ /**
598
+ * Check if a node's JSDoc comment contains a specific @tag (e.g. @internal).
599
+ */
600
+ private hasJSDocTag(node: ts.Node, tagName: string): boolean {
601
+ const sourceFile = node.getSourceFile();
602
+ const fullText = sourceFile.getFullText();
603
+ const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
604
+
605
+ if (!ranges || ranges.length === 0) return false;
606
+
607
+ for (let i = ranges.length - 1; i >= 0; i--) {
608
+ const range = ranges[i];
609
+ if (!range) continue;
610
+ const commentText = fullText.substring(range.pos, range.end);
611
+ if (!commentText.startsWith('/**')) continue;
612
+
613
+ // Check for @tagName anywhere in the JSDoc
614
+ if (new RegExp(`@${tagName}\\b`).test(commentText)) {
615
+ return true;
616
+ }
617
+ }
618
+
619
+ return false;
620
+ }
621
+
557
622
  private extractJSDocDescriptionFromAccessor(node: ts.GetAccessorDeclaration): string | null {
558
623
  const sourceFile = node.getSourceFile();
559
624
  const fullText = sourceFile.getFullText();
@@ -781,6 +846,161 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
781
846
  return Object.keys(members).length > 0 ? members : null as any;
782
847
  }
783
848
 
849
+ /**
850
+ * Collect all non-primitive type names referenced in method parameters and return types,
851
+ * then resolve each to its properties from the source file. Returns a map of type name
852
+ * to its property definitions, suitable for emitting as standalone interface declarations.
853
+ */
854
+ private collectReferencedTypes(
855
+ methods: Record<string, MethodIntrospection>,
856
+ getters: Record<string, { returns: string; description: string }>,
857
+ sourceFile: ts.SourceFile
858
+ ): Record<string, { description: string; properties: Record<string, { type: string; description: string; optional?: boolean }> }> {
859
+ const types: Record<string, { description: string; properties: Record<string, { type: string; description: string; optional?: boolean }> }> = {};
860
+ const seen = new Set<string>();
861
+
862
+ const tryResolve = (rawType: string) => {
863
+ // Extract type names from complex type strings
864
+ // e.g. "Promise<GrepMatch[]>" -> "GrepMatch"
865
+ // e.g. "GrepOptions" -> "GrepOptions"
866
+ // e.g. "string | Foo" -> "Foo"
867
+ // e.g. "Omit<GrepOptions, 'pattern'>" -> "GrepOptions"
868
+ const typeNames = this.extractTypeNames(rawType);
869
+
870
+ for (const typeName of typeNames) {
871
+ if (seen.has(typeName)) continue;
872
+ seen.add(typeName);
873
+
874
+ if (IntrospectionScannerFeature.PRIMITIVE_TYPES.has(typeName)) continue;
875
+
876
+ const properties = this.resolveTypePropertiesWithOptional(typeName, sourceFile);
877
+ if (properties && Object.keys(properties).length > 0) {
878
+ // Extract JSDoc description from the type declaration itself
879
+ const description = this.extractTypeDescription(typeName, sourceFile) || '';
880
+ types[typeName] = { description, properties };
881
+
882
+ // Recursively resolve types referenced in properties
883
+ for (const prop of Object.values(properties)) {
884
+ tryResolve(prop.type);
885
+ }
886
+ }
887
+ }
888
+ };
889
+
890
+ // Collect from method parameters and return types
891
+ for (const method of Object.values(methods)) {
892
+ for (const param of Object.values(method.parameters || {})) {
893
+ tryResolve(param.type);
894
+ }
895
+ if (method.returns) {
896
+ tryResolve(method.returns);
897
+ }
898
+ }
899
+
900
+ // Collect from getter return types
901
+ for (const getter of Object.values(getters)) {
902
+ if (getter.returns) {
903
+ tryResolve(getter.returns);
904
+ }
905
+ }
906
+
907
+ return types;
908
+ }
909
+
910
+ /**
911
+ * Extract concrete type names from a potentially complex type string.
912
+ * Handles generics, unions, intersections, arrays, and utility types.
913
+ */
914
+ private extractTypeNames(typeStr: string): string[] {
915
+ if (!typeStr) return [];
916
+
917
+ // Remove Promise<...> wrapper but keep the inner type
918
+ // Remove array suffix [], Partial<>, Omit<>, Pick<>, etc.
919
+ // Split on | and & for unions/intersections
920
+ const names: string[] = [];
921
+
922
+ // Match identifiers that look like type names (PascalCase or known patterns)
923
+ // This regex finds all capitalized identifiers that aren't JS built-ins
924
+ const matches = typeStr.match(/\b([A-Z][A-Za-z0-9_]*)\b/g) || [];
925
+ const BUILTIN_TYPES = new Set([
926
+ 'Promise', 'Array', 'Record', 'Map', 'Set', 'WeakMap', 'WeakSet',
927
+ 'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude', 'Extract',
928
+ 'Buffer', 'Date', 'RegExp', 'Error', 'Function', 'Symbol',
929
+ 'AsyncIterable', 'Iterable', 'Iterator', 'AsyncIterator',
930
+ 'InstanceType', 'ReturnType', 'Parameters',
931
+ 'HTMLElement', 'Event', 'EventTarget',
932
+ 'Stats', // Node.js fs.Stats — commonly used but not resolvable in-file
933
+ ]);
934
+
935
+ for (const match of matches) {
936
+ if (!BUILTIN_TYPES.has(match) && !IntrospectionScannerFeature.PRIMITIVE_TYPES.has(match.toLowerCase())) {
937
+ names.push(match);
938
+ }
939
+ }
940
+
941
+ return [...new Set(names)];
942
+ }
943
+
944
+ /**
945
+ * Like resolveTypeProperties but also captures whether each property is optional (has ?).
946
+ */
947
+ private resolveTypePropertiesWithOptional(typeName: string, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
948
+ for (const statement of sourceFile.statements) {
949
+ if (ts.isTypeAliasDeclaration(statement) && statement.name.text === typeName) {
950
+ if (ts.isTypeLiteralNode(statement.type)) {
951
+ return this.extractTypeLiteralMembersWithOptional(statement.type, sourceFile);
952
+ }
953
+ }
954
+ if (ts.isInterfaceDeclaration(statement) && statement.name.text === typeName) {
955
+ return this.extractInterfaceMembersWithOptional(statement, sourceFile);
956
+ }
957
+ }
958
+ return null;
959
+ }
960
+
961
+ private extractTypeLiteralMembersWithOptional(node: ts.TypeLiteralNode, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
962
+ const members: Record<string, { type: string; description: string; optional?: boolean }> = {};
963
+ for (const member of node.members) {
964
+ if (ts.isPropertySignature(member) && member.name) {
965
+ const name = member.name.getText();
966
+ const type = member.type ? member.type.getText() : 'any';
967
+ const description = this.extractJSDocFromNode(member, sourceFile) || '';
968
+ const optional = !!member.questionToken;
969
+ members[name] = { type, description, ...(optional ? { optional } : {}) };
970
+ }
971
+ }
972
+ return Object.keys(members).length > 0 ? members : null;
973
+ }
974
+
975
+ private extractInterfaceMembersWithOptional(node: ts.InterfaceDeclaration, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
976
+ const members: Record<string, { type: string; description: string; optional?: boolean }> = {};
977
+ for (const member of node.members) {
978
+ if (ts.isPropertySignature(member) && member.name) {
979
+ const name = member.name.getText();
980
+ const type = member.type ? member.type.getText() : 'any';
981
+ const description = this.extractJSDocFromNode(member, sourceFile) || '';
982
+ const optional = !!member.questionToken;
983
+ members[name] = { type, description, ...(optional ? { optional } : {}) };
984
+ }
985
+ }
986
+ return Object.keys(members).length > 0 ? members : null;
987
+ }
988
+
989
+ /**
990
+ * Extract the JSDoc description from a type alias or interface declaration.
991
+ */
992
+ private extractTypeDescription(typeName: string, sourceFile: ts.SourceFile): string | null {
993
+ for (const statement of sourceFile.statements) {
994
+ if (
995
+ (ts.isTypeAliasDeclaration(statement) || ts.isInterfaceDeclaration(statement)) &&
996
+ statement.name.text === typeName
997
+ ) {
998
+ return this.extractJSDocFromNode(statement, sourceFile);
999
+ }
1000
+ }
1001
+ return null;
1002
+ }
1003
+
784
1004
  /**
785
1005
  * Extracts a JSDoc description from any node that may have leading comments.
786
1006
  */
@@ -828,10 +1048,7 @@ export class IntrospectionScannerFeature extends Feature<IntrospectionScannerSta
828
1048
  }
829
1049
 
830
1050
  private extractReturnType(method: ts.MethodDeclaration): string {
831
- if (method.type) {
832
- return method.type.getText();
833
- }
834
- return 'void';
1051
+ return this.resolveReturnType(method, method.type, 'void');
835
1052
  }
836
1053
 
837
1054
  private extractEvents(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, EventIntrospection> {
@@ -57,7 +57,6 @@ import "./features/google-sheets";
57
57
  import "./features/google-calendar";
58
58
  import "./features/google-docs";
59
59
  import "./features/google-mail";
60
- import "./features/window-manager";
61
60
  import "./features/nlp";
62
61
  import "./features/process-manager"
63
62
  import "./features/tts";
@@ -102,7 +101,6 @@ import type { GoogleSheets } from './features/google-sheets';
102
101
  import type { GoogleCalendar } from './features/google-calendar';
103
102
  import type { GoogleDocs } from './features/google-docs';
104
103
  import type { GoogleMail } from './features/google-mail';
105
- import type { WindowManager } from './features/window-manager';
106
104
  import type { NLP } from './features/nlp';
107
105
  import type { ProcessManager } from './features/process-manager'
108
106
  import type { TTS } from './features/tts';
@@ -142,7 +140,6 @@ export {
142
140
  type GoogleCalendar,
143
141
  type GoogleDocs,
144
142
  type GoogleMail,
145
- type WindowManager,
146
143
  type NLP,
147
144
  type ProcessManager,
148
145
  type TTS,
@@ -208,7 +205,6 @@ export interface NodeFeatures extends AvailableFeatures {
208
205
  googleCalendar: typeof GoogleCalendar;
209
206
  googleDocs: typeof GoogleDocs;
210
207
  googleMail: typeof GoogleMail;
211
- windowManager: typeof WindowManager;
212
208
  nlp: typeof NLP;
213
209
  processManager: typeof ProcessManager;
214
210
  tts: typeof TTS;
@@ -233,9 +229,35 @@ export interface NodeContainerState extends ContainerState {
233
229
  }
234
230
  */
235
231
 
232
+ /**
233
+ * Server-side container for Node.js and Bun environments. Extends the base Container with
234
+ * file system access, process management, git integration, and other server-side capabilities.
235
+ *
236
+ * Auto-enables core features on construction: fs, proc, git, grep, os, networking, ui, vm, esbuild, helpers.
237
+ * Also attaches Client, Server, Command, Endpoint, and Selector helper types, providing
238
+ * `container.client()`, `container.server()`, `container.command()`, etc. factory methods.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * import container from '@soederpop/luca/node'
243
+ *
244
+ * // File operations
245
+ * const content = container.fs.readFile('README.md')
246
+ *
247
+ * // Path utilities (scoped to cwd)
248
+ * const full = container.paths.resolve('src/index.ts')
249
+ *
250
+ * // Create a REST client
251
+ * const api = container.client('rest', { baseURL: 'https://api.example.com' })
252
+ *
253
+ * // Start an Express server
254
+ * const app = container.server('express', { port: 3000 })
255
+ * await app.start()
256
+ * ```
257
+ */
236
258
  export class NodeContainer<
237
259
  Features extends NodeFeatures = NodeFeatures,
238
- K extends ContainerState = ContainerState
260
+ K extends ContainerState = ContainerState
239
261
  > extends Container<Features, K> {
240
262
  fs!: FS;
241
263
  git!: Git;
@@ -268,7 +290,6 @@ export class NodeContainer<
268
290
  googleCalendar?: GoogleCalendar;
269
291
  googleDocs?: GoogleDocs;
270
292
  googleMail?: GoogleMail;
271
- windowManager?: WindowManager;
272
293
  nlp?: NLP;
273
294
  processManager?: ProcessManager;
274
295
  tts?: TTS;
@@ -318,7 +339,7 @@ export class NodeContainer<
318
339
  }
319
340
 
320
341
  /** Returns the parsed package.json manifest for the current working directory. */
321
- get manifest() {
342
+ get manifest(): Record<string, any> {
322
343
  try {
323
344
  const packageJson = this.fs.findUp("packageon");
324
345
 
@@ -339,19 +360,19 @@ export class NodeContainer<
339
360
  }
340
361
 
341
362
  /** Returns the parsed command-line arguments (from minimist). */
342
- get argv() {
363
+ get argv(): Record<string, any> & { _: string[] } {
343
364
  return this.options as any;
344
365
  }
345
366
 
346
367
  /** Returns URL utility functions for parsing URIs. */
347
- get urlUtils() {
368
+ get urlUtils(): { parse: (uri: string) => url.URL | null } {
348
369
  return {
349
370
  parse: (uri: string) => url.parse(uri)
350
371
  }
351
372
  }
352
373
 
353
374
  /** Returns path utility functions scoped to the current working directory (join, resolve, relative, dirname, parse). */
354
- get paths() {
375
+ get paths(): { dirname: (path: string) => string; join: (...paths: string[]) => string; resolve: (...paths: string[]) => string; relative: (...paths: string[]) => string; basename: typeof basename; parse: typeof parse } {
355
376
  const { cwd } = this;
356
377
  return {
357
378
  dirname(path: string) {
@@ -118,15 +118,15 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
118
118
  }
119
119
 
120
120
  /** Whether the content database has been loaded. */
121
- get isLoaded() {
122
- return this.state.get('started')
121
+ get isLoaded(): boolean {
122
+ return !!this.state.get('started')
123
123
  }
124
124
 
125
125
  _collection?: Collection
126
126
  private _contentbaseSeeded = false
127
127
 
128
128
  /** Returns the lazily-initialized Collection instance for the configured rootPath. */
129
- get collection() {
129
+ get collection(): Collection {
130
130
  if (this._collection) return this._collection
131
131
 
132
132
  const opts: any = { rootPath: this.options.rootPath }
@@ -172,7 +172,7 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
172
172
  }
173
173
 
174
174
  /** Returns the absolute resolved path to the collection root directory. */
175
- get collectionPath() {
175
+ get collectionPath(): string {
176
176
  return this.container.paths.resolve(this.options.rootPath)
177
177
  }
178
178
 
@@ -429,7 +429,7 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
429
429
  return this.collection.generateModelSummary(options)
430
430
  }
431
431
 
432
- get modelDefinitionTable() {
432
+ get modelDefinitionTable(): Record<string, { description: string; glob: string; routePatterns: string[] }> {
433
433
  return Object.fromEntries(this.collection.modelDefinitions.map(d => {
434
434
 
435
435
  const prefixPattern = this.container.paths.relative(this.collection.resolve(d.prefix))
@@ -442,7 +442,7 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
442
442
  }))
443
443
  }
444
444
 
445
- get fileTree() {
445
+ get fileTree(): string {
446
446
  return this.collection.renderFileTree()
447
447
  }
448
448
 
@@ -579,7 +579,7 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
579
579
  /**
580
580
  * Get the current search index status.
581
581
  */
582
- get searchIndexStatus() {
582
+ get searchIndexStatus(): { exists: boolean; documentCount: number; chunkCount: number; embeddingCount: number; lastIndexedAt: any; provider: any; model: any; dimensions: number; dbSizeBytes: number } {
583
583
  if (!this._semanticSearch?.state?.get('dbReady')) {
584
584
  if (!this._hasSearchIndex()) {
585
585
  return { exists: false, documentCount: 0, chunkCount: 0, embeddingCount: 0, lastIndexedAt: null, provider: null, model: null, dimensions: 0, dbSizeBytes: 0 }
@@ -37,7 +37,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
37
37
  static { Feature.register(this, 'diskCache') }
38
38
 
39
39
  /** Returns the underlying cacache instance configured with the cache directory path. */
40
- get cache() {
40
+ get cache(): ReturnType<typeof this.create> {
41
41
  if(this._cache) {
42
42
  return this._cache
43
43
  }
@@ -61,7 +61,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
61
61
  * await diskCache.saveFile('encodedImage', './images/photo.jpg', true)
62
62
  * ```
63
63
  */
64
- async saveFile(key: string, outputPath: string, isBase64 = false) {
64
+ async saveFile(key: string, outputPath: string, isBase64 = false): Promise<Buffer | string> {
65
65
  const outPath = this.container.paths.resolve(outputPath)
66
66
  const content = await this.get(key)
67
67
  const data = isBase64 ? Buffer.from(content, 'base64') : content
@@ -79,7 +79,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
79
79
  * await diskCache.ensure('config', JSON.stringify(defaultConfig))
80
80
  * ```
81
81
  */
82
- async ensure(key: string, content: string) {
82
+ async ensure(key: string, content: string): Promise<string> {
83
83
  const exists = await this.has(key)
84
84
 
85
85
  if (!exists) {
@@ -102,7 +102,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
102
102
  * await diskCache.copy('file1', 'file2', true) // force overwrite
103
103
  * ```
104
104
  */
105
- async copy(source: string, destination: string, overwrite: boolean = false) {
105
+ async copy(source: string, destination: string, overwrite: boolean = false): Promise<string> {
106
106
  if(!overwrite && (await this.has(destination))) {
107
107
  throw new Error('Destination already exists')
108
108
  }
@@ -125,7 +125,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
125
125
  * await diskCache.move('old_key', 'new_key', true) // force overwrite
126
126
  * ```
127
127
  */
128
- async move(source: string, destination: string, overwrite: boolean = false) {
128
+ async move(source: string, destination: string, overwrite: boolean = false): Promise<string> {
129
129
  if(!overwrite && (await this.has(destination))) {
130
130
  throw new Error('Destination already exists')
131
131
  }
@@ -147,7 +147,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
147
147
  * }
148
148
  * ```
149
149
  */
150
- async has(key: string) {
150
+ async has(key: string): Promise<boolean> {
151
151
  return this.cache.get.info(key).then((r:any) => r != null)
152
152
  }
153
153
 
@@ -162,7 +162,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
162
162
  * const data = await diskCache.get('myData', true) // parse as JSON
163
163
  * ```
164
164
  */
165
- async get(key: string, json = false) {
165
+ async get(key: string, json = false): Promise<any> {
166
166
  const val = this.options.encrypt
167
167
  ? await this.securely.get(key)
168
168
  : await this.cache.get(key).then((data: any) => data.data.toString())
@@ -191,7 +191,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
191
191
  * await diskCache.set('file', content, { size: 1024, type: 'image' })
192
192
  * ```
193
193
  */
194
- async set(key: string, value: any, meta?: any) {
194
+ async set(key: string, value: any, meta?: any): Promise<any> {
195
195
  if (this.options.encrypt) {
196
196
  return this.securely.set(key, value, meta)
197
197
  }
@@ -216,7 +216,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
216
216
  * await diskCache.rm('obsoleteKey')
217
217
  * ```
218
218
  */
219
- async rm(key: string) {
219
+ async rm(key: string): Promise<any> {
220
220
  return this.cache.rm.entry(key)
221
221
  }
222
222
 
@@ -230,7 +230,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
230
230
  * await diskCache.clearAll(true) // Must explicitly confirm
231
231
  * ```
232
232
  */
233
- async clearAll(confirm = false) {
233
+ async clearAll(confirm = false): Promise<this> {
234
234
  if(confirm !== true) {
235
235
  throw new Error('Must confirm with clearAll(true)')
236
236
  }
@@ -281,7 +281,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
281
281
  * const decrypted = await cache.securely.get('sensitive')
282
282
  * ```
283
283
  */
284
- get securely() {
284
+ get securely(): { set(name: string, payload: any, meta?: any): Promise<any>; get(name: string): Promise<any> } {
285
285
  const { secret, encrypt } = this.options
286
286
 
287
287
  if (!encrypt) {
@@ -27,7 +27,7 @@ export class ESBuild extends Feature {
27
27
  * @param options - The options to pass to esbuild
28
28
  * @returns The transformed code
29
29
  */
30
- transformSync(code: string, options?: esbuild.TransformOptions) {
30
+ transformSync(code: string, options?: esbuild.TransformOptions): esbuild.TransformResult {
31
31
  return esbuild.transformSync(code, {
32
32
  loader: 'ts',
33
33
  format: 'esm',
@@ -44,7 +44,7 @@ export class ESBuild extends Feature {
44
44
  * @param options - The options to pass to esbuild
45
45
  * @returns The transformed code
46
46
  */
47
- async transform(code: string, options?: esbuild.TransformOptions) {
47
+ async transform(code: string, options?: esbuild.TransformOptions): Promise<esbuild.TransformResult> {
48
48
  return esbuild.transform(code, {
49
49
  loader: 'ts',
50
50
  format: 'esm',
@@ -63,7 +63,7 @@ export class ESBuild extends Feature {
63
63
  * @param options - esbuild BuildOptions overrides
64
64
  * @returns The build result with outputFiles when write is false
65
65
  */
66
- async bundle(entryPoints: string[], options?: esbuild.BuildOptions) {
66
+ async bundle(entryPoints: string[], options?: esbuild.BuildOptions): Promise<esbuild.BuildResult> {
67
67
  return esbuild.build({
68
68
  entryPoints,
69
69
  bundle: true,