@soederpop/luca 0.0.6 → 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
package/src/commands/describe.ts
CHANGED
|
@@ -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. */
|
|
@@ -75,6 +75,24 @@ class DescribeError extends Error {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Extract a short summary from a potentially long description string.
|
|
80
|
+
* Takes text up to the first markdown heading, bullet list, or code block,
|
|
81
|
+
* capped at ~300 chars on a sentence boundary.
|
|
82
|
+
*/
|
|
83
|
+
function extractSummary(description: string): string {
|
|
84
|
+
// Strip from the first markdown heading/bullet/code block onward
|
|
85
|
+
const cut = description.search(/\s\*\*[A-Z][\w\s]+:\*\*|```|^\s*[-*]\s/m)
|
|
86
|
+
const text = cut > 0 ? description.slice(0, cut).trim() : description
|
|
87
|
+
|
|
88
|
+
if (text.length <= 300) return text
|
|
89
|
+
|
|
90
|
+
// Truncate on sentence boundary
|
|
91
|
+
const sentenceEnd = text.lastIndexOf('. ', 300)
|
|
92
|
+
if (sentenceEnd > 100) return text.slice(0, sentenceEnd + 1)
|
|
93
|
+
return text.slice(0, 300).trim() + '...'
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
/**
|
|
79
97
|
* Normalize an identifier to a comparable form by stripping file extensions,
|
|
80
98
|
* converting kebab-case and snake_case to lowercase-no-separators.
|
|
@@ -319,6 +337,77 @@ function getContainerData(container: any, sections: (IntrospectionSection | 'des
|
|
|
319
337
|
}
|
|
320
338
|
}
|
|
321
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Walk the prototype chain of a base class to collect shared methods and getters.
|
|
342
|
+
* These are the methods/getters inherited by all helpers in a registry.
|
|
343
|
+
*/
|
|
344
|
+
function collectSharedMembers(baseClass: any): { methods: string[]; getters: string[] } {
|
|
345
|
+
const methods: string[] = []
|
|
346
|
+
const getters: string[] = []
|
|
347
|
+
|
|
348
|
+
let proto = baseClass?.prototype
|
|
349
|
+
while (proto && proto.constructor.name !== 'Object') {
|
|
350
|
+
for (const k of Object.getOwnPropertyNames(proto)) {
|
|
351
|
+
if (k === 'constructor' || k.startsWith('_')) continue
|
|
352
|
+
const desc = Object.getOwnPropertyDescriptor(proto, k)
|
|
353
|
+
if (!desc) continue
|
|
354
|
+
if (desc.get && !getters.includes(k)) getters.push(k)
|
|
355
|
+
else if (typeof desc.value === 'function' && !methods.includes(k)) methods.push(k)
|
|
356
|
+
}
|
|
357
|
+
proto = Object.getPrototypeOf(proto)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { methods: methods.sort(), getters: getters.sort() }
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Find the intermediate parent class between a helper constructor and the registry's
|
|
365
|
+
* base class (e.g. RestClient between ElevenLabsClient and Client).
|
|
366
|
+
* Returns the intermediate class constructor, or null if the helper extends the base directly.
|
|
367
|
+
*/
|
|
368
|
+
function findIntermediateParent(Ctor: any, baseClass: any): { name: string; methods: string[]; getters: string[] } | null {
|
|
369
|
+
if (!baseClass) return null
|
|
370
|
+
|
|
371
|
+
// Walk up from Ctor's parent to find the chain
|
|
372
|
+
let parent = Object.getPrototypeOf(Ctor)
|
|
373
|
+
if (!parent || parent === baseClass) return null
|
|
374
|
+
|
|
375
|
+
// Check if the parent itself is a direct child of baseClass (i.e., 2nd level)
|
|
376
|
+
// We want to find the class between Ctor and baseClass
|
|
377
|
+
const chain: any[] = []
|
|
378
|
+
let current = parent
|
|
379
|
+
while (current && current !== baseClass && current !== Function.prototype) {
|
|
380
|
+
chain.push(current)
|
|
381
|
+
current = Object.getPrototypeOf(current)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// If the chain is empty or parent IS the baseClass, no intermediate
|
|
385
|
+
if (chain.length === 0 || current !== baseClass) return null
|
|
386
|
+
|
|
387
|
+
// The first entry in the chain is the direct parent of Ctor — that's our intermediate
|
|
388
|
+
const intermediate = chain[0]
|
|
389
|
+
const methods: string[] = []
|
|
390
|
+
const getters: string[] = []
|
|
391
|
+
|
|
392
|
+
// Collect only the methods/getters defined directly on the intermediate class
|
|
393
|
+
const proto = intermediate?.prototype
|
|
394
|
+
if (proto) {
|
|
395
|
+
for (const k of Object.getOwnPropertyNames(proto)) {
|
|
396
|
+
if (k === 'constructor' || k.startsWith('_')) continue
|
|
397
|
+
const desc = Object.getOwnPropertyDescriptor(proto, k)
|
|
398
|
+
if (!desc) continue
|
|
399
|
+
if (desc.get && !getters.includes(k)) getters.push(k)
|
|
400
|
+
else if (typeof desc.value === 'function' && !methods.includes(k)) methods.push(k)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
name: intermediate.name,
|
|
406
|
+
methods: methods.sort(),
|
|
407
|
+
getters: getters.sort(),
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
322
411
|
function getRegistryData(container: any, registryName: RegistryName, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
323
412
|
const registry = container[registryName]
|
|
324
413
|
const available: string[] = registry.available
|
|
@@ -327,6 +416,83 @@ function getRegistryData(container: any, registryName: RegistryName, sections: (
|
|
|
327
416
|
return { json: {}, text: `No ${registryName} are registered.` }
|
|
328
417
|
}
|
|
329
418
|
|
|
419
|
+
// When no section filters are specified, render a concise index (like describeAll)
|
|
420
|
+
// rather than full introspection for every single helper
|
|
421
|
+
if (sections.length === 0) {
|
|
422
|
+
const h = '#'.repeat(headingDepth)
|
|
423
|
+
const hSub = '#'.repeat(headingDepth + 1)
|
|
424
|
+
const jsonResult: Record<string, any> = {}
|
|
425
|
+
const textParts: string[] = [`${h} Available ${registryName} (${available.length})\n`]
|
|
426
|
+
|
|
427
|
+
// Show shared methods/getters from the base class at the top
|
|
428
|
+
const baseClass = registry.baseClass
|
|
429
|
+
if (baseClass) {
|
|
430
|
+
const shared = collectSharedMembers(baseClass)
|
|
431
|
+
const label = registryName === 'features' ? 'Feature'
|
|
432
|
+
: registryName === 'clients' ? 'Client'
|
|
433
|
+
: registryName === 'servers' ? 'Server'
|
|
434
|
+
: registryName[0]!.toUpperCase() + registryName.slice(1).replace(/s$/, '')
|
|
435
|
+
|
|
436
|
+
if (shared.getters.length) {
|
|
437
|
+
textParts.push(`**Shared ${label} Getters:** ${shared.getters.join(', ')}\n`)
|
|
438
|
+
}
|
|
439
|
+
if (shared.methods.length) {
|
|
440
|
+
textParts.push(`**Shared ${label} Methods:** ${shared.methods.map(m => m + '()').join(', ')}\n`)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
jsonResult._shared = { methods: shared.methods, getters: shared.getters }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Sort: core framework classes (direct children of baseClass) first, then the rest
|
|
447
|
+
const sorted = [...available].sort((a, b) => {
|
|
448
|
+
const aCtor = registry.lookup(a)
|
|
449
|
+
const bCtor = registry.lookup(b)
|
|
450
|
+
const aIsDirect = !findIntermediateParent(aCtor, baseClass)
|
|
451
|
+
const bIsDirect = !findIntermediateParent(bCtor, baseClass)
|
|
452
|
+
if (aIsDirect && !bIsDirect) return -1
|
|
453
|
+
if (!aIsDirect && bIsDirect) return 1
|
|
454
|
+
return 0
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
for (const id of sorted) {
|
|
458
|
+
const Ctor = registry.lookup(id)
|
|
459
|
+
const introspection = Ctor.introspect?.()
|
|
460
|
+
const description = introspection?.description || Ctor.description || 'No description provided'
|
|
461
|
+
// Take only the first 1-2 sentences as the summary
|
|
462
|
+
const summary = extractSummary(description)
|
|
463
|
+
|
|
464
|
+
const featureGetters = Object.keys(introspection?.getters || {}).sort()
|
|
465
|
+
const featureMethods = Object.keys(introspection?.methods || {}).sort()
|
|
466
|
+
|
|
467
|
+
// Detect intermediate parent class (e.g. RestClient between ElevenLabsClient and Client)
|
|
468
|
+
const intermediate = findIntermediateParent(Ctor, baseClass)
|
|
469
|
+
|
|
470
|
+
const entryJson: Record<string, any> = { description: summary, methods: featureMethods, getters: featureGetters }
|
|
471
|
+
if (intermediate) {
|
|
472
|
+
entryJson.extends = intermediate.name
|
|
473
|
+
entryJson.inheritedMethods = intermediate.methods
|
|
474
|
+
entryJson.inheritedGetters = intermediate.getters
|
|
475
|
+
}
|
|
476
|
+
jsonResult[id] = entryJson
|
|
477
|
+
|
|
478
|
+
const extendsLine = intermediate ? `\n> extends ${intermediate.name}\n` : ''
|
|
479
|
+
|
|
480
|
+
const memberLines: string[] = []
|
|
481
|
+
if (featureGetters.length) memberLines.push(` getters: ${featureGetters.join(', ')}`)
|
|
482
|
+
if (featureMethods.length) memberLines.push(` methods: ${featureMethods.map(m => m + '()').join(', ')}`)
|
|
483
|
+
if (intermediate) {
|
|
484
|
+
if (intermediate.getters.length) memberLines.push(` inherited getters: ${intermediate.getters.join(', ')}`)
|
|
485
|
+
if (intermediate.methods.length) memberLines.push(` inherited methods: ${intermediate.methods.map(m => m + '()').join(', ')}`)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const memberBlock = memberLines.length ? '\n' + memberLines.join('\n') + '\n' : ''
|
|
489
|
+
textParts.push(`${hSub} ${id}${extendsLine}\n${summary}\n${memberBlock}`)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return { json: jsonResult, text: textParts.join('\n') }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// When specific sections are requested, render full detail for each helper
|
|
330
496
|
const jsonResult: Record<string, any> = {}
|
|
331
497
|
const textParts: string[] = []
|
|
332
498
|
for (const id of available) {
|
|
@@ -338,13 +504,70 @@ function getRegistryData(container: any, registryName: RegistryName, sections: (
|
|
|
338
504
|
return { json: jsonResult, text: textParts.join('\n\n---\n\n') }
|
|
339
505
|
}
|
|
340
506
|
|
|
507
|
+
/** Known top-level helper base class names — anything above these is "shared" */
|
|
508
|
+
const BASE_CLASS_NAMES = new Set(['Helper', 'Feature', 'Client', 'Server'])
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build a concise summary block for an individual helper listing its interface at a glance.
|
|
512
|
+
* Shows extends line if there's an intermediate parent, then own methods/getters,
|
|
513
|
+
* then inherited methods/getters from the intermediate parent.
|
|
514
|
+
*/
|
|
515
|
+
function buildHelperSummary(Ctor: any): string {
|
|
516
|
+
const introspection = Ctor.introspect?.()
|
|
517
|
+
const ownMethods = Object.keys(introspection?.methods || {}).sort()
|
|
518
|
+
const ownGetters = Object.keys(introspection?.getters || {}).sort()
|
|
519
|
+
|
|
520
|
+
// Walk up the prototype chain to find an intermediate parent
|
|
521
|
+
const chain: any[] = []
|
|
522
|
+
let current = Object.getPrototypeOf(Ctor)
|
|
523
|
+
while (current && current.name && !BASE_CLASS_NAMES.has(current.name) && current !== Function.prototype) {
|
|
524
|
+
chain.push(current)
|
|
525
|
+
current = Object.getPrototypeOf(current)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const lines: string[] = []
|
|
529
|
+
|
|
530
|
+
if (chain.length > 0) {
|
|
531
|
+
lines.push(`> extends ${chain[0].name}`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (ownGetters.length) lines.push(`getters: ${ownGetters.join(', ')}`)
|
|
535
|
+
if (ownMethods.length) lines.push(`methods: ${ownMethods.map(m => m + '()').join(', ')}`)
|
|
536
|
+
|
|
537
|
+
// Collect inherited members from intermediate parent(s)
|
|
538
|
+
for (const parent of chain) {
|
|
539
|
+
const parentIntrospection = parent.introspect?.()
|
|
540
|
+
const inheritedMethods = Object.keys(parentIntrospection?.methods || {}).sort()
|
|
541
|
+
const inheritedGetters = Object.keys(parentIntrospection?.getters || {}).sort()
|
|
542
|
+
if (inheritedGetters.length) lines.push(`inherited getters (${parent.name}): ${inheritedGetters.join(', ')}`)
|
|
543
|
+
if (inheritedMethods.length) lines.push(`inherited methods (${parent.name}): ${inheritedMethods.map(m => m + '()').join(', ')}`)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return lines.join('\n')
|
|
547
|
+
}
|
|
548
|
+
|
|
341
549
|
function getHelperData(container: any, registryName: RegistryName, id: string, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
342
550
|
const registry = container[registryName]
|
|
343
551
|
const Ctor = registry.lookup(id)
|
|
552
|
+
const text = renderHelperText(Ctor, sections, noTitle, headingDepth)
|
|
553
|
+
|
|
554
|
+
// Inject summary after the title + description block for full (no-section) renders
|
|
555
|
+
let finalText = text
|
|
556
|
+
if (sections.length === 0 && !noTitle) {
|
|
557
|
+
const summary = buildHelperSummary(Ctor)
|
|
558
|
+
if (summary) {
|
|
559
|
+
// Find the first ## heading and insert the summary before it
|
|
560
|
+
const sectionHeading = '#'.repeat(headingDepth + 1) + ' '
|
|
561
|
+
const idx = text.indexOf('\n' + sectionHeading)
|
|
562
|
+
if (idx >= 0) {
|
|
563
|
+
finalText = text.slice(0, idx) + '\n\n' + summary + '\n' + text.slice(idx)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
344
567
|
|
|
345
568
|
return {
|
|
346
569
|
json: renderHelperJson(Ctor, sections, noTitle),
|
|
347
|
-
text:
|
|
570
|
+
text: finalText,
|
|
348
571
|
}
|
|
349
572
|
}
|
|
350
573
|
|
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) {
|
package/src/commands/run.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const argsSchema = CommandOptionsSchema.extend({
|
|
|
13
13
|
safe: z.boolean().default(false).describe('Require approval before each code block (markdown mode)'),
|
|
14
14
|
console: z.boolean().default(false).describe('Start an interactive REPL after executing a markdown file, with all accumulated context'),
|
|
15
15
|
onlySections: z.string().optional().describe('Comma-separated list of section headings to run (case-insensitive, markdown only)'),
|
|
16
|
+
dontInjectContext: z.boolean().default(false).describe('Skip auto-injecting container context into scripts (run with plain bun instead)'),
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
function resolveScript(ref: string, context: ContainerContext): string | null {
|
|
@@ -87,6 +88,7 @@ async function runMarkdown(scriptPath: string, options: z.infer<typeof argsSchem
|
|
|
87
88
|
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
88
89
|
fetch, URL, URLSearchParams,
|
|
89
90
|
...container.context,
|
|
91
|
+
$doc: doc
|
|
90
92
|
})
|
|
91
93
|
|
|
92
94
|
// ─── Parse and register ## Blocks section ──────────────────────────
|
|
@@ -173,20 +175,41 @@ async function runMarkdown(scriptPath: string, options: z.infer<typeof argsSchem
|
|
|
173
175
|
return shared
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
async function runScript(scriptPath: string, context: ContainerContext) {
|
|
178
|
+
async function runScript(scriptPath: string, context: ContainerContext, options: { dontInjectContext?: boolean } = {}) {
|
|
177
179
|
const container = context.container as any
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
if (options.dontInjectContext) {
|
|
182
|
+
const { exitCode, stderr } = await container.proc.execAndCapture(`bun run ${scriptPath}`, {
|
|
183
|
+
onOutput: (data: string) => process.stdout.write(data),
|
|
184
|
+
onError: (data: string) => process.stderr.write(data),
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
if (exitCode === 0) return
|
|
183
188
|
|
|
184
|
-
|
|
189
|
+
console.error(`\nScript failed with exit code ${exitCode}.\n`)
|
|
190
|
+
if (stderr.length) {
|
|
191
|
+
console.error(stderr)
|
|
192
|
+
}
|
|
193
|
+
return
|
|
194
|
+
}
|
|
185
195
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
const vm = container.feature('vm')
|
|
197
|
+
const esbuild = container.feature('esbuild')
|
|
198
|
+
const raw = container.fs.readFile(scriptPath)
|
|
199
|
+
const { code } = esbuild.transformSync(raw, { format: 'cjs' })
|
|
200
|
+
|
|
201
|
+
const ctx = {
|
|
202
|
+
require: vm.createRequireFor(scriptPath),
|
|
203
|
+
exports: {},
|
|
204
|
+
module: { exports: {} },
|
|
205
|
+
console,
|
|
206
|
+
setTimeout, setInterval, clearTimeout, clearInterval,
|
|
207
|
+
process, Buffer, URL, URLSearchParams,
|
|
208
|
+
fetch,
|
|
209
|
+
...container.context,
|
|
189
210
|
}
|
|
211
|
+
|
|
212
|
+
await vm.run(code, ctx)
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
async function diagnoseError(_scriptPath: string, error: Error, _context: ContainerContext) {
|
|
@@ -236,7 +259,7 @@ export default async function run(options: z.infer<typeof argsSchema>, context:
|
|
|
236
259
|
})
|
|
237
260
|
}
|
|
238
261
|
} else {
|
|
239
|
-
await runScript(scriptPath, context)
|
|
262
|
+
await runScript(scriptPath, context, { dontInjectContext: options.dontInjectContext })
|
|
240
263
|
}
|
|
241
264
|
} catch (err: any) {
|
|
242
265
|
await diagnoseError(scriptPath, err instanceof Error ? err : new Error(String(err)), context)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { commands } from '../command.js'
|
|
3
|
+
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
4
|
+
import type { ContainerContext } from '../container.js'
|
|
5
|
+
|
|
6
|
+
declare module '../command.js' {
|
|
7
|
+
interface AvailableCommands {
|
|
8
|
+
introspect: ReturnType<typeof commands.registerHandler>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
13
|
+
outputPath: z.string().default('docs/luca').describe('The path to save generated API docs to')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export async function apiDocs(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
17
|
+
const { container } = context
|
|
18
|
+
await container.helpers.discoverAll()
|
|
19
|
+
const outputFolder = options.outputPath ? container.paths.resolve(options.outputPath) : container.paths.resolve('docs','luca')
|
|
20
|
+
|
|
21
|
+
await container.fs.ensureFolder(
|
|
22
|
+
outputFolder
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const mkPath = (...args) => container.paths.resolve(outputFolder, ...args)
|
|
26
|
+
|
|
27
|
+
const result = await container.fs.writeFileAsync(mkPath('agi-container.md'), container.inspectAsText())
|
|
28
|
+
|
|
29
|
+
for(let reg of ['features','clients','servers']) {
|
|
30
|
+
const helperIds = container[reg].available
|
|
31
|
+
const folder = mkPath(reg)
|
|
32
|
+
await container.fs.ensureFolder(folder)
|
|
33
|
+
|
|
34
|
+
await Promise.all(
|
|
35
|
+
helperIds.map((helperId) => container.fs.writeFileAsync(
|
|
36
|
+
container.paths.resolve(folder, `${helperId}.md`),
|
|
37
|
+
container[reg].describe(helperId)
|
|
38
|
+
))
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
container.ui.print.green(`Finished saving API Docs`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
commands.registerHandler('api-docs', {
|
|
46
|
+
description: 'Save the helper introspection() content as markdown API docs in docs/luca',
|
|
47
|
+
argsSchema,
|
|
48
|
+
handler: apiDocs,
|
|
49
|
+
})
|