@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
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod'
2
- import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
2
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
3
  import { Feature } from "../../feature.js";
4
4
  import { type ContainerContext } from '../container.js'
5
5
 
@@ -17,6 +17,20 @@ export const VoiceRecognitionStateSchema = FeatureStateSchema.extend({
17
17
  export type VoiceRecognitionOptions = z.infer<typeof VoiceRecognitionOptionsSchema>
18
18
  export type VoiceRecognitionState = z.infer<typeof VoiceRecognitionStateSchema>
19
19
 
20
+ export const VoiceRecognitionEventsSchema = FeatureEventsSchema.extend({
21
+ result: z.tuple([
22
+ z.object({
23
+ finalTranscript: z.string().describe('Accumulated final transcript text'),
24
+ interimTranscript: z.string().describe('Current interim transcript text'),
25
+ }).describe('Recognition result payload'),
26
+ ]).describe('Fires when speech recognition produces a result'),
27
+ error: z.tuple([z.string().describe('Error message from the recognizer')]).describe('Fires when speech recognition encounters an error'),
28
+ end: z.tuple([]).describe('Fires when speech recognition ends'),
29
+ start: z.tuple([]).describe('Fires when speech recognition starts listening'),
30
+ stop: z.tuple([]).describe('Fires when speech recognition is manually stopped'),
31
+ abort: z.tuple([]).describe('Fires when speech recognition is aborted'),
32
+ }).describe('VoiceRecognition events')
33
+
20
34
  /**
21
35
  * Speech-to-text recognition using the Web Speech API (SpeechRecognition).
22
36
  *
@@ -44,6 +58,7 @@ export class VoiceRecognition<T extends VoiceRecognitionState = VoiceRecognition
44
58
 
45
59
  static override stateSchema = VoiceRecognitionStateSchema
46
60
  static override optionsSchema = VoiceRecognitionOptionsSchema
61
+ static override eventsSchema = VoiceRecognitionEventsSchema
47
62
  static override shortcut = "features.voice" as const
48
63
 
49
64
  static { Feature.register(this as any, 'voice') }
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, spyOn } from 'bun:test'
2
2
  import { NodeContainer } from '../src/node/container'
3
- import { WebSocketClient, GraphClient } from '../src/client'
3
+ import { WebSocketClient } from '../src/clients/websocket'
4
+ import { GraphClient } from '../src/clients/graph'
4
5
 
5
6
  describe('Clients', () => {
6
7
  it('container has clients registry after construction', () => {
@@ -0,0 +1,267 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { Command, commands } from '../src/command'
3
+ import { graftModule, isNativeHelperClass } from '../src/graft'
4
+ import { NodeContainer } from '../src/node/container'
5
+ import { z } from 'zod'
6
+ import { CommandOptionsSchema } from '../src/schemas/base'
7
+ // Side-effect import to register built-in commands
8
+ import '../src/commands/index'
9
+
10
+ describe('graftModule', () => {
11
+ it('creates a Command subclass from a run export', () => {
12
+ const argsSchema = CommandOptionsSchema.extend({
13
+ file: z.string(),
14
+ })
15
+
16
+ const Grafted = graftModule(Command as any, {
17
+ description: 'Run a file',
18
+ argsSchema,
19
+ positionals: ['file'],
20
+ run: async (args: any) => {},
21
+ }, 'graft-run-test', 'commands')
22
+
23
+ expect((Grafted as any).shortcut).toBe('commands.graft-run-test')
24
+ expect((Grafted as any).description).toBe('Run a file')
25
+ expect((Grafted as any).commandDescription).toBe('Run a file')
26
+ expect((Grafted as any).positionals).toEqual(['file'])
27
+ expect((Grafted as any).argsSchema).toBe(argsSchema)
28
+ expect(Grafted.name).toBe('GraftRunTestCommand')
29
+ })
30
+
31
+ it('creates a Command subclass from a handler export (legacy)', () => {
32
+ const Grafted = graftModule(Command as any, {
33
+ description: 'Legacy handler',
34
+ handler: async (opts: any, ctx: any) => {},
35
+ }, 'graft-handler-test', 'commands')
36
+
37
+ expect((Grafted as any).shortcut).toBe('commands.graft-handler-test')
38
+ expect((Grafted as any).description).toBe('Legacy handler')
39
+ expect(typeof (Grafted as any).prototype.run).toBe('function')
40
+ })
41
+
42
+ it('grafts extra exported functions as prototype methods', () => {
43
+ const Grafted = graftModule(Command as any, {
44
+ run: async () => {},
45
+ formatOutput: function (data: any) { return JSON.stringify(data) },
46
+ }, 'graft-methods-test', 'commands')
47
+
48
+ expect(typeof (Grafted as any).prototype.formatOutput).toBe('function')
49
+ })
50
+
51
+ it('grafts getters onto the prototype', () => {
52
+ const Grafted = graftModule(Command as any, {
53
+ run: async () => {},
54
+ getters: {
55
+ isReady() { return true },
56
+ },
57
+ }, 'graft-getters-test', 'commands')
58
+
59
+ const desc = Object.getOwnPropertyDescriptor((Grafted as any).prototype, 'isReady')
60
+ expect(desc).toBeDefined()
61
+ expect(typeof desc!.get).toBe('function')
62
+ })
63
+ })
64
+
65
+ describe('isNativeHelperClass', () => {
66
+ it('returns true for a direct subclass', () => {
67
+ class MyCmd extends Command {}
68
+ expect(isNativeHelperClass(MyCmd, Command)).toBe(true)
69
+ })
70
+
71
+ it('returns false for a plain function', () => {
72
+ function notACommand() {}
73
+ expect(isNativeHelperClass(notACommand, Command)).toBe(false)
74
+ })
75
+
76
+ it('returns false for null/undefined', () => {
77
+ expect(isNativeHelperClass(null, Command)).toBe(false)
78
+ expect(isNativeHelperClass(undefined, Command)).toBe(false)
79
+ })
80
+
81
+ it('returns true for the base class itself', () => {
82
+ expect(isNativeHelperClass(Command, Command)).toBe(true)
83
+ })
84
+ })
85
+
86
+ describe('Command.register', () => {
87
+ it('registers a class-based command and sets shortcut', () => {
88
+ class TestDeployCommand extends Command {
89
+ static override description = 'Deploy to production'
90
+ }
91
+ Command.register(TestDeployCommand, 'test-deploy')
92
+
93
+ expect(commands.has('test-deploy')).toBe(true)
94
+ expect((TestDeployCommand as any).shortcut).toBe('commands.test-deploy')
95
+ expect((TestDeployCommand as any).commandDescription).toBe('Deploy to production')
96
+ })
97
+ })
98
+
99
+ describe('Command.dispatch', () => {
100
+ it('calls run() with parsed args for CLI dispatch', async () => {
101
+ let received: any = null
102
+
103
+ const argsSchema = CommandOptionsSchema.extend({
104
+ target: z.string().default('prod'),
105
+ })
106
+
107
+ const Grafted = graftModule(Command as any, {
108
+ argsSchema,
109
+ run: async (args: any, ctx: any) => { received = args },
110
+ }, 'dispatch-cli-test', 'commands')
111
+
112
+ commands.register('dispatch-cli-test', Grafted as any)
113
+ const container = new NodeContainer()
114
+ const cmd = container.command('dispatch-cli-test' as any)
115
+
116
+ await cmd.dispatch({ _: ['dispatch-cli-test'], target: 'staging' }, 'cli')
117
+
118
+ expect(received).toBeDefined()
119
+ expect(received.target).toBe('staging')
120
+ })
121
+
122
+ it('maps positionals to named args for CLI dispatch', async () => {
123
+ let received: any = null
124
+
125
+ const argsSchema = CommandOptionsSchema.extend({
126
+ file: z.string(),
127
+ })
128
+
129
+ const Grafted = graftModule(Command as any, {
130
+ argsSchema,
131
+ positionals: ['file'],
132
+ run: async (args: any, ctx: any) => { received = args },
133
+ }, 'dispatch-positional-test', 'commands')
134
+
135
+ commands.register('dispatch-positional-test', Grafted as any)
136
+ const container = new NodeContainer()
137
+ const cmd = container.command('dispatch-positional-test' as any)
138
+
139
+ // Simulate: luca dispatch-positional-test myfile.ts
140
+ // minimist produces: { _: ['dispatch-positional-test', 'myfile.ts'] }
141
+ await cmd.dispatch({ _: ['dispatch-positional-test', 'myfile.ts'] }, 'cli')
142
+
143
+ expect(received).toBeDefined()
144
+ expect(received.file).toBe('myfile.ts')
145
+ })
146
+
147
+ it('collects remaining positionals into an array when schema expects one', async () => {
148
+ let received: any = null
149
+
150
+ const argsSchema = CommandOptionsSchema.extend({
151
+ action: z.string(),
152
+ files: z.array(z.string()),
153
+ })
154
+
155
+ const Grafted = graftModule(Command as any, {
156
+ argsSchema,
157
+ positionals: ['action', 'files'],
158
+ run: async (args: any, ctx: any) => { received = args },
159
+ }, 'dispatch-glob-test', 'commands')
160
+
161
+ commands.register('dispatch-glob-test', Grafted as any)
162
+ const container = new NodeContainer()
163
+ const cmd = container.command('dispatch-glob-test' as any)
164
+
165
+ // Simulate: luca dispatch-glob-test process foo.md bar.md baz.md
166
+ // Shell expands *.md before luca sees it
167
+ await cmd.dispatch({ _: ['dispatch-glob-test', 'process', 'foo.md', 'bar.md', 'baz.md'] }, 'cli')
168
+
169
+ expect(received).toBeDefined()
170
+ expect(received.action).toBe('process')
171
+ expect(received.files).toEqual(['foo.md', 'bar.md', 'baz.md'])
172
+ })
173
+
174
+ it('passes named args through for headless dispatch', async () => {
175
+ let received: any = null
176
+
177
+ const argsSchema = CommandOptionsSchema.extend({
178
+ file: z.string(),
179
+ })
180
+
181
+ const Grafted = graftModule(Command as any, {
182
+ argsSchema,
183
+ positionals: ['file'],
184
+ run: async (args: any, ctx: any) => { received = args },
185
+ }, 'dispatch-headless-test', 'commands')
186
+
187
+ commands.register('dispatch-headless-test', Grafted as any)
188
+ const container = new NodeContainer()
189
+ const cmd = container.command('dispatch-headless-test' as any)
190
+
191
+ // Headless: named args directly, no positional mapping
192
+ await cmd.dispatch({ file: 'script.ts' }, 'headless')
193
+
194
+ expect(received).toBeDefined()
195
+ expect(received.file).toBe('script.ts')
196
+ })
197
+
198
+ it('captures stdout/stderr for headless dispatch', async () => {
199
+ const argsSchema = CommandOptionsSchema.extend({})
200
+
201
+ const Grafted = graftModule(Command as any, {
202
+ argsSchema,
203
+ run: async (args: any, ctx: any) => {
204
+ console.log('hello from command')
205
+ console.error('warning: something')
206
+ },
207
+ }, 'dispatch-capture-test', 'commands')
208
+
209
+ commands.register('dispatch-capture-test', Grafted as any)
210
+ const container = new NodeContainer()
211
+ const cmd = container.command('dispatch-capture-test' as any)
212
+
213
+ const result = await cmd.dispatch({}, 'headless')
214
+
215
+ expect(result).toBeDefined()
216
+ expect(result!.exitCode).toBe(0)
217
+ expect(result!.stdout).toContain('hello from command')
218
+ expect(result!.stderr).toContain('warning: something')
219
+ })
220
+
221
+ it('captures errors for headless dispatch', async () => {
222
+ const argsSchema = CommandOptionsSchema.extend({})
223
+
224
+ const Grafted = graftModule(Command as any, {
225
+ argsSchema,
226
+ run: async () => { throw new Error('boom') },
227
+ }, 'dispatch-error-test', 'commands')
228
+
229
+ commands.register('dispatch-error-test', Grafted as any)
230
+ const container = new NodeContainer()
231
+ const cmd = container.command('dispatch-error-test' as any)
232
+
233
+ const result = await cmd.dispatch({}, 'headless')
234
+
235
+ expect(result).toBeDefined()
236
+ expect(result!.exitCode).toBe(1)
237
+ expect(result!.stderr).toContain('boom')
238
+ })
239
+ })
240
+
241
+ describe('Command registry', () => {
242
+ it('has built-in commands registered', () => {
243
+ const container = new NodeContainer()
244
+ expect(container.commands.has('run')).toBe(true)
245
+ expect(container.commands.has('help')).toBe(true)
246
+ expect(container.commands.has('eval')).toBe(true)
247
+ expect(container.commands.has('chat')).toBe(true)
248
+ })
249
+
250
+ it('built-in commands still work through registerHandler', () => {
251
+ const container = new NodeContainer()
252
+ const RunClass = container.commands.lookup('run')
253
+ expect(typeof RunClass).toBe('function')
254
+ expect(typeof RunClass.prototype.run).toBe('function')
255
+ })
256
+ })
257
+
258
+ describe('SimpleCommand type', () => {
259
+ it('grafted commands have positionals as a static property', () => {
260
+ const Grafted = graftModule(Command as any, {
261
+ positionals: ['env', 'region'],
262
+ run: async () => {},
263
+ }, 'simple-cmd-type-test', 'commands')
264
+
265
+ expect((Grafted as any).positionals).toEqual(['env', 'region'])
266
+ })
267
+ })
@@ -51,20 +51,16 @@ describe('Assistants Manager Integration', () => {
51
51
  })
52
52
 
53
53
  it('discovers assistants from directory', () => {
54
- const manager = container.feature('assistantsManager', {
55
- folder: 'assistants',
56
- })
57
- manager.discover()
54
+ const manager = container.feature('assistantsManager')
55
+ await manager.discover()
58
56
 
59
57
  const list = manager.list()
60
58
  expect(list.length).toBe(3)
61
59
  })
62
60
 
63
61
  it('returns correct entry metadata', () => {
64
- const manager = container.feature('assistantsManager', {
65
- folder: 'assistants',
66
- })
67
- manager.discover()
62
+ const manager = container.feature('assistantsManager')
63
+ await manager.discover()
68
64
 
69
65
  const entryA = manager.get('assistant-a')
70
66
  expect(entryA).toBeDefined()
@@ -84,10 +80,8 @@ describe('Assistants Manager Integration', () => {
84
80
  })
85
81
 
86
82
  it('creates an assistant instance', () => {
87
- const manager = container.feature('assistantsManager', {
88
- folder: 'assistants',
89
- })
90
- manager.discover()
83
+ const manager = container.feature('assistantsManager')
84
+ await manager.discover()
91
85
 
92
86
  const assistant = manager.create('assistant-a', {
93
87
  model: 'gpt-4o-mini',
@@ -99,10 +93,8 @@ describe('Assistants Manager Integration', () => {
99
93
  })
100
94
 
101
95
  it('generates a summary listing', () => {
102
- const manager = container.feature('assistantsManager', {
103
- folder: 'assistants',
104
- })
105
- manager.discover()
96
+ const manager = container.feature('assistantsManager')
97
+ await manager.discover()
106
98
 
107
99
  const summary = manager.toSummary()
108
100
  expect(typeof summary).toBe('string')
@@ -112,10 +104,8 @@ describe('Assistants Manager Integration', () => {
112
104
  })
113
105
 
114
106
  it('get returns undefined for non-existent assistant', () => {
115
- const manager = container.feature('assistantsManager', {
116
- folder: 'assistants',
117
- })
118
- manager.discover()
107
+ const manager = container.feature('assistantsManager')
108
+ await manager.discover()
119
109
 
120
110
  const entry = manager.get('non-existent')
121
111
  expect(entry).toBeUndefined()
@@ -0,0 +1 @@
1
+ {"id":"d587b833-0933-4782-8c20-ad1c28d12c7a","title":"Untitled","model":"gpt-5.2","tags":[],"thread":"assistant:zapldb:2026-03-15","createdAt":"2026-03-15T03:08:58.783Z","updatedAt":"2026-03-15T03:08:58.783Z","messageCount":5,"metadata":{}}
@@ -0,0 +1 @@
1
+ {"id":"d587b833-0933-4782-8c20-ad1c28d12c7a","title":"Untitled","model":"gpt-5.2","messages":[{"role":"system","content":"# Luca Assistant Example\n\nYou are currently an example / template \"Assistant\" provided by the Luca framework. ( You'll probably have no idea what that is, don't worry, it doesn't matter ).\n\nYou are what gets scaffolded when a user writes the `luca scaffold assistant` command.\n\nIn luca, an Assistant is backed by a folder which has a few components:\n\n- CORE.md -- this is a markdown file that will get injected into the system prompt of a chat completion call\n- tools.ts -- this file is expected to export functions, and a schemas object whose keys are the names of the functions that get exported, and whose values are zod v4 schemas that describe the parameters\n- hooks.ts -- this file is expexted to export functions, whose names match the events emitted by the luca assistant helper\n\nCurrently, the user is chatting with you from the `luca chat` CLI. \n\nYou should tell them what each of these files is and how to edit them.\n\nIt is also important for them to know that the luca `container` is globally available for them in the context of the `tools.ts` and `hooks.ts` files."},{"role":"user","content":"What should we do?"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_bwGYIZLkPRtuqUMVRAg8ykPy","type":"function","function":{"name":"README","arguments":"{}"}}]},{"role":"tool","tool_call_id":"call_bwGYIZLkPRtuqUMVRAg8ykPy","content":"YO YO"},{"role":"assistant","content":"You’ll typically do three things: customize the assistant’s behavior (CORE.md), add capabilities (tools.ts), and wire up lifecycle events (hooks.ts).\n\n## 1) Edit `CORE.md` (behavior + policy)\n**What it is:** A markdown file whose contents get injected into the **system prompt** at runtime. This is where you define the assistant’s identity, boundaries, style, domain knowledge, do/don’t rules, and any project-specific instructions.\n\n**What to put in it:**\n- Role + purpose (“You are an assistant that helps with X…”)\n- Tone/format preferences (concise, code-first, etc.)\n- Hard constraints (what it must never do, how it handles secrets, etc.)\n- Domain context (your product, APIs, repos, conventions)\n\n**When to edit:** Any time you want the assistant to “think/behave differently” without changing code.\n\n---\n\n## 2) Edit `tools.ts` (add callable functions)\n**What it is:** A TypeScript module that exports:\n- **functions** the model can call as tools (e.g., `searchDocs`, `runQuery`, `readFile`, `createTicket`)\n- a **`schemas` object** mapping tool/function names → **zod v4 schemas** describing parameters\n\n**Why it matters:** Tools are how your assistant safely does real work (fetch data, manipulate files, call APIs) instead of guessing.\n\n**Key points:**\n- Keep tools small and composable.\n- Validate all inputs with zod schemas.\n- Return structured data the model can use.\n\n**Important:** The luca **`container`** is globally available inside `tools.ts`. Use it to access shared services/config (whatever your luca setup provides) without manually importing/constructing everything.\n\n---\n\n## 3) Edit `hooks.ts` (react to events)\n**What it is:** A TypeScript module exporting functions whose names match luca assistant helper **events** (lifecycle hooks). These run automatically when those events fire (e.g., session start, before/after tool call, etc.—depending on what luca emits in your setup).\n\n**Use cases:**\n- Logging / tracing\n- Metrics\n- Tool-call auditing\n- Injecting/refreshing context\n- Guardrails (block certain actions, redact secrets)\n\n**Important:** The luca **`container`** is also globally available in `hooks.ts`.\n\n---\n\n## What to do next (practical plan)\n1) **Open `CORE.md`** and write 10–30 lines that clearly define the assistant’s job and constraints.\n2) **Add 1–2 tools** in `tools.ts` that reflect what you actually want the assistant to be able to do (start minimal).\n3) **Add a basic hook** in `hooks.ts` for logging (so you can see what’s happening and iterate safely).\n4) Run `luca chat` again and test; tighten instructions/schemas based on failures.\n\nIf you tell me what your assistant is supposed to help with (e.g., “triage support tickets”, “write PRs”, “query an internal API”), I can suggest exactly what to put into `CORE.md` and the first tools/hooks to implement."}],"tags":[],"thread":"assistant:zapldb:2026-03-15","createdAt":"2026-03-15T03:08:58.783Z","updatedAt":"2026-03-15T03:08:58.783Z","messageCount":5,"metadata":{}}
@@ -0,0 +1,2 @@
1
+
2
+ 0633d48140563c44b78c1c0b9ff1c1fc5d79b689 {"key":"conversation-history:__index__","integrity":"sha512-G7XHWyh5TwD5TE1gmpiXjpQg6bcUbSBKf79bCzBHcpJYFwXSB8AQDauqwn7vVAqq7OM3SvdRBKkyGdTsi/tE5w==","time":1773544138800,"size":40}
@@ -0,0 +1,2 @@
1
+
2
+ db975a1cc1e358a00d66dab7a468db4ee63eb62d {"key":"conversation-history:meta:d587b833-0933-4782-8c20-ad1c28d12c7a","integrity":"sha512-2t8dkM5OBCq+sDWhl4MsbWiTQgp0egVr53PrAOT3RaA31QXI2xPd59NrNra4k63bt98PX+nwwT5mXyAFZEcxiw==","time":1773544138799,"size":240}
@@ -0,0 +1,2 @@
1
+
2
+ 74e795aeefad56844bf7be3dc434e7e99a145283 {"key":"conversation-history:d587b833-0933-4782-8c20-ad1c28d12c7a","integrity":"sha512-7QTh0MKljC2ymzkhyir/s+pP6+gxxTs468IQGft5mCOrpu1bRhGHPSzSXUItSZVbhSqcMm2g1niJm8HCwpYJAQ==","time":1773544138797,"size":4603}
@@ -1,145 +0,0 @@
1
- # LauncherAppCommandListener (features.launcherAppCommandListener)
2
-
3
- LauncherAppCommandListener — IPC transport for commands from the LucaVoiceLauncher app Listens on a Unix domain socket for the native macOS launcher app to connect. When a command event arrives (voice, hotkey, text input), it wraps it in a `CommandHandle` and emits a `command` event. The consumer is responsible for acknowledging, processing, and finishing the command via the handle. Uses NDJSON (newline-delimited JSON) over the socket per the CLIENT_SPEC protocol.
4
-
5
- ## Usage
6
-
7
- ```ts
8
- container.feature('launcherAppCommandListener', {
9
- // Path to the Unix domain socket to listen on
10
- socketPath,
11
- // Automatically start listening when the feature is enabled
12
- autoListen,
13
- })
14
- ```
15
-
16
- ## Options (Zod v4 schema)
17
-
18
- | Property | Type | Description |
19
- |----------|------|-------------|
20
- | `socketPath` | `string` | Path to the Unix domain socket to listen on |
21
- | `autoListen` | `boolean` | Automatically start listening when the feature is enabled |
22
-
23
- ## Methods
24
-
25
- ### enable
26
-
27
- **Parameters:**
28
-
29
- | Name | Type | Required | Description |
30
- |------|------|----------|-------------|
31
- | `options` | `any` | | Parameter options |
32
-
33
- **Returns:** `Promise<this>`
34
-
35
-
36
-
37
- ### listen
38
-
39
- Start listening on the Unix domain socket for the native app to connect. Fire-and-forget — binds the socket and returns immediately. Sits quietly until the native app connects; does nothing visible if it never does.
40
-
41
- **Parameters:**
42
-
43
- | Name | Type | Required | Description |
44
- |------|------|----------|-------------|
45
- | `socketPath` | `string` | | Override the configured socket path |
46
-
47
- **Returns:** `this`
48
-
49
-
50
-
51
- ### stop
52
-
53
- Stop the IPC server and clean up all connections.
54
-
55
- **Returns:** `Promise<this>`
56
-
57
-
58
-
59
- ### send
60
-
61
- Write an NDJSON message to the connected app client.
62
-
63
- **Parameters:**
64
-
65
- | Name | Type | Required | Description |
66
- |------|------|----------|-------------|
67
- | `msg` | `Record<string, any>` | ✓ | The message object to send (will be JSON-serialized + newline) |
68
-
69
- **Returns:** `boolean`
70
-
71
-
72
-
73
- ## Getters
74
-
75
- | Property | Type | Description |
76
- |----------|------|-------------|
77
- | `isListening` | `boolean` | Whether the IPC server is currently listening. |
78
- | `isClientConnected` | `boolean` | Whether the native app client is currently connected. |
79
-
80
- ## Events (Zod v4 schema)
81
-
82
- ### listening
83
-
84
- Event emitted by LauncherAppCommandListener
85
-
86
-
87
-
88
- ### clientConnected
89
-
90
- Event emitted by LauncherAppCommandListener
91
-
92
-
93
-
94
- ### clientDisconnected
95
-
96
- Event emitted by LauncherAppCommandListener
97
-
98
-
99
-
100
- ### command
101
-
102
- Event emitted by LauncherAppCommandListener
103
-
104
-
105
-
106
- ### message
107
-
108
- Event emitted by LauncherAppCommandListener
109
-
110
-
111
-
112
- ## State (Zod v4 schema)
113
-
114
- | Property | Type | Description |
115
- |----------|------|-------------|
116
- | `enabled` | `boolean` | Whether this feature is currently enabled |
117
- | `listening` | `boolean` | Whether the IPC server is listening |
118
- | `clientConnected` | `boolean` | Whether the native launcher app is connected |
119
- | `socketPath` | `string` | The socket path in use |
120
- | `commandsReceived` | `number` | Total number of commands received |
121
- | `lastCommandText` | `string` | The text of the last received command |
122
- | `lastError` | `string` | Last error message |
123
-
124
- ## Examples
125
-
126
- **features.launcherAppCommandListener**
127
-
128
- ```ts
129
- const listener = container.feature('launcherAppCommandListener', {
130
- enable: true,
131
- autoListen: true,
132
- })
133
-
134
- listener.on('command', async (cmd) => {
135
- cmd.ack('Working on it!') // or just cmd.ack() for silent
136
-
137
- // ... do your actual work ...
138
- cmd.progress(0.5, 'Halfway there')
139
-
140
- cmd.finish() // silent finish
141
- cmd.finish({ result: { action: 'completed' }, speech: 'All done!' })
142
- // or: cmd.fail({ error: 'not found', speech: 'Sorry, that failed.' })
143
- })
144
- ```
145
-
@@ -1,120 +0,0 @@
1
- ---
2
- title: "Launcher App Command Listener"
3
- tags: [launcherAppCommandListener, ipc, macos, voice, commands]
4
- lastTested: null
5
- lastTestPassed: null
6
- ---
7
-
8
- # launcherAppCommandListener
9
-
10
- IPC transport for receiving commands from the LucaVoiceLauncher macOS app. Listens on a Unix domain socket using NDJSON and wraps incoming commands in a `CommandHandle` for structured acknowledgement, progress, and completion.
11
-
12
- ## Overview
13
-
14
- Use the `launcherAppCommandListener` feature when you need to receive and process commands from the native macOS launcher app (voice commands, hotkeys, or text input). It provides the server side of the IPC protocol: listen for the app to connect, receive command events, and respond with acknowledgements, progress updates, and results.
15
-
16
- Requires the LucaVoiceLauncher native macOS app to be running.
17
-
18
- ## Enabling the Feature
19
-
20
- ```ts
21
- const listener = container.feature('launcherAppCommandListener', {
22
- autoListen: false
23
- })
24
- console.log('Command Listener feature created')
25
- console.log('Listening:', listener.isListening)
26
- console.log('Client connected:', listener.isClientConnected)
27
- ```
28
-
29
- ## API Documentation
30
-
31
- ```ts
32
- const info = await container.features.describe('launcherAppCommandListener')
33
- console.log(info)
34
- ```
35
-
36
- ## Listening for Commands
37
-
38
- Start the IPC server and handle incoming commands with the `CommandHandle` API.
39
-
40
- ```ts skip
41
- const listener = container.feature('launcherAppCommandListener', {
42
- autoListen: true
43
- })
44
-
45
- listener.on('command', async (cmd) => {
46
- console.log('Received command:', cmd.text)
47
-
48
- cmd.ack('Working on it!')
49
- // ... process the command ...
50
- cmd.progress(0.5, 'Halfway there')
51
- // ... finish processing ...
52
- cmd.finish({ result: { action: 'completed' }, speech: 'All done!' })
53
- })
54
- ```
55
-
56
- Each incoming command is wrapped in a `CommandHandle` that provides `ack()`, `progress()`, `finish()`, and `fail()` methods. The native app displays acknowledgements and speaks the `speech` text.
57
-
58
- ## Command Handle Lifecycle
59
-
60
- The `CommandHandle` follows a structured lifecycle: acknowledge, optionally report progress, then finish or fail.
61
-
62
- ```ts skip
63
- listener.on('command', async (cmd) => {
64
- // Silent acknowledge
65
- cmd.ack()
66
-
67
- try {
68
- // Report progress (0-1 scale)
69
- cmd.progress(0.25, 'Starting...')
70
- cmd.progress(0.75, 'Almost done...')
71
-
72
- // Finish with result and optional speech
73
- cmd.finish({
74
- result: { data: 'some result' },
75
- speech: 'Task completed successfully'
76
- })
77
- } catch (err) {
78
- // Report failure with optional speech
79
- cmd.fail({
80
- error: err.message,
81
- speech: 'Sorry, that failed.'
82
- })
83
- }
84
- })
85
- ```
86
-
87
- The native app uses the `speech` field for text-to-speech feedback to the user.
88
-
89
- ## Connection Events
90
-
91
- Monitor the IPC connection state.
92
-
93
- ```ts skip
94
- listener.on('listening', () => {
95
- console.log('IPC server listening on:', listener.state.socketPath)
96
- })
97
- listener.on('clientConnected', () => {
98
- console.log('Launcher app connected')
99
- })
100
- listener.on('clientDisconnected', () => {
101
- console.log('Launcher app disconnected')
102
- })
103
- ```
104
-
105
- The `clientConnected` and `clientDisconnected` events fire as the native app connects and disconnects from the socket.
106
-
107
- ## Sending Messages
108
-
109
- Send arbitrary NDJSON messages back to the connected app.
110
-
111
- ```ts skip
112
- listener.send({ status: 'ready', message: 'Luca is online' })
113
- listener.send({ notification: 'Task queue empty' })
114
- ```
115
-
116
- Use `send()` for custom protocol messages beyond the standard command lifecycle.
117
-
118
- ## Summary
119
-
120
- The `launcherAppCommandListener` feature provides the IPC server for the LucaVoiceLauncher app. It receives voice, hotkey, and text commands, wrapping each in a `CommandHandle` with structured lifecycle methods (ack, progress, finish, fail). Key methods: `listen()`, `stop()`, `send()`. Key events: `command`, `clientConnected`, `clientDisconnected`.