@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.
Files changed (86) hide show
  1. package/README.md +241 -36
  2. package/bun.lock +24 -5
  3. package/commands/build-python-bridge.ts +43 -0
  4. package/docs/apis/clients/rest.md +7 -7
  5. package/docs/apis/clients/websocket.md +23 -10
  6. package/docs/apis/features/agi/assistant.md +155 -8
  7. package/docs/apis/features/agi/assistants-manager.md +90 -22
  8. package/docs/apis/features/agi/auto-assistant.md +377 -0
  9. package/docs/apis/features/agi/browser-use.md +802 -0
  10. package/docs/apis/features/agi/claude-code.md +6 -1
  11. package/docs/apis/features/agi/conversation-history.md +7 -6
  12. package/docs/apis/features/agi/conversation.md +111 -38
  13. package/docs/apis/features/agi/docs-reader.md +35 -57
  14. package/docs/apis/features/agi/file-tools.md +163 -0
  15. package/docs/apis/features/agi/openapi.md +2 -2
  16. package/docs/apis/features/agi/skills-library.md +227 -0
  17. package/docs/apis/features/node/content-db.md +125 -4
  18. package/docs/apis/features/node/disk-cache.md +11 -11
  19. package/docs/apis/features/node/downloader.md +1 -1
  20. package/docs/apis/features/node/file-manager.md +15 -15
  21. package/docs/apis/features/node/fs.md +78 -21
  22. package/docs/apis/features/node/git.md +50 -10
  23. package/docs/apis/features/node/google-calendar.md +3 -0
  24. package/docs/apis/features/node/google-docs.md +10 -1
  25. package/docs/apis/features/node/google-drive.md +3 -0
  26. package/docs/apis/features/node/google-mail.md +214 -0
  27. package/docs/apis/features/node/google-sheets.md +3 -0
  28. package/docs/apis/features/node/ink.md +10 -10
  29. package/docs/apis/features/node/ipc-socket.md +83 -93
  30. package/docs/apis/features/node/networking.md +5 -5
  31. package/docs/apis/features/node/os.md +7 -7
  32. package/docs/apis/features/node/package-finder.md +14 -14
  33. package/docs/apis/features/node/proc.md +2 -1
  34. package/docs/apis/features/node/process-manager.md +70 -3
  35. package/docs/apis/features/node/python.md +265 -9
  36. package/docs/apis/features/node/redis.md +380 -0
  37. package/docs/apis/features/node/ui.md +13 -13
  38. package/docs/apis/servers/express.md +35 -7
  39. package/docs/apis/servers/mcp.md +3 -3
  40. package/docs/apis/servers/websocket.md +51 -8
  41. package/docs/bootstrap/CLAUDE.md +1 -1
  42. package/docs/bootstrap/SKILL.md +93 -7
  43. package/docs/examples/feature-as-tool-provider.md +143 -0
  44. package/docs/examples/python.md +42 -1
  45. package/docs/introspection.md +15 -5
  46. package/docs/tutorials/00-bootstrap.md +3 -3
  47. package/docs/tutorials/02-container.md +2 -2
  48. package/docs/tutorials/10-creating-features.md +5 -0
  49. package/docs/tutorials/13-introspection.md +12 -2
  50. package/docs/tutorials/19-python-sessions.md +401 -0
  51. package/package.json +8 -4
  52. package/src/agi/container.server.ts +8 -0
  53. package/src/agi/features/assistant.ts +18 -0
  54. package/src/agi/features/autonomous-assistant.ts +435 -0
  55. package/src/agi/features/conversation.ts +58 -6
  56. package/src/agi/features/file-tools.ts +286 -0
  57. package/src/agi/features/luca-coder.ts +643 -0
  58. package/src/bootstrap/generated.ts +705 -17
  59. package/src/cli/build-info.ts +2 -2
  60. package/src/cli/cli.ts +22 -13
  61. package/src/commands/bootstrap.ts +49 -6
  62. package/src/commands/code.ts +369 -0
  63. package/src/commands/describe.ts +7 -2
  64. package/src/commands/index.ts +1 -0
  65. package/src/commands/sandbox-mcp.ts +7 -7
  66. package/src/commands/save-api-docs.ts +1 -1
  67. package/src/container-describer.ts +4 -4
  68. package/src/container.ts +10 -19
  69. package/src/helper.ts +24 -33
  70. package/src/introspection/generated.agi.ts +3026 -590
  71. package/src/introspection/generated.node.ts +1625 -688
  72. package/src/introspection/generated.web.ts +15 -57
  73. package/src/node/container.ts +5 -0
  74. package/src/node/features/figlet-fonts.ts +597 -0
  75. package/src/node/features/fs.ts +3 -9
  76. package/src/node/features/helpers.ts +20 -0
  77. package/src/node/features/python.ts +429 -16
  78. package/src/node/features/redis.ts +446 -0
  79. package/src/node/features/ui.ts +4 -11
  80. package/src/python/bridge.py +220 -0
  81. package/src/python/generated.ts +227 -0
  82. package/src/scaffolds/generated.ts +1 -1
  83. package/test/python-session.test.ts +105 -0
  84. package/assistants/lucaExpert/CORE.md +0 -37
  85. package/assistants/lucaExpert/hooks.ts +0 -9
  86. package/assistants/lucaExpert/tools.ts +0 -177
@@ -0,0 +1,227 @@
1
+ // Auto-generated Python bridge script
2
+ // Generated at: 2026-03-26T03:30:23.085Z
3
+ // Source: src/python/bridge.py
4
+ //
5
+ // Do not edit manually. Run: luca build-python-bridge
6
+
7
+ export const bridgeScript = `#!/usr/bin/env python3
8
+ """Luca Python Bridge - persistent interactive Python session.
9
+
10
+ Communicates via JSON lines over stdin/stdout. Each request is a single
11
+ JSON object per line on stdin; each response is a single JSON object per
12
+ line on stdout. User print() output is captured per-execution via
13
+ io.StringIO so it never corrupts the protocol.
14
+
15
+ Python 3.8+ compatible (stdlib only).
16
+ """
17
+ import sys
18
+ import json
19
+ import io
20
+ import traceback
21
+ import os
22
+
23
+ # Persistent namespace shared across all exec/eval calls
24
+ _namespace = {"__builtins__": __builtins__}
25
+
26
+
27
+ def setup_sys_path(project_dir):
28
+ """Insert project_dir and common sub-paths into sys.path."""
29
+ paths_to_add = [project_dir]
30
+
31
+ # src/ layout (PEP 621 / setuptools)
32
+ src_dir = os.path.join(project_dir, "src")
33
+ if os.path.isdir(src_dir):
34
+ paths_to_add.append(src_dir)
35
+
36
+ # lib/ layout (less common but exists)
37
+ lib_dir = os.path.join(project_dir, "lib")
38
+ if os.path.isdir(lib_dir):
39
+ paths_to_add.append(lib_dir)
40
+
41
+ for p in reversed(paths_to_add):
42
+ if p not in sys.path:
43
+ sys.path.insert(0, p)
44
+
45
+
46
+ def _safe_serialize(value):
47
+ """Attempt JSON serialization; fall back to repr()."""
48
+ try:
49
+ json.dumps(value, default=str)
50
+ return value
51
+ except (TypeError, ValueError, OverflowError):
52
+ return repr(value)
53
+
54
+
55
+ def handle_exec(req):
56
+ """Execute code in the persistent namespace."""
57
+ code = req.get("code", "")
58
+ variables = req.get("variables", {})
59
+ _namespace.update(variables)
60
+
61
+ old_stdout = sys.stdout
62
+ captured = io.StringIO()
63
+ sys.stdout = captured
64
+ try:
65
+ exec(code, _namespace)
66
+ sys.stdout = old_stdout
67
+ return {"ok": True, "stdout": captured.getvalue(), "result": None}
68
+ except Exception as e:
69
+ sys.stdout = old_stdout
70
+ return {
71
+ "ok": False,
72
+ "error": str(e),
73
+ "traceback": traceback.format_exc(),
74
+ "stdout": captured.getvalue(),
75
+ }
76
+
77
+
78
+ def handle_eval(req):
79
+ """Evaluate an expression and return its value."""
80
+ expression = req.get("expression", "")
81
+
82
+ old_stdout = sys.stdout
83
+ captured = io.StringIO()
84
+ sys.stdout = captured
85
+ try:
86
+ result = eval(expression, _namespace)
87
+ sys.stdout = old_stdout
88
+ return {
89
+ "ok": True,
90
+ "result": _safe_serialize(result),
91
+ "stdout": captured.getvalue(),
92
+ }
93
+ except Exception as e:
94
+ sys.stdout = old_stdout
95
+ return {
96
+ "ok": False,
97
+ "error": str(e),
98
+ "traceback": traceback.format_exc(),
99
+ "stdout": captured.getvalue(),
100
+ }
101
+
102
+
103
+ def handle_import(req):
104
+ """Import a module into the namespace."""
105
+ module_name = req.get("module", "")
106
+ alias = req.get("alias", module_name.split(".")[-1])
107
+ try:
108
+ mod = __import__(
109
+ module_name,
110
+ fromlist=[module_name.split(".")[-1]] if "." in module_name else [],
111
+ )
112
+ _namespace[alias] = mod
113
+ return {"ok": True, "result": "Imported {} as {}".format(module_name, alias)}
114
+ except Exception as e:
115
+ return {
116
+ "ok": False,
117
+ "error": str(e),
118
+ "traceback": traceback.format_exc(),
119
+ }
120
+
121
+
122
+ def handle_call(req):
123
+ """Call a function by dotted path in the namespace."""
124
+ func_path = req.get("function", "")
125
+ args = req.get("args", [])
126
+ kwargs = req.get("kwargs", {})
127
+
128
+ old_stdout = sys.stdout
129
+ captured = io.StringIO()
130
+ sys.stdout = captured
131
+ try:
132
+ parts = func_path.split(".")
133
+ obj = _namespace[parts[0]]
134
+ for part in parts[1:]:
135
+ obj = getattr(obj, part)
136
+ result = obj(*args, **kwargs)
137
+ sys.stdout = old_stdout
138
+ return {
139
+ "ok": True,
140
+ "result": _safe_serialize(result),
141
+ "stdout": captured.getvalue(),
142
+ }
143
+ except Exception as e:
144
+ sys.stdout = old_stdout
145
+ return {
146
+ "ok": False,
147
+ "error": str(e),
148
+ "traceback": traceback.format_exc(),
149
+ "stdout": captured.getvalue(),
150
+ }
151
+
152
+
153
+ def handle_get_locals(req):
154
+ """Return all non-dunder keys from the namespace."""
155
+ safe = {}
156
+ for k, v in _namespace.items():
157
+ if k.startswith("__"):
158
+ continue
159
+ safe[k] = _safe_serialize(v)
160
+ return {"ok": True, "result": safe}
161
+
162
+
163
+ def handle_reset(req):
164
+ """Clear the namespace."""
165
+ _namespace.clear()
166
+ _namespace["__builtins__"] = __builtins__
167
+ return {"ok": True, "result": "Session reset"}
168
+
169
+
170
+ HANDLERS = {
171
+ "exec": handle_exec,
172
+ "eval": handle_eval,
173
+ "import": handle_import,
174
+ "call": handle_call,
175
+ "get_locals": handle_get_locals,
176
+ "reset": handle_reset,
177
+ }
178
+
179
+
180
+ def main():
181
+ # First line from stdin is the init handshake with project_dir
182
+ init_line = sys.stdin.readline().strip()
183
+ if init_line:
184
+ try:
185
+ init = json.loads(init_line)
186
+ if "project_dir" in init:
187
+ setup_sys_path(init["project_dir"])
188
+ except json.JSONDecodeError:
189
+ pass
190
+
191
+ # Signal ready
192
+ sys.stdout.write(json.dumps({"ok": True, "type": "ready"}) + "\\n")
193
+ sys.stdout.flush()
194
+
195
+ # Main loop: read JSON commands, execute, respond
196
+ for line in sys.stdin:
197
+ line = line.strip()
198
+ if not line:
199
+ continue
200
+
201
+ try:
202
+ req = json.loads(line)
203
+ except json.JSONDecodeError as e:
204
+ resp = {"ok": False, "error": "Invalid JSON: {}".format(e)}
205
+ sys.stdout.write(json.dumps(resp) + "\\n")
206
+ sys.stdout.flush()
207
+ continue
208
+
209
+ req_id = req.get("id")
210
+ req_type = req.get("type", "exec")
211
+ handler = HANDLERS.get(req_type)
212
+
213
+ if not handler:
214
+ resp = {"ok": False, "error": "Unknown request type: {}".format(req_type)}
215
+ else:
216
+ resp = handler(req)
217
+
218
+ if req_id:
219
+ resp["id"] = req_id
220
+
221
+ sys.stdout.write(json.dumps(resp, default=str) + "\\n")
222
+ sys.stdout.flush()
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()
227
+ `
@@ -1,5 +1,5 @@
1
1
  // Auto-generated scaffold and MCP readme content
2
- // Generated at: 2026-03-24T09:09:10.786Z
2
+ // Generated at: 2026-03-26T03:30:21.414Z
3
3
  // Source: docs/scaffolds/*.md, docs/examples/assistant/, and docs/mcp/readme.md
4
4
  //
5
5
  // Do not edit manually. Run: luca build-scaffolds
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
2
+
3
+ describe('Python Persistent Session', () => {
4
+ let container: any
5
+ let python: any
6
+
7
+ beforeAll(async () => {
8
+ const mod = await import('../src/node.js')
9
+ container = mod.default
10
+ python = container.feature('python', { dir: container.cwd })
11
+ await python.enable()
12
+ await python.startSession()
13
+ })
14
+
15
+ afterAll(async () => {
16
+ if (python?.state?.get('sessionActive')) {
17
+ await python.stopSession()
18
+ }
19
+ })
20
+
21
+ it('session is active after start', () => {
22
+ expect(python.state.get('sessionActive')).toBe(true)
23
+ expect(python.state.get('sessionId')).toBeTruthy()
24
+ })
25
+
26
+ it('run() executes code and captures stdout', async () => {
27
+ const result = await python.run('print("hello from bridge")')
28
+ expect(result.ok).toBe(true)
29
+ expect(result.stdout).toContain('hello from bridge')
30
+ })
31
+
32
+ it('state persists across calls', async () => {
33
+ await python.run('x = 42')
34
+ const result = await python.eval('x + 1')
35
+ expect(result).toBe(43)
36
+ })
37
+
38
+ it('variables are injected', async () => {
39
+ const result = await python.run('print(name)', { name: 'luca' })
40
+ expect(result.ok).toBe(true)
41
+ expect(result.stdout).toContain('luca')
42
+ })
43
+
44
+ it('importModule() works', async () => {
45
+ await python.importModule('json')
46
+ const result = await python.eval('json.dumps({"a": 1})')
47
+ expect(result).toBe('{"a": 1}')
48
+ })
49
+
50
+ it('call() invokes functions', async () => {
51
+ await python.run('def add(a, b): return a + b')
52
+ const result = await python.call('add', [3, 4])
53
+ expect(result).toBe(7)
54
+ })
55
+
56
+ it('getLocals() returns namespace', async () => {
57
+ await python.run('y = 99')
58
+ const locals = await python.getLocals()
59
+ expect(locals.y).toBe(99)
60
+ expect(locals.x).toBe(42)
61
+ })
62
+
63
+ it('handles errors gracefully without crashing the session', async () => {
64
+ const result = await python.run('raise ValueError("test error")')
65
+ expect(result.ok).toBe(false)
66
+ expect(result.error).toContain('test error')
67
+ // Session should still be alive
68
+ const check = await python.run('print("still alive")')
69
+ expect(check.ok).toBe(true)
70
+ expect(check.stdout).toContain('still alive')
71
+ })
72
+
73
+ it('resetSession() clears state', async () => {
74
+ await python.run('z = 123')
75
+ await python.resetSession()
76
+ const result = await python.run('print(z)')
77
+ expect(result.ok).toBe(false)
78
+ expect(result.error).toContain('is not defined')
79
+ })
80
+
81
+ it('stopSession() cleans up', async () => {
82
+ await python.stopSession()
83
+ expect(python.state.get('sessionActive')).toBe(false)
84
+ expect(python.state.get('sessionId')).toBeNull()
85
+ })
86
+
87
+ it('can start a new session after stopping', async () => {
88
+ await python.startSession()
89
+ expect(python.state.get('sessionActive')).toBe(true)
90
+ const result = await python.run('print("fresh session")')
91
+ expect(result.ok).toBe(true)
92
+ expect(result.stdout).toContain('fresh session')
93
+ })
94
+ })
95
+
96
+ describe('Python backward compatibility', () => {
97
+ it('execute() still works without a session', async () => {
98
+ const mod = await import('../src/node.js')
99
+ const container = mod.default
100
+ const python = container.feature('python', { dir: container.cwd })
101
+ await python.enable()
102
+ const result = await python.execute('print("stateless")')
103
+ expect(result.stdout).toContain('stateless')
104
+ })
105
+ })
@@ -1,37 +0,0 @@
1
- # Luca Framework Expert
2
-
3
- You are a Luca framework expert. You have deep knowledge of the container architecture, its features, clients, servers, commands, and how to build with them. Your job is to help people understand, use, and build on the Luca framework.
4
-
5
- You have powerful tools to explore the framework at runtime — use them liberally before answering questions. Don't guess when you can look it up.
6
-
7
- ## Your Tools
8
-
9
- ### Discovery & Documentation
10
-
11
- - **readSkill** — Read the SKILL.md learning guide and follow referenced documentation. Start here when orienting yourself.
12
- - **readDoc** — Read any document from the docs/ folder: examples, tutorials, API references, scaffolds. Use `list` mode to browse what's available in a category, or `read` mode to get the full content.
13
- - **lucaDescribe** — Run `luca describe` to get live documentation for any feature, client, server, or specific method/getter. This is generated from the actual source code and is always current. Use dot notation (`fs.readFile`, `ui.banner`) to drill into specific members.
14
-
15
- ### Live Code Execution
16
-
17
- - **lucaEval** — Execute JavaScript/TypeScript in a live container sandbox. All features are available as top-level variables (fs, git, proc, vm, etc). The last expression's value is returned. For async code, put the await call as the last expression.
18
-
19
- ### Deep Research
20
-
21
- - **askCodingAssistant** — Delegate deep codebase research to the coding assistant, who has ripgrep, cat, ls, sed, and awk at its disposal. Use this when you need to understand implementation details, trace code paths, or find patterns across files that the documentation doesn't cover.
22
-
23
- ## How to Work
24
-
25
- 1. **Start with describe.** When asked about any feature, client, or server — run `lucaDescribe` first. It's the fastest path to accurate information.
26
- 2. **Consult docs for depth.** Use `readDoc` to pull up examples, tutorials, and API docs when you need patterns, best practices, or complete working code.
27
- 3. **Eval to verify.** When uncertain about behavior, run it. `lucaEval` gives you a live container — test the actual API rather than speculating.
28
- 4. **Delegate research.** When you need to understand how something is implemented (not just how to use it), ask the codingAssistant to dig into the source.
29
- 5. **Read the SKILL.md** via `readSkill` when you need to orient a user to the framework's learning path.
30
-
31
- ## Response Style
32
-
33
- - Lead with working code examples when possible
34
- - Reference specific methods and their signatures
35
- - Explain the "why" behind patterns, not just the "what"
36
- - When a user's approach conflicts with framework conventions, explain the idiomatic way and why it matters
37
- - Be concise but thorough — framework users need precision
@@ -1,9 +0,0 @@
1
- import type { Assistant } from '@soederpop/luca/agi'
2
-
3
- declare global {
4
- var assistant: Assistant
5
- }
6
-
7
- export function started() {
8
- console.log('Luca Expert assistant started')
9
- }
@@ -1,177 +0,0 @@
1
- import { z } from 'zod'
2
- import type { Assistant, AGIContainer } from '@soederpop/luca/agi'
3
-
4
- declare global {
5
- var assistant: Assistant
6
- var container: AGIContainer
7
- }
8
-
9
- const proc = () => container.feature('proc')
10
- const fs = () => container.feature('fs')
11
-
12
- // ---------------------------------------------------------------------------
13
- // readSkill — Read the SKILL.md and progressively discover referenced docs
14
- // ---------------------------------------------------------------------------
15
-
16
- export const schemas = {
17
- readSkill: z.object({
18
- section: z.string().default('').describe(
19
- 'Section to focus on (e.g. "Phase 1", "Phase 2", "Key Concepts"). Leave empty for the full SKILL.md.'
20
- ),
21
- }).describe('Read the SKILL.md learning guide for the Luca framework. Use this to orient yourself or a user to how the framework is learned and used.'),
22
-
23
- readDoc: z.object({
24
- mode: z.enum(['list', 'read']).describe(
25
- '"list" to browse available documents in a category, "read" to get full content of a specific document'
26
- ),
27
- category: z.string().default('').describe(
28
- 'Document category: examples, tutorials, apis, apis/features/node, apis/features/agi, apis/features/web, apis/clients, apis/servers, challenges, prompts, scaffolds, sessions, bootstrap. Leave empty when using mode "read".'
29
- ),
30
- path: z.string().default('').describe(
31
- 'Relative path within docs/ to read (e.g. "examples/fs.md", "tutorials/00-bootstrap.md"). Required when mode is "read". Leave empty when using mode "list".'
32
- ),
33
- }).describe('Browse and read documentation from the docs/ folder — examples, tutorials, API references, scaffolds, and more.'),
34
-
35
- lucaDescribe: z.object({
36
- args: z.string().describe(
37
- 'Arguments to pass to luca describe. Examples: "fs", "git", "features", "clients", "servers", "fs.readFile", "ui.banner", "express --options", "fs --methods", "fs git --examples"'
38
- ),
39
- }).describe('Run the luca describe CLI to get live, source-generated documentation for any feature, client, server, or specific method/getter. This is always current.'),
40
-
41
- lucaEval: z.object({
42
- code: z.string().describe(
43
- 'JavaScript/TypeScript code to run in a live container. All features available as top-level vars (fs, git, proc, vm, etc). The last expression value is returned. For async: put the await expression on the last line.'
44
- ),
45
- }).describe('Execute code in a live Luca container sandbox. Use this to test APIs, verify behavior, and prototype ideas. Top-level await is supported — put async calls as the last expression to capture their result.'),
46
-
47
- askCodingAssistant: z.object({
48
- question: z.string().describe(
49
- 'A research question for the coding assistant. Be specific about what you want to find: file paths, function implementations, patterns, class hierarchies, etc.'
50
- ),
51
- }).describe('Delegate deep codebase research to the coding assistant who has ripgrep, cat, ls, sed, and awk. Use for implementation details, tracing code paths, or finding patterns the docs do not cover.'),
52
- }
53
-
54
- export function readSkill({ section }: z.infer<typeof schemas.readSkill>): string {
55
- const skillPath = 'docs/bootstrap/SKILL.md'
56
-
57
- if (!fs().exists(skillPath)) {
58
- return 'SKILL.md not found at docs/bootstrap/SKILL.md'
59
- }
60
-
61
- const content = fs().readFile(skillPath)
62
-
63
- if (!section || !section.trim()) return content
64
-
65
- // Extract section by heading match
66
- const lines = content.split('\n')
67
- const sectionLines: string[] = []
68
- let capturing = false
69
- let sectionLevel = 0
70
-
71
- for (const line of lines) {
72
- const headingMatch = line.match(/^(#{1,6})\s+(.*)/)
73
-
74
- if (headingMatch) {
75
- const level = headingMatch[1].length
76
- const title = headingMatch[2]
77
-
78
- if (capturing && level <= sectionLevel) break
79
-
80
- if (title.toLowerCase().includes(section.toLowerCase())) {
81
- capturing = true
82
- sectionLevel = level
83
- }
84
- }
85
-
86
- if (capturing) sectionLines.push(line)
87
- }
88
-
89
- return sectionLines.length > 0
90
- ? sectionLines.join('\n')
91
- : `Section "${section}" not found in SKILL.md. Available sections:\n${lines.filter(l => l.startsWith('#')).join('\n')}`
92
- }
93
-
94
- export function readDoc({ mode, category, path: docPath }: z.infer<typeof schemas.readDoc>): string {
95
- if (mode === 'list') {
96
- const dir = category ? `docs/${category}` : 'docs'
97
-
98
- if (!fs().exists(dir)) {
99
- return `Directory "${dir}" not found. Available top-level categories:\n${fs().readdirSync('docs').join('\n')}`
100
- }
101
-
102
- const entries = fs().readdirSync(dir)
103
- return `Contents of ${dir}/:\n${entries.join('\n')}`
104
- }
105
-
106
- // mode === 'read'
107
- if (!docPath) {
108
- return 'Error: path is required when mode is "read". Use mode "list" to browse available docs first.'
109
- }
110
-
111
- const fullPath = docPath.startsWith('docs/') ? docPath : `docs/${docPath}`
112
-
113
- if (!fs().exists(fullPath)) {
114
- return `Document not found: ${fullPath}`
115
- }
116
-
117
- return fs().readFile(fullPath)
118
- }
119
-
120
- export function lucaDescribe({ args }: z.infer<typeof schemas.lucaDescribe>): string {
121
- try {
122
- return proc().exec(`bun run src/cli/cli.ts describe ${args}`)
123
- } catch (err: any) {
124
- return `Error running luca describe ${args}: ${err.message || err}`
125
- }
126
- }
127
-
128
- export async function lucaEval({ code }: z.infer<typeof schemas.lucaEval>): Promise<string> {
129
- try {
130
- const vm = container.feature('vm')
131
- const { result, console: calls } = await vm.runCaptured(code, {
132
- container,
133
- fs: container.feature('fs'),
134
- git: container.feature('git'),
135
- proc: container.feature('proc'),
136
- vm,
137
- ui: container.feature('ui'),
138
- os: container.feature('os'),
139
- grep: container.feature('grep'),
140
- networking: container.feature('networking'),
141
- })
142
-
143
- const parts: string[] = []
144
-
145
- if (calls.length > 0) {
146
- const consoleLines = calls.map(c => {
147
- const prefix = c.method === 'log' ? '' : `[${c.method}] `
148
- return prefix + c.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')
149
- })
150
- parts.push(consoleLines.join('\n'))
151
- }
152
-
153
- if (result === undefined) {
154
- parts.push('(undefined)')
155
- } else if (result === null) {
156
- parts.push('(null)')
157
- } else if (typeof result === 'string') {
158
- parts.push(result)
159
- } else {
160
- try { parts.push(JSON.stringify(result, null, 2)) } catch { parts.push(String(result)) }
161
- }
162
-
163
- return parts.join('\n')
164
- } catch (err: any) {
165
- return `Eval error: ${err.message || err}`
166
- }
167
- }
168
-
169
- export async function askCodingAssistant({ question }: z.infer<typeof schemas.askCodingAssistant>): Promise<string> {
170
- try {
171
- const sub = await assistant.subagent('codingAssistant')
172
- const answer = await sub.ask(question)
173
- return answer
174
- } catch (err: any) {
175
- return `Error asking coding assistant: ${err.message || err}`
176
- }
177
- }