@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
|
@@ -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-
|
|
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,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
|
-
}
|