@letta-ai/letta-code 0.32.11 → 0.32.13
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/dist/channels-slack.js +135 -54
- package/dist/channels-slack.js.map +3 -3
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/agent/message.d.ts.map +1 -1
- package/dist/types/agent/subagents/manager.d.ts.map +1 -1
- package/dist/types/agent/subagents/subagent-process.d.ts +23 -0
- package/dist/types/agent/subagents/subagent-process.d.ts.map +1 -0
- package/dist/types/backend/api/metadata.d.ts +12 -0
- package/dist/types/backend/api/metadata.d.ts.map +1 -1
- package/dist/types/channels/slack/status-controller.d.ts +2 -0
- package/dist/types/channels/slack/status-controller.d.ts.map +1 -1
- package/dist/types/cli/helpers/error-formatter.d.ts.map +1 -1
- package/dist/types/cli/helpers/stream-processor.d.ts +2 -0
- package/dist/types/cli/helpers/stream-processor.d.ts.map +1 -1
- package/dist/types/telemetry/index.d.ts +16 -1
- package/dist/types/telemetry/index.d.ts.map +1 -1
- package/dist/types/tools/impl/process_manager.d.ts +2 -0
- package/dist/types/tools/impl/process_manager.d.ts.map +1 -1
- package/dist/types/tools/impl/task-stop.d.ts.map +1 -1
- package/dist/types/tools/impl/task.d.ts.map +1 -1
- package/dist/types/utils/cloud-api-shutdown.d.ts +17 -0
- package/dist/types/utils/cloud-api-shutdown.d.ts.map +1 -0
- package/dist/types/websocket/listener/constants.d.ts +1 -0
- package/dist/types/websocket/listener/constants.d.ts.map +1 -1
- package/letta.js +2 -2
- package/package.json +1 -1
- package/scripts/build-pypi.py +172 -0
- package/scripts/isolated-unit-tests.json +5 -0
- package/scripts/smoke-pypi.py +236 -0
- package/scripts/source-file-size-baseline.json +1 -1
- package/scripts/stage-pypi-deps.mjs +90 -0
- package/skills/finding-agents/SKILL.md +6 -2
- package/skills/letta-guide/SKILL.md +21 -2
- package/skills/self-configuration/SKILL.md +11 -1
- package/skills/converting-mcps-to-skills/SKILL.md +0 -202
- package/skills/converting-mcps-to-skills/references/skill-templates.md +0 -141
- package/skills/converting-mcps-to-skills/scripts/mcp-http.ts +0 -980
- package/skills/converting-mcps-to-skills/scripts/mcp-stdio.ts +0 -494
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Install and exercise a wheel offline, outside the checkout and without Node/npm on PATH."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import venv
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ARGUMENT_PROBE = r"""
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
assert.equal(process.env.LETTA_CODE_DISTRIBUTION, 'pypi');
|
|
14
|
+
assert.deepEqual(process.argv.slice(2), ['space argument', 'unicode-λ', '--literal=$HOME']);
|
|
15
|
+
assert.ok(process.execPath.includes('_payload'));
|
|
16
|
+
process.exit(23); // Verify the Python launcher preserves child exit status.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
RUNTIME_PROBE = r"""
|
|
20
|
+
import {createRequire as bootstrapRequire} from 'node:module';
|
|
21
|
+
const require = bootstrapRequire(import.meta.url);
|
|
22
|
+
const assert = require('node:assert/strict');
|
|
23
|
+
const {createRequire} = require('node:module');
|
|
24
|
+
const {spawnSync} = require('node:child_process');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const cli = process.argv[2];
|
|
28
|
+
const app = path.dirname(cli);
|
|
29
|
+
const req = createRequire(cli);
|
|
30
|
+
const env = {...process.env};
|
|
31
|
+
delete env.NODE_OPTIONS;
|
|
32
|
+
assert.equal(process.env.LETTA_CODE_DISTRIBUTION, 'pypi');
|
|
33
|
+
assert.ok(process.execPath.includes('_payload'));
|
|
34
|
+
const child = spawnSync('node', ['-p', 'process.execPath'], {env, encoding:'utf8'});
|
|
35
|
+
assert.equal(child.status, 0, child.stderr);
|
|
36
|
+
assert.equal(fs.realpathSync(child.stdout.trim()), fs.realpathSync(process.execPath));
|
|
37
|
+
if (process.platform !== 'win32') {
|
|
38
|
+
const direct = spawnSync(cli, ['--version'], {env, encoding:'utf8'});
|
|
39
|
+
assert.equal(direct.status, 0, direct.stderr); // Executable bits + /usr/bin/env node.
|
|
40
|
+
assert.match(direct.stdout, /Letta Code/);
|
|
41
|
+
}
|
|
42
|
+
assert.ok(fs.existsSync(path.join(app, 'skills', 'letta-guide', 'SKILL.md')));
|
|
43
|
+
assert.ok(fs.existsSync(path.join(app, 'assets', 'tutor-profile.png')));
|
|
44
|
+
const rg = spawnSync(req('@vscode/ripgrep').rgPath, ['--version'], {env, encoding:'utf8'});
|
|
45
|
+
assert.equal(rg.status, 0, rg.stderr);
|
|
46
|
+
assert.match(rg.stdout, /ripgrep/);
|
|
47
|
+
assert.equal(typeof req('ws').WebSocketServer, 'function');
|
|
48
|
+
(async () => {
|
|
49
|
+
const {Bot} = req('grammy');
|
|
50
|
+
const bot = new Bot('123:test');
|
|
51
|
+
bot.api.config.use(async () => ({ok:true, result:{id:123, is_bot:true, first_name:'wheel', username:'wheel_bot'}}));
|
|
52
|
+
await bot.init();
|
|
53
|
+
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="3000" height="2"><rect width="3000" height="2" fill="red"/></svg>';
|
|
54
|
+
const resized = spawnSync(process.execPath, [path.join(app, 'image-resize-worker.js'), 'image/svg+xml'], {input:svg, env});
|
|
55
|
+
assert.equal(resized.status, 0, resized.stderr.toString());
|
|
56
|
+
const image = JSON.parse(resized.stdout);
|
|
57
|
+
assert.ok(image.width > 0 && image.width < 3000, JSON.stringify(image));
|
|
58
|
+
const ptyCommand = process.platform === 'win32' ? process.env.ComSpec : process.execPath;
|
|
59
|
+
const ptyArgs = process.platform === 'win32' ? ['/d', '/s', '/c', 'echo LETTA_PTY_OK'] : [cli, '--help'];
|
|
60
|
+
const ptyExpected = process.platform === 'win32' ? /LETTA_PTY_OK/ : /USAGE/;
|
|
61
|
+
const pty = req('node-pty').spawn(ptyCommand, ptyArgs, {env, cols:100, rows:30});
|
|
62
|
+
let output = '';
|
|
63
|
+
pty.onData(data => { output += data; });
|
|
64
|
+
const timeout = setTimeout(() => { pty.kill(); throw new Error('PTY timed out'); }, 30000);
|
|
65
|
+
pty.onExit(({exitCode}) => {
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
assert.equal(exitCode, 0, output);
|
|
68
|
+
assert.match(output, ptyExpected);
|
|
69
|
+
console.log('native PTY, image worker, ripgrep, Telegram, assets and child Node passed');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
});
|
|
72
|
+
})().catch(error => {console.error(error); process.exit(1)});
|
|
73
|
+
// Hold the script until the asynchronous probe exits.
|
|
74
|
+
await new Promise(() => {});
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
wheel = Path(sys.argv[1]).resolve()
|
|
80
|
+
if wheel.stat().st_size >= 100 * 1024 * 1024:
|
|
81
|
+
raise SystemExit("Wheel exceeds the default PyPI 100 MiB artifact limit")
|
|
82
|
+
with tempfile.TemporaryDirectory(prefix="letta wheel smoke ") as temporary:
|
|
83
|
+
root = Path(temporary)
|
|
84
|
+
environment = root / "venv"
|
|
85
|
+
venv.create(environment, with_pip=True)
|
|
86
|
+
binary = environment / ("Scripts" if os.name == "nt" else "bin")
|
|
87
|
+
python = binary / ("python.exe" if os.name == "nt" else "python")
|
|
88
|
+
cli = binary / ("letta.exe" if os.name == "nt" else "letta")
|
|
89
|
+
subprocess.run(
|
|
90
|
+
[
|
|
91
|
+
str(python),
|
|
92
|
+
"-m",
|
|
93
|
+
"pip",
|
|
94
|
+
"install",
|
|
95
|
+
"--no-index",
|
|
96
|
+
"--no-deps",
|
|
97
|
+
str(wheel),
|
|
98
|
+
],
|
|
99
|
+
check=True,
|
|
100
|
+
)
|
|
101
|
+
home = root / "home"
|
|
102
|
+
home.mkdir()
|
|
103
|
+
env = {
|
|
104
|
+
k: v
|
|
105
|
+
for k, v in os.environ.items()
|
|
106
|
+
if not k.startswith(("LETTA", "NODE_", "NPM_"))
|
|
107
|
+
and k not in ("AGENT_ID", "CONVERSATION_ID", "MEMORY_DIR")
|
|
108
|
+
}
|
|
109
|
+
env.update(
|
|
110
|
+
HOME=str(home),
|
|
111
|
+
USERPROFILE=str(home),
|
|
112
|
+
PATH=str(binary),
|
|
113
|
+
DISABLE_AUTOUPDATER="0",
|
|
114
|
+
)
|
|
115
|
+
if os.name == "nt":
|
|
116
|
+
env["PATH"] += os.pathsep + str(Path(os.environ["SystemRoot"]) / "System32")
|
|
117
|
+
for args, expected, code in [
|
|
118
|
+
(["--help"], "USAGE", 0),
|
|
119
|
+
(["--version"], "Letta Code", 0),
|
|
120
|
+
(["update"], "uv tool upgrade letta", 1),
|
|
121
|
+
]:
|
|
122
|
+
result = subprocess.run(
|
|
123
|
+
[str(cli), *args],
|
|
124
|
+
cwd=root,
|
|
125
|
+
env=env,
|
|
126
|
+
check=False,
|
|
127
|
+
capture_output=True,
|
|
128
|
+
text=True,
|
|
129
|
+
timeout=45,
|
|
130
|
+
)
|
|
131
|
+
assert result.returncode == code, (args, result.stdout, result.stderr)
|
|
132
|
+
assert expected in result.stdout + result.stderr, result
|
|
133
|
+
env.update(
|
|
134
|
+
LETTA_LOCAL_BACKEND_DIR=str(root / "local-store"),
|
|
135
|
+
LETTA_LOCAL_BACKEND_EXECUTOR="deterministic",
|
|
136
|
+
LETTA_SKIP_KEYCHAIN_CHECK="1",
|
|
137
|
+
)
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
[
|
|
140
|
+
str(cli),
|
|
141
|
+
"--backend",
|
|
142
|
+
"local",
|
|
143
|
+
"--ephemeral",
|
|
144
|
+
"-m",
|
|
145
|
+
"openai/gpt-5.6-luna",
|
|
146
|
+
"-p",
|
|
147
|
+
"wheel runtime smoke",
|
|
148
|
+
"--tools=",
|
|
149
|
+
"--output-format",
|
|
150
|
+
"json",
|
|
151
|
+
],
|
|
152
|
+
cwd=root,
|
|
153
|
+
env=env,
|
|
154
|
+
check=False,
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True,
|
|
157
|
+
timeout=60,
|
|
158
|
+
)
|
|
159
|
+
assert result.returncode == 0, (result.stdout, result.stderr)
|
|
160
|
+
response = json.loads(result.stdout)
|
|
161
|
+
assert response["conversation_id"].startswith("local-conv-"), response
|
|
162
|
+
assert response["result"], response
|
|
163
|
+
print("Installed headless deterministic local turn passed")
|
|
164
|
+
argument_probe = root / "argument-probe.mjs"
|
|
165
|
+
argument_probe.write_text(ARGUMENT_PROBE, encoding="utf-8")
|
|
166
|
+
env["NODE_OPTIONS"] = "--import=" + json.dumps(argument_probe.as_uri())
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
[str(cli), "space argument", "unicode-λ", "--literal=$HOME"],
|
|
169
|
+
cwd=root,
|
|
170
|
+
env=env,
|
|
171
|
+
check=False,
|
|
172
|
+
timeout=60,
|
|
173
|
+
)
|
|
174
|
+
assert result.returncode == 23, result
|
|
175
|
+
del env["NODE_OPTIONS"]
|
|
176
|
+
payload = Path(
|
|
177
|
+
subprocess.check_output(
|
|
178
|
+
[
|
|
179
|
+
str(python),
|
|
180
|
+
"-c",
|
|
181
|
+
"from pathlib import Path; import letta_code; print(Path(letta_code.__file__).parent / '_payload')",
|
|
182
|
+
],
|
|
183
|
+
text=True,
|
|
184
|
+
).strip()
|
|
185
|
+
)
|
|
186
|
+
node = payload / "bin" / ("node.exe" if os.name == "nt" else "node")
|
|
187
|
+
installed_cli = payload / "app" / "letta.js"
|
|
188
|
+
runtime_probe = root / "runtime-probe.mjs"
|
|
189
|
+
runtime_probe.write_text(RUNTIME_PROBE, encoding="utf-8")
|
|
190
|
+
env["PATH"] = str(node.parent) + os.pathsep + env["PATH"]
|
|
191
|
+
env["LETTA_CODE_DISTRIBUTION"] = "pypi"
|
|
192
|
+
result = subprocess.run(
|
|
193
|
+
[str(node), str(runtime_probe), str(installed_cli)],
|
|
194
|
+
cwd=root,
|
|
195
|
+
env=env,
|
|
196
|
+
check=False,
|
|
197
|
+
timeout=60,
|
|
198
|
+
)
|
|
199
|
+
assert result.returncode == 0, result
|
|
200
|
+
if os.name != "nt":
|
|
201
|
+
import selectors
|
|
202
|
+
import signal
|
|
203
|
+
|
|
204
|
+
signal_probe = root / "signal.mjs"
|
|
205
|
+
signal_probe.write_text(
|
|
206
|
+
"process.on('SIGTERM', () => process.exit(42)); console.log(process.pid); setInterval(() => {}, 1000); await new Promise(() => {});"
|
|
207
|
+
)
|
|
208
|
+
env["NODE_OPTIONS"] = "--import=" + json.dumps(signal_probe.as_uri())
|
|
209
|
+
process = subprocess.Popen(
|
|
210
|
+
[str(cli)],
|
|
211
|
+
cwd=root,
|
|
212
|
+
env=env,
|
|
213
|
+
stdout=subprocess.PIPE,
|
|
214
|
+
stderr=subprocess.PIPE,
|
|
215
|
+
text=True,
|
|
216
|
+
)
|
|
217
|
+
try:
|
|
218
|
+
with selectors.DefaultSelector() as selector:
|
|
219
|
+
selector.register(process.stdout, selectors.EVENT_READ)
|
|
220
|
+
assert selector.select(timeout=15), "Signal probe did not start"
|
|
221
|
+
assert int(process.stdout.readline()) == process.pid, (
|
|
222
|
+
"Launcher did not exec Node"
|
|
223
|
+
)
|
|
224
|
+
process.send_signal(signal.SIGTERM)
|
|
225
|
+
process.communicate(timeout=15)
|
|
226
|
+
assert process.returncode == 42, process.returncode
|
|
227
|
+
finally:
|
|
228
|
+
if process.poll() is None:
|
|
229
|
+
process.kill()
|
|
230
|
+
process.wait()
|
|
231
|
+
print("POSIX PID and SIGTERM forwarding passed")
|
|
232
|
+
print(f"Installed wheel smoke passed: {wheel.name}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
main()
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"src/cli/app/AppCoordinator.tsx": 5141,
|
|
9
9
|
"src/cli/app/AppView.tsx": 1735,
|
|
10
10
|
"src/cli/app/use-approval-flow.ts": 1163,
|
|
11
|
-
"src/cli/app/use-configuration-handlers.ts":
|
|
11
|
+
"src/cli/app/use-configuration-handlers.ts": 1420,
|
|
12
12
|
"src/cli/app/use-conversation-loop.ts": 2903,
|
|
13
13
|
"src/cli/app/use-submit-handler.ts": 3979,
|
|
14
14
|
"src/cli/components/AgentSelector.tsx": 1104,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copy the locked runtime dependency closure, preserving Node's nested versions.
|
|
2
|
+
// No dependency installation happens on the user's machine.
|
|
3
|
+
import {
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
const root = process.cwd();
|
|
14
|
+
const app = resolve(process.argv[2]);
|
|
15
|
+
// sharp-electron is only reachable under Electron, never our bundled Node.
|
|
16
|
+
const roots = ["ws", "@vscode/ripgrep", "node-pty", "grammy"];
|
|
17
|
+
|
|
18
|
+
function locate(name, from) {
|
|
19
|
+
for (let dir = from; ; dir = dirname(dir)) {
|
|
20
|
+
const candidate = join(dir, "node_modules", name);
|
|
21
|
+
if (existsSync(join(candidate, "package.json")))
|
|
22
|
+
return realpathSync(candidate);
|
|
23
|
+
if (dir === dirname(dir))
|
|
24
|
+
throw new Error(`Missing locked dependency ${name} from ${from}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function compatible(pkg) {
|
|
28
|
+
return (
|
|
29
|
+
(!pkg.os || pkg.os.includes(process.platform)) &&
|
|
30
|
+
(!pkg.cpu || pkg.cpu.includes(process.arch)) &&
|
|
31
|
+
(!pkg.libc || pkg.libc.includes("glibc"))
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function copy(name, from, dest, ancestors = new Set()) {
|
|
35
|
+
const source = locate(name, from);
|
|
36
|
+
const pkg = JSON.parse(readFileSync(join(source, "package.json")));
|
|
37
|
+
if (!compatible(pkg)) return;
|
|
38
|
+
if (ancestors.has(source)) throw new Error(`Dependency cycle at ${source}`);
|
|
39
|
+
const target = join(dest, "node_modules", name);
|
|
40
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
41
|
+
cpSync(source, target, {
|
|
42
|
+
recursive: true,
|
|
43
|
+
dereference: true,
|
|
44
|
+
filter: (path) =>
|
|
45
|
+
path === source ||
|
|
46
|
+
!["node_modules", ".git"].includes(
|
|
47
|
+
path.slice(source.length + 1).split(/[\\/]/)[0],
|
|
48
|
+
),
|
|
49
|
+
});
|
|
50
|
+
const chain = new Set([...ancestors, source]);
|
|
51
|
+
const optional = pkg.optionalDependencies || {};
|
|
52
|
+
for (const dependency of Object.keys({ ...pkg.dependencies, ...optional })) {
|
|
53
|
+
try {
|
|
54
|
+
locate(dependency, source);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (dependency in optional) continue;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
copy(dependency, source, target, chain);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const name of roots) copy(name, root, app);
|
|
63
|
+
// Bun bundles Sharp JS but leaves its computed @img native requires unresolved.
|
|
64
|
+
const sharp = locate("sharp", root);
|
|
65
|
+
const sharpPackage = JSON.parse(readFileSync(join(sharp, "package.json")));
|
|
66
|
+
for (const name of Object.keys(sharpPackage.optionalDependencies || {})) {
|
|
67
|
+
try {
|
|
68
|
+
locate(name, sharp);
|
|
69
|
+
} catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
copy(name, sharp, app);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Retain license notices for code in the JS bundle as well as external modules.
|
|
76
|
+
function licenses(dir, destination) {
|
|
77
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
78
|
+
const path = join(dir, entry.name);
|
|
79
|
+
if (entry.isDirectory() && entry.name !== ".bin")
|
|
80
|
+
licenses(path, join(destination, entry.name));
|
|
81
|
+
else if (
|
|
82
|
+
entry.isFile() &&
|
|
83
|
+
/^(licen[sc]e|copying|notice)([.-]|$)/i.test(entry.name)
|
|
84
|
+
) {
|
|
85
|
+
mkdirSync(destination, { recursive: true });
|
|
86
|
+
cpSync(path, join(destination, entry.name));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
licenses(join(root, "node_modules"), join(app, "third-party-licenses"));
|
|
@@ -30,6 +30,7 @@ letta agents list [options]
|
|
|
30
30
|
| `--tags <tag1,tag2>` | Filter by tags (comma-separated) |
|
|
31
31
|
| `--match-all-tags` | Require ALL tags (default: ANY) |
|
|
32
32
|
| `--include-blocks` | Include agent.blocks in response |
|
|
33
|
+
| `--shared` | List agents shared with the current user |
|
|
33
34
|
| `--limit <n>` | Max results (default: 20) |
|
|
34
35
|
|
|
35
36
|
## Common Patterns
|
|
@@ -44,14 +45,17 @@ letta agents list --tags "origin:letta-code"
|
|
|
44
45
|
|
|
45
46
|
This is useful when the user is looking for agents they've worked with in Letta Code CLI sessions.
|
|
46
47
|
|
|
47
|
-
### Finding All Agents
|
|
48
|
+
### Finding All Accessible Agents
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
List agents owned by the current user, then list agents shared with them:
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
53
|
letta agents list
|
|
54
|
+
letta agents list --shared
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
Use `--query <text>` with either command to search by name.
|
|
58
|
+
|
|
55
59
|
## Examples
|
|
56
60
|
|
|
57
61
|
**List all agents (up to 20):**
|
|
@@ -34,7 +34,8 @@ helper below fetches the live index first, so you pick a URL that exists.
|
|
|
34
34
|
The helper retrieves `https://docs.letta.com/llms.txt` from the docs host,
|
|
35
35
|
verifies its ETag against the body, and prints the paths to a current local
|
|
36
36
|
copy and heading outline. Read the outline, then read the relevant index
|
|
37
|
-
lines to pick
|
|
37
|
+
lines and their applicability labels to pick a page matching the user's
|
|
38
|
+
backend and interface (see below).
|
|
38
39
|
3. **Fetch the specific page directly.** Pass the exact canonical URL from the
|
|
39
40
|
index back to the same helper, for example:
|
|
40
41
|
|
|
@@ -43,7 +44,8 @@ helper below fetches the live index first, so you pick a URL that exists.
|
|
|
43
44
|
--docs-url "https://docs.letta.com/configuration/models/index.md"
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
Read the returned docs path
|
|
47
|
+
Read the returned docs path, including its frontmatter, before applying its
|
|
48
|
+
instructions or running the helper for another URL. The
|
|
47
49
|
helper uses native HTTPS with a curl fallback; do not use `fetch_webpage`
|
|
48
50
|
for the normal docs route because its upstream content cache may be stale.
|
|
49
51
|
Cite the public doc URL so the user can go deeper.
|
|
@@ -53,6 +55,23 @@ helper below fetches the live index first, so you pick a URL that exists.
|
|
|
53
55
|
give your best answer, and clearly mark it as possibly out of date with a
|
|
54
56
|
link to https://docs.letta.com. Never silently fall back to memory.
|
|
55
57
|
|
|
58
|
+
## Match documentation to the user's setup
|
|
59
|
+
|
|
60
|
+
Read each page's `applies_to` frontmatter before using its instructions:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
applies_to:
|
|
64
|
+
backends: [cloud, local]
|
|
65
|
+
interfaces: [web, desktop, cli, sdk]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **Backends describe agent state, not the computer running tools.** `cloud`
|
|
69
|
+
means Letta Cloud; `local` means a user-managed backend.
|
|
70
|
+
- **Interfaces** refer to the user's current interface: chat.letta.com (`web`,
|
|
71
|
+
cloud-only), CLI, Desktop, or SDK.
|
|
72
|
+
- Do NOT use context from files marked `status: legacy` unless providing
|
|
73
|
+
historical context.
|
|
74
|
+
|
|
56
75
|
## Inspect or change your model from the CLI
|
|
57
76
|
|
|
58
77
|
- `letta model list [--byok | --hosted]` lists available models.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: self-configuration
|
|
3
|
-
description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, agent secrets, and local runtime settings. Use when the user asks how this agent or conversation is configured, asks you to change how you behave or how the harness runs you, or renames you.
|
|
3
|
+
description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, agent secrets, and local runtime settings. Use when the user asks how this agent or conversation is configured, asks about account usage, remaining credits, or model quota, asks you to change how you behave or how the harness runs you, or renames you.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -53,6 +53,16 @@ Local settings, server state, and the current process are different sources of t
|
|
|
53
53
|
- `letta model set [model_handle] [--reasoning <reasoning-option>] [--default]` changes the current conversation's model or reasoning; add `--default` only when the user asks for the agent default.
|
|
54
54
|
- `letta model get [--default]` gets the current model configuration; `--default` gets the agent's default configuration.
|
|
55
55
|
|
|
56
|
+
### Account credits and model quota
|
|
57
|
+
|
|
58
|
+
Run `letta usage` for a Markdown overview of the current plan, credit balance, and `letta/*` model quota (`lettaTier` only). Report the server's bucket (`full`, `high`, `medium`, `low`, or `empty`) and quota/daily reset timestamps as-is; do not infer exact requests or percentages. Amounts are credits, not dollars; preserve negative balances. An omitted daily reset is shown as unavailable.
|
|
59
|
+
|
|
60
|
+
The command uses CLI auth and respects `LETTA_API_KEY`/`LETTA_BASE_URL`, not agent or conversation selectors. Credits belong to the organization; user-scoped quota belongs to the authenticated user, not necessarily the person chatting with the agent. In local mode, use `letta --backend cloud usage` only when the user wants Cloud account usage.
|
|
61
|
+
|
|
62
|
+
Use `letta model list` for available models; credits and quota buckets do not guarantee inference availability. `letta usage` does not include session token statistics; the interactive `/usage` command is a separate surface. If either lookup fails, the command exits nonzero without partial usage. Treat that as unavailable data, not zero credits or exhausted quota.
|
|
63
|
+
|
|
64
|
+
### Harness and server settings
|
|
65
|
+
|
|
56
66
|
Use the secret-safe local/runtime report for harness settings, permissions, and backend diagnostics:
|
|
57
67
|
|
|
58
68
|
```bash
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: converting-mcps-to-skills
|
|
3
|
-
description: Connect to MCP (Model Context Protocol) servers and create skills for repeated use. Load when a user wants to use an MCP server, connect to external tools via MCP, or when they mention MCP, model context protocol, or specific MCP servers.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Converting MCP Servers to Skills
|
|
7
|
-
|
|
8
|
-
Letta Code is not itself an MCP client, but as a general computer-use agent, you can easily connect to any MCP server using the scripts in this skill.
|
|
9
|
-
|
|
10
|
-
## What is MCP?
|
|
11
|
-
|
|
12
|
-
MCP (Model Context Protocol) is a standard for exposing tools to AI agents. MCP servers provide tools via JSON-RPC, either over:
|
|
13
|
-
- **HTTP** - Server running at a URL (e.g., `http://localhost:3001/mcp`)
|
|
14
|
-
- **stdio** - Server runs as a subprocess, communicating via stdin/stdout
|
|
15
|
-
|
|
16
|
-
## Quick Start: Connecting to an MCP Server
|
|
17
|
-
|
|
18
|
-
### Step 1: Determine the transport type
|
|
19
|
-
|
|
20
|
-
Ask the user:
|
|
21
|
-
- Is it an HTTP server (has a URL)?
|
|
22
|
-
- Is it a stdio server (runs via command like `npx`, `node`, `python`)?
|
|
23
|
-
|
|
24
|
-
### Step 2: Test the connection
|
|
25
|
-
|
|
26
|
-
**For HTTP servers:**
|
|
27
|
-
```bash
|
|
28
|
-
npx tsx <SKILL_DIR>/scripts/mcp-http.ts <url> list-tools
|
|
29
|
-
|
|
30
|
-
# With auth header
|
|
31
|
-
npx tsx <SKILL_DIR>/scripts/mcp-http.ts <url> --header "Authorization: Bearer KEY" list-tools
|
|
32
|
-
```
|
|
33
|
-
Where `<SKILL_DIR>` is the Skill Directory shown when the skill was loaded (visible in the injection header).
|
|
34
|
-
|
|
35
|
-
**For stdio servers:**
|
|
36
|
-
```bash
|
|
37
|
-
npx tsx <SKILL_DIR>/scripts/mcp-stdio.ts "<command>" list-tools
|
|
38
|
-
|
|
39
|
-
# Examples
|
|
40
|
-
npx tsx <SKILL_DIR>/scripts/mcp-stdio.ts "npx -y @modelcontextprotocol/server-filesystem ." list-tools
|
|
41
|
-
npx tsx <SKILL_DIR>/scripts/mcp-stdio.ts "python server.py" list-tools
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Step 3: Explore available tools
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# List all tools
|
|
48
|
-
... list-tools
|
|
49
|
-
|
|
50
|
-
# Get schema for a specific tool
|
|
51
|
-
... info <tool-name>
|
|
52
|
-
|
|
53
|
-
# Test calling a tool
|
|
54
|
-
... call <tool-name> '{"arg": "value"}'
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Creating a Dedicated Skill
|
|
58
|
-
|
|
59
|
-
When an MCP server will be used repeatedly, create a dedicated skill for it. This makes future use easier and documents the server's capabilities.
|
|
60
|
-
|
|
61
|
-
### Decision: Simple vs Rich Skill
|
|
62
|
-
|
|
63
|
-
**Simple skill** (just SKILL.md):
|
|
64
|
-
- Good for straightforward servers
|
|
65
|
-
- Documents how to use the parent skill's scripts with this specific server
|
|
66
|
-
- No additional scripts needed
|
|
67
|
-
|
|
68
|
-
**Rich skill** (SKILL.md + scripts/):
|
|
69
|
-
- Good for frequently-used servers
|
|
70
|
-
- Includes convenience wrapper scripts with defaults baked in
|
|
71
|
-
- Provides a simpler interface than the generic scripts
|
|
72
|
-
|
|
73
|
-
See `references/skill-templates.md` for templates.
|
|
74
|
-
|
|
75
|
-
## Built-in Scripts Reference
|
|
76
|
-
|
|
77
|
-
### mcp-http.ts - HTTP Transport
|
|
78
|
-
|
|
79
|
-
Connects to MCP servers over HTTP. No dependencies required.
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npx tsx mcp-http.ts <url> [options] <command> [args]
|
|
83
|
-
|
|
84
|
-
Commands:
|
|
85
|
-
list-tools List available tools
|
|
86
|
-
list-resources List available resources
|
|
87
|
-
info <tool> Show tool schema
|
|
88
|
-
call <tool> '<json>' Call a tool
|
|
89
|
-
login Run OAuth flow and cache tokens for this server
|
|
90
|
-
logout Clear cached OAuth tokens for this server
|
|
91
|
-
|
|
92
|
-
Options:
|
|
93
|
-
--header "K: V" Add HTTP header (repeatable). Disables auto-OAuth.
|
|
94
|
-
--auth <mode> "auto" (default), "oauth", or "none"
|
|
95
|
-
--timeout <ms> Request timeout (default: 30000)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Examples:**
|
|
99
|
-
```bash
|
|
100
|
-
# Basic usage
|
|
101
|
-
npx tsx mcp-http.ts http://localhost:3001/mcp list-tools
|
|
102
|
-
|
|
103
|
-
# With static bearer authentication
|
|
104
|
-
npx tsx mcp-http.ts http://localhost:3001/mcp --header "Authorization: Bearer KEY" list-tools
|
|
105
|
-
|
|
106
|
-
# OAuth-protected server (opens a browser to sign in, then caches tokens)
|
|
107
|
-
npx tsx mcp-http.ts https://example.com/mcp login
|
|
108
|
-
npx tsx mcp-http.ts https://example.com/mcp list-tools
|
|
109
|
-
|
|
110
|
-
# Call a tool
|
|
111
|
-
npx tsx mcp-http.ts http://localhost:3001/mcp call vault '{"action":"search","query":"notes"}'
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**OAuth support:**
|
|
115
|
-
When a server returns `401 WWW-Authenticate: Bearer ...` and no static
|
|
116
|
-
`Authorization` header was supplied, `mcp-http.ts` will automatically:
|
|
117
|
-
|
|
118
|
-
1. Discover the authorization server via `resource_metadata`, the
|
|
119
|
-
`realm=` param, or the server's own origin (`.well-known/oauth-authorization-server`
|
|
120
|
-
then `.well-known/openid-configuration`).
|
|
121
|
-
2. Dynamically register a public client with PKCE (`token_endpoint_auth_method: none`).
|
|
122
|
-
3. Open the system browser to the authorization endpoint, catch the redirect
|
|
123
|
-
on a `127.0.0.1` loopback port, and exchange the code for tokens.
|
|
124
|
-
4. Cache the token set (and the registered client) at
|
|
125
|
-
`~/.letta/mcp-oauth/<host>_<path>.json` with `0600` perms.
|
|
126
|
-
5. Auto-refresh expired access tokens using the stored refresh token before
|
|
127
|
-
each request; if refresh fails, it re-runs the browser flow once.
|
|
128
|
-
|
|
129
|
-
Use `login` to run the flow explicitly (e.g. as a first step in a skill's
|
|
130
|
-
setup) and `logout` to clear cached tokens. Passing an explicit
|
|
131
|
-
`--header "Authorization: ..."` disables auto-OAuth so you stay in control.
|
|
132
|
-
Pass `--auth none` to force static-only behavior.
|
|
133
|
-
|
|
134
|
-
### mcp-stdio.ts - stdio Transport
|
|
135
|
-
|
|
136
|
-
Connects to MCP servers that run as subprocesses. No dependencies required.
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
npx tsx mcp-stdio.ts "<command>" [options] <action> [args]
|
|
140
|
-
|
|
141
|
-
Actions:
|
|
142
|
-
list-tools List available tools
|
|
143
|
-
list-resources List available resources
|
|
144
|
-
info <tool> Show tool schema
|
|
145
|
-
call <tool> '<json>' Call a tool
|
|
146
|
-
|
|
147
|
-
Options:
|
|
148
|
-
--env "KEY=VALUE" Set environment variable (repeatable)
|
|
149
|
-
--cwd <path> Set working directory
|
|
150
|
-
--timeout <ms> Request timeout (default: 30000)
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Examples:**
|
|
154
|
-
```bash
|
|
155
|
-
# Filesystem server
|
|
156
|
-
npx tsx mcp-stdio.ts "npx -y @modelcontextprotocol/server-filesystem ." list-tools
|
|
157
|
-
|
|
158
|
-
# With environment variable
|
|
159
|
-
npx tsx mcp-stdio.ts "node server.js" --env "API_KEY=xxx" list-tools
|
|
160
|
-
|
|
161
|
-
# Call a tool
|
|
162
|
-
npx tsx mcp-stdio.ts "python server.py" call read_file '{"path":"./README.md"}'
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Common MCP Servers
|
|
166
|
-
|
|
167
|
-
Here are some well-known MCP servers:
|
|
168
|
-
|
|
169
|
-
| Server | Transport | Command/URL |
|
|
170
|
-
|--------|-----------|-------------|
|
|
171
|
-
| Filesystem | stdio | `npx -y @modelcontextprotocol/server-filesystem <path>` |
|
|
172
|
-
| GitHub | stdio | `npx -y @modelcontextprotocol/server-github` |
|
|
173
|
-
| Brave Search | stdio | `npx -y @modelcontextprotocol/server-brave-search` |
|
|
174
|
-
| obsidian-mcp-plugin | HTTP | `http://localhost:3001/mcp` |
|
|
175
|
-
|
|
176
|
-
## Troubleshooting
|
|
177
|
-
|
|
178
|
-
**"Cannot connect" error:**
|
|
179
|
-
- For HTTP: Check the URL is correct and server is running
|
|
180
|
-
- For stdio: Check the command works when run directly in terminal
|
|
181
|
-
|
|
182
|
-
**"Authentication required" error:**
|
|
183
|
-
- Add `--header "Authorization: Bearer YOUR_KEY"` for HTTP servers using static bearers
|
|
184
|
-
- Or `--env "API_KEY=xxx"` for stdio servers that need env vars
|
|
185
|
-
- For OAuth-protected HTTP servers, just run any command (or `login`) — the helper
|
|
186
|
-
will do PKCE + dynamic client registration and cache tokens under
|
|
187
|
-
`~/.letta/mcp-oauth/`. Delete that file (or run `logout`) to force a re-login.
|
|
188
|
-
|
|
189
|
-
**OAuth issues:**
|
|
190
|
-
- "Could not discover OAuth server metadata": the server didn't include
|
|
191
|
-
`resource_metadata` and its origin doesn't serve `.well-known/oauth-authorization-server`
|
|
192
|
-
or `.well-known/openid-configuration`. Fall back to a static bearer, or point the
|
|
193
|
-
helper at the auth server manually via a custom skill.
|
|
194
|
-
- "Dynamic client registration failed": the auth server disables open DCR.
|
|
195
|
-
You'll need to pre-register a client and pass its `client_id` (and any required
|
|
196
|
-
credentials) via headers, or wrap this skill with a server-specific one.
|
|
197
|
-
- "state mismatch" / callback timeout: another process may be holding the browser
|
|
198
|
-
callback; re-run and complete the sign-in in the newly opened tab.
|
|
199
|
-
|
|
200
|
-
**Tool call fails:**
|
|
201
|
-
- Use `info <tool>` to see the expected input schema
|
|
202
|
-
- Ensure JSON arguments match the schema
|