@soederpop/luca 0.0.6 → 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
|
@@ -37,6 +37,19 @@ export class Opener extends Feature {
|
|
|
37
37
|
static override optionsSchema = FeatureOptionsSchema
|
|
38
38
|
static { Feature.register(this, 'opener') }
|
|
39
39
|
|
|
40
|
+
private _binCache: Record<string, string> = {}
|
|
41
|
+
|
|
42
|
+
/** Resolve a binary path via `which`, caching the result. */
|
|
43
|
+
private resolveBin(name: string): string {
|
|
44
|
+
if (this._binCache[name]) return this._binCache[name]
|
|
45
|
+
try {
|
|
46
|
+
this._binCache[name] = this.container.proc.exec(`which ${name}`).trim()
|
|
47
|
+
} catch {
|
|
48
|
+
this._binCache[name] = name
|
|
49
|
+
}
|
|
50
|
+
return this._binCache[name]
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
/**
|
|
41
54
|
* Opens a path or URL with the appropriate application.
|
|
42
55
|
*
|
|
@@ -62,18 +75,18 @@ export class Opener extends Feature {
|
|
|
62
75
|
const proc = this.container.proc
|
|
63
76
|
|
|
64
77
|
if (platform === 'darwin') {
|
|
65
|
-
await proc.spawnAndCapture('open', ['-a', 'Google Chrome', url])
|
|
78
|
+
await proc.spawnAndCapture(this.resolveBin('open'), ['-a', 'Google Chrome', url])
|
|
66
79
|
} else if (platform === 'win32') {
|
|
67
|
-
await proc.spawnAndCapture('cmd', ['/c', 'start', 'chrome', url])
|
|
80
|
+
await proc.spawnAndCapture(this.resolveBin('cmd'), ['/c', 'start', 'chrome', url])
|
|
68
81
|
} else {
|
|
69
82
|
// Linux - try google-chrome, then chromium, then fall back to xdg-open
|
|
70
83
|
try {
|
|
71
|
-
await proc.spawnAndCapture('google-chrome', [url])
|
|
84
|
+
await proc.spawnAndCapture(this.resolveBin('google-chrome'), [url])
|
|
72
85
|
} catch {
|
|
73
86
|
try {
|
|
74
|
-
await proc.spawnAndCapture('chromium', [url])
|
|
87
|
+
await proc.spawnAndCapture(this.resolveBin('chromium'), [url])
|
|
75
88
|
} catch {
|
|
76
|
-
await proc.spawnAndCapture('xdg-open', [url])
|
|
89
|
+
await proc.spawnAndCapture(this.resolveBin('xdg-open'), [url])
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
}
|
|
@@ -93,11 +106,11 @@ export class Opener extends Feature {
|
|
|
93
106
|
const proc = this.container.proc
|
|
94
107
|
|
|
95
108
|
if (platform === 'darwin') {
|
|
96
|
-
await proc.spawnAndCapture('open', ['-a', name])
|
|
109
|
+
await proc.spawnAndCapture(this.resolveBin('open'), ['-a', name])
|
|
97
110
|
} else if (platform === 'win32') {
|
|
98
|
-
await proc.spawnAndCapture('cmd', ['/c', 'start', '', name])
|
|
111
|
+
await proc.spawnAndCapture(this.resolveBin('cmd'), ['/c', 'start', '', name])
|
|
99
112
|
} else {
|
|
100
|
-
await proc.spawnAndCapture(name.toLowerCase(), [])
|
|
113
|
+
await proc.spawnAndCapture(this.resolveBin(name.toLowerCase()), [])
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
|
|
@@ -113,10 +126,10 @@ export class Opener extends Feature {
|
|
|
113
126
|
const proc = this.container.proc
|
|
114
127
|
|
|
115
128
|
try {
|
|
116
|
-
await proc.spawnAndCapture('code', [path])
|
|
129
|
+
await proc.spawnAndCapture(this.resolveBin('code'), [path])
|
|
117
130
|
} catch {
|
|
118
131
|
if (this.container.os.platform === 'darwin') {
|
|
119
|
-
await proc.spawnAndCapture('open', ['-a', 'Visual Studio Code', path])
|
|
132
|
+
await proc.spawnAndCapture(this.resolveBin('open'), ['-a', 'Visual Studio Code', path])
|
|
120
133
|
} else {
|
|
121
134
|
throw new Error('VS Code CLI (code) not found. Install it from VS Code: Command Palette > "Shell Command: Install code command in PATH"')
|
|
122
135
|
}
|
|
@@ -135,10 +148,10 @@ export class Opener extends Feature {
|
|
|
135
148
|
const proc = this.container.proc
|
|
136
149
|
|
|
137
150
|
try {
|
|
138
|
-
await proc.spawnAndCapture('cursor', [path])
|
|
151
|
+
await proc.spawnAndCapture(this.resolveBin('cursor'), [path])
|
|
139
152
|
} catch {
|
|
140
153
|
if (this.container.os.platform === 'darwin') {
|
|
141
|
-
await proc.spawnAndCapture('open', ['-a', 'Cursor', path])
|
|
154
|
+
await proc.spawnAndCapture(this.resolveBin('open'), ['-a', 'Cursor', path])
|
|
142
155
|
} else {
|
|
143
156
|
throw new Error('Cursor CLI (cursor) not found. Install it from Cursor: Command Palette > "Shell Command: Install cursor command in PATH"')
|
|
144
157
|
}
|
|
@@ -149,11 +162,11 @@ export class Opener extends Feature {
|
|
|
149
162
|
const proc = this.container.proc
|
|
150
163
|
|
|
151
164
|
if (platform === 'darwin') {
|
|
152
|
-
await proc.spawnAndCapture('open', [target])
|
|
165
|
+
await proc.spawnAndCapture(this.resolveBin('open'), [target])
|
|
153
166
|
} else if (platform === 'win32') {
|
|
154
|
-
await proc.spawnAndCapture('cmd', ['/c', 'start', '', target])
|
|
167
|
+
await proc.spawnAndCapture(this.resolveBin('cmd'), ['/c', 'start', '', target])
|
|
155
168
|
} else {
|
|
156
|
-
await proc.spawnAndCapture('xdg-open', [target])
|
|
169
|
+
await proc.spawnAndCapture(this.resolveBin('xdg-open'), [target])
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
}
|
package/src/node/features/os.ts
CHANGED
|
@@ -2,6 +2,16 @@ import { Feature } from '../feature.js'
|
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
3
|
import os from 'os'
|
|
4
4
|
|
|
5
|
+
/** Information about a connected display. */
|
|
6
|
+
export interface DisplayInfo {
|
|
7
|
+
name: string
|
|
8
|
+
resolution: { width: number; height: number }
|
|
9
|
+
retina: boolean
|
|
10
|
+
main: boolean
|
|
11
|
+
refreshRate?: number
|
|
12
|
+
connectionType?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* The OS feature provides access to operating system utilities and information.
|
|
7
17
|
*
|
|
@@ -153,6 +163,72 @@ export class OS extends Feature {
|
|
|
153
163
|
get macAddresses() : string[] {
|
|
154
164
|
return Object.values(this.networkInterfaces).flat().filter(v => typeof v !== 'undefined' && v.internal === false && v.family === 'IPv4').map(v => v?.mac!).filter(Boolean)
|
|
155
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Gets information about all connected displays.
|
|
169
|
+
*
|
|
170
|
+
* Platform-specific: currently implemented for macOS (darwin).
|
|
171
|
+
* Linux and Windows will throw with a clear "not yet implemented" message.
|
|
172
|
+
*
|
|
173
|
+
* @returns {DisplayInfo[]} Array of display information objects
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const displays = os.getDisplayInfo()
|
|
178
|
+
* displays.forEach(d => {
|
|
179
|
+
* console.log(`${d.name}: ${d.resolution.width}x${d.resolution.height}${d.retina ? ' (Retina)' : ''}`)
|
|
180
|
+
* })
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
getDisplayInfo(): DisplayInfo[] {
|
|
184
|
+
const platform = this.platform as NodeJS.Platform
|
|
185
|
+
const handler = this._displayHandlers[platform]
|
|
186
|
+
|
|
187
|
+
if (!handler) {
|
|
188
|
+
throw new Error(`getDisplayInfo() is not yet implemented for platform: ${platform}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return handler()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _displayHandlers: Partial<Record<NodeJS.Platform, () => DisplayInfo[]>> = {
|
|
195
|
+
darwin: (): DisplayInfo[] => {
|
|
196
|
+
const proc = this.container.feature('proc')
|
|
197
|
+
const raw = proc.exec('system_profiler SPDisplaysDataType -json')
|
|
198
|
+
const data = JSON.parse(raw)
|
|
199
|
+
const displays: DisplayInfo[] = []
|
|
200
|
+
|
|
201
|
+
for (const gpu of data.SPDisplaysDataType ?? []) {
|
|
202
|
+
for (const d of gpu.spdisplays_ndrvs ?? []) {
|
|
203
|
+
const resStr: string = d._spdisplays_resolution ?? ''
|
|
204
|
+
const resMatch = resStr.match(/(\d+)\s*x\s*(\d+)/)
|
|
205
|
+
const hzMatch = resStr.match(/@\s*([\d.]+)\s*Hz/i)
|
|
206
|
+
|
|
207
|
+
displays.push({
|
|
208
|
+
name: d._name ?? 'Unknown',
|
|
209
|
+
resolution: {
|
|
210
|
+
width: resMatch ? parseInt(resMatch[1], 10) : 0,
|
|
211
|
+
height: resMatch ? parseInt(resMatch[2], 10) : 0,
|
|
212
|
+
},
|
|
213
|
+
retina: /retina/i.test(d._spdisplays_resolution ?? '') || /retina/i.test(d.spdisplays_display_type ?? ''),
|
|
214
|
+
main: d.spdisplays_main === 'spdisplays_yes' || /yes/i.test(d.spdisplays_main ?? ''),
|
|
215
|
+
refreshRate: hzMatch ? parseFloat(hzMatch[1]) : undefined,
|
|
216
|
+
connectionType: d.spdisplays_connection_type ?? undefined,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return displays
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
linux: (): DisplayInfo[] => {
|
|
225
|
+
throw new Error('getDisplayInfo() is not yet implemented for Linux')
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
win32: (): DisplayInfo[] => {
|
|
229
|
+
throw new Error('getDisplayInfo() is not yet implemented for Windows')
|
|
230
|
+
},
|
|
231
|
+
}
|
|
156
232
|
}
|
|
157
233
|
|
|
158
234
|
export default OS
|
|
@@ -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 * as ngrok from '@ngrok/ngrok'
|
|
4
4
|
import { Feature } from '../../feature.js'
|
|
5
5
|
|
|
@@ -49,6 +49,15 @@ export const PortExposerOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
49
49
|
})
|
|
50
50
|
export type PortExposerOptions = z.infer<typeof PortExposerOptionsSchema>
|
|
51
51
|
|
|
52
|
+
export const PortExposerEventsSchema = FeatureEventsSchema.extend({
|
|
53
|
+
exposed: z.tuple([z.object({
|
|
54
|
+
publicUrl: z.string().optional().describe('The public ngrok URL'),
|
|
55
|
+
localPort: z.number().describe('The local port being exposed'),
|
|
56
|
+
}).describe('Exposure details')]).describe('When a local port is successfully exposed via ngrok'),
|
|
57
|
+
closed: z.tuple([]).describe('When the ngrok tunnel is closed'),
|
|
58
|
+
error: z.tuple([z.any().describe('The error object')]).describe('When an ngrok operation fails'),
|
|
59
|
+
}).describe('Port Exposer events')
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Port Exposer Feature
|
|
54
63
|
*
|
|
@@ -81,6 +90,7 @@ export class PortExposer extends Feature<PortExposerState, PortExposerOptions> {
|
|
|
81
90
|
static override shortcut = 'portExposer' as const
|
|
82
91
|
static override stateSchema = PortExposerStateSchema
|
|
83
92
|
static override optionsSchema = PortExposerOptionsSchema
|
|
93
|
+
static override eventsSchema = PortExposerEventsSchema
|
|
84
94
|
static { Feature.register(this, 'portExposer') }
|
|
85
95
|
|
|
86
96
|
private ngrokListener?: ngrok.Listener
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { SQL } from 'bun'
|
|
3
3
|
import { Feature } from '../feature.js'
|
|
4
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
4
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
5
5
|
import type { ContainerContext } from '../../container.js'
|
|
6
6
|
|
|
7
7
|
type SqlValue = string | number | boolean | bigint | Uint8Array | Buffer | null
|
|
@@ -21,6 +21,21 @@ export const PostgresOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
21
21
|
export type PostgresState = z.infer<typeof PostgresStateSchema>
|
|
22
22
|
export type PostgresOptions = z.infer<typeof PostgresOptionsSchema>
|
|
23
23
|
|
|
24
|
+
export const PostgresEventsSchema = FeatureEventsSchema.extend({
|
|
25
|
+
query: z.tuple([
|
|
26
|
+
z.string().describe('The SQL query text'),
|
|
27
|
+
z.array(z.any()).describe('Bound parameter values'),
|
|
28
|
+
z.number().describe('Number of rows returned'),
|
|
29
|
+
]).describe('When a SELECT-like query is executed'),
|
|
30
|
+
execute: z.tuple([
|
|
31
|
+
z.string().describe('The SQL statement text'),
|
|
32
|
+
z.array(z.any()).describe('Bound parameter values'),
|
|
33
|
+
z.number().describe('Number of rows affected'),
|
|
34
|
+
]).describe('When a write/update/delete statement is executed'),
|
|
35
|
+
error: z.tuple([z.any().describe('The error object')]).describe('When a postgres operation fails'),
|
|
36
|
+
closed: z.tuple([]).describe('When the postgres connection is closed'),
|
|
37
|
+
}).describe('Postgres events')
|
|
38
|
+
|
|
24
39
|
/**
|
|
25
40
|
* Postgres feature for safe SQL execution through Bun's native SQL client.
|
|
26
41
|
*
|
|
@@ -46,6 +61,7 @@ export class Postgres extends Feature<PostgresState, PostgresOptions> {
|
|
|
46
61
|
static override shortcut = 'features.postgres' as const
|
|
47
62
|
static override stateSchema = PostgresStateSchema
|
|
48
63
|
static override optionsSchema = PostgresOptionsSchema
|
|
64
|
+
static override eventsSchema = PostgresEventsSchema
|
|
49
65
|
static { Feature.register(this, 'postgres') }
|
|
50
66
|
|
|
51
67
|
private _client: SQL
|
|
@@ -22,6 +22,8 @@ interface SpawnOptions {
|
|
|
22
22
|
onOutput?: (data: string) => void;
|
|
23
23
|
/** Callback invoked when the process exits */
|
|
24
24
|
onExit?: (code: number) => void;
|
|
25
|
+
/** Callback invoked when the process starts */
|
|
26
|
+
onStart?: (childProcess: ChildProcess) => void;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
interface RawSpawnOptions {
|
|
@@ -69,6 +71,7 @@ export class ChildProcess extends Feature {
|
|
|
69
71
|
static override shortcut = "features.proc" as const
|
|
70
72
|
static override stateSchema = FeatureStateSchema
|
|
71
73
|
static override optionsSchema = FeatureOptionsSchema
|
|
74
|
+
// @ts-ignore TODO: fix this
|
|
72
75
|
static { Feature.register(this, 'proc') }
|
|
73
76
|
|
|
74
77
|
/**
|
|
@@ -184,7 +187,7 @@ export class ChildProcess extends Feature {
|
|
|
184
187
|
const childProcess = proc.childProcess!;
|
|
185
188
|
|
|
186
189
|
if (typeof options?.onStart === 'function') {
|
|
187
|
-
options.onStart(childProcess)
|
|
190
|
+
options.onStart(childProcess as any)
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
if (childProcess.stdout && childProcess.stderr) {
|
|
@@ -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 { existsSync } from 'fs';
|
|
5
5
|
import { join, resolve } from 'path';
|
|
@@ -31,6 +31,46 @@ export const PythonOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
31
31
|
export type PythonState = z.infer<typeof PythonStateSchema>
|
|
32
32
|
export type PythonOptions = z.infer<typeof PythonOptionsSchema>
|
|
33
33
|
|
|
34
|
+
export const PythonEventsSchema = FeatureEventsSchema.extend({
|
|
35
|
+
ready: z.tuple([]).describe('When the Python environment is ready for execution'),
|
|
36
|
+
environmentDetected: z.tuple([z.object({
|
|
37
|
+
pythonPath: z.string().nullable().describe('Path to the detected Python executable'),
|
|
38
|
+
environmentType: z.enum(['uv', 'conda', 'venv', 'system']).nullable().describe('Detected environment type'),
|
|
39
|
+
}).describe('Environment detection result')]).describe('When the Python environment type is detected'),
|
|
40
|
+
installingDependencies: z.tuple([z.object({
|
|
41
|
+
command: z.string().describe('The install command being run'),
|
|
42
|
+
}).describe('Install details')]).describe('When dependency installation begins'),
|
|
43
|
+
dependenciesInstalled: z.tuple([z.object({
|
|
44
|
+
stdout: z.string().describe('Standard output from install'),
|
|
45
|
+
stderr: z.string().describe('Standard error from install'),
|
|
46
|
+
exitCode: z.number().describe('Process exit code'),
|
|
47
|
+
}).describe('Install result')]).describe('When dependencies are successfully installed'),
|
|
48
|
+
dependencyInstallFailed: z.tuple([z.object({
|
|
49
|
+
stdout: z.string().describe('Standard output from install'),
|
|
50
|
+
stderr: z.string().describe('Standard error from install'),
|
|
51
|
+
exitCode: z.number().describe('Process exit code'),
|
|
52
|
+
}).describe('Install result')]).describe('When dependency installation fails'),
|
|
53
|
+
codeExecuted: z.tuple([z.object({
|
|
54
|
+
code: z.string().describe('The Python code that was executed'),
|
|
55
|
+
variables: z.record(z.any()).describe('Variables passed to the execution'),
|
|
56
|
+
result: z.object({
|
|
57
|
+
stdout: z.string().describe('Standard output'),
|
|
58
|
+
stderr: z.string().describe('Standard error'),
|
|
59
|
+
exitCode: z.number().describe('Process exit code'),
|
|
60
|
+
}).describe('Execution result'),
|
|
61
|
+
}).describe('Code execution details')]).describe('When Python code finishes executing'),
|
|
62
|
+
fileExecuted: z.tuple([z.object({
|
|
63
|
+
filePath: z.string().describe('Path to the executed Python file'),
|
|
64
|
+
variables: z.record(z.any()).describe('Variables passed as arguments'),
|
|
65
|
+
result: z.object({
|
|
66
|
+
stdout: z.string().describe('Standard output'),
|
|
67
|
+
stderr: z.string().describe('Standard error'),
|
|
68
|
+
exitCode: z.number().describe('Process exit code'),
|
|
69
|
+
}).describe('Execution result'),
|
|
70
|
+
}).describe('File execution details')]).describe('When a Python file finishes executing'),
|
|
71
|
+
localsParseError: z.tuple([z.any().describe('The parse error')]).describe('When captured locals fail to parse as JSON'),
|
|
72
|
+
}).describe('Python events')
|
|
73
|
+
|
|
34
74
|
/**
|
|
35
75
|
* The Python VM feature provides Python virtual machine capabilities for executing Python code.
|
|
36
76
|
*
|
|
@@ -64,6 +104,7 @@ export class Python<
|
|
|
64
104
|
static override shortcut = "features.python" as const
|
|
65
105
|
static override stateSchema = PythonStateSchema
|
|
66
106
|
static override optionsSchema = PythonOptionsSchema
|
|
107
|
+
static override eventsSchema = PythonEventsSchema
|
|
67
108
|
static { Feature.register(this, 'python') }
|
|
68
109
|
|
|
69
110
|
override get initialState(): T {
|
|
@@ -137,6 +178,13 @@ export class Python<
|
|
|
137
178
|
let pythonPath: string | null = null
|
|
138
179
|
let environmentType: PythonState['environmentType'] = null
|
|
139
180
|
|
|
181
|
+
const proc = this.container.feature('proc')
|
|
182
|
+
|
|
183
|
+
/** Resolve a binary to its full path via `which`, falling back to the bare name. */
|
|
184
|
+
const resolveBin = (name: string): string => {
|
|
185
|
+
try { return proc.exec(`which ${name}`).trim() } catch { return name }
|
|
186
|
+
}
|
|
187
|
+
|
|
140
188
|
// Use explicitly provided Python path
|
|
141
189
|
if (this.options.pythonPath) {
|
|
142
190
|
pythonPath = this.options.pythonPath
|
|
@@ -145,10 +193,10 @@ export class Python<
|
|
|
145
193
|
// Check for uv
|
|
146
194
|
else if (existsSync(join(projectDir, 'uv.lock')) || existsSync(join(projectDir, 'pyproject.toml'))) {
|
|
147
195
|
try {
|
|
148
|
-
const
|
|
149
|
-
const result = await proc.execAndCapture(
|
|
196
|
+
const uvBin = resolveBin('uv')
|
|
197
|
+
const result = await proc.execAndCapture(`${uvBin} run python --version`)
|
|
150
198
|
if (result.exitCode === 0) {
|
|
151
|
-
pythonPath =
|
|
199
|
+
pythonPath = `${uvBin} run python`
|
|
152
200
|
environmentType = 'uv'
|
|
153
201
|
}
|
|
154
202
|
} catch (error) {
|
|
@@ -158,10 +206,10 @@ export class Python<
|
|
|
158
206
|
// Check for conda
|
|
159
207
|
else if (existsSync(join(projectDir, 'environment.yml')) || existsSync(join(projectDir, 'conda.yml'))) {
|
|
160
208
|
try {
|
|
161
|
-
const
|
|
162
|
-
const result = await proc.execAndCapture(
|
|
209
|
+
const condaBin = resolveBin('conda')
|
|
210
|
+
const result = await proc.execAndCapture(`${condaBin} run python --version`)
|
|
163
211
|
if (result.exitCode === 0) {
|
|
164
|
-
pythonPath =
|
|
212
|
+
pythonPath = `${condaBin} run python`
|
|
165
213
|
environmentType = 'conda'
|
|
166
214
|
}
|
|
167
215
|
} catch (error) {
|
|
@@ -171,10 +219,10 @@ export class Python<
|
|
|
171
219
|
// Check for venv
|
|
172
220
|
else if (existsSync(join(projectDir, 'venv')) || existsSync(join(projectDir, '.venv'))) {
|
|
173
221
|
const venvPath = existsSync(join(projectDir, 'venv')) ? 'venv' : '.venv'
|
|
174
|
-
const venvPython = process.platform === 'win32'
|
|
222
|
+
const venvPython = process.platform === 'win32'
|
|
175
223
|
? join(projectDir, venvPath, 'Scripts', 'python.exe')
|
|
176
224
|
: join(projectDir, venvPath, 'bin', 'python')
|
|
177
|
-
|
|
225
|
+
|
|
178
226
|
if (existsSync(venvPython)) {
|
|
179
227
|
pythonPath = venvPython
|
|
180
228
|
environmentType = 'venv'
|
|
@@ -184,15 +232,16 @@ export class Python<
|
|
|
184
232
|
// Fall back to system Python
|
|
185
233
|
if (!pythonPath) {
|
|
186
234
|
try {
|
|
187
|
-
const
|
|
188
|
-
const result = await proc.execAndCapture(
|
|
235
|
+
const python3Bin = resolveBin('python3')
|
|
236
|
+
const result = await proc.execAndCapture(`${python3Bin} --version`)
|
|
189
237
|
if (result.exitCode === 0) {
|
|
190
|
-
pythonPath =
|
|
238
|
+
pythonPath = python3Bin
|
|
191
239
|
environmentType = 'system'
|
|
192
240
|
} else {
|
|
193
|
-
const
|
|
241
|
+
const pythonBin = resolveBin('python')
|
|
242
|
+
const result2 = await proc.execAndCapture(`${pythonBin} --version`)
|
|
194
243
|
if (result2.exitCode === 0) {
|
|
195
|
-
pythonPath =
|
|
244
|
+
pythonPath = pythonBin
|
|
196
245
|
environmentType = 'system'
|
|
197
246
|
}
|
|
198
247
|
}
|
|
@@ -61,7 +61,7 @@ export class Repl<
|
|
|
61
61
|
* Type `.exit` or `exit` to quit. Supports top-level await.
|
|
62
62
|
*
|
|
63
63
|
* @param options - Configuration for the REPL session
|
|
64
|
-
* @param options.historyPath - Custom path for the history file (defaults to
|
|
64
|
+
* @param options.historyPath - Custom path for the history file (defaults to ~/.cache/luca/repl-{cwdHash}.history)
|
|
65
65
|
* @param options.context - Additional variables to inject into the VM context
|
|
66
66
|
* @returns The Repl instance
|
|
67
67
|
*
|
|
@@ -81,17 +81,20 @@ export class Repl<
|
|
|
81
81
|
|
|
82
82
|
const { prompt = "> " } = this.options;
|
|
83
83
|
|
|
84
|
-
// Set up history file
|
|
84
|
+
// Set up history file — per-project history keyed by cwd hash
|
|
85
85
|
const userHistoryPath = options.historyPath || this.options.historyPath
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
if (typeof userHistoryPath === 'string') {
|
|
87
|
+
this._historyPath = this.container.paths.resolve(userHistoryPath)
|
|
88
|
+
} else {
|
|
89
|
+
const cwdHash = this.container.utils.hashObject(this.container.cwd)
|
|
90
|
+
this._historyPath = this.container.paths.resolve(process.env.HOME!, '.cache', 'luca', `repl-${cwdHash}.history`)
|
|
91
|
+
}
|
|
89
92
|
|
|
90
93
|
this.container.fs.ensureFolder(this.container.paths.dirname(this._historyPath))
|
|
91
94
|
|
|
92
95
|
// Load existing history
|
|
93
96
|
try {
|
|
94
|
-
const content = fs.
|
|
97
|
+
const content = fs.readFile(this._historyPath, 'utf-8')
|
|
95
98
|
this._history = content.split('\n').filter(Boolean).reverse()
|
|
96
99
|
} catch {}
|
|
97
100
|
|
|
@@ -99,6 +102,7 @@ export class Repl<
|
|
|
99
102
|
this._vmContext = vm.createContext({
|
|
100
103
|
...this.container.context,
|
|
101
104
|
...options.context,
|
|
105
|
+
setTimeout, setInterval, process, clearInterval, clearTimeout, Buffer, URL, URLSearchParams,
|
|
102
106
|
// @ts-ignore
|
|
103
107
|
client: (...args: any[]) => this.container.client(...args),
|
|
104
108
|
})
|
|
@@ -192,4 +196,4 @@ export class Repl<
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
export default Repl
|
|
199
|
+
export default Repl
|
|
@@ -37,11 +37,24 @@ export class Runpod extends Feature<RunpodState, RunpodOptions> {
|
|
|
37
37
|
static override optionsSchema = RunpodOptionsSchema
|
|
38
38
|
static { Feature.register(this, 'runpod') }
|
|
39
39
|
|
|
40
|
+
private _resolvedRunpodctlPath: string | null = null
|
|
41
|
+
|
|
40
42
|
/** The proc feature used for executing CLI commands like runpodctl. */
|
|
41
43
|
get proc() {
|
|
42
44
|
return this.container.feature('proc')
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/** Resolve the runpodctl binary path via `which`, caching the result. */
|
|
48
|
+
get runpodctlPath(): string {
|
|
49
|
+
if (this._resolvedRunpodctlPath) return this._resolvedRunpodctlPath
|
|
50
|
+
try {
|
|
51
|
+
this._resolvedRunpodctlPath = this.proc.exec('which runpodctl').trim()
|
|
52
|
+
} catch {
|
|
53
|
+
this._resolvedRunpodctlPath = 'runpodctl'
|
|
54
|
+
}
|
|
55
|
+
return this._resolvedRunpodctlPath
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
/** RunPod API key from options or the RUNPOD_API_KEY environment variable. */
|
|
46
59
|
get apiKey() {
|
|
47
60
|
return this.options.apiKey || process.env.RUNPOD_API_KEY || ''
|
|
@@ -510,7 +523,7 @@ export class Runpod extends Feature<RunpodState, RunpodOptions> {
|
|
|
510
523
|
* ```
|
|
511
524
|
*/
|
|
512
525
|
async listPods(detailed = false): Promise<PodInfo[]> {
|
|
513
|
-
const { stdout: output } = await this.proc.spawnAndCapture(
|
|
526
|
+
const { stdout: output } = await this.proc.spawnAndCapture(this.runpodctlPath, ['get', 'pod', '-a'])
|
|
514
527
|
const pods = output
|
|
515
528
|
.trim()
|
|
516
529
|
.split("\n")
|
|
@@ -550,7 +563,7 @@ export class Runpod extends Feature<RunpodState, RunpodOptions> {
|
|
|
550
563
|
* ```
|
|
551
564
|
*/
|
|
552
565
|
async getPodInfo(podId: string): Promise<PodInfo> {
|
|
553
|
-
const { stdout: output } = await this.proc.spawnAndCapture(
|
|
566
|
+
const { stdout: output } = await this.proc.spawnAndCapture(this.runpodctlPath, ['get', 'pod', podId, '-a'])
|
|
554
567
|
|
|
555
568
|
return output
|
|
556
569
|
.trim()
|
|
@@ -588,7 +601,7 @@ export class Runpod extends Feature<RunpodState, RunpodOptions> {
|
|
|
588
601
|
* ```
|
|
589
602
|
*/
|
|
590
603
|
async listSecureGPUs() {
|
|
591
|
-
const { stdout: output } = await this.proc.spawnAndCapture(
|
|
604
|
+
const { stdout: output } = await this.proc.spawnAndCapture(this.runpodctlPath, ['get', 'cloud', '--secure'])
|
|
592
605
|
|
|
593
606
|
return output
|
|
594
607
|
.split("\n")
|
|
@@ -58,6 +58,9 @@ export class SecureShell extends Feature<SecureShellState, SecureShellOptions> {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
private _resolvedSshPath: string | null = null
|
|
62
|
+
private _resolvedScpPath: string | null = null
|
|
63
|
+
|
|
61
64
|
/**
|
|
62
65
|
* Get the proc feature for executing shell commands
|
|
63
66
|
*/
|
|
@@ -65,6 +68,28 @@ export class SecureShell extends Feature<SecureShellState, SecureShellOptions> {
|
|
|
65
68
|
return this.container.feature('proc')
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/** Resolved path to the ssh binary */
|
|
72
|
+
get sshPath(): string {
|
|
73
|
+
if (this._resolvedSshPath) return this._resolvedSshPath
|
|
74
|
+
try {
|
|
75
|
+
this._resolvedSshPath = this.proc.exec('which ssh').trim()
|
|
76
|
+
} catch {
|
|
77
|
+
this._resolvedSshPath = 'ssh'
|
|
78
|
+
}
|
|
79
|
+
return this._resolvedSshPath
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Resolved path to the scp binary */
|
|
83
|
+
get scpPath(): string {
|
|
84
|
+
if (this._resolvedScpPath) return this._resolvedScpPath
|
|
85
|
+
try {
|
|
86
|
+
this._resolvedScpPath = this.proc.exec('which scp').trim()
|
|
87
|
+
} catch {
|
|
88
|
+
this._resolvedScpPath = 'scp'
|
|
89
|
+
}
|
|
90
|
+
return this._resolvedScpPath
|
|
91
|
+
}
|
|
92
|
+
|
|
68
93
|
/**
|
|
69
94
|
* Validate that required options are provided
|
|
70
95
|
*/
|
|
@@ -86,7 +111,7 @@ export class SecureShell extends Feature<SecureShellState, SecureShellOptions> {
|
|
|
86
111
|
private buildSSHConnectionString(): string {
|
|
87
112
|
this.validateOptions()
|
|
88
113
|
const { host, port = 22, username, key } = this.options
|
|
89
|
-
let sshCmd =
|
|
114
|
+
let sshCmd = `${this.sshPath} -p ${port}`
|
|
90
115
|
|
|
91
116
|
if (key) {
|
|
92
117
|
sshCmd += ` -i "${key}"`
|
|
@@ -106,7 +131,7 @@ export class SecureShell extends Feature<SecureShellState, SecureShellOptions> {
|
|
|
106
131
|
private buildSCPConnectionString(): string {
|
|
107
132
|
this.validateOptions()
|
|
108
133
|
const { host, port = 22, username, key } = this.options
|
|
109
|
-
let scpCmd =
|
|
134
|
+
let scpCmd = `${this.scpPath} -P ${port}`
|
|
110
135
|
|
|
111
136
|
if (key) {
|
|
112
137
|
scpCmd += ` -i "${key}"`
|
|
@@ -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 { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
4
4
|
import { Feature } from '../feature.js'
|
|
5
5
|
import { Database } from 'bun:sqlite'
|
|
@@ -34,6 +34,16 @@ export const SemanticSearchStateSchema = FeatureStateSchema.extend({
|
|
|
34
34
|
export type SemanticSearchOptions = z.infer<typeof SemanticSearchOptionsSchema>
|
|
35
35
|
export type SemanticSearchState = z.infer<typeof SemanticSearchStateSchema>
|
|
36
36
|
|
|
37
|
+
export const SemanticSearchEventsSchema = FeatureEventsSchema.extend({
|
|
38
|
+
modelLoaded: z.tuple([]).describe('When the local embedding model is loaded into memory'),
|
|
39
|
+
dbReady: z.tuple([]).describe('When the SQLite database is initialized and ready'),
|
|
40
|
+
indexed: z.tuple([z.object({
|
|
41
|
+
documents: z.number().describe('Number of documents indexed'),
|
|
42
|
+
chunks: z.number().describe('Number of chunks created'),
|
|
43
|
+
}).describe('Indexing result')]).describe('When documents are indexed with embeddings'),
|
|
44
|
+
modelDisposed: z.tuple([]).describe('When the local embedding model is disposed from memory'),
|
|
45
|
+
}).describe('Semantic Search events')
|
|
46
|
+
|
|
37
47
|
// ── Types ───────────────────────────────────────────────────────────
|
|
38
48
|
|
|
39
49
|
export interface Chunk {
|
|
@@ -273,6 +283,7 @@ function chunkByDocument(doc: DocumentInput): Chunk[] {
|
|
|
273
283
|
export class SemanticSearch extends Feature<SemanticSearchState, SemanticSearchOptions> {
|
|
274
284
|
static override stateSchema = SemanticSearchStateSchema
|
|
275
285
|
static override optionsSchema = SemanticSearchOptionsSchema
|
|
286
|
+
static override eventsSchema = SemanticSearchEventsSchema
|
|
276
287
|
static override shortcut = 'features.semanticSearch' as const
|
|
277
288
|
static { Feature.register(this, 'semanticSearch') }
|
|
278
289
|
|