@soederpop/luca 0.0.32 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +241 -36
  2. package/bun.lock +24 -6
  3. package/commands/build-python-bridge.ts +43 -0
  4. package/docs/README.md +1 -1
  5. package/docs/TABLE-OF-CONTENTS.md +0 -1
  6. package/docs/apis/clients/rest.md +7 -7
  7. package/docs/apis/clients/websocket.md +23 -10
  8. package/docs/apis/features/agi/assistant.md +155 -8
  9. package/docs/apis/features/agi/assistants-manager.md +90 -22
  10. package/docs/apis/features/agi/auto-assistant.md +377 -0
  11. package/docs/apis/features/agi/browser-use.md +802 -0
  12. package/docs/apis/features/agi/claude-code.md +6 -1
  13. package/docs/apis/features/agi/conversation-history.md +7 -6
  14. package/docs/apis/features/agi/conversation.md +111 -38
  15. package/docs/apis/features/agi/docs-reader.md +35 -57
  16. package/docs/apis/features/agi/file-tools.md +163 -0
  17. package/docs/apis/features/agi/openapi.md +2 -2
  18. package/docs/apis/features/agi/skills-library.md +227 -0
  19. package/docs/apis/features/node/content-db.md +125 -4
  20. package/docs/apis/features/node/disk-cache.md +11 -11
  21. package/docs/apis/features/node/downloader.md +1 -1
  22. package/docs/apis/features/node/file-manager.md +15 -15
  23. package/docs/apis/features/node/fs.md +78 -21
  24. package/docs/apis/features/node/git.md +50 -10
  25. package/docs/apis/features/node/google-calendar.md +3 -0
  26. package/docs/apis/features/node/google-docs.md +10 -1
  27. package/docs/apis/features/node/google-drive.md +3 -0
  28. package/docs/apis/features/node/google-mail.md +214 -0
  29. package/docs/apis/features/node/google-sheets.md +3 -0
  30. package/docs/apis/features/node/ink.md +10 -10
  31. package/docs/apis/features/node/ipc-socket.md +83 -93
  32. package/docs/apis/features/node/networking.md +5 -5
  33. package/docs/apis/features/node/os.md +7 -7
  34. package/docs/apis/features/node/package-finder.md +14 -14
  35. package/docs/apis/features/node/proc.md +2 -1
  36. package/docs/apis/features/node/process-manager.md +70 -3
  37. package/docs/apis/features/node/python.md +265 -9
  38. package/docs/apis/features/node/redis.md +380 -0
  39. package/docs/apis/features/node/ui.md +13 -13
  40. package/docs/apis/servers/express.md +35 -7
  41. package/docs/apis/servers/mcp.md +3 -3
  42. package/docs/apis/servers/websocket.md +51 -8
  43. package/docs/bootstrap/CLAUDE.md +1 -1
  44. package/docs/bootstrap/SKILL.md +93 -7
  45. package/docs/examples/feature-as-tool-provider.md +143 -0
  46. package/docs/examples/python.md +42 -1
  47. package/docs/introspection.md +15 -5
  48. package/docs/tutorials/00-bootstrap.md +3 -3
  49. package/docs/tutorials/02-container.md +2 -2
  50. package/docs/tutorials/10-creating-features.md +5 -0
  51. package/docs/tutorials/13-introspection.md +12 -2
  52. package/docs/tutorials/19-python-sessions.md +401 -0
  53. package/package.json +8 -5
  54. package/scripts/examples/using-assistant-with-mcp.ts +2 -7
  55. package/scripts/test-linux-binary.sh +80 -0
  56. package/src/agi/container.server.ts +8 -0
  57. package/src/agi/features/assistant.ts +18 -0
  58. package/src/agi/features/autonomous-assistant.ts +435 -0
  59. package/src/agi/features/conversation.ts +58 -6
  60. package/src/agi/features/file-tools.ts +286 -0
  61. package/src/agi/features/luca-coder.ts +643 -0
  62. package/src/bootstrap/generated.ts +705 -107
  63. package/src/cli/build-info.ts +2 -2
  64. package/src/cli/cli.ts +22 -13
  65. package/src/commands/bootstrap.ts +49 -6
  66. package/src/commands/code.ts +369 -0
  67. package/src/commands/describe.ts +7 -2
  68. package/src/commands/index.ts +1 -0
  69. package/src/commands/sandbox-mcp.ts +7 -7
  70. package/src/commands/save-api-docs.ts +1 -1
  71. package/src/container-describer.ts +4 -4
  72. package/src/container.ts +10 -19
  73. package/src/helper.ts +24 -33
  74. package/src/introspection/generated.agi.ts +3026 -849
  75. package/src/introspection/generated.node.ts +1690 -1012
  76. package/src/introspection/generated.web.ts +15 -57
  77. package/src/node/container.ts +5 -5
  78. package/src/node/features/figlet-fonts.ts +597 -0
  79. package/src/node/features/fs.ts +3 -9
  80. package/src/node/features/helpers.ts +20 -0
  81. package/src/node/features/python.ts +429 -16
  82. package/src/node/features/redis.ts +446 -0
  83. package/src/node/features/ui.ts +4 -11
  84. package/src/python/bridge.py +220 -0
  85. package/src/python/generated.ts +227 -0
  86. package/src/scaffolds/generated.ts +1 -1
  87. package/test/python-session.test.ts +105 -0
  88. package/assistants/lucaExpert/CORE.md +0 -37
  89. package/assistants/lucaExpert/hooks.ts +0 -9
  90. package/assistants/lucaExpert/tools.ts +0 -177
  91. package/docs/examples/port-exposer.md +0 -89
  92. package/src/node/features/port-exposer.ts +0 -351
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bootstrap content
2
- // Generated at: 2026-03-24T09:09:11.574Z
2
+ // Generated at: 2026-03-27T03:29:28.307Z
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. Before reading source files, searching for APIs, or writing any codeask describe. It outputs full documentation for any part of the container: methods, options, events, state, examples.
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.inspectAsText() // full container overview as markdown
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
- ## Reference
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\` | Network utilities, DNS |
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:
332
+
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
259
341
 
260
- - \`references/api-docs/\` full pre-generated API reference for every built-in feature, client, and server
261
- - \`references/examples/\` — runnable example docs for each feature (e.g. \`fs.md\`, \`git.md\`, \`proc.md\`)
262
- - \`references/tutorials/\` step-by-step tutorials covering the container, helpers, commands, endpoints, and more
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** — Browse \`.claude/skills/luca-framework/references/\` for pre-generated API docs, runnable examples, and tutorials
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 inline code execution with variable injection. It supports uv, conda, venv, and system Python installations.
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"
@@ -2413,96 +2684,6 @@ console.log('Done')
2413
2684
  ## Summary
2414
2685
 
2415
2686
  The ask/reply protocol gives you awaitable request/response over WebSocket without leaving the Luca helper API. The client calls \`ask(type, data)\` and gets back a promise. The server's message handler gets \`reply()\` and \`replyError()\` injected on any message that carries a \`requestId\`. The server can also \`ask()\` a specific client. Timeouts, error propagation, and cleanup of pending requests on disconnect are all handled automatically.
2416
- `,
2417
- "port-exposer.md": `---
2418
- title: "Port Exposer"
2419
- tags: [portExposer, ngrok, networking, tunnel]
2420
- lastTested: null
2421
- lastTestPassed: null
2422
- ---
2423
-
2424
- # portExposer
2425
-
2426
- Exposes local HTTP services via ngrok with SSL-enabled public URLs. Useful for development, testing webhooks, and sharing local services with external consumers.
2427
-
2428
- ## Overview
2429
-
2430
- The \`portExposer\` feature creates an ngrok tunnel from a local port to a public HTTPS URL. It supports custom subdomains, regional endpoints, basic auth, and OAuth (features that require a paid ngrok plan). Requires ngrok to be installed or available as a dependency, and optionally an auth token for premium features.
2431
-
2432
- ## Enabling the Feature
2433
-
2434
- \`\`\`ts
2435
- const exposer = container.feature('portExposer', {
2436
- port: 3000,
2437
- enable: true
2438
- })
2439
- console.log('Port Exposer enabled:', exposer.state.get('enabled'))
2440
- \`\`\`
2441
-
2442
- ## Exploring the API
2443
-
2444
- \`\`\`ts
2445
- const docs = container.features.describe('portExposer')
2446
- console.log(docs)
2447
- \`\`\`
2448
-
2449
- ## Checking Connection State
2450
-
2451
- \`\`\`ts
2452
- const exposer = container.feature('portExposer', { port: 3000 })
2453
- console.log('Connected:', exposer.isConnected())
2454
- \`\`\`
2455
-
2456
- ## Exposing a Port
2457
-
2458
- Create a tunnel and get the public URL.
2459
-
2460
- \`\`\`ts skip
2461
- const url = await exposer.expose()
2462
- console.log('Public URL:', url)
2463
- console.log('Connected:', exposer.isConnected())
2464
- \`\`\`
2465
-
2466
- The returned URL is an HTTPS endpoint that forwards traffic to \`localhost:3000\`. The tunnel remains active until \`close()\` is called or the process exits.
2467
-
2468
- ## Getting Connection Info
2469
-
2470
- Retrieve a snapshot of the current tunnel state.
2471
-
2472
- \`\`\`ts skip
2473
- await exposer.expose()
2474
- const info = exposer.getConnectionInfo()
2475
- console.log('Public URL:', info.publicUrl)
2476
- console.log('Local port:', info.localPort)
2477
- console.log('Connected at:', info.connectedAt)
2478
- \`\`\`
2479
-
2480
- ## Reconnecting with New Options
2481
-
2482
- Close the existing tunnel and re-expose with different settings.
2483
-
2484
- \`\`\`ts skip
2485
- const url1 = await exposer.expose()
2486
- console.log('First URL:', url1)
2487
-
2488
- const url2 = await exposer.reconnect({ port: 8080 })
2489
- console.log('New URL (port 8080):', url2)
2490
- \`\`\`
2491
-
2492
- The \`reconnect\` method calls \`close()\` internally, merges the new options, then calls \`expose()\` again.
2493
-
2494
- ## Closing the Tunnel
2495
-
2496
- \`\`\`ts skip
2497
- await exposer.close()
2498
- console.log('Tunnel closed:', !exposer.isConnected())
2499
- \`\`\`
2500
-
2501
- Calling \`close()\` when no tunnel is active is a safe no-op. The \`disable()\` method also closes the tunnel before disabling the feature.
2502
-
2503
- ## Summary
2504
-
2505
- The \`portExposer\` feature wraps ngrok to expose local ports as public HTTPS endpoints. It supports connection lifecycle management, reconnection with new options, and event-driven notifications for tunnel state changes. Requires ngrok to be installed.
2506
2687
  `,
2507
2688
  "secure-shell.md": `---
2508
2689
  title: "Secure Shell"
@@ -5175,6 +5356,11 @@ Then anyone (human or AI) can discover your feature:
5175
5356
  \`\`\`typescript
5176
5357
  container.features.describe('sessionManager')
5177
5358
  // Returns the full markdown documentation extracted from your JSDoc
5359
+
5360
+ // Quick discovery — list available methods and getters
5361
+ const session = container.feature('sessionManager')
5362
+ session.$methods // => ['createSession', ...]
5363
+ session.$getters // => ['activeCount', ...]
5178
5364
  \`\`\`
5179
5365
 
5180
5366
  ## Best Practices
@@ -5205,11 +5391,11 @@ Introspection serves two audiences:
5205
5391
 
5206
5392
  \`\`\`typescript
5207
5393
  // Structured data about the entire container
5208
- const info = container.inspect()
5394
+ const info = container.introspect()
5209
5395
  // Returns: registries, enabled features, state schema, available helpers
5210
5396
 
5211
5397
  // Human-readable markdown
5212
- const docs = container.inspectAsText()
5398
+ const docs = container.introspectAsText()
5213
5399
  \`\`\`
5214
5400
 
5215
5401
  ## Registry-Level Discovery
@@ -5260,6 +5446,16 @@ const info = fs.introspect()
5260
5446
  const docs = fs.introspectAsText()
5261
5447
  \`\`\`
5262
5448
 
5449
+ ### Quick Discovery with $getters and $methods
5450
+
5451
+ 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:
5452
+
5453
+ \`\`\`typescript
5454
+ const fs = container.feature('fs')
5455
+ fs.$methods // => ['readFile', 'writeFile', 'walk', 'readdir', ...]
5456
+ fs.$getters // => ['cwd', 'sep', ...]
5457
+ \`\`\`
5458
+
5263
5459
  ### What's in the Introspection Data?
5264
5460
 
5265
5461
  - **Class name** and description (from JSDoc)
@@ -5638,6 +5834,408 @@ export async function post(params: any, ctx: EndpointContext) {
5638
5834
  response.end()
5639
5835
  }
5640
5836
  \`\`\`
5837
+ `,
5838
+ "19-python-sessions.md": `---
5839
+ title: Working with Python Projects
5840
+ tags: [python, sessions, persistent, bridge, codebase, interop, data-science]
5841
+ ---
5842
+
5843
+ # Working with Python Projects
5844
+
5845
+ 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.
5846
+
5847
+ ## When to Use Sessions
5848
+
5849
+ Stateless \`execute()\` is fine for one-off scripts. But if you need any of these, you want a session:
5850
+
5851
+ - **Imports that persist** — load \`pandas\` once, use it across many calls
5852
+ - **State that builds up** — query a database, filter results, then export
5853
+ - **Working inside a real project** — import your own modules, call your own functions
5854
+ - **Expensive setup** — ML model loading, database connections, API client initialization
5855
+
5856
+ ## Quick Start
5857
+
5858
+ \`\`\`ts skip
5859
+ const python = container.feature('python', { dir: '/path/to/my-python-project' })
5860
+ await python.enable()
5861
+ await python.startSession()
5862
+
5863
+ // Everything below runs in the same Python process.
5864
+ // Variables, imports, and state persist across calls.
5865
+
5866
+ await python.run('import pandas as pd')
5867
+ await python.run('df = pd.read_csv("data/sales.csv")')
5868
+
5869
+ const result = await python.run('print(df.shape)')
5870
+ console.log(result.stdout) // '(1000, 12)\\n'
5871
+
5872
+ const total = await python.eval('df["revenue"].sum()')
5873
+ console.log('Total revenue:', total)
5874
+
5875
+ await python.stopSession()
5876
+ \`\`\`
5877
+
5878
+ ## Project Directory
5879
+
5880
+ The \`dir\` option tells Luca where the Python project lives. This determines:
5881
+
5882
+ 1. **sys.path** — the bridge adds the project root (and \`src/\`, \`lib/\` if they exist) so your imports work
5883
+ 2. **Environment detection** — Luca looks for \`uv.lock\`, \`pyproject.toml\`, \`venv/\`, etc. in this directory
5884
+ 3. **Working directory** — the bridge process runs with \`cwd\` set to this path
5885
+
5886
+ \`\`\`ts skip
5887
+ // Explicit project directory
5888
+ const python = container.feature('python', { dir: '/Users/me/projects/my-api' })
5889
+
5890
+ // Or defaults to wherever luca was invoked from
5891
+ const python = container.feature('python')
5892
+ \`\`\`
5893
+
5894
+ If your project uses a \`src/\` layout (common in modern Python), the bridge automatically adds it to \`sys.path\`:
5895
+
5896
+ \`\`\`
5897
+ my-project/
5898
+ src/
5899
+ myapp/
5900
+ __init__.py
5901
+ models.py
5902
+ pyproject.toml
5903
+ \`\`\`
5904
+
5905
+ \`\`\`ts skip
5906
+ await python.startSession()
5907
+ // This works because src/ was added to sys.path
5908
+ await python.importModule('myapp.models', 'models')
5909
+ \`\`\`
5910
+
5911
+ ## Session Lifecycle
5912
+
5913
+ ### Starting
5914
+
5915
+ \`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.
5916
+
5917
+ \`\`\`ts skip
5918
+ await python.enable()
5919
+ await python.startSession()
5920
+
5921
+ console.log(python.state.get('sessionActive')) // true
5922
+ console.log(python.state.get('sessionId')) // uuid
5923
+ \`\`\`
5924
+
5925
+ ### Stopping
5926
+
5927
+ \`stopSession()\` kills the bridge process and cleans up. Any pending requests are rejected.
5928
+
5929
+ \`\`\`ts skip
5930
+ await python.stopSession()
5931
+ console.log(python.state.get('sessionActive')) // false
5932
+ \`\`\`
5933
+
5934
+ ### Crash Recovery
5935
+
5936
+ If the Python process dies unexpectedly (segfault, killed externally), the feature:
5937
+ - Sets \`sessionActive\` to \`false\`
5938
+ - Rejects all pending requests
5939
+ - Emits a \`sessionError\` event
5940
+
5941
+ \`\`\`ts skip
5942
+ python.on('sessionError', ({ error, sessionId }) => {
5943
+ console.error('Python session error:', error)
5944
+ // You could restart: await python.startSession()
5945
+ })
5946
+ \`\`\`
5947
+
5948
+ ## The Session API
5949
+
5950
+ ### run(code, variables?)
5951
+
5952
+ Execute Python code in the persistent namespace. This is the workhorse method.
5953
+
5954
+ \`\`\`ts skip
5955
+ // Simple execution
5956
+ const result = await python.run('print("hello")')
5957
+ // result.ok === true
5958
+ // result.stdout === 'hello\\n'
5959
+
5960
+ // With variable injection
5961
+ const result = await python.run('print(f"Processing {count} items")', { count: 42 })
5962
+
5963
+ // Errors don't crash the session
5964
+ const bad = await python.run('raise ValueError("oops")')
5965
+ // bad.ok === false
5966
+ // bad.error === 'oops'
5967
+ // bad.traceback === 'Traceback (most recent call last):\\n...'
5968
+
5969
+ // Session still alive after error
5970
+ const good = await python.run('print("still here")')
5971
+ // good.ok === true
5972
+ \`\`\`
5973
+
5974
+ ### eval(expression)
5975
+
5976
+ Evaluate a Python expression and return its value to JavaScript.
5977
+
5978
+ \`\`\`ts skip
5979
+ await python.run('x = [1, 2, 3]')
5980
+ const length = await python.eval('len(x)') // 3
5981
+ const doubled = await python.eval('[i*2 for i in x]') // [2, 4, 6]
5982
+ \`\`\`
5983
+
5984
+ Values are JSON-serialized. Complex types that can't be serialized come back as their \`repr()\` string.
5985
+
5986
+ ### importModule(name, alias?)
5987
+
5988
+ Import a module into the session namespace. The alias defaults to the last segment of the module path.
5989
+
5990
+ \`\`\`ts skip
5991
+ await python.importModule('json') // import json
5992
+ await python.importModule('myapp.models', 'models') // import myapp.models as models
5993
+ await python.importModule('os.path') // import os.path (available as "path")
5994
+ \`\`\`
5995
+
5996
+ ### call(funcPath, args?, kwargs?)
5997
+
5998
+ Call a function by its dotted path in the namespace.
5999
+
6000
+ \`\`\`ts skip
6001
+ await python.importModule('json')
6002
+ const encoded = await python.call('json.dumps', [{ a: 1 }], { indent: 2 })
6003
+ // '{\\n "a": 1\\n}'
6004
+
6005
+ // Works with your own functions too
6006
+ await python.run('def add(a, b): return a + b')
6007
+ const sum = await python.call('add', [3, 4]) // 7
6008
+ \`\`\`
6009
+
6010
+ ### getLocals()
6011
+
6012
+ Inspect everything in the session namespace.
6013
+
6014
+ \`\`\`ts skip
6015
+ await python.run('x = 42')
6016
+ await python.importModule('json')
6017
+ const locals = await python.getLocals()
6018
+ // { x: 42, json: '<module ...>' }
6019
+ \`\`\`
6020
+
6021
+ ### resetSession()
6022
+
6023
+ Clear all variables and imports without restarting the process.
6024
+
6025
+ \`\`\`ts skip
6026
+ await python.run('big_model = load_model()')
6027
+ await python.resetSession()
6028
+ // big_model is gone, but the session process is still running
6029
+ \`\`\`
6030
+
6031
+ ## Real-World Patterns
6032
+
6033
+ ### Data Analysis Pipeline
6034
+
6035
+ \`\`\`ts skip
6036
+ const python = container.feature('python', { dir: '/path/to/analytics' })
6037
+ await python.enable()
6038
+ await python.startSession()
6039
+
6040
+ // Setup
6041
+ await python.run('import pandas as pd')
6042
+ await python.run('import matplotlib')
6043
+ await python.run('matplotlib.use("Agg")') // headless
6044
+ await python.run('import matplotlib.pyplot as plt')
6045
+
6046
+ // Load and analyze
6047
+ await python.run('df = pd.read_csv("data/events.csv")')
6048
+ const shape = await python.eval('list(df.shape)')
6049
+ console.log(\`Loaded \${shape[0]} rows, \${shape[1]} columns\`)
6050
+
6051
+ const columns = await python.eval('list(df.columns)')
6052
+ console.log('Columns:', columns)
6053
+
6054
+ // Filter and aggregate
6055
+ await python.run(\`
6056
+ filtered = df[df["status"] == "completed"]
6057
+ summary = filtered.groupby("category")["amount"].agg(["sum", "mean", "count"])
6058
+ \`)
6059
+
6060
+ const summary = await python.eval('summary.to_dict()')
6061
+ console.log('Summary:', summary)
6062
+
6063
+ // Generate a chart
6064
+ await python.run(\`
6065
+ fig, ax = plt.subplots(figsize=(10, 6))
6066
+ summary["sum"].plot(kind="bar", ax=ax)
6067
+ ax.set_title("Revenue by Category")
6068
+ fig.savefig("output/revenue.png", dpi=150, bbox_inches="tight")
6069
+ plt.close(fig)
6070
+ \`)
6071
+
6072
+ await python.stopSession()
6073
+ \`\`\`
6074
+
6075
+ ### Working with a Django Project
6076
+
6077
+ \`\`\`ts skip
6078
+ const python = container.feature('python', { dir: '/path/to/django-project' })
6079
+ await python.enable()
6080
+ await python.startSession()
6081
+
6082
+ // Django requires this before you can import models
6083
+ await python.run(\`
6084
+ import os
6085
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
6086
+
6087
+ import django
6088
+ django.setup()
6089
+ \`)
6090
+
6091
+ // Now you can work with the ORM
6092
+ await python.run('from myapp.models import User, Order')
6093
+
6094
+ const userCount = await python.eval('User.objects.count()')
6095
+ console.log(\`\${userCount} users in database\`)
6096
+
6097
+ const recentOrders = await python.eval(\`
6098
+ list(Order.objects.filter(status="pending").values("id", "total", "created_at")[:10])
6099
+ \`)
6100
+ console.log('Recent pending orders:', recentOrders)
6101
+
6102
+ await python.stopSession()
6103
+ \`\`\`
6104
+
6105
+ ### ML Model Interaction
6106
+
6107
+ \`\`\`ts skip
6108
+ const python = container.feature('python', { dir: '/path/to/ml-project' })
6109
+ await python.enable()
6110
+ await python.startSession()
6111
+
6112
+ // Expensive setup — only happens once
6113
+ await python.run(\`
6114
+ from transformers import pipeline
6115
+ classifier = pipeline("sentiment-analysis")
6116
+ print("Model loaded")
6117
+ \`)
6118
+
6119
+ // Now you can call it cheaply many times
6120
+ async function classify(text: string) {
6121
+ return python.call('classifier', [text])
6122
+ }
6123
+
6124
+ const results = await Promise.all([
6125
+ classify('I love this product!'),
6126
+ classify('Terrible experience.'),
6127
+ classify('It was okay, nothing special.'),
6128
+ ])
6129
+
6130
+ console.log(results)
6131
+ // [
6132
+ // [{ label: 'POSITIVE', score: 0.9998 }],
6133
+ // [{ label: 'NEGATIVE', score: 0.9994 }],
6134
+ // [{ label: 'NEGATIVE', score: 0.7231 }],
6135
+ // ]
6136
+
6137
+ await python.stopSession()
6138
+ \`\`\`
6139
+
6140
+ ### Luca Command That Uses Python
6141
+
6142
+ \`\`\`ts skip
6143
+ // commands/analyze.ts
6144
+ import { z } from 'zod'
6145
+ import type { ContainerContext } from '@soederpop/luca'
6146
+ import { CommandOptionsSchema } from '@soederpop/luca/schemas'
6147
+
6148
+ export const positionals = ['target']
6149
+ export const argsSchema = CommandOptionsSchema.extend({
6150
+ target: z.string().describe('Path to CSV file to analyze'),
6151
+ })
6152
+
6153
+ async function handler(options: z.infer<typeof argsSchema>, context: ContainerContext) {
6154
+ const container = context.container as any
6155
+ const python = container.feature('python')
6156
+ await python.enable()
6157
+ await python.startSession()
6158
+
6159
+ try {
6160
+ await python.run('import pandas as pd')
6161
+ await python.run(\`df = pd.read_csv("\${options.target}")\`)
6162
+
6163
+ const shape = await python.eval('list(df.shape)')
6164
+ const dtypes = await python.eval('dict(df.dtypes.astype(str))')
6165
+ const nulls = await python.eval('dict(df.isnull().sum())')
6166
+
6167
+ console.log(\`Rows: \${shape[0]}, Columns: \${shape[1]}\`)
6168
+ console.log('Column types:', dtypes)
6169
+ console.log('Null counts:', nulls)
6170
+ } finally {
6171
+ await python.stopSession()
6172
+ }
6173
+ }
6174
+
6175
+ export default {
6176
+ description: 'Analyze a CSV file using pandas',
6177
+ argsSchema,
6178
+ handler,
6179
+ }
6180
+ \`\`\`
6181
+
6182
+ \`\`\`bash
6183
+ luca analyze data/sales.csv
6184
+ \`\`\`
6185
+
6186
+ ## Stateless vs. Session: Choosing the Right Mode
6187
+
6188
+ | | \`execute()\` (stateless) | \`run()\` (session) |
6189
+ |---|---|---|
6190
+ | Process | Fresh per call | Shared, long-lived |
6191
+ | State | None — each call starts clean | Persists across calls |
6192
+ | Imports | Re-imported every time | Imported once, reused |
6193
+ | Startup cost | ~50-200ms per call | ~200ms once, then ~1ms per call |
6194
+ | Use case | One-off scripts, simple eval | Real projects, data pipelines, REPL-like |
6195
+ | Error isolation | Perfect — crash is contained | Errors caught, session survives |
6196
+
6197
+ Both modes use the same environment detection (uv, conda, venv, system) and respect the same \`dir\` and \`pythonPath\` options.
6198
+
6199
+ ## Environment Detection
6200
+
6201
+ The feature detects Python environments in this order:
6202
+
6203
+ 1. **Explicit** — \`pythonPath\` option overrides everything
6204
+ 2. **uv** — \`uv.lock\` or \`pyproject.toml\` present, \`uv run python\` works
6205
+ 3. **conda** — \`environment.yml\` or \`conda.yml\` present
6206
+ 4. **venv** — \`venv/\` or \`.venv/\` directory with a Python binary inside
6207
+ 5. **system** — falls back to \`python3\` or \`python\` on PATH
6208
+
6209
+ \`\`\`ts skip
6210
+ const python = container.feature('python', { dir: '/path/to/project' })
6211
+ await python.enable()
6212
+ console.log(python.environmentType) // 'uv' | 'conda' | 'venv' | 'system'
6213
+ console.log(python.pythonPath) // e.g. '/Users/me/.local/bin/uv run python'
6214
+ \`\`\`
6215
+
6216
+ ## Events
6217
+
6218
+ The session emits events you can listen to for monitoring and debugging:
6219
+
6220
+ \`\`\`ts skip
6221
+ python.on('sessionStarted', ({ sessionId }) => {
6222
+ console.log('Session started:', sessionId)
6223
+ })
6224
+
6225
+ python.on('sessionStopped', ({ sessionId }) => {
6226
+ console.log('Session stopped:', sessionId)
6227
+ })
6228
+
6229
+ python.on('sessionError', ({ error, sessionId }) => {
6230
+ console.error('Session error:', error)
6231
+ })
6232
+ \`\`\`
6233
+
6234
+ ## What's Next
6235
+
6236
+ - [Creating Features](./10-creating-features.md) — build your own feature that wraps a Python service
6237
+ - [Commands](./08-commands.md) — create CLI commands that leverage Python
6238
+ - [Servers and Endpoints](./06-servers.md) — expose Python-powered analysis via HTTP
5641
6239
  `,
5642
6240
  "18-semantic-search.md": `---
5643
6241
  title: Semantic Search
@@ -6432,8 +7030,8 @@ fs.introspectAsText()
6432
7030
  The container itself is introspectable:
6433
7031
 
6434
7032
  \`\`\`js
6435
- container.inspect() // structured object with all registries, state, events
6436
- container.inspectAsText() // full markdown overview
7033
+ container.introspect() // structured object with all registries, state, events
7034
+ container.introspectAsText() // full markdown overview
6437
7035
  \`\`\`
6438
7036
 
6439
7037
  ## The REPL
@@ -6468,7 +7066,7 @@ luca eval "grep.search('.', 'TODO')"
6468
7066
  | Structured introspection? | \`feature.introspect()\` |
6469
7067
  | What state does it have? | \`feature.state.current\` |
6470
7068
  | What events does it emit? | \`feature.introspect().events\` |
6471
- | Full container overview? | \`container.inspectAsText()\` |
7069
+ | Full container overview? | \`container.introspectAsText()\` |
6472
7070
  | CLI docs for a helper? | \`luca describe <name>\` |
6473
7071
 
6474
7072
  ## Gotchas
@@ -7127,10 +7725,10 @@ Discover everything about the container at runtime:
7127
7725
 
7128
7726
  \`\`\`typescript
7129
7727
  // Structured introspection data
7130
- const info = container.inspect()
7728
+ const info = container.introspect()
7131
7729
 
7132
7730
  // Human-readable markdown
7133
- const docs = container.inspectAsText()
7731
+ const docs = container.introspectAsText()
7134
7732
  \`\`\`
7135
7733
 
7136
7734
  This is what makes Luca especially powerful for AI agents -- they can discover the entire API surface at runtime without reading documentation.