@soederpop/luca 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -1
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +540 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +181 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +225 -2
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +33 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +20 -0
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
- package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- package/src/node/features/launcher-app-command-listener.ts +0 -389
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
// Auto-generated bootstrap content
|
|
2
|
+
// Generated at: 2026-03-19T00:28:07.695Z
|
|
3
|
+
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
|
|
4
|
+
//
|
|
5
|
+
// Do not edit manually. Run: luca build-bootstrap
|
|
6
|
+
|
|
7
|
+
export const bootstrapFiles: Record<string, string> = {
|
|
8
|
+
"SKILL": `---
|
|
9
|
+
name: Using the luca framework
|
|
10
|
+
description: Learn the luca container — discover what's available with luca describe, build new helpers with luca scaffold, and prototype with luca eval
|
|
11
|
+
---
|
|
12
|
+
# Luca: Learning the Container
|
|
13
|
+
|
|
14
|
+
The Luca framework \`@soederpop/luca\` ships a \`luca\` binary — a bun-based CLI for a dependency injection container. This project is based on it if this skill is present. The container auto-discovers modules in \`commands/\`, \`clients/\`, \`servers/\`, \`features/\`, and \`endpoints/\` folders.
|
|
15
|
+
|
|
16
|
+
There are three things to learn, in this order:
|
|
17
|
+
|
|
18
|
+
1. **Discover** what the container can do — \`luca describe\`
|
|
19
|
+
2. **Build** new helpers when your project needs them — \`luca scaffold\`
|
|
20
|
+
3. **Prototype** and debug with live code — \`luca eval\`
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Phase 1: Discover with \`luca describe\`
|
|
25
|
+
|
|
26
|
+
This is your primary tool. Before reading source files, searching for APIs, or writing any code — ask describe. It outputs full documentation for any part of the container: methods, options, events, state, examples.
|
|
27
|
+
|
|
28
|
+
### See what's available
|
|
29
|
+
|
|
30
|
+
\`\`\`shell
|
|
31
|
+
luca describe features # index of all available features
|
|
32
|
+
luca describe clients # index of all available clients
|
|
33
|
+
luca describe servers # index of all available servers
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
### Learn about specific helpers
|
|
37
|
+
|
|
38
|
+
\`\`\`shell
|
|
39
|
+
luca describe fs # full docs for the fs feature
|
|
40
|
+
luca describe git # full docs for git
|
|
41
|
+
luca describe rest # full docs for the rest client
|
|
42
|
+
luca describe express # full docs for the express server
|
|
43
|
+
luca describe git fs proc # multiple helpers in one shot
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
### Get targeted documentation
|
|
47
|
+
|
|
48
|
+
You can filter to only the sections you need:
|
|
49
|
+
|
|
50
|
+
\`\`\`shell
|
|
51
|
+
luca describe fs --methods # just the methods
|
|
52
|
+
luca describe git --events # just the events it emits
|
|
53
|
+
luca describe express --options # just the constructor options
|
|
54
|
+
luca describe fs git --examples # just examples for both
|
|
55
|
+
luca describe fs --usage --methods # combine sections
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
### Describe the container itself
|
|
59
|
+
|
|
60
|
+
\`\`\`shell
|
|
61
|
+
luca describe # overview of the container
|
|
62
|
+
luca describe self # same thing
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
### Get help on any command
|
|
66
|
+
|
|
67
|
+
\`\`\`shell
|
|
68
|
+
luca # list all available commands
|
|
69
|
+
luca describe --help # full flag reference for describe
|
|
70
|
+
luca help scaffold # help for any command
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
**Use \`luca describe\` liberally.** It is the fastest, safest way to understand what the container provides. Every feature, client, and server is self-describing — if you know a name, describe will tell you everything about it.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Phase 2: Build with \`luca scaffold\`
|
|
78
|
+
|
|
79
|
+
When your project needs a new helper, scaffold it. The \`scaffold\` command generates correct boilerplate — you fill in the logic.
|
|
80
|
+
|
|
81
|
+
### Learn how to build each type
|
|
82
|
+
|
|
83
|
+
Before creating anything, read the tutorial for that helper type:
|
|
84
|
+
|
|
85
|
+
\`\`\`shell
|
|
86
|
+
luca scaffold feature --tutorial # how features work, full guide
|
|
87
|
+
luca scaffold command --tutorial # how commands work
|
|
88
|
+
luca scaffold endpoint --tutorial # how endpoints work
|
|
89
|
+
luca scaffold client --tutorial # how clients work
|
|
90
|
+
luca scaffold server --tutorial # how servers work
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
These tutorials are the authoritative reference for each helper type. They cover imports, schemas, class structure, registration, conventions, and complete examples.
|
|
94
|
+
|
|
95
|
+
### Generate a helper
|
|
96
|
+
|
|
97
|
+
\`\`\`shell
|
|
98
|
+
luca scaffold <type> <name> --description "What it does"
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
The workflow after scaffolding:
|
|
102
|
+
|
|
103
|
+
\`\`\`shell
|
|
104
|
+
luca scaffold command sync-data --description "Pull data from staging"
|
|
105
|
+
# edit commands/sync-data.ts — add your logic
|
|
106
|
+
luca describe sync-data # verify it shows up and reads correctly
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
Every scaffolded helper is auto-discovered by the container at runtime.
|
|
110
|
+
|
|
111
|
+
### When to use each type
|
|
112
|
+
|
|
113
|
+
| You need to... | Scaffold a... | Example |
|
|
114
|
+
|----------------------------------------------------|----------------|----------------------------------------------------------------|
|
|
115
|
+
| Add a reusable local capability (caching, crypto) | **feature** | \`luca scaffold feature disk-cache --description "File-backed key-value cache"\` |
|
|
116
|
+
| Add a CLI task (build, deploy, generate) | **command** | \`luca scaffold command deploy --description "Deploy to production"\` |
|
|
117
|
+
| Talk to an external API or service | **client** | \`luca scaffold client github --description "GitHub API wrapper"\` |
|
|
118
|
+
| Accept incoming connections (HTTP, WS) | **server** | \`luca scaffold server mqtt --description "MQTT broker"\` |
|
|
119
|
+
| Add a REST route to \`luca serve\` | **endpoint** | \`luca scaffold endpoint users --description "User management API"\` |
|
|
120
|
+
|
|
121
|
+
### Scaffold options
|
|
122
|
+
|
|
123
|
+
\`\`\`shell
|
|
124
|
+
luca scaffold command deploy --description "..." # writes to commands/deploy.ts
|
|
125
|
+
luca scaffold endpoint users --print # print to stdout instead of writing
|
|
126
|
+
luca scaffold feature cache --output lib/cache.ts # override output path
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Phase 3: Prototype with \`luca eval\`
|
|
132
|
+
|
|
133
|
+
Once you know what's available (describe) and how to build things (scaffold), use \`luca eval\` to test ideas, verify behavior, and debug.
|
|
134
|
+
|
|
135
|
+
\`\`\`shell
|
|
136
|
+
luca eval "container.features.available"
|
|
137
|
+
luca eval "container.feature('proc').exec('ls')"
|
|
138
|
+
luca eval "container.feature('fs').readFile('package.json')"
|
|
139
|
+
\`\`\`
|
|
140
|
+
|
|
141
|
+
The eval command boots a full container with all helpers discovered and registered. Core features are available as top-level shortcuts:
|
|
142
|
+
|
|
143
|
+
\`\`\`shell
|
|
144
|
+
luca eval "fs.readFile('package.json')"
|
|
145
|
+
luca eval "git.branch"
|
|
146
|
+
luca eval "proc.exec('ls')"
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
**Reach for eval when you're stuck.** It gives you full control of the container at runtime — you can test method calls, inspect state, verify event behavior, and debug issues that are hard to reason about from docs alone.
|
|
150
|
+
|
|
151
|
+
**Use eval as a testing tool.** Before wiring up a full command handler or feature, test your logic in eval first. Want to verify how \`fs.moveAsync\` behaves, or whether a watcher event fires the way you expect? Run it in eval. This is the fastest way to validate container code without the overhead of building the full command around it.
|
|
152
|
+
|
|
153
|
+
\`\`\`shell
|
|
154
|
+
# Test file operations before building a command around them
|
|
155
|
+
luca eval "await fs.moveAsync('inbox/test.json', 'inbox/valid/test.json')"
|
|
156
|
+
|
|
157
|
+
# First: luca describe fileManager --events (to learn what events exist)
|
|
158
|
+
# Then test the behavior:
|
|
159
|
+
luca eval "const fm = container.feature('fileManager'); fm.on('file:change', (e) => console.log(e)); await fm.watch({ paths: ['inbox'] })"
|
|
160
|
+
\`\`\`
|
|
161
|
+
|
|
162
|
+
### The REPL
|
|
163
|
+
|
|
164
|
+
For interactive exploration, \`luca console\` opens a persistent REPL with the container in scope. Useful when you need to try multiple things in sequence.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Key Concepts
|
|
169
|
+
|
|
170
|
+
### The Container
|
|
171
|
+
|
|
172
|
+
The container is a singleton that holds everything your application needs. It organizes components into **registries**: features, clients, servers, commands, and endpoints. Use the factory functions to get instances:
|
|
173
|
+
|
|
174
|
+
\`\`\`js
|
|
175
|
+
const fs = container.feature('fs')
|
|
176
|
+
const rest = container.client('rest')
|
|
177
|
+
const server = container.server('express')
|
|
178
|
+
\`\`\`
|
|
179
|
+
|
|
180
|
+
### State
|
|
181
|
+
|
|
182
|
+
Every helper and the container itself have observable state:
|
|
183
|
+
|
|
184
|
+
\`\`\`js
|
|
185
|
+
const feature = container.feature('fs')
|
|
186
|
+
|
|
187
|
+
feature.state.current // snapshot of all state
|
|
188
|
+
feature.state.get('someKey') // single value
|
|
189
|
+
feature.state.set('key', 'value') // update
|
|
190
|
+
|
|
191
|
+
// Watch for changes
|
|
192
|
+
feature.state.observe((changeType, key, value) => {
|
|
193
|
+
// changeType: 'add' | 'update' | 'delete'
|
|
194
|
+
})
|
|
195
|
+
\`\`\`
|
|
196
|
+
|
|
197
|
+
The container has state too: \`container.state.current\`, \`container.state.observe()\`.
|
|
198
|
+
|
|
199
|
+
### Events
|
|
200
|
+
|
|
201
|
+
Every helper and the container are event emitters — \`on\`, \`once\`, \`emit\`, \`waitFor\` all work as expected. Use \`luca describe <name> --events\` to see what a helper emits.
|
|
202
|
+
|
|
203
|
+
### Utilities
|
|
204
|
+
|
|
205
|
+
The container provides common utilities at \`container.utils\` — no external imports needed:
|
|
206
|
+
|
|
207
|
+
- \`container.utils.uuid()\` — v4 UUID
|
|
208
|
+
- \`container.utils.hashObject(obj)\` — deterministic hash
|
|
209
|
+
- \`container.utils.stringUtils\` — camelCase, kebabCase, pluralize, etc.
|
|
210
|
+
- \`container.utils.lodash\` — groupBy, keyBy, pick, omit, debounce, etc.
|
|
211
|
+
- \`container.paths.resolve()\` / \`container.paths.join()\` — path operations
|
|
212
|
+
|
|
213
|
+
### Programmatic introspection
|
|
214
|
+
|
|
215
|
+
Everything \`luca describe\` outputs is also available at runtime in code:
|
|
216
|
+
|
|
217
|
+
\`\`\`js
|
|
218
|
+
container.features.describe('fs') // markdown docs (same as the CLI)
|
|
219
|
+
feature.introspect() // structured object: { methods, events, state, options }
|
|
220
|
+
container.inspectAsText() // full container overview as markdown
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
This is useful inside commands and scripts where you need introspection data programmatically.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Reference
|
|
228
|
+
|
|
229
|
+
See \`references/api-docs/\` for the full pre-generated API reference for every built-in feature, client, and server.
|
|
230
|
+
`,
|
|
231
|
+
"CLAUDE": `# Luca Project
|
|
232
|
+
|
|
233
|
+
This project uses the [Luca framework](https://github.com/soederpop/luca) — Lightweight Universal Conversational Architecture.
|
|
234
|
+
|
|
235
|
+
For a deep dive into the framework internals, see the [Luca GitHub repository](https://github.com/soederpop/luca).
|
|
236
|
+
|
|
237
|
+
## Runtime
|
|
238
|
+
|
|
239
|
+
The runtime is **bun**. Use \`bun run\` for scripts, \`bun test\` for tests.
|
|
240
|
+
|
|
241
|
+
## The \`luca\` CLI
|
|
242
|
+
|
|
243
|
+
The \`luca\` binary is available in the path. Key commands:
|
|
244
|
+
|
|
245
|
+
- \`luca\` — list available commands (built-in + project commands)
|
|
246
|
+
- \`luca eval "expression"\` — evaluate JS with the container in scope
|
|
247
|
+
- \`luca describe <name>\` — full docs for any feature, client, or server (e.g. \`luca describe fs\`)
|
|
248
|
+
- \`luca describe features\` — index of all available features (also: \`clients\`, \`servers\`)
|
|
249
|
+
- \`luca serve\` — start a local server using \`endpoints/\` folder
|
|
250
|
+
- \`luca run script.ts\` — run a script with the container
|
|
251
|
+
- \`luca scaffold <type> <name>\` — generate boilerplate for a new helper (run \`luca scaffold\` for full help)
|
|
252
|
+
|
|
253
|
+
## Container Rules
|
|
254
|
+
|
|
255
|
+
- **NEVER import from \`fs\`, \`path\`, or other Node builtins.** Use \`container.feature('fs')\` for file operations, \`container.paths\` for path operations.
|
|
256
|
+
- The container should provide everything you need. If something is missing, raise the concern rather than pulling in external dependencies.
|
|
257
|
+
- Use \`container.utils\` for common utilities (uuid, lodash helpers, string utils).
|
|
258
|
+
|
|
259
|
+
## Learning the Framework
|
|
260
|
+
|
|
261
|
+
1. **Discover** — Run \`luca describe features\`, \`luca describe clients\`, \`luca describe servers\` to see what's available. Then \`luca describe <name>\` for full docs on any helper. This is your first move, always. (See \`.claude/skills/luca-framework/SKILL.md\` for the full mental model.)
|
|
262
|
+
2. **Build** — Run \`luca scaffold <type> --tutorial\` before creating a new helper. It covers the full guide for that type.
|
|
263
|
+
3. **Prototype** — Use \`luca eval "expression"\` to test container code before wiring up full handlers. Reach for eval when you're stuck — it gives you full runtime access.
|
|
264
|
+
4. **Reference** — Browse \`.claude/skills/luca-framework/references/api-docs/\` for pre-generated API docs
|
|
265
|
+
|
|
266
|
+
## Project Structure
|
|
267
|
+
|
|
268
|
+
- \`commands/\` — custom CLI commands, run via \`luca <commandName>\` (auto-discovered)
|
|
269
|
+
- \`endpoints/\` — file-based HTTP routes, served via \`luca serve\` (auto-discovered)
|
|
270
|
+
- \`features/\` — custom container features, discovered via \`container.helpers.discoverAll()\` (auto-discovered)
|
|
271
|
+
- \`docs/\` — content documents managed by the \`contentDb\` feature (\`container.docs\`). See [contentbase](https://github.com/soederpop/contentbase) for the document model system.
|
|
272
|
+
- \`luca.cli.ts\` — optional project-level CLI customization (runs before any command)
|
|
273
|
+
|
|
274
|
+
## Command Arguments
|
|
275
|
+
|
|
276
|
+
Command handlers receive \`(options, context)\`. The \`options\` object contains:
|
|
277
|
+
- **Named flags** from \`argsSchema\`: \`--verbose\` → \`options.verbose\`
|
|
278
|
+
- **Positional args** mapped via \`positionals\` export: \`luca cmd ./src\` → \`options.target\`
|
|
279
|
+
- **Raw positionals** in \`options._\`: array where \`_[0]\` is the command name, \`_[1+]\` are positional args
|
|
280
|
+
|
|
281
|
+
To accept positional arguments, export a \`positionals\` array that maps them to named fields in \`argsSchema\`:
|
|
282
|
+
|
|
283
|
+
\`\`\`ts
|
|
284
|
+
export const positionals = ['target'] // luca myCmd ./src => options.target === './src'
|
|
285
|
+
export const argsSchema = z.object({
|
|
286
|
+
target: z.string().optional().describe('The target to operate on'),
|
|
287
|
+
verbose: z.boolean().default(false).describe('Enable verbose output'),
|
|
288
|
+
})
|
|
289
|
+
\`\`\`
|
|
290
|
+
|
|
291
|
+
## What's Available
|
|
292
|
+
|
|
293
|
+
The container provides more than you might expect. Before importing anything external, check here:
|
|
294
|
+
|
|
295
|
+
- **YAML** — \`container.feature('yaml')\` wraps \`js-yaml\`. Use \`.parse(str)\` and \`.stringify(obj)\`.
|
|
296
|
+
- **SQLite** — \`container.feature('sqlite')\` for databases. Parameterized queries, tagged templates.
|
|
297
|
+
- **REST client** — \`container.client('rest', { baseURL })\`. Methods (\`get\`, \`post\`, etc.) return **parsed JSON directly**, not \`{ data, status, headers }\`. On HTTP errors, the error is returned (not thrown).
|
|
298
|
+
- **Content DB** — \`container.docs\` (alias for \`container.feature('contentDb')\`) manages markdown documents with frontmatter. Query with \`docs.query(docs.models.MyModel).fetchAll()\`.
|
|
299
|
+
- **Grep** — \`container.feature('grep')\` has \`search()\` and \`codeAnnotations()\` for finding TODOs/FIXMEs/etc.
|
|
300
|
+
- **chalk** — available as \`container.feature('ui').colors\`, not via \`import('chalk')\`.
|
|
301
|
+
- **figlet** — available as \`container.feature('ui').asciiArt(text)\`.
|
|
302
|
+
- **uuid** — \`container.utils.uuid()\`
|
|
303
|
+
- **lodash** — \`container.utils.lodash\` (groupBy, keyBy, pick, omit, debounce, etc.)
|
|
304
|
+
- **string utils** — \`container.utils.stringUtils\` (camelCase, kebabCase, pluralize, etc.)
|
|
305
|
+
|
|
306
|
+
## Known Gotchas
|
|
307
|
+
|
|
308
|
+
- **For DELETE endpoint handlers, use \`export { del as delete }\`** — \`delete\` is a JS reserved word. Define your function with any name, then re-export it as \`delete\`.
|
|
309
|
+
- **Bun globals (\`Bun.spawn\`, \`Bun.serve\`) are unavailable** in command/endpoint handlers. Use Node's \`child_process\` for spawning processes, or use \`container.feature('proc').exec()\`.
|
|
310
|
+
- **\`ui.print.*\` writes to stdout** — if your command supports \`--json\`, gate UI output behind \`if (!options.json)\`.
|
|
311
|
+
- **VM contexts start empty** — when using \`container.feature('vm')\`, inject globals explicitly (\`console\`, \`Date\`, \`Promise\`, \`crypto\`, \`TextEncoder\`, \`setTimeout\`).
|
|
312
|
+
- **Long-running commands** (servers, watchers) need \`await new Promise(() => {})\` at the end with a \`process.on('SIGINT', ...)\` handler for cleanup.
|
|
313
|
+
- **Shared state between endpoints**: use \`ctx.request.app.locals\` to share data across endpoint files.
|
|
314
|
+
- **Database init**: use \`luca.cli.ts\` \`main()\` hook for table creation and seeding — it runs before any command or server starts.
|
|
315
|
+
|
|
316
|
+
## Extending the Container
|
|
317
|
+
|
|
318
|
+
Use \`luca scaffold\` to generate new helpers:
|
|
319
|
+
|
|
320
|
+
\`\`\`sh
|
|
321
|
+
luca scaffold command myTask --description "Automate something"
|
|
322
|
+
luca scaffold feature myCache --description "Custom caching layer"
|
|
323
|
+
luca scaffold endpoint users --description "User management API"
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
Run \`luca scaffold\` with no arguments for full usage and examples.
|
|
327
|
+
|
|
328
|
+
## Git Strategy
|
|
329
|
+
|
|
330
|
+
Roll on main. Commit with good messages that explain why, not just what.
|
|
331
|
+
`
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const bootstrapTemplates: Record<string, string> = {
|
|
335
|
+
"docs-models": `import { defineModel, z } from 'contentbase'
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Define your content models here. Each model maps to a folder prefix
|
|
339
|
+
* inside the docs/ directory. Documents in those folders follow the
|
|
340
|
+
* model's metadata schema.
|
|
341
|
+
*
|
|
342
|
+
* Access documents at runtime:
|
|
343
|
+
* const docs = container.docs // contentDb feature
|
|
344
|
+
* if (!docs.isLoaded) await docs.load()
|
|
345
|
+
* const notes = await docs.query(docs.models.Note).fetchAll()
|
|
346
|
+
*
|
|
347
|
+
* See https://github.com/soederpop/contentbase for full documentation.
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
export const Note = defineModel('Note', {
|
|
351
|
+
prefix: 'notes',
|
|
352
|
+
meta: z.object({
|
|
353
|
+
tags: z.array(z.string()).default([]),
|
|
354
|
+
status: z.enum(['draft', 'published']).default('draft'),
|
|
355
|
+
}),
|
|
356
|
+
})
|
|
357
|
+
`,
|
|
358
|
+
"luca-cli": `/**
|
|
359
|
+
* luca.cli.ts — Project-level CLI customization
|
|
360
|
+
*
|
|
361
|
+
* This file is automatically loaded by the \`luca\` CLI before any command runs.
|
|
362
|
+
* Use it to:
|
|
363
|
+
*
|
|
364
|
+
* - Discover project-level helpers (features, commands, endpoints)
|
|
365
|
+
* - Register custom context variables accessible in \`luca eval\`
|
|
366
|
+
* - Set up project-specific container configuration
|
|
367
|
+
*
|
|
368
|
+
* Exports:
|
|
369
|
+
* main(container) — called at CLI startup, before command execution
|
|
370
|
+
* onStart(container) — called when the container's 'started' event fires
|
|
371
|
+
*
|
|
372
|
+
* Example:
|
|
373
|
+
* export async function main(container: any) {
|
|
374
|
+
* await container.helpers.discoverAll()
|
|
375
|
+
* container.addContext('myFeature', container.feature('myFeature'))
|
|
376
|
+
* }
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
export async function main(container: any) {
|
|
380
|
+
// Discover project-level helpers (commands/, features/, endpoints/)
|
|
381
|
+
await container.helpers.discoverAll()
|
|
382
|
+
}
|
|
383
|
+
`,
|
|
384
|
+
"docs-readme": `# Docs
|
|
385
|
+
|
|
386
|
+
This folder contains structured content documents managed by the [contentbase](https://github.com/soederpop/contentbase) system.
|
|
387
|
+
|
|
388
|
+
## How it works
|
|
389
|
+
|
|
390
|
+
Documents are markdown files with YAML frontmatter. Each document belongs to a **model** defined in \`docs/models.ts\`. Models specify:
|
|
391
|
+
- A **prefix** (subfolder name, e.g. \`notes/\`)
|
|
392
|
+
- A **metadata schema** (validated frontmatter fields)
|
|
393
|
+
|
|
394
|
+
## Accessing documents at runtime
|
|
395
|
+
|
|
396
|
+
The \`contentDb\` feature (aliased to \`container.docs\`) loads and queries documents:
|
|
397
|
+
|
|
398
|
+
\`\`\`typescript
|
|
399
|
+
const docs = container.docs
|
|
400
|
+
if (!docs.isLoaded) await docs.load()
|
|
401
|
+
|
|
402
|
+
// Query all notes
|
|
403
|
+
const notes = await docs.query(docs.models.Note).fetchAll()
|
|
404
|
+
|
|
405
|
+
// Get a specific document
|
|
406
|
+
const doc = docs.collection('notes').document('my-note')
|
|
407
|
+
\`\`\`
|
|
408
|
+
|
|
409
|
+
## Creating a new document
|
|
410
|
+
|
|
411
|
+
Add a markdown file in the appropriate subfolder:
|
|
412
|
+
|
|
413
|
+
\`\`\`markdown
|
|
414
|
+
---
|
|
415
|
+
title: My First Note
|
|
416
|
+
tags: [example]
|
|
417
|
+
status: draft
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
Content goes here.
|
|
421
|
+
\`\`\`
|
|
422
|
+
|
|
423
|
+
## Learn more
|
|
424
|
+
|
|
425
|
+
- [Contentbase GitHub](https://github.com/soederpop/contentbase) — full documentation and API reference
|
|
426
|
+
- \`luca describe contentDb\` — runtime docs for the contentDb feature
|
|
427
|
+
`,
|
|
428
|
+
"example-feature": `import { z } from 'zod'
|
|
429
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
|
|
430
|
+
import { Feature } from '@soederpop/luca'
|
|
431
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
432
|
+
|
|
433
|
+
declare module '@soederpop/luca' {
|
|
434
|
+
interface AvailableFeatures {
|
|
435
|
+
example: typeof Example
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const ExampleStateSchema = FeatureStateSchema.extend({
|
|
440
|
+
greetCount: z.number().default(0).describe('Number of times greet() has been called'),
|
|
441
|
+
})
|
|
442
|
+
type ExampleState = z.infer<typeof ExampleStateSchema>
|
|
443
|
+
|
|
444
|
+
const ExampleOptionsSchema = FeatureOptionsSchema.extend({})
|
|
445
|
+
type ExampleOptions = z.infer<typeof ExampleOptionsSchema>
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* An example feature demonstrating the luca feature pattern.
|
|
449
|
+
*
|
|
450
|
+
* Discovered automatically by \`container.helpers.discoverAll()\`
|
|
451
|
+
* and available as \`container.feature('example')\`.
|
|
452
|
+
*
|
|
453
|
+
* To learn more: \`luca scaffold feature --tutorial\`
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* \`\`\`typescript
|
|
457
|
+
* const example = container.feature('example')
|
|
458
|
+
* example.greet('Luca') // => "Hello, Luca! (greeting #1)"
|
|
459
|
+
* \`\`\`
|
|
460
|
+
*/
|
|
461
|
+
export class Example extends Feature<ExampleState, ExampleOptions> {
|
|
462
|
+
static override shortcut = 'features.example' as const
|
|
463
|
+
static override stateSchema = ExampleStateSchema
|
|
464
|
+
static override optionsSchema = ExampleOptionsSchema
|
|
465
|
+
static override description = 'An example feature demonstrating the luca feature pattern'
|
|
466
|
+
static { Feature.register(this, 'example') }
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* A simple method to show the feature works.
|
|
470
|
+
* @param name - Name to greet
|
|
471
|
+
* @returns Greeting string
|
|
472
|
+
*/
|
|
473
|
+
greet(name = 'World') {
|
|
474
|
+
const count = (this.state.get('greetCount') || 0) + 1
|
|
475
|
+
this.state.set('greetCount', count)
|
|
476
|
+
return \`Hello, \${name}! (greeting #\${count})\`
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export default Example
|
|
481
|
+
`,
|
|
482
|
+
"about-command": `/**
|
|
483
|
+
* about — Display project information and discovered helpers.
|
|
484
|
+
* Run with: luca about
|
|
485
|
+
*
|
|
486
|
+
* Positional words after the command name are available as options._
|
|
487
|
+
* For example: \`luca about commands\` → options._[1] === 'commands'
|
|
488
|
+
*/
|
|
489
|
+
import { z } from 'zod'
|
|
490
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
491
|
+
|
|
492
|
+
export const description = 'Display project information and discovered helpers'
|
|
493
|
+
|
|
494
|
+
export const argsSchema = z.object({})
|
|
495
|
+
|
|
496
|
+
export default async function about(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
497
|
+
const { container } = context
|
|
498
|
+
const ui = container.feature('ui')
|
|
499
|
+
|
|
500
|
+
// Discover all project-level helpers (commands, features, endpoints, etc.)
|
|
501
|
+
const discovered = await container.helpers.discoverAll()
|
|
502
|
+
|
|
503
|
+
const projectName = container.paths.resolve('.').split('/').pop() || 'project'
|
|
504
|
+
|
|
505
|
+
ui.print.cyan(\`\\n \${projectName}\\n\`)
|
|
506
|
+
ui.print(' Powered by luca — Lightweight Universal Conversational Architecture\\n')
|
|
507
|
+
|
|
508
|
+
const types = ['features', 'clients', 'servers', 'commands', 'endpoints']
|
|
509
|
+
|
|
510
|
+
for (const type of types) {
|
|
511
|
+
const names = discovered[type] || []
|
|
512
|
+
if (names.length > 0) {
|
|
513
|
+
ui.print.green(\` \${type} (\${names.length})\`)
|
|
514
|
+
for (const name of names) {
|
|
515
|
+
ui.print(\` • \${name}\`)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const totalBuiltIn = types.reduce((sum: number, t: string) => sum + (container[t]?.available?.length || 0), 0)
|
|
521
|
+
ui.print.dim(\`\\n \${totalBuiltIn} built-in helpers available. Run \\\`luca describe\\\` for details.\\n\`)
|
|
522
|
+
}
|
|
523
|
+
`,
|
|
524
|
+
"health-endpoint": `/**
|
|
525
|
+
* Health check endpoint.
|
|
526
|
+
* Accessible at GET /api/health when you run \`luca serve\`.
|
|
527
|
+
*/
|
|
528
|
+
export const path = '/api/health'
|
|
529
|
+
export const description = 'Health check endpoint'
|
|
530
|
+
export const tags = ['health']
|
|
531
|
+
|
|
532
|
+
export async function get(_params: any, ctx: any) {
|
|
533
|
+
return {
|
|
534
|
+
status: 'ok',
|
|
535
|
+
timestamp: Date.now(),
|
|
536
|
+
uptime: process.uptime(),
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
`
|
|
540
|
+
}
|
package/src/cli/cli.ts
CHANGED
|
@@ -5,30 +5,74 @@ import { homedir } from 'os'
|
|
|
5
5
|
import { join } from 'path'
|
|
6
6
|
|
|
7
7
|
async function main() {
|
|
8
|
+
const profile = process.env.LUCA_PROFILE === '1'
|
|
9
|
+
const t = (label?: string) => {
|
|
10
|
+
if (!profile) return () => {}
|
|
11
|
+
const start = performance.now()
|
|
12
|
+
return (suffix?: string) => {
|
|
13
|
+
const ms = (performance.now() - start).toFixed(1)
|
|
14
|
+
console.error(`[profile] ${label}${suffix ? ` ${suffix}` : ''}: ${ms}ms`)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tTotal = t('total boot')
|
|
19
|
+
|
|
20
|
+
// LUCA_COMMAND_DISCOVERY: "disable" skips all, "no-local" skips project, "no-home" skips user
|
|
21
|
+
const discovery = process.env.LUCA_COMMAND_DISCOVERY || ''
|
|
22
|
+
|
|
23
|
+
// Snapshot built-in commands BEFORE loadCliModule — luca.cli.ts may call
|
|
24
|
+
// helpers.discoverAll() which registers project commands early
|
|
25
|
+
const builtinCommands = new Set(container.commands.available as string[])
|
|
26
|
+
|
|
8
27
|
// Load project-level CLI module (luca.cli.ts) for container customization
|
|
28
|
+
let done = t('loadCliModule')
|
|
9
29
|
await loadCliModule()
|
|
30
|
+
done()
|
|
31
|
+
|
|
10
32
|
// Discover project-local commands (commands/ or src/commands/)
|
|
11
|
-
|
|
33
|
+
done = t('discoverProjectCommands')
|
|
34
|
+
if (discovery !== 'disable' && discovery !== 'no-local') {
|
|
35
|
+
await discoverProjectCommands()
|
|
36
|
+
}
|
|
37
|
+
done()
|
|
38
|
+
const afterProject = new Set(container.commands.available as string[])
|
|
39
|
+
const projectCommands = new Set([...afterProject].filter((n) => !builtinCommands.has(n)))
|
|
40
|
+
|
|
12
41
|
// Discover user-level commands (~/.luca/commands/)
|
|
13
|
-
|
|
42
|
+
done = t('discoverUserCommands')
|
|
43
|
+
if (discovery !== 'disable' && discovery !== 'no-home') {
|
|
44
|
+
await discoverUserCommands()
|
|
45
|
+
}
|
|
46
|
+
done()
|
|
47
|
+
const afterUser = new Set(container.commands.available as string[])
|
|
48
|
+
const userCommands = new Set([...afterUser].filter((n) => !builtinCommands.has(n) && !projectCommands.has(n)))
|
|
49
|
+
|
|
50
|
+
// Store command sources for help display
|
|
51
|
+
;(container as any)._commandSources = { builtinCommands, projectCommands, userCommands }
|
|
52
|
+
|
|
14
53
|
// Load generated introspection data if present
|
|
54
|
+
done = t('loadProjectIntrospection')
|
|
15
55
|
await loadProjectIntrospection()
|
|
56
|
+
done()
|
|
16
57
|
|
|
17
58
|
const commandName = container.argv._[0] as string
|
|
18
59
|
|
|
60
|
+
done = t('dispatch')
|
|
19
61
|
if (commandName && container.commands.has(commandName)) {
|
|
20
62
|
const cmd = container.command(commandName as any)
|
|
21
|
-
await cmd.
|
|
63
|
+
await cmd.dispatch()
|
|
22
64
|
} else if (commandName) {
|
|
23
65
|
// not a known command — treat as implicit `run`
|
|
24
66
|
container.argv._.splice(0, 0, 'run')
|
|
25
67
|
const cmd = container.command('run' as any)
|
|
26
|
-
await cmd.
|
|
68
|
+
await cmd.dispatch()
|
|
27
69
|
} else {
|
|
28
70
|
container.argv._.splice(0, 0, 'help')
|
|
29
71
|
const cmd = container.command('help' as any)
|
|
30
|
-
await cmd.
|
|
72
|
+
await cmd.dispatch()
|
|
31
73
|
}
|
|
74
|
+
done()
|
|
75
|
+
tTotal()
|
|
32
76
|
}
|
|
33
77
|
|
|
34
78
|
|
|
@@ -36,28 +80,26 @@ async function loadCliModule() {
|
|
|
36
80
|
const modulePath = container.paths.resolve('luca.cli.ts')
|
|
37
81
|
if (!container.fs.exists(modulePath)) return
|
|
38
82
|
|
|
39
|
-
|
|
40
|
-
|
|
83
|
+
// Use the helpers feature to load the module — it handles the native import
|
|
84
|
+
// vs VM decision using the same useNativeImport check as discovery
|
|
85
|
+
const helpers = container.feature('helpers') as any
|
|
86
|
+
const exports = await helpers.loadModuleExports(modulePath)
|
|
41
87
|
|
|
42
|
-
if (typeof exports
|
|
88
|
+
if (typeof exports?.main === 'function') {
|
|
43
89
|
await exports.main(container)
|
|
44
90
|
}
|
|
45
91
|
|
|
46
|
-
if (typeof exports
|
|
92
|
+
if (typeof exports?.onStart === 'function') {
|
|
47
93
|
container.once('started', () => exports.onStart(container))
|
|
48
94
|
}
|
|
49
95
|
}
|
|
50
96
|
|
|
51
97
|
async function discoverProjectCommands() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await container.commands.discover({ directory: dir })
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
}
|
|
98
|
+
// Always route through the helpers feature — it handles native import vs VM
|
|
99
|
+
// internally, and deduplicates concurrent/repeated discovery via promise caching.
|
|
100
|
+
// If luca.cli.ts already called helpers.discoverAll(), this resolves instantly.
|
|
101
|
+
const helpers = container.feature('helpers') as any
|
|
102
|
+
await helpers.discover('commands')
|
|
61
103
|
}
|
|
62
104
|
|
|
63
105
|
async function loadProjectIntrospection() {
|
|
@@ -81,11 +123,12 @@ async function loadProjectIntrospection() {
|
|
|
81
123
|
}
|
|
82
124
|
|
|
83
125
|
async function discoverUserCommands() {
|
|
84
|
-
const { fs } = container
|
|
85
126
|
const dir = join(homedir(), '.luca', 'commands')
|
|
86
127
|
|
|
87
|
-
if (fs.exists(dir)) {
|
|
88
|
-
|
|
128
|
+
if (container.fs.exists(dir)) {
|
|
129
|
+
// Route through helpers for consistent dedup and VM/native handling
|
|
130
|
+
const helpers = container.feature('helpers') as any
|
|
131
|
+
await helpers.discover('commands', { directory: dir })
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
134
|
|