@soederpop/luca 0.0.23 → 0.0.26
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/AGENTS.md +1 -1
- package/CLAUDE.md +6 -1
- package/assistants/codingAssistant/hooks.ts +0 -1
- package/assistants/lucaExpert/CORE.md +37 -0
- package/assistants/lucaExpert/hooks.ts +9 -0
- package/assistants/lucaExpert/tools.ts +177 -0
- package/commands/build-bootstrap.ts +41 -1
- package/docs/TABLE-OF-CONTENTS.md +0 -1
- package/docs/apis/clients/rest.md +5 -5
- package/docs/apis/features/agi/assistant.md +1 -1
- package/docs/apis/features/agi/conversation-history.md +6 -7
- package/docs/apis/features/agi/conversation.md +1 -1
- package/docs/apis/features/agi/semantic-search.md +1 -1
- package/docs/bootstrap/CLAUDE.md +1 -1
- package/docs/bootstrap/SKILL.md +7 -3
- package/docs/bootstrap/templates/luca-cli.ts +5 -0
- package/docs/mcp/readme.md +1 -1
- package/docs/tutorials/00-bootstrap.md +18 -0
- package/package.json +2 -2
- package/scripts/stamp-build.sh +12 -0
- package/scripts/test-docs-reader.ts +10 -0
- package/src/agi/container.server.ts +8 -5
- package/src/agi/features/assistant.ts +210 -55
- package/src/agi/features/assistants-manager.ts +138 -66
- package/src/agi/features/conversation.ts +46 -14
- package/src/agi/features/docs-reader.ts +166 -0
- package/src/agi/features/openapi.ts +1 -1
- package/src/agi/features/skills-library.ts +257 -313
- package/src/bootstrap/generated.ts +8163 -6
- package/src/cli/build-info.ts +4 -0
- package/src/cli/cli.ts +2 -1
- package/src/command.ts +75 -0
- package/src/commands/bootstrap.ts +16 -1
- package/src/commands/describe.ts +29 -1089
- package/src/commands/eval.ts +6 -1
- package/src/commands/sandbox-mcp.ts +17 -7
- package/src/container-describer.ts +1098 -0
- package/src/container.ts +11 -0
- package/src/helper.ts +56 -2
- package/src/introspection/generated.agi.ts +1684 -799
- package/src/introspection/generated.node.ts +964 -572
- package/src/introspection/generated.web.ts +9 -1
- package/src/node/container.ts +1 -1
- package/src/node/features/content-db.ts +268 -13
- package/src/node/features/fs.ts +18 -0
- package/src/node/features/git.ts +90 -0
- package/src/node/features/grep.ts +1 -1
- package/src/node/features/proc.ts +1 -0
- package/src/node/features/tts.ts +1 -1
- package/src/node/features/vm.ts +48 -0
- package/src/scaffolds/generated.ts +2 -2
- package/src/server.ts +40 -0
- package/src/servers/express.ts +2 -0
- package/src/servers/mcp.ts +1 -0
- package/src/servers/socket.ts +2 -0
- package/assistants/architect/CORE.md +0 -3
- package/assistants/architect/hooks.ts +0 -3
- package/assistants/architect/tools.ts +0 -10
- package/docs/apis/features/agi/skills-library.md +0 -234
- package/docs/reports/assistant-bugs.md +0 -38
- package/docs/reports/attach-pattern-usage.md +0 -18
- package/docs/reports/code-audit-results.md +0 -391
- package/docs/reports/console-hmr-design.md +0 -170
- package/docs/reports/helper-semantic-search.md +0 -72
- package/docs/reports/introspection-audit-tasks.md +0 -378
- package/docs/reports/luca-mcp-improvements.md +0 -128
- package/test-integration/skills-library.test.ts +0 -157
package/AGENTS.md
CHANGED
|
@@ -36,7 +36,7 @@ On the frontend the browser container is perfect for highly reactive, stateful w
|
|
|
36
36
|
- The container is intended to provide a collection of blessed, approved, audited modules that we've built and curated together. It is intended to be the primary API and interface through the system
|
|
37
37
|
- The container should provide you with everything you need, and you should not need to be importing dependencies or other modules. If you find yourself stuck by this constraint, raise this concern, and we can work on finding a way to bring in a feature or client
|
|
38
38
|
- When trying to find paths in the project, use `container.paths.resolve()` or `container.paths.join()` instead of `import { resolve } from 'path'`
|
|
39
|
-
- **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies
|
|
39
|
+
- **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies everywhere — command handlers, scripts, and feature implementations alike. If a container feature wraps the functionality, use it.
|
|
40
40
|
|
|
41
41
|
## Container Utilities
|
|
42
42
|
|
package/CLAUDE.md
CHANGED
|
@@ -31,12 +31,17 @@ On the frontend the browser container is perfect for highly reactive, stateful w
|
|
|
31
31
|
|
|
32
32
|
**IMPORTANT NOTE** When trying to investigate features, clients, servers, etc, see if these tools can help you first instead of searching for files and reading them that way. If youw ant to understand what they do, vs how theyre actually implemented
|
|
33
33
|
|
|
34
|
+
## OpenAI Tool Schemas (Zod → JSON Schema)
|
|
35
|
+
|
|
36
|
+
**OpenAI requires `required` to list ALL property keys in `properties`**, even optional ones. Zod's `toJSONSchema()` only puts non-optional fields in `required`, which OpenAI rejects with "Missing 'X'" errors. The `assistant.addTool()` method handles this automatically by always setting `required: Object.keys(properties)`. Do NOT use `z.any()` or `z.record(z.any())` in tool schemas — Zod v4's `toJSONSchema()` cannot serialize `z.any()` and will throw `schema._zod is undefined`. Use concrete types like `z.string()` instead (e.g. accept a JSON string and parse it at runtime).
|
|
37
|
+
|
|
34
38
|
## Coding style and guidelines
|
|
35
39
|
|
|
36
40
|
- The container is intended to provide a collection of blessed, approved, audited modules that we've built and curated together. It is intended to be the primary API and interface through the system
|
|
37
41
|
- The container should provide you with everything you need, and you should not need to be importing dependencies or other modules. If you find yourself stuck by this constraint, raise this concern, and we can work on finding a way to bring in a feature or client
|
|
38
42
|
- When trying to find paths in the project, use `container.paths.resolve()` or `container.paths.join()` instead of `import { resolve } from 'path'`
|
|
39
|
-
-
|
|
43
|
+
- **`paths.join()` vs `paths.resolve()` gotcha:** `paths.join()` always prepends `container.cwd` — even if you pass an absolute path as the first arg. Use `paths.resolve(absolutePath, 'sub')` when the base is already absolute (e.g. `os.tmpdir`). `resolve` respects absolute first args just like Node's `path.resolve`.
|
|
44
|
+
- **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies everywhere — command handlers, scripts, and feature implementations alike. If a container feature wraps the functionality, use it.
|
|
40
45
|
|
|
41
46
|
## Container Utilities
|
|
42
47
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Luca Framework Expert
|
|
2
|
+
|
|
3
|
+
You are a Luca framework expert. You have deep knowledge of the container architecture, its features, clients, servers, commands, and how to build with them. Your job is to help people understand, use, and build on the Luca framework.
|
|
4
|
+
|
|
5
|
+
You have powerful tools to explore the framework at runtime — use them liberally before answering questions. Don't guess when you can look it up.
|
|
6
|
+
|
|
7
|
+
## Your Tools
|
|
8
|
+
|
|
9
|
+
### Discovery & Documentation
|
|
10
|
+
|
|
11
|
+
- **readSkill** — Read the SKILL.md learning guide and follow referenced documentation. Start here when orienting yourself.
|
|
12
|
+
- **readDoc** — Read any document from the docs/ folder: examples, tutorials, API references, scaffolds. Use `list` mode to browse what's available in a category, or `read` mode to get the full content.
|
|
13
|
+
- **lucaDescribe** — Run `luca describe` to get live documentation for any feature, client, server, or specific method/getter. This is generated from the actual source code and is always current. Use dot notation (`fs.readFile`, `ui.banner`) to drill into specific members.
|
|
14
|
+
|
|
15
|
+
### Live Code Execution
|
|
16
|
+
|
|
17
|
+
- **lucaEval** — Execute JavaScript/TypeScript in a live container sandbox. All features are available as top-level variables (fs, git, proc, vm, etc). The last expression's value is returned. For async code, put the await call as the last expression.
|
|
18
|
+
|
|
19
|
+
### Deep Research
|
|
20
|
+
|
|
21
|
+
- **askCodingAssistant** — Delegate deep codebase research to the coding assistant, who has ripgrep, cat, ls, sed, and awk at its disposal. Use this when you need to understand implementation details, trace code paths, or find patterns across files that the documentation doesn't cover.
|
|
22
|
+
|
|
23
|
+
## How to Work
|
|
24
|
+
|
|
25
|
+
1. **Start with describe.** When asked about any feature, client, or server — run `lucaDescribe` first. It's the fastest path to accurate information.
|
|
26
|
+
2. **Consult docs for depth.** Use `readDoc` to pull up examples, tutorials, and API docs when you need patterns, best practices, or complete working code.
|
|
27
|
+
3. **Eval to verify.** When uncertain about behavior, run it. `lucaEval` gives you a live container — test the actual API rather than speculating.
|
|
28
|
+
4. **Delegate research.** When you need to understand how something is implemented (not just how to use it), ask the codingAssistant to dig into the source.
|
|
29
|
+
5. **Read the SKILL.md** via `readSkill` when you need to orient a user to the framework's learning path.
|
|
30
|
+
|
|
31
|
+
## Response Style
|
|
32
|
+
|
|
33
|
+
- Lead with working code examples when possible
|
|
34
|
+
- Reference specific methods and their signatures
|
|
35
|
+
- Explain the "why" behind patterns, not just the "what"
|
|
36
|
+
- When a user's approach conflicts with framework conventions, explain the idiomatic way and why it matters
|
|
37
|
+
- Be concise but thorough — framework users need precision
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { Assistant, AGIContainer } from '@soederpop/luca/agi'
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
var assistant: Assistant
|
|
6
|
+
var container: AGIContainer
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const proc = () => container.feature('proc')
|
|
10
|
+
const fs = () => container.feature('fs')
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// readSkill — Read the SKILL.md and progressively discover referenced docs
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export const schemas = {
|
|
17
|
+
readSkill: z.object({
|
|
18
|
+
section: z.string().default('').describe(
|
|
19
|
+
'Section to focus on (e.g. "Phase 1", "Phase 2", "Key Concepts"). Leave empty for the full SKILL.md.'
|
|
20
|
+
),
|
|
21
|
+
}).describe('Read the SKILL.md learning guide for the Luca framework. Use this to orient yourself or a user to how the framework is learned and used.'),
|
|
22
|
+
|
|
23
|
+
readDoc: z.object({
|
|
24
|
+
mode: z.enum(['list', 'read']).describe(
|
|
25
|
+
'"list" to browse available documents in a category, "read" to get full content of a specific document'
|
|
26
|
+
),
|
|
27
|
+
category: z.string().default('').describe(
|
|
28
|
+
'Document category: examples, tutorials, apis, apis/features/node, apis/features/agi, apis/features/web, apis/clients, apis/servers, challenges, prompts, scaffolds, sessions, bootstrap. Leave empty when using mode "read".'
|
|
29
|
+
),
|
|
30
|
+
path: z.string().default('').describe(
|
|
31
|
+
'Relative path within docs/ to read (e.g. "examples/fs.md", "tutorials/00-bootstrap.md"). Required when mode is "read". Leave empty when using mode "list".'
|
|
32
|
+
),
|
|
33
|
+
}).describe('Browse and read documentation from the docs/ folder — examples, tutorials, API references, scaffolds, and more.'),
|
|
34
|
+
|
|
35
|
+
lucaDescribe: z.object({
|
|
36
|
+
args: z.string().describe(
|
|
37
|
+
'Arguments to pass to luca describe. Examples: "fs", "git", "features", "clients", "servers", "fs.readFile", "ui.banner", "express --options", "fs --methods", "fs git --examples"'
|
|
38
|
+
),
|
|
39
|
+
}).describe('Run the luca describe CLI to get live, source-generated documentation for any feature, client, server, or specific method/getter. This is always current.'),
|
|
40
|
+
|
|
41
|
+
lucaEval: z.object({
|
|
42
|
+
code: z.string().describe(
|
|
43
|
+
'JavaScript/TypeScript code to run in a live container. All features available as top-level vars (fs, git, proc, vm, etc). The last expression value is returned. For async: put the await expression on the last line.'
|
|
44
|
+
),
|
|
45
|
+
}).describe('Execute code in a live Luca container sandbox. Use this to test APIs, verify behavior, and prototype ideas. Top-level await is supported — put async calls as the last expression to capture their result.'),
|
|
46
|
+
|
|
47
|
+
askCodingAssistant: z.object({
|
|
48
|
+
question: z.string().describe(
|
|
49
|
+
'A research question for the coding assistant. Be specific about what you want to find: file paths, function implementations, patterns, class hierarchies, etc.'
|
|
50
|
+
),
|
|
51
|
+
}).describe('Delegate deep codebase research to the coding assistant who has ripgrep, cat, ls, sed, and awk. Use for implementation details, tracing code paths, or finding patterns the docs do not cover.'),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readSkill({ section }: z.infer<typeof schemas.readSkill>): string {
|
|
55
|
+
const skillPath = 'docs/bootstrap/SKILL.md'
|
|
56
|
+
|
|
57
|
+
if (!fs().exists(skillPath)) {
|
|
58
|
+
return 'SKILL.md not found at docs/bootstrap/SKILL.md'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const content = fs().readFile(skillPath)
|
|
62
|
+
|
|
63
|
+
if (!section || !section.trim()) return content
|
|
64
|
+
|
|
65
|
+
// Extract section by heading match
|
|
66
|
+
const lines = content.split('\n')
|
|
67
|
+
const sectionLines: string[] = []
|
|
68
|
+
let capturing = false
|
|
69
|
+
let sectionLevel = 0
|
|
70
|
+
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)/)
|
|
73
|
+
|
|
74
|
+
if (headingMatch) {
|
|
75
|
+
const level = headingMatch[1].length
|
|
76
|
+
const title = headingMatch[2]
|
|
77
|
+
|
|
78
|
+
if (capturing && level <= sectionLevel) break
|
|
79
|
+
|
|
80
|
+
if (title.toLowerCase().includes(section.toLowerCase())) {
|
|
81
|
+
capturing = true
|
|
82
|
+
sectionLevel = level
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (capturing) sectionLines.push(line)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sectionLines.length > 0
|
|
90
|
+
? sectionLines.join('\n')
|
|
91
|
+
: `Section "${section}" not found in SKILL.md. Available sections:\n${lines.filter(l => l.startsWith('#')).join('\n')}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function readDoc({ mode, category, path: docPath }: z.infer<typeof schemas.readDoc>): string {
|
|
95
|
+
if (mode === 'list') {
|
|
96
|
+
const dir = category ? `docs/${category}` : 'docs'
|
|
97
|
+
|
|
98
|
+
if (!fs().exists(dir)) {
|
|
99
|
+
return `Directory "${dir}" not found. Available top-level categories:\n${fs().readdirSync('docs').join('\n')}`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const entries = fs().readdirSync(dir)
|
|
103
|
+
return `Contents of ${dir}/:\n${entries.join('\n')}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// mode === 'read'
|
|
107
|
+
if (!docPath) {
|
|
108
|
+
return 'Error: path is required when mode is "read". Use mode "list" to browse available docs first.'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fullPath = docPath.startsWith('docs/') ? docPath : `docs/${docPath}`
|
|
112
|
+
|
|
113
|
+
if (!fs().exists(fullPath)) {
|
|
114
|
+
return `Document not found: ${fullPath}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return fs().readFile(fullPath)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function lucaDescribe({ args }: z.infer<typeof schemas.lucaDescribe>): string {
|
|
121
|
+
try {
|
|
122
|
+
return proc().exec(`bun run src/cli/cli.ts describe ${args}`)
|
|
123
|
+
} catch (err: any) {
|
|
124
|
+
return `Error running luca describe ${args}: ${err.message || err}`
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function lucaEval({ code }: z.infer<typeof schemas.lucaEval>): Promise<string> {
|
|
129
|
+
try {
|
|
130
|
+
const vm = container.feature('vm')
|
|
131
|
+
const { result, console: calls } = await vm.runCaptured(code, {
|
|
132
|
+
container,
|
|
133
|
+
fs: container.feature('fs'),
|
|
134
|
+
git: container.feature('git'),
|
|
135
|
+
proc: container.feature('proc'),
|
|
136
|
+
vm,
|
|
137
|
+
ui: container.feature('ui'),
|
|
138
|
+
os: container.feature('os'),
|
|
139
|
+
grep: container.feature('grep'),
|
|
140
|
+
networking: container.feature('networking'),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const parts: string[] = []
|
|
144
|
+
|
|
145
|
+
if (calls.length > 0) {
|
|
146
|
+
const consoleLines = calls.map(c => {
|
|
147
|
+
const prefix = c.method === 'log' ? '' : `[${c.method}] `
|
|
148
|
+
return prefix + c.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')
|
|
149
|
+
})
|
|
150
|
+
parts.push(consoleLines.join('\n'))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (result === undefined) {
|
|
154
|
+
parts.push('(undefined)')
|
|
155
|
+
} else if (result === null) {
|
|
156
|
+
parts.push('(null)')
|
|
157
|
+
} else if (typeof result === 'string') {
|
|
158
|
+
parts.push(result)
|
|
159
|
+
} else {
|
|
160
|
+
try { parts.push(JSON.stringify(result, null, 2)) } catch { parts.push(String(result)) }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parts.join('\n')
|
|
164
|
+
} catch (err: any) {
|
|
165
|
+
return `Eval error: ${err.message || err}`
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function askCodingAssistant({ question }: z.infer<typeof schemas.askCodingAssistant>): Promise<string> {
|
|
170
|
+
try {
|
|
171
|
+
const sub = await assistant.subagent('codingAssistant')
|
|
172
|
+
const answer = await sub.ask(question)
|
|
173
|
+
return answer
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
return `Error asking coding assistant: ${err.message || err}`
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -41,6 +41,30 @@ async function buildBootstrap(options: z.infer<typeof argsSchema>, context: Cont
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// 3. Collect docs/examples/*.md
|
|
45
|
+
const examples: Record<string, string> = {}
|
|
46
|
+
const examplesDir = 'docs/examples'
|
|
47
|
+
if (fs.exists(examplesDir)) {
|
|
48
|
+
const exampleFiles = (await fs.readdir(examplesDir)).filter((f: string) => f.endsWith('.md'))
|
|
49
|
+
for (const file of exampleFiles) {
|
|
50
|
+
const content = await fs.readFileAsync(`${examplesDir}/${file}`)
|
|
51
|
+
examples[file] = content
|
|
52
|
+
console.log(` example/${file}: ${content.length} chars`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 4. Collect docs/tutorials/*.md
|
|
57
|
+
const tutorials: Record<string, string> = {}
|
|
58
|
+
const tutorialsDir = 'docs/tutorials'
|
|
59
|
+
if (fs.exists(tutorialsDir)) {
|
|
60
|
+
const tutorialFiles = (await fs.readdir(tutorialsDir)).filter((f: string) => f.endsWith('.md'))
|
|
61
|
+
for (const file of tutorialFiles) {
|
|
62
|
+
const content = await fs.readFileAsync(`${tutorialsDir}/${file}`)
|
|
63
|
+
tutorials[file] = content
|
|
64
|
+
console.log(` tutorial/${file}: ${content.length} chars`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
44
68
|
const escapeForTemplate = (s: string) => s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')
|
|
45
69
|
|
|
46
70
|
const fileEntries = Object.entries(entries).map(([name, content]) =>
|
|
@@ -51,9 +75,17 @@ async function buildBootstrap(options: z.infer<typeof argsSchema>, context: Cont
|
|
|
51
75
|
` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
|
|
52
76
|
).join(',\n')
|
|
53
77
|
|
|
78
|
+
const exampleEntries = Object.entries(examples).map(([name, content]) =>
|
|
79
|
+
` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
|
|
80
|
+
).join(',\n')
|
|
81
|
+
|
|
82
|
+
const tutorialEntries = Object.entries(tutorials).map(([name, content]) =>
|
|
83
|
+
` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
|
|
84
|
+
).join(',\n')
|
|
85
|
+
|
|
54
86
|
const output = `// Auto-generated bootstrap content
|
|
55
87
|
// Generated at: ${new Date().toISOString()}
|
|
56
|
-
// Source: docs/bootstrap/*.md, docs/bootstrap/templates
|
|
88
|
+
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
|
|
57
89
|
//
|
|
58
90
|
// Do not edit manually. Run: luca build-bootstrap
|
|
59
91
|
|
|
@@ -64,6 +96,14 @@ ${fileEntries}
|
|
|
64
96
|
export const bootstrapTemplates: Record<string, string> = {
|
|
65
97
|
${templateEntries}
|
|
66
98
|
}
|
|
99
|
+
|
|
100
|
+
export const bootstrapExamples: Record<string, string> = {
|
|
101
|
+
${exampleEntries}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const bootstrapTutorials: Record<string, string> = {
|
|
105
|
+
${tutorialEntries}
|
|
106
|
+
}
|
|
67
107
|
`
|
|
68
108
|
|
|
69
109
|
fs.ensureFolder('src/bootstrap')
|
|
@@ -120,7 +120,6 @@
|
|
|
120
120
|
- [OpenAICodex (features.openaiCodex)](./apis/features/agi/openai-codex.md)
|
|
121
121
|
- [OpenAPI (features.openapi)](./apis/features/agi/openapi.md)
|
|
122
122
|
- [SemanticSearch (features.semanticSearch)](./apis/features/agi/semantic-search.md)
|
|
123
|
-
- [SkillsLibrary (features.skillsLibrary)](./apis/features/agi/skills-library.md)
|
|
124
123
|
- [ContainerLink (features.containerLink)](./apis/features/node/container-link.md)
|
|
125
124
|
- [ContentDb (features.contentDb)](./apis/features/node/content-db.md)
|
|
126
125
|
- [DiskCache (features.diskCache)](./apis/features/node/disk-cache.md)
|
|
@@ -40,7 +40,7 @@ Send a PATCH request.
|
|
|
40
40
|
| `data` | `any` | | Request body |
|
|
41
41
|
| `options` | `AxiosRequestConfig` | | Additional axios request config |
|
|
42
42
|
|
|
43
|
-
**Returns:** `
|
|
43
|
+
**Returns:** `Promise<any>`
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
|
|
@@ -56,7 +56,7 @@ Send a PUT request.
|
|
|
56
56
|
| `data` | `any` | | Request body |
|
|
57
57
|
| `options` | `AxiosRequestConfig` | | Additional axios request config |
|
|
58
58
|
|
|
59
|
-
**Returns:** `
|
|
59
|
+
**Returns:** `Promise<any>`
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
|
|
@@ -72,7 +72,7 @@ Send a POST request.
|
|
|
72
72
|
| `data` | `any` | | Request body |
|
|
73
73
|
| `options` | `AxiosRequestConfig` | | Additional axios request config |
|
|
74
74
|
|
|
75
|
-
**Returns:** `
|
|
75
|
+
**Returns:** `Promise<any>`
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
|
|
@@ -88,7 +88,7 @@ Send a DELETE request.
|
|
|
88
88
|
| `params` | `any` | | Query parameters |
|
|
89
89
|
| `options` | `AxiosRequestConfig` | | Additional axios request config |
|
|
90
90
|
|
|
91
|
-
**Returns:** `
|
|
91
|
+
**Returns:** `Promise<any>`
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
|
|
@@ -104,7 +104,7 @@ Send a GET request.
|
|
|
104
104
|
| `params` | `any` | | Query parameters |
|
|
105
105
|
| `options` | `AxiosRequestConfig` | | Additional axios request config |
|
|
106
106
|
|
|
107
|
-
**Returns:** `
|
|
107
|
+
**Returns:** `Promise<any>`
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Assistant (features.assistant)
|
|
2
2
|
|
|
3
|
-
An Assistant is a
|
|
3
|
+
An Assistant is a wrapper around an LLM chat thread where you supply a system prompt and tools. You can optionally use a folder to load the system prompt (CORE.md), tool implementations (tools.ts), and event hooks (hooks.ts) from disk. The AssistantsManager feature will automatically discover any assistant definitions that live on disk in your project.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# ConversationHistory (features.conversationHistory)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A queryable store, backed by the file system, for maintaining conversation history and easily restoring it. Each conversation is stored as a JSON record keyed by ID, with lightweight metadata stored alongside for efficient listing and search without loading full message arrays.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
container.feature('conversationHistory', {
|
|
9
|
-
//
|
|
9
|
+
// Directory for conversation storage
|
|
10
10
|
cachePath,
|
|
11
|
-
// Namespace prefix
|
|
11
|
+
// Namespace prefix to isolate datasets
|
|
12
12
|
namespace,
|
|
13
13
|
})
|
|
14
14
|
```
|
|
@@ -17,8 +17,8 @@ container.feature('conversationHistory', {
|
|
|
17
17
|
|
|
18
18
|
| Property | Type | Description |
|
|
19
19
|
|----------|------|-------------|
|
|
20
|
-
| `cachePath` | `string` |
|
|
21
|
-
| `namespace` | `string` | Namespace prefix
|
|
20
|
+
| `cachePath` | `string` | Directory for conversation storage |
|
|
21
|
+
| `namespace` | `string` | Namespace prefix to isolate datasets |
|
|
22
22
|
|
|
23
23
|
## Methods
|
|
24
24
|
|
|
@@ -307,8 +307,7 @@ Delete all conversations matching a thread prefix.
|
|
|
307
307
|
|
|
308
308
|
| Property | Type | Description |
|
|
309
309
|
|----------|------|-------------|
|
|
310
|
-
| `
|
|
311
|
-
| `namespace` | `string` | |
|
|
310
|
+
| `namespace` | `string` | The namespace prefix used for key isolation |
|
|
312
311
|
|
|
313
312
|
## Events (Zod v4 schema)
|
|
314
313
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Conversation (features.conversation)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An observable conversation class that enables long-running chat threads with an LLM. Supports streaming responses, tool calling with automatic handler dispatch, message state management, and context window compaction. Works with both local (Ollama) and remote (OpenAI) backends.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SemanticSearch (features.semanticSearch)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A vector search engine backed by SQLite that indexes documents with embeddings for semantic, keyword, and hybrid search. Supports local GGUF embedding models and configurable chunking strategies for splitting documents into searchable segments.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
package/docs/bootstrap/CLAUDE.md
CHANGED
|
@@ -32,7 +32,7 @@ The `luca` binary is available in the path. Key commands:
|
|
|
32
32
|
1. **Discover** — Run `luca describe features`, `luca describe clients`, `luca describe servers` to see what's available. Then `luca describe <name>` for full docs on any helper, or `luca describe <name>.<member>` to drill into a specific method or getter. This is your first move, always. (See `.claude/skills/luca-framework/SKILL.md` for the full mental model.)
|
|
33
33
|
2. **Build** — Run `luca scaffold <type> --tutorial` before creating a new helper. It covers the full guide for that type.
|
|
34
34
|
3. **Prototype** — Use `luca eval "expression"` to test container code before wiring up full handlers. Reach for eval when you're stuck — it gives you full runtime access.
|
|
35
|
-
4. **Reference** — Browse `.claude/skills/luca-framework/references
|
|
35
|
+
4. **Reference** — Browse `.claude/skills/luca-framework/references/` for pre-generated API docs, runnable examples, and tutorials
|
|
36
36
|
|
|
37
37
|
## Project Structure
|
|
38
38
|
|
package/docs/bootstrap/SKILL.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Using the luca framework
|
|
3
|
-
description:
|
|
3
|
+
description: The @soederpop/luca framework, when you see a project with docs/ commands/ features/ luca.cli.ts endpoints/ folders, or @soederpop/luca is in the package.json, or the user is asking you to develop a new Luca feature, use this skill to learn about the APIs and how to learn the framework at runtime. The luca cli bundles all of the documentation in a searchable, progressively learnable interface designed for students and AI assistants alike
|
|
4
4
|
---
|
|
5
5
|
# Luca: Learning the Container
|
|
6
6
|
|
|
7
7
|
The Luca framework `@soederpop/luca` ships a `luca` binary — a bun-based CLI for a dependency injection container. This project is based on it if this skill is present. The container auto-discovers modules in `commands/`, `clients/`, `servers/`, `features/`, and `endpoints/` folders.
|
|
8
8
|
|
|
9
|
+
The `luca` cli loads typescript modules in through its VM which injects a `container` global that is a singleton object from which you can learn about, and access all different kinds of utils and Helpers (features, clients, servers, commands, and compositions thereof)
|
|
10
|
+
|
|
9
11
|
There are three things to learn, in this order:
|
|
10
12
|
|
|
11
13
|
1. **Discover** what the container can do — `luca describe`
|
|
12
14
|
2. **Build** new helpers when your project needs them — `luca scaffold`
|
|
13
15
|
3. **Prototype** and debug with live code — `luca eval`
|
|
14
|
-
|
|
16
|
+
4. **Write Runnable Markdown** a great usecase is `luca run markdown.md` where the markdown codeblocks are executed inside the Luca VM.
|
|
15
17
|
---
|
|
16
18
|
|
|
17
19
|
## Phase 1: Discover with `luca describe`
|
|
@@ -248,4 +250,6 @@ This is useful inside commands and scripts where you need introspection data pro
|
|
|
248
250
|
|
|
249
251
|
## Reference
|
|
250
252
|
|
|
251
|
-
|
|
253
|
+
- `references/api-docs/` — full pre-generated API reference for every built-in feature, client, and server
|
|
254
|
+
- `references/examples/` — runnable example docs for each feature (e.g. `fs.md`, `git.md`, `proc.md`)
|
|
255
|
+
- `references/tutorials/` — step-by-step tutorials covering the container, helpers, commands, endpoints, and more
|
|
@@ -22,4 +22,9 @@
|
|
|
22
22
|
export async function main(container: any) {
|
|
23
23
|
// Discover project-level helpers (commands/, features/, endpoints/)
|
|
24
24
|
await container.helpers.discoverAll()
|
|
25
|
+
|
|
26
|
+
// Handle unknown commands gracefully instead of silently failing
|
|
27
|
+
container.onMissingCommand(async ({ phrase }: { phrase: string }) => {
|
|
28
|
+
container.command('help').dispatch()
|
|
29
|
+
})
|
|
25
30
|
}
|
package/docs/mcp/readme.md
CHANGED
|
@@ -17,7 +17,7 @@ import { Server, servers, ServerStateSchema } from '@soederpop/luca'
|
|
|
17
17
|
import { commands, CommandOptionsSchema } from '@soederpop/luca'
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Never import from `fs`, `path`, `crypto`, or other Node builtins. Never import third-party packages in consumer code.
|
|
20
|
+
Never import from `fs`, `path`, `crypto`, or other Node builtins. Never import third-party packages in consumer code. If a container feature wraps the functionality, use it.
|
|
21
21
|
|
|
22
22
|
## Zod v4
|
|
23
23
|
|
|
@@ -140,6 +140,24 @@ luca eval "grep.search('.', 'TODO')"
|
|
|
140
140
|
| Full container overview? | `container.inspectAsText()` |
|
|
141
141
|
| CLI docs for a helper? | `luca describe <name>` |
|
|
142
142
|
|
|
143
|
+
## Gotchas
|
|
144
|
+
|
|
145
|
+
### `paths.join()` vs `paths.resolve()`
|
|
146
|
+
|
|
147
|
+
`container.paths.join()` and `container.paths.resolve()` are Node's `path.join` and `path.resolve` curried with `container.cwd`. This means `paths.join()` always prepends `cwd` — even if you pass an absolute path as the first argument.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
// WRONG — paths.join will prepend cwd to the absolute tmpdir path
|
|
151
|
+
const bad = container.paths.join(os.tmpdir, 'mydir')
|
|
152
|
+
// => "/your/project/tmp/mydir" (not what you want)
|
|
153
|
+
|
|
154
|
+
// RIGHT — paths.resolve respects absolute first args
|
|
155
|
+
const good = container.paths.resolve(os.tmpdir, 'mydir')
|
|
156
|
+
// => "/tmp/mydir"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Rule of thumb:** Use `paths.join()` for project-relative paths, `paths.resolve()` when the base is already absolute.
|
|
160
|
+
|
|
143
161
|
## What's Next
|
|
144
162
|
|
|
145
163
|
- [The Container](./02-container.md) — deep dive into state, events, and lifecycle
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soederpop/luca",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
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>",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"clean": "rm -rf dist build package",
|
|
49
49
|
"build:web": "bun run scripts/build-web.ts",
|
|
50
50
|
"build:introspection": "bun run src/cli/cli.ts introspect",
|
|
51
|
-
"compile": "bun run build:introspection && bun run build:scaffolds && bun run build:bootstrap && bun build ./src/cli/cli.ts --compile --outfile dist/luca --external node-llama-cpp",
|
|
51
|
+
"compile": "bun run build:introspection && bun run build:scaffolds && bun run build:bootstrap && bash scripts/stamp-build.sh && bun build ./src/cli/cli.ts --compile --outfile dist/luca --external node-llama-cpp",
|
|
52
52
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
53
|
"build:scaffolds": "bun run src/cli/cli.ts build-scaffolds",
|
|
54
54
|
"build:bootstrap": "bun run src/cli/cli.ts build-bootstrap",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stamps git SHA, branch, and date into src/cli/build-info.ts before compile
|
|
3
|
+
SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
4
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
5
|
+
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
6
|
+
|
|
7
|
+
cat > src/cli/build-info.ts <<EOF
|
|
8
|
+
// Generated at compile time — do not edit manually
|
|
9
|
+
export const BUILD_SHA = '${SHA}'
|
|
10
|
+
export const BUILD_BRANCH = '${BRANCH}'
|
|
11
|
+
export const BUILD_DATE = '${DATE}'
|
|
12
|
+
EOF
|
|
@@ -6,10 +6,11 @@ import { ElevenLabsClient } from '../clients/elevenlabs'
|
|
|
6
6
|
import { ClaudeCode } from './features/claude-code'
|
|
7
7
|
import { OpenAICodex } from './features/openai-codex'
|
|
8
8
|
import { Conversation } from './features/conversation'
|
|
9
|
-
import { SkillsLibrary } from './features/skills-library'
|
|
10
9
|
import { ConversationHistory } from './features/conversation-history'
|
|
11
10
|
import { Assistant } from './features/assistant'
|
|
12
11
|
import { AssistantsManager } from './features/assistants-manager'
|
|
12
|
+
import { DocsReader } from './features/docs-reader'
|
|
13
|
+
import { SkillsLibrary } from './features/skills-library'
|
|
13
14
|
import { SemanticSearch } from '@soederpop/luca/node/features/semantic-search'
|
|
14
15
|
import { ContentDb } from '@soederpop/luca/node/features/content-db'
|
|
15
16
|
|
|
@@ -20,10 +21,11 @@ export {
|
|
|
20
21
|
ClaudeCode,
|
|
21
22
|
OpenAICodex,
|
|
22
23
|
Conversation,
|
|
23
|
-
SkillsLibrary,
|
|
24
24
|
ConversationHistory,
|
|
25
25
|
Assistant,
|
|
26
26
|
AssistantsManager,
|
|
27
|
+
DocsReader,
|
|
28
|
+
SkillsLibrary,
|
|
27
29
|
SemanticSearch,
|
|
28
30
|
ContentDb,
|
|
29
31
|
NodeContainer,
|
|
@@ -42,10 +44,11 @@ export interface AGIFeatures extends NodeFeatures {
|
|
|
42
44
|
conversation: typeof Conversation
|
|
43
45
|
claudeCode: typeof ClaudeCode
|
|
44
46
|
openaiCodex: typeof OpenAICodex
|
|
45
|
-
skillsLibrary: typeof SkillsLibrary
|
|
46
47
|
conversationHistory: typeof ConversationHistory
|
|
47
48
|
assistant: typeof Assistant
|
|
48
49
|
assistantsManager: typeof AssistantsManager
|
|
50
|
+
docsReader: typeof DocsReader
|
|
51
|
+
skillsLibrary: typeof SkillsLibrary
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
export interface ConversationFactoryOptions {
|
|
@@ -73,7 +76,6 @@ export class AGIContainer<
|
|
|
73
76
|
openai!: OpenAIClient
|
|
74
77
|
claudeCode?: ClaudeCode
|
|
75
78
|
openaiCodex?: OpenAICodex
|
|
76
|
-
skillsLibrary?: SkillsLibrary
|
|
77
79
|
conversationHistory?: ConversationHistory
|
|
78
80
|
docs!: ContentDb
|
|
79
81
|
|
|
@@ -114,10 +116,11 @@ const container = new AGIContainer()
|
|
|
114
116
|
.use(ClaudeCode)
|
|
115
117
|
.use(OpenAICodex)
|
|
116
118
|
.use(Conversation)
|
|
117
|
-
.use(SkillsLibrary)
|
|
118
119
|
.use(ConversationHistory)
|
|
119
120
|
.use(Assistant)
|
|
120
121
|
.use(AssistantsManager)
|
|
122
|
+
.use(DocsReader)
|
|
123
|
+
.use(SkillsLibrary)
|
|
121
124
|
.use(SemanticSearch)
|
|
122
125
|
|
|
123
126
|
container.docs = container.feature('contentDb', {
|