@soederpop/luca 0.0.21 → 0.0.23

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.
@@ -0,0 +1,27 @@
1
+ ---
2
+ agentOptions:
3
+ permissionMode: bypassPermissions
4
+ ---
5
+
6
+ # Check for undocumented features
7
+
8
+ The luca introspection and luca cli's describe command is powered by the JSDoc comments in the code base, and the values passed to the describe methods in the zod schema. This documentation is what is fed to AI Coding assistants to be able to understand how to use the individual features
9
+
10
+ Please scan the following paths:
11
+
12
+ ```ts
13
+ const fm = await container.feature('fileManager').start()
14
+ const results = fm.match(['src/node/features/*.ts', 'src/agi/features/*.ts', 'src/web/features/*.ts'])
15
+ console.log(results.join("\n"))
16
+ ```
17
+
18
+ Please ensure every feature has accurate and valid JSDoc documentation for the class definitions, methods, and getters.
19
+
20
+ Validate the output of `bun run src/cli/cli.ts describe whatever` for each item you're working on, that it displays properly, is easy to understand, accurate, in terms of how your comments display there. You may need to run `bun run src/cli/cli.ts introspect` to generate the build time data.
21
+
22
+ You can pass `--platform=web` to see web specific output.
23
+
24
+ ## Quality not just Quantity
25
+
26
+ - The presence of documentation isn't sufficient. Is it good documentation? Is it not overly verbose? Does it not explain stuff that doesn't matter? Is it accurate? If there are examples, do they work?
27
+ - The main consumer of `luca describe` will be an LLM so token budget matters. Every word has to be doing work. It should still be nice to look at for human coders too.
package/luca.cli.ts CHANGED
@@ -1,3 +1,16 @@
1
- export async function main(container) {
1
+ export async function main(container: any) {
2
2
  container.addContext('luca', container)
3
+
4
+ try {
5
+ container.onMissingCommand(handleMissingCommand)
6
+ } catch(error) {
7
+
8
+ }
9
+
10
+
11
+ async function handleMissingCommand({ words, phrase } : { words: string[], phrase: string }) {
12
+ const { ui } = container
13
+
14
+ ui.print.red('oh shit ' + phrase)
15
+ }
3
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soederpop/luca",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "website": "https://luca.soederpop.com",
5
5
  "description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
6
6
  "author": "jon soeder aka the people's champ <jon@soederpop.com>",
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bootstrap content
2
- // Generated at: 2026-03-21T05:24:16.832Z
2
+ // Generated at: 2026-03-21T05:25:04.090Z
3
3
  // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
4
4
  //
5
5
  // Do not edit manually. Run: luca build-bootstrap
package/src/cli/cli.ts CHANGED
@@ -19,7 +19,7 @@ import { join } from 'path'
19
19
  async function main() {
20
20
  const profile = process.env.LUCA_PROFILE === '1'
21
21
  const t = (label?: string) => {
22
- if (!profile) return () => {}
22
+ if (!profile) return () => { }
23
23
  const start = performance.now()
24
24
  return (suffix?: string) => {
25
25
  const ms = (performance.now() - start).toFixed(1)
@@ -59,8 +59,8 @@ async function main() {
59
59
  const afterUser = new Set(container.commands.available as string[])
60
60
  const userCommands = new Set([...afterUser].filter((n) => !builtinCommands.has(n) && !projectCommands.has(n)))
61
61
 
62
- // Store command sources for help display
63
- ;(container as any)._commandSources = { builtinCommands, projectCommands, userCommands }
62
+ // Store command sources for help display
63
+ ; (container as any)._commandSources = { builtinCommands, projectCommands, userCommands }
64
64
 
65
65
  // Load generated introspection data if present
66
66
  done = t('loadProjectIntrospection')
@@ -82,9 +82,33 @@ async function main() {
82
82
  await cmd.dispatch()
83
83
  } else if (commandName) {
84
84
  // not a known command — treat as implicit `run`
85
- container.argv._.splice(0, 0, 'run')
86
- const cmd = container.command('run' as any)
87
- await cmd.dispatch()
85
+ //
86
+ if (resolveScript(commandName, container)) {
87
+ container.argv._.splice(0, 0, 'run')
88
+ const cmd = container.command('run' as any)
89
+ await cmd.dispatch()
90
+ } else {
91
+
92
+ // @ts-ignore TODO come up with a typesafe way to do this
93
+ if (container.state.get('missingCommandHandler')) {
94
+ // @ts-ignore TODO come up with a typesafe way to do this
95
+ const missingCommandHandler = container.state.get('missingCommandHandler') as any
96
+
97
+ if (typeof missingCommandHandler === 'function') {
98
+ await missingCommandHandler({
99
+ words: container.argv._,
100
+ phrase: container.argv._.join(' ')
101
+ }).catch((err: any) => {
102
+ console.error(`Missing command handler error: ${err.message}`, err)
103
+ })
104
+ }
105
+ } else {
106
+ container.argv._.splice(0, 0, 'help')
107
+ const cmd = container.command('help' as any)
108
+ await cmd.dispatch()
109
+ }
110
+ }
111
+
88
112
  } else {
89
113
  container.argv._.splice(0, 0, 'help')
90
114
  const cmd = container.command('help' as any)
@@ -94,6 +118,21 @@ async function main() {
94
118
  tTotal()
95
119
  }
96
120
 
121
+ function resolveScript(ref: string, container: any) {
122
+ const candidates = [
123
+ ref,
124
+ `${ref}.ts`,
125
+ `${ref}.js`,
126
+ `${ref}.md`,
127
+ ]
128
+
129
+ for (const candidate of candidates) {
130
+ const resolved = container.paths.resolve(candidate)
131
+ if (container.fs.exists(resolved)) return resolved
132
+ }
133
+
134
+ return null
135
+ }
97
136
 
98
137
  async function loadCliModule() {
99
138
  const modulePath = container.paths.resolve('luca.cli.ts')
@@ -12,7 +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
- '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 before sending to the agent.'),
16
16
  'permission-mode': z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).default('acceptEdits').describe('Permission mode for CLI agents (default: acceptEdits)'),
17
17
  'in-folder': z.string().optional().describe('Run the CLI agent in this directory (resolved via container.paths)'),
18
18
  'out-file': z.string().optional().describe('Save session output as a markdown file'),
@@ -22,8 +22,16 @@ export const argsSchema = CommandOptionsSchema.extend({
22
22
  'exclude-sections': z.string().optional().describe('Comma-separated list of section headings to exclude from the prompt'),
23
23
  'chrome': z.boolean().default(false).describe('Launch Claude Code with a Chrome browser tool'),
24
24
  'dry-run': z.boolean().default(false).describe('Display the resolved prompt and options without running the assistant'),
25
+ 'local': z.boolean().default(false).describe('Use local models for assistant mode'),
25
26
  })
26
27
 
28
+ function normalizeTarget(raw: string): string {
29
+ const lower = raw.toLowerCase().replace(/[-_]/g, '')
30
+ if (/claude/.test(lower)) return 'claude'
31
+ if (/codex/.test(lower) || /openai/.test(lower)) return 'codex'
32
+ return raw
33
+ }
34
+
27
35
  const CLI_TARGETS = new Set(['claude', 'codex'])
28
36
 
29
37
  function formatSessionMarkdown(events: any[], includeOutput: boolean): string {
@@ -163,6 +171,7 @@ async function runAssistant(name: string, promptContent: string, options: z.infe
163
171
  const createOptions: Record<string, any> = { ...agentOptions }
164
172
  // CLI flags override agentOptions from frontmatter
165
173
  if (options.model) createOptions.model = options.model
174
+ if (options.local) createOptions.local = true
166
175
 
167
176
  const assistant = manager.create(name, createOptions)
168
177
  let isFirstChunk = true
@@ -360,6 +369,7 @@ async function runParallel(
360
369
  const assistants = prepared.map((p, i) => {
361
370
  const createOptions: Record<string, any> = { ...p.agentOptions }
362
371
  if (options.model) createOptions.model = options.model
372
+ if (options.local) createOptions.local = true
363
373
  const assistant = manager.create(target, createOptions)
364
374
 
365
375
  assistant.on('chunk', (text: string) => {
@@ -827,10 +837,16 @@ export default async function prompt(options: z.infer<typeof argsSchema>, contex
827
837
  const candidate = paths.resolve(target)
828
838
  if (fs.exists(candidate)) {
829
839
  allPaths.push(target)
830
- target = 'claude'
840
+ // this gives a way for you to say on a per project basis what you want the default coding assistant to be for the prompt command
841
+ // TODO need to document this somewhere
842
+ const { codingAssistant } = (container.manifest.luca || {})
843
+ target = codingAssistant || 'claude'
831
844
  }
832
845
  }
833
846
 
847
+ // Normalize target aliases (e.g. claude-code → claude, openai-codex → codex)
848
+ if (target) target = normalizeTarget(target)
849
+
834
850
  if (!target || allPaths.length === 0) {
835
851
  console.error('Usage: luca prompt [claude|codex|assistant-name] <path/to/prompt.md> [more paths...]')
836
852
  process.exit(1)