@soederpop/luca 0.0.23 → 0.0.25

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.
Files changed (58) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +6 -1
  3. package/assistants/codingAssistant/hooks.ts +0 -1
  4. package/assistants/lucaExpert/CORE.md +37 -0
  5. package/assistants/lucaExpert/hooks.ts +9 -0
  6. package/assistants/lucaExpert/tools.ts +177 -0
  7. package/commands/build-bootstrap.ts +41 -1
  8. package/docs/TABLE-OF-CONTENTS.md +0 -1
  9. package/docs/apis/clients/rest.md +5 -5
  10. package/docs/apis/features/agi/assistant.md +1 -1
  11. package/docs/apis/features/agi/conversation-history.md +6 -7
  12. package/docs/apis/features/agi/conversation.md +1 -1
  13. package/docs/apis/features/agi/semantic-search.md +1 -1
  14. package/docs/bootstrap/CLAUDE.md +1 -1
  15. package/docs/bootstrap/SKILL.md +7 -3
  16. package/docs/bootstrap/templates/luca-cli.ts +5 -0
  17. package/docs/mcp/readme.md +1 -1
  18. package/docs/tutorials/00-bootstrap.md +18 -0
  19. package/package.json +2 -2
  20. package/scripts/stamp-build.sh +12 -0
  21. package/scripts/test-docs-reader.ts +10 -0
  22. package/src/agi/container.server.ts +8 -5
  23. package/src/agi/features/assistant.ts +208 -55
  24. package/src/agi/features/assistants-manager.ts +138 -66
  25. package/src/agi/features/conversation.ts +46 -14
  26. package/src/agi/features/docs-reader.ts +142 -0
  27. package/src/agi/features/openapi.ts +1 -1
  28. package/src/agi/features/skills-library.ts +257 -313
  29. package/src/bootstrap/generated.ts +8163 -6
  30. package/src/cli/build-info.ts +4 -0
  31. package/src/cli/cli.ts +2 -1
  32. package/src/commands/bootstrap.ts +16 -1
  33. package/src/commands/eval.ts +6 -1
  34. package/src/commands/sandbox-mcp.ts +17 -7
  35. package/src/helper.ts +56 -2
  36. package/src/introspection/generated.agi.ts +2409 -1608
  37. package/src/introspection/generated.node.ts +902 -594
  38. package/src/introspection/generated.web.ts +1 -1
  39. package/src/node/container.ts +1 -1
  40. package/src/node/features/content-db.ts +251 -13
  41. package/src/node/features/git.ts +90 -0
  42. package/src/node/features/grep.ts +1 -1
  43. package/src/node/features/proc.ts +1 -0
  44. package/src/node/features/tts.ts +1 -1
  45. package/src/node/features/vm.ts +48 -0
  46. package/src/scaffolds/generated.ts +2 -2
  47. package/assistants/architect/CORE.md +0 -3
  48. package/assistants/architect/hooks.ts +0 -3
  49. package/assistants/architect/tools.ts +0 -10
  50. package/docs/apis/features/agi/skills-library.md +0 -234
  51. package/docs/reports/assistant-bugs.md +0 -38
  52. package/docs/reports/attach-pattern-usage.md +0 -18
  53. package/docs/reports/code-audit-results.md +0 -391
  54. package/docs/reports/console-hmr-design.md +0 -170
  55. package/docs/reports/helper-semantic-search.md +0 -72
  56. package/docs/reports/introspection-audit-tasks.md +0 -378
  57. package/docs/reports/luca-mcp-improvements.md +0 -128
  58. package/test-integration/skills-library.test.ts +0 -157
@@ -0,0 +1,4 @@
1
+ // Generated at compile time — do not edit manually
2
+ export const BUILD_SHA = '33ebc12'
3
+ export const BUILD_BRANCH = 'main'
4
+ export const BUILD_DATE = '2026-03-22T06:53:18Z'
package/src/cli/cli.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env bun
2
2
  // @ts-ignore — bun resolves JSON imports at bundle time
3
3
  import pkg from '../../package.json'
4
+ import { BUILD_SHA, BUILD_BRANCH, BUILD_DATE } from './build-info'
4
5
 
5
6
  // Fast-path flags that don't need the container
6
7
  const args = process.argv.slice(2)
7
8
  if (args.includes('--version') || args.includes('-v')) {
8
- console.log(`luca v${pkg.version}`)
9
+ console.log(`luca v${pkg.version} (${BUILD_BRANCH}@${BUILD_SHA}) built ${BUILD_DATE}`)
9
10
  console.log(` npm: https://www.npmjs.com/package/@soederpop/luca`)
10
11
  console.log(` git: https://github.com/soederpop/luca`)
11
12
  process.exit(0)
@@ -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 { bootstrapFiles, bootstrapTemplates } from '../bootstrap/generated.js'
5
+ import { bootstrapFiles, bootstrapTemplates, bootstrapExamples, bootstrapTutorials } from '../bootstrap/generated.js'
6
6
  import { apiDocs } from './save-api-docs.js'
7
7
  import { generateScaffold } from '../scaffolds/template.js'
8
8
 
@@ -52,6 +52,21 @@ async function bootstrap(options: z.infer<typeof argsSchema>, context: Container
52
52
  const apiDocsPath = container.paths.resolve(skillDir, 'references', 'api-docs')
53
53
  await apiDocs({ _: [], outputPath: apiDocsPath }, context)
54
54
 
55
+ // ── 3b. examples and tutorials ─────────────────────────────────
56
+ const examplesDir = container.paths.resolve(skillDir, 'references', 'examples')
57
+ await fs.ensureFolder(examplesDir)
58
+ for (const [filename, content] of Object.entries(bootstrapExamples)) {
59
+ await fs.writeFileAsync(container.paths.resolve(examplesDir, filename), content)
60
+ }
61
+ ui.print.cyan(` Writing ${Object.keys(bootstrapExamples).length} example docs...`)
62
+
63
+ const tutorialsDir = container.paths.resolve(skillDir, 'references', 'tutorials')
64
+ await fs.ensureFolder(tutorialsDir)
65
+ for (const [filename, content] of Object.entries(bootstrapTutorials)) {
66
+ await fs.writeFileAsync(container.paths.resolve(tutorialsDir, filename), content)
67
+ }
68
+ ui.print.cyan(` Writing ${Object.keys(bootstrapTutorials).length} tutorial docs...`)
69
+
55
70
  // ── 4. docs/ folder ────────────────────────────────────────────
56
71
  await fs.ensureFolder(mkPath('docs'))
57
72
  await writeFile(fs, ui, mkPath('docs', 'models.ts'), bootstrapTemplates['docs-models'] || '', 'docs/models.ts')
@@ -24,7 +24,12 @@ export default async function evalCommand(options: z.infer<typeof argsSchema>, c
24
24
 
25
25
  const args = container.argv._ as string[]
26
26
  // args[0] is "eval", the rest is the code snippet
27
- const code = args.slice(1).join(' ')
27
+ let code = args.slice(1).join(' ')
28
+
29
+ // Read from stdin if no inline code was provided
30
+ if (!code.trim()) {
31
+ code = await Bun.stdin.text()
32
+ }
28
33
 
29
34
  if (!code.trim()) {
30
35
  console.error('Usage: luca eval "<code>" [--json]')
@@ -42,11 +42,7 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
42
42
  const vmFeature = container.feature('vm')
43
43
  const sandboxContext = vmFeature.createContext({
44
44
  container,
45
- console: {
46
- log: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
47
- error: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
48
- warn: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
49
- },
45
+ console,
50
46
  setTimeout,
51
47
  setInterval,
52
48
  clearTimeout,
@@ -154,8 +150,20 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
154
150
  }),
155
151
  handler: async (args) => {
156
152
  try {
157
- const result = await vmFeature.run(args.code, sandboxContext)
153
+ const { result, console: calls } = await vmFeature.runCaptured(args.code, sandboxContext)
154
+
155
+ const content: Array<{ type: 'text', text: string }> = []
158
156
 
157
+ // Include captured console output if any
158
+ if (calls.length > 0) {
159
+ const consoleLines = calls.map(c => {
160
+ const prefix = c.method === 'log' ? '' : `[${c.method}] `
161
+ return prefix + c.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')
162
+ })
163
+ content.push({ type: 'text' as const, text: consoleLines.join('\n') })
164
+ }
165
+
166
+ // Include the result
159
167
  let text: string
160
168
  if (result === undefined) {
161
169
  text = 'undefined'
@@ -173,7 +181,9 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
173
181
  text = String(result)
174
182
  }
175
183
 
176
- return { content: [{ type: 'text' as const, text }] }
184
+ content.push({ type: 'text' as const, text })
185
+
186
+ return { content }
177
187
  } catch (error: any) {
178
188
  return {
179
189
  content: [{ type: 'text' as const, text: `Error: ${error.message}\n\n${error.stack || ''}` }],
package/src/helper.ts CHANGED
@@ -35,10 +35,12 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
35
35
  static stateSchema: z.ZodType = HelperStateSchema
36
36
  static optionsSchema: z.ZodType = HelperOptionsSchema
37
37
  static eventsSchema: z.ZodType = HelperEventsSchema
38
+ static tools: Record<string, { schema: z.ZodType, handler?: Function }> = {}
38
39
 
39
40
  protected readonly _context: ContainerContext
40
41
  protected readonly _events = new Bus<E>()
41
42
  protected readonly _options: K
43
+ protected readonly _instanceTools: Record<string, { schema: z.ZodType, handler?: Function }> = {}
42
44
 
43
45
  readonly state: State<T>
44
46
 
@@ -125,7 +127,7 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
125
127
  this._context = context;
126
128
  this.state = new State<T>({ initialState: this.initialState });
127
129
 
128
- this.hide('_context', '_state', '_options', '_events', 'uuid')
130
+ this.hide('_context', '_state', '_options', '_events', '_instanceTools', 'uuid')
129
131
 
130
132
  this.state.observe(() => {
131
133
  (this as any).emit('stateChange', this.state.current)
@@ -170,7 +172,7 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
170
172
  return this
171
173
  }
172
174
 
173
- /**
175
+ /**
174
176
  * python / lodash style get method, which will get a value from the container using dot notation
175
177
  * and will return a default value if the value is not found.
176
178
  */
@@ -178,6 +180,58 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
178
180
  return get(this, key, defaultValue)
179
181
  }
180
182
 
183
+ /**
184
+ * Register a tool on this instance at runtime. Instance tools take precedence
185
+ * over class-level static tools in toTools().
186
+ */
187
+ tool(name: string, options: { schema: z.ZodType, handler?: Function }): this {
188
+ this._instanceTools[name] = options
189
+ return this
190
+ }
191
+
192
+ /**
193
+ * Collect all tools from the inheritance chain and instance, returning
194
+ * { schemas, handlers } with matching keys. Walks the prototype chain
195
+ * so subclass tools override parent tools. Instance tools win over all.
196
+ *
197
+ * If a tool has no explicit handler but this instance has a method with
198
+ * the same name, a handler is auto-generated that delegates to that method.
199
+ */
200
+ toTools(): { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> } {
201
+ // Walk the prototype chain collecting static tools (parent-first, child overwrites)
202
+ const merged: Record<string, { schema: z.ZodType, handler?: Function }> = {}
203
+ const chain: Function[] = []
204
+
205
+ let current = this.constructor as any
206
+ while (current && current !== Object) {
207
+ if (Object.hasOwn(current, 'tools') && current.tools) {
208
+ chain.unshift(current)
209
+ }
210
+ current = Object.getPrototypeOf(current)
211
+ }
212
+
213
+ for (const ctor of chain) {
214
+ Object.assign(merged, (ctor as any).tools)
215
+ }
216
+
217
+ // Instance tools win over static
218
+ Object.assign(merged, this._instanceTools)
219
+
220
+ const schemas: Record<string, z.ZodType> = {}
221
+ const handlers: Record<string, Function> = {}
222
+
223
+ for (const [name, entry] of Object.entries(merged)) {
224
+ schemas[name] = entry.schema
225
+ if (entry.handler) {
226
+ handlers[name] = (args: any) => entry.handler!(args, this)
227
+ } else if (typeof (this as any)[name] === 'function') {
228
+ handlers[name] = (args: any) => (this as any)[name](args)
229
+ }
230
+ }
231
+
232
+ return { schemas, handlers }
233
+ }
234
+
181
235
  /**
182
236
  * The options passed to the helper when it was created.
183
237
  */