@soederpop/luca 0.0.32 → 0.0.34
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/README.md +241 -36
- package/bun.lock +24 -5
- package/commands/build-python-bridge.ts +43 -0
- package/docs/apis/clients/rest.md +7 -7
- package/docs/apis/clients/websocket.md +23 -10
- package/docs/apis/features/agi/assistant.md +155 -8
- package/docs/apis/features/agi/assistants-manager.md +90 -22
- package/docs/apis/features/agi/auto-assistant.md +377 -0
- package/docs/apis/features/agi/browser-use.md +802 -0
- package/docs/apis/features/agi/claude-code.md +6 -1
- package/docs/apis/features/agi/conversation-history.md +7 -6
- package/docs/apis/features/agi/conversation.md +111 -38
- package/docs/apis/features/agi/docs-reader.md +35 -57
- package/docs/apis/features/agi/file-tools.md +163 -0
- package/docs/apis/features/agi/openapi.md +2 -2
- package/docs/apis/features/agi/skills-library.md +227 -0
- package/docs/apis/features/node/content-db.md +125 -4
- package/docs/apis/features/node/disk-cache.md +11 -11
- package/docs/apis/features/node/downloader.md +1 -1
- package/docs/apis/features/node/file-manager.md +15 -15
- package/docs/apis/features/node/fs.md +78 -21
- package/docs/apis/features/node/git.md +50 -10
- package/docs/apis/features/node/google-calendar.md +3 -0
- package/docs/apis/features/node/google-docs.md +10 -1
- package/docs/apis/features/node/google-drive.md +3 -0
- package/docs/apis/features/node/google-mail.md +214 -0
- package/docs/apis/features/node/google-sheets.md +3 -0
- package/docs/apis/features/node/ink.md +10 -10
- package/docs/apis/features/node/ipc-socket.md +83 -93
- package/docs/apis/features/node/networking.md +5 -5
- package/docs/apis/features/node/os.md +7 -7
- package/docs/apis/features/node/package-finder.md +14 -14
- package/docs/apis/features/node/proc.md +2 -1
- package/docs/apis/features/node/process-manager.md +70 -3
- package/docs/apis/features/node/python.md +265 -9
- package/docs/apis/features/node/redis.md +380 -0
- package/docs/apis/features/node/ui.md +13 -13
- package/docs/apis/servers/express.md +35 -7
- package/docs/apis/servers/mcp.md +3 -3
- package/docs/apis/servers/websocket.md +51 -8
- package/docs/bootstrap/CLAUDE.md +1 -1
- package/docs/bootstrap/SKILL.md +93 -7
- package/docs/examples/feature-as-tool-provider.md +143 -0
- package/docs/examples/python.md +42 -1
- package/docs/introspection.md +15 -5
- package/docs/tutorials/00-bootstrap.md +3 -3
- package/docs/tutorials/02-container.md +2 -2
- package/docs/tutorials/10-creating-features.md +5 -0
- package/docs/tutorials/13-introspection.md +12 -2
- package/docs/tutorials/19-python-sessions.md +401 -0
- package/package.json +8 -4
- package/src/agi/container.server.ts +8 -0
- package/src/agi/features/assistant.ts +18 -0
- package/src/agi/features/autonomous-assistant.ts +435 -0
- package/src/agi/features/conversation.ts +58 -6
- package/src/agi/features/file-tools.ts +286 -0
- package/src/agi/features/luca-coder.ts +643 -0
- package/src/bootstrap/generated.ts +705 -17
- package/src/cli/build-info.ts +2 -2
- package/src/cli/cli.ts +22 -13
- package/src/commands/bootstrap.ts +49 -6
- package/src/commands/code.ts +369 -0
- package/src/commands/describe.ts +7 -2
- package/src/commands/index.ts +1 -0
- package/src/commands/sandbox-mcp.ts +7 -7
- package/src/commands/save-api-docs.ts +1 -1
- package/src/container-describer.ts +4 -4
- package/src/container.ts +10 -19
- package/src/helper.ts +24 -33
- package/src/introspection/generated.agi.ts +3026 -590
- package/src/introspection/generated.node.ts +1625 -688
- package/src/introspection/generated.web.ts +15 -57
- package/src/node/container.ts +5 -0
- package/src/node/features/figlet-fonts.ts +597 -0
- package/src/node/features/fs.ts +3 -9
- package/src/node/features/helpers.ts +20 -0
- package/src/node/features/python.ts +429 -16
- package/src/node/features/redis.ts +446 -0
- package/src/node/features/ui.ts +4 -11
- package/src/python/bridge.py +220 -0
- package/src/python/generated.ts +227 -0
- package/src/scaffolds/generated.ts +1 -1
- package/test/python-session.test.ts +105 -0
- package/assistants/lucaExpert/CORE.md +0 -37
- package/assistants/lucaExpert/hooks.ts +0 -9
- package/assistants/lucaExpert/tools.ts +0 -177
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated bootstrap content
|
|
2
|
-
// Generated at: 2026-03-
|
|
2
|
+
// Generated at: 2026-03-26T03:30:22.249Z
|
|
3
3
|
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
|
|
4
4
|
//
|
|
5
5
|
// Do not edit manually. Run: luca build-bootstrap
|
|
@@ -25,7 +25,7 @@ There are three things to learn, in this order:
|
|
|
25
25
|
|
|
26
26
|
## Phase 1: Discover with \`luca describe\`
|
|
27
27
|
|
|
28
|
-
This is your primary tool.
|
|
28
|
+
This is your primary tool. The \`luca\` binary is a compiled artifact that bundles all introspection data — it is the authority on what the container provides. Run \`luca describe\` first — it outputs full documentation for any part of the container: methods, options, events, state, examples. Reading source can be helpful for additional context if it exists in the project, but the source for built-in helpers may not be present — the binary is always the ground truth.
|
|
29
29
|
|
|
30
30
|
### See what's available
|
|
31
31
|
|
|
@@ -77,6 +77,18 @@ luca describe fs git --examples # just examples for both
|
|
|
77
77
|
luca describe fs --usage --methods # combine sections
|
|
78
78
|
\`\`\`
|
|
79
79
|
|
|
80
|
+
### Get approximate TypeScript types
|
|
81
|
+
|
|
82
|
+
Need to know the shape of a helper for type-safe code? Use \`--ts\`:
|
|
83
|
+
|
|
84
|
+
\`\`\`shell
|
|
85
|
+
luca describe fs --ts # approximate TS interface for fs
|
|
86
|
+
luca describe conversation --ts # see the conversation feature's type surface
|
|
87
|
+
luca describe rest --ts # client type shape
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
This outputs a ~95% accurate TypeScript representation based on runtime introspection. If a type looks wrong or a method signature seems off, verify with \`luca eval\` against the live instance.
|
|
91
|
+
|
|
80
92
|
### Describe the container itself
|
|
81
93
|
|
|
82
94
|
\`\`\`shell
|
|
@@ -92,7 +104,9 @@ luca describe --help # full flag reference for describe
|
|
|
92
104
|
luca help scaffold # help for any command
|
|
93
105
|
\`\`\`
|
|
94
106
|
|
|
95
|
-
**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.
|
|
107
|
+
**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. Use \`--ts\` when you need type information for writing code.
|
|
108
|
+
|
|
109
|
+
> **NOTE:** The \`luca\` binary is compiled and bundles all introspection data. \`luca describe\` reflects what actually ships in the binary — source files for built-in helpers may not exist in your project. Reading source can add context when it's available, but \`luca describe\` and \`luca eval\` are always the authority.
|
|
96
110
|
|
|
97
111
|
---
|
|
98
112
|
|
|
@@ -239,7 +253,7 @@ Everything \`luca describe\` outputs is also available at runtime in code:
|
|
|
239
253
|
\`\`\`js
|
|
240
254
|
container.features.describe('fs') // markdown docs (same as the CLI)
|
|
241
255
|
feature.introspect() // structured object: { methods, events, state, options }
|
|
242
|
-
container.
|
|
256
|
+
container.introspectAsText() // full container overview as markdown
|
|
243
257
|
\`\`\`
|
|
244
258
|
|
|
245
259
|
This is useful inside commands and scripts where you need introspection data programmatically.
|
|
@@ -255,11 +269,83 @@ This is useful inside commands and scripts where you need introspection data pro
|
|
|
255
269
|
- \`luca serve --any-port\` will open on any port
|
|
256
270
|
|
|
257
271
|
|
|
258
|
-
##
|
|
272
|
+
## Framework Index
|
|
273
|
+
|
|
274
|
+
A table of contents for the container. **Run \`luca describe <name>\` for full docs on any item.** Use \`luca describe <name> --ts\` when you need type information. Source may not exist locally for built-in helpers — the compiled binary is the authority.
|
|
275
|
+
|
|
276
|
+
### Features by Category
|
|
277
|
+
|
|
278
|
+
| Category | Features | What they do |
|
|
279
|
+
|----------|----------|--------------|
|
|
280
|
+
| **File System & Code** | \`fs\`, \`grep\`, \`fileManager\` | Read/write files, search code, watch for changes |
|
|
281
|
+
| **Process & Shell** | \`proc\`, \`processManager\`, \`secureShell\` | Run commands, manage long-running processes, SSH |
|
|
282
|
+
| **AI Assistants** | \`assistant\`, \`assistantsManager\`, \`conversation\`, \`conversationHistory\`, \`fileTools\` | Build AI assistants, manage conversations, tool calling. \`fileTools\` composes lower-level features (\`fs\`, \`grep\`) into an assistant-ready tool surface — a good example of how features can define tools for assistants (see \`references/examples/feature-as-tool-provider.md\`). |
|
|
283
|
+
| **AI Agent Wrappers** | \`claudeCode\`, \`openaiCodex\`, \`lucaCoder\` | Spawn and manage external AI agent CLIs as subprocesses |
|
|
284
|
+
| **Data & Storage** | \`sqlite\`, \`postgres\`, \`diskCache\`, \`contentDb\`, \`redis\` | Databases, caching, document management |
|
|
285
|
+
| **Networking** | \`networking\`, \`dns\`, \`portExposer\` | Network utilities, DNS, tunneling |
|
|
286
|
+
| **Google Workspace** | \`googleAuth\`, \`googleDrive\`, \`googleDocs\`, \`googleSheets\`, \`googleCalendar\`, \`googleMail\` | OAuth and Google service wrappers |
|
|
287
|
+
| **Dev Tools** | \`git\`, \`docker\`, \`esbuild\`, \`vm\`, \`python\`, \`packageFinder\` | Version control, containers, bundling, sandboxed execution |
|
|
288
|
+
| **Content & NLP** | \`docsReader\`, \`nlp\`, \`semanticSearch\`, \`skillsLibrary\`, \`jsonTree\`, \`yamlTree\` | Document Q&A, text analysis, semantic search, skills, structured file ingestion |
|
|
289
|
+
| **UI & Output** | \`ui\`, \`ink\`, \`yaml\` | Terminal UI, colors, ascii art, structured data display |
|
|
290
|
+
| **Media & Browser** | \`browserUse\`, \`tts\`, \`downloader\`, \`opener\`, \`telegram\` | Browser automation, text-to-speech, downloads, messaging |
|
|
291
|
+
| **System** | \`os\`, \`vault\`, \`helpers\`, \`introspectionScanner\`, \`containerLink\`, \`repl\`, \`runpod\` | OS info, secrets, runtime introspection, remote container linking |
|
|
292
|
+
|
|
293
|
+
### Clients
|
|
294
|
+
|
|
295
|
+
| Client | Purpose |
|
|
296
|
+
|--------|---------|
|
|
297
|
+
| \`openai\` | Chat completions, embeddings, image generation |
|
|
298
|
+
| \`rest\` | Generic HTTP client (GET/POST/PUT/PATCH/DELETE) |
|
|
299
|
+
| \`websocket\` | WebSocket connections |
|
|
300
|
+
| \`elevenlabs\` | Text-to-speech synthesis |
|
|
301
|
+
| \`graph\` | GraphQL queries and mutations |
|
|
302
|
+
|
|
303
|
+
### Servers
|
|
304
|
+
|
|
305
|
+
| Server | Purpose |
|
|
306
|
+
|--------|---------|
|
|
307
|
+
| \`express\` | HTTP server with file-based endpoint routing |
|
|
308
|
+
| \`mcp\` | Model Context Protocol server for AI tool exposure |
|
|
309
|
+
| \`websocket\` | WebSocket server with JSON framing |
|
|
310
|
+
| \`ipcSocket\` | Local IPC socket server for inter-process communication |
|
|
311
|
+
|
|
312
|
+
### Type Discovery
|
|
313
|
+
|
|
314
|
+
\`luca describe <name> --ts\` outputs an approximate TypeScript representation of any helper as it exists at runtime — ~95% accurate. This is your go-to for writing type-safe code against the container. The binary compiles in the introspection data, so \`--ts\` reflects what actually exists at runtime even when source isn't available. Reading source can provide additional context when it's there.
|
|
315
|
+
|
|
316
|
+
\`\`\`shell
|
|
317
|
+
luca describe fs --ts # approximate TS interface for the fs feature
|
|
318
|
+
luca describe conversation --ts # conversation feature type surface
|
|
319
|
+
luca describe express --ts # express server type shape
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
If a method signature or return type looks wrong, verify with \`luca eval\`:
|
|
323
|
+
|
|
324
|
+
\`\`\`shell
|
|
325
|
+
luca eval "typeof container.feature('fs').readFile"
|
|
326
|
+
luca eval "container.feature('fs').readFile('package.json')"
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
### Bundled Examples and Tutorials
|
|
330
|
+
|
|
331
|
+
The skill directory includes reference material:
|
|
259
332
|
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
|
|
333
|
+
- **\`references/examples/\`** — short, focused example docs for individual features (e.g. \`fs.md\`, \`git.md\`, \`proc.md\`)
|
|
334
|
+
- **\`references/tutorials/\`** — longer-form guides covering the container, helpers, commands, endpoints, state/events, assistants, and more
|
|
335
|
+
|
|
336
|
+
These complement \`luca describe\` — describe gives you the API surface, examples show you patterns in action, and tutorials walk through building things end to end.
|
|
337
|
+
|
|
338
|
+
**Tip:** Runnable markdown is a great artifact to produce when building with luca. \`luca run doc.md\` executes code blocks inside the Luca VM — useful for both testing and documentation. When prototyping a feature or writing a how-to, consider writing it as a markdown file that can be run.
|
|
339
|
+
|
|
340
|
+
### Container Primitives
|
|
341
|
+
|
|
342
|
+
| Primitive | Access | Purpose |
|
|
343
|
+
|-----------|--------|---------|
|
|
344
|
+
| State | \`container.state\`, \`helper.state\` | Observable key-value state on every object |
|
|
345
|
+
| Events | \`container.on()\`, \`helper.on()\` | Event bus on every object |
|
|
346
|
+
| Paths | \`container.paths\` | \`resolve()\`, \`join()\`, \`cwd\` |
|
|
347
|
+
| Utils | \`container.utils\` | \`uuid()\`, \`lodash\`, \`stringUtils\`, \`hashObject()\` |
|
|
348
|
+
| Registries | \`container.features\`, \`.clients\`, \`.servers\` | Discovery and metadata for all helpers |
|
|
263
349
|
`,
|
|
264
350
|
"CLAUDE": `# Luca Project
|
|
265
351
|
|
|
@@ -295,7 +381,7 @@ The \`luca\` binary is available in the path. Key commands:
|
|
|
295
381
|
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.)
|
|
296
382
|
2. **Build** — Run \`luca scaffold <type> --tutorial\` before creating a new helper. It covers the full guide for that type.
|
|
297
383
|
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.
|
|
298
|
-
4. **Reference** —
|
|
384
|
+
4. **Reference** — The skill file (\`.claude/skills/luca-framework/SKILL.md\`) includes a full Framework Index with every feature, client, and server organized by category
|
|
299
385
|
|
|
300
386
|
## Project Structure
|
|
301
387
|
|
|
@@ -1433,9 +1519,50 @@ console.log('Install exit code:', result.exitCode)
|
|
|
1433
1519
|
|
|
1434
1520
|
When no \`installCommand\` is provided, the feature infers the correct command from the detected environment type (e.g., \`uv sync\` for uv, \`pip install -e .\` for venv).
|
|
1435
1521
|
|
|
1522
|
+
## Persistent Sessions
|
|
1523
|
+
|
|
1524
|
+
Start a long-lived Python process where state persists across calls. Ideal for working inside real Python codebases, data analysis, and anything with expensive setup.
|
|
1525
|
+
|
|
1526
|
+
\`\`\`ts skip
|
|
1527
|
+
const python = container.feature('python', { dir: '/path/to/python-project' })
|
|
1528
|
+
await python.enable()
|
|
1529
|
+
await python.startSession()
|
|
1530
|
+
|
|
1531
|
+
// State persists across calls
|
|
1532
|
+
await python.run('x = 42')
|
|
1533
|
+
const result = await python.run('print(x * 2)')
|
|
1534
|
+
console.log('stdout:', result.stdout) // '84\\n'
|
|
1535
|
+
|
|
1536
|
+
// Evaluate expressions and get values back
|
|
1537
|
+
const val = await python.eval('x + 1')
|
|
1538
|
+
console.log('eval:', val) // 43
|
|
1539
|
+
|
|
1540
|
+
// Import project modules (sys.path is set up automatically)
|
|
1541
|
+
await python.importModule('json')
|
|
1542
|
+
const encoded = await python.call('json.dumps', [{ key: 'value' }], { indent: 2 })
|
|
1543
|
+
console.log('call:', encoded)
|
|
1544
|
+
|
|
1545
|
+
// Inspect the namespace
|
|
1546
|
+
const locals = await python.getLocals()
|
|
1547
|
+
console.log('locals:', Object.keys(locals))
|
|
1548
|
+
|
|
1549
|
+
// Errors don't crash the session
|
|
1550
|
+
const bad = await python.run('raise ValueError("oops")')
|
|
1551
|
+
console.log('error:', bad.error) // 'oops'
|
|
1552
|
+
|
|
1553
|
+
// Still alive
|
|
1554
|
+
const check = await python.run('print("still here")')
|
|
1555
|
+
console.log(check.stdout) // 'still here\\n'
|
|
1556
|
+
|
|
1557
|
+
// Clean up
|
|
1558
|
+
await python.stopSession()
|
|
1559
|
+
\`\`\`
|
|
1560
|
+
|
|
1561
|
+
See the [Python Sessions tutorial](../tutorials/19-python-sessions.md) for real-world patterns (data pipelines, Django, ML models).
|
|
1562
|
+
|
|
1436
1563
|
## Summary
|
|
1437
1564
|
|
|
1438
|
-
The \`python\` feature bridges Luca and Python by auto-detecting environments, managing dependencies, and providing
|
|
1565
|
+
The \`python\` feature bridges Luca and Python by auto-detecting environments, managing dependencies, and providing both stateless execution and persistent sessions. It supports uv, conda, venv, and system Python installations.
|
|
1439
1566
|
`,
|
|
1440
1567
|
"yaml.md": `---
|
|
1441
1568
|
title: "YAML"
|
|
@@ -2003,6 +2130,150 @@ Supports pagination via \`pageToken\`, ordering by \`startTime\` or \`updated\`,
|
|
|
2003
2130
|
## Summary
|
|
2004
2131
|
|
|
2005
2132
|
The \`googleCalendar\` feature provides read access to Google Calendar events. Use the convenience methods \`getToday()\` and \`getUpcoming()\` for quick lookups, \`searchEvents()\` for text search, or \`listEvents()\` for full query control. Authentication is handled by \`googleAuth\`. Key methods: \`listCalendars()\`, \`getToday()\`, \`getUpcoming()\`, \`searchEvents()\`, \`listEvents()\`.
|
|
2133
|
+
`,
|
|
2134
|
+
"feature-as-tool-provider.md": `---
|
|
2135
|
+
title: "Features as Tool Providers for Assistants"
|
|
2136
|
+
tags: [feature, tools, assistant, composition, use, setupToolsConsumer]
|
|
2137
|
+
lastTested: null
|
|
2138
|
+
lastTestPassed: null
|
|
2139
|
+
---
|
|
2140
|
+
|
|
2141
|
+
# Features as Tool Providers for Assistants
|
|
2142
|
+
|
|
2143
|
+
Any feature can expose tools that assistants pick up via \`assistant.use(feature)\`. This is how you compose lower-level container capabilities into an assistant-ready tool surface. The built-in \`fileTools\` feature is the canonical example — it wraps \`fs\` and \`grep\` into a focused set of tools modeled on what coding assistants need.
|
|
2144
|
+
|
|
2145
|
+
## The Pattern
|
|
2146
|
+
|
|
2147
|
+
A feature becomes a tool provider by defining three things:
|
|
2148
|
+
|
|
2149
|
+
1. **\`static tools\`** — a record mapping tool names to Zod schemas with descriptions
|
|
2150
|
+
2. **Matching methods** — instance methods whose names match the keys in \`static tools\`
|
|
2151
|
+
3. **\`setupToolsConsumer()\`** (optional) — a hook that runs when an assistant calls \`use()\`, perfect for injecting system prompt guidance
|
|
2152
|
+
|
|
2153
|
+
When an assistant calls \`assistant.use(feature)\`, the framework:
|
|
2154
|
+
- Reads \`static tools\` to register each tool with its schema
|
|
2155
|
+
- Routes tool calls to the matching instance methods
|
|
2156
|
+
- Calls \`setupToolsConsumer()\` so the feature can configure the assistant (e.g. add system prompt extensions)
|
|
2157
|
+
|
|
2158
|
+
## Anatomy of fileTools
|
|
2159
|
+
|
|
2160
|
+
Here's the structure of the built-in \`fileTools\` feature (simplified for clarity):
|
|
2161
|
+
|
|
2162
|
+
\`\`\`ts
|
|
2163
|
+
import { z } from 'zod'
|
|
2164
|
+
import { Feature } from '@soederpop/luca/feature'
|
|
2165
|
+
|
|
2166
|
+
export class FileTools extends Feature {
|
|
2167
|
+
static { Feature.register(this, 'fileTools') }
|
|
2168
|
+
|
|
2169
|
+
// ── 1. Declare tools with Zod schemas ──────────────────────────
|
|
2170
|
+
static tools = {
|
|
2171
|
+
readFile: {
|
|
2172
|
+
description: 'Read the contents of a file.',
|
|
2173
|
+
schema: z.object({
|
|
2174
|
+
path: z.string().describe('File path relative to the project root'),
|
|
2175
|
+
offset: z.number().optional().describe('Line number to start reading from'),
|
|
2176
|
+
limit: z.number().optional().describe('Maximum number of lines to read'),
|
|
2177
|
+
}),
|
|
2178
|
+
},
|
|
2179
|
+
searchFiles: {
|
|
2180
|
+
description: 'Search file contents for a pattern using ripgrep.',
|
|
2181
|
+
schema: z.object({
|
|
2182
|
+
pattern: z.string().describe('Search pattern (regex supported)'),
|
|
2183
|
+
path: z.string().optional().describe('Directory to search in'),
|
|
2184
|
+
include: z.string().optional().describe('Glob pattern to filter files'),
|
|
2185
|
+
}),
|
|
2186
|
+
},
|
|
2187
|
+
editFile: {
|
|
2188
|
+
description: 'Replace an exact string match in a file.',
|
|
2189
|
+
schema: z.object({
|
|
2190
|
+
path: z.string().describe('File path relative to the project root'),
|
|
2191
|
+
oldString: z.string().describe('The exact text to find and replace'),
|
|
2192
|
+
newString: z.string().describe('The replacement text'),
|
|
2193
|
+
}),
|
|
2194
|
+
},
|
|
2195
|
+
// ... more tools
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// ── 2. Implement each tool as an instance method ───────────────
|
|
2199
|
+
// Method names must match the keys in static tools exactly.
|
|
2200
|
+
// Each receives the parsed args object and returns a string.
|
|
2201
|
+
|
|
2202
|
+
async readFile(args: { path: string; offset?: number; limit?: number }) {
|
|
2203
|
+
const fs = this.container.feature('fs')
|
|
2204
|
+
const content = await fs.readFileAsync(args.path)
|
|
2205
|
+
// ... handle offset/limit
|
|
2206
|
+
return content
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
async searchFiles(args: { pattern: string; path?: string; include?: string }) {
|
|
2210
|
+
const grep = this.container.feature('grep')
|
|
2211
|
+
const results = await grep.search({ pattern: args.pattern, path: args.path, include: args.include })
|
|
2212
|
+
return JSON.stringify(results.map(r => ({ file: r.file, line: r.line, content: r.content })))
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
async editFile(args: { path: string; oldString: string; newString: string }) {
|
|
2216
|
+
const fs = this.container.feature('fs')
|
|
2217
|
+
const content = await fs.readFileAsync(args.path)
|
|
2218
|
+
const updated = content.replace(args.oldString, args.newString)
|
|
2219
|
+
await fs.writeFileAsync(args.path, updated)
|
|
2220
|
+
return \`Edited \${args.path}\`
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// ── 3. Configure the assistant when it calls use() ─────────────
|
|
2224
|
+
override setupToolsConsumer(consumer) {
|
|
2225
|
+
// If the consumer is an assistant, inject guidance into its system prompt
|
|
2226
|
+
if (typeof consumer.addSystemPromptExtension === 'function') {
|
|
2227
|
+
consumer.addSystemPromptExtension('fileTools', [
|
|
2228
|
+
'## File Tools',
|
|
2229
|
+
'- All file paths are relative to the project root unless they start with /',
|
|
2230
|
+
'- Use searchFiles to understand code before modifying it',
|
|
2231
|
+
'- Use editFile for surgical changes — prefer it over writeFile',
|
|
2232
|
+
].join('\\n'))
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
\`\`\`
|
|
2237
|
+
|
|
2238
|
+
## Using It
|
|
2239
|
+
|
|
2240
|
+
\`\`\`ts
|
|
2241
|
+
const assistant = container.feature('assistant', {
|
|
2242
|
+
systemPrompt: 'You are a coding assistant.',
|
|
2243
|
+
model: 'gpt-4.1-mini',
|
|
2244
|
+
})
|
|
2245
|
+
|
|
2246
|
+
const fileTools = container.feature('fileTools')
|
|
2247
|
+
assistant.use(fileTools)
|
|
2248
|
+
await assistant.start()
|
|
2249
|
+
|
|
2250
|
+
// The assistant now has readFile, searchFiles, editFile, etc.
|
|
2251
|
+
// and its system prompt includes the fileTools guidance.
|
|
2252
|
+
console.log(Object.keys(assistant.tools))
|
|
2253
|
+
\`\`\`
|
|
2254
|
+
|
|
2255
|
+
### Selective tool registration
|
|
2256
|
+
|
|
2257
|
+
You can expose only a subset of tools:
|
|
2258
|
+
|
|
2259
|
+
\`\`\`ts
|
|
2260
|
+
assistant.use(fileTools.toTools({ only: ['readFile', 'searchFiles', 'listDirectory'] }))
|
|
2261
|
+
\`\`\`
|
|
2262
|
+
|
|
2263
|
+
## Why This Pattern Matters
|
|
2264
|
+
|
|
2265
|
+
This is how features compose for AI. Instead of the assistant importing \`fs\` and \`grep\` directly:
|
|
2266
|
+
|
|
2267
|
+
- The **feature** owns the tool surface — schemas, descriptions, and implementations in one place
|
|
2268
|
+
- The **assistant** gets a curated interface, not raw container access
|
|
2269
|
+
- **\`setupToolsConsumer()\`** lets the feature teach the assistant how to use the tools well
|
|
2270
|
+
- **\`toTools({ only })\`** lets you scope down what the assistant can do
|
|
2271
|
+
|
|
2272
|
+
Any feature you build can follow this same pattern. Define \`static tools\`, implement matching methods, optionally override \`setupToolsConsumer()\`, and assistants can \`use()\` it.
|
|
2273
|
+
|
|
2274
|
+
## Summary
|
|
2275
|
+
|
|
2276
|
+
Features are the natural place to package tools for assistants. The \`static tools\` record declares the schema, instance methods implement the logic, and \`setupToolsConsumer()\` wires up assistant-specific configuration like system prompt extensions. This keeps tool definitions, implementations, and assistant guidance co-located in a single feature class.
|
|
2006
2277
|
`,
|
|
2007
2278
|
"content-db.md": `---
|
|
2008
2279
|
title: "Content Database"
|
|
@@ -5175,6 +5446,11 @@ Then anyone (human or AI) can discover your feature:
|
|
|
5175
5446
|
\`\`\`typescript
|
|
5176
5447
|
container.features.describe('sessionManager')
|
|
5177
5448
|
// Returns the full markdown documentation extracted from your JSDoc
|
|
5449
|
+
|
|
5450
|
+
// Quick discovery — list available methods and getters
|
|
5451
|
+
const session = container.feature('sessionManager')
|
|
5452
|
+
session.$methods // => ['createSession', ...]
|
|
5453
|
+
session.$getters // => ['activeCount', ...]
|
|
5178
5454
|
\`\`\`
|
|
5179
5455
|
|
|
5180
5456
|
## Best Practices
|
|
@@ -5205,11 +5481,11 @@ Introspection serves two audiences:
|
|
|
5205
5481
|
|
|
5206
5482
|
\`\`\`typescript
|
|
5207
5483
|
// Structured data about the entire container
|
|
5208
|
-
const info = container.
|
|
5484
|
+
const info = container.introspect()
|
|
5209
5485
|
// Returns: registries, enabled features, state schema, available helpers
|
|
5210
5486
|
|
|
5211
5487
|
// Human-readable markdown
|
|
5212
|
-
const docs = container.
|
|
5488
|
+
const docs = container.introspectAsText()
|
|
5213
5489
|
\`\`\`
|
|
5214
5490
|
|
|
5215
5491
|
## Registry-Level Discovery
|
|
@@ -5260,6 +5536,16 @@ const info = fs.introspect()
|
|
|
5260
5536
|
const docs = fs.introspectAsText()
|
|
5261
5537
|
\`\`\`
|
|
5262
5538
|
|
|
5539
|
+
### Quick Discovery with $getters and $methods
|
|
5540
|
+
|
|
5541
|
+
Every helper exposes \`$getters\` and \`$methods\` — string arrays listing what's available on the instance. Useful for quick exploration without parsing the full introspection object:
|
|
5542
|
+
|
|
5543
|
+
\`\`\`typescript
|
|
5544
|
+
const fs = container.feature('fs')
|
|
5545
|
+
fs.$methods // => ['readFile', 'writeFile', 'walk', 'readdir', ...]
|
|
5546
|
+
fs.$getters // => ['cwd', 'sep', ...]
|
|
5547
|
+
\`\`\`
|
|
5548
|
+
|
|
5263
5549
|
### What's in the Introspection Data?
|
|
5264
5550
|
|
|
5265
5551
|
- **Class name** and description (from JSDoc)
|
|
@@ -5638,6 +5924,408 @@ export async function post(params: any, ctx: EndpointContext) {
|
|
|
5638
5924
|
response.end()
|
|
5639
5925
|
}
|
|
5640
5926
|
\`\`\`
|
|
5927
|
+
`,
|
|
5928
|
+
"19-python-sessions.md": `---
|
|
5929
|
+
title: Working with Python Projects
|
|
5930
|
+
tags: [python, sessions, persistent, bridge, codebase, interop, data-science]
|
|
5931
|
+
---
|
|
5932
|
+
|
|
5933
|
+
# Working with Python Projects
|
|
5934
|
+
|
|
5935
|
+
Luca's \`python\` feature has two modes: **stateless** execution (fire-and-forget, one process per call) and **persistent sessions** (a long-lived Python process that maintains state across calls). This tutorial focuses on sessions — the mode that lets you actually work inside a Python codebase.
|
|
5936
|
+
|
|
5937
|
+
## When to Use Sessions
|
|
5938
|
+
|
|
5939
|
+
Stateless \`execute()\` is fine for one-off scripts. But if you need any of these, you want a session:
|
|
5940
|
+
|
|
5941
|
+
- **Imports that persist** — load \`pandas\` once, use it across many calls
|
|
5942
|
+
- **State that builds up** — query a database, filter results, then export
|
|
5943
|
+
- **Working inside a real project** — import your own modules, call your own functions
|
|
5944
|
+
- **Expensive setup** — ML model loading, database connections, API client initialization
|
|
5945
|
+
|
|
5946
|
+
## Quick Start
|
|
5947
|
+
|
|
5948
|
+
\`\`\`ts skip
|
|
5949
|
+
const python = container.feature('python', { dir: '/path/to/my-python-project' })
|
|
5950
|
+
await python.enable()
|
|
5951
|
+
await python.startSession()
|
|
5952
|
+
|
|
5953
|
+
// Everything below runs in the same Python process.
|
|
5954
|
+
// Variables, imports, and state persist across calls.
|
|
5955
|
+
|
|
5956
|
+
await python.run('import pandas as pd')
|
|
5957
|
+
await python.run('df = pd.read_csv("data/sales.csv")')
|
|
5958
|
+
|
|
5959
|
+
const result = await python.run('print(df.shape)')
|
|
5960
|
+
console.log(result.stdout) // '(1000, 12)\\n'
|
|
5961
|
+
|
|
5962
|
+
const total = await python.eval('df["revenue"].sum()')
|
|
5963
|
+
console.log('Total revenue:', total)
|
|
5964
|
+
|
|
5965
|
+
await python.stopSession()
|
|
5966
|
+
\`\`\`
|
|
5967
|
+
|
|
5968
|
+
## Project Directory
|
|
5969
|
+
|
|
5970
|
+
The \`dir\` option tells Luca where the Python project lives. This determines:
|
|
5971
|
+
|
|
5972
|
+
1. **sys.path** — the bridge adds the project root (and \`src/\`, \`lib/\` if they exist) so your imports work
|
|
5973
|
+
2. **Environment detection** — Luca looks for \`uv.lock\`, \`pyproject.toml\`, \`venv/\`, etc. in this directory
|
|
5974
|
+
3. **Working directory** — the bridge process runs with \`cwd\` set to this path
|
|
5975
|
+
|
|
5976
|
+
\`\`\`ts skip
|
|
5977
|
+
// Explicit project directory
|
|
5978
|
+
const python = container.feature('python', { dir: '/Users/me/projects/my-api' })
|
|
5979
|
+
|
|
5980
|
+
// Or defaults to wherever luca was invoked from
|
|
5981
|
+
const python = container.feature('python')
|
|
5982
|
+
\`\`\`
|
|
5983
|
+
|
|
5984
|
+
If your project uses a \`src/\` layout (common in modern Python), the bridge automatically adds it to \`sys.path\`:
|
|
5985
|
+
|
|
5986
|
+
\`\`\`
|
|
5987
|
+
my-project/
|
|
5988
|
+
src/
|
|
5989
|
+
myapp/
|
|
5990
|
+
__init__.py
|
|
5991
|
+
models.py
|
|
5992
|
+
pyproject.toml
|
|
5993
|
+
\`\`\`
|
|
5994
|
+
|
|
5995
|
+
\`\`\`ts skip
|
|
5996
|
+
await python.startSession()
|
|
5997
|
+
// This works because src/ was added to sys.path
|
|
5998
|
+
await python.importModule('myapp.models', 'models')
|
|
5999
|
+
\`\`\`
|
|
6000
|
+
|
|
6001
|
+
## Session Lifecycle
|
|
6002
|
+
|
|
6003
|
+
### Starting
|
|
6004
|
+
|
|
6005
|
+
\`startSession()\` spawns a Python bridge process that talks to Luca over stdin/stdout using a JSON-line protocol. The bridge sets up \`sys.path\` and signals when it's ready.
|
|
6006
|
+
|
|
6007
|
+
\`\`\`ts skip
|
|
6008
|
+
await python.enable()
|
|
6009
|
+
await python.startSession()
|
|
6010
|
+
|
|
6011
|
+
console.log(python.state.get('sessionActive')) // true
|
|
6012
|
+
console.log(python.state.get('sessionId')) // uuid
|
|
6013
|
+
\`\`\`
|
|
6014
|
+
|
|
6015
|
+
### Stopping
|
|
6016
|
+
|
|
6017
|
+
\`stopSession()\` kills the bridge process and cleans up. Any pending requests are rejected.
|
|
6018
|
+
|
|
6019
|
+
\`\`\`ts skip
|
|
6020
|
+
await python.stopSession()
|
|
6021
|
+
console.log(python.state.get('sessionActive')) // false
|
|
6022
|
+
\`\`\`
|
|
6023
|
+
|
|
6024
|
+
### Crash Recovery
|
|
6025
|
+
|
|
6026
|
+
If the Python process dies unexpectedly (segfault, killed externally), the feature:
|
|
6027
|
+
- Sets \`sessionActive\` to \`false\`
|
|
6028
|
+
- Rejects all pending requests
|
|
6029
|
+
- Emits a \`sessionError\` event
|
|
6030
|
+
|
|
6031
|
+
\`\`\`ts skip
|
|
6032
|
+
python.on('sessionError', ({ error, sessionId }) => {
|
|
6033
|
+
console.error('Python session error:', error)
|
|
6034
|
+
// You could restart: await python.startSession()
|
|
6035
|
+
})
|
|
6036
|
+
\`\`\`
|
|
6037
|
+
|
|
6038
|
+
## The Session API
|
|
6039
|
+
|
|
6040
|
+
### run(code, variables?)
|
|
6041
|
+
|
|
6042
|
+
Execute Python code in the persistent namespace. This is the workhorse method.
|
|
6043
|
+
|
|
6044
|
+
\`\`\`ts skip
|
|
6045
|
+
// Simple execution
|
|
6046
|
+
const result = await python.run('print("hello")')
|
|
6047
|
+
// result.ok === true
|
|
6048
|
+
// result.stdout === 'hello\\n'
|
|
6049
|
+
|
|
6050
|
+
// With variable injection
|
|
6051
|
+
const result = await python.run('print(f"Processing {count} items")', { count: 42 })
|
|
6052
|
+
|
|
6053
|
+
// Errors don't crash the session
|
|
6054
|
+
const bad = await python.run('raise ValueError("oops")')
|
|
6055
|
+
// bad.ok === false
|
|
6056
|
+
// bad.error === 'oops'
|
|
6057
|
+
// bad.traceback === 'Traceback (most recent call last):\\n...'
|
|
6058
|
+
|
|
6059
|
+
// Session still alive after error
|
|
6060
|
+
const good = await python.run('print("still here")')
|
|
6061
|
+
// good.ok === true
|
|
6062
|
+
\`\`\`
|
|
6063
|
+
|
|
6064
|
+
### eval(expression)
|
|
6065
|
+
|
|
6066
|
+
Evaluate a Python expression and return its value to JavaScript.
|
|
6067
|
+
|
|
6068
|
+
\`\`\`ts skip
|
|
6069
|
+
await python.run('x = [1, 2, 3]')
|
|
6070
|
+
const length = await python.eval('len(x)') // 3
|
|
6071
|
+
const doubled = await python.eval('[i*2 for i in x]') // [2, 4, 6]
|
|
6072
|
+
\`\`\`
|
|
6073
|
+
|
|
6074
|
+
Values are JSON-serialized. Complex types that can't be serialized come back as their \`repr()\` string.
|
|
6075
|
+
|
|
6076
|
+
### importModule(name, alias?)
|
|
6077
|
+
|
|
6078
|
+
Import a module into the session namespace. The alias defaults to the last segment of the module path.
|
|
6079
|
+
|
|
6080
|
+
\`\`\`ts skip
|
|
6081
|
+
await python.importModule('json') // import json
|
|
6082
|
+
await python.importModule('myapp.models', 'models') // import myapp.models as models
|
|
6083
|
+
await python.importModule('os.path') // import os.path (available as "path")
|
|
6084
|
+
\`\`\`
|
|
6085
|
+
|
|
6086
|
+
### call(funcPath, args?, kwargs?)
|
|
6087
|
+
|
|
6088
|
+
Call a function by its dotted path in the namespace.
|
|
6089
|
+
|
|
6090
|
+
\`\`\`ts skip
|
|
6091
|
+
await python.importModule('json')
|
|
6092
|
+
const encoded = await python.call('json.dumps', [{ a: 1 }], { indent: 2 })
|
|
6093
|
+
// '{\\n "a": 1\\n}'
|
|
6094
|
+
|
|
6095
|
+
// Works with your own functions too
|
|
6096
|
+
await python.run('def add(a, b): return a + b')
|
|
6097
|
+
const sum = await python.call('add', [3, 4]) // 7
|
|
6098
|
+
\`\`\`
|
|
6099
|
+
|
|
6100
|
+
### getLocals()
|
|
6101
|
+
|
|
6102
|
+
Inspect everything in the session namespace.
|
|
6103
|
+
|
|
6104
|
+
\`\`\`ts skip
|
|
6105
|
+
await python.run('x = 42')
|
|
6106
|
+
await python.importModule('json')
|
|
6107
|
+
const locals = await python.getLocals()
|
|
6108
|
+
// { x: 42, json: '<module ...>' }
|
|
6109
|
+
\`\`\`
|
|
6110
|
+
|
|
6111
|
+
### resetSession()
|
|
6112
|
+
|
|
6113
|
+
Clear all variables and imports without restarting the process.
|
|
6114
|
+
|
|
6115
|
+
\`\`\`ts skip
|
|
6116
|
+
await python.run('big_model = load_model()')
|
|
6117
|
+
await python.resetSession()
|
|
6118
|
+
// big_model is gone, but the session process is still running
|
|
6119
|
+
\`\`\`
|
|
6120
|
+
|
|
6121
|
+
## Real-World Patterns
|
|
6122
|
+
|
|
6123
|
+
### Data Analysis Pipeline
|
|
6124
|
+
|
|
6125
|
+
\`\`\`ts skip
|
|
6126
|
+
const python = container.feature('python', { dir: '/path/to/analytics' })
|
|
6127
|
+
await python.enable()
|
|
6128
|
+
await python.startSession()
|
|
6129
|
+
|
|
6130
|
+
// Setup
|
|
6131
|
+
await python.run('import pandas as pd')
|
|
6132
|
+
await python.run('import matplotlib')
|
|
6133
|
+
await python.run('matplotlib.use("Agg")') // headless
|
|
6134
|
+
await python.run('import matplotlib.pyplot as plt')
|
|
6135
|
+
|
|
6136
|
+
// Load and analyze
|
|
6137
|
+
await python.run('df = pd.read_csv("data/events.csv")')
|
|
6138
|
+
const shape = await python.eval('list(df.shape)')
|
|
6139
|
+
console.log(\`Loaded \${shape[0]} rows, \${shape[1]} columns\`)
|
|
6140
|
+
|
|
6141
|
+
const columns = await python.eval('list(df.columns)')
|
|
6142
|
+
console.log('Columns:', columns)
|
|
6143
|
+
|
|
6144
|
+
// Filter and aggregate
|
|
6145
|
+
await python.run(\`
|
|
6146
|
+
filtered = df[df["status"] == "completed"]
|
|
6147
|
+
summary = filtered.groupby("category")["amount"].agg(["sum", "mean", "count"])
|
|
6148
|
+
\`)
|
|
6149
|
+
|
|
6150
|
+
const summary = await python.eval('summary.to_dict()')
|
|
6151
|
+
console.log('Summary:', summary)
|
|
6152
|
+
|
|
6153
|
+
// Generate a chart
|
|
6154
|
+
await python.run(\`
|
|
6155
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
6156
|
+
summary["sum"].plot(kind="bar", ax=ax)
|
|
6157
|
+
ax.set_title("Revenue by Category")
|
|
6158
|
+
fig.savefig("output/revenue.png", dpi=150, bbox_inches="tight")
|
|
6159
|
+
plt.close(fig)
|
|
6160
|
+
\`)
|
|
6161
|
+
|
|
6162
|
+
await python.stopSession()
|
|
6163
|
+
\`\`\`
|
|
6164
|
+
|
|
6165
|
+
### Working with a Django Project
|
|
6166
|
+
|
|
6167
|
+
\`\`\`ts skip
|
|
6168
|
+
const python = container.feature('python', { dir: '/path/to/django-project' })
|
|
6169
|
+
await python.enable()
|
|
6170
|
+
await python.startSession()
|
|
6171
|
+
|
|
6172
|
+
// Django requires this before you can import models
|
|
6173
|
+
await python.run(\`
|
|
6174
|
+
import os
|
|
6175
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
|
|
6176
|
+
|
|
6177
|
+
import django
|
|
6178
|
+
django.setup()
|
|
6179
|
+
\`)
|
|
6180
|
+
|
|
6181
|
+
// Now you can work with the ORM
|
|
6182
|
+
await python.run('from myapp.models import User, Order')
|
|
6183
|
+
|
|
6184
|
+
const userCount = await python.eval('User.objects.count()')
|
|
6185
|
+
console.log(\`\${userCount} users in database\`)
|
|
6186
|
+
|
|
6187
|
+
const recentOrders = await python.eval(\`
|
|
6188
|
+
list(Order.objects.filter(status="pending").values("id", "total", "created_at")[:10])
|
|
6189
|
+
\`)
|
|
6190
|
+
console.log('Recent pending orders:', recentOrders)
|
|
6191
|
+
|
|
6192
|
+
await python.stopSession()
|
|
6193
|
+
\`\`\`
|
|
6194
|
+
|
|
6195
|
+
### ML Model Interaction
|
|
6196
|
+
|
|
6197
|
+
\`\`\`ts skip
|
|
6198
|
+
const python = container.feature('python', { dir: '/path/to/ml-project' })
|
|
6199
|
+
await python.enable()
|
|
6200
|
+
await python.startSession()
|
|
6201
|
+
|
|
6202
|
+
// Expensive setup — only happens once
|
|
6203
|
+
await python.run(\`
|
|
6204
|
+
from transformers import pipeline
|
|
6205
|
+
classifier = pipeline("sentiment-analysis")
|
|
6206
|
+
print("Model loaded")
|
|
6207
|
+
\`)
|
|
6208
|
+
|
|
6209
|
+
// Now you can call it cheaply many times
|
|
6210
|
+
async function classify(text: string) {
|
|
6211
|
+
return python.call('classifier', [text])
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
const results = await Promise.all([
|
|
6215
|
+
classify('I love this product!'),
|
|
6216
|
+
classify('Terrible experience.'),
|
|
6217
|
+
classify('It was okay, nothing special.'),
|
|
6218
|
+
])
|
|
6219
|
+
|
|
6220
|
+
console.log(results)
|
|
6221
|
+
// [
|
|
6222
|
+
// [{ label: 'POSITIVE', score: 0.9998 }],
|
|
6223
|
+
// [{ label: 'NEGATIVE', score: 0.9994 }],
|
|
6224
|
+
// [{ label: 'NEGATIVE', score: 0.7231 }],
|
|
6225
|
+
// ]
|
|
6226
|
+
|
|
6227
|
+
await python.stopSession()
|
|
6228
|
+
\`\`\`
|
|
6229
|
+
|
|
6230
|
+
### Luca Command That Uses Python
|
|
6231
|
+
|
|
6232
|
+
\`\`\`ts skip
|
|
6233
|
+
// commands/analyze.ts
|
|
6234
|
+
import { z } from 'zod'
|
|
6235
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
6236
|
+
import { CommandOptionsSchema } from '@soederpop/luca/schemas'
|
|
6237
|
+
|
|
6238
|
+
export const positionals = ['target']
|
|
6239
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
6240
|
+
target: z.string().describe('Path to CSV file to analyze'),
|
|
6241
|
+
})
|
|
6242
|
+
|
|
6243
|
+
async function handler(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
6244
|
+
const container = context.container as any
|
|
6245
|
+
const python = container.feature('python')
|
|
6246
|
+
await python.enable()
|
|
6247
|
+
await python.startSession()
|
|
6248
|
+
|
|
6249
|
+
try {
|
|
6250
|
+
await python.run('import pandas as pd')
|
|
6251
|
+
await python.run(\`df = pd.read_csv("\${options.target}")\`)
|
|
6252
|
+
|
|
6253
|
+
const shape = await python.eval('list(df.shape)')
|
|
6254
|
+
const dtypes = await python.eval('dict(df.dtypes.astype(str))')
|
|
6255
|
+
const nulls = await python.eval('dict(df.isnull().sum())')
|
|
6256
|
+
|
|
6257
|
+
console.log(\`Rows: \${shape[0]}, Columns: \${shape[1]}\`)
|
|
6258
|
+
console.log('Column types:', dtypes)
|
|
6259
|
+
console.log('Null counts:', nulls)
|
|
6260
|
+
} finally {
|
|
6261
|
+
await python.stopSession()
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
export default {
|
|
6266
|
+
description: 'Analyze a CSV file using pandas',
|
|
6267
|
+
argsSchema,
|
|
6268
|
+
handler,
|
|
6269
|
+
}
|
|
6270
|
+
\`\`\`
|
|
6271
|
+
|
|
6272
|
+
\`\`\`bash
|
|
6273
|
+
luca analyze data/sales.csv
|
|
6274
|
+
\`\`\`
|
|
6275
|
+
|
|
6276
|
+
## Stateless vs. Session: Choosing the Right Mode
|
|
6277
|
+
|
|
6278
|
+
| | \`execute()\` (stateless) | \`run()\` (session) |
|
|
6279
|
+
|---|---|---|
|
|
6280
|
+
| Process | Fresh per call | Shared, long-lived |
|
|
6281
|
+
| State | None — each call starts clean | Persists across calls |
|
|
6282
|
+
| Imports | Re-imported every time | Imported once, reused |
|
|
6283
|
+
| Startup cost | ~50-200ms per call | ~200ms once, then ~1ms per call |
|
|
6284
|
+
| Use case | One-off scripts, simple eval | Real projects, data pipelines, REPL-like |
|
|
6285
|
+
| Error isolation | Perfect — crash is contained | Errors caught, session survives |
|
|
6286
|
+
|
|
6287
|
+
Both modes use the same environment detection (uv, conda, venv, system) and respect the same \`dir\` and \`pythonPath\` options.
|
|
6288
|
+
|
|
6289
|
+
## Environment Detection
|
|
6290
|
+
|
|
6291
|
+
The feature detects Python environments in this order:
|
|
6292
|
+
|
|
6293
|
+
1. **Explicit** — \`pythonPath\` option overrides everything
|
|
6294
|
+
2. **uv** — \`uv.lock\` or \`pyproject.toml\` present, \`uv run python\` works
|
|
6295
|
+
3. **conda** — \`environment.yml\` or \`conda.yml\` present
|
|
6296
|
+
4. **venv** — \`venv/\` or \`.venv/\` directory with a Python binary inside
|
|
6297
|
+
5. **system** — falls back to \`python3\` or \`python\` on PATH
|
|
6298
|
+
|
|
6299
|
+
\`\`\`ts skip
|
|
6300
|
+
const python = container.feature('python', { dir: '/path/to/project' })
|
|
6301
|
+
await python.enable()
|
|
6302
|
+
console.log(python.environmentType) // 'uv' | 'conda' | 'venv' | 'system'
|
|
6303
|
+
console.log(python.pythonPath) // e.g. '/Users/me/.local/bin/uv run python'
|
|
6304
|
+
\`\`\`
|
|
6305
|
+
|
|
6306
|
+
## Events
|
|
6307
|
+
|
|
6308
|
+
The session emits events you can listen to for monitoring and debugging:
|
|
6309
|
+
|
|
6310
|
+
\`\`\`ts skip
|
|
6311
|
+
python.on('sessionStarted', ({ sessionId }) => {
|
|
6312
|
+
console.log('Session started:', sessionId)
|
|
6313
|
+
})
|
|
6314
|
+
|
|
6315
|
+
python.on('sessionStopped', ({ sessionId }) => {
|
|
6316
|
+
console.log('Session stopped:', sessionId)
|
|
6317
|
+
})
|
|
6318
|
+
|
|
6319
|
+
python.on('sessionError', ({ error, sessionId }) => {
|
|
6320
|
+
console.error('Session error:', error)
|
|
6321
|
+
})
|
|
6322
|
+
\`\`\`
|
|
6323
|
+
|
|
6324
|
+
## What's Next
|
|
6325
|
+
|
|
6326
|
+
- [Creating Features](./10-creating-features.md) — build your own feature that wraps a Python service
|
|
6327
|
+
- [Commands](./08-commands.md) — create CLI commands that leverage Python
|
|
6328
|
+
- [Servers and Endpoints](./06-servers.md) — expose Python-powered analysis via HTTP
|
|
5641
6329
|
`,
|
|
5642
6330
|
"18-semantic-search.md": `---
|
|
5643
6331
|
title: Semantic Search
|
|
@@ -6432,8 +7120,8 @@ fs.introspectAsText()
|
|
|
6432
7120
|
The container itself is introspectable:
|
|
6433
7121
|
|
|
6434
7122
|
\`\`\`js
|
|
6435
|
-
container.
|
|
6436
|
-
container.
|
|
7123
|
+
container.introspect() // structured object with all registries, state, events
|
|
7124
|
+
container.introspectAsText() // full markdown overview
|
|
6437
7125
|
\`\`\`
|
|
6438
7126
|
|
|
6439
7127
|
## The REPL
|
|
@@ -6468,7 +7156,7 @@ luca eval "grep.search('.', 'TODO')"
|
|
|
6468
7156
|
| Structured introspection? | \`feature.introspect()\` |
|
|
6469
7157
|
| What state does it have? | \`feature.state.current\` |
|
|
6470
7158
|
| What events does it emit? | \`feature.introspect().events\` |
|
|
6471
|
-
| Full container overview? | \`container.
|
|
7159
|
+
| Full container overview? | \`container.introspectAsText()\` |
|
|
6472
7160
|
| CLI docs for a helper? | \`luca describe <name>\` |
|
|
6473
7161
|
|
|
6474
7162
|
## Gotchas
|
|
@@ -7127,10 +7815,10 @@ Discover everything about the container at runtime:
|
|
|
7127
7815
|
|
|
7128
7816
|
\`\`\`typescript
|
|
7129
7817
|
// Structured introspection data
|
|
7130
|
-
const info = container.
|
|
7818
|
+
const info = container.introspect()
|
|
7131
7819
|
|
|
7132
7820
|
// Human-readable markdown
|
|
7133
|
-
const docs = container.
|
|
7821
|
+
const docs = container.introspectAsText()
|
|
7134
7822
|
\`\`\`
|
|
7135
7823
|
|
|
7136
7824
|
This is what makes Luca especially powerful for AI agents -- they can discover the entire API surface at runtime without reading documentation.
|