@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.
- package/CLAUDE.md +10 -1
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +540 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +181 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +225 -2
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +33 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +20 -0
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
- package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
["d587b833-0933-4782-8c20-ad1c28d12c7a"]
|
|
@@ -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":{}}
|
|
@@ -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`.
|