@soederpop/luca 0.0.2
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 +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature, features } from '../feature.js'
|
|
4
|
+
import { Feature as UniversalFeature } from '../../feature.js'
|
|
5
|
+
import { Client, clients } from '../../client.js'
|
|
6
|
+
import { Server, servers } from '../../server.js'
|
|
7
|
+
import { commands } from '../../command.js'
|
|
8
|
+
import { endpoints } from '../../endpoint.js'
|
|
9
|
+
import type { Registry } from '../../registry.js'
|
|
10
|
+
import type { FileManager } from './file-manager.js'
|
|
11
|
+
import { resolve, parse } from 'path'
|
|
12
|
+
|
|
13
|
+
export const HelpersStateSchema = FeatureStateSchema.extend({
|
|
14
|
+
discovered: z.record(z.string(), z.boolean()).default({}).describe('Which registry types have been discovered'),
|
|
15
|
+
registered: z.array(z.string()).default([]).describe('Names of project-level helpers that were discovered (type.name)'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export type HelpersState = z.infer<typeof HelpersStateSchema>
|
|
19
|
+
|
|
20
|
+
export const HelpersOptionsSchema = FeatureOptionsSchema.extend({
|
|
21
|
+
rootDir: z.string().optional().describe('Root directory to scan for helper folders. Defaults to container.cwd'),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export type HelpersOptions = z.infer<typeof HelpersOptionsSchema>
|
|
25
|
+
|
|
26
|
+
export const HelpersEventsSchema = FeatureEventsSchema.extend({
|
|
27
|
+
discovered: z.tuple([
|
|
28
|
+
z.string().describe('Registry type that was discovered'),
|
|
29
|
+
z.array(z.string()).describe('Names of newly registered helpers'),
|
|
30
|
+
]).describe('Emitted after a registry type has been discovered'),
|
|
31
|
+
registered: z.tuple([
|
|
32
|
+
z.string().describe('Registry type'),
|
|
33
|
+
z.string().describe('Helper name'),
|
|
34
|
+
z.any().describe('The helper class or module'),
|
|
35
|
+
]).describe('Emitted when a single helper is registered'),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
type RegistryType = 'features' | 'clients' | 'servers' | 'commands' | 'endpoints'
|
|
39
|
+
|
|
40
|
+
const CLASS_BASED: RegistryType[] = ['features', 'clients', 'servers']
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The Helpers feature is a unified gateway for discovering and registering
|
|
44
|
+
* project-level helpers from conventional folder locations.
|
|
45
|
+
*
|
|
46
|
+
* It scans known folder names (features/, clients/, servers/, commands/, endpoints/)
|
|
47
|
+
* and handles registration differently based on the helper type:
|
|
48
|
+
*
|
|
49
|
+
* - Class-based (features, clients, servers): Dynamic import, validate subclass, register
|
|
50
|
+
* - Config-based (commands, endpoints): Delegate to existing discovery mechanisms
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const helpers = container.feature('helpers', { enable: true })
|
|
55
|
+
*
|
|
56
|
+
* // Discover all helper types
|
|
57
|
+
* await helpers.discoverAll()
|
|
58
|
+
*
|
|
59
|
+
* // Discover a specific type
|
|
60
|
+
* await helpers.discover('features')
|
|
61
|
+
*
|
|
62
|
+
* // Unified view of all available helpers
|
|
63
|
+
* console.log(helpers.available)
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
67
|
+
static override shortcut = 'features.helpers' as const
|
|
68
|
+
static override description = 'Unified gateway for discovering and registering project-level helpers'
|
|
69
|
+
static override stateSchema = HelpersStateSchema
|
|
70
|
+
static override optionsSchema = HelpersOptionsSchema
|
|
71
|
+
static override eventsSchema = HelpersEventsSchema
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns a mapping from registry type name to its registry singleton, base class, and conventional folder candidates.
|
|
75
|
+
*/
|
|
76
|
+
private get registryMap(): Record<RegistryType, { registry: Registry<any>, baseClass: any, folders: string[] }> {
|
|
77
|
+
return {
|
|
78
|
+
features: { registry: this.container.features as any, baseClass: UniversalFeature, folders: ['features'] },
|
|
79
|
+
clients: { registry: clients, baseClass: Client, folders: ['clients'] },
|
|
80
|
+
servers: { registry: servers, baseClass: Server, folders: ['servers'] },
|
|
81
|
+
commands: { registry: commands, baseClass: null, folders: ['commands'] },
|
|
82
|
+
endpoints: { registry: endpoints, baseClass: null, folders: ['endpoints'] },
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The root directory to scan for helper folders. */
|
|
87
|
+
get rootDir(): string {
|
|
88
|
+
return this.options.rootDir || this.container.cwd
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns a unified view of all available helpers across all registries.
|
|
93
|
+
* Each key is a registry type, each value is the list of helper names in that registry.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* container.helpers.available
|
|
98
|
+
* // { features: ['fs', 'git', ...], clients: ['rest', 'websocket'], ... }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
get available(): Record<string, string[]> {
|
|
102
|
+
const result: Record<string, string[]> = {}
|
|
103
|
+
for (const [type, { registry }] of Object.entries(this.registryMap)) {
|
|
104
|
+
result[type] = registry.available
|
|
105
|
+
}
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Ensures the fileManager feature is started before using it for discovery.
|
|
111
|
+
*
|
|
112
|
+
* @returns The started fileManager instance
|
|
113
|
+
*/
|
|
114
|
+
private async ensureFileManager(): Promise<FileManager> {
|
|
115
|
+
const fm = this.container.feature('fileManager', { enable: true }) as unknown as FileManager
|
|
116
|
+
if (!fm.isStarted) {
|
|
117
|
+
await fm.start()
|
|
118
|
+
}
|
|
119
|
+
return fm
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resolves which conventional folder path exists for a given registry type.
|
|
124
|
+
* Tries each candidate folder in order and returns the first one that exists.
|
|
125
|
+
*
|
|
126
|
+
* @param type - The registry type to resolve the folder for
|
|
127
|
+
* @returns Absolute path to the folder, or null if none exist
|
|
128
|
+
*/
|
|
129
|
+
private resolveFolderPath(type: RegistryType): string | null {
|
|
130
|
+
const { folders } = this.registryMap[type]
|
|
131
|
+
const { fs } = this.container
|
|
132
|
+
|
|
133
|
+
for (const candidate of folders) {
|
|
134
|
+
const dir = resolve(this.rootDir, candidate)
|
|
135
|
+
if (fs.exists(dir)) {
|
|
136
|
+
return dir
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Discover and register project-level helpers of the given type.
|
|
145
|
+
*
|
|
146
|
+
* For class-based types (features, clients, servers), scans the matching
|
|
147
|
+
* directory for .ts files, dynamically imports each, validates the default
|
|
148
|
+
* export is a subclass of the registry's base class, and registers it.
|
|
149
|
+
*
|
|
150
|
+
* For config-based types (commands, endpoints), delegates to existing discovery mechanisms.
|
|
151
|
+
*
|
|
152
|
+
* @param type - Which type of helpers to discover
|
|
153
|
+
* @param options - Optional overrides
|
|
154
|
+
* @param options.directory - Override the directory to scan
|
|
155
|
+
* @returns Names of helpers that were discovered and registered
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const names = await container.helpers.discover('features')
|
|
160
|
+
* console.log(names) // ['myCustomFeature']
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
async discover(type: RegistryType, options: { directory?: string } = {}): Promise<string[]> {
|
|
164
|
+
const discovered = this.state.get('discovered') || {}
|
|
165
|
+
|
|
166
|
+
if (discovered[type]) {
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const dir = options.directory || this.resolveFolderPath(type)
|
|
171
|
+
|
|
172
|
+
if (!dir) {
|
|
173
|
+
this.state.set('discovered', { ...discovered, [type]: true })
|
|
174
|
+
return []
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let names: string[]
|
|
178
|
+
|
|
179
|
+
if (CLASS_BASED.includes(type)) {
|
|
180
|
+
names = await this.discoverClassBased(type, dir)
|
|
181
|
+
} else {
|
|
182
|
+
names = await this.discoverConfigBased(type, dir)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.state.set('discovered', { ...this.state.get('discovered'), [type]: true })
|
|
186
|
+
|
|
187
|
+
const existing = this.state.get('registered') || []
|
|
188
|
+
this.state.set('registered', [...existing, ...names.map(n => `${type}.${n}`)])
|
|
189
|
+
|
|
190
|
+
this.emit('discovered' as any, type, names)
|
|
191
|
+
|
|
192
|
+
return names
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Discover all helper types from their conventional folder locations.
|
|
197
|
+
*
|
|
198
|
+
* @returns Map of registry type to discovered helper names
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const results = await container.helpers.discoverAll()
|
|
203
|
+
* // { features: ['myFeature'], clients: [], servers: [], commands: ['deploy'], endpoints: [] }
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async discoverAll(): Promise<Record<string, string[]>> {
|
|
207
|
+
const results: Record<string, string[]> = {}
|
|
208
|
+
|
|
209
|
+
for (const type of ['features', 'clients', 'servers', 'commands', 'endpoints'] as RegistryType[]) {
|
|
210
|
+
results[type] = await this.discover(type)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return results
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Look up a helper class by type and name.
|
|
218
|
+
*
|
|
219
|
+
* @param type - The registry type (features, clients, servers, commands, endpoints)
|
|
220
|
+
* @param name - The helper name within that registry
|
|
221
|
+
* @returns The helper constructor
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const FsClass = container.helpers.lookup('features', 'fs')
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
lookup(type: RegistryType, name: string): any {
|
|
229
|
+
const { registry } = this.registryMap[type]
|
|
230
|
+
return registry.lookup(name)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the introspection description for a specific helper.
|
|
235
|
+
*
|
|
236
|
+
* @param type - The registry type
|
|
237
|
+
* @param name - The helper name
|
|
238
|
+
* @returns Markdown description of the helper's interface
|
|
239
|
+
*/
|
|
240
|
+
describe(type: RegistryType, name: string): string {
|
|
241
|
+
const { registry } = this.registryMap[type]
|
|
242
|
+
return registry.describe(name)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Discovers class-based helpers (features, clients, servers) from a directory.
|
|
247
|
+
* Uses fileManager for fast file matching.
|
|
248
|
+
*/
|
|
249
|
+
private async discoverClassBased(type: RegistryType, dir: string): Promise<string[]> {
|
|
250
|
+
const { registry, baseClass } = this.registryMap[type]
|
|
251
|
+
const fm = await this.ensureFileManager()
|
|
252
|
+
const discovered: string[] = []
|
|
253
|
+
|
|
254
|
+
const tests = [`${type}/*/*.ts`, `${type}/*.ts`]
|
|
255
|
+
const files = fm.match(tests)
|
|
256
|
+
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
const absPath = resolve(this.rootDir, file)
|
|
259
|
+
const { name: fileName } = parse(absPath)
|
|
260
|
+
|
|
261
|
+
if (fileName.includes('.test.') || fileName.includes('.spec.')) {
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const mod = await import(absPath)
|
|
267
|
+
const ExportedClass = mod.default || mod
|
|
268
|
+
|
|
269
|
+
if (typeof ExportedClass !== 'function') {
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!this.isSubclassOf(ExportedClass, baseClass)) {
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const shortcut = ExportedClass.shortcut as string | undefined
|
|
278
|
+
const registryName = shortcut
|
|
279
|
+
? shortcut.replace(`${type}.`, '')
|
|
280
|
+
: this.fileNameToRegistryName(fileName)
|
|
281
|
+
|
|
282
|
+
discovered.push(registryName)
|
|
283
|
+
|
|
284
|
+
if (!registry.has(registryName)) {
|
|
285
|
+
registry.register(registryName, ExportedClass)
|
|
286
|
+
// this is only if they didn't export it by default
|
|
287
|
+
this.emit('registered' as any, type, registryName, ExportedClass)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
if (err.message?.includes('name collision')) {
|
|
292
|
+
throw err
|
|
293
|
+
}
|
|
294
|
+
console.warn(`Helpers gateway: failed to load ${type} from ${absPath}: ${err.message}`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return discovered
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Discovers config-based helpers (commands, endpoints) by delegating
|
|
303
|
+
* to existing discovery mechanisms.
|
|
304
|
+
*/
|
|
305
|
+
private async discoverConfigBased(type: RegistryType, dir: string): Promise<string[]> {
|
|
306
|
+
const { registry } = this.registryMap[type]
|
|
307
|
+
const beforeNames = new Set(registry.available)
|
|
308
|
+
|
|
309
|
+
if (type === 'commands') {
|
|
310
|
+
await commands.discover({ directory: dir })
|
|
311
|
+
} else if (type === 'endpoints') {
|
|
312
|
+
await this.discoverEndpoints(dir)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const afterNames = new Set(registry.available)
|
|
316
|
+
return [...afterNames].filter(n => !beforeNames.has(n))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Discovers endpoints from a directory, registering them for discoverability.
|
|
321
|
+
* Actual mounting to an express server is handled separately by ExpressServer.useEndpoints().
|
|
322
|
+
*/
|
|
323
|
+
private async discoverEndpoints(dir: string): Promise<void> {
|
|
324
|
+
const { Glob } = globalThis.Bun || (await import('bun'))
|
|
325
|
+
const glob = new Glob('**/*.ts')
|
|
326
|
+
|
|
327
|
+
for await (const file of glob.scan({ cwd: dir, absolute: true })) {
|
|
328
|
+
try {
|
|
329
|
+
const mod = await import(file)
|
|
330
|
+
const endpointModule = mod.default || mod
|
|
331
|
+
|
|
332
|
+
if (endpointModule.path && typeof endpointModule.path === 'string') {
|
|
333
|
+
const name = endpointModule.path.replace(/^\//, '').replace(/\//g, '_') || parse(file).name
|
|
334
|
+
|
|
335
|
+
if (!endpoints.has(name)) {
|
|
336
|
+
// Import the module so it's available, but don't mount it to a server
|
|
337
|
+
// The express server's useEndpoints() handles the actual mounting
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch (err: any) {
|
|
341
|
+
console.warn(`Helpers gateway: failed to load endpoint from ${file}: ${err.message}`)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if a class is a subclass of a given base class by walking the prototype chain.
|
|
348
|
+
* Uses identity comparison first, then falls back to name comparison to handle
|
|
349
|
+
* cross-module boundaries (e.g. compiled binary vs dynamically imported modules
|
|
350
|
+
* that resolve to separate module instances of the same class).
|
|
351
|
+
*/
|
|
352
|
+
private isSubclassOf(candidate: any, base: any): boolean {
|
|
353
|
+
if (!candidate || !base) return false
|
|
354
|
+
if (candidate === base) return true
|
|
355
|
+
|
|
356
|
+
let proto = Object.getPrototypeOf(candidate)
|
|
357
|
+
while (proto) {
|
|
358
|
+
if (proto === base || (base.name && proto.name === base.name)) return true
|
|
359
|
+
proto = Object.getPrototypeOf(proto)
|
|
360
|
+
}
|
|
361
|
+
return false
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Convert a kebab-case or snake_case filename to a camelCase registry name.
|
|
366
|
+
*/
|
|
367
|
+
private fileNameToRegistryName(fileName: string): string {
|
|
368
|
+
return fileName
|
|
369
|
+
.replace(/[-_](.)/g, (_, c: string) => c.toUpperCase())
|
|
370
|
+
.replace(/^(.)/, (_, c: string) => c.toLowerCase())
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export default features.register('helpers', Helpers)
|