@soederpop/luca 0.0.3 → 0.0.4

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 (113) hide show
  1. package/AGENTS.md +98 -0
  2. package/CLAUDE.md +27 -0
  3. package/SPEC.md +304 -0
  4. package/bun.lock +110 -265
  5. package/docs/CLI.md +1 -1
  6. package/docs/apis/features/node/content-db.md +16 -0
  7. package/docs/apis/features/node/fs.md +24 -0
  8. package/docs/apis/features/node/ipc-socket.md +0 -1
  9. package/docs/apis/features/node/package-finder.md +1 -11
  10. package/docs/apis/features/node/proc.md +0 -41
  11. package/docs/apis/features/node/ui.md +0 -2
  12. package/package.json +12 -8
  13. package/src/agi/container.server.ts +16 -3
  14. package/src/agi/features/assistant.ts +3 -7
  15. package/src/agi/features/assistants-manager.ts +3 -7
  16. package/src/agi/features/claude-code.ts +3 -7
  17. package/src/agi/features/conversation-history.ts +3 -7
  18. package/src/agi/features/conversation.ts +4 -8
  19. package/src/agi/features/openai-codex.ts +3 -7
  20. package/src/agi/features/openapi.ts +4 -2
  21. package/src/agi/features/skills-library.ts +4 -8
  22. package/src/cli/cli.ts +22 -0
  23. package/src/client.ts +69 -26
  24. package/src/clients/civitai/index.ts +3 -7
  25. package/src/clients/comfyui/index.ts +5 -9
  26. package/src/clients/elevenlabs/index.ts +39 -19
  27. package/src/clients/openai/index.ts +3 -7
  28. package/src/clients/supabase/index.ts +4 -13
  29. package/src/commands/console.ts +0 -3
  30. package/src/commands/eval.ts +1 -1
  31. package/src/commands/index.ts +1 -0
  32. package/src/commands/introspect.ts +128 -0
  33. package/src/commands/prompt.ts +1 -4
  34. package/src/commands/run.ts +6 -13
  35. package/src/commands/sandbox-mcp.ts +1 -13
  36. package/src/feature.ts +45 -2
  37. package/src/introspection/generated.agi.ts +175 -101
  38. package/src/introspection/generated.node.ts +175 -101
  39. package/src/introspection/generated.web.ts +113 -29
  40. package/src/introspection/index.ts +1 -1
  41. package/src/introspection/scan.ts +3 -1
  42. package/src/node/features/container-link.ts +3 -2
  43. package/src/node/features/content-db.ts +10 -2
  44. package/src/node/features/disk-cache.ts +3 -4
  45. package/src/node/features/dns.ts +3 -2
  46. package/src/node/features/docker.ts +3 -2
  47. package/src/node/features/downloader.ts +3 -16
  48. package/src/node/features/esbuild.ts +3 -12
  49. package/src/node/features/file-manager.ts +3 -2
  50. package/src/node/features/fs.ts +12 -3
  51. package/src/node/features/git.ts +3 -2
  52. package/src/node/features/google-auth.ts +3 -2
  53. package/src/node/features/google-calendar.ts +3 -2
  54. package/src/node/features/google-docs.ts +3 -2
  55. package/src/node/features/google-drive.ts +3 -2
  56. package/src/node/features/google-sheets.ts +3 -2
  57. package/src/node/features/grep.ts +3 -2
  58. package/src/node/features/helpers.ts +13 -2
  59. package/src/node/features/ink.ts +3 -3
  60. package/src/node/features/ipc-socket.ts +3 -3
  61. package/src/node/features/json-tree.ts +3 -21
  62. package/src/node/features/launcher-app-command-listener.ts +3 -2
  63. package/src/node/features/networking.ts +3 -2
  64. package/src/node/features/nlp.ts +3 -2
  65. package/src/node/features/opener.ts +8 -7
  66. package/src/node/features/os.ts +3 -2
  67. package/src/node/features/package-finder.ts +3 -2
  68. package/src/node/features/port-exposer.ts +3 -4
  69. package/src/node/features/postgres.ts +3 -3
  70. package/src/node/features/proc.ts +37 -64
  71. package/src/node/features/process-manager.ts +3 -2
  72. package/src/node/features/python.ts +3 -3
  73. package/src/node/features/repl.ts +3 -2
  74. package/src/node/features/runpod.ts +3 -3
  75. package/src/node/features/secure-shell.ts +3 -2
  76. package/src/node/features/semantic-search.ts +4 -6
  77. package/src/node/features/sqlite.ts +3 -3
  78. package/src/node/features/telegram.ts +3 -2
  79. package/src/node/features/tts.ts +3 -2
  80. package/src/node/features/ui.ts +3 -3
  81. package/src/node/features/vault.ts +3 -14
  82. package/src/node/features/vm.ts +41 -3
  83. package/src/node/features/window-manager.ts +165 -22
  84. package/src/node/features/yaml-tree.ts +3 -4
  85. package/src/node/features/yaml.ts +3 -2
  86. package/src/registry.ts +1 -1
  87. package/src/scaffolds/generated.ts +1 -1
  88. package/src/server.ts +43 -0
  89. package/src/servers/express.ts +24 -8
  90. package/src/servers/mcp.ts +2 -6
  91. package/src/servers/socket.ts +22 -7
  92. package/src/web/clients/socket.ts +3 -5
  93. package/src/web/features/asset-loader.ts +20 -12
  94. package/src/web/features/container-link.ts +3 -6
  95. package/src/web/features/esbuild.ts +21 -7
  96. package/src/web/features/helpers.ts +4 -2
  97. package/src/web/features/network.ts +24 -7
  98. package/src/web/features/speech.ts +24 -7
  99. package/src/web/features/vault.ts +21 -3
  100. package/src/web/features/vm.ts +20 -13
  101. package/src/web/features/voice-recognition.ts +26 -9
  102. package/commands/update-introspection.ts +0 -67
  103. package/docs/ideas/class-registration-refactor-possibilities.md +0 -197
  104. package/docs/ideas/container-use-api.md +0 -9
  105. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  106. package/docs/ideas/feature-stacks.md +0 -22
  107. package/docs/ideas/luca-cli-self-sufficiency-demo.md +0 -23
  108. package/docs/ideas/mcp-design.md +0 -9
  109. package/docs/ideas/web-container-debugging-feature.md +0 -13
  110. package/scripts/animations/chrome-glitch.ts +0 -55
  111. package/scripts/animations/index.ts +0 -16
  112. package/scripts/animations/neon-pulse.ts +0 -64
  113. package/scripts/animations/types.ts +0 -6
package/docs/CLI.md CHANGED
@@ -34,7 +34,7 @@ Run a script or markdown file (.ts, .js, .md).
34
34
  luca run <file> [options]
35
35
  ```
36
36
 
37
- Resolves the file by trying the path as-is, then appending `.ts`, `.js`, `.md` in order. Markdown files are executed block-by-block with the container in scope. TypeScript and JavaScript files are run as standalone scripts via `proc.runScript`.
37
+ Resolves the file by trying the path as-is, then appending `.ts`, `.js`, `.md` in order. Markdown files are executed block-by-block with the container in scope. TypeScript and JavaScript files are run as standalone scripts via `proc.execAndCapture`.
38
38
 
39
39
  | Flag | Type | Default | Description |
40
40
  |------|------|---------|-------------|
@@ -72,6 +72,14 @@ console.log(contentDb.isLoaded) // true
72
72
 
73
73
 
74
74
 
75
+ ### reload
76
+
77
+ Force-reload the collection from disk, picking up new/changed/deleted documents.
78
+
79
+ **Returns:** `Promise<ContentDb>`
80
+
81
+
82
+
75
83
  ### read
76
84
 
77
85
  Read a single document by its path ID, optionally filtering to specific sections. The document title (H1) is always included in the output. When using `include`, the leading content (paragraphs between the H1 and first H2) is also included by default, controlled by the `leadingContent` option. When `include` is provided, only those sections are returned (via extractSections in flat mode). When `exclude` is provided, those sections are removed from the full document. If both are set, `include` takes precedence.
@@ -232,6 +240,14 @@ Rebuild the entire search index from scratch.
232
240
  | `searchIndexStatus` | `any` | Get the current search index status. |
233
241
  | `queries` | `Record<string, ReturnType<typeof this.query>>` | Returns an object with query builders keyed by model name (singular and plural, lowercased). Provides a convenient shorthand for querying without looking up model definitions manually. |
234
242
 
243
+ ## Events (Zod v4 schema)
244
+
245
+ ### reloaded
246
+
247
+ Event emitted by ContentDb
248
+
249
+
250
+
235
251
  ## State (Zod v4 schema)
236
252
 
237
253
  | Property | Type | Description |
@@ -206,6 +206,18 @@ fs.ensureFolder('logs/debug')
206
206
 
207
207
 
208
208
 
209
+ ### mkdirp
210
+
211
+ **Parameters:**
212
+
213
+ | Name | Type | Required | Description |
214
+ |------|------|----------|-------------|
215
+ | `folder` | `string` | ✓ | Parameter folder |
216
+
217
+ **Returns:** `void`
218
+
219
+
220
+
209
221
  ### ensureFile
210
222
 
211
223
  Synchronously ensures a file exists with the specified content, creating directories as needed.
@@ -295,6 +307,18 @@ if (fs.exists('config.json')) {
295
307
 
296
308
 
297
309
 
310
+ ### existsSync
311
+
312
+ **Parameters:**
313
+
314
+ | Name | Type | Required | Description |
315
+ |------|------|----------|-------------|
316
+ | `path` | `string` | ✓ | Parameter path |
317
+
318
+ **Returns:** `boolean`
319
+
320
+
321
+
298
322
  ### rm
299
323
 
300
324
  Asynchronously removes a file.
@@ -169,7 +169,6 @@ Event emitted by IpcSocket
169
169
  | Property | Type | Description |
170
170
  |----------|------|-------------|
171
171
  | `enabled` | `boolean` | Whether this feature is currently enabled |
172
- | `mode` | `string` | The current mode of the IPC socket - either server or client |
173
172
 
174
173
  ## Examples
175
174
 
@@ -5,18 +5,9 @@ PackageFinder Feature - Comprehensive package discovery and analysis tool This f
5
5
  ## Usage
6
6
 
7
7
  ```ts
8
- container.feature('packageFinder', {
9
- // Optional configuration parameter
10
- option,
11
- })
8
+ container.feature('packageFinder')
12
9
  ```
13
10
 
14
- ## Options (Zod v4 schema)
15
-
16
- | Property | Type | Description |
17
- |----------|------|-------------|
18
- | `option` | `string` | Optional configuration parameter |
19
-
20
11
  ## Methods
21
12
 
22
13
  ### afterInitialize
@@ -237,7 +228,6 @@ const unscoped = finder.exclude(pkg => pkg.name.startsWith('@'));
237
228
  | Property | Type | Description |
238
229
  |----------|------|-------------|
239
230
  | `enabled` | `boolean` | Whether this feature is currently enabled |
240
- | `started` | `boolean` | Whether the package finder has been started and initial scan completed |
241
231
 
242
232
  ## Examples
243
233
 
@@ -130,36 +130,6 @@ Spawn a raw child process and return the handle immediately. Useful when callers
130
130
 
131
131
 
132
132
 
133
- ### runScript
134
-
135
- Runs a script file with Bun, inheriting stdout for full TTY passthrough (animations, colors, cursor movement) while capturing stderr in a rolling buffer.
136
-
137
- **Parameters:**
138
-
139
- | Name | Type | Required | Description |
140
- |------|------|----------|-------------|
141
- | `scriptPath` | `string` | ✓ | Absolute path to the script file |
142
- | `options` | `{ cwd?: string; maxLines?: number; env?: Record<string, string> }` | | Options |
143
-
144
- `{ cwd?: string; maxLines?: number; env?: Record<string, string> }` properties:
145
-
146
- | Property | Type | Description |
147
- |----------|------|-------------|
148
- | `cwd` | `any` | Working directory |
149
- | `maxLines` | `any` | Max stderr lines to keep |
150
- | `env` | `any` | Extra environment variables |
151
-
152
- **Returns:** `Promise<{ exitCode: number; stderr: string[] }>`
153
-
154
- ```ts
155
- const { exitCode, stderr } = await proc.runScript('/path/to/script.ts')
156
- if (exitCode !== 0) {
157
- console.log('Error:', stderr.join('\n'))
158
- }
159
- ```
160
-
161
-
162
-
163
133
  ### exec
164
134
 
165
135
  Execute a command synchronously and return its output. Runs a shell command and waits for it to complete before returning. Useful for simple commands where you need the result immediately.
@@ -354,17 +324,6 @@ const buildResult = await proc.spawnAndCapture('npm', ['run', 'build'], {
354
324
 
355
325
 
356
326
 
357
- **runScript**
358
-
359
- ```ts
360
- const { exitCode, stderr } = await proc.runScript('/path/to/script.ts')
361
- if (exitCode !== 0) {
362
- console.log('Error:', stderr.join('\n'))
363
- }
364
- ```
365
-
366
-
367
-
368
327
  **exec**
369
328
 
370
329
  ```ts
@@ -417,8 +417,6 @@ const menuItem = ui.padRight('Coffee', 20, '.') + '$3.50';
417
417
  | Property | Type | Description |
418
418
  |----------|------|-------------|
419
419
  | `enabled` | `boolean` | Whether this feature is currently enabled |
420
- | `fonts` | `array` | Array of available fonts for ASCII art generation |
421
- | `colorPalette` | `array` | Color palette of hex colors for automatic color assignment |
422
420
 
423
421
  ## Examples
424
422
 
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@soederpop/luca",
3
- "version": "0.0.3",
4
- "private": false,
5
- "description": "lightweight universal conversational architecture",
6
- "author": "jon soeder <jon@soederpop.com>",
3
+ "version": "0.0.4",
4
+ "website": "https://luca.soederpop.com",
5
+ "description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
6
+ "author": "jon soeder aka the people's champ <jon@soederpop.com>",
7
7
  "type": "module",
8
8
  "repository": "https://github.com/soederpop/luca",
9
9
  "license": "MIT",
@@ -12,7 +12,11 @@
12
12
  },
13
13
  "keywords": [
14
14
  "conversational",
15
- "architecture"
15
+ "architecture",
16
+ "universal",
17
+ "ai",
18
+ "agentic",
19
+ "coding assistant"
16
20
  ],
17
21
  "main": "./src/node.ts",
18
22
  "types": "./src/node.ts",
@@ -31,7 +35,8 @@
31
35
  "./client": "./src/client.ts",
32
36
  "./clients/*": "./src/clients/*",
33
37
  "./feature": "./src/feature.ts",
34
- "./react": "./src/react/index.ts"
38
+ "./react": "./src/react/index.ts",
39
+ "./introspection": "./src/introspection/index.ts"
35
40
  },
36
41
  "bin": {
37
42
  "luca": "./src/cli/cli.ts"
@@ -42,7 +47,7 @@
42
47
  "lint": "eslint src/ --ext .js,.jsx,.ts,.tsx",
43
48
  "clean": "rm -rf dist build package",
44
49
  "build:web": "bun run scripts/build-web.ts",
45
- "build:introspection": "bun run src/cli/cli.ts update-introspection",
50
+ "build:introspection": "bun run src/cli/cli.ts introspect",
46
51
  "compile": "bun build ./src/cli/cli.ts --compile --outfile dist/luca --external node-llama-cpp",
47
52
  "typecheck": "tsc -p tsconfig.json --noEmit",
48
53
  "build:scaffolds": "bun run src/cli/cli.ts build-scaffolds",
@@ -98,7 +103,6 @@
98
103
  "compromise": "^14.14.5",
99
104
  "contentbase": "",
100
105
  "cors": "^2.8.5",
101
- "cross-fetch": "^4.1.0",
102
106
  "detect-port": "^1.5.1",
103
107
  "dotenv": "^17.2.4",
104
108
  "endent": "^2.1.0",
@@ -1,5 +1,5 @@
1
- import type { ContainerState } from '@soederpop/luca/container'
2
- import { type NodeFeatures, NodeContainer } from '@soederpop/luca/node/container'
1
+ import type { ContainerState } from '../container'
2
+ import { type NodeFeatures, NodeContainer } from '../node/container'
3
3
  import '@/introspection/generated.agi.js'
4
4
  import { OpenAIClient } from '../clients/openai'
5
5
  import { ElevenLabsClient } from '../clients/elevenlabs'
@@ -38,6 +38,16 @@ export type {
38
38
  NodeFeatures,
39
39
  }
40
40
 
41
+ export interface AGIFeatures extends NodeFeatures {
42
+ conversation: typeof Conversation
43
+ claudeCode: typeof ClaudeCode
44
+ openaiCodex: typeof OpenAICodex
45
+ skillsLibrary: typeof SkillsLibrary
46
+ conversationHistory: typeof ConversationHistory
47
+ assistant: typeof Assistant
48
+ assistantsManager: typeof AssistantsManager
49
+ }
50
+
41
51
  export interface ConversationFactoryOptions {
42
52
  tools?: {
43
53
  handlers: Record<string, ConversationTool['handler']>
@@ -56,7 +66,10 @@ export interface ConversationFactoryOptions {
56
66
  * AGI-specific container that extends NodeContainer with AI capabilities including
57
67
  * OpenAI conversations, code generation, and self-modifying agent features.
58
68
  */
59
- export class AGIContainer extends NodeContainer {
69
+ export class AGIContainer<
70
+ Features extends AGIFeatures = AGIFeatures,
71
+ K extends ContainerState = ContainerState
72
+ > extends NodeContainer<Features, K> {
60
73
  openai!: OpenAIClient
61
74
  claudeCode?: ClaudeCode
62
75
  openaiCodex?: OpenAICodex
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import type { Container } from '@soederpop/luca/container'
4
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
5
- import { features, Feature } from '@soederpop/luca/feature'
4
+ import { Feature } from '@soederpop/luca/feature'
6
5
  import type { Conversation, ConversationTool, ContentPart, AskOptions, Message } from './conversation'
7
6
  import type { AGIContainer } from '../container.server.js'
8
7
  import type { ContentDb } from '@soederpop/luca/node'
@@ -94,10 +93,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
94
93
  static override eventsSchema = AssistantEventsSchema
95
94
  static override shortcut = 'features.assistant' as const
96
95
 
97
- static attach(container: Container<AvailableFeatures, any>) {
98
- features.register('assistant', Assistant)
99
- return container
100
- }
96
+ static { Feature.register(this, 'assistant') }
101
97
 
102
98
  /** @returns Default state with the assistant not started, zero conversations, and the resolved folder path. */
103
99
  override get initialState(): AssistantState {
@@ -764,4 +760,4 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
764
760
  }
765
761
  }
766
762
 
767
- export default features.register('assistant', Assistant)
763
+ export default Assistant
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
- import type { Container } from '@soederpop/luca/container'
4
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
5
- import { features, Feature } from '@soederpop/luca/feature'
4
+ import { Feature } from '@soederpop/luca/feature'
6
5
  import type { AGIContainer } from '../container.server.js'
7
6
  import type { Assistant } from './assistant.js'
8
7
 
@@ -78,10 +77,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
78
77
  static override eventsSchema = AssistantsManagerEventsSchema
79
78
  static override shortcut = 'features.assistantsManager' as const
80
79
 
81
- static attach(container: Container<AvailableFeatures, any>) {
82
- features.register('assistantsManager', AssistantsManager)
83
- return container
84
- }
80
+ static { Feature.register(this, 'assistantsManager') }
85
81
 
86
82
  /** @returns Default state with discovery not yet run and zero counts. */
87
83
  override get initialState(): AssistantsManagerState {
@@ -257,4 +253,4 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
257
253
  }
258
254
  }
259
255
 
260
- export default features.register('assistantsManager', AssistantsManager)
256
+ export default AssistantsManager
@@ -1,9 +1,8 @@
1
1
  // @ts-nocheck
2
2
  import { z } from 'zod'
3
3
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
4
- import type { Container } from '@soederpop/luca/container'
5
4
  import { type AvailableFeatures } from '@soederpop/luca/feature'
6
- import { features, Feature } from '@soederpop/luca/feature'
5
+ import { Feature } from '@soederpop/luca/feature'
7
6
 
8
7
  declare module '@soederpop/luca/feature' {
9
8
  interface AvailableFeatures {
@@ -281,10 +280,7 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
281
280
  static override shortcut = 'features.claudeCode' as const
282
281
  static override envVars = ['TMPDIR']
283
282
 
284
- static attach(container: Container<AvailableFeatures, any>) {
285
- container.features.register('claudeCode', ClaudeCode)
286
- return container
287
- }
283
+ static { Feature.register(this, 'claudeCode') }
288
284
 
289
285
  override get initialState(): ClaudeCodeState {
290
286
  return {
@@ -1108,4 +1104,4 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
1108
1104
  }
1109
1105
  }
1110
1106
 
1111
- export default features.register('claudeCode', ClaudeCode)
1107
+ export default ClaudeCode
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
- import type { Container } from '@soederpop/luca/container'
4
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
5
- import { features, Feature } from '@soederpop/luca/feature'
4
+ import { Feature } from '@soederpop/luca/feature'
6
5
  import { NodeContainer, type DiskCache, type NodeFeatures } from '@soederpop/luca/node/container'
7
6
  import type { Message } from './conversation'
8
7
 
@@ -79,10 +78,7 @@ export class ConversationHistory extends Feature<ConversationHistoryState, Conve
79
78
  static override optionsSchema = ConversationHistoryOptionsSchema
80
79
  static override shortcut = 'features.conversationHistory' as const
81
80
 
82
- static attach(container: Container<AvailableFeatures, any>) {
83
- features.register('conversationHistory', ConversationHistory)
84
- return container
85
- }
81
+ static { Feature.register(this, 'conversationHistory') }
86
82
 
87
83
  /** @returns Default state with zero conversations and no last-saved timestamp. */
88
84
  override get initialState(): ConversationHistoryState {
@@ -494,4 +490,4 @@ export class ConversationHistory extends Feature<ConversationHistoryState, Conve
494
490
  }
495
491
  }
496
492
 
497
- export default features.register('conversationHistory', ConversationHistory)
493
+ export default ConversationHistory
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
- import type { Container } from '@soederpop/luca/container';
4
3
  import { type AvailableFeatures } from '@soederpop/luca/feature'
5
- import { features, Feature } from '@soederpop/luca/feature'
4
+ import { Feature } from '@soederpop/luca/feature'
6
5
  import type { OpenAIClient } from '../../clients/openai';
7
6
  import type OpenAI from 'openai';
8
7
  import type { ConversationHistory } from './conversation-history';
@@ -125,6 +124,8 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
125
124
  static override optionsSchema = ConversationOptionsSchema
126
125
  static override shortcut = 'features.conversation' as const
127
126
 
127
+ static { Feature.register(this, 'conversation') }
128
+
128
129
  private _callMaxTokens: number | undefined = undefined
129
130
 
130
131
  /** Resolved max tokens: per-call override > options-level > undefined (no limit). */
@@ -136,11 +137,6 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
136
137
  return this.options.tools || {}
137
138
  }
138
139
 
139
- static attach(container: Container<AvailableFeatures, any>) {
140
- features.register('conversation', Conversation)
141
- return container
142
- }
143
-
144
140
  /** @returns Default state seeded from options: id, thread, model, initial history, and zero token usage. */
145
141
  override get initialState(): ConversationState {
146
142
  return {
@@ -796,4 +792,4 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
796
792
  }
797
793
  }
798
794
 
799
- export default features.register('conversation', Conversation)
795
+ export default Conversation
@@ -1,9 +1,8 @@
1
1
  // @ts-nocheck
2
2
  import { z } from 'zod'
3
3
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
4
- import type { Container } from '@soederpop/luca/container'
5
4
  import { type AvailableFeatures } from '@soederpop/luca/feature'
6
- import { features, Feature } from '@soederpop/luca/feature'
5
+ import { Feature } from '@soederpop/luca/feature'
7
6
 
8
7
  declare module '@soederpop/luca/feature' {
9
8
  interface AvailableFeatures {
@@ -149,10 +148,7 @@ export class OpenAICodex extends Feature<OpenAICodexState, OpenAICodexOptions> {
149
148
  static override optionsSchema = OpenAICodexOptionsSchema
150
149
  static override shortcut = 'features.openaiCodex' as const
151
150
 
152
- static attach(container: Container<AvailableFeatures, any>) {
153
- container.features.register('openaiCodex', OpenAICodex)
154
- return container
155
- }
151
+ static { Feature.register(this, 'openaiCodex') }
156
152
 
157
153
  override get initialState(): OpenAICodexState {
158
154
  return {
@@ -628,4 +624,4 @@ export class OpenAICodex extends Feature<OpenAICodexState, OpenAICodexOptions> {
628
624
  }
629
625
  }
630
626
 
631
- export default features.register('openaiCodex', OpenAICodex)
627
+ export default OpenAICodex
@@ -1,4 +1,4 @@
1
- import { Feature, features } from '../../feature.js'
1
+ import { Feature } from '../../feature.js'
2
2
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
3
  import { z } from 'zod'
4
4
  import { camelCase } from 'lodash-es'
@@ -103,6 +103,8 @@ export class OpenAPI extends Feature<OpenAPIState, OpenAPIOptions> {
103
103
  static override stateSchema = OpenAPIStateSchema
104
104
  static override optionsSchema = OpenAPIOptionsSchema
105
105
 
106
+ static { Feature.register(this, 'openapi') }
107
+
106
108
  /** Raw parsed spec document */
107
109
  private _spec: any = null
108
110
 
@@ -435,4 +437,4 @@ function schemaToJsonSchema(schema: any): any {
435
437
  return result
436
438
  }
437
439
 
438
- export default features.register('openapi', OpenAPI)
440
+ export default OpenAPI
@@ -5,8 +5,7 @@ import fs from 'fs/promises'
5
5
  import yaml from 'js-yaml'
6
6
  import { kebabCase } from 'lodash-es'
7
7
  import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
8
- import type { Container } from '@soederpop/luca/container'
9
- import { type AvailableFeatures, features, Feature } from '@soederpop/luca/feature'
8
+ import { type AvailableFeatures, Feature } from '@soederpop/luca/feature'
10
9
  import { Collection, defineModel } from 'contentbase'
11
10
  import type { ConversationTool } from './conversation'
12
11
 
@@ -94,14 +93,11 @@ export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOpti
94
93
  static override optionsSchema = SkillsLibraryOptionsSchema
95
94
  static override shortcut = 'features.skillsLibrary' as const
96
95
 
96
+ static { Feature.register(this, 'skillsLibrary') }
97
+
97
98
  private _projectCollection?: Collection
98
99
  private _userCollection?: Collection
99
100
 
100
- static attach(container: Container<AvailableFeatures, any>) {
101
- features.register('skillsLibrary', SkillsLibrary)
102
- return container
103
- }
104
-
105
101
  /** @returns Default state with loaded=false and zero skill counts across both collections. */
106
102
  override get initialState(): SkillsLibraryState {
107
103
  return {
@@ -422,4 +418,4 @@ export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOpti
422
418
  }
423
419
  }
424
420
 
425
- export default features.register('skillsLibrary', SkillsLibrary)
421
+ export default SkillsLibrary
package/src/cli/cli.ts CHANGED
@@ -11,6 +11,8 @@ async function main() {
11
11
  await discoverProjectCommands()
12
12
  // Discover user-level commands (~/.luca/commands/)
13
13
  await discoverUserCommands()
14
+ // Load generated introspection data if present
15
+ await loadProjectIntrospection()
14
16
 
15
17
  const commandName = container.argv._[0] as string
16
18
 
@@ -58,6 +60,26 @@ async function discoverProjectCommands() {
58
60
  }
59
61
  }
60
62
 
63
+ async function loadProjectIntrospection() {
64
+ const candidates = [
65
+ 'features/introspection.generated.ts',
66
+ 'src/introspection.generated.ts',
67
+ 'introspection.generated.ts',
68
+ ]
69
+
70
+ for (const candidate of candidates) {
71
+ const filePath = container.paths.resolve(candidate)
72
+ if (container.fs.exists(filePath)) {
73
+ try {
74
+ await import(filePath)
75
+ } catch {
76
+ // Generated file may be stale or malformed — skip silently
77
+ }
78
+ return
79
+ }
80
+ }
81
+ }
82
+
61
83
  async function discoverUserCommands() {
62
84
  const { fs } = container
63
85
  const dir = join(homedir(), '.luca', 'commands')