@soederpop/luca 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -1
- package/RUNME.md +56 -0
- 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/bootstrap/templates/runme.md +54 -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 +595 -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 +185 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +341 -4
- 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 +75 -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 +22 -2
- 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/vm-context.test.ts +146 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- 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
package/src/commands/describe.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod'
|
|
|
2
2
|
import { commands } from '../command.js'
|
|
3
3
|
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
4
4
|
import type { ContainerContext } from '../container.js'
|
|
5
|
-
import type { IntrospectionSection } from '../introspection/index.js'
|
|
5
|
+
import type { IntrospectionSection, MethodIntrospection, GetterIntrospection } from '../introspection/index.js'
|
|
6
6
|
|
|
7
7
|
declare module '../command.js' {
|
|
8
8
|
interface AvailableCommands {
|
|
@@ -10,7 +10,7 @@ declare module '../command.js' {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const REGISTRY_NAMES = ['features', 'clients', 'servers', 'commands', 'endpoints'] as const
|
|
13
|
+
const REGISTRY_NAMES = ['features', 'clients', 'servers', 'commands', 'endpoints', 'selectors'] as const
|
|
14
14
|
type RegistryName = (typeof REGISTRY_NAMES)[number]
|
|
15
15
|
|
|
16
16
|
/** Maps flag names to the section they represent. 'description' is handled specially. */
|
|
@@ -67,6 +67,7 @@ type ResolvedTarget =
|
|
|
67
67
|
| { kind: 'container' }
|
|
68
68
|
| { kind: 'registry'; name: RegistryName }
|
|
69
69
|
| { kind: 'helper'; registry: RegistryName; id: string }
|
|
70
|
+
| { kind: 'member'; registry: RegistryName; id: string; member: string; memberType: 'method' | 'getter' }
|
|
70
71
|
|
|
71
72
|
class DescribeError extends Error {
|
|
72
73
|
constructor(message: string) {
|
|
@@ -75,6 +76,24 @@ class DescribeError extends Error {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Extract a short summary from a potentially long description string.
|
|
81
|
+
* Takes text up to the first markdown heading, bullet list, or code block,
|
|
82
|
+
* capped at ~300 chars on a sentence boundary.
|
|
83
|
+
*/
|
|
84
|
+
function extractSummary(description: string): string {
|
|
85
|
+
// Strip from the first markdown heading/bullet/code block onward
|
|
86
|
+
const cut = description.search(/\s\*\*[A-Z][\w\s]+:\*\*|```|^\s*[-*]\s/m)
|
|
87
|
+
const text = cut > 0 ? description.slice(0, cut).trim() : description
|
|
88
|
+
|
|
89
|
+
if (text.length <= 300) return text
|
|
90
|
+
|
|
91
|
+
// Truncate on sentence boundary
|
|
92
|
+
const sentenceEnd = text.lastIndexOf('. ', 300)
|
|
93
|
+
if (sentenceEnd > 100) return text.slice(0, sentenceEnd + 1)
|
|
94
|
+
return text.slice(0, 300).trim() + '...'
|
|
95
|
+
}
|
|
96
|
+
|
|
78
97
|
/**
|
|
79
98
|
* Normalize an identifier to a comparable form by stripping file extensions,
|
|
80
99
|
* converting kebab-case and snake_case to lowercase-no-separators.
|
|
@@ -99,9 +118,44 @@ function fuzzyFind(registry: any, input: string): string | undefined {
|
|
|
99
118
|
return (registry.available as string[]).find((id: string) => normalize(id) === norm)
|
|
100
119
|
}
|
|
101
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Try to resolve "helperName.memberName" by searching all registries for the helper,
|
|
123
|
+
* then checking if memberName is a method or getter on it.
|
|
124
|
+
* Returns a 'member' target or null if no match.
|
|
125
|
+
*/
|
|
126
|
+
function resolveHelperMember(helperName: string, memberName: string, container: any): ResolvedTarget | null {
|
|
127
|
+
for (const registryName of REGISTRY_NAMES) {
|
|
128
|
+
const reg = container[registryName]
|
|
129
|
+
if (!reg) continue
|
|
130
|
+
const found = fuzzyFind(reg, helperName)
|
|
131
|
+
if (!found) continue
|
|
132
|
+
|
|
133
|
+
const Ctor = reg.lookup(found)
|
|
134
|
+
const introspection = Ctor.introspect?.()
|
|
135
|
+
if (!introspection) continue
|
|
136
|
+
|
|
137
|
+
if (introspection.methods?.[memberName]) {
|
|
138
|
+
return { kind: 'member', registry: registryName, id: found, member: memberName, memberType: 'method' }
|
|
139
|
+
}
|
|
140
|
+
if (introspection.getters?.[memberName]) {
|
|
141
|
+
return { kind: 'member', registry: registryName, id: found, member: memberName, memberType: 'getter' }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// If we found the helper but not the member, give a helpful error
|
|
145
|
+
const allMembers = [
|
|
146
|
+
...Object.keys(introspection.methods || {}).map((m: string) => m + '()'),
|
|
147
|
+
...Object.keys(introspection.getters || {}),
|
|
148
|
+
].sort()
|
|
149
|
+
throw new DescribeError(
|
|
150
|
+
`"${memberName}" is not a known method or getter on ${found}.\n\nAvailable members:\n ${allMembers.join(', ')}`
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
102
156
|
/**
|
|
103
157
|
* Parse a single target string into a resolved target.
|
|
104
|
-
* Accepts: "container", "features", "features.fs", "fs", etc.
|
|
158
|
+
* Accepts: "container", "features", "features.fs", "fs", "ui.banner", etc.
|
|
105
159
|
*/
|
|
106
160
|
function resolveTarget(target: string, container: any): ResolvedTarget {
|
|
107
161
|
const lower = target.toLowerCase()
|
|
@@ -135,6 +189,12 @@ function resolveTarget(target: string, container: any): ResolvedTarget {
|
|
|
135
189
|
}
|
|
136
190
|
return { kind: 'helper', registry, id: resolved }
|
|
137
191
|
}
|
|
192
|
+
|
|
193
|
+
// Not a registry prefix — try "helper.member" (e.g. "ui.banner", "fs.readFile")
|
|
194
|
+
const helperName = prefix!
|
|
195
|
+
const memberName = rest.join('.')
|
|
196
|
+
const memberResult = resolveHelperMember(helperName, memberName, container)
|
|
197
|
+
if (memberResult) return memberResult
|
|
138
198
|
}
|
|
139
199
|
|
|
140
200
|
// Unqualified name: search all registries (fuzzy)
|
|
@@ -319,6 +379,77 @@ function getContainerData(container: any, sections: (IntrospectionSection | 'des
|
|
|
319
379
|
}
|
|
320
380
|
}
|
|
321
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Walk the prototype chain of a base class to collect shared methods and getters.
|
|
384
|
+
* These are the methods/getters inherited by all helpers in a registry.
|
|
385
|
+
*/
|
|
386
|
+
function collectSharedMembers(baseClass: any): { methods: string[]; getters: string[] } {
|
|
387
|
+
const methods: string[] = []
|
|
388
|
+
const getters: string[] = []
|
|
389
|
+
|
|
390
|
+
let proto = baseClass?.prototype
|
|
391
|
+
while (proto && proto.constructor.name !== 'Object') {
|
|
392
|
+
for (const k of Object.getOwnPropertyNames(proto)) {
|
|
393
|
+
if (k === 'constructor' || k.startsWith('_')) continue
|
|
394
|
+
const desc = Object.getOwnPropertyDescriptor(proto, k)
|
|
395
|
+
if (!desc) continue
|
|
396
|
+
if (desc.get && !getters.includes(k)) getters.push(k)
|
|
397
|
+
else if (typeof desc.value === 'function' && !methods.includes(k)) methods.push(k)
|
|
398
|
+
}
|
|
399
|
+
proto = Object.getPrototypeOf(proto)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return { methods: methods.sort(), getters: getters.sort() }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Find the intermediate parent class between a helper constructor and the registry's
|
|
407
|
+
* base class (e.g. RestClient between ElevenLabsClient and Client).
|
|
408
|
+
* Returns the intermediate class constructor, or null if the helper extends the base directly.
|
|
409
|
+
*/
|
|
410
|
+
function findIntermediateParent(Ctor: any, baseClass: any): { name: string; methods: string[]; getters: string[] } | null {
|
|
411
|
+
if (!baseClass) return null
|
|
412
|
+
|
|
413
|
+
// Walk up from Ctor's parent to find the chain
|
|
414
|
+
let parent = Object.getPrototypeOf(Ctor)
|
|
415
|
+
if (!parent || parent === baseClass) return null
|
|
416
|
+
|
|
417
|
+
// Check if the parent itself is a direct child of baseClass (i.e., 2nd level)
|
|
418
|
+
// We want to find the class between Ctor and baseClass
|
|
419
|
+
const chain: any[] = []
|
|
420
|
+
let current = parent
|
|
421
|
+
while (current && current !== baseClass && current !== Function.prototype) {
|
|
422
|
+
chain.push(current)
|
|
423
|
+
current = Object.getPrototypeOf(current)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// If the chain is empty or parent IS the baseClass, no intermediate
|
|
427
|
+
if (chain.length === 0 || current !== baseClass) return null
|
|
428
|
+
|
|
429
|
+
// The first entry in the chain is the direct parent of Ctor — that's our intermediate
|
|
430
|
+
const intermediate = chain[0]
|
|
431
|
+
const methods: string[] = []
|
|
432
|
+
const getters: string[] = []
|
|
433
|
+
|
|
434
|
+
// Collect only the methods/getters defined directly on the intermediate class
|
|
435
|
+
const proto = intermediate?.prototype
|
|
436
|
+
if (proto) {
|
|
437
|
+
for (const k of Object.getOwnPropertyNames(proto)) {
|
|
438
|
+
if (k === 'constructor' || k.startsWith('_')) continue
|
|
439
|
+
const desc = Object.getOwnPropertyDescriptor(proto, k)
|
|
440
|
+
if (!desc) continue
|
|
441
|
+
if (desc.get && !getters.includes(k)) getters.push(k)
|
|
442
|
+
else if (typeof desc.value === 'function' && !methods.includes(k)) methods.push(k)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
name: intermediate.name,
|
|
448
|
+
methods: methods.sort(),
|
|
449
|
+
getters: getters.sort(),
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
322
453
|
function getRegistryData(container: any, registryName: RegistryName, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
323
454
|
const registry = container[registryName]
|
|
324
455
|
const available: string[] = registry.available
|
|
@@ -327,6 +458,83 @@ function getRegistryData(container: any, registryName: RegistryName, sections: (
|
|
|
327
458
|
return { json: {}, text: `No ${registryName} are registered.` }
|
|
328
459
|
}
|
|
329
460
|
|
|
461
|
+
// When no section filters are specified, render a concise index (like describeAll)
|
|
462
|
+
// rather than full introspection for every single helper
|
|
463
|
+
if (sections.length === 0) {
|
|
464
|
+
const h = '#'.repeat(headingDepth)
|
|
465
|
+
const hSub = '#'.repeat(headingDepth + 1)
|
|
466
|
+
const jsonResult: Record<string, any> = {}
|
|
467
|
+
const textParts: string[] = [`${h} Available ${registryName} (${available.length})\n`]
|
|
468
|
+
|
|
469
|
+
// Show shared methods/getters from the base class at the top
|
|
470
|
+
const baseClass = registry.baseClass
|
|
471
|
+
if (baseClass) {
|
|
472
|
+
const shared = collectSharedMembers(baseClass)
|
|
473
|
+
const label = registryName === 'features' ? 'Feature'
|
|
474
|
+
: registryName === 'clients' ? 'Client'
|
|
475
|
+
: registryName === 'servers' ? 'Server'
|
|
476
|
+
: registryName[0]!.toUpperCase() + registryName.slice(1).replace(/s$/, '')
|
|
477
|
+
|
|
478
|
+
if (shared.getters.length) {
|
|
479
|
+
textParts.push(`**Shared ${label} Getters:** ${shared.getters.join(', ')}\n`)
|
|
480
|
+
}
|
|
481
|
+
if (shared.methods.length) {
|
|
482
|
+
textParts.push(`**Shared ${label} Methods:** ${shared.methods.map(m => m + '()').join(', ')}\n`)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
jsonResult._shared = { methods: shared.methods, getters: shared.getters }
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Sort: core framework classes (direct children of baseClass) first, then the rest
|
|
489
|
+
const sorted = [...available].sort((a, b) => {
|
|
490
|
+
const aCtor = registry.lookup(a)
|
|
491
|
+
const bCtor = registry.lookup(b)
|
|
492
|
+
const aIsDirect = !findIntermediateParent(aCtor, baseClass)
|
|
493
|
+
const bIsDirect = !findIntermediateParent(bCtor, baseClass)
|
|
494
|
+
if (aIsDirect && !bIsDirect) return -1
|
|
495
|
+
if (!aIsDirect && bIsDirect) return 1
|
|
496
|
+
return 0
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
for (const id of sorted) {
|
|
500
|
+
const Ctor = registry.lookup(id)
|
|
501
|
+
const introspection = Ctor.introspect?.()
|
|
502
|
+
const description = introspection?.description || Ctor.description || 'No description provided'
|
|
503
|
+
// Take only the first 1-2 sentences as the summary
|
|
504
|
+
const summary = extractSummary(description)
|
|
505
|
+
|
|
506
|
+
const featureGetters = Object.keys(introspection?.getters || {}).sort()
|
|
507
|
+
const featureMethods = Object.keys(introspection?.methods || {}).sort()
|
|
508
|
+
|
|
509
|
+
// Detect intermediate parent class (e.g. RestClient between ElevenLabsClient and Client)
|
|
510
|
+
const intermediate = findIntermediateParent(Ctor, baseClass)
|
|
511
|
+
|
|
512
|
+
const entryJson: Record<string, any> = { description: summary, methods: featureMethods, getters: featureGetters }
|
|
513
|
+
if (intermediate) {
|
|
514
|
+
entryJson.extends = intermediate.name
|
|
515
|
+
entryJson.inheritedMethods = intermediate.methods
|
|
516
|
+
entryJson.inheritedGetters = intermediate.getters
|
|
517
|
+
}
|
|
518
|
+
jsonResult[id] = entryJson
|
|
519
|
+
|
|
520
|
+
const extendsLine = intermediate ? `\n> extends ${intermediate.name}\n` : ''
|
|
521
|
+
|
|
522
|
+
const memberLines: string[] = []
|
|
523
|
+
if (featureGetters.length) memberLines.push(` getters: ${featureGetters.join(', ')}`)
|
|
524
|
+
if (featureMethods.length) memberLines.push(` methods: ${featureMethods.map(m => m + '()').join(', ')}`)
|
|
525
|
+
if (intermediate) {
|
|
526
|
+
if (intermediate.getters.length) memberLines.push(` inherited getters: ${intermediate.getters.join(', ')}`)
|
|
527
|
+
if (intermediate.methods.length) memberLines.push(` inherited methods: ${intermediate.methods.map(m => m + '()').join(', ')}`)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const memberBlock = memberLines.length ? '\n' + memberLines.join('\n') + '\n' : ''
|
|
531
|
+
textParts.push(`${hSub} ${id}${extendsLine}\n${summary}\n${memberBlock}`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return { json: jsonResult, text: textParts.join('\n') }
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// When specific sections are requested, render full detail for each helper
|
|
330
538
|
const jsonResult: Record<string, any> = {}
|
|
331
539
|
const textParts: string[] = []
|
|
332
540
|
for (const id of available) {
|
|
@@ -338,13 +546,140 @@ function getRegistryData(container: any, registryName: RegistryName, sections: (
|
|
|
338
546
|
return { json: jsonResult, text: textParts.join('\n\n---\n\n') }
|
|
339
547
|
}
|
|
340
548
|
|
|
549
|
+
/** Known top-level helper base class names — anything above these is "shared" */
|
|
550
|
+
const BASE_CLASS_NAMES = new Set(['Helper', 'Feature', 'Client', 'Server'])
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Build a concise summary block for an individual helper listing its interface at a glance.
|
|
554
|
+
* Shows extends line if there's an intermediate parent, then own methods/getters,
|
|
555
|
+
* then inherited methods/getters from the intermediate parent.
|
|
556
|
+
*/
|
|
557
|
+
function buildHelperSummary(Ctor: any): string {
|
|
558
|
+
const introspection = Ctor.introspect?.()
|
|
559
|
+
const ownMethods = Object.keys(introspection?.methods || {}).sort()
|
|
560
|
+
const ownGetters = Object.keys(introspection?.getters || {}).sort()
|
|
561
|
+
|
|
562
|
+
// Walk up the prototype chain to find an intermediate parent
|
|
563
|
+
const chain: any[] = []
|
|
564
|
+
let current = Object.getPrototypeOf(Ctor)
|
|
565
|
+
while (current && current.name && !BASE_CLASS_NAMES.has(current.name) && current !== Function.prototype) {
|
|
566
|
+
chain.push(current)
|
|
567
|
+
current = Object.getPrototypeOf(current)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const lines: string[] = []
|
|
571
|
+
|
|
572
|
+
if (chain.length > 0) {
|
|
573
|
+
lines.push(`> extends ${chain[0].name}`)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (ownGetters.length) lines.push(`getters: ${ownGetters.join(', ')}`)
|
|
577
|
+
if (ownMethods.length) lines.push(`methods: ${ownMethods.map(m => m + '()').join(', ')}`)
|
|
578
|
+
|
|
579
|
+
// Collect inherited members from intermediate parent(s)
|
|
580
|
+
for (const parent of chain) {
|
|
581
|
+
const parentIntrospection = parent.introspect?.()
|
|
582
|
+
const inheritedMethods = Object.keys(parentIntrospection?.methods || {}).sort()
|
|
583
|
+
const inheritedGetters = Object.keys(parentIntrospection?.getters || {}).sort()
|
|
584
|
+
if (inheritedGetters.length) lines.push(`inherited getters (${parent.name}): ${inheritedGetters.join(', ')}`)
|
|
585
|
+
if (inheritedMethods.length) lines.push(`inherited methods (${parent.name}): ${inheritedMethods.map(m => m + '()').join(', ')}`)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return lines.join('\n')
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function getMemberData(container: any, registryName: RegistryName, id: string, member: string, memberType: 'method' | 'getter', headingDepth = 1): { json: any; text: string } {
|
|
592
|
+
const registry = container[registryName]
|
|
593
|
+
const Ctor = registry.lookup(id)
|
|
594
|
+
const introspection = Ctor.introspect?.()
|
|
595
|
+
const h = '#'.repeat(headingDepth)
|
|
596
|
+
const hSub = '#'.repeat(headingDepth + 1)
|
|
597
|
+
|
|
598
|
+
if (memberType === 'method') {
|
|
599
|
+
const method = introspection?.methods?.[member] as MethodIntrospection | undefined
|
|
600
|
+
if (!method) return { json: {}, text: `No introspection data for ${id}.${member}()` }
|
|
601
|
+
|
|
602
|
+
const parts: string[] = []
|
|
603
|
+
parts.push(`${h} ${id}.${member}()`)
|
|
604
|
+
parts.push(`> method on **${introspection.className || id}**`)
|
|
605
|
+
|
|
606
|
+
if (method.description) parts.push(method.description)
|
|
607
|
+
|
|
608
|
+
// Parameters
|
|
609
|
+
const paramEntries = Object.entries(method.parameters || {})
|
|
610
|
+
if (paramEntries.length > 0) {
|
|
611
|
+
const paramLines = [`${hSub} Parameters`, '']
|
|
612
|
+
for (const [name, info] of paramEntries) {
|
|
613
|
+
const req = (method.required || []).includes(name) ? ' *(required)*' : ''
|
|
614
|
+
paramLines.push(`- **${name}** \`${info.type}\`${req}${info.description ? ' — ' + info.description : ''}`)
|
|
615
|
+
// Nested properties (e.g. options objects)
|
|
616
|
+
if (info.properties) {
|
|
617
|
+
for (const [propName, propInfo] of Object.entries(info.properties)) {
|
|
618
|
+
paramLines.push(` - **${propName}** \`${propInfo.type}\`${propInfo.description ? ' — ' + propInfo.description : ''}`)
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
parts.push(paramLines.join('\n'))
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Returns
|
|
626
|
+
if (method.returns && method.returns !== 'void') {
|
|
627
|
+
parts.push(`${hSub} Returns\n\n\`${method.returns}\``)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Examples
|
|
631
|
+
if (method.examples?.length) {
|
|
632
|
+
parts.push(`${hSub} Examples`)
|
|
633
|
+
for (const ex of method.examples) {
|
|
634
|
+
parts.push(`\`\`\`${ex.language || 'typescript'}\n${ex.code}\n\`\`\``)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return { json: { [member]: method, _helper: id, _type: 'method' }, text: parts.join('\n\n') }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Getter
|
|
642
|
+
const getter = introspection?.getters?.[member] as GetterIntrospection | undefined
|
|
643
|
+
if (!getter) return { json: {}, text: `No introspection data for ${id}.${member}` }
|
|
644
|
+
|
|
645
|
+
const parts: string[] = []
|
|
646
|
+
parts.push(`${h} ${id}.${member}`)
|
|
647
|
+
parts.push(`> getter on **${introspection.className || id}** — returns \`${getter.returns || 'unknown'}\``)
|
|
648
|
+
|
|
649
|
+
if (getter.description) parts.push(getter.description)
|
|
650
|
+
|
|
651
|
+
if (getter.examples?.length) {
|
|
652
|
+
parts.push(`${hSub} Examples`)
|
|
653
|
+
for (const ex of getter.examples) {
|
|
654
|
+
parts.push(`\`\`\`${ex.language || 'typescript'}\n${ex.code}\n\`\`\``)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return { json: { [member]: getter, _helper: id, _type: 'getter' }, text: parts.join('\n\n') }
|
|
659
|
+
}
|
|
660
|
+
|
|
341
661
|
function getHelperData(container: any, registryName: RegistryName, id: string, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
342
662
|
const registry = container[registryName]
|
|
343
663
|
const Ctor = registry.lookup(id)
|
|
664
|
+
const text = renderHelperText(Ctor, sections, noTitle, headingDepth)
|
|
665
|
+
|
|
666
|
+
// Inject summary after the title + description block for full (no-section) renders
|
|
667
|
+
let finalText = text
|
|
668
|
+
if (sections.length === 0 && !noTitle) {
|
|
669
|
+
const summary = buildHelperSummary(Ctor)
|
|
670
|
+
if (summary) {
|
|
671
|
+
// Find the first ## heading and insert the summary before it
|
|
672
|
+
const sectionHeading = '#'.repeat(headingDepth + 1) + ' '
|
|
673
|
+
const idx = text.indexOf('\n' + sectionHeading)
|
|
674
|
+
if (idx >= 0) {
|
|
675
|
+
finalText = text.slice(0, idx) + '\n\n' + summary + '\n' + text.slice(idx)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
344
679
|
|
|
345
680
|
return {
|
|
346
681
|
json: renderHelperJson(Ctor, sections, noTitle),
|
|
347
|
-
text:
|
|
682
|
+
text: finalText,
|
|
348
683
|
}
|
|
349
684
|
}
|
|
350
685
|
|
|
@@ -405,6 +740,8 @@ export default async function describe(options: z.infer<typeof argsSchema>, cont
|
|
|
405
740
|
return getRegistryData(container, item.name, sections, noTitle, headingDepth)
|
|
406
741
|
case 'helper':
|
|
407
742
|
return getHelperData(container, item.registry, item.id, sections, noTitle, headingDepth)
|
|
743
|
+
case 'member':
|
|
744
|
+
return getMemberData(container, item.registry, item.id, item.member, item.memberType, headingDepth)
|
|
408
745
|
}
|
|
409
746
|
}
|
|
410
747
|
|
package/src/commands/help.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const argsSchema = CommandOptionsSchema.extend({})
|
|
|
13
13
|
|
|
14
14
|
/** Hidden option prefixes — legacy aliases that shouldn't clutter help output. */
|
|
15
15
|
const HIDDEN_PREFIXES = ['only-']
|
|
16
|
-
const HIDDEN_KEYS = new Set(['_', 'name', '_cacheKey'])
|
|
16
|
+
const HIDDEN_KEYS = new Set(['_', 'name', '_cacheKey', 'dispatchSource'])
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Extract CLI option info from a Zod schema.
|
|
@@ -176,17 +176,43 @@ export default async function help(_options: z.infer<typeof argsSchema>, context
|
|
|
176
176
|
console.log()
|
|
177
177
|
console.log(c.white(' Usage: ') + c.cyan('luca') + c.dim(' <command|file> [options]'))
|
|
178
178
|
console.log()
|
|
179
|
+
const allNames = (container.commands.available as string[]).filter((n: string) => n !== 'help')
|
|
180
|
+
const maxNameLen = Math.max(...allNames.map((n: string) => n.length)) + 2
|
|
181
|
+
|
|
182
|
+
const sources = (container as any)._commandSources as
|
|
183
|
+
| { builtinCommands: Set<string>; projectCommands: Set<string>; userCommands: Set<string> }
|
|
184
|
+
| undefined
|
|
185
|
+
|
|
186
|
+
const printCommands = (names: string[]) => {
|
|
187
|
+
for (const name of names) {
|
|
188
|
+
const Cmd = container.commands.lookup(name) as any
|
|
189
|
+
const desc = Cmd.commandDescription || ''
|
|
190
|
+
console.log(` ${c.cyan(name.padEnd(maxNameLen))} ${c.dim(desc)}`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Built-in commands
|
|
195
|
+
const builtinNames = sources
|
|
196
|
+
? allNames.filter((n) => sources.builtinCommands.has(n))
|
|
197
|
+
: allNames
|
|
179
198
|
console.log(c.white(' Commands:'))
|
|
180
199
|
console.log()
|
|
200
|
+
printCommands(builtinNames)
|
|
201
|
+
|
|
202
|
+
// Project-local commands
|
|
203
|
+
if (sources && sources.projectCommands.size > 0) {
|
|
204
|
+
console.log()
|
|
205
|
+
console.log(c.white(' Project Commands') + c.dim(' (./commands/*)'))
|
|
206
|
+
console.log()
|
|
207
|
+
printCommands(allNames.filter((n) => sources.projectCommands.has(n)))
|
|
208
|
+
}
|
|
181
209
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const desc = Cmd.commandDescription || ''
|
|
189
|
-
console.log(` ${c.cyan(name.padEnd(maxNameLen))} ${c.dim(desc)}`)
|
|
210
|
+
// User-level commands
|
|
211
|
+
if (sources && sources.userCommands.size > 0) {
|
|
212
|
+
console.log()
|
|
213
|
+
console.log(c.white(' User Commands') + c.dim(' (~/.luca/commands/*)'))
|
|
214
|
+
console.log()
|
|
215
|
+
printCommands(allNames.filter((n) => sources.userCommands.has(n)))
|
|
190
216
|
}
|
|
191
217
|
|
|
192
218
|
console.log()
|
package/src/commands/index.ts
CHANGED
|
@@ -15,8 +15,81 @@ export const argsSchema = CommandOptionsSchema.extend({
|
|
|
15
15
|
output: z.string().optional().describe('Output file path (default: features/introspection.generated.ts)'),
|
|
16
16
|
'dry-run': z.boolean().default(false).describe('Preview without writing'),
|
|
17
17
|
'include-private': z.boolean().default(false).describe('Include private methods in output'),
|
|
18
|
+
lint: z.boolean().default(false).describe('Warn about undocumented getters, methods, options, and class descriptions'),
|
|
18
19
|
})
|
|
19
20
|
|
|
21
|
+
function lintResults(results: any[]) {
|
|
22
|
+
type LintWarning = { helper: string; category: string; member: string; message: string }
|
|
23
|
+
const warnings: LintWarning[] = []
|
|
24
|
+
|
|
25
|
+
for (const result of results) {
|
|
26
|
+
const helper = result.className || result.id
|
|
27
|
+
|
|
28
|
+
// Class-level description
|
|
29
|
+
if (!result.description || result.description === `${result.className} helper`) {
|
|
30
|
+
warnings.push({ helper, category: 'class', member: '', message: 'Missing class description (add a JSDoc block above the class)' })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Methods
|
|
34
|
+
for (const [name, method] of Object.entries(result.methods || {}) as [string, any][]) {
|
|
35
|
+
if (!method.description) {
|
|
36
|
+
warnings.push({ helper, category: 'method', member: name, message: 'Missing JSDoc description' })
|
|
37
|
+
}
|
|
38
|
+
// Check for undocumented parameters (generic fallback description)
|
|
39
|
+
for (const [paramName, param] of Object.entries(method.parameters || {}) as [string, any][]) {
|
|
40
|
+
if (param.description === `Parameter ${paramName}`) {
|
|
41
|
+
warnings.push({ helper, category: 'method', member: `${name}(@param ${paramName})`, message: 'Missing @param description' })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (method.returns === 'void' || method.returns === 'any') {
|
|
45
|
+
// not necessarily a problem, skip
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Getters
|
|
50
|
+
for (const [name, getter] of Object.entries(result.getters || {}) as [string, any][]) {
|
|
51
|
+
if (!getter.description) {
|
|
52
|
+
warnings.push({ helper, category: 'getter', member: name, message: 'Missing JSDoc description' })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Options (populated at runtime from Zod, but build-time scan may have empty options)
|
|
57
|
+
// We check if options exist and have empty descriptions
|
|
58
|
+
for (const [name, opt] of Object.entries(result.options || {}) as [string, any][]) {
|
|
59
|
+
if (!opt.description) {
|
|
60
|
+
warnings.push({ helper, category: 'option', member: name, message: 'Missing description (add .describe() to the Zod schema field)' })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return warnings
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printLintReport(warnings: ReturnType<typeof lintResults>, label?: string) {
|
|
69
|
+
if (warnings.length === 0) {
|
|
70
|
+
console.log(label ? ` ${label}: No lint warnings.` : 'No lint warnings.')
|
|
71
|
+
return 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Group by helper
|
|
75
|
+
const byHelper = new Map<string, typeof warnings>()
|
|
76
|
+
for (const w of warnings) {
|
|
77
|
+
if (!byHelper.has(w.helper)) byHelper.set(w.helper, [])
|
|
78
|
+
byHelper.get(w.helper)!.push(w)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(label ? `\n ${label}: ${warnings.length} lint warning(s)` : `\n${warnings.length} lint warning(s)`)
|
|
82
|
+
for (const [helper, ws] of byHelper) {
|
|
83
|
+
console.log(`\n ${helper}:`)
|
|
84
|
+
for (const w of ws) {
|
|
85
|
+
const target = w.member ? `${w.category} ${w.member}` : w.category
|
|
86
|
+
console.log(` - [${target}] ${w.message}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return warnings.length
|
|
91
|
+
}
|
|
92
|
+
|
|
20
93
|
async function introspect(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
21
94
|
const container = context.container as any
|
|
22
95
|
const { fs, paths } = container
|
|
@@ -33,7 +106,7 @@ async function introspect(options: z.infer<typeof argsSchema>, context: Containe
|
|
|
33
106
|
const targets = [
|
|
34
107
|
{
|
|
35
108
|
name: 'node',
|
|
36
|
-
src: ['src/node/features', 'src/servers', 'src/container.ts', 'src/node/container.ts'],
|
|
109
|
+
src: ['src/node/features', 'src/clients', 'src/servers', 'src/container.ts', 'src/node/container.ts'],
|
|
37
110
|
outputPath: 'src/introspection/generated.node.ts',
|
|
38
111
|
},
|
|
39
112
|
{
|
|
@@ -43,11 +116,13 @@ async function introspect(options: z.infer<typeof argsSchema>, context: Containe
|
|
|
43
116
|
},
|
|
44
117
|
{
|
|
45
118
|
name: 'agi',
|
|
46
|
-
src: ['src/node/features', 'src/servers', 'src/agi/features', 'src/container.ts', 'src/node/container.ts', 'src/agi/container.server.ts'],
|
|
119
|
+
src: ['src/node/features', 'src/clients', 'src/servers', 'src/agi/features', 'src/container.ts', 'src/node/container.ts', 'src/agi/container.server.ts'],
|
|
47
120
|
outputPath: 'src/introspection/generated.agi.ts',
|
|
48
121
|
},
|
|
49
122
|
]
|
|
50
123
|
|
|
124
|
+
let totalWarnings = 0
|
|
125
|
+
|
|
51
126
|
for (const target of targets) {
|
|
52
127
|
console.log(`\nGenerating ${target.name} introspection data...`)
|
|
53
128
|
console.log(` Sources: ${target.src.join(', ')}`)
|
|
@@ -72,9 +147,18 @@ async function introspect(options: z.infer<typeof argsSchema>, context: Containe
|
|
|
72
147
|
} else {
|
|
73
148
|
console.log(` Wrote ${target.outputPath}`)
|
|
74
149
|
}
|
|
150
|
+
|
|
151
|
+
if (options.lint) {
|
|
152
|
+
const scanResults = scanner.state.get('scanResults') || []
|
|
153
|
+
const warnings = lintResults(scanResults)
|
|
154
|
+
totalWarnings += printLintReport(warnings, target.name)
|
|
155
|
+
}
|
|
75
156
|
}
|
|
76
157
|
|
|
77
158
|
console.log('\nAll introspection data generated.')
|
|
159
|
+
if (options.lint && totalWarnings > 0) {
|
|
160
|
+
console.log(`\nTotal: ${totalWarnings} lint warning(s) across all targets.`)
|
|
161
|
+
}
|
|
78
162
|
return
|
|
79
163
|
}
|
|
80
164
|
|
|
@@ -119,6 +203,12 @@ async function introspect(options: z.infer<typeof argsSchema>, context: Containe
|
|
|
119
203
|
} else {
|
|
120
204
|
console.log(`Wrote ${outputPath}`)
|
|
121
205
|
}
|
|
206
|
+
|
|
207
|
+
if (options.lint) {
|
|
208
|
+
const scanResults = scanner.state.get('scanResults') || []
|
|
209
|
+
const warnings = lintResults(scanResults)
|
|
210
|
+
printLintReport(warnings)
|
|
211
|
+
}
|
|
122
212
|
}
|
|
123
213
|
|
|
124
214
|
commands.registerHandler('introspect', {
|
package/src/commands/prompt.ts
CHANGED
|
@@ -12,8 +12,7 @@ declare module '../command.js' {
|
|
|
12
12
|
|
|
13
13
|
export const argsSchema = CommandOptionsSchema.extend({
|
|
14
14
|
model: z.string().optional().describe('Override the LLM model (assistant mode only)'),
|
|
15
|
-
|
|
16
|
-
'preserve-frontmatter': z.boolean().default(false).describe('Keep YAML frontmatter in the prompt instead of stripping it'),
|
|
15
|
+
'preserve-frontmatter': z.boolean().default(false).describe('Keep YAML frontmatter in the prompt instead of stripping it'),
|
|
17
16
|
'permission-mode': z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).default('acceptEdits').describe('Permission mode for CLI agents (default: acceptEdits)'),
|
|
18
17
|
'in-folder': z.string().optional().describe('Run the CLI agent in this directory (resolved via container.paths)'),
|
|
19
18
|
'out-file': z.string().optional().describe('Save session output as a markdown file'),
|
|
@@ -144,8 +143,8 @@ async function runClaudeOrCodex(target: 'claude' | 'codex', promptContent: strin
|
|
|
144
143
|
|
|
145
144
|
async function runAssistant(name: string, promptContent: string, options: z.infer<typeof argsSchema>, container: any): Promise<RunStats> {
|
|
146
145
|
const ui = container.feature('ui')
|
|
147
|
-
const manager = container.feature('assistantsManager'
|
|
148
|
-
manager.discover()
|
|
146
|
+
const manager = container.feature('assistantsManager')
|
|
147
|
+
await manager.discover()
|
|
149
148
|
|
|
150
149
|
const entry = manager.get(name)
|
|
151
150
|
if (!entry) {
|
|
@@ -335,8 +334,8 @@ async function runParallel(
|
|
|
335
334
|
})
|
|
336
335
|
} else {
|
|
337
336
|
// Assistant targets
|
|
338
|
-
const manager = container.feature('assistantsManager'
|
|
339
|
-
manager.discover()
|
|
337
|
+
const manager = container.feature('assistantsManager')
|
|
338
|
+
await manager.discover()
|
|
340
339
|
|
|
341
340
|
const entry = manager.get(target)
|
|
342
341
|
if (!entry) {
|