@soederpop/luca 0.0.6 → 0.0.8

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 (208) hide show
  1. package/CLAUDE.md +10 -1
  2. package/RUNME.md +56 -0
  3. package/bun.lock +1 -1
  4. package/commands/build-bootstrap.ts +78 -0
  5. package/commands/build-scaffolds.ts +24 -2
  6. package/commands/try-all-challenges.ts +543 -0
  7. package/commands/try-challenge.ts +100 -0
  8. package/docs/README.md +52 -80
  9. package/docs/TABLE-OF-CONTENTS.md +82 -51
  10. package/docs/apis/clients/elevenlabs.md +232 -8
  11. package/docs/apis/clients/graph.md +59 -8
  12. package/docs/apis/clients/openai.md +362 -2
  13. package/docs/apis/clients/rest.md +122 -2
  14. package/docs/apis/clients/websocket.md +71 -17
  15. package/docs/apis/features/agi/assistant.md +9 -3
  16. package/docs/apis/features/agi/assistants-manager.md +2 -2
  17. package/docs/apis/features/agi/claude-code.md +153 -14
  18. package/docs/apis/features/agi/conversation-history.md +15 -3
  19. package/docs/apis/features/agi/conversation.md +133 -20
  20. package/docs/apis/features/agi/openai-codex.md +90 -12
  21. package/docs/apis/features/agi/skills-library.md +23 -5
  22. package/docs/apis/features/node/container-link.md +59 -0
  23. package/docs/apis/features/node/content-db.md +1 -1
  24. package/docs/apis/features/node/disk-cache.md +1 -1
  25. package/docs/apis/features/node/dns.md +1 -0
  26. package/docs/apis/features/node/docker.md +2 -1
  27. package/docs/apis/features/node/esbuild.md +4 -3
  28. package/docs/apis/features/node/file-manager.md +13 -4
  29. package/docs/apis/features/node/fs.md +726 -171
  30. package/docs/apis/features/node/git.md +1 -0
  31. package/docs/apis/features/node/google-auth.md +23 -4
  32. package/docs/apis/features/node/google-calendar.md +14 -2
  33. package/docs/apis/features/node/google-docs.md +15 -2
  34. package/docs/apis/features/node/google-drive.md +21 -3
  35. package/docs/apis/features/node/google-sheets.md +14 -2
  36. package/docs/apis/features/node/grep.md +2 -0
  37. package/docs/apis/features/node/helpers.md +29 -0
  38. package/docs/apis/features/node/ink.md +2 -2
  39. package/docs/apis/features/node/networking.md +39 -4
  40. package/docs/apis/features/node/os.md +28 -0
  41. package/docs/apis/features/node/postgres.md +26 -4
  42. package/docs/apis/features/node/proc.md +37 -28
  43. package/docs/apis/features/node/process-manager.md +33 -5
  44. package/docs/apis/features/node/repl.md +1 -1
  45. package/docs/apis/features/node/runpod.md +1 -0
  46. package/docs/apis/features/node/secure-shell.md +7 -0
  47. package/docs/apis/features/node/semantic-search.md +12 -5
  48. package/docs/apis/features/node/sqlite.md +26 -4
  49. package/docs/apis/features/node/telegram.md +30 -5
  50. package/docs/apis/features/node/tts.md +17 -2
  51. package/docs/apis/features/node/ui.md +1 -1
  52. package/docs/apis/features/node/vault.md +4 -9
  53. package/docs/apis/features/node/vm.md +3 -12
  54. package/docs/apis/features/node/window-manager.md +128 -20
  55. package/docs/apis/features/web/asset-loader.md +13 -1
  56. package/docs/apis/features/web/container-link.md +59 -0
  57. package/docs/apis/features/web/esbuild.md +4 -3
  58. package/docs/apis/features/web/helpers.md +29 -0
  59. package/docs/apis/features/web/network.md +16 -2
  60. package/docs/apis/features/web/speech.md +16 -2
  61. package/docs/apis/features/web/vault.md +4 -9
  62. package/docs/apis/features/web/vm.md +3 -12
  63. package/docs/apis/features/web/voice.md +18 -1
  64. package/docs/apis/servers/express.md +18 -2
  65. package/docs/apis/servers/mcp.md +29 -4
  66. package/docs/apis/servers/websocket.md +34 -6
  67. package/docs/bootstrap/CLAUDE.md +100 -0
  68. package/docs/bootstrap/SKILL.md +222 -0
  69. package/docs/bootstrap/templates/about-command.ts +41 -0
  70. package/docs/bootstrap/templates/docs-models.ts +22 -0
  71. package/docs/bootstrap/templates/docs-readme.md +43 -0
  72. package/docs/bootstrap/templates/example-feature.ts +53 -0
  73. package/docs/bootstrap/templates/health-endpoint.ts +15 -0
  74. package/docs/bootstrap/templates/luca-cli.ts +25 -0
  75. package/docs/bootstrap/templates/runme.md +54 -0
  76. package/docs/challenges/caching-proxy.md +16 -0
  77. package/docs/challenges/content-db-round-trip.md +14 -0
  78. package/docs/challenges/custom-command.md +9 -0
  79. package/docs/challenges/file-watcher-pipeline.md +11 -0
  80. package/docs/challenges/grep-audit-report.md +15 -0
  81. package/docs/challenges/multi-feature-dashboard.md +14 -0
  82. package/docs/challenges/process-orchestrator.md +17 -0
  83. package/docs/challenges/rest-api-server-with-client.md +12 -0
  84. package/docs/challenges/script-runner-with-vm.md +11 -0
  85. package/docs/challenges/simple-rest-api.md +15 -0
  86. package/docs/challenges/websocket-serve-and-client.md +11 -0
  87. package/docs/challenges/yaml-config-system.md +14 -0
  88. package/docs/command-system-overhaul.md +94 -0
  89. package/docs/examples/assistant/CORE.md +18 -0
  90. package/docs/examples/assistant/hooks.ts +3 -0
  91. package/docs/examples/assistant/tools.ts +10 -0
  92. package/docs/examples/window-manager-layouts.md +180 -0
  93. package/docs/in-memory-fs.md +4 -0
  94. package/docs/models.ts +13 -10
  95. package/docs/philosophy.md +4 -3
  96. package/docs/reports/console-hmr-design.md +170 -0
  97. package/docs/reports/helper-semantic-search.md +72 -0
  98. package/docs/scaffolds/client.md +29 -20
  99. package/docs/scaffolds/command.md +64 -50
  100. package/docs/scaffolds/endpoint.md +31 -36
  101. package/docs/scaffolds/feature.md +28 -18
  102. package/docs/scaffolds/selector.md +91 -0
  103. package/docs/scaffolds/server.md +18 -9
  104. package/docs/selectors.md +115 -0
  105. package/docs/sessions/custom-command/attempt-log-2.md +195 -0
  106. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
  107. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
  108. package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
  109. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
  110. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
  111. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
  112. package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
  113. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
  114. package/docs/tutorials/00-bootstrap.md +148 -0
  115. package/docs/tutorials/07-endpoints.md +7 -7
  116. package/docs/tutorials/08-commands.md +153 -72
  117. package/luca.cli.ts +3 -0
  118. package/package.json +6 -5
  119. package/public/index.html +1430 -0
  120. package/scripts/examples/using-ollama.ts +2 -1
  121. package/scripts/update-introspection-data.ts +2 -2
  122. package/src/agi/endpoints/experts.ts +1 -1
  123. package/src/agi/features/assistant.ts +7 -0
  124. package/src/agi/features/assistants-manager.ts +5 -5
  125. package/src/agi/features/claude-code.ts +263 -3
  126. package/src/agi/features/conversation-history.ts +7 -1
  127. package/src/agi/features/conversation.ts +26 -3
  128. package/src/agi/features/openai-codex.ts +26 -2
  129. package/src/agi/features/openapi.ts +6 -1
  130. package/src/agi/features/skills-library.ts +9 -1
  131. package/src/bootstrap/generated.ts +595 -0
  132. package/src/cli/cli.ts +64 -21
  133. package/src/client.ts +23 -357
  134. package/src/clients/civitai/index.ts +1 -1
  135. package/src/clients/client-template.ts +1 -1
  136. package/src/clients/comfyui/index.ts +13 -2
  137. package/src/clients/elevenlabs/index.ts +2 -1
  138. package/src/clients/graph.ts +87 -0
  139. package/src/clients/openai/index.ts +10 -1
  140. package/src/clients/rest.ts +207 -0
  141. package/src/clients/websocket.ts +176 -0
  142. package/src/command.ts +281 -34
  143. package/src/commands/bootstrap.ts +185 -0
  144. package/src/commands/chat.ts +5 -4
  145. package/src/commands/describe.ts +341 -4
  146. package/src/commands/help.ts +35 -9
  147. package/src/commands/index.ts +3 -0
  148. package/src/commands/introspect.ts +92 -2
  149. package/src/commands/prompt.ts +5 -6
  150. package/src/commands/run.ts +75 -10
  151. package/src/commands/save-api-docs.ts +49 -0
  152. package/src/commands/scaffold.ts +169 -23
  153. package/src/commands/select.ts +94 -0
  154. package/src/commands/serve.ts +10 -1
  155. package/src/container.ts +15 -0
  156. package/src/endpoint.ts +19 -0
  157. package/src/graft.ts +181 -0
  158. package/src/introspection/generated.agi.ts +12458 -8968
  159. package/src/introspection/generated.node.ts +10573 -7145
  160. package/src/introspection/generated.web.ts +1 -1
  161. package/src/introspection/index.ts +26 -0
  162. package/src/node/container.ts +6 -7
  163. package/src/node/features/content-db.ts +49 -2
  164. package/src/node/features/disk-cache.ts +16 -9
  165. package/src/node/features/dns.ts +16 -3
  166. package/src/node/features/docker.ts +16 -4
  167. package/src/node/features/esbuild.ts +22 -2
  168. package/src/node/features/file-manager.ts +184 -29
  169. package/src/node/features/fs.ts +704 -248
  170. package/src/node/features/git.ts +21 -8
  171. package/src/node/features/grep.ts +23 -3
  172. package/src/node/features/helpers.ts +372 -43
  173. package/src/node/features/networking.ts +39 -4
  174. package/src/node/features/opener.ts +28 -15
  175. package/src/node/features/os.ts +76 -0
  176. package/src/node/features/port-exposer.ts +11 -1
  177. package/src/node/features/postgres.ts +17 -1
  178. package/src/node/features/proc.ts +4 -1
  179. package/src/node/features/python.ts +63 -14
  180. package/src/node/features/repl.ts +11 -7
  181. package/src/node/features/runpod.ts +16 -3
  182. package/src/node/features/secure-shell.ts +27 -2
  183. package/src/node/features/semantic-search.ts +12 -1
  184. package/src/node/features/ui.ts +5 -69
  185. package/src/node/features/vm.ts +17 -0
  186. package/src/node/features/window-manager.ts +68 -20
  187. package/src/node.ts +5 -0
  188. package/src/scaffolds/generated.ts +492 -290
  189. package/src/scaffolds/template.ts +9 -0
  190. package/src/schemas/base.ts +46 -5
  191. package/src/selector.ts +282 -0
  192. package/src/server.ts +11 -0
  193. package/src/servers/express.ts +27 -12
  194. package/src/servers/socket.ts +45 -11
  195. package/src/web/clients/socket.ts +4 -1
  196. package/src/web/container.ts +2 -1
  197. package/src/web/features/network.ts +7 -1
  198. package/src/web/features/voice-recognition.ts +16 -1
  199. package/test/clients-servers.test.ts +2 -1
  200. package/test/command.test.ts +267 -0
  201. package/test/vm-context.test.ts +146 -0
  202. package/test-integration/assistants-manager.test.ts +10 -20
  203. package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
  204. package/docs/examples/launcher-app-command-listener.md +0 -120
  205. package/docs/tasks/web-container-helper-discovery.md +0 -71
  206. package/docs/todos.md +0 -1
  207. package/scripts/test-command-listener.ts +0 -123
  208. package/src/node/features/launcher-app-command-listener.ts +0 -389
@@ -1,7 +1,7 @@
1
1
  import { setBuildTimeData, setContainerBuildTimeData } from './index.js';
2
2
 
3
3
  // Auto-generated introspection registry data
4
- // Generated at: 2026-03-12T02:48:58.250Z
4
+ // Generated at: 2026-03-19T00:28:05.997Z
5
5
 
6
6
  setBuildTimeData('features.containerLink', {
7
7
  "id": "features.containerLink",
@@ -161,12 +161,38 @@ export function getContainerBuildTimeData(className: string): Partial<ContainerI
161
161
  export function setBuildTimeData(key: string, data: HelperIntrospection) {
162
162
  const existing = __INTROSPECTION__.get(key)
163
163
 
164
+ // Merge events: build-time AST provides descriptions, runtime Zod schemas provide arguments.
165
+ // For each event, preserve runtime arguments if the build-time entry has none.
166
+ const mergedEvents: Record<string, any> = { ...(data.events || {}) }
167
+ if (existing?.events) {
168
+ for (const [eventName, existingEvent] of Object.entries(existing.events)) {
169
+ if (mergedEvents[eventName]) {
170
+ // Build-time entry exists — merge in runtime arguments and description if build-time has none
171
+ const buildArgs = mergedEvents[eventName].arguments || {}
172
+ const runtimeArgs = (existingEvent as any).arguments || {}
173
+ const buildDesc = mergedEvents[eventName].description || ''
174
+ const runtimeDesc = (existingEvent as any).description || ''
175
+ const isGenericDesc = buildDesc.startsWith('Event emitted by ')
176
+ if (Object.keys(buildArgs).length === 0 && Object.keys(runtimeArgs).length > 0) {
177
+ mergedEvents[eventName] = { ...mergedEvents[eventName], arguments: runtimeArgs }
178
+ }
179
+ if (isGenericDesc && runtimeDesc) {
180
+ mergedEvents[eventName] = { ...mergedEvents[eventName], description: runtimeDesc }
181
+ }
182
+ } else {
183
+ // Event only exists in runtime (from Zod schema) — preserve it
184
+ mergedEvents[eventName] = existingEvent
185
+ }
186
+ }
187
+ }
188
+
164
189
  __INTROSPECTION__.set(key, {
165
190
  ...data,
166
191
  // preserve runtime-derived className/state/options if registration already happened
167
192
  className: data.className || existing?.className,
168
193
  state: existing?.state || data.state || {},
169
194
  options: existing?.options || data.options || {},
195
+ events: mergedEvents,
170
196
  getters: data.getters || existing?.getters || {},
171
197
  envVars: existing?.envVars || data.envVars || [],
172
198
  examples: data.examples || existing?.examples,
@@ -6,12 +6,16 @@ import type { FeatureOptions } from "./feature";
6
6
  import { features, Feature } from "./feature";
7
7
  import type { AvailableFeatures } from "../feature";
8
8
  import { Client, type ClientsInterface } from "../client";
9
+ import "../clients/rest";
10
+ import "../clients/graph";
11
+ import "../clients/websocket";
9
12
  import { Server, type ServersInterface } from "../server";
10
13
  import "../servers/express";
11
14
  import "../servers/socket";
12
15
  import "../servers/mcp";
13
16
  import { Command, type CommandsInterface } from "../command";
14
17
  import { Endpoint, type EndpointsInterface } from "../endpoint";
18
+ import { Selector, type SelectorsInterface } from "../selector";
15
19
 
16
20
  import minimist from "minimist";
17
21
  import { omit, kebabCase, camelCase, mapKeys, castArray } from "lodash-es";
@@ -53,7 +57,6 @@ import "./features/google-sheets";
53
57
  import "./features/google-calendar";
54
58
  import "./features/google-docs";
55
59
  import "./features/window-manager";
56
- import "./features/launcher-app-command-listener";
57
60
  import "./features/nlp";
58
61
  import "./features/process-manager"
59
62
  import "./features/tts";
@@ -98,7 +101,6 @@ import type { GoogleSheets } from './features/google-sheets';
98
101
  import type { GoogleCalendar } from './features/google-calendar';
99
102
  import type { GoogleDocs } from './features/google-docs';
100
103
  import type { WindowManager } from './features/window-manager';
101
- import type { LauncherAppCommandListener } from './features/launcher-app-command-listener';
102
104
  import type { NLP } from './features/nlp';
103
105
  import type { ProcessManager } from './features/process-manager'
104
106
  import type { TTS } from './features/tts';
@@ -138,7 +140,6 @@ export {
138
140
  type GoogleCalendar,
139
141
  type GoogleDocs,
140
142
  type WindowManager,
141
- type LauncherAppCommandListener,
142
143
  type NLP,
143
144
  type ProcessManager,
144
145
  type TTS,
@@ -204,7 +205,6 @@ export interface NodeFeatures extends AvailableFeatures {
204
205
  googleCalendar: typeof GoogleCalendar;
205
206
  googleDocs: typeof GoogleDocs;
206
207
  windowManager: typeof WindowManager;
207
- launcherAppCommandListener: typeof LauncherAppCommandListener;
208
208
  nlp: typeof NLP;
209
209
  processManager: typeof ProcessManager;
210
210
  tts: typeof TTS;
@@ -214,7 +214,7 @@ export interface NodeFeatures extends AvailableFeatures {
214
214
  dns: typeof Dns;
215
215
  }
216
216
 
217
- export type ClientsAndServersInterface = ClientsInterface & ServersInterface & CommandsInterface & EndpointsInterface;
217
+ export type ClientsAndServersInterface = ClientsInterface & ServersInterface & CommandsInterface & EndpointsInterface & SelectorsInterface;
218
218
 
219
219
  export interface NodeContainer extends ClientsAndServersInterface {}
220
220
 
@@ -253,7 +253,6 @@ export class NodeContainer<
253
253
  googleCalendar?: GoogleCalendar;
254
254
  googleDocs?: GoogleDocs;
255
255
  windowManager?: WindowManager;
256
- launcherAppCommandListener?: LauncherAppCommandListener;
257
256
  nlp?: NLP;
258
257
  processManager?: ProcessManager;
259
258
  tts?: TTS;
@@ -286,7 +285,7 @@ export class NodeContainer<
286
285
  }
287
286
  });
288
287
 
289
- this.use(Client).use(Server).use(Command).use(Endpoint);
288
+ this.use(Client).use(Server).use(Command).use(Endpoint).use(Selector);
290
289
  }
291
290
 
292
291
  override get Feature() {
@@ -1,7 +1,8 @@
1
1
  import { Feature } from '../feature.js'
2
+ import * as contentbaseExports from 'contentbase'
2
3
  import { parse, Collection, extractSections, type ModelDefinition } from 'contentbase'
3
4
  import { z } from 'zod'
4
- import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
5
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
5
6
  import { join, dirname } from 'node:path'
6
7
  import { existsSync, readdirSync } from 'node:fs'
7
8
 
@@ -18,6 +19,10 @@ export const ContentDbOptionsSchema = FeatureOptionsSchema.extend({
18
19
  export type ContentDbState = z.infer<typeof ContentDbStateSchema>
19
20
  export type ContentDbOptions = z.infer<typeof ContentDbOptionsSchema>
20
21
 
22
+ export const ContentDbEventsSchema = FeatureEventsSchema.extend({
23
+ reloaded: z.tuple([]).describe('When the content collection is reloaded from disk'),
24
+ }).describe('ContentDb events')
25
+
21
26
  /**
22
27
  * Provides access to a Contentbase Collection for a folder of structured markdown files.
23
28
  *
@@ -37,6 +42,7 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
37
42
  static override shortcut = 'features.contentDb' as const
38
43
  static override stateSchema = ContentDbStateSchema
39
44
  static override optionsSchema = ContentDbOptionsSchema
45
+ static override eventsSchema = ContentDbEventsSchema
40
46
  static { Feature.register(this, 'contentDb') }
41
47
 
42
48
  override get initialState(): ContentDbState {
@@ -52,11 +58,52 @@ export class ContentDb extends Feature<ContentDbState, ContentDbOptions> {
52
58
  }
53
59
 
54
60
  _collection?: Collection
61
+ private _contentbaseSeeded = false
55
62
 
56
63
  /** Returns the lazily-initialized Collection instance for the configured rootPath. */
57
64
  get collection() {
58
65
  if (this._collection) return this._collection
59
- return this._collection = new Collection({ rootPath: this.options.rootPath })
66
+
67
+ const opts: any = { rootPath: this.options.rootPath }
68
+
69
+ // When contentbase isn't in node_modules (e.g. compiled luca binary),
70
+ // provide a VM-based module loader so models.ts can resolve its imports
71
+ if (!this._canNativeImportContentbase()) {
72
+ opts.moduleLoader = (filePath: string) => {
73
+ this._seedContentbaseVirtualModules()
74
+ const vm = this.container.feature('vm') as any
75
+ return vm.loadModule(filePath)
76
+ }
77
+ }
78
+
79
+ return this._collection = new Collection(opts)
80
+ }
81
+
82
+ /** Check if contentbase is resolvable via native import from the project root */
83
+ private _canNativeImportContentbase(): boolean {
84
+ const cwd = this.container.cwd
85
+ return existsSync(join(cwd, 'node_modules', 'contentbase'))
86
+ }
87
+
88
+ /** Seed the VM with virtual modules so models.ts can import from 'contentbase', 'zod', etc. */
89
+ private _seedContentbaseVirtualModules(): void {
90
+ if (this._contentbaseSeeded) return
91
+ this._contentbaseSeeded = true
92
+
93
+ const vm = this.container.feature('vm') as any
94
+
95
+ // Seed luca modules first (helpers does this for @soederpop/luca)
96
+ const helpers = this.container.feature('helpers') as any
97
+ if (helpers?.seedVirtualModules) {
98
+ helpers.seedVirtualModules()
99
+ }
100
+
101
+ // Register contentbase barrel — everything the library exports
102
+ vm.defineModule('contentbase', contentbaseExports)
103
+
104
+ // Common deps that models.ts files tend to use
105
+ try { vm.defineModule('js-yaml', require('js-yaml')) } catch {}
106
+ try { vm.defineModule('mdast-util-to-string', require('mdast-util-to-string')) } catch {}
60
107
  }
61
108
 
62
109
  /** Returns the absolute resolved path to the collection root directory. */
@@ -36,15 +36,17 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
36
36
  static override optionsSchema = DiskCacheOptionsSchema
37
37
  static { Feature.register(this, 'diskCache') }
38
38
 
39
- constructor(options: DiskCacheOptions, context: ContainerContext) {
40
- super(options, context)
41
- this._cache = this.create()
42
- this.hide('_cache')
43
- }
44
-
45
39
  /** Returns the underlying cacache instance configured with the cache directory path. */
46
40
  get cache() {
47
- return this._cache
41
+ if(this._cache) {
42
+ return this._cache
43
+ }
44
+
45
+ const cache = this.create()
46
+
47
+ Object.defineProperty(this, '_cache', { value: cache, enumerable: false })
48
+
49
+ return cache
48
50
  }
49
51
 
50
52
  /**
@@ -328,7 +330,7 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
328
330
 
329
331
  /**
330
332
  * Create a cacache instance with the specified path
331
- * @param path - Optional cache directory path (defaults to options.path or node_modules/.cache/luca-disk-cache)
333
+ * @param path - Optional cache directory path (defaults to options.path or ~/.cache/luca/disk-cache-{cwdHash})
332
334
  * @returns Configured cacache instance with all methods bound to the path
333
335
  * @example
334
336
  * ```typescript
@@ -336,7 +338,12 @@ export class DiskCache extends Feature<FeatureState,DiskCacheOptions> {
336
338
  * ```
337
339
  */
338
340
  create(path?: string) {
339
- path = path || this.options.path || this.container.paths.resolve('node_modules', '.cache', 'luca-disk-cache')
341
+ if (!path && !this.options.path) {
342
+ const cwdHash = this.container.utils.hashObject(this.container.cwd)
343
+ path = this.container.paths.resolve(process.env.HOME!, '.cache', 'luca', `disk-cache-${cwdHash}`)
344
+ } else {
345
+ path = path || this.options.path!
346
+ }
340
347
  this.container.fs.ensureFolder(path)
341
348
  const arg = (fn: (...args: any) => any) => partial(fn, path);
342
349
 
@@ -131,10 +131,23 @@ export class Dns extends Feature<DnsState, DnsOptions> {
131
131
  }
132
132
  }
133
133
 
134
+ private _resolvedDigPath: string | null = null
135
+
134
136
  get proc() {
135
137
  return this.container.feature('proc')
136
138
  }
137
139
 
140
+ /** Resolved path to the dig binary */
141
+ get digPath(): string {
142
+ if (this._resolvedDigPath) return this._resolvedDigPath
143
+ try {
144
+ this._resolvedDigPath = this.proc.exec('which dig').trim()
145
+ } catch {
146
+ this._resolvedDigPath = 'dig'
147
+ }
148
+ return this._resolvedDigPath
149
+ }
150
+
138
151
  /**
139
152
  * Checks whether the `dig` binary is available on the system.
140
153
  *
@@ -148,7 +161,7 @@ export class Dns extends Feature<DnsState, DnsOptions> {
148
161
  * ```
149
162
  */
150
163
  async isAvailable(): Promise<boolean> {
151
- const result = await this.proc.spawnAndCapture('dig', ['-v'])
164
+ const result = await this.proc.spawnAndCapture(this.digPath, ['-v'])
152
165
  // dig -v prints version to stderr and exits 0
153
166
  return result.exitCode === 0
154
167
  }
@@ -180,7 +193,7 @@ export class Dns extends Feature<DnsState, DnsOptions> {
180
193
  */
181
194
  async resolve(domain: string, type: DnsRecordType, options: QueryOptions = {}): Promise<DnsQueryResult> {
182
195
  const args = this.buildDigArgs(domain, type, options)
183
- const result = await this.proc.spawnAndCapture('dig', args)
196
+ const result = await this.proc.spawnAndCapture(this.digPath, args)
184
197
 
185
198
  if (result.exitCode !== 0) {
186
199
  throw new Error(`dig query failed: ${result.stderr || 'unknown error'}`)
@@ -446,7 +459,7 @@ export class Dns extends Feature<DnsState, DnsOptions> {
446
459
  }
447
460
  args.unshift('-x', ip)
448
461
 
449
- const result = await this.proc.spawnAndCapture('dig', args)
462
+ const result = await this.proc.spawnAndCapture(this.digPath, args)
450
463
 
451
464
  if (result.exitCode !== 0) {
452
465
  throw new Error(`dig reverse lookup failed: ${result.stderr || 'unknown error'}`)
@@ -101,10 +101,24 @@ export class Docker extends Feature<DockerState, DockerOptions> {
101
101
  /**
102
102
  * Get the proc feature for executing shell commands
103
103
  */
104
+ private _resolvedDockerPath: string | null = null
105
+
104
106
  get proc() {
105
107
  return this.container.feature('proc')
106
108
  }
107
109
 
110
+ /** Resolve the docker binary path via `which`, caching the result. Options take precedence. */
111
+ get dockerPath(): string {
112
+ if (this.options.dockerPath) return this.options.dockerPath
113
+ if (this._resolvedDockerPath) return this._resolvedDockerPath
114
+ try {
115
+ this._resolvedDockerPath = this.proc.exec('which docker').trim()
116
+ } catch {
117
+ this._resolvedDockerPath = 'docker'
118
+ }
119
+ return this._resolvedDockerPath
120
+ }
121
+
108
122
  /**
109
123
  * Check if Docker is available and working.
110
124
  *
@@ -117,8 +131,7 @@ export class Docker extends Feature<DockerState, DockerOptions> {
117
131
  */
118
132
  async checkDockerAvailability(): Promise<boolean> {
119
133
  try {
120
- const dockerPath = this.options.dockerPath || 'docker'
121
- const result = await this.proc.spawnAndCapture(dockerPath, ['--version'])
134
+ const result = await this.proc.spawnAndCapture(this.dockerPath, ['--version'])
122
135
 
123
136
  if (result.exitCode === 0) {
124
137
  this.setState({ isDockerAvailable: true, lastError: undefined })
@@ -152,8 +165,7 @@ export class Docker extends Feature<DockerState, DockerOptions> {
152
165
  }
153
166
 
154
167
  try {
155
- const dockerPath = this.options.dockerPath || 'docker'
156
- const result = await this.proc.spawnAndCapture(dockerPath, args)
168
+ const result = await this.proc.spawnAndCapture(this.dockerPath, args)
157
169
 
158
170
  if (result.exitCode !== 0) {
159
171
  this.setState({ lastError: result.stderr })
@@ -31,7 +31,7 @@ export class ESBuild extends Feature {
31
31
  return esbuild.transformSync(code, {
32
32
  loader: 'ts',
33
33
  format: 'esm',
34
- target: 'es2020',
34
+ target: 'esnext',
35
35
  sourcemap: false,
36
36
  minify: false,
37
37
  ...options
@@ -48,12 +48,32 @@ export class ESBuild extends Feature {
48
48
  return esbuild.transform(code, {
49
49
  loader: 'ts',
50
50
  format: 'esm',
51
- target: 'es2020',
51
+ target: 'esnext',
52
52
  sourcemap: false,
53
53
  minify: false,
54
54
  ...options
55
55
  })
56
56
  }
57
+
58
+ /**
59
+ * Bundle one or more entry points, resolving imports and requires into a single output.
60
+ * Supports Node platform by default so require() and Node builtins are handled.
61
+ * Returns in-memory output files unless write is enabled in options.
62
+ * @param entryPoints - File paths to bundle from
63
+ * @param options - esbuild BuildOptions overrides
64
+ * @returns The build result with outputFiles when write is false
65
+ */
66
+ async bundle(entryPoints: string[], options?: esbuild.BuildOptions) {
67
+ return esbuild.build({
68
+ entryPoints,
69
+ bundle: true,
70
+ platform: 'node',
71
+ format: 'esm',
72
+ target: 'esnext',
73
+ write: false,
74
+ ...options
75
+ })
76
+ }
57
77
  }
58
78
 
59
79
  export default ESBuild