@soederpop/luca 0.0.5 → 0.0.7

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 (211) hide show
  1. package/CLAUDE.md +10 -1
  2. package/bun.lock +1 -1
  3. package/commands/build-bootstrap.ts +78 -0
  4. package/commands/build-scaffolds.ts +24 -2
  5. package/commands/try-all-challenges.ts +543 -0
  6. package/commands/try-challenge.ts +100 -0
  7. package/docs/README.md +52 -80
  8. package/docs/TABLE-OF-CONTENTS.md +82 -51
  9. package/docs/apis/clients/elevenlabs.md +232 -8
  10. package/docs/apis/clients/graph.md +59 -8
  11. package/docs/apis/clients/openai.md +362 -2
  12. package/docs/apis/clients/rest.md +122 -2
  13. package/docs/apis/clients/websocket.md +71 -17
  14. package/docs/apis/features/agi/assistant.md +9 -3
  15. package/docs/apis/features/agi/assistants-manager.md +2 -2
  16. package/docs/apis/features/agi/claude-code.md +153 -14
  17. package/docs/apis/features/agi/conversation-history.md +15 -3
  18. package/docs/apis/features/agi/conversation.md +133 -20
  19. package/docs/apis/features/agi/openai-codex.md +90 -12
  20. package/docs/apis/features/agi/skills-library.md +23 -5
  21. package/docs/apis/features/node/container-link.md +59 -0
  22. package/docs/apis/features/node/content-db.md +1 -1
  23. package/docs/apis/features/node/disk-cache.md +1 -1
  24. package/docs/apis/features/node/dns.md +1 -0
  25. package/docs/apis/features/node/docker.md +2 -1
  26. package/docs/apis/features/node/esbuild.md +4 -3
  27. package/docs/apis/features/node/file-manager.md +13 -4
  28. package/docs/apis/features/node/fs.md +726 -171
  29. package/docs/apis/features/node/git.md +1 -0
  30. package/docs/apis/features/node/google-auth.md +23 -4
  31. package/docs/apis/features/node/google-calendar.md +14 -2
  32. package/docs/apis/features/node/google-docs.md +15 -2
  33. package/docs/apis/features/node/google-drive.md +21 -3
  34. package/docs/apis/features/node/google-sheets.md +14 -2
  35. package/docs/apis/features/node/grep.md +2 -0
  36. package/docs/apis/features/node/helpers.md +29 -0
  37. package/docs/apis/features/node/ink.md +2 -2
  38. package/docs/apis/features/node/networking.md +39 -4
  39. package/docs/apis/features/node/os.md +28 -0
  40. package/docs/apis/features/node/postgres.md +26 -4
  41. package/docs/apis/features/node/proc.md +37 -28
  42. package/docs/apis/features/node/process-manager.md +33 -5
  43. package/docs/apis/features/node/repl.md +1 -1
  44. package/docs/apis/features/node/runpod.md +1 -0
  45. package/docs/apis/features/node/secure-shell.md +7 -0
  46. package/docs/apis/features/node/semantic-search.md +12 -5
  47. package/docs/apis/features/node/sqlite.md +26 -4
  48. package/docs/apis/features/node/telegram.md +30 -5
  49. package/docs/apis/features/node/tts.md +17 -2
  50. package/docs/apis/features/node/ui.md +1 -1
  51. package/docs/apis/features/node/vault.md +4 -9
  52. package/docs/apis/features/node/vm.md +3 -12
  53. package/docs/apis/features/node/window-manager.md +128 -20
  54. package/docs/apis/features/web/asset-loader.md +13 -1
  55. package/docs/apis/features/web/container-link.md +59 -0
  56. package/docs/apis/features/web/esbuild.md +4 -3
  57. package/docs/apis/features/web/helpers.md +29 -0
  58. package/docs/apis/features/web/network.md +16 -2
  59. package/docs/apis/features/web/speech.md +16 -2
  60. package/docs/apis/features/web/vault.md +4 -9
  61. package/docs/apis/features/web/vm.md +3 -12
  62. package/docs/apis/features/web/voice.md +18 -1
  63. package/docs/apis/servers/express.md +18 -2
  64. package/docs/apis/servers/mcp.md +29 -4
  65. package/docs/apis/servers/websocket.md +34 -6
  66. package/docs/bootstrap/CLAUDE.md +100 -0
  67. package/docs/bootstrap/SKILL.md +222 -0
  68. package/docs/bootstrap/templates/about-command.ts +41 -0
  69. package/docs/bootstrap/templates/docs-models.ts +22 -0
  70. package/docs/bootstrap/templates/docs-readme.md +43 -0
  71. package/docs/bootstrap/templates/example-feature.ts +53 -0
  72. package/docs/bootstrap/templates/health-endpoint.ts +15 -0
  73. package/docs/bootstrap/templates/luca-cli.ts +25 -0
  74. package/docs/challenges/caching-proxy.md +16 -0
  75. package/docs/challenges/content-db-round-trip.md +14 -0
  76. package/docs/challenges/custom-command.md +9 -0
  77. package/docs/challenges/file-watcher-pipeline.md +11 -0
  78. package/docs/challenges/grep-audit-report.md +15 -0
  79. package/docs/challenges/multi-feature-dashboard.md +14 -0
  80. package/docs/challenges/process-orchestrator.md +17 -0
  81. package/docs/challenges/rest-api-server-with-client.md +12 -0
  82. package/docs/challenges/script-runner-with-vm.md +11 -0
  83. package/docs/challenges/simple-rest-api.md +15 -0
  84. package/docs/challenges/websocket-serve-and-client.md +11 -0
  85. package/docs/challenges/yaml-config-system.md +14 -0
  86. package/docs/command-system-overhaul.md +94 -0
  87. package/docs/examples/assistant/CORE.md +18 -0
  88. package/docs/examples/assistant/hooks.ts +3 -0
  89. package/docs/examples/assistant/tools.ts +10 -0
  90. package/docs/examples/window-manager-layouts.md +180 -0
  91. package/docs/in-memory-fs.md +4 -0
  92. package/docs/models.ts +13 -10
  93. package/docs/philosophy.md +4 -3
  94. package/docs/reports/console-hmr-design.md +170 -0
  95. package/docs/reports/helper-semantic-search.md +72 -0
  96. package/docs/scaffolds/client.md +29 -20
  97. package/docs/scaffolds/command.md +64 -50
  98. package/docs/scaffolds/endpoint.md +31 -36
  99. package/docs/scaffolds/feature.md +28 -18
  100. package/docs/scaffolds/selector.md +91 -0
  101. package/docs/scaffolds/server.md +18 -9
  102. package/docs/selectors.md +115 -0
  103. package/docs/sessions/custom-command/attempt-log-2.md +195 -0
  104. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
  105. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
  106. package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
  107. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
  108. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
  109. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
  110. package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
  111. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
  112. package/docs/tutorials/00-bootstrap.md +148 -0
  113. package/docs/tutorials/07-endpoints.md +7 -7
  114. package/docs/tutorials/08-commands.md +153 -72
  115. package/luca.cli.ts +3 -0
  116. package/package.json +6 -5
  117. package/public/index.html +1430 -0
  118. package/scripts/examples/using-ollama.ts +2 -1
  119. package/scripts/update-introspection-data.ts +2 -2
  120. package/src/agi/endpoints/experts.ts +1 -1
  121. package/src/agi/features/assistant.ts +7 -0
  122. package/src/agi/features/assistants-manager.ts +5 -5
  123. package/src/agi/features/claude-code.ts +263 -3
  124. package/src/agi/features/conversation-history.ts +7 -1
  125. package/src/agi/features/conversation.ts +26 -3
  126. package/src/agi/features/openai-codex.ts +26 -2
  127. package/src/agi/features/openapi.ts +6 -1
  128. package/src/agi/features/skills-library.ts +9 -1
  129. package/src/bootstrap/generated.ts +540 -0
  130. package/src/cli/cli.ts +64 -21
  131. package/src/client.ts +23 -357
  132. package/src/clients/civitai/index.ts +1 -1
  133. package/src/clients/client-template.ts +1 -1
  134. package/src/clients/comfyui/index.ts +13 -2
  135. package/src/clients/elevenlabs/index.ts +2 -1
  136. package/src/clients/graph.ts +87 -0
  137. package/src/clients/openai/index.ts +10 -1
  138. package/src/clients/rest.ts +207 -0
  139. package/src/clients/websocket.ts +176 -0
  140. package/src/command.ts +281 -34
  141. package/src/commands/bootstrap.ts +181 -0
  142. package/src/commands/chat.ts +5 -4
  143. package/src/commands/describe.ts +225 -2
  144. package/src/commands/help.ts +35 -9
  145. package/src/commands/index.ts +3 -0
  146. package/src/commands/introspect.ts +92 -2
  147. package/src/commands/prompt.ts +5 -6
  148. package/src/commands/run.ts +33 -10
  149. package/src/commands/save-api-docs.ts +49 -0
  150. package/src/commands/scaffold.ts +169 -23
  151. package/src/commands/select.ts +94 -0
  152. package/src/commands/serve.ts +10 -1
  153. package/src/container.ts +15 -0
  154. package/src/endpoint.ts +19 -0
  155. package/src/graft.ts +181 -0
  156. package/src/introspection/generated.agi.ts +12458 -8968
  157. package/src/introspection/generated.node.ts +10573 -7145
  158. package/src/introspection/generated.web.ts +1 -1
  159. package/src/introspection/index.ts +26 -0
  160. package/src/node/container.ts +6 -7
  161. package/src/node/features/content-db.ts +49 -2
  162. package/src/node/features/disk-cache.ts +16 -9
  163. package/src/node/features/dns.ts +16 -3
  164. package/src/node/features/docker.ts +16 -4
  165. package/src/node/features/esbuild.ts +20 -0
  166. package/src/node/features/file-manager.ts +184 -29
  167. package/src/node/features/fs.ts +704 -248
  168. package/src/node/features/git.ts +21 -8
  169. package/src/node/features/grep.ts +23 -3
  170. package/src/node/features/helpers.ts +372 -43
  171. package/src/node/features/networking.ts +39 -4
  172. package/src/node/features/opener.ts +28 -15
  173. package/src/node/features/os.ts +76 -0
  174. package/src/node/features/port-exposer.ts +11 -1
  175. package/src/node/features/postgres.ts +17 -1
  176. package/src/node/features/proc.ts +4 -1
  177. package/src/node/features/python.ts +63 -14
  178. package/src/node/features/repl.ts +11 -7
  179. package/src/node/features/runpod.ts +16 -3
  180. package/src/node/features/secure-shell.ts +27 -2
  181. package/src/node/features/semantic-search.ts +12 -1
  182. package/src/node/features/ui.ts +5 -69
  183. package/src/node/features/vm.ts +17 -0
  184. package/src/node/features/window-manager.ts +68 -20
  185. package/src/node.ts +5 -0
  186. package/src/scaffolds/generated.ts +492 -290
  187. package/src/scaffolds/template.ts +9 -0
  188. package/src/schemas/base.ts +46 -5
  189. package/src/selector.ts +282 -0
  190. package/src/server.ts +11 -0
  191. package/src/servers/express.ts +27 -12
  192. package/src/servers/socket.ts +45 -11
  193. package/src/web/clients/socket.ts +4 -1
  194. package/src/web/container.ts +2 -1
  195. package/src/web/features/network.ts +7 -1
  196. package/src/web/features/voice-recognition.ts +16 -1
  197. package/test/clients-servers.test.ts +2 -1
  198. package/test/command.test.ts +267 -0
  199. package/test-integration/assistants-manager.test.ts +10 -20
  200. package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
  201. package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
  202. package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
  203. package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
  204. package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
  205. package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
  206. package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
  207. package/docs/examples/launcher-app-command-listener.md +0 -120
  208. package/docs/tasks/web-container-helper-discovery.md +0 -71
  209. package/docs/todos.md +0 -1
  210. package/scripts/test-command-listener.ts +0 -123
  211. package/src/node/features/launcher-app-command-listener.ts +0 -389
@@ -18,12 +18,14 @@ Run `luca serve` and they're automatically discovered and mounted.
18
18
 
19
19
  ## Imports
20
20
 
21
- ```ts
22
- import { z } from 'zod'
23
- import type { EndpointContext } from '@soederpop/luca'
24
- ```
21
+ Endpoints are lightweight — just exports and handler functions. No imports are required.
22
+
23
+ If your project has `@soederpop/luca` as an npm dependency, you can import `z` from `zod` and `EndpointContext` from `@soederpop/luca` for type safety. Otherwise, use `any` types — the framework handles validation and context injection for you.
25
24
 
26
- That's it. Endpoints are lightweight just exports and functions.
25
+ Access framework capabilities through the `ctx` parameter:
26
+ - `ctx.container.feature('fs')` for file operations
27
+ - `ctx.container.feature('yaml')` for YAML parsing
28
+ - `ctx.container.feature('sqlite')` for database access
27
29
 
28
30
  ## Required Exports
29
31
 
@@ -37,16 +39,16 @@ export const tags = ['{{camelName}}']
37
39
 
38
40
  ## Handler Functions
39
41
 
40
- Export named functions for each HTTP method you support. Each receives validated parameters and an `EndpointContext`:
42
+ Export named functions for each HTTP method you support. Each receives validated parameters and a context object:
41
43
 
42
44
  ```ts
43
- export async function get(params: any, ctx: EndpointContext) {
45
+ export async function get(params: any, ctx: any) {
44
46
  const fs = ctx.container.feature('fs')
45
47
  // Your logic here
46
48
  return { message: 'ok' }
47
49
  }
48
50
 
49
- export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
51
+ export async function post(params: any, ctx: any) {
50
52
  // Create something
51
53
  return { created: true }
52
54
  }
@@ -64,9 +66,11 @@ Return any object — it's automatically JSON-serialized as the response.
64
66
 
65
67
  ## Validation Schemas
66
68
 
67
- Export Zod schemas to validate parameters for each method. Name them `{method}Schema`:
69
+ If `zod` is available (via `@soederpop/luca` dependency or `node_modules`), export Zod schemas to validate parameters for each method. Name them `{method}Schema`:
68
70
 
69
71
  ```ts
72
+ import { z } from 'zod'
73
+
70
74
  export const getSchema = z.object({
71
75
  q: z.string().optional().describe('Search query'),
72
76
  limit: z.number().default(20).describe('Max results'),
@@ -78,7 +82,7 @@ export const postSchema = z.object({
78
82
  })
79
83
  ```
80
84
 
81
- Invalid requests automatically return 400 with Zod error details. Schemas also feed the auto-generated OpenAPI spec.
85
+ Invalid requests automatically return 400 with Zod error details. Schemas also feed the auto-generated OpenAPI spec. If zod is not available, skip schema exports — the endpoint still works, you just lose automatic validation.
82
86
 
83
87
  ## Rate Limiting
84
88
 
@@ -94,42 +98,40 @@ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }
94
98
 
95
99
  ## Delete Handler
96
100
 
97
- `delete` is a reserved word in JS. Use an alias:
101
+ `delete` is a reserved word in JS, so you can't use it as a function name directly. Use a named export alias:
98
102
 
99
103
  ```ts
100
- const del = async (params: any, ctx: EndpointContext) => {
104
+ // Use a local name, then re-export as `delete`
105
+ const del = async (params: any, ctx: any) => {
101
106
  return { deleted: true }
102
107
  }
103
108
  export { del as delete }
104
109
  ```
105
110
 
111
+ You can also export `deleteSchema` and `deleteRateLimit` for validation and rate limiting on DELETE.
112
+
106
113
  ## Complete Example
107
114
 
108
- A CRUD endpoint for a resource:
115
+ A CRUD endpoint for a resource (no external imports needed):
109
116
 
110
117
  ```ts
111
- import { z } from 'zod'
112
- import type { EndpointContext } from '@soederpop/luca'
113
-
114
118
  export const path = '/api/{{camelName}}'
115
119
  export const description = '{{description}}'
116
120
  export const tags = ['{{camelName}}']
117
121
 
118
- export const getSchema = z.object({
119
- q: z.string().optional().describe('Search query'),
120
- })
121
-
122
- export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
122
+ export async function get(params: any, ctx: any) {
123
123
  return { items: [], total: 0 }
124
124
  }
125
125
 
126
- export const postSchema = z.object({
127
- name: z.string().min(1).describe('Item name'),
128
- })
129
-
130
- export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
126
+ export async function post(params: any, ctx: any) {
131
127
  return { item: { id: '1', ...params }, message: 'Created' }
132
128
  }
129
+
130
+ const del = async (params: any, ctx: any) => {
131
+ const { id } = ctx.params
132
+ return { message: `Deleted ${id}` }
133
+ }
134
+ export { del as delete }
133
135
  ```
134
136
 
135
137
  ## Dynamic Route Example
@@ -138,28 +140,21 @@ For routes with URL parameters, create a nested file:
138
140
 
139
141
  ```ts
140
142
  // endpoints/{{camelName}}/[id].ts
141
- import { z } from 'zod'
142
- import type { EndpointContext } from '@soederpop/luca'
143
-
144
143
  export const path = '/api/{{camelName}}/:id'
145
144
  export const description = 'Get, update, or delete a specific item'
146
145
  export const tags = ['{{camelName}}']
147
146
 
148
- export async function get(params: any, ctx: EndpointContext) {
147
+ export async function get(params: any, ctx: any) {
149
148
  const { id } = ctx.params
150
149
  return { item: { id } }
151
150
  }
152
151
 
153
- export const putSchema = z.object({
154
- name: z.string().min(1).optional().describe('Updated name'),
155
- })
156
-
157
- export async function put(params: z.infer<typeof putSchema>, ctx: EndpointContext) {
152
+ export async function put(params: any, ctx: any) {
158
153
  const { id } = ctx.params
159
154
  return { item: { id, ...params }, message: 'Updated' }
160
155
  }
161
156
 
162
- const del = async (params: any, ctx: EndpointContext) => {
157
+ const del = async (params: any, ctx: any) => {
163
158
  const { id } = ctx.params
164
159
  return { message: `Deleted ${id}` }
165
160
  }
@@ -12,12 +12,15 @@ When to build a feature:
12
12
  ```ts
13
13
  import { z } from 'zod'
14
14
  import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
15
- import { Feature, features } from '@soederpop/luca'
16
- import type { ContainerContext } from '@soederpop/luca'
15
+ import { Feature } from '@soederpop/luca'
17
16
  ```
18
17
 
19
18
  These are the only imports your feature file needs from luca. If your feature wraps a third-party library, import it here too — feature implementations are the ONE place where direct library imports are allowed.
20
19
 
20
+ The use of dynamic imports is encouraged here, only import libraries you need when the feature is used, and only when necessary in the lifecycle of the feature if it can be done.
21
+
22
+ feature's have built in ways to check if their requirements are supported and can be enabled cautiously.
23
+
21
24
  ## Schemas
22
25
 
23
26
  Define the shape of your feature's state, options, and events using Zod. Every field must have a `.describe()` — this becomes the documentation.
@@ -45,11 +48,11 @@ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
45
48
 
46
49
  The class extends `Feature` with your state and options types. Static properties drive registration and introspection. Every public method needs a JSDoc block with `@param`, `@returns`, and `@example`.
47
50
 
51
+ Running `luca introspect` captures JSDoc blocks and Zod schemas and includes them in the description whenever somebody calls `container.features.describe('{{camelName}}')` or `luca describe {{camelName}}`.
52
+
48
53
  ```ts
49
54
  /**
50
55
  * {{description}}
51
- *
52
- * @example
53
56
  * ```typescript
54
57
  * const {{camelName}} = container.feature('{{camelName}}')
55
58
  * ```
@@ -61,17 +64,21 @@ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}O
61
64
  static override stateSchema = {{PascalName}}StateSchema
62
65
  static override optionsSchema = {{PascalName}}OptionsSchema
63
66
  static override eventsSchema = {{PascalName}}EventsSchema
64
- static override description = '{{description}}'
65
67
 
66
- constructor(options: {{PascalName}}Options, context: ContainerContext) {
67
- super(options, context)
68
- // Initialize state, set up resources
69
- }
68
+ static { Feature.register(this, '{{camelName}}') }
70
69
 
71
- // Add your methods here. Every public method needs JSDoc.
70
+ /**
71
+ * Called after the feature is initialized. Use this for any setup logic
72
+ * instead of overriding the constructor.
73
+ */
74
+ async afterInitialize() {
75
+ // Set up initial state, start background tasks, etc.
76
+ }
72
77
  }
73
78
  ```
74
79
 
80
+ **Important**: You almost never need to override the constructor. Use `afterInitialize()` for any setup logic — it runs after the feature is fully wired into the container and has access to `this.container`, `this.options`, `this.state`, etc.
81
+
75
82
  ## Module Augmentation
76
83
 
77
84
  This is what gives `container.feature('yourName')` TypeScript autocomplete. Without it, the feature works but TypeScript won't know about it.
@@ -86,10 +93,14 @@ declare module '@soederpop/luca' {
86
93
 
87
94
  ## Registration
88
95
 
89
- The last line of the file registers the feature in the global registry. This must happen at module level (not inside a function).
96
+ Registration happens inside the class body using a static block. The default export is just the class itself.
90
97
 
91
98
  ```ts
92
- export default features.register('{{camelName}}', {{PascalName}})
99
+ // Inside the class:
100
+ static { Feature.register(this, '{{camelName}}') }
101
+
102
+ // At module level:
103
+ export default {{PascalName}}
93
104
  ```
94
105
 
95
106
  ## Complete Example
@@ -99,8 +110,7 @@ Here's a minimal but complete feature. This is what a real feature file looks li
99
110
  ```ts
100
111
  import { z } from 'zod'
101
112
  import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
102
- import { Feature, features } from '@soederpop/luca'
103
- import type { ContainerContext } from '@soederpop/luca'
113
+ import { Feature } from '@soederpop/luca'
104
114
 
105
115
  declare module '@soederpop/luca' {
106
116
  interface AvailableFeatures {
@@ -128,14 +138,14 @@ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}O
128
138
  static override shortcut = 'features.{{camelName}}' as const
129
139
  static override stateSchema = {{PascalName}}StateSchema
130
140
  static override optionsSchema = {{PascalName}}OptionsSchema
131
- static override description = '{{description}}'
141
+ static { Feature.register(this, '{{camelName}}') }
132
142
 
133
- constructor(options: {{PascalName}}Options, context: ContainerContext) {
134
- super(options, context)
143
+ async afterInitialize() {
144
+ // Setup logic goes here — not in the constructor
135
145
  }
136
146
  }
137
147
 
138
- export default features.register('{{camelName}}', {{PascalName}})
148
+ export default {{PascalName}}
139
149
  ```
140
150
 
141
151
  ## Conventions
@@ -0,0 +1,91 @@
1
+ # Building a Selector
2
+
3
+ A selector returns data. Where commands perform actions, selectors query and return structured results with built-in caching. Selectors live in a project's `selectors/` folder and are automatically discovered.
4
+
5
+ When to build a selector:
6
+ - You need to query project data (package info, file listings, config values)
7
+ - The result benefits from caching (keyed by git SHA or custom key)
8
+ - You want the data available via `container.select('name')` or `luca select name`
9
+
10
+ ## Imports
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import type { ContainerContext } from '@soederpop/luca'
15
+ ```
16
+
17
+ ## Args Schema
18
+
19
+ Define the selector's input arguments with Zod.
20
+
21
+ ```ts
22
+ export const argsSchema = z.object({
23
+ // Add your input arguments here.
24
+ // Example: field: z.string().optional().describe('Specific field to return'),
25
+ })
26
+ ```
27
+
28
+ ## Description
29
+
30
+ Export a description string for discoverability:
31
+
32
+ ```ts
33
+ export const description = '{{description}}'
34
+ ```
35
+
36
+ ## Caching
37
+
38
+ Selectors cache by default. The default cache key is `hashObject({ selectorName, args, gitSha })` — same args + same commit = cache hit.
39
+
40
+ To customize the cache key:
41
+
42
+ ```ts
43
+ export function cacheKey(args: z.infer<typeof argsSchema>, context: ContainerContext) {
44
+ return context.container.git.currentCommitSha
45
+ }
46
+ ```
47
+
48
+ To disable caching:
49
+
50
+ ```ts
51
+ export const cacheable = false
52
+ ```
53
+
54
+ ## Handler
55
+
56
+ Export a `run` function that returns data. It receives parsed args and the container context.
57
+
58
+ ```ts
59
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
60
+ const { container } = context
61
+ // Query and return your data
62
+ return { /* your data */ }
63
+ }
64
+ ```
65
+
66
+ ## Complete Example
67
+
68
+ ```ts
69
+ import { z } from 'zod'
70
+ import type { ContainerContext } from '@soederpop/luca'
71
+
72
+ export const description = '{{description}}'
73
+
74
+ export const argsSchema = z.object({})
75
+
76
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
77
+ const { container } = context
78
+
79
+ // Return your data here
80
+ return {}
81
+ }
82
+ ```
83
+
84
+ ## Conventions
85
+
86
+ - **File location**: `selectors/{{kebabName}}.ts` in the project root. Discovered automatically.
87
+ - **Naming**: kebab-case for filename. `luca select {{kebabName}}` maps to `selectors/{{kebabName}}.ts`.
88
+ - **Use the container**: Never import `fs`, `path` directly. Use `container.feature('fs')`, `container.paths`.
89
+ - **Return data**: The `run` function must return the data. It gets wrapped in `{ data, cached, cacheKey }` by the framework.
90
+ - **Caching**: On by default. Override `cacheKey()` for custom invalidation, or set `cacheable = false` to skip.
91
+ - **CLI**: `luca select {{kebabName}}` runs the selector and prints JSON. Use `--json` for data only, `--no-cache` to force fresh.
@@ -11,9 +11,9 @@ When to build a server:
11
11
 
12
12
  ```ts
13
13
  import { z } from 'zod'
14
- import { Server, servers } from '@soederpop/luca'
14
+ import { Server } from '@soederpop/luca'
15
15
  import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
16
- import type { ContainerContext, NodeContainer } from '@soederpop/luca'
16
+ import type { NodeContainer } from '@soederpop/luca'
17
17
  import type { ServersInterface } from '@soederpop/luca'
18
18
  ```
19
19
 
@@ -40,6 +40,8 @@ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
40
40
 
41
41
  ## Class
42
42
 
43
+ Running `luca introspect` captures JSDoc blocks and Zod schemas and includes them in the description whenever somebody calls `container.servers.describe('{{camelName}}')` or `luca describe {{camelName}}`.
44
+
43
45
  ```ts
44
46
  /**
45
47
  * {{description}}
@@ -57,7 +59,7 @@ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Op
57
59
  static override stateSchema = {{PascalName}}StateSchema
58
60
  static override optionsSchema = {{PascalName}}OptionsSchema
59
61
  static override eventsSchema = {{PascalName}}EventsSchema
60
- static override description = '{{description}}'
62
+ static { Server.register(this, '{{camelName}}') }
61
63
 
62
64
  static override attach(container: NodeContainer & ServersInterface) {
63
65
  return container
@@ -103,17 +105,23 @@ declare module '@soederpop/luca' {
103
105
 
104
106
  ## Registration
105
107
 
108
+ Registration happens inside the class body using a static block. The default export is just the class itself.
109
+
106
110
  ```ts
107
- export default servers.register('{{camelName}}', {{PascalName}})
111
+ // Inside the class:
112
+ static { Server.register(this, '{{camelName}}') }
113
+
114
+ // At module level:
115
+ export default {{PascalName}}
108
116
  ```
109
117
 
110
118
  ## Complete Example
111
119
 
112
120
  ```ts
113
121
  import { z } from 'zod'
114
- import { Server, servers } from '@soederpop/luca'
122
+ import { Server } from '@soederpop/luca'
115
123
  import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
116
- import type { ContainerContext, NodeContainer } from '@soederpop/luca'
124
+ import type { NodeContainer } from '@soederpop/luca'
117
125
  import type { ServersInterface } from '@soederpop/luca'
118
126
 
119
127
  declare module '@soederpop/luca' {
@@ -146,7 +154,7 @@ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Op
146
154
  static override stateSchema = {{PascalName}}StateSchema
147
155
  static override optionsSchema = {{PascalName}}OptionsSchema
148
156
  static override eventsSchema = {{PascalName}}EventsSchema
149
- static override description = '{{description}}'
157
+ static { Server.register(this, '{{camelName}}') }
150
158
 
151
159
  static override attach(container: NodeContainer & ServersInterface) {
152
160
  return container
@@ -175,13 +183,14 @@ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Op
175
183
  }
176
184
  }
177
185
 
178
- export default servers.register('{{camelName}}', {{PascalName}})
186
+ export default {{PascalName}}
179
187
  ```
180
188
 
181
189
  ## Conventions
182
190
 
183
191
  - **Lifecycle**: Implement `configure()`, `start()`, and `stop()`. Check guards (`isConfigured`, `isListening`, `isStopped`) at the top of each.
192
+ - **Use `afterInitialize()`**: For any setup logic instead of overriding the constructor. Lifecycle methods (`configure`, `start`, `stop`) handle the server's runtime phases.
184
193
  - **State tracking**: Set `configured`, `listening`, `stopped`, and `port` on the state. This powers the introspection system.
185
194
  - **attach() is static**: It runs when the container first loads the server class. Use it for container-level setup if needed.
186
195
  - **Port from options**: Accept port via options schema and respect it in `start()`. Allow override via start options.
187
- - **JSDoc everything**: Every public method needs `@param`, `@returns`, `@example`.
196
+ - **JSDoc everything**: Every public method needs `@param`, `@returns`, `@example`. Run `luca introspect` after changes to update generated docs.
@@ -0,0 +1,115 @@
1
+ # Selector System
2
+
3
+ ## Vision
4
+
5
+ Commands perform actions. Selectors return data. Together they form a complete agent tool interface — pick which commands and selectors you need and you get free assistant tools. Agents only need to know the external APIs of features, servers, and clients already in the container (which can be learned from `luca describe`).
6
+
7
+ ## What Is a Selector?
8
+
9
+ A Selector is a new helper type (like Feature, Client, Server, Command) that:
10
+
11
+ - Extends `Helper` with a `select(options)` method that **returns data**
12
+ - Lives in a `SelectorsRegistry`, queryable via `container.selectors`
13
+ - Is instantiated via `container.selector('name', options)` factory
14
+ - Supports **caching** via `diskCache` — many selector values don't change if git SHAs don't change, so SHA makes a great cache key
15
+ - Is discoverable from a `selectors/` folder in projects, just like `commands/`
16
+ - Supports both **class-based** and **module-based** (export a `select` function) patterns
17
+
18
+ ## Architecture — How It Maps to Existing Patterns
19
+
20
+ The implementation follows the exact same pattern as `Command`:
21
+
22
+ ### Files to Create/Modify
23
+
24
+ 1. **`src/schemas/base.ts`** — Add `SelectorStateSchema`, `SelectorOptionsSchema`, `SelectorEventsSchema`
25
+ 2. **`src/selector.ts`** — New file mirroring `src/command.ts`:
26
+ - `Selector` class extending `Helper` with `select()` method
27
+ - `SelectorsRegistry` extending `Registry` with `discover()` support
28
+ - `selectors` singleton, `helperCache`, `Selector.register()`, `Selector.attach()`
29
+ - `AvailableSelectors` interface, `SelectorsInterface`, `SelectorFactory` type
30
+ - Module-based pattern: `SimpleSelector` type for graft compatibility
31
+ 3. **`src/node/container.ts`** — Import `Selector` + `SelectorsInterface`, add to `ClientsAndServersInterface`, wire `this.use(Selector)`
32
+ 4. **`src/node/features/helpers.ts`** — Add `selectors` to `registryMap` and `RegistryType`, add to `discoverAll()` iteration
33
+
34
+ ### Selector Base Class Shape
35
+
36
+ ```typescript
37
+ class Selector<T, K> extends Helper<T, K> {
38
+ static shortcut = 'selectors.base'
39
+ static argsSchema: z.ZodType // schema for select() input
40
+ static cacheable: boolean = true // opt-out of caching
41
+
42
+ // The core method — override in subclass or graft from module export
43
+ async select(args, context): Promise<any> {}
44
+
45
+ // Dispatch normalizer (like Command.execute)
46
+ async resolve(args?, source?): Promise<SelectorResult> {}
47
+ }
48
+ ```
49
+
50
+ ### Module-Based Pattern (selectors/ folder)
51
+
52
+ ```typescript
53
+ // selectors/package-info.ts
54
+ export const description = 'Returns parsed package.json data'
55
+ export const argsSchema = z.object({ field: z.string().optional() })
56
+ export const cacheable = true
57
+
58
+ export function cacheKey(args, context) {
59
+ // return a string key — if unchanged, cached value is returned
60
+ return context.container.git.sha
61
+ }
62
+
63
+ export async function select(args, context) {
64
+ const manifest = context.container.manifest
65
+ return args.field ? manifest[args.field] : manifest
66
+ }
67
+ ```
68
+
69
+ ### Caching Strategy
70
+
71
+ - Built into the `Selector` base class, powered by `diskCache` feature
72
+ - Subclasses/modules can export a `cacheKey(args, context)` function
73
+ - Default cache key strategy: `hashObject({ selectorName, args, gitSha })`
74
+ - `cacheable: false` opts out entirely
75
+ - Cache is key-invalidated (key changes = miss), no TTL by default
76
+ - The `resolve()` method checks cache before calling `select()`
77
+
78
+ ## Open Questions (Needs Decision)
79
+
80
+ ### 1. Return Shape
81
+ Should `select()` return data directly, or a wrapped result like `{ data, metadata, cached }`?
82
+
83
+ **Leaning toward:** data directly from `select()`, but `resolve()` (the dispatch method) returns `{ data, cached, cacheKey }` so callers know if it was a cache hit.
84
+
85
+ ### 2. CLI Integration
86
+ Should there be a `luca select <name>` CLI command? Would make selectors usable from the terminal, printing JSON output.
87
+
88
+ ### 3. Cache TTL
89
+ Is pure key-invalidation enough, or do some selectors need time-based expiry?
90
+
91
+ ### 4. Scope of First Delivery
92
+
93
+ Proposed skateboard:
94
+ - `Selector` base class with `select()` and `resolve()` (cached dispatch)
95
+ - `SelectorsRegistry` with `discover()`
96
+ - Container wiring (`container.selectors`, `container.selector()`)
97
+ - Schemas in `schemas/base.ts`
98
+ - `selectors/` folder discovery via `Helpers` feature
99
+ - Caching integration with `diskCache`
100
+ - Module-based pattern support (export `select` + optional `cacheKey`)
101
+ - `Selector` + `selectors` exported from `@soederpop/luca` barrel + seeded in VM virtual modules
102
+
103
+ ## Codebase Exploration Notes
104
+
105
+ These are the key files that were studied to inform this design:
106
+
107
+ - `src/command.ts` — The primary pattern to mirror. `Command` extends `Helper`, has `CommandsRegistry`, `commands` singleton, `Command.register()`, `Command.attach()`, `execute()`/`run()` split, headless capture, `graftModule` support.
108
+ - `src/helper.ts` — Base class providing state, events, options (Zod-validated), introspection, `afterInitialize()` hook.
109
+ - `src/registry.ts` — Abstract `Registry<T>` with `register()`, `lookup()`, `has()`, `available`, `describe()`, `describeAll()`, event bus.
110
+ - `src/graft.ts` — `graftModule()` synthesizes a class from plain module exports. `RESERVED_EXPORTS` list determines what becomes static vs prototype methods.
111
+ - `src/schemas/base.ts` — All Zod schemas. Each helper type has State/Options/Events schemas.
112
+ - `src/node/container.ts` — `NodeContainer` wires everything: side-effect imports, `NodeFeatures` interface, `ClientsAndServersInterface`, `this.use(Command)` pattern.
113
+ - `src/node/features/helpers.ts` — `Helpers` feature: `registryMap`, `RegistryType`, `discoverAll()`, class-based vs config-based discovery, VM module seeding.
114
+ - `src/node/features/disk-cache.ts` — `DiskCache` feature: `get/set/has/rm`, `cacache`-backed, per-project cache dir, `container.utils.hashObject()` for keys.
115
+ - `src/container.ts` — Base `Container`: `createHelperInstance()` with `helperCache` Map, `use()` plugin system, `registerHelperType()`.