@soederpop/luca 0.0.29 → 0.0.31

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 (50) hide show
  1. package/commands/try-all-challenges.ts +1 -1
  2. package/docs/TABLE-OF-CONTENTS.md +0 -3
  3. package/docs/tutorials/20-browser-esm.md +234 -0
  4. package/package.json +1 -1
  5. package/src/agi/container.server.ts +4 -0
  6. package/src/agi/features/assistant.ts +120 -3
  7. package/src/agi/features/browser-use.ts +623 -0
  8. package/src/bootstrap/generated.ts +236 -308
  9. package/src/cli/build-info.ts +2 -2
  10. package/src/clients/rest.ts +7 -7
  11. package/src/command.ts +20 -1
  12. package/src/commands/chat.ts +22 -0
  13. package/src/commands/describe.ts +67 -2
  14. package/src/commands/prompt.ts +23 -3
  15. package/src/commands/serve.ts +27 -0
  16. package/src/container.ts +411 -113
  17. package/src/endpoint.ts +6 -0
  18. package/src/helper.ts +226 -5
  19. package/src/introspection/generated.agi.ts +16089 -10021
  20. package/src/introspection/generated.node.ts +5102 -2077
  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 +15 -15
  29. package/src/node/features/fs.ts +23 -22
  30. package/src/node/features/git.ts +10 -10
  31. package/src/node/features/helpers.ts +5 -2
  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 +24 -6
  42. package/src/servers/mcp.ts +4 -4
  43. package/src/servers/socket.ts +6 -6
  44. package/docs/apis/features/node/window-manager.md +0 -445
  45. package/docs/examples/window-manager-layouts.md +0 -180
  46. package/docs/examples/window-manager.md +0 -125
  47. package/docs/window-manager-fix.md +0 -249
  48. package/scripts/test-window-manager-lifecycle.ts +0 -86
  49. package/scripts/test-window-manager.ts +0 -43
  50. package/src/node/features/window-manager.ts +0 -1603
package/src/container.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Bus } from './bus'
3
3
  import { SetStateValue, State } from './state'
4
4
  import { AvailableFeatures, features, Feature, FeaturesRegistry } from './feature'
5
- import { Helper } from './helper'
5
+ import { Helper, normalizeTypeString, renderTypeScriptParams, isGenericObjectType } from './helper'
6
6
  import uuid from 'node-uuid'
7
7
  import hashObject from './hash-object'
8
8
  import { uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit, kebabCase, camelCase, upperFirst, lowerFirst } from 'lodash-es'
@@ -40,22 +40,53 @@ export interface Plugin<T> {
40
40
 
41
41
  export type Extension<T> = 'string' | keyof AvailableFeatures | Plugin<T> | { attach: (container: Container<any>, options?: any) => T}
42
42
 
43
+ export interface ContainerUtils {
44
+ /** Generate a v4 UUID */
45
+ uuid: () => string
46
+ /** Deterministic hash of any object */
47
+ hashObject: (obj: any) => string
48
+ /** String case conversion and inflection utilities */
49
+ stringUtils: { kebabCase: typeof kebabCase; camelCase: typeof camelCase; upperFirst: typeof upperFirst; lowerFirst: typeof lowerFirst; pluralize: typeof pluralize; singularize: typeof singularize }
50
+ /** Lodash utility subset */
51
+ lodash: { uniq: typeof uniq; keyBy: typeof keyBy; uniqBy: typeof uniqBy; groupBy: typeof groupBy; debounce: typeof debounce; throttle: typeof throttle; mapValues: typeof mapValues; mapKeys: typeof mapKeys; pick: typeof pick; get: typeof get; set: typeof set; omit: typeof omit }
52
+ }
53
+
43
54
  export interface ContainerContext<T extends AvailableFeatures = any> {
44
- container: Container<T>
55
+ container: Container<T>
45
56
  }
46
57
 
47
- /**
48
- * Containers are single objects that contain state, an event bus, and registries of helpers such as:
49
- *
50
- * - features
51
- * - clients
52
- * - servers
53
- *
54
- * A Helper represents a category of components in your program which have a common interface, e.g. all servers can be started / stopped, all features can be enabled, if supported, all clients can connect to something.
55
- *
56
- * A Helper can be introspected at runtime to learn about the interface of the helper. A helper has state, and emits events.
57
- *
58
- * You can design your own containers and load them up with the helpers you want for that environment.
58
+ /**
59
+ * The Container is the core runtime object in Luca. It is a singleton per process that acts as an
60
+ * event bus, state machine, and dependency injector. It holds registries of helpers (features, clients,
61
+ * servers, commands, endpoints) and provides factory methods to create instances from them.
62
+ *
63
+ * All helper instances share the container's context, enabling them to communicate and coordinate.
64
+ * The container detects its runtime environment (Node, Bun, browser, Electron) and can load
65
+ * platform-specific feature implementations accordingly.
66
+ *
67
+ * Use `container.feature('name')` to create feature instances, `container.use(Plugin)` to extend
68
+ * the container with new capabilities, and `container.on('event', handler)` to react to lifecycle events.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * // Create a feature instance (cached — same args return same instance)
73
+ * const fs = container.feature('fs')
74
+ * const content = fs.readFile('README.md')
75
+ * ```
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * // Listen for state changes
80
+ * container.on('stateChange', (state) => console.log('State changed:', state))
81
+ * container.setState({ started: true })
82
+ * ```
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Extend with a plugin
87
+ * container.use(MyClient) // calls MyClient.attach(container)
88
+ * container.use('contentDb') // enable a feature by name
89
+ * ```
59
90
  */
60
91
  export class Container<Features extends AvailableFeatures = AvailableFeatures, ContainerState extends ContainerState = ContainerState > {
61
92
  static stateSchema = ContainerStateSchema
@@ -100,13 +131,18 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
100
131
 
101
132
  /**
102
133
  * Creates a new subcontainer instance of the same concrete Container subclass.
103
- *
104
134
  * The new instance is constructed with the same options as this container,
105
135
  * shallow-merged with any overrides you provide. This preserves the runtime
106
- * container type (e.g. NodeContainer, BrowserContainer, etc.).
136
+ * container type (e.g. NodeContainer, AGIContainer, etc.).
137
+ *
138
+ * @param options - Options to override for the new container instance
139
+ * @returns A new container instance of the same subclass
107
140
  *
108
- * @param options - Options to override for the new container instance.
109
- * @returns A new container instance of the same subclass.
141
+ * @example
142
+ * ```ts
143
+ * const child = container.subcontainer({ cwd: '/tmp/workspace' })
144
+ * child.cwd // '/tmp/workspace'
145
+ * ```
110
146
  */
111
147
  subcontainer<This extends Container<any, any>>(
112
148
  this: This,
@@ -124,12 +160,12 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
124
160
 
125
161
 
126
162
  /** The observable state object for this container instance. */
127
- get state() {
163
+ get state(): State<ContainerState> {
128
164
  return this._state
129
165
  }
130
166
 
131
167
  /** Returns the list of shortcut IDs for all currently enabled features. */
132
- get enabledFeatureIds() {
168
+ get enabledFeatureIds(): string[] {
133
169
  return this.state.get('enabledFeatures') || []
134
170
  }
135
171
 
@@ -140,18 +176,40 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
140
176
  ) as AvailableInstanceTypes<Features>
141
177
  }
142
178
 
143
- utils = {
144
- hashObject: (obj: any) => hashObject(obj),
145
- get stringUtils() { return stringUtils },
146
- uuid: () => v4(),
147
- lodash: {
148
- uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit,
179
+ /**
180
+ * Common utilities available on every container. Provides UUID generation, object hashing,
181
+ * string case conversion, and lodash helpers — no imports needed.
182
+ *
183
+ * - `utils.uuid()` — generate a v4 UUID
184
+ * - `utils.hashObject(obj)` deterministic hash of any object
185
+ * - `utils.stringUtils` — `{ kebabCase, camelCase, upperFirst, lowerFirst, pluralize, singularize }`
186
+ * - `utils.lodash` — `{ uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit }`
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const id = container.utils.uuid()
191
+ * const hash = container.utils.hashObject({ foo: 'bar' })
192
+ * const name = container.utils.stringUtils.camelCase('my-feature')
193
+ * const unique = container.utils.lodash.uniq([1, 2, 2, 3])
194
+ * ```
195
+ */
196
+ get utils(): ContainerUtils {
197
+ return {
198
+ hashObject: (obj: any) => hashObject(obj),
199
+ get stringUtils() { return stringUtils },
200
+ uuid: () => v4(),
201
+ lodash: {
202
+ uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit,
203
+ }
149
204
  }
150
205
  }
151
206
 
152
207
  private _describer?: ContainerDescriber
153
208
 
154
- /** Lazy-initialized ContainerDescriber for introspecting registries, helpers, and members. */
209
+ /**
210
+ * Lazy-initialized ContainerDescriber for introspecting registries, helpers, and members.
211
+ * @internal
212
+ */
155
213
  get describer(): ContainerDescriber {
156
214
  if (!this._describer) {
157
215
  this._describer = new ContainerDescriber(this)
@@ -159,15 +217,22 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
159
217
  return this._describer
160
218
  }
161
219
 
220
+ addContext<K extends keyof ContainerContext>(key: K, value: ContainerContext[K]): this
221
+ addContext(context: Partial<ContainerContext>): this
162
222
  /**
163
223
  * Add a value to the container's shared context, which is passed to all helper instances.
164
- * Accepts either a key and value, or an object of key-value pairs to add.
224
+ * Accepts either a key and value, or an object of key-value pairs to merge in.
165
225
  *
166
- * @param {K} key - The context key (or object of key-value pairs)
167
- * @param {ContainerContext[K]} value - The context value (omit when passing an object)
226
+ * @param key - The context key, or an object of key-value pairs to merge
227
+ * @param value - The context value (omit when passing an object)
228
+ * @returns The container instance (for chaining)
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * container.addContext('db', dbConnection)
233
+ * container.addContext({ db: dbConnection, cache: redisClient })
234
+ * ```
168
235
  */
169
- addContext<K extends keyof ContainerContext>(key: K, value: ContainerContext[K]): this
170
- addContext(context: Partial<ContainerContext>): this
171
236
  addContext(keyOrContext: keyof ContainerContext | Partial<ContainerContext>, value?: ContainerContext[keyof ContainerContext]): this {
172
237
  if (arguments.length === 1 && typeof keyOrContext === 'object' && keyOrContext !== null) {
173
238
  for (const [k, v] of Object.entries(keyOrContext)) {
@@ -205,17 +270,24 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
205
270
  *
206
271
  * @returns The current state of the container.
207
272
  */
208
- get currentState() {
273
+ get currentState(): ContainerState {
209
274
  return this.state.current
210
275
  }
211
276
 
212
- /**
213
- * Sets the state of the container.
214
- *
215
- * @param newState - The new state of the container.
216
- * @returns The container instance.
277
+ /**
278
+ * Sets the state of the container. Accepts a partial state object to merge, or a function
279
+ * that receives the current state and returns the new state.
280
+ *
281
+ * @param newState - A partial state object to merge, or a function `(current) => newState`
282
+ * @returns The container instance (for chaining)
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * container.setState({ started: true })
287
+ * container.setState((prev) => ({ ...prev, started: true }))
288
+ * ```
217
289
  */
218
- setState(newState: SetStateValue<ContainerState>) {
290
+ setState(newState: SetStateValue<ContainerState>): this {
219
291
  this.state.setState(newState)
220
292
  return this
221
293
  }
@@ -232,26 +304,59 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
232
304
  return State
233
305
  }
234
306
 
307
+ /**
308
+ * The features registry. Use it to check what features are available, look up feature classes,
309
+ * or check if a feature is registered.
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * container.features.available // ['fs', 'git', 'grep', ...]
314
+ * container.features.has('fs') // true
315
+ * container.features.lookup('fs') // FS class
316
+ * ```
317
+ */
235
318
  get features(): FeaturesRegistry {
236
319
  return features
237
320
  }
238
321
 
239
- /**
240
- * Convenience method for creating a new event bus instance.
322
+ /**
323
+ * Create a new standalone event bus instance. Useful when you need a scoped event channel
324
+ * that is independent of the container's own event bus.
325
+ *
326
+ * @returns A new Bus instance
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * const myBus = container.bus()
331
+ * myBus.on('data', (payload) => console.log(payload))
332
+ * myBus.emit('data', { count: 42 })
333
+ * ```
241
334
  */
242
- bus() {
335
+ bus(): Bus {
243
336
  return new Bus()
244
337
  }
245
-
246
- /**
247
- * Convenience method for creating a new observable State object.
338
+
339
+ /**
340
+ * Create a new standalone observable State object. Useful when you need reactive state
341
+ * that is independent of the container's own state.
342
+ *
343
+ * @param initialState - The initial state object (defaults to empty)
344
+ * @returns A new State instance
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * const myState = container.newState({ count: 0, loading: false })
349
+ * myState.observe(() => console.log('Changed:', myState.current))
350
+ * myState.set('count', 1)
351
+ * ```
248
352
  */
249
- newState<T extends object = any>(initialState: T = {} as T) {
353
+ newState<T extends object = any>(initialState: T = {} as T): State<T> {
250
354
  return new State<T>({ initialState })
251
355
  }
252
356
 
253
357
  /**
254
358
  * Parse helper options through the helper's static options schema so defaults are materialized.
359
+ * @internal
255
360
  */
256
361
  normalizeHelperOptions(BaseClass: any, options: any, fallbackName?: string) {
257
362
  const candidate = { ...(options || {}) }
@@ -277,6 +382,7 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
277
382
  throw new Error(`Invalid options for ${target}: ${details || parsed.error.message}`)
278
383
  }
279
384
 
385
+ /** @internal */
280
386
  buildHelperCacheKey(type: string, id: string, options: any, omitOptionKeys: string[] = []) {
281
387
  const hashableOptions = omit(options || {}, uniq(['_cacheKey', ...omitOptionKeys]))
282
388
 
@@ -288,6 +394,7 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
288
394
  })
289
395
  }
290
396
 
397
+ /** @internal */
291
398
  createHelperInstance({
292
399
  cache,
293
400
  type,
@@ -353,17 +460,22 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
353
460
  }) as InstanceType<Features[T]>
354
461
  }
355
462
 
356
- /**
357
- * TODO:
358
- *
359
- * A container should be able to container.use(plugin) and that plugin should be able to define
360
- * an asynchronous method that will be run when the container is started. Right now there's nothing
361
- * to do with starting / stopping a container but that might be neat.
463
+ /**
464
+ * Start the container. Emits the 'started' event and sets `state.started` to true.
465
+ * Plugins and features can listen for this event to perform initialization.
466
+ *
467
+ * @returns The container instance
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * container.on('started', () => console.log('Ready'))
472
+ * await container.start()
473
+ * ```
362
474
  */
363
- async start() {
475
+ async start(): Promise<this> {
364
476
  this.emit('started', this as Container<Features>)
365
477
  this.state.set('started', true)
366
- return this
478
+ return this
367
479
  }
368
480
 
369
481
  /**
@@ -377,80 +489,126 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
377
489
  /**
378
490
  * Returns true if the container is running in a browser.
379
491
  */
380
- get isBrowser() {
492
+ get isBrowser(): boolean {
381
493
  return typeof window !== 'undefined' && typeof document !== 'undefined'
382
494
  }
383
-
384
- /**
495
+
496
+ /**
385
497
  * Returns true if the container is running in Bun.
386
498
  */
387
- get isBun() {
499
+ get isBun(): boolean {
388
500
  return this.isNode && typeof Bun !== 'undefined'
389
501
  }
390
502
 
391
- /**
503
+ /**
392
504
  * Returns true if the container is running in Node.
393
505
  */
394
- get isNode() {
506
+ get isNode(): boolean {
395
507
  return typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.node !== 'undefined'
396
508
  }
397
-
398
- /**
509
+
510
+ /**
399
511
  * Returns true if the container is running in Electron.
400
512
  */
401
- get isElectron() {
513
+ get isElectron(): boolean {
402
514
  return typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined'
403
515
  }
404
-
405
- /**
516
+
517
+ /**
406
518
  * Returns true if the container is running in development mode.
407
519
  */
408
- get isDevelopment() {
520
+ get isDevelopment(): boolean {
409
521
  return typeof process !== 'undefined' && process.env.NODE_ENV === 'development'
410
522
  }
411
-
412
- /**
523
+
524
+ /**
413
525
  * Returns true if the container is running in production mode.
414
526
  */
415
- get isProduction() {
527
+ get isProduction(): boolean {
416
528
  return typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
417
529
  }
418
-
419
- /**
530
+
531
+ /**
420
532
  * Returns true if the container is running in a CI environment.
421
533
  */
422
- get isCI() {
534
+ get isCI(): boolean {
423
535
  return typeof process !== 'undefined' && process.env.CI !== undefined && String(process.env.CI).length > 0
424
536
  }
425
537
 
426
- /** Emit an event on the container's event bus. */
427
- emit(event: string, ...args: any[]) {
538
+ /**
539
+ * Emit an event on the container's event bus.
540
+ *
541
+ * @param event - The event name
542
+ * @param args - Arguments to pass to listeners
543
+ * @returns The container instance (for chaining)
544
+ *
545
+ * @example
546
+ * ```ts
547
+ * container.emit('taskCompleted', { id: 'abc', result: 42 })
548
+ * ```
549
+ */
550
+ emit(event: string, ...args: any[]): this {
428
551
  this._events.emit(event, ...args)
429
552
  return this
430
553
  }
431
554
 
432
- /** Subscribe to an event on the container's event bus. */
433
- on(event: string, listener: (...args: any[]) => void) {
555
+ /**
556
+ * Subscribe to an event on the container's event bus.
557
+ *
558
+ * @param event - The event name
559
+ * @param listener - The callback function
560
+ * @returns The container instance (for chaining)
561
+ *
562
+ * @example
563
+ * ```ts
564
+ * container.on('featureEnabled', (id, feature) => {
565
+ * console.log(`Feature ${id} enabled`)
566
+ * })
567
+ * ```
568
+ */
569
+ on(event: string, listener: (...args: any[]) => void): this {
434
570
  this._events.on(event, listener)
435
571
  return this
436
572
  }
437
573
 
438
- /** Unsubscribe a listener from an event on the container's event bus. */
439
- off(event: string, listener?: (...args: any[]) => void) {
574
+ /**
575
+ * Unsubscribe a listener from an event on the container's event bus.
576
+ *
577
+ * @param event - The event name
578
+ * @param listener - The listener to remove
579
+ * @returns The container instance (for chaining)
580
+ */
581
+ off(event: string, listener?: (...args: any[]) => void): this {
440
582
  this._events.off(event, listener)
441
583
  return this
442
584
  }
443
585
 
444
- /** Subscribe to an event on the container's event bus, but only fire once. */
445
- once(event: string, listener: (...args: any[]) => void) {
586
+ /**
587
+ * Subscribe to an event on the container's event bus, but only fire once.
588
+ *
589
+ * @param event - The event name
590
+ * @param listener - The callback function (invoked at most once)
591
+ * @returns The container instance (for chaining)
592
+ */
593
+ once(event: string, listener: (...args: any[]) => void): this {
446
594
  this._events.once(event, listener)
447
595
  return this
448
596
  }
449
597
 
450
598
  /**
451
- * Returns a promise that will resolve when the event is emitted
599
+ * Returns a promise that resolves the next time the given event is emitted.
600
+ * Useful for awaiting one-time lifecycle transitions.
601
+ *
602
+ * @param event - The event name to wait for
603
+ * @returns A promise that resolves with the event arguments
604
+ *
605
+ * @example
606
+ * ```ts
607
+ * await container.waitFor('started')
608
+ * console.log('Container is ready')
609
+ * ```
452
610
  */
453
- async waitFor(event: string) {
611
+ async waitFor(event: string): Promise<any> {
454
612
  const resp = await this._events.waitFor(event)
455
613
  return resp
456
614
  }
@@ -458,9 +616,7 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
458
616
  /**
459
617
  * Register a helper type (registry + factory pair) on this container.
460
618
  * Called automatically by Helper.attach() methods (e.g. Client.attach, Server.attach).
461
- *
462
- * @param registryName - The plural name of the registry, e.g. "clients", "servers"
463
- * @param factoryName - The singular factory method name, e.g. "client", "server"
619
+ * @internal
464
620
  */
465
621
  registerHelperType(registryName: string, factoryName: string) {
466
622
  const registries = uniq([...this.state.get('registries')!, registryName])
@@ -484,7 +640,15 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
484
640
  * Returns a full introspection object for this container, merging build-time AST data
485
641
  * (JSDoc descriptions, methods, getters) with runtime data (registries, factories, state, environment).
486
642
  *
487
- * @returns {ContainerIntrospection} The complete introspection data
643
+ * @returns The complete introspection data as a structured object
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * const info = container.inspect()
648
+ * console.log(info.methods) // all public methods with descriptions
649
+ * console.log(info.getters) // all getters with return types
650
+ * console.log(info.registries) // features, clients, servers, etc.
651
+ * ```
488
652
  */
489
653
  inspect(): ContainerIntrospection {
490
654
  const className = this.constructor.name
@@ -550,12 +714,18 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
550
714
 
551
715
  /**
552
716
  * Returns a human-readable markdown representation of this container's introspection data.
553
- * Useful in REPLs, AI agent contexts, or documentation generation.
717
+ * Useful in REPLs, AI agent contexts, or documentation generation. Pass a section name
718
+ * to render only that section (e.g. 'methods', 'getters', 'events', 'state').
554
719
  *
555
- * The first argument can be a section name (`'methods'`, `'getters'`, etc.) to render only
556
- * that section, or a number for the starting heading depth (backward compatible).
720
+ * @param sectionOrDepth - A section name to render, or heading depth number
721
+ * @param startHeadingDepth - Starting markdown heading depth (default 1)
722
+ * @returns Markdown-formatted introspection text
557
723
  *
558
- * @returns {string} Markdown-formatted introspection text
724
+ * @example
725
+ * ```ts
726
+ * console.log(container.inspectAsText()) // full description
727
+ * console.log(container.inspectAsText('methods')) // just methods
728
+ * ```
559
729
  */
560
730
  inspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): string {
561
731
  let section: IntrospectionSection | undefined
@@ -572,15 +742,37 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
572
742
  return presentContainerIntrospectionAsMarkdown(data, depth, section)
573
743
  }
574
744
 
575
- /** Alias for inspectAsText */
745
+ /** Alias for inspectAsText. */
576
746
  introspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): string {
577
747
  return this.inspectAsText(sectionOrDepth, startHeadingDepth)
578
748
  }
579
749
 
580
- /** Alias for inspectAsJSON */
581
- introspectAsJSON(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): any {
750
+ /** Alias for inspect, returns JSON introspection data. */
751
+ introspectAsJSON(): ContainerIntrospection {
752
+ return this.inspect()
753
+ }
754
+
755
+ /**
756
+ * Returns the container's introspection data formatted as a TypeScript interface declaration.
757
+ * Includes the container's own methods, getters, factories, and registered helper types.
758
+ *
759
+ * @example
760
+ * ```ts
761
+ * console.log(container.inspectAsType())
762
+ * // interface NodeContainer {
763
+ * // feature<T>(id: string, options?: object): T;
764
+ * // readonly uuid: string;
765
+ * // ...
766
+ * // }
767
+ * ```
768
+ */
769
+ inspectAsType(): string {
770
+ return this.introspectAsType()
771
+ }
772
+
773
+ introspectAsType(): string {
582
774
  const data = this.inspect()
583
- return presentContainerIntrospectionAsJSON(data, depth, section)
775
+ return presentContainerIntrospectionAsTypeScript(data)
584
776
  }
585
777
 
586
778
  /** Make a property non-enumerable, which is nice for inspecting it in the REPL */
@@ -593,7 +785,7 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
593
785
  }
594
786
 
595
787
  /** Sleep for the specified number of milliseconds. Useful for scripting and sequencing. */
596
- async sleep(ms = 1000) {
788
+ async sleep(ms: number = 1000): Promise<this> {
597
789
  await new Promise((res) => setTimeout(res,ms))
598
790
  return this
599
791
  }
@@ -601,10 +793,22 @@ export class Container<Features extends AvailableFeatures = AvailableFeatures, C
601
793
  _plugins: (() => void)[] = []
602
794
 
603
795
  /**
604
- * Apply a plugin or enable a feature by string name. Plugins must have a static attach(container) method.
796
+ * Apply a plugin or enable a feature by string name. Plugins are classes with a static `attach(container)` method
797
+ * that extend the container with new registries, factories, or capabilities.
798
+ *
799
+ * @param plugin - A feature name string, or a class/object with a static attach method
800
+ * @param options - Options to pass to the plugin's attach method
801
+ * @returns The container instance (with the plugin's type merged in)
605
802
  *
606
- * @param {Extension<T>} plugin - A feature name string, or a class/object with a static attach method
607
- * @param {any} options - Options to pass to the plugin's attach method
803
+ * @example
804
+ * ```ts
805
+ * // Enable a feature by name
806
+ * container.use('contentDb')
807
+ *
808
+ * // Attach a plugin class (e.g. Client, Server, or custom)
809
+ * container.use(Client) // registers the clients registry + client() factory
810
+ * container.use(Server) // registers the servers registry + server() factory
811
+ * ```
608
812
  */
609
813
  use<T = {}>(plugin: Extension<T>, options: any = {}) : this & T {
610
814
  const container = this
@@ -640,20 +844,8 @@ function presentContainerIntrospectionAsMarkdown(data: ContainerIntrospection, s
640
844
  // Header
641
845
  sections.push(`${heading(1)} ${data.className}\n\n${data.description || ''}`)
642
846
 
643
- // Container Properties section (node containers expose these top-level getters)
644
- if (data.environment?.isNode || data.environment?.isBun) {
645
- sections.push([
646
- `${heading(2)} Container Properties`,
647
- '',
648
- '| Property | Description |',
649
- '|----------|-------------|',
650
- '| `container.cwd` | Current working directory |',
651
- '| `container.paths` | Path utilities scoped to cwd: `resolve()`, `join()`, `relative()`, `dirname()`, `basename()`, `parse()` |',
652
- '| `container.manifest` | Parsed `package.json` for the current directory (`name`, `version`, `dependencies`, etc.) |',
653
- '| `container.argv` | Raw parsed CLI arguments (from minimist). Prefer `positionals` export for positional args in commands |',
654
- '| `container.utils` | Common utilities: `uuid()`, `hashObject()`, `stringUtils`, `lodash` |',
655
- ].join('\n'))
656
- }
847
+ // Container Properties section dynamic from getters data, not hardcoded
848
+ // (cwd, paths, manifest, argv, utils etc. come through as getters from the introspection scanner)
657
849
 
658
850
  // Registries section
659
851
  if (data.registries && data.registries.length > 0) {
@@ -718,6 +910,15 @@ function presentContainerIntrospectionAsMarkdown(data: ContainerIntrospection, s
718
910
  sections.push(`**Returns:** \`${methodInfo.returns}\``)
719
911
  }
720
912
 
913
+ if ((methodInfo as any).examples && (methodInfo as any).examples.length > 0) {
914
+ for (const example of (methodInfo as any).examples) {
915
+ if (example.title) {
916
+ sections.push(`**Example: ${example.title}**`)
917
+ }
918
+ sections.push(`\`\`\`${example.language || 'ts'}\n${example.code}\n\`\`\``)
919
+ }
920
+ }
921
+
721
922
  sections.push('')
722
923
  }
723
924
  }
@@ -731,10 +932,27 @@ function presentContainerIntrospectionAsMarkdown(data: ContainerIntrospection, s
731
932
  `|----------|------|-------------|`,
732
933
  ]
733
934
 
935
+ const gettersWithExamples: [string, any][] = []
936
+
734
937
  for (const [getterName, getterInfo] of Object.entries(data.getters)) {
735
- getterTableRows.push(`| \`${getterName}\` | \`${getterInfo.returns || 'any'}\` | ${getterInfo.description || ''} |`)
938
+ // Truncate long descriptions in the table
939
+ const desc = getterInfo.description || ''
940
+ const shortDesc = desc.length > 120 ? desc.slice(0, 117) + '...' : desc
941
+ getterTableRows.push(`| \`${getterName}\` | \`${getterInfo.returns || 'any'}\` | ${shortDesc} |`)
942
+ if ((getterInfo as any).examples && (getterInfo as any).examples.length > 0) {
943
+ gettersWithExamples.push([getterName, getterInfo])
944
+ }
736
945
  }
737
946
  sections.push(getterTableRows.join('\n'))
947
+
948
+ // Render examples for getters that have them
949
+ if (gettersWithExamples.length > 0) {
950
+ for (const [getterName, getterInfo] of gettersWithExamples) {
951
+ for (const example of (getterInfo as any).examples) {
952
+ sections.push(`\`\`\`${example.language || 'ts'}\n${example.code}\n\`\`\``)
953
+ }
954
+ }
955
+ }
738
956
  }
739
957
 
740
958
  // Events section
@@ -805,3 +1023,83 @@ function presentContainerIntrospectionAsMarkdown(data: ContainerIntrospection, s
805
1023
 
806
1024
  return sections.join('\n\n')
807
1025
  }
1026
+
1027
+ function presentContainerIntrospectionAsTypeScript(data: ContainerIntrospection): string {
1028
+ const members: string[] = []
1029
+
1030
+ // Getters
1031
+ if (data.getters && Object.keys(data.getters).length > 0) {
1032
+ for (const [name, info] of Object.entries(data.getters)) {
1033
+ if (info.description) {
1034
+ members.push(` /** ${info.description} */`)
1035
+ }
1036
+ members.push(` readonly ${name}: ${normalizeTypeString(info.returns || 'any')};`)
1037
+ }
1038
+ }
1039
+
1040
+ // Methods
1041
+ if (data.methods && Object.keys(data.methods).length > 0) {
1042
+ if (members.length > 0) members.push('')
1043
+ for (const [name, info] of Object.entries(data.methods)) {
1044
+ if (info.description) {
1045
+ members.push(` /** ${info.description} */`)
1046
+ }
1047
+ const params = renderTypeScriptParams(info)
1048
+ members.push(` ${name}(${params}): ${normalizeTypeString(info.returns || 'void')};`)
1049
+ }
1050
+ }
1051
+
1052
+ // Factory methods
1053
+ if (data.factories && data.factories.length > 0) {
1054
+ if (members.length > 0) members.push('')
1055
+ members.push(' // Factory methods')
1056
+ for (const factory of data.factories) {
1057
+ members.push(` ${factory}(id: string, options?: Record<string, any>): any;`)
1058
+ }
1059
+ }
1060
+
1061
+ // Registries
1062
+ if (data.registries && data.registries.length > 0) {
1063
+ if (members.length > 0) members.push('')
1064
+ members.push(' // Registries')
1065
+ for (const reg of data.registries) {
1066
+ const available = reg.available.length > 0
1067
+ ? reg.available.map(a => `'${a}'`).join(' | ')
1068
+ : 'string'
1069
+ members.push(` readonly ${reg.name}: { available: (${available})[]; lookup(id: string): any; };`)
1070
+ }
1071
+ }
1072
+
1073
+ // Events — as on() overloads
1074
+ if (data.events && Object.keys(data.events).length > 0) {
1075
+ if (members.length > 0) members.push('')
1076
+ for (const [eventName, eventInfo] of Object.entries(data.events)) {
1077
+ const args = Object.entries(eventInfo.arguments || {})
1078
+ const listenerParams = args.length > 0
1079
+ ? args.map(([argName, argInfo]) => `${argName}: ${normalizeTypeString(argInfo.type || 'any')}`).join(', ')
1080
+ : ''
1081
+ if (eventInfo.description) {
1082
+ members.push(` /** ${eventInfo.description} */`)
1083
+ }
1084
+ members.push(` on(event: '${eventName}', listener: (${listenerParams}) => void): this;`)
1085
+ }
1086
+ }
1087
+
1088
+ // State
1089
+ if (data.state && Object.keys(data.state).length > 0) {
1090
+ if (members.length > 0) members.push('')
1091
+ const stateMembers = Object.entries(data.state)
1092
+ .map(([name, info]) => {
1093
+ const comment = info.description ? ` /** ${info.description} */\n` : ''
1094
+ return `${comment} ${name}: ${normalizeTypeString(info.type || 'any')};`
1095
+ })
1096
+ .join('\n')
1097
+ members.push(` state: {\n${stateMembers}\n };`)
1098
+ }
1099
+
1100
+ const description = data.description
1101
+ ? `/**\n * ${data.description.split('\n').join('\n * ')}\n */\n`
1102
+ : ''
1103
+
1104
+ return `${description}interface ${data.className} {\n${members.join('\n')}\n}`
1105
+ }