luca 3.0.0 → 3.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/.github/workflows/release.yaml +1 -0
- package/CLAUDE.md +10 -2
- package/README.md +130 -112
- package/assistants/codingAssistant/CORE.md +6 -1
- package/assistants/codingAssistant/hooks.ts +1 -1
- package/assistants/inkbot/hooks.ts +1 -1
- package/assistants/inkbot/tools.ts +1 -1
- package/bun.lock +220 -322
- package/commands/audit-docs.ts +2 -2
- package/commands/build-bootstrap.ts +2 -3
- package/commands/build-python-bridge.ts +2 -3
- package/commands/build-scaffolds.ts +2 -3
- package/commands/bundle-consumer-project.ts +521 -0
- package/commands/generate-api-docs.ts +2 -2
- package/commands/inkbot.ts +2 -2
- package/commands/release.ts +2 -2
- package/commands/try-all-challenges.ts +3 -3
- package/commands/try-challenge.ts +3 -3
- package/dist/agi/container.server.d.ts +2 -2
- package/dist/agi/features/assistant.d.ts +2 -2
- package/dist/agi/features/assistants-manager.d.ts +1 -1
- package/dist/agi/features/autonomous-assistant.d.ts +1 -1
- package/dist/agi/features/browser-use.d.ts +1 -1
- package/dist/agi/features/claude-code.d.ts +1 -1
- package/dist/agi/features/conversation-history.d.ts +2 -2
- package/dist/agi/features/conversation.d.ts +1 -1
- package/dist/agi/features/docs-reader.d.ts +1 -1
- package/dist/agi/features/file-tools.d.ts +1 -1
- package/dist/agi/features/luca-coder.d.ts +1 -1
- package/dist/agi/features/openai-codex.d.ts +1 -1
- package/dist/agi/features/skills-library.d.ts +1 -1
- package/dist/clients/civitai/index.d.ts +4 -4
- package/dist/clients/client-template.d.ts +4 -4
- package/dist/clients/comfyui/index.d.ts +2 -2
- package/dist/clients/elevenlabs/index.d.ts +2 -2
- package/dist/clients/openai/index.d.ts +2 -2
- package/dist/clients/supabase/index.d.ts +3 -3
- package/dist/command.d.ts +1 -1
- package/dist/node/container.d.ts +1 -1
- package/dist/node/features/helpers.d.ts +3 -3
- package/dist/node/features/semantic-search.d.ts +1 -1
- package/dist/node/features/vm.d.ts +3 -3
- package/dist/node.d.ts +1 -1
- package/dist/scaffolds/generated.d.ts +1 -1
- package/dist/selector.d.ts +1 -1
- package/index.html +217 -190
- package/luca.console.ts +1 -1
- package/package.json +2 -2
- package/public/index.html +217 -190
- package/public/slides-ai-native.html +1 -1
- package/public/slides-intro.html +2 -2
- package/scripts/examples/ask-luca-expert.ts +1 -1
- package/scripts/examples/assistant-questions.ts +1 -1
- package/scripts/examples/excalidraw-expert.ts +1 -1
- package/scripts/examples/file-manager.ts +1 -1
- package/scripts/examples/ideas.ts +1 -1
- package/scripts/examples/interactive-chat.ts +1 -1
- package/scripts/examples/opening-a-web-browser.ts +1 -1
- package/scripts/examples/telegram-bot.ts +1 -1
- package/scripts/examples/using-assistant-with-mcp.ts +1 -1
- package/scripts/examples/using-claude-code.ts +1 -1
- package/scripts/examples/using-contentdb.ts +2 -2
- package/scripts/examples/using-conversations.ts +1 -1
- package/scripts/examples/using-disk-cache.ts +1 -1
- package/scripts/examples/using-docker-shell.ts +1 -1
- package/scripts/examples/using-elevenlabs.ts +1 -1
- package/scripts/examples/using-google-calendar.ts +1 -1
- package/scripts/examples/using-google-docs.ts +1 -1
- package/scripts/examples/using-google-drive.ts +1 -1
- package/scripts/examples/using-google-sheets.ts +1 -1
- package/scripts/examples/using-nlp.ts +1 -1
- package/scripts/examples/using-ollama.ts +1 -1
- package/scripts/examples/using-postgres.ts +1 -1
- package/scripts/examples/using-runpod.ts +1 -1
- package/scripts/examples/using-tts.ts +1 -1
- package/scripts/scaffold.ts +5 -5
- package/scripts/scratch.ts +1 -1
- package/scripts/test-assistant-hooks.ts +1 -1
- package/scripts/test-docs-reader.ts +1 -1
- package/src/agi/container.server.ts +6 -2
- package/src/agi/features/agent-memory.ts +25 -25
- package/src/agi/features/assistant.ts +34 -5
- package/src/agi/features/assistants-manager.ts +122 -6
- package/src/agi/features/autonomous-assistant.ts +1 -1
- package/src/agi/features/browser-use.ts +20 -1
- package/src/agi/features/claude-code.ts +51 -5
- package/src/agi/features/coding-tools.ts +1 -1
- package/src/agi/features/conversation-history.ts +181 -4
- package/src/agi/features/conversation.ts +186 -15
- package/src/agi/features/docs-reader.ts +2 -2
- package/src/agi/features/file-tools.ts +49 -2
- package/src/agi/features/luca-coder.ts +7 -5
- package/src/agi/features/mcp-bridge.ts +532 -0
- package/src/agi/features/openai-codex.ts +2 -2
- package/src/agi/features/skills-library.ts +131 -52
- package/src/agi/lib/token-counter.ts +80 -0
- package/src/bootstrap/generated.ts +56 -57
- package/src/browser.ts +1 -1
- package/src/cli/build-info.ts +2 -2
- package/src/cli/cli.ts +2 -2
- package/src/clients/civitai/index.ts +5 -5
- package/src/clients/client-template.ts +4 -4
- package/src/clients/comfyui/index.ts +4 -4
- package/src/clients/elevenlabs/index.ts +4 -4
- package/src/clients/openai/index.ts +7 -7
- package/src/clients/supabase/index.ts +4 -4
- package/src/clients/voicebox/index.ts +4 -4
- package/src/command.ts +2 -1
- package/src/commands/chat.ts +1 -0
- package/src/commands/eval.ts +2 -56
- package/src/commands/introspect.ts +1 -1
- package/src/commands/prompt.ts +41 -9
- package/src/container-describer.ts +8 -1
- package/src/container.ts +13 -0
- package/src/entity.ts +2 -2
- package/src/helper.ts +1 -1
- package/src/introspection/generated.agi.ts +28563 -27571
- package/src/introspection/generated.node.ts +20281 -20194
- package/src/introspection/generated.web.ts +605 -584
- package/src/introspection/scan.ts +11 -6
- package/src/node/container.ts +1 -1
- package/src/node/features/content-db.ts +39 -2
- package/src/node/features/display-result.ts +57 -0
- package/src/node/features/helpers.ts +42 -15
- package/src/node/features/python.ts +25 -19
- package/src/node/features/repl.ts +1 -1
- package/src/node/features/secure-shell.ts +11 -17
- package/src/node/features/semantic-search.ts +2 -2
- package/src/node/features/transpiler.ts +2 -3
- package/src/node/features/ui.ts +5 -0
- package/src/node/features/vm.ts +3 -3
- package/src/node.ts +3 -3
- package/src/python/generated.ts +0 -1
- package/src/scaffolds/generated.ts +82 -83
- package/src/selector.ts +1 -1
- package/src/servers/express.ts +1 -1
- package/src/web/features/helpers.ts +22 -0
- package/tsconfig.json +12 -12
- package/docs/CLI.md +0 -335
- package/docs/CNAME +0 -1
- package/docs/README.md +0 -60
- package/docs/TABLE-OF-CONTENTS.md +0 -183
- package/docs/apis/clients/elevenlabs.md +0 -308
- package/docs/apis/clients/graph.md +0 -107
- package/docs/apis/clients/openai.md +0 -429
- package/docs/apis/clients/rest.md +0 -161
- package/docs/apis/clients/websocket.md +0 -174
- package/docs/apis/features/agi/assistant.md +0 -625
- package/docs/apis/features/agi/assistants-manager.md +0 -282
- package/docs/apis/features/agi/auto-assistant.md +0 -279
- package/docs/apis/features/agi/browser-use.md +0 -802
- package/docs/apis/features/agi/claude-code.md +0 -884
- package/docs/apis/features/agi/conversation-history.md +0 -364
- package/docs/apis/features/agi/conversation.md +0 -548
- package/docs/apis/features/agi/docs-reader.md +0 -99
- package/docs/apis/features/agi/file-tools.md +0 -163
- package/docs/apis/features/agi/luca-coder.md +0 -407
- package/docs/apis/features/agi/openai-codex.md +0 -396
- package/docs/apis/features/agi/openapi.md +0 -138
- package/docs/apis/features/agi/semantic-search.md +0 -387
- package/docs/apis/features/agi/skills-library.md +0 -239
- package/docs/apis/features/node/container-link.md +0 -192
- package/docs/apis/features/node/content-db.md +0 -450
- package/docs/apis/features/node/disk-cache.md +0 -379
- package/docs/apis/features/node/dns.md +0 -652
- package/docs/apis/features/node/docker.md +0 -706
- package/docs/apis/features/node/downloader.md +0 -81
- package/docs/apis/features/node/esbuild.md +0 -60
- package/docs/apis/features/node/file-manager.md +0 -191
- package/docs/apis/features/node/fs.md +0 -1217
- package/docs/apis/features/node/git.md +0 -371
- package/docs/apis/features/node/google-auth.md +0 -193
- package/docs/apis/features/node/google-calendar.md +0 -202
- package/docs/apis/features/node/google-docs.md +0 -173
- package/docs/apis/features/node/google-drive.md +0 -246
- package/docs/apis/features/node/google-mail.md +0 -214
- package/docs/apis/features/node/google-sheets.md +0 -194
- package/docs/apis/features/node/grep.md +0 -292
- package/docs/apis/features/node/helpers.md +0 -164
- package/docs/apis/features/node/ink.md +0 -334
- package/docs/apis/features/node/ipc-socket.md +0 -249
- package/docs/apis/features/node/json-tree.md +0 -86
- package/docs/apis/features/node/networking.md +0 -316
- package/docs/apis/features/node/nlp.md +0 -133
- package/docs/apis/features/node/opener.md +0 -97
- package/docs/apis/features/node/os.md +0 -146
- package/docs/apis/features/node/package-finder.md +0 -392
- package/docs/apis/features/node/postgres.md +0 -234
- package/docs/apis/features/node/proc.md +0 -399
- package/docs/apis/features/node/process-manager.md +0 -305
- package/docs/apis/features/node/python.md +0 -604
- package/docs/apis/features/node/redis.md +0 -380
- package/docs/apis/features/node/repl.md +0 -88
- package/docs/apis/features/node/runpod.md +0 -674
- package/docs/apis/features/node/secure-shell.md +0 -176
- package/docs/apis/features/node/semantic-search.md +0 -408
- package/docs/apis/features/node/sqlite.md +0 -233
- package/docs/apis/features/node/telegram.md +0 -279
- package/docs/apis/features/node/transpiler.md +0 -74
- package/docs/apis/features/node/tts.md +0 -133
- package/docs/apis/features/node/ui.md +0 -701
- package/docs/apis/features/node/vault.md +0 -59
- package/docs/apis/features/node/vm.md +0 -75
- package/docs/apis/features/node/yaml-tree.md +0 -85
- package/docs/apis/features/node/yaml.md +0 -176
- package/docs/apis/features/web/asset-loader.md +0 -59
- package/docs/apis/features/web/container-link.md +0 -192
- package/docs/apis/features/web/esbuild.md +0 -54
- package/docs/apis/features/web/helpers.md +0 -164
- package/docs/apis/features/web/network.md +0 -44
- package/docs/apis/features/web/speech.md +0 -69
- package/docs/apis/features/web/vault.md +0 -59
- package/docs/apis/features/web/vm.md +0 -75
- package/docs/apis/features/web/voice.md +0 -84
- package/docs/apis/servers/express.md +0 -171
- package/docs/apis/servers/mcp.md +0 -238
- package/docs/apis/servers/websocket.md +0 -170
- package/docs/bootstrap/CLAUDE.md +0 -101
- package/docs/bootstrap/SKILL.md +0 -341
- package/docs/bootstrap/templates/about-command.ts +0 -41
- package/docs/bootstrap/templates/docs-models.ts +0 -22
- package/docs/bootstrap/templates/docs-readme.md +0 -43
- package/docs/bootstrap/templates/example-feature.ts +0 -53
- package/docs/bootstrap/templates/health-endpoint.ts +0 -15
- package/docs/bootstrap/templates/luca-cli.ts +0 -30
- package/docs/bootstrap/templates/runme.md +0 -54
- package/docs/challenges/caching-proxy.md +0 -16
- package/docs/challenges/content-db-round-trip.md +0 -14
- package/docs/challenges/custom-command.md +0 -9
- package/docs/challenges/file-watcher-pipeline.md +0 -11
- package/docs/challenges/grep-audit-report.md +0 -15
- package/docs/challenges/multi-feature-dashboard.md +0 -14
- package/docs/challenges/process-orchestrator.md +0 -17
- package/docs/challenges/rest-api-server-with-client.md +0 -12
- package/docs/challenges/script-runner-with-vm.md +0 -11
- package/docs/challenges/simple-rest-api.md +0 -15
- package/docs/challenges/websocket-serve-and-client.md +0 -11
- package/docs/challenges/yaml-config-system.md +0 -14
- package/docs/command-system-overhaul.md +0 -94
- package/docs/documentation-audit.md +0 -134
- package/docs/examples/assistant/CORE.md +0 -18
- package/docs/examples/assistant/hooks.ts +0 -3
- package/docs/examples/assistant/tools.ts +0 -10
- package/docs/examples/assistant-hooks-reference.ts +0 -171
- package/docs/examples/assistant-with-process-manager.md +0 -84
- package/docs/examples/content-db.md +0 -77
- package/docs/examples/disk-cache.md +0 -83
- package/docs/examples/docker.md +0 -101
- package/docs/examples/downloader.md +0 -70
- package/docs/examples/entity.md +0 -124
- package/docs/examples/esbuild.md +0 -80
- package/docs/examples/feature-as-tool-provider.md +0 -143
- package/docs/examples/file-manager.md +0 -82
- package/docs/examples/fs.md +0 -83
- package/docs/examples/git.md +0 -85
- package/docs/examples/google-auth.md +0 -88
- package/docs/examples/google-calendar.md +0 -94
- package/docs/examples/google-docs.md +0 -82
- package/docs/examples/google-drive.md +0 -96
- package/docs/examples/google-sheets.md +0 -95
- package/docs/examples/grep.md +0 -85
- package/docs/examples/ink-blocks.md +0 -75
- package/docs/examples/ink-renderer.md +0 -41
- package/docs/examples/ink.md +0 -103
- package/docs/examples/ipc-socket.md +0 -103
- package/docs/examples/json-tree.md +0 -91
- package/docs/examples/networking.md +0 -58
- package/docs/examples/nlp.md +0 -91
- package/docs/examples/opener.md +0 -78
- package/docs/examples/os.md +0 -72
- package/docs/examples/package-finder.md +0 -89
- package/docs/examples/postgres.md +0 -91
- package/docs/examples/proc.md +0 -81
- package/docs/examples/process-manager.md +0 -79
- package/docs/examples/python.md +0 -132
- package/docs/examples/repl.md +0 -93
- package/docs/examples/runpod.md +0 -119
- package/docs/examples/secure-shell.md +0 -92
- package/docs/examples/sqlite.md +0 -86
- package/docs/examples/structured-output-with-assistants.md +0 -144
- package/docs/examples/telegram.md +0 -77
- package/docs/examples/tts.md +0 -86
- package/docs/examples/ui.md +0 -80
- package/docs/examples/vault.md +0 -70
- package/docs/examples/vm.md +0 -86
- package/docs/examples/websocket-ask-and-reply-example.md +0 -128
- package/docs/examples/yaml-tree.md +0 -93
- package/docs/examples/yaml.md +0 -104
- package/docs/ideas/assistant-factory-pattern.md +0 -142
- package/docs/in-memory-fs.md +0 -4
- package/docs/introspection-audit.md +0 -49
- package/docs/introspection.md +0 -164
- package/docs/mcp/readme.md +0 -162
- package/docs/models.ts +0 -41
- package/docs/philosophy.md +0 -86
- package/docs/principles.md +0 -7
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +0 -34
- package/docs/prompts/check-for-undocumented-features.md +0 -27
- package/docs/prompts/mcp-test-easy-command.md +0 -27
- package/docs/scaffolds/client.md +0 -149
- package/docs/scaffolds/command.md +0 -120
- package/docs/scaffolds/endpoint.md +0 -171
- package/docs/scaffolds/feature.md +0 -158
- package/docs/scaffolds/selector.md +0 -91
- package/docs/scaffolds/server.md +0 -196
- package/docs/selectors.md +0 -115
- package/docs/sessions/custom-command/attempt-log-2.md +0 -195
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +0 -728
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +0 -555
- package/docs/sessions/grep-audit-report/attempt-log-1.md +0 -289
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +0 -679
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +0 -1
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +0 -920
- package/docs/sessions/simple-rest-api/attempt-log-1.md +0 -593
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +0 -995
- package/docs/tutorials/00-bootstrap.md +0 -166
- package/docs/tutorials/01-getting-started.md +0 -106
- package/docs/tutorials/02-container.md +0 -210
- package/docs/tutorials/03-scripts.md +0 -194
- package/docs/tutorials/04-features-overview.md +0 -196
- package/docs/tutorials/05-state-and-events.md +0 -171
- package/docs/tutorials/06-servers.md +0 -157
- package/docs/tutorials/07-endpoints.md +0 -198
- package/docs/tutorials/08-commands.md +0 -252
- package/docs/tutorials/09-clients.md +0 -162
- package/docs/tutorials/10-creating-features.md +0 -203
- package/docs/tutorials/11-contentbase.md +0 -191
- package/docs/tutorials/12-assistants.md +0 -215
- package/docs/tutorials/13-introspection.md +0 -157
- package/docs/tutorials/14-type-system.md +0 -174
- package/docs/tutorials/15-project-patterns.md +0 -222
- package/docs/tutorials/16-google-features.md +0 -534
- package/docs/tutorials/17-tui-blocks.md +0 -530
- package/docs/tutorials/18-semantic-search.md +0 -334
- package/docs/tutorials/19-python-sessions.md +0 -401
- package/docs/tutorials/20-browser-esm.md +0 -234
- package/src/agi/endpoints/ask.ts +0 -60
- package/src/agi/endpoints/conversations/[id].ts +0 -45
- package/src/agi/endpoints/conversations.ts +0 -31
- package/src/agi/endpoints/experts.ts +0 -37
- package/test/assistant-hooks.test.ts +0 -306
- package/test/assistant.test.ts +0 -81
- package/test/bus.test.ts +0 -134
- package/test/clients-servers.test.ts +0 -217
- package/test/command.test.ts +0 -267
- package/test/container-link.test.ts +0 -274
- package/test/conversation.test.ts +0 -220
- package/test/features.test.ts +0 -160
- package/test/fork-and-research.test.ts +0 -450
- package/test/integration.test.ts +0 -787
- package/test/interceptor-chain.test.ts +0 -61
- package/test/node-container.test.ts +0 -121
- package/test/python-session.test.ts +0 -105
- package/test/rate-limit.test.ts +0 -272
- package/test/semantic-search.test.ts +0 -550
- package/test/state.test.ts +0 -121
- package/test/vm-context.test.ts +0 -146
- package/test/vm-loadmodule.test.ts +0 -213
- package/test/websocket-ask.test.ts +0 -101
- package/test-integration/assistant.test.ts +0 -138
- package/test-integration/assistants-manager.test.ts +0 -113
- package/test-integration/claude-code.test.ts +0 -98
- package/test-integration/conversation-history.test.ts +0 -205
- package/test-integration/conversation.test.ts +0 -137
- package/test-integration/elevenlabs.test.ts +0 -55
- package/test-integration/google-services.test.ts +0 -80
- package/test-integration/helpers.ts +0 -89
- package/test-integration/memory.test.ts +0 -204
- package/test-integration/openai-codex.test.ts +0 -93
- package/test-integration/runpod.test.ts +0 -58
- package/test-integration/server-endpoints.test.ts +0 -97
- package/test-integration/telegram.test.ts +0 -46
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Writing Commands
|
|
3
|
-
tags: [commands, cli, luca-cli, scripts, args]
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Writing Commands
|
|
7
|
-
|
|
8
|
-
Commands are CLI actions that the `luca` command discovers and runs. They are Helper subclasses under the hood — the framework grafts your module exports into a proper Command class at runtime, so you get the full Helper lifecycle (state, events, introspection) without ceremony.
|
|
9
|
-
|
|
10
|
-
## Basic Command
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
// commands/seed.ts
|
|
14
|
-
import { z } from 'zod'
|
|
15
|
-
import type { ContainerContext } from '@soederpop/luca'
|
|
16
|
-
|
|
17
|
-
export const description = 'Seed the database with sample data'
|
|
18
|
-
|
|
19
|
-
export const argsSchema = z.object({
|
|
20
|
-
count: z.number().default(10).describe('Number of records to seed'),
|
|
21
|
-
table: z.string().optional().describe('Specific table to seed'),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
export default async function seed(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
25
|
-
const { container } = context
|
|
26
|
-
|
|
27
|
-
console.log(`Seeding ${options.count} records...`)
|
|
28
|
-
|
|
29
|
-
for (let i = 0; i < options.count; i++) {
|
|
30
|
-
console.log(` Created record ${i + 1}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log('Done.')
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Run it:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
luca seed --count 20 --table users
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## How Discovery Works
|
|
44
|
-
|
|
45
|
-
When you run `luca <command>`, the CLI:
|
|
46
|
-
|
|
47
|
-
1. Loads built-in commands (serve, run, eval, describe, etc.)
|
|
48
|
-
2. Loads `luca.cli.ts` if present (for project-level container customization)
|
|
49
|
-
3. Discovers project commands via the `helpers` feature — scans `commands/` for `.ts` files
|
|
50
|
-
4. Discovers user commands from `~/.luca/commands/`
|
|
51
|
-
5. The filename becomes the command name: `commands/seed.ts` → `luca seed`
|
|
52
|
-
|
|
53
|
-
Discovery routes through the `helpers` feature (`container.feature('helpers')`), which handles native import vs VM loading and deduplicates concurrent discovery calls. Commands loaded through the VM get `container` injected as a global.
|
|
54
|
-
|
|
55
|
-
The `LUCA_COMMAND_DISCOVERY` env var controls discovery: `"disable"` skips all, `"no-local"` skips project, `"no-home"` skips user commands.
|
|
56
|
-
|
|
57
|
-
## Command Module Patterns
|
|
58
|
-
|
|
59
|
-
### Pattern 1: Default Export Function (recommended for project commands)
|
|
60
|
-
|
|
61
|
-
The simplest pattern — export a default async function. The function becomes the command's `run` method.
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
// commands/greet.ts
|
|
65
|
-
import { z } from 'zod'
|
|
66
|
-
import type { ContainerContext } from '@soederpop/luca'
|
|
67
|
-
|
|
68
|
-
export const description = 'Greet someone'
|
|
69
|
-
export const argsSchema = z.object({
|
|
70
|
-
name: z.string().default('world').describe('Who to greet'),
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
export default async function greet(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
74
|
-
console.log(`Hello, ${options.name}!`)
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Pattern 2: Object Default Export with handler
|
|
79
|
-
|
|
80
|
-
Useful when you want to co-locate all exports in one object:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// commands/deploy.ts
|
|
84
|
-
import { z } from 'zod'
|
|
85
|
-
import type { ContainerContext } from '@soederpop/luca'
|
|
86
|
-
|
|
87
|
-
export const argsSchema = z.object({
|
|
88
|
-
env: z.enum(['staging', 'production']).describe('Target environment'),
|
|
89
|
-
dryRun: z.boolean().default(false).describe('Preview without deploying'),
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
async function deploy(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
93
|
-
const { container } = context
|
|
94
|
-
if (options.dryRun) {
|
|
95
|
-
console.log(`[DRY RUN] Would deploy to ${options.env}`)
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
console.log(`Deploying ${container.git.sha} to ${options.env}...`)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default {
|
|
102
|
-
description: 'Deploy the application',
|
|
103
|
-
argsSchema,
|
|
104
|
-
handler: deploy,
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Pattern 3: registerHandler (used by built-in commands)
|
|
109
|
-
|
|
110
|
-
Built-in commands use `commands.registerHandler()` for explicit registration. This is the pattern used in `src/commands/`:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// src/commands/my-builtin.ts
|
|
114
|
-
import { z } from 'zod'
|
|
115
|
-
import { commands } from '../command'
|
|
116
|
-
import { CommandOptionsSchema } from '../schemas/base'
|
|
117
|
-
import type { ContainerContext } from '../container'
|
|
118
|
-
|
|
119
|
-
declare module '../command.js' {
|
|
120
|
-
interface AvailableCommands {
|
|
121
|
-
'my-builtin': ReturnType<typeof commands.registerHandler>
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const argsSchema = CommandOptionsSchema.extend({
|
|
126
|
-
verbose: z.boolean().default(false).describe('Enable verbose output'),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
export default async function myBuiltin(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
130
|
-
// implementation
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
commands.registerHandler('my-builtin', {
|
|
134
|
-
description: 'A built-in command',
|
|
135
|
-
argsSchema,
|
|
136
|
-
handler: myBuiltin,
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Project commands generally don't need `registerHandler` — discovery handles registration automatically.
|
|
141
|
-
|
|
142
|
-
## Module Exports Reference
|
|
143
|
-
|
|
144
|
-
| Export | Type | Description |
|
|
145
|
-
|--------|------|-------------|
|
|
146
|
-
| `default` | function or object | Async function becomes `run`, or object with `{ description, argsSchema, handler }` |
|
|
147
|
-
| `description` | string | Help text shown in `luca --help` |
|
|
148
|
-
| `argsSchema` | Zod schema | Defines accepted flags, parsed from CLI args automatically |
|
|
149
|
-
| `positionals` | string[] | Names for positional arguments (mapped from `container.argv._`) |
|
|
150
|
-
| `run` | function | Named export alternative to default function — grafted as the command's run method |
|
|
151
|
-
| `handler` | function | Legacy alternative to `run` — receives parsed args via `parseArgs()` |
|
|
152
|
-
|
|
153
|
-
When discovery loads your module, `graftModule()` synthesizes a Command subclass from these exports. The `run` or `handler` function becomes the command's implementation, schemas become static properties, and any other exported functions become methods on the command instance.
|
|
154
|
-
|
|
155
|
-
## Arguments and Schemas
|
|
156
|
-
|
|
157
|
-
The `argsSchema` uses Zod to define what flags your command accepts. These are parsed from the CLI automatically:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
export const argsSchema = z.object({
|
|
161
|
-
// String flag: --name "John"
|
|
162
|
-
name: z.string().describe('User name'),
|
|
163
|
-
|
|
164
|
-
// Number flag: --port 3000
|
|
165
|
-
port: z.number().default(3000).describe('Port number'),
|
|
166
|
-
|
|
167
|
-
// Boolean flag: --verbose
|
|
168
|
-
verbose: z.boolean().default(false).describe('Enable verbose logging'),
|
|
169
|
-
|
|
170
|
-
// Optional flag: --output file.json
|
|
171
|
-
output: z.string().optional().describe('Output file path'),
|
|
172
|
-
|
|
173
|
-
// Enum flag: --format json
|
|
174
|
-
format: z.enum(['json', 'csv', 'table']).default('table').describe('Output format'),
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Positional Arguments
|
|
179
|
-
|
|
180
|
-
Export a `positionals` array to map CLI positional args into named fields on `options`. Each entry names the corresponding positional — `positionals[0]` maps `_[1]` (the first arg after the command name), `positionals[1]` maps `_[2]`, etc.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
export const positionals = ['target', 'destination']
|
|
184
|
-
|
|
185
|
-
export const argsSchema = z.object({
|
|
186
|
-
target: z.string().describe('Source path to operate on'),
|
|
187
|
-
destination: z.string().optional().describe('Where to write output'),
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// luca my-command ./src ./out
|
|
191
|
-
// => options.target === './src', options.destination === './out'
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Positional mapping only applies when dispatched from the CLI. For programmatic dispatch (`cmd.dispatch({ target: './src' }, 'headless')`), args are already named.
|
|
195
|
-
|
|
196
|
-
The raw positional array is still available as `options._` if you need it — `_[0]` is always the command name:
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
// luca greet Alice Bob
|
|
200
|
-
// options._ => ['greet', 'Alice', 'Bob']
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Using the Container
|
|
204
|
-
|
|
205
|
-
Commands receive a context with the full container:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
export default async function handler(options: any, context: ContainerContext) {
|
|
209
|
-
const { container } = context
|
|
210
|
-
|
|
211
|
-
// File system operations
|
|
212
|
-
const config = container.fs.readJson('./config.json')
|
|
213
|
-
|
|
214
|
-
// Git info (these are getters, not methods)
|
|
215
|
-
const branch = container.git.branch
|
|
216
|
-
const sha = container.git.sha
|
|
217
|
-
|
|
218
|
-
// Terminal UI
|
|
219
|
-
container.ui.colors.green('Success!')
|
|
220
|
-
|
|
221
|
-
// Run external processes (synchronous, returns string)
|
|
222
|
-
const result = container.proc.exec('ls -la')
|
|
223
|
-
|
|
224
|
-
// Use any feature
|
|
225
|
-
const cache = container.feature('diskCache', { path: './.cache' })
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Command Dispatch
|
|
230
|
-
|
|
231
|
-
When the CLI runs a command, it calls `cmd.dispatch()` which:
|
|
232
|
-
|
|
233
|
-
1. Reads raw input from `container.argv` (or explicit args if called programmatically)
|
|
234
|
-
2. Validates args against `argsSchema` if present
|
|
235
|
-
3. Maps positional args if `positionals` is declared
|
|
236
|
-
4. Intercepts `--help` to show auto-generated help text
|
|
237
|
-
5. Calls `run(parsedOptions, context)` with the validated, typed options
|
|
238
|
-
|
|
239
|
-
You can also dispatch commands programmatically:
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
const cmd = container.command('seed')
|
|
243
|
-
await cmd.dispatch({ count: 20, table: 'users' }, 'headless')
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Conventions
|
|
247
|
-
|
|
248
|
-
- **File location**: `commands/<name>.ts` in the project root. Auto-discovered by the CLI.
|
|
249
|
-
- **Naming**: kebab-case filenames. `commands/build-site.ts` → `luca build-site`.
|
|
250
|
-
- **Use the container**: Never import `fs`, `path`, `child_process` directly. Use `container.feature('fs')`, `container.paths`, `container.feature('proc')`.
|
|
251
|
-
- **Exit codes**: Return nothing for success. Throw for errors — the CLI catches and reports them.
|
|
252
|
-
- **Help text**: Use `.describe()` on every schema field — it powers `luca <command> --help`.
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Using Clients
|
|
3
|
-
tags: [clients, rest, graphql, websocket, http, api, axios]
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Using Clients
|
|
7
|
-
|
|
8
|
-
Clients connect your application to external services. Luca provides built-in clients for REST APIs, GraphQL, and WebSocket connections.
|
|
9
|
-
|
|
10
|
-
## REST Client
|
|
11
|
-
|
|
12
|
-
The REST client wraps axios with Luca's helper patterns (state, events, introspection):
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
const api = container.client('rest', {
|
|
16
|
-
baseURL: 'https://api.example.com',
|
|
17
|
-
headers: {
|
|
18
|
-
Authorization: 'Bearer my-token',
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
await api.connect()
|
|
23
|
-
|
|
24
|
-
// Standard HTTP methods
|
|
25
|
-
const users = await api.get('/users')
|
|
26
|
-
const user = await api.get('/users/123')
|
|
27
|
-
const created = await api.post('/users', { name: 'Alice', email: 'alice@example.com' })
|
|
28
|
-
const updated = await api.put('/users/123', { name: 'Alice Updated' })
|
|
29
|
-
await api.delete('/users/123')
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### REST Client Events
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
api.on('failure', (error) => {
|
|
36
|
-
console.error('Request failed:', error.message)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// State changes track connection status
|
|
40
|
-
api.state.observe((type, key, value) => {
|
|
41
|
-
if (key === 'connected') {
|
|
42
|
-
console.log(`Client connected: ${value}`)
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## GraphQL Client
|
|
48
|
-
|
|
49
|
-
For GraphQL APIs, use the REST client's `post()` method to send queries and mutations:
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
const graph = container.client('rest', {
|
|
53
|
-
baseURL: 'https://api.example.com/graphql',
|
|
54
|
-
headers: { Authorization: 'Bearer my-token' },
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
await graph.connect()
|
|
58
|
-
|
|
59
|
-
// Send a query
|
|
60
|
-
const result = await graph.post('/', {
|
|
61
|
-
query: `
|
|
62
|
-
query GetUser($id: ID!) {
|
|
63
|
-
user(id: $id) {
|
|
64
|
-
name
|
|
65
|
-
email
|
|
66
|
-
posts { title }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
`,
|
|
70
|
-
variables: { id: '123' },
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// Send a mutation
|
|
74
|
-
const mutationResult = await graph.post('/', {
|
|
75
|
-
query: `
|
|
76
|
-
mutation CreatePost($input: PostInput!) {
|
|
77
|
-
createPost(input: $input) {
|
|
78
|
-
id
|
|
79
|
-
title
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
`,
|
|
83
|
-
variables: { input: { title: 'Hello World', body: '...' } },
|
|
84
|
-
})
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## WebSocket Client
|
|
88
|
-
|
|
89
|
-
The WebSocket client wraps a raw `WebSocket` connection:
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
const ws = container.client('websocket', {
|
|
93
|
-
baseURL: 'wss://realtime.example.com',
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
await ws.connect()
|
|
97
|
-
|
|
98
|
-
// Access the underlying WebSocket via ws.ws
|
|
99
|
-
ws.ws.onmessage = (event) => {
|
|
100
|
-
console.log('Received:', event.data)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
ws.ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }))
|
|
104
|
-
|
|
105
|
-
// Clean up
|
|
106
|
-
ws.ws.close()
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Discovering Clients
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
container.clients.available // ['rest', 'graph', 'websocket']
|
|
113
|
-
container.clients.describe('rest')
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Using Clients in Endpoints
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
// endpoints/proxy.ts
|
|
120
|
-
import { z } from 'zod'
|
|
121
|
-
import type { EndpointContext } from '@soederpop/luca'
|
|
122
|
-
|
|
123
|
-
export const path = '/api/external-data'
|
|
124
|
-
|
|
125
|
-
export const getSchema = z.object({
|
|
126
|
-
query: z.string().describe('Search query'),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
|
|
130
|
-
const api = ctx.container.client('rest', {
|
|
131
|
-
baseURL: 'https://external-api.com',
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
await api.connect()
|
|
135
|
-
const data = await api.get(`/search?q=${encodeURIComponent(params.query)}`)
|
|
136
|
-
|
|
137
|
-
return { results: data }
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Using Clients in Features
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
class WeatherService extends Feature<WeatherState, WeatherOptions> {
|
|
145
|
-
private api: any
|
|
146
|
-
|
|
147
|
-
async initialize() {
|
|
148
|
-
this.api = this.container.client('rest', {
|
|
149
|
-
baseURL: 'https://api.weather.com',
|
|
150
|
-
headers: { 'X-API-Key': this.options.apiKey },
|
|
151
|
-
})
|
|
152
|
-
await this.api.connect()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async getForecast(city: string) {
|
|
156
|
-
const data = await this.api.get(`/forecast/${encodeURIComponent(city)}`)
|
|
157
|
-
this.state.set('lastForecast', data)
|
|
158
|
-
this.emit('forecastFetched', data)
|
|
159
|
-
return data
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
```
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Creating Custom Features
|
|
3
|
-
tags: [features, custom, extend, zod, state, events, module-augmentation, helper]
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Creating Custom Features
|
|
7
|
-
|
|
8
|
-
You can create your own features to encapsulate domain logic, then register them so they're available through `container.feature('yourFeature')` with full type safety.
|
|
9
|
-
|
|
10
|
-
## Anatomy of a Feature
|
|
11
|
-
|
|
12
|
-
A feature has:
|
|
13
|
-
- **State** -- observable, defined by a Zod schema
|
|
14
|
-
- **Options** -- configuration passed at creation, defined by a Zod schema
|
|
15
|
-
- **Events** -- typed event bus
|
|
16
|
-
- **Methods** -- your domain logic
|
|
17
|
-
- **Access to the container** -- via `this.container`
|
|
18
|
-
|
|
19
|
-
## Basic Example
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { z } from 'zod'
|
|
23
|
-
import { Feature, features, FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
|
|
24
|
-
|
|
25
|
-
// Define state schema by extending the base FeatureStateSchema
|
|
26
|
-
export const CounterStateSchema = FeatureStateSchema.extend({
|
|
27
|
-
count: z.number().describe('Current count value'),
|
|
28
|
-
lastUpdated: z.string().optional().describe('ISO timestamp of last update'),
|
|
29
|
-
})
|
|
30
|
-
export type CounterState = z.infer<typeof CounterStateSchema>
|
|
31
|
-
|
|
32
|
-
// Define options schema by extending the base FeatureOptionsSchema
|
|
33
|
-
export const CounterOptionsSchema = FeatureOptionsSchema.extend({
|
|
34
|
-
initialCount: z.number().default(0).describe('Starting count value'),
|
|
35
|
-
step: z.number().default(1).describe('Increment step size'),
|
|
36
|
-
})
|
|
37
|
-
export type CounterOptions = z.infer<typeof CounterOptionsSchema>
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A simple counter feature that demonstrates the feature pattern.
|
|
41
|
-
* Tracks a count value with observable state and events.
|
|
42
|
-
*/
|
|
43
|
-
export class Counter extends Feature<CounterState, CounterOptions> {
|
|
44
|
-
static override stateSchema = CounterStateSchema
|
|
45
|
-
static override optionsSchema = CounterOptionsSchema
|
|
46
|
-
|
|
47
|
-
/** Called when the feature is created */
|
|
48
|
-
async initialize() {
|
|
49
|
-
this.state.set('count', this.options.initialCount ?? 0)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Increment the counter by the configured step */
|
|
53
|
-
increment() {
|
|
54
|
-
const current = this.state.get('count') || 0
|
|
55
|
-
const next = current + (this.options.step ?? 1)
|
|
56
|
-
this.state.set('count', next)
|
|
57
|
-
this.state.set('lastUpdated', new Date().toISOString())
|
|
58
|
-
this.emit('incremented', next)
|
|
59
|
-
return next
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Decrement the counter by the configured step */
|
|
63
|
-
decrement() {
|
|
64
|
-
const current = this.state.get('count') || 0
|
|
65
|
-
const next = current - (this.options.step ?? 1)
|
|
66
|
-
this.state.set('count', next)
|
|
67
|
-
this.state.set('lastUpdated', new Date().toISOString())
|
|
68
|
-
this.emit('decremented', next)
|
|
69
|
-
return next
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Reset the counter to its initial value */
|
|
73
|
-
reset() {
|
|
74
|
-
this.state.set('count', this.options.initialCount ?? 0)
|
|
75
|
-
this.emit('reset')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Get the current count */
|
|
79
|
-
get value(): number {
|
|
80
|
-
return this.state.get('count') || 0
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Register the feature
|
|
85
|
-
features.register('counter', Counter)
|
|
86
|
-
|
|
87
|
-
// Module augmentation for type safety
|
|
88
|
-
declare module '@soederpop/luca' {
|
|
89
|
-
interface AvailableFeatures {
|
|
90
|
-
counter: typeof Counter
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Using Your Feature
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import './features/counter' // Side-effect import to register
|
|
99
|
-
|
|
100
|
-
const counter = container.feature('counter', { initialCount: 10, step: 5 })
|
|
101
|
-
|
|
102
|
-
counter.on('incremented', (value) => {
|
|
103
|
-
console.log(`Count is now ${value}`)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
counter.increment() // 15
|
|
107
|
-
counter.increment() // 20
|
|
108
|
-
counter.value // 20
|
|
109
|
-
counter.reset() // Back to 10
|
|
110
|
-
|
|
111
|
-
// Observe state changes
|
|
112
|
-
counter.state.observe((type, key, value) => {
|
|
113
|
-
console.log(`${key} ${type}d:`, value)
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Enabling on the Container
|
|
118
|
-
|
|
119
|
-
If your feature should be a container-level singleton with a shortcut:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
export class Counter extends Feature<CounterState, CounterOptions> {
|
|
123
|
-
// This creates the container.counter shortcut when enabled
|
|
124
|
-
static override shortcut = 'features.counter' as const
|
|
125
|
-
// ...
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Enable it
|
|
129
|
-
container.feature('counter', { enable: true })
|
|
130
|
-
|
|
131
|
-
// Now accessible as:
|
|
132
|
-
container.counter.increment()
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Feature with Container Access
|
|
136
|
-
|
|
137
|
-
Features can access other features and the full container:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
export class Analytics extends Feature<AnalyticsState, AnalyticsOptions> {
|
|
141
|
-
/** Log an event, writing to disk cache for persistence */
|
|
142
|
-
async logEvent(name: string, data: Record<string, any>) {
|
|
143
|
-
const cache = this.container.feature('diskCache', { path: './.analytics' })
|
|
144
|
-
const timestamp = new Date().toISOString()
|
|
145
|
-
|
|
146
|
-
await cache.set(`event:${timestamp}`, { name, data, timestamp })
|
|
147
|
-
|
|
148
|
-
this.state.set('totalEvents', (this.state.get('totalEvents') || 0) + 1)
|
|
149
|
-
this.emit('eventLogged', { name, data })
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Get recent events from the cache */
|
|
153
|
-
async recentEvents(limit = 10) {
|
|
154
|
-
const fs = this.container.fs
|
|
155
|
-
// ... read from cache directory
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Documenting Your Feature
|
|
161
|
-
|
|
162
|
-
Document your classes, methods, and getters with JSDoc. This is important because Luca's introspection system extracts these docs and makes them available at runtime:
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
/**
|
|
166
|
-
* Manages user sessions with automatic expiration and renewal.
|
|
167
|
-
* Sessions are persisted to disk and can survive process restarts.
|
|
168
|
-
*/
|
|
169
|
-
export class SessionManager extends Feature<SessionState, SessionOptions> {
|
|
170
|
-
/**
|
|
171
|
-
* Create a new session for the given user.
|
|
172
|
-
* Returns a session token that can be used for authentication.
|
|
173
|
-
*/
|
|
174
|
-
async createSession(userId: string): Promise<string> {
|
|
175
|
-
// ...
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** The number of currently active sessions */
|
|
179
|
-
get activeCount(): number {
|
|
180
|
-
return this.state.get('sessions')?.length || 0
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Then anyone (human or AI) can discover your feature:
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
container.features.describe('sessionManager')
|
|
189
|
-
// Returns the full markdown documentation extracted from your JSDoc
|
|
190
|
-
|
|
191
|
-
// Quick discovery — list available methods and getters
|
|
192
|
-
const session = container.feature('sessionManager')
|
|
193
|
-
session.$methods // => ['createSession', ...]
|
|
194
|
-
session.$getters // => ['activeCount', ...]
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Best Practices
|
|
198
|
-
|
|
199
|
-
1. **Use Zod `.describe()` on schema fields** -- these descriptions appear in introspection and help documentation
|
|
200
|
-
2. **Emit events for significant actions** -- enables reactive patterns and decoupled observers
|
|
201
|
-
3. **Use state for observable values** -- don't hide important state in private variables if consumers need to watch it
|
|
202
|
-
4. **Access the container, not imports** -- prefer `this.container.feature('fs')` over importing fs directly, so the feature works in any container
|
|
203
|
-
5. **Document everything** -- JSDoc on the class, methods, and getters feeds the introspection system
|