@soederpop/luca 0.0.25 → 0.0.26

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.
@@ -1,7 +1,7 @@
1
1
  import { setBuildTimeData, setContainerBuildTimeData } from './index.js';
2
2
 
3
3
  // Auto-generated introspection registry data
4
- // Generated at: 2026-03-22T06:53:17.219Z
4
+ // Generated at: 2026-03-22T20:57:23.113Z
5
5
 
6
6
  setBuildTimeData('features.containerLink', {
7
7
  "id": "features.containerLink",
@@ -982,6 +982,10 @@ setContainerBuildTimeData('Container', {
982
982
  "description": "Returns a map of enabled feature shortcut IDs to their instances.",
983
983
  "returns": "Partial<AvailableInstanceTypes<Features>>"
984
984
  },
985
+ "describer": {
986
+ "description": "Lazy-initialized ContainerDescriber for introspecting registries, helpers, and members.",
987
+ "returns": "ContainerDescriber"
988
+ },
985
989
  "context": {
986
990
  "description": "The Container's context is an object that contains the enabled features, the container itself, and any additional context that has been added to the container. All helper instances that are created by the container will have access to the shared context.",
987
991
  "returns": "ContainerContext<Features> & Partial<AvailableInstanceTypes<AvailableFeatures>>"
@@ -2029,6 +2033,10 @@ export const containerIntrospectionData = [
2029
2033
  "description": "Returns a map of enabled feature shortcut IDs to their instances.",
2030
2034
  "returns": "Partial<AvailableInstanceTypes<Features>>"
2031
2035
  },
2036
+ "describer": {
2037
+ "description": "Lazy-initialized ContainerDescriber for introspecting registries, helpers, and members.",
2038
+ "returns": "ContainerDescriber"
2039
+ },
2032
2040
  "context": {
2033
2041
  "description": "The Container's context is an object that contains the enabled features, the container itself, and any additional context that has been added to the container. All helper instances that are created by the container will have access to the shared context.",
2034
2042
  "returns": "ContainerContext<Features> & Partial<AvailableInstanceTypes<AvailableFeatures>>"
@@ -428,6 +428,23 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
428
428
  async generateModelSummary(options: any) {
429
429
  return this.collection.generateModelSummary(options)
430
430
  }
431
+
432
+ get modelDefinitionTable() {
433
+ return Object.fromEntries(this.collection.modelDefinitions.map(d => {
434
+
435
+ const prefixPattern = this.container.paths.relative(this.collection.resolve(d.prefix))
436
+
437
+ return [d.name, {
438
+ description: d.name === 'Base' ? 'Any markdown document not matched to a model' : d.description,
439
+ glob: `${prefixPattern}/**/*.md`,
440
+ routePatterns: Array(d.pattern).flatMap(p => p).filter(Boolean).map(p => `${prefixPattern}/${p}`)
441
+ }]
442
+ }))
443
+ }
444
+
445
+ get fileTree() {
446
+ return this.collection.renderFileTree()
447
+ }
431
448
 
432
449
  // ── Search Integration ─────────────────────────────────────────────
433
450
 
@@ -88,6 +88,16 @@ export class FS extends Feature {
88
88
  return readFileSync(filePath, encoding ?? 'utf-8')
89
89
  }
90
90
 
91
+ /**
92
+ * Synchronously reads a file and returns its contents as a string.
93
+ * added this method because AI Assistants are understandly confused by this deviation from 2000's era node style
94
+ * @alias readFile
95
+ */
96
+ readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer {
97
+ return this.readFile(path,encoding)
98
+ }
99
+
100
+
91
101
  /**
92
102
  * Asynchronously reads a file and returns its contents as a string.
93
103
  *
@@ -126,6 +136,14 @@ export class FS extends Feature {
126
136
  readJson(path: string) {
127
137
  return JSON.parse(this.readFile(path) as string)
128
138
  }
139
+
140
+ /**
141
+ * Read and parse a JSON file synchronously
142
+ * @alias readJson
143
+ */
144
+ readJsonSync(path: string) {
145
+ return this.readJson(path)
146
+ }
129
147
 
130
148
  /**
131
149
  * Asynchronously reads and parses a JSON file.
@@ -1,5 +1,5 @@
1
1
  // Auto-generated scaffold and MCP readme content
2
- // Generated at: 2026-03-22T06:53:18.105Z
2
+ // Generated at: 2026-03-22T20:57:24.026Z
3
3
  // Source: docs/scaffolds/*.md, docs/examples/assistant/, and docs/mcp/readme.md
4
4
  //
5
5
  // Do not edit manually. Run: luca build-scaffolds
package/src/server.ts CHANGED
@@ -79,6 +79,44 @@ export class Server<T extends ServerState = ServerState, K extends ServerOptions
79
79
  return super.container as NodeContainer
80
80
  }
81
81
 
82
+ /** Async functions passed to `.use()` before `start()` — drained in `start()`. */
83
+ _pendingPlugins: Promise<void>[] = []
84
+
85
+ /**
86
+ * Register a setup function against this server. The function receives the
87
+ * server instance and can configure it however it likes.
88
+ *
89
+ * - If the server hasn't started yet, async return values are queued and
90
+ * awaited when `start()` is called.
91
+ * - If the server is already listening, the function is fire-and-forget.
92
+ * - Always returns `this` synchronously for chaining.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * server
97
+ * .use((s) => s.app.use(cors()))
98
+ * .use(async (s) => { await loadRoutes(s) })
99
+ * await server.start()
100
+ * ```
101
+ */
102
+ use(fn: (server: this) => void | Promise<void>): this {
103
+ const result = fn(this)
104
+ if (result && typeof (result as any).then === 'function') {
105
+ if (!this.isListening) {
106
+ this._pendingPlugins.push(result as Promise<void>)
107
+ }
108
+ }
109
+ return this
110
+ }
111
+
112
+ /** Drain all pending `.use()` promises. Subclasses should call this at the top of `start()`. */
113
+ protected async _drainPendingPlugins() {
114
+ if (this._pendingPlugins.length) {
115
+ await Promise.all(this._pendingPlugins)
116
+ this._pendingPlugins = []
117
+ }
118
+ }
119
+
82
120
  get isListening() {
83
121
  return !!this.state.get('listening')
84
122
  }
@@ -117,6 +155,8 @@ export class Server<T extends ServerState = ServerState, K extends ServerOptions
117
155
  return this
118
156
  }
119
157
 
158
+ await this._drainPendingPlugins()
159
+
120
160
  if (options?.port) {
121
161
  this.state.set('port', options.port)
122
162
  }
@@ -104,6 +104,8 @@ export class ExpressServer<T extends ServerState = ServerState, K extends Expres
104
104
  return this
105
105
  }
106
106
 
107
+ await this._drainPendingPlugins()
108
+
107
109
  // Apply runtime port to state so this.port reflects the override
108
110
  if (options?.port) {
109
111
  this.state.set('port', options.port)
@@ -472,6 +472,7 @@ export class MCPServer extends Server<MCPServerState, MCPServerOptions> {
472
472
  stdioCompat?: StdioCompatMode
473
473
  }) {
474
474
  if (this.isListening) return this
475
+ await this._drainPendingPlugins()
475
476
  if (!this.isConfigured) await this.configure()
476
477
 
477
478
  const transport = options?.transport || this.options.transport || 'stdio'
@@ -89,6 +89,8 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
89
89
  return this
90
90
  }
91
91
 
92
+ await this._drainPendingPlugins()
93
+
92
94
  // Apply runtime port to state before configure/wss touches it
93
95
  if (options?.port) {
94
96
  this.state.set('port', options.port)