@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
package/src/helper.ts
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { Bus, type EventMap } from "./bus.js";
|
|
2
|
+
import { type SetStateValue, State } from "./state.js";
|
|
3
|
+
import type { ContainerContext } from './container.js'
|
|
4
|
+
import uuid from 'node-uuid'
|
|
5
|
+
import { get } from 'lodash-es'
|
|
6
|
+
import { introspect, type HelperIntrospection, type IntrospectionSection, type ExampleIntrospection } from "./introspection/index.js";
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { HelperStateSchema, HelperOptionsSchema, HelperEventsSchema } from './schemas/base.js'
|
|
9
|
+
|
|
10
|
+
export type HelperState = z.infer<typeof HelperStateSchema>
|
|
11
|
+
export type HelperOptions = z.infer<typeof HelperOptionsSchema>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helpers are used to represent types of modules.
|
|
15
|
+
*
|
|
16
|
+
* You don't create instances of helpers directly, the container creates instances through
|
|
17
|
+
* factory functions that use the subclasses of Helper as a template. The container
|
|
18
|
+
* provides dependency injection and injects a context object into the Helper constructor.
|
|
19
|
+
*
|
|
20
|
+
* A Helper is something that can be introspected at runtime to learn about the interface.
|
|
21
|
+
*
|
|
22
|
+
* A helper has state.
|
|
23
|
+
*
|
|
24
|
+
* A helper is an event bus.
|
|
25
|
+
*
|
|
26
|
+
* A helper is connected to the container and can access the container's state, events, shared context, or
|
|
27
|
+
* other helpers and features in the container's registry.
|
|
28
|
+
*/
|
|
29
|
+
export abstract class Helper<T extends HelperState = HelperState, K extends HelperOptions = any, E extends EventMap = EventMap> {
|
|
30
|
+
static shortcut: string = "unspecified"
|
|
31
|
+
|
|
32
|
+
static description: string = "No description provided"
|
|
33
|
+
static envVars: string[] = []
|
|
34
|
+
|
|
35
|
+
static stateSchema: z.ZodType = HelperStateSchema
|
|
36
|
+
static optionsSchema: z.ZodType = HelperOptionsSchema
|
|
37
|
+
static eventsSchema: z.ZodType = HelperEventsSchema
|
|
38
|
+
|
|
39
|
+
protected readonly _context: ContainerContext
|
|
40
|
+
protected readonly _events = new Bus<E>()
|
|
41
|
+
protected readonly _options: K
|
|
42
|
+
|
|
43
|
+
readonly state: State<T>
|
|
44
|
+
|
|
45
|
+
readonly uuid = uuid.v4()
|
|
46
|
+
|
|
47
|
+
get initialState() : T {
|
|
48
|
+
return {} as T
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Alias for introspect */
|
|
52
|
+
static inspect(section?: IntrospectionSection) : HelperIntrospection | undefined {
|
|
53
|
+
return this.introspect(section)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static introspect(section?: IntrospectionSection) : HelperIntrospection | undefined {
|
|
57
|
+
const data = introspect((this as any).shortcut || '')
|
|
58
|
+
if (!data || !section) return data
|
|
59
|
+
return filterIntrospection(data, section)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Alias for introspectAsText */
|
|
63
|
+
static inspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) : string {
|
|
64
|
+
return this.introspectAsText(sectionOrDepth, startHeadingDepth)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static introspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) : string {
|
|
68
|
+
const { section, depth } = resolveIntrospectAsTextArgs(sectionOrDepth, startHeadingDepth)
|
|
69
|
+
const introspection = this.introspect()
|
|
70
|
+
if (!introspection) return ''
|
|
71
|
+
return presentIntrospectionJSONAsMarkdown(introspection, depth, section)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* All Helpers can be introspect()ed and, assuming the introspection data has been loaded into the registry,
|
|
77
|
+
* will report information about the Helper that can only get extracted by reading the code, e.g. the type interfaces
|
|
78
|
+
* for the helper's options, state, and the events it emits, as well as the documentation from the helpers code for
|
|
79
|
+
* each of the methods and properties.
|
|
80
|
+
*
|
|
81
|
+
* Pass a section name to get only that section: `'methods'`, `'getters'`, `'events'`, `'state'`, `'options'`, `'envVars'`
|
|
82
|
+
*/
|
|
83
|
+
introspect(section?: IntrospectionSection) : HelperIntrospection | undefined {
|
|
84
|
+
const base = (this.constructor as any).introspect()
|
|
85
|
+
if (!base || !section) return base
|
|
86
|
+
return filterIntrospection(base, section)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Alias for introspect */
|
|
90
|
+
inspect(section?: IntrospectionSection) : HelperIntrospection | undefined {
|
|
91
|
+
return this.introspect()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the introspection data formatted as a markdown string.
|
|
96
|
+
*
|
|
97
|
+
* The first argument can be a section name (`'methods'`, `'getters'`, etc.) to render only
|
|
98
|
+
* that section, or a number for the starting heading depth (backward compatible).
|
|
99
|
+
*/
|
|
100
|
+
introspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) : string {
|
|
101
|
+
const { section, depth } = resolveIntrospectAsTextArgs(sectionOrDepth, startHeadingDepth)
|
|
102
|
+
const introspection = this.introspect()
|
|
103
|
+
if (!introspection) return ''
|
|
104
|
+
return presentIntrospectionJSONAsMarkdown(introspection, depth, section)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Alias for introspectAsText */
|
|
108
|
+
inspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) : string {
|
|
109
|
+
return this.introspectAsText(sectionOrDepth, startHeadingDepth)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
constructor(options: K, context: ContainerContext) {
|
|
113
|
+
const optionSchema = (this.constructor as any).optionsSchema
|
|
114
|
+
if (optionSchema && typeof optionSchema.safeParse === 'function') {
|
|
115
|
+
const parsed = optionSchema.safeParse(options || {})
|
|
116
|
+
if (parsed.success) {
|
|
117
|
+
this._options = parsed.data as K
|
|
118
|
+
} else {
|
|
119
|
+
const details = parsed.error.issues.map((issue: any) => `${issue.path?.join('.') || 'options'}: ${issue.message}`).join('; ')
|
|
120
|
+
throw new Error(`Invalid options for ${(this.constructor as any).shortcut || this.constructor.name}: ${details || parsed.error.message}`)
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
this._options = options
|
|
124
|
+
}
|
|
125
|
+
this._context = context;
|
|
126
|
+
this.state = new State<T>({ initialState: this.initialState });
|
|
127
|
+
|
|
128
|
+
this.hide('_context', '_state', '_options', '_events', 'uuid')
|
|
129
|
+
|
|
130
|
+
this.state.observe(() => {
|
|
131
|
+
(this as any).emit('stateChange', this.state.current)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
this.afterInitialize()
|
|
135
|
+
|
|
136
|
+
this.container.emit('helperInitialized', this)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Every helper has a cache key which is computed at the time it is created through the container.
|
|
141
|
+
*
|
|
142
|
+
* This ensures only a single instance of the helper exists for the requested options.
|
|
143
|
+
*/
|
|
144
|
+
get cacheKey() {
|
|
145
|
+
return this._options._cacheKey
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* This method will get called in the constructor and can be used instead of overriding the constructor
|
|
150
|
+
* in your helper subclases.
|
|
151
|
+
*/
|
|
152
|
+
afterInitialize() {
|
|
153
|
+
// override this method to do something after the helper is initialized
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
setState(newState: SetStateValue<T>) {
|
|
157
|
+
this.state.setState(newState)
|
|
158
|
+
return this
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Convenience method for putting properties on the helper that aren't enumerable,
|
|
163
|
+
* which is a convenience for the REPL mainly.
|
|
164
|
+
*/
|
|
165
|
+
hide(...propNames: string[]) {
|
|
166
|
+
propNames.map((propName) => {
|
|
167
|
+
Object.defineProperty(this, propName, { enumerable: false })
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return this
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* python / lodash style get method, which will get a value from the container using dot notation
|
|
175
|
+
* and will return a default value if the value is not found.
|
|
176
|
+
*/
|
|
177
|
+
tryGet<K extends (string | string[]), T extends object = any>(key: K, defaultValue?: T) {
|
|
178
|
+
return get(this, key, defaultValue)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The options passed to the helper when it was created.
|
|
183
|
+
*/
|
|
184
|
+
get options() {
|
|
185
|
+
return this._options;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The context object that was passed to the helper when it was created, this is decided by the container
|
|
190
|
+
* and not something you would manipulate.
|
|
191
|
+
*/
|
|
192
|
+
get context() {
|
|
193
|
+
return this._context;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The container that the helper is connected to.
|
|
198
|
+
*/
|
|
199
|
+
get container() {
|
|
200
|
+
return this.context.container;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
emit<Ev extends string & keyof E>(event: Ev, ...args: E[Ev]) {
|
|
204
|
+
this._events.emit(event, ...args)
|
|
205
|
+
return this
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
on<Ev extends string & keyof E>(event: Ev, listener: (...args: E[Ev]) => void) {
|
|
209
|
+
this._events.on(event, listener)
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
off<Ev extends string & keyof E>(event: Ev, listener?: (...args: E[Ev]) => void) {
|
|
214
|
+
this._events.off(event, listener)
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
once<Ev extends string & keyof E>(event: Ev, listener: (...args: E[Ev]) => void) {
|
|
219
|
+
this._events.once(event, listener)
|
|
220
|
+
return this
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async waitFor<Ev extends string & keyof E>(event: Ev) {
|
|
224
|
+
const resp = await this._events.waitFor(event)
|
|
225
|
+
return resp
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const INTROSPECTION_SECTIONS: IntrospectionSection[] = ['methods', 'getters', 'events', 'state', 'options', 'envVars', 'examples', 'usage']
|
|
230
|
+
|
|
231
|
+
function filterIntrospection(data: HelperIntrospection, section: IntrospectionSection): HelperIntrospection {
|
|
232
|
+
const filtered: HelperIntrospection = {
|
|
233
|
+
id: data.id,
|
|
234
|
+
description: data.description,
|
|
235
|
+
shortcut: data.shortcut,
|
|
236
|
+
methods: {},
|
|
237
|
+
getters: {},
|
|
238
|
+
events: {},
|
|
239
|
+
state: {},
|
|
240
|
+
options: {},
|
|
241
|
+
envVars: [],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (section === 'examples') {
|
|
245
|
+
// For examples section, include class-level examples and full methods/getters (they carry inline examples)
|
|
246
|
+
filtered.examples = data.examples
|
|
247
|
+
filtered.methods = data.methods
|
|
248
|
+
filtered.getters = data.getters
|
|
249
|
+
} else if (section === 'usage') {
|
|
250
|
+
// Usage is derived from options + shortcut, so pass those through
|
|
251
|
+
filtered.options = data.options
|
|
252
|
+
} else {
|
|
253
|
+
filtered[section] = data[section] as any
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return filtered
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function resolveIntrospectAsTextArgs(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) {
|
|
260
|
+
let section: IntrospectionSection | undefined
|
|
261
|
+
let depth = 1
|
|
262
|
+
|
|
263
|
+
if (typeof sectionOrDepth === 'string') {
|
|
264
|
+
section = sectionOrDepth
|
|
265
|
+
depth = startHeadingDepth ?? 1
|
|
266
|
+
} else if (typeof sectionOrDepth === 'number') {
|
|
267
|
+
depth = sectionOrDepth
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { section, depth }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function renderMethodsSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
274
|
+
const sections: string[] = []
|
|
275
|
+
if (!introspection.methods || Object.keys(introspection.methods).length === 0) return sections
|
|
276
|
+
|
|
277
|
+
sections.push(`${heading(2)} Methods`)
|
|
278
|
+
|
|
279
|
+
for (const [methodName, methodInfo] of Object.entries(introspection.methods)) {
|
|
280
|
+
sections.push(`${heading(3)} ${methodName}`)
|
|
281
|
+
|
|
282
|
+
if (methodInfo.description) {
|
|
283
|
+
sections.push(methodInfo.description)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (methodInfo.parameters && Object.keys(methodInfo.parameters).length > 0) {
|
|
287
|
+
const tableRows = [
|
|
288
|
+
`**Parameters:**`,
|
|
289
|
+
'',
|
|
290
|
+
`| Name | Type | Required | Description |`,
|
|
291
|
+
`|------|------|----------|-------------|`,
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
for (const [paramName, paramInfo] of Object.entries(methodInfo.parameters)) {
|
|
295
|
+
const isRequired = methodInfo.required?.includes(paramName) ? '✓' : ''
|
|
296
|
+
const type = paramInfo.type || 'any'
|
|
297
|
+
const description = paramInfo.description || ''
|
|
298
|
+
tableRows.push(`| \`${paramName}\` | \`${type}\` | ${isRequired} | ${description} |`)
|
|
299
|
+
|
|
300
|
+
// Render expanded type properties if available
|
|
301
|
+
if (paramInfo.properties && Object.keys(paramInfo.properties).length > 0) {
|
|
302
|
+
tableRows.push('')
|
|
303
|
+
tableRows.push(`\`${type}\` properties:`)
|
|
304
|
+
tableRows.push('')
|
|
305
|
+
tableRows.push(`| Property | Type | Description |`)
|
|
306
|
+
tableRows.push(`|----------|------|-------------|`)
|
|
307
|
+
|
|
308
|
+
for (const [propName, propInfo] of Object.entries(paramInfo.properties)) {
|
|
309
|
+
tableRows.push(`| \`${propName}\` | \`${propInfo.type || 'any'}\` | ${propInfo.description || ''} |`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
sections.push(tableRows.join('\n'))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (methodInfo.returns) {
|
|
317
|
+
sections.push(`**Returns:** \`${methodInfo.returns}\``)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (methodInfo.examples && methodInfo.examples.length > 0) {
|
|
321
|
+
for (const example of methodInfo.examples) {
|
|
322
|
+
sections.push(`\`\`\`${normalizeLang(example.language)}\n${example.code}\n\`\`\``)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
sections.push('')
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return sections
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function renderGettersSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
333
|
+
const sections: string[] = []
|
|
334
|
+
if (!introspection.getters || Object.keys(introspection.getters).length === 0) return sections
|
|
335
|
+
|
|
336
|
+
const tableRows = [
|
|
337
|
+
`${heading(2)} Getters`,
|
|
338
|
+
'',
|
|
339
|
+
`| Property | Type | Description |`,
|
|
340
|
+
`|----------|------|-------------|`,
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
for (const [getterName, getterInfo] of Object.entries(introspection.getters)) {
|
|
344
|
+
const type = getterInfo.returns || 'any'
|
|
345
|
+
const description = getterInfo.description || ''
|
|
346
|
+
tableRows.push(`| \`${getterName}\` | \`${type}\` | ${description} |`)
|
|
347
|
+
}
|
|
348
|
+
sections.push(tableRows.join('\n'))
|
|
349
|
+
|
|
350
|
+
return sections
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderEventsSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
354
|
+
const sections: string[] = []
|
|
355
|
+
if (!introspection.events || Object.keys(introspection.events).length === 0) return sections
|
|
356
|
+
|
|
357
|
+
sections.push(`${heading(2)} Events (Zod v4 schema)`)
|
|
358
|
+
|
|
359
|
+
for (const [eventName, eventInfo] of Object.entries(introspection.events)) {
|
|
360
|
+
sections.push(`${heading(3)} ${eventName}`)
|
|
361
|
+
|
|
362
|
+
if (eventInfo.description) {
|
|
363
|
+
sections.push(eventInfo.description)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (eventInfo.arguments && Object.keys(eventInfo.arguments).length > 0) {
|
|
367
|
+
const tableRows = [
|
|
368
|
+
`**Event Arguments:**`,
|
|
369
|
+
'',
|
|
370
|
+
`| Name | Type | Description |`,
|
|
371
|
+
`|------|------|-------------|`,
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
for (const [argName, argInfo] of Object.entries(eventInfo.arguments)) {
|
|
375
|
+
tableRows.push(`| \`${argName}\` | \`${argInfo.type || 'any'}\` | ${argInfo.description || ''} |`)
|
|
376
|
+
}
|
|
377
|
+
sections.push(tableRows.join('\n'))
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
sections.push('')
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return sections
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderStateSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
387
|
+
const sections: string[] = []
|
|
388
|
+
if (!introspection.state || Object.keys(introspection.state).length === 0) return sections
|
|
389
|
+
|
|
390
|
+
const tableRows = [
|
|
391
|
+
`${heading(2)} State (Zod v4 schema)`,
|
|
392
|
+
'',
|
|
393
|
+
`| Property | Type | Description |`,
|
|
394
|
+
`|----------|------|-------------|`,
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
for (const [stateName, stateInfo] of Object.entries(introspection.state)) {
|
|
398
|
+
tableRows.push(`| \`${stateName}\` | \`${stateInfo.type || 'any'}\` | ${stateInfo.description || ''} |`)
|
|
399
|
+
}
|
|
400
|
+
sections.push(tableRows.join('\n'))
|
|
401
|
+
|
|
402
|
+
return sections
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function renderOptionsSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
406
|
+
const sections: string[] = []
|
|
407
|
+
if (!introspection.options || Object.keys(introspection.options).length === 0) return sections
|
|
408
|
+
|
|
409
|
+
const tableRows = [
|
|
410
|
+
`${heading(2)} Options (Zod v4 schema)`,
|
|
411
|
+
'',
|
|
412
|
+
`| Property | Type | Description |`,
|
|
413
|
+
`|----------|------|-------------|`,
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
for (const [optName, optInfo] of Object.entries(introspection.options)) {
|
|
417
|
+
tableRows.push(`| \`${optName}\` | \`${optInfo.type || 'any'}\` | ${optInfo.description || ''} |`)
|
|
418
|
+
}
|
|
419
|
+
sections.push(tableRows.join('\n'))
|
|
420
|
+
|
|
421
|
+
return sections
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function renderUsageSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
425
|
+
const sections: string[] = []
|
|
426
|
+
|
|
427
|
+
// Derive the factory method from the shortcut, e.g. "features.diskCache" -> container.feature('diskCache', { ... })
|
|
428
|
+
const shortcut = introspection.shortcut || introspection.id || ''
|
|
429
|
+
const parts = shortcut.split('.')
|
|
430
|
+
if (parts.length < 2) return sections
|
|
431
|
+
|
|
432
|
+
const scope = parts[0]! // e.g. "features"
|
|
433
|
+
const name = parts.slice(1).join('.') // e.g. "diskCache"
|
|
434
|
+
// Singular form: features -> feature, clients -> client, servers -> server, commands -> command, endpoints -> endpoint
|
|
435
|
+
const factoryMethod = scope.endsWith('s') ? scope.slice(0, -1) : scope
|
|
436
|
+
|
|
437
|
+
const options = introspection.options || {}
|
|
438
|
+
const optionEntries = Object.entries(options)
|
|
439
|
+
|
|
440
|
+
sections.push(`${heading(2)} Usage`)
|
|
441
|
+
|
|
442
|
+
if (optionEntries.length === 0) {
|
|
443
|
+
sections.push(`\`\`\`ts\ncontainer.${factoryMethod}('${name}')\n\`\`\``)
|
|
444
|
+
} else {
|
|
445
|
+
const optionLines = optionEntries.map(([optName, optInfo]) => {
|
|
446
|
+
const desc = optInfo.description ? `// ${optInfo.description}` : ''
|
|
447
|
+
return ` ${desc}\n ${optName},`
|
|
448
|
+
}).join('\n')
|
|
449
|
+
|
|
450
|
+
sections.push(`\`\`\`ts\ncontainer.${factoryMethod}('${name}', {\n${optionLines}\n})\n\`\`\``)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return sections
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function renderEnvVarsSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
457
|
+
const sections: string[] = []
|
|
458
|
+
if (!introspection.envVars || introspection.envVars.length === 0) return sections
|
|
459
|
+
|
|
460
|
+
sections.push(`${heading(2)} Environment Variables`)
|
|
461
|
+
sections.push(introspection.envVars.map((envVar) => `- \`${envVar}\``).join('\n'))
|
|
462
|
+
|
|
463
|
+
return sections
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function renderExamplesSection(introspection: HelperIntrospection, heading: (level: number) => string): string[] {
|
|
467
|
+
const sections: string[] = []
|
|
468
|
+
|
|
469
|
+
// Collect all examples: class-level, method-level, getter-level
|
|
470
|
+
const allExamples: { source: string; examples: ExampleIntrospection[] }[] = []
|
|
471
|
+
|
|
472
|
+
if (introspection.examples && introspection.examples.length > 0) {
|
|
473
|
+
allExamples.push({ source: introspection.id, examples: introspection.examples })
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const [name, method] of Object.entries(introspection.methods || {})) {
|
|
477
|
+
if (method.examples && method.examples.length > 0) {
|
|
478
|
+
allExamples.push({ source: name, examples: method.examples })
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
for (const [name, getter] of Object.entries(introspection.getters || {})) {
|
|
483
|
+
if (getter.examples && getter.examples.length > 0) {
|
|
484
|
+
allExamples.push({ source: name, examples: getter.examples })
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (allExamples.length === 0) return sections
|
|
489
|
+
|
|
490
|
+
sections.push(`${heading(2)} Examples`)
|
|
491
|
+
|
|
492
|
+
for (const { source, examples } of allExamples) {
|
|
493
|
+
sections.push(`**${source}**`)
|
|
494
|
+
for (const example of examples) {
|
|
495
|
+
sections.push(`\`\`\`${normalizeLang(example.language)}\n${example.code}\n\`\`\``)
|
|
496
|
+
}
|
|
497
|
+
sections.push('')
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return sections
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Normalize verbose language names to short tags for code fences */
|
|
504
|
+
function normalizeLang(lang: string): string {
|
|
505
|
+
if (lang === 'typescript') return 'ts'
|
|
506
|
+
if (lang === 'javascript') return 'js'
|
|
507
|
+
return lang
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export { presentIntrospectionJSONAsMarkdown as presentIntrospectionAsMarkdown }
|
|
511
|
+
|
|
512
|
+
function presentIntrospectionJSONAsMarkdown(introspection: HelperIntrospection, startHeadingDepth: number = 1, section?: IntrospectionSection) {
|
|
513
|
+
const sections: string[] = []
|
|
514
|
+
const heading = (level: number) => '#'.repeat(Math.max(1, startHeadingDepth + level - 1))
|
|
515
|
+
|
|
516
|
+
if (!section) {
|
|
517
|
+
const title = introspection.className
|
|
518
|
+
? `${heading(1)} ${introspection.className} (${introspection.id})`
|
|
519
|
+
: `${heading(1)} ${introspection.id}`
|
|
520
|
+
sections.push(`${title}\n\n${introspection.description}`)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const renderers: Record<IntrospectionSection, () => string[]> = {
|
|
524
|
+
usage: () => renderUsageSection(introspection, heading),
|
|
525
|
+
options: () => renderOptionsSection(introspection, heading),
|
|
526
|
+
methods: () => renderMethodsSection(introspection, heading),
|
|
527
|
+
getters: () => renderGettersSection(introspection, heading),
|
|
528
|
+
events: () => renderEventsSection(introspection, heading),
|
|
529
|
+
state: () => renderStateSection(introspection, heading),
|
|
530
|
+
envVars: () => renderEnvVarsSection(introspection, heading),
|
|
531
|
+
examples: () => renderExamplesSection(introspection, heading),
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (section) {
|
|
535
|
+
sections.push(...renderers[section]())
|
|
536
|
+
} else {
|
|
537
|
+
for (const renderer of Object.values(renderers)) {
|
|
538
|
+
sections.push(...renderer())
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return sections.join('\n\n')
|
|
543
|
+
}
|