@soederpop/luca 0.0.9 → 0.0.11
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/commands/release.ts +186 -0
- package/package.json +2 -2
- package/src/bootstrap/generated.ts +17 -3
- package/src/cli/cli.ts +20 -1
- package/src/introspection/generated.agi.ts +1138 -1138
- package/src/introspection/generated.node.ts +470 -470
- package/src/introspection/generated.web.ts +1 -1
- package/src/scaffolds/generated.ts +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
3
|
+
import { CommandOptionsSchema } from '@soederpop/luca/schemas'
|
|
4
|
+
|
|
5
|
+
const TARGETS = [
|
|
6
|
+
{ name: 'linux-x64', bunTarget: 'bun-linux-x64', suffix: 'linux-x64' },
|
|
7
|
+
{ name: 'linux-arm64', bunTarget: 'bun-linux-arm64', suffix: 'linux-arm64' },
|
|
8
|
+
{ name: 'darwin-x64', bunTarget: 'bun-darwin-x64', suffix: 'darwin-x64' },
|
|
9
|
+
{ name: 'darwin-arm64', bunTarget: 'bun-darwin-arm64', suffix: 'darwin-arm64' },
|
|
10
|
+
{ name: 'windows-x64', bunTarget: 'bun-windows-x64', suffix: 'windows-x64', ext: '.exe' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
14
|
+
dryRun: z.boolean().optional().describe('Build binaries but skip tagging and uploading'),
|
|
15
|
+
skipBuild: z.boolean().optional().describe('Skip pre-build steps (introspection, scaffolds, bootstrap)'),
|
|
16
|
+
skipTests: z.boolean().optional().describe('Skip running tests before release'),
|
|
17
|
+
draft: z.boolean().optional().describe('Create the GitHub release as a draft'),
|
|
18
|
+
targets: z.string().optional().describe('Comma-separated list of targets to build (e.g. linux-x64,darwin-arm64). Defaults to all'),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
async function release(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
22
|
+
const container = context.container as any
|
|
23
|
+
const proc = container.feature('proc')
|
|
24
|
+
const fileSystem = container.feature('fs')
|
|
25
|
+
const ui = container.feature('ui')
|
|
26
|
+
|
|
27
|
+
const pkg = JSON.parse(await fileSystem.readFileAsync('package.json'))
|
|
28
|
+
const version = pkg.version
|
|
29
|
+
const tag = `v${version}`
|
|
30
|
+
const distDir = 'dist/release'
|
|
31
|
+
|
|
32
|
+
ui.banner(`Luca Release ${tag}`)
|
|
33
|
+
|
|
34
|
+
// Filter targets if specified
|
|
35
|
+
let selectedTargets = TARGETS
|
|
36
|
+
if (options.targets) {
|
|
37
|
+
const requested = options.targets.split(',').map((t: string) => t.trim())
|
|
38
|
+
selectedTargets = TARGETS.filter(t => requested.includes(t.suffix) || requested.includes(t.name))
|
|
39
|
+
if (selectedTargets.length === 0) {
|
|
40
|
+
console.error(`No valid targets found. Available: ${TARGETS.map(t => t.suffix).join(', ')}`)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 1. Run tests
|
|
46
|
+
if (!options.skipTests) {
|
|
47
|
+
console.log('\n→ Running tests...')
|
|
48
|
+
const testResult = await proc.execAndCapture('bun test test/*.test.ts', { silent: false })
|
|
49
|
+
if (testResult.exitCode !== 0) {
|
|
50
|
+
console.error('Tests failed. Fix them before releasing.')
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Pre-build steps
|
|
56
|
+
if (!options.skipBuild) {
|
|
57
|
+
console.log('\n→ Running pre-build steps...')
|
|
58
|
+
const steps = [
|
|
59
|
+
['build:introspection', 'bun run build:introspection'],
|
|
60
|
+
['build:scaffolds', 'bun run build:scaffolds'],
|
|
61
|
+
['build:bootstrap', 'bun run build:bootstrap'],
|
|
62
|
+
]
|
|
63
|
+
for (const [label, cmd] of steps) {
|
|
64
|
+
console.log(` ${label}...`)
|
|
65
|
+
const r = await proc.execAndCapture(cmd, { silent: true })
|
|
66
|
+
if (r.exitCode !== 0) {
|
|
67
|
+
console.error(`${label} failed:\n${r.stderr}`)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. Cross-compile for all targets
|
|
74
|
+
fileSystem.ensureFolder(distDir)
|
|
75
|
+
|
|
76
|
+
console.log(`\n→ Compiling for ${selectedTargets.length} targets...`)
|
|
77
|
+
for (const target of selectedTargets) {
|
|
78
|
+
const ext = target.ext || ''
|
|
79
|
+
const outfile = `${distDir}/luca-${target.suffix}${ext}`
|
|
80
|
+
const cmd = `bun build ./src/cli/cli.ts --compile --target=${target.bunTarget} --outfile ${outfile} --external node-llama-cpp`
|
|
81
|
+
|
|
82
|
+
console.log(` ${target.name}...`)
|
|
83
|
+
const result = await proc.execAndCapture(cmd, { silent: true })
|
|
84
|
+
if (result.exitCode !== 0) {
|
|
85
|
+
console.error(` Failed to compile for ${target.name}:\n${result.stderr}`)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sizeBytes = proc.exec(`stat -f%z ${container.paths.resolve(outfile)}`)
|
|
90
|
+
const sizeMB = (parseInt(sizeBytes, 10) / 1024 / 1024).toFixed(1)
|
|
91
|
+
console.log(` ✓ ${outfile} (${sizeMB} MB)`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.dryRun) {
|
|
95
|
+
console.log(`\n→ Dry run complete. Binaries are in ${distDir}/`)
|
|
96
|
+
console.log(' Skipping git tag and GitHub release.')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4. Check if tag already exists
|
|
101
|
+
const tagCheck = await proc.execAndCapture(`git tag -l "${tag}"`, { silent: true })
|
|
102
|
+
if (tagCheck.stdout.trim() === tag) {
|
|
103
|
+
console.error(`\nTag ${tag} already exists. Bump the version in package.json first.`)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 5. Check for clean working tree (allow untracked)
|
|
108
|
+
const statusCheck = await proc.execAndCapture('git status --porcelain', { silent: true })
|
|
109
|
+
const dirtyFiles = statusCheck.stdout.trim().split('\n').filter((l: string) => l && !l.startsWith('??'))
|
|
110
|
+
if (dirtyFiles.length > 0) {
|
|
111
|
+
console.error('\nWorking tree has uncommitted changes. Commit or stash them first.')
|
|
112
|
+
console.error(dirtyFiles.join('\n'))
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 6. Create git tag
|
|
117
|
+
console.log(`\n→ Creating tag ${tag}...`)
|
|
118
|
+
const tagResult = await proc.execAndCapture(`git tag -a "${tag}" -m "Release ${tag}"`, { silent: true })
|
|
119
|
+
if (tagResult.exitCode !== 0) {
|
|
120
|
+
console.error(`Failed to create tag:\n${tagResult.stderr}`)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 7. Push tag
|
|
125
|
+
console.log(`→ Pushing tag ${tag}...`)
|
|
126
|
+
const pushResult = await proc.execAndCapture(`git push origin "${tag}"`, { silent: true })
|
|
127
|
+
if (pushResult.exitCode !== 0) {
|
|
128
|
+
console.error(`Failed to push tag:\n${pushResult.stderr}`)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 8. Create GitHub release and upload binaries
|
|
133
|
+
const assetPaths = selectedTargets
|
|
134
|
+
.map(t => container.paths.resolve(`${distDir}/luca-${t.suffix}${t.ext || ''}`))
|
|
135
|
+
|
|
136
|
+
const releaseTitle = `Luca ${tag}`
|
|
137
|
+
const releaseNotes = await generateReleaseNotes(proc, tag)
|
|
138
|
+
|
|
139
|
+
// Write notes to a temp file so gh doesn't need shell quoting
|
|
140
|
+
const notesFile = container.paths.resolve(distDir, 'release-notes.md')
|
|
141
|
+
await fileSystem.writeFileAsync(notesFile, releaseNotes)
|
|
142
|
+
|
|
143
|
+
console.log(`\n→ Creating GitHub release ${tag}...`)
|
|
144
|
+
const ghArgs = [
|
|
145
|
+
'release', 'create', tag,
|
|
146
|
+
...assetPaths,
|
|
147
|
+
'--title', releaseTitle,
|
|
148
|
+
'--notes-file', notesFile,
|
|
149
|
+
...(options.draft ? ['--draft'] : []),
|
|
150
|
+
]
|
|
151
|
+
const ghResult = await proc.spawnAndCapture('gh', ghArgs)
|
|
152
|
+
|
|
153
|
+
if (ghResult.exitCode !== 0) {
|
|
154
|
+
console.error(`Failed to create GitHub release:\n${ghResult.stderr}`)
|
|
155
|
+
console.log('The tag was pushed. You can manually create the release with:')
|
|
156
|
+
console.log(` gh release create ${tag} ${assetPaths.join(' ')}`)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`\n✓ Released ${tag} successfully!`)
|
|
161
|
+
console.log(` https://github.com/soederpop/luca/releases/tag/${tag}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function generateReleaseNotes(proc: any, tag: string): Promise<string> {
|
|
165
|
+
// Get commits since last tag
|
|
166
|
+
const lastTag = await proc.execAndCapture('git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo ""', { silent: true })
|
|
167
|
+
const since = lastTag.stdout.trim()
|
|
168
|
+
|
|
169
|
+
let logCmd: string
|
|
170
|
+
if (since) {
|
|
171
|
+
logCmd = `git log ${since}..HEAD --oneline --no-decorate`
|
|
172
|
+
} else {
|
|
173
|
+
logCmd = 'git log --oneline --no-decorate -20'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const log = await proc.execAndCapture(logCmd, { silent: true })
|
|
177
|
+
const commits = log.stdout.trim()
|
|
178
|
+
|
|
179
|
+
return `## What's Changed\n\n${commits ? commits.split('\n').map((c: string) => `- ${c}`).join('\n') : 'Initial release'}\n\n## Platforms\n\n- Linux x64\n- Linux ARM64\n- macOS x64 (Intel)\n- macOS ARM64 (Apple Silicon)\n- Windows x64`
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default {
|
|
183
|
+
description: 'Build cross-platform binaries and publish a GitHub release tagged by version',
|
|
184
|
+
argsSchema,
|
|
185
|
+
handler: release,
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soederpop/luca",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
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>",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"chokidar": "^3.5.3",
|
|
103
103
|
"cli-markdown": "^3.5.0",
|
|
104
104
|
"compromise": "^14.14.5",
|
|
105
|
-
"contentbase": "^0.1.
|
|
105
|
+
"contentbase": "^0.1.4",
|
|
106
106
|
"cors": "^2.8.5",
|
|
107
107
|
"detect-port": "^1.5.1",
|
|
108
108
|
"dotenv": "^17.2.4",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated bootstrap content
|
|
2
|
-
// Generated at: 2026-03-
|
|
2
|
+
// Generated at: 2026-03-20T00:15:40.841Z
|
|
3
3
|
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
|
|
4
4
|
//
|
|
5
5
|
// Do not edit manually. Run: luca build-bootstrap
|
|
@@ -43,6 +43,19 @@ luca describe express # full docs for the express server
|
|
|
43
43
|
luca describe git fs proc # multiple helpers in one shot
|
|
44
44
|
\`\`\`
|
|
45
45
|
|
|
46
|
+
### Drill into a specific method or getter
|
|
47
|
+
|
|
48
|
+
Use dot notation to get docs for a single method or getter on any helper:
|
|
49
|
+
|
|
50
|
+
\`\`\`shell
|
|
51
|
+
luca describe ui.banner # docs for the banner() method on ui
|
|
52
|
+
luca describe fs.readFile # docs for readFile() on fs
|
|
53
|
+
luca describe ui.colors # docs for the colors getter on ui
|
|
54
|
+
luca describe git.branch # docs for the branch getter on git
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
This shows the description, parameters, return type, and examples for just that member. If the member doesn't exist, it lists all available methods and getters on the helper.
|
|
58
|
+
|
|
46
59
|
### Get targeted documentation
|
|
47
60
|
|
|
48
61
|
You can filter to only the sections you need:
|
|
@@ -70,7 +83,7 @@ luca describe --help # full flag reference for describe
|
|
|
70
83
|
luca help scaffold # help for any command
|
|
71
84
|
\`\`\`
|
|
72
85
|
|
|
73
|
-
**Use \`luca describe\` liberally.** It is the fastest, safest way to understand what the container provides. Every feature, client, and server is self-describing — if you know a name, describe will tell you everything about it.
|
|
86
|
+
**Use \`luca describe\` liberally.** It is the fastest, safest way to understand what the container provides. Every feature, client, and server is self-describing — if you know a name, describe will tell you everything about it. Use dot notation (\`ui.banner\`, \`fs.readFile\`) when you need docs on just one method or getter.
|
|
74
87
|
|
|
75
88
|
---
|
|
76
89
|
|
|
@@ -245,6 +258,7 @@ The \`luca\` binary is available in the path. Key commands:
|
|
|
245
258
|
- \`luca\` — list available commands (built-in + project commands)
|
|
246
259
|
- \`luca eval "expression"\` — evaluate JS with the container in scope
|
|
247
260
|
- \`luca describe <name>\` — full docs for any feature, client, or server (e.g. \`luca describe fs\`)
|
|
261
|
+
- \`luca describe <name>.<member>\` — docs for a specific method or getter (e.g. \`luca describe ui.banner\`, \`luca describe fs.readFile\`)
|
|
248
262
|
- \`luca describe features\` — index of all available features (also: \`clients\`, \`servers\`)
|
|
249
263
|
- \`luca serve\` — start a local server using \`endpoints/\` folder
|
|
250
264
|
- \`luca run script.ts\` — run a script with the container
|
|
@@ -258,7 +272,7 @@ The \`luca\` binary is available in the path. Key commands:
|
|
|
258
272
|
|
|
259
273
|
## Learning the Framework
|
|
260
274
|
|
|
261
|
-
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. This is your first move, always. (See \`.claude/skills/luca-framework/SKILL.md\` for the full mental model.)
|
|
275
|
+
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.)
|
|
262
276
|
2. **Build** — Run \`luca scaffold <type> --tutorial\` before creating a new helper. It covers the full guide for that type.
|
|
263
277
|
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.
|
|
264
278
|
4. **Reference** — Browse \`.claude/skills/luca-framework/references/api-docs/\` for pre-generated API docs
|
package/src/cli/cli.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
// @ts-ignore — bun resolves JSON imports at bundle time
|
|
3
|
+
import pkg from '../../package.json'
|
|
4
|
+
|
|
5
|
+
// Fast-path flags that don't need the container
|
|
6
|
+
const args = process.argv.slice(2)
|
|
7
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
8
|
+
console.log(`luca v${pkg.version}`)
|
|
9
|
+
console.log(` npm: https://www.npmjs.com/package/@soederpop/luca`)
|
|
10
|
+
console.log(` git: https://github.com/soederpop/luca`)
|
|
11
|
+
process.exit(0)
|
|
12
|
+
}
|
|
13
|
+
|
|
2
14
|
import container from '@soederpop/luca/agi'
|
|
3
15
|
import '@/commands/index.js'
|
|
4
16
|
import { homedir } from 'os'
|
|
@@ -58,7 +70,14 @@ async function main() {
|
|
|
58
70
|
const commandName = container.argv._[0] as string
|
|
59
71
|
|
|
60
72
|
done = t('dispatch')
|
|
61
|
-
if (
|
|
73
|
+
if (container.argv.help && !commandName) {
|
|
74
|
+
// --help with no command is the same as `luca` with no args
|
|
75
|
+
// Clear the help flag so the help command's handler runs (not the --help intercept)
|
|
76
|
+
delete container.argv.help
|
|
77
|
+
container.argv._.splice(0, 0, 'help')
|
|
78
|
+
const cmd = container.command('help' as any)
|
|
79
|
+
await cmd.dispatch()
|
|
80
|
+
} else if (commandName && container.commands.has(commandName)) {
|
|
62
81
|
const cmd = container.command(commandName as any)
|
|
63
82
|
await cmd.dispatch()
|
|
64
83
|
} else if (commandName) {
|