@letta-ai/letta-code 0.11.1 → 0.11.2-next.2
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/letta.js +4313 -5374
- package/package.json +2 -2
- package/skills/finding-agents/SKILL.md +115 -0
- package/skills/finding-agents/scripts/find-agents.ts +177 -0
- package/skills/initializing-memory/SKILL.md +12 -0
- package/skills/migrating-memory/SKILL.md +132 -0
- package/skills/migrating-memory/scripts/attach-block.ts +161 -0
- package/skills/migrating-memory/scripts/copy-block.ts +173 -0
- package/skills/migrating-memory/scripts/get-agent-blocks.ts +103 -0
- package/skills/searching-messages/SKILL.md +127 -0
- package/skills/searching-messages/scripts/get-messages.ts +230 -0
- package/skills/searching-messages/scripts/search-messages.ts +200 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@letta-ai/letta-code",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2-next.2",
|
|
4
4
|
"description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@letta-ai/letta-client": "1.6.
|
|
33
|
+
"@letta-ai/letta-client": "1.6.4",
|
|
34
34
|
"glob": "^13.0.0",
|
|
35
35
|
"ink-link": "^5.0.0",
|
|
36
36
|
"open": "^10.2.0"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: finding-agents
|
|
3
|
+
description: Find other agents on the same server. Use when the user asks about other agents, wants to migrate memory from another agent, or needs to find an agent by name or tags.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Finding Agents
|
|
7
|
+
|
|
8
|
+
This skill helps you find other agents on the same Letta server.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- User asks about other agents they have
|
|
13
|
+
- User wants to find a specific agent by name
|
|
14
|
+
- User wants to list agents with certain tags
|
|
15
|
+
- You need to find an agent ID for memory migration
|
|
16
|
+
- You found an agent_id via message search and need details about that agent
|
|
17
|
+
|
|
18
|
+
## Script Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx ts-node scripts/find-agents.ts [options]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Options
|
|
25
|
+
|
|
26
|
+
| Option | Description |
|
|
27
|
+
|--------|-------------|
|
|
28
|
+
| `--name <name>` | Exact name match |
|
|
29
|
+
| `--query <text>` | Fuzzy search by name |
|
|
30
|
+
| `--tags <tag1,tag2>` | Filter by tags (comma-separated) |
|
|
31
|
+
| `--match-all-tags` | Require ALL tags (default: ANY) |
|
|
32
|
+
| `--include-blocks` | Include agent.blocks in response |
|
|
33
|
+
| `--limit <n>` | Max results (default: 20) |
|
|
34
|
+
|
|
35
|
+
## Common Patterns
|
|
36
|
+
|
|
37
|
+
### Finding Letta Code Agents
|
|
38
|
+
|
|
39
|
+
Agents created by Letta Code are tagged with `origin:letta-code`. To find only Letta Code agents:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx ts-node scripts/find-agents.ts --tags "origin:letta-code"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This is useful when the user is looking for agents they've worked with in Letta Code CLI sessions.
|
|
46
|
+
|
|
47
|
+
### Finding All Agents
|
|
48
|
+
|
|
49
|
+
If the user has agents created outside Letta Code (via ADE, SDK, etc.), search without the tag filter:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx ts-node scripts/find-agents.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
**List all agents (up to 20):**
|
|
58
|
+
```bash
|
|
59
|
+
npx ts-node scripts/find-agents.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Find agent by exact name:**
|
|
63
|
+
```bash
|
|
64
|
+
npx ts-node scripts/find-agents.ts --name "ProjectX-v1"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Search agents by name (fuzzy):**
|
|
68
|
+
```bash
|
|
69
|
+
npx ts-node scripts/find-agents.ts --query "project"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Find only Letta Code agents:**
|
|
73
|
+
```bash
|
|
74
|
+
npx ts-node scripts/find-agents.ts --tags "origin:letta-code"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Find agents with multiple tags:**
|
|
78
|
+
```bash
|
|
79
|
+
npx ts-node scripts/find-agents.ts --tags "frontend,production" --match-all-tags
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Include memory blocks in results:**
|
|
83
|
+
```bash
|
|
84
|
+
npx ts-node scripts/find-agents.ts --query "project" --include-blocks
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Output
|
|
88
|
+
|
|
89
|
+
Returns the raw API response with full agent details. Key fields:
|
|
90
|
+
- `id` - Agent ID (e.g., `agent-abc123`)
|
|
91
|
+
- `name` - Agent name
|
|
92
|
+
- `description` - Agent description
|
|
93
|
+
- `tags` - Agent tags
|
|
94
|
+
- `blocks` - Memory blocks (if `--include-blocks` used)
|
|
95
|
+
|
|
96
|
+
## Related Skills
|
|
97
|
+
|
|
98
|
+
- **migrating-memory** - Once you find an agent, use this skill to copy/share memory blocks
|
|
99
|
+
- **searching-messages** - Search messages across all agents to find which agent discussed a topic. Use `--all-agents` to get `agent_id` values, then use this skill to get full agent details.
|
|
100
|
+
|
|
101
|
+
### Finding Agents by Topic
|
|
102
|
+
|
|
103
|
+
If you need to find which agent worked on a specific topic:
|
|
104
|
+
|
|
105
|
+
1. Load both skills: `searching-messages` and `finding-agents`
|
|
106
|
+
2. Search messages across all agents:
|
|
107
|
+
```bash
|
|
108
|
+
search-messages.ts --query "topic" --all-agents --limit 10
|
|
109
|
+
```
|
|
110
|
+
3. Note the `agent_id` values from matching messages
|
|
111
|
+
4. Get agent details:
|
|
112
|
+
```bash
|
|
113
|
+
find-agents.ts --query "partial-name"
|
|
114
|
+
```
|
|
115
|
+
Or use the agent_id directly in the Letta API
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Find Agents - Search for agents with various filters
|
|
4
|
+
*
|
|
5
|
+
* This script is standalone and can be run outside the CLI process.
|
|
6
|
+
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx find-agents.ts [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --name <name> Exact name match
|
|
13
|
+
* --query <text> Fuzzy search by name
|
|
14
|
+
* --tags <tag1,tag2> Filter by tags (comma-separated)
|
|
15
|
+
* --match-all-tags Require ALL tags (default: ANY)
|
|
16
|
+
* --include-blocks Include agent.blocks in response
|
|
17
|
+
* --limit <n> Max results (default: 20)
|
|
18
|
+
*
|
|
19
|
+
* Output:
|
|
20
|
+
* Raw API response from GET /v1/agents
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
|
|
29
|
+
// (ES module imports don't respect NODE_PATH, but require does)
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const Letta = require("@letta-ai/letta-client")
|
|
32
|
+
.default as typeof import("@letta-ai/letta-client").default;
|
|
33
|
+
type LettaClient = InstanceType<typeof Letta>;
|
|
34
|
+
|
|
35
|
+
interface FindAgentsOptions {
|
|
36
|
+
name?: string;
|
|
37
|
+
query?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
matchAllTags?: boolean;
|
|
40
|
+
includeBlocks?: boolean;
|
|
41
|
+
limit?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get API key from env var or settings file
|
|
46
|
+
*/
|
|
47
|
+
function getApiKey(): string {
|
|
48
|
+
// First check env var (set by CLI's getShellEnv)
|
|
49
|
+
if (process.env.LETTA_API_KEY) {
|
|
50
|
+
return process.env.LETTA_API_KEY;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fall back to settings file
|
|
54
|
+
const settingsPath = join(homedir(), ".letta", "settings.json");
|
|
55
|
+
try {
|
|
56
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
57
|
+
if (settings.env?.LETTA_API_KEY) {
|
|
58
|
+
return settings.env.LETTA_API_KEY;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Settings file doesn't exist or is invalid
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error(
|
|
65
|
+
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a Letta client with auth from env/settings
|
|
71
|
+
*/
|
|
72
|
+
function createClient(): LettaClient {
|
|
73
|
+
return new Letta({ apiKey: getApiKey() });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find agents matching the given criteria
|
|
78
|
+
* @param client - Letta client instance
|
|
79
|
+
* @param options - Search options
|
|
80
|
+
* @returns Array of agent objects from the API
|
|
81
|
+
*/
|
|
82
|
+
export async function findAgents(
|
|
83
|
+
client: LettaClient,
|
|
84
|
+
options: FindAgentsOptions = {},
|
|
85
|
+
): Promise<Awaited<ReturnType<typeof client.agents.list>>> {
|
|
86
|
+
const params: Parameters<typeof client.agents.list>[0] = {
|
|
87
|
+
limit: options.limit ?? 20,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (options.name) {
|
|
91
|
+
params.name = options.name;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.query) {
|
|
95
|
+
params.query_text = options.query;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.tags && options.tags.length > 0) {
|
|
99
|
+
params.tags = options.tags;
|
|
100
|
+
if (options.matchAllTags) {
|
|
101
|
+
params.match_all_tags = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (options.includeBlocks) {
|
|
106
|
+
params.include = ["agent.blocks"];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return await client.agents.list(params);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseArgs(args: string[]): FindAgentsOptions {
|
|
113
|
+
const options: FindAgentsOptions = {};
|
|
114
|
+
|
|
115
|
+
const nameIndex = args.indexOf("--name");
|
|
116
|
+
if (nameIndex !== -1 && nameIndex + 1 < args.length) {
|
|
117
|
+
options.name = args[nameIndex + 1];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const queryIndex = args.indexOf("--query");
|
|
121
|
+
if (queryIndex !== -1 && queryIndex + 1 < args.length) {
|
|
122
|
+
options.query = args[queryIndex + 1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const tagsIndex = args.indexOf("--tags");
|
|
126
|
+
if (tagsIndex !== -1 && tagsIndex + 1 < args.length) {
|
|
127
|
+
options.tags = args[tagsIndex + 1]?.split(",").map((t) => t.trim());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (args.includes("--match-all-tags")) {
|
|
131
|
+
options.matchAllTags = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (args.includes("--include-blocks")) {
|
|
135
|
+
options.includeBlocks = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const limitIndex = args.indexOf("--limit");
|
|
139
|
+
if (limitIndex !== -1 && limitIndex + 1 < args.length) {
|
|
140
|
+
const limit = Number.parseInt(args[limitIndex + 1] as string, 10);
|
|
141
|
+
if (!Number.isNaN(limit)) {
|
|
142
|
+
options.limit = limit;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return options;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// CLI entry point - check if this file is being run directly
|
|
150
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
151
|
+
if (isMainModule) {
|
|
152
|
+
(async () => {
|
|
153
|
+
try {
|
|
154
|
+
const options = parseArgs(process.argv.slice(2));
|
|
155
|
+
const client = createClient();
|
|
156
|
+
const result = await findAgents(client, options);
|
|
157
|
+
console.log(JSON.stringify(result, null, 2));
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(
|
|
160
|
+
"Error:",
|
|
161
|
+
error instanceof Error ? error.message : String(error),
|
|
162
|
+
);
|
|
163
|
+
console.error(`
|
|
164
|
+
Usage: npx tsx find-agents.ts [options]
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
--name <name> Exact name match
|
|
168
|
+
--query <text> Fuzzy search by name
|
|
169
|
+
--tags <tag1,tag2> Filter by tags (comma-separated)
|
|
170
|
+
--match-all-tags Require ALL tags (default: ANY)
|
|
171
|
+
--include-blocks Include agent.blocks in response
|
|
172
|
+
--limit <n> Max results (default: 20)
|
|
173
|
+
`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
}
|
|
@@ -18,6 +18,18 @@ This command may be run in different scenarios:
|
|
|
18
18
|
|
|
19
19
|
Before making changes, use the `memory` tool to inspect your current memory blocks and understand what already exists.
|
|
20
20
|
|
|
21
|
+
## Memory Migration Option
|
|
22
|
+
|
|
23
|
+
If you're setting up a new agent that should inherit memory from an existing agent, consider using the `migrating-memory` skill:
|
|
24
|
+
|
|
25
|
+
1. Load the skill: `Skill({ command: "load", skills: ["migrating-memory"] })`
|
|
26
|
+
2. Follow its workflow to copy or share blocks from another agent
|
|
27
|
+
|
|
28
|
+
**When to suggest migration**:
|
|
29
|
+
- User mentions they have an existing agent with useful memory
|
|
30
|
+
- User is replacing an old agent with a new one
|
|
31
|
+
- User wants to share memory blocks across multiple agents
|
|
32
|
+
|
|
21
33
|
## What Coding Agents Should Remember
|
|
22
34
|
|
|
23
35
|
### 1. Procedures (Rules & Workflows)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrating-memory
|
|
3
|
+
description: Migrate memory blocks from an existing agent to the current agent. Use when the user wants to copy or share memory from another agent, or during /init when setting up a new agent that should inherit memory from an existing one.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migrating Memory
|
|
7
|
+
|
|
8
|
+
This skill helps migrate memory blocks from an existing agent to a new agent, similar to macOS Migration Assistant for AI agents.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- User is setting up a new agent that should inherit memory from an existing one
|
|
13
|
+
- User wants to share memory blocks across multiple agents
|
|
14
|
+
- User is replacing an old agent with a new one
|
|
15
|
+
- User mentions they have an existing agent with useful memory
|
|
16
|
+
|
|
17
|
+
## Migration Methods
|
|
18
|
+
|
|
19
|
+
### 1. Manual Copy (Recommended for partial content)
|
|
20
|
+
|
|
21
|
+
If you only need **part** of a source block, or the source is messy and needs cleanup:
|
|
22
|
+
1. Use `get-agent-blocks.ts` to view the source block's content
|
|
23
|
+
2. Use the `memory` tool to create a new block with just the content you want
|
|
24
|
+
3. No scripts needed - you have full control over what gets copied
|
|
25
|
+
|
|
26
|
+
Best for: Extracting sections, cleaning up messy content, selective migration.
|
|
27
|
+
|
|
28
|
+
### 2. Script Copy (Full block duplication)
|
|
29
|
+
|
|
30
|
+
Creates new blocks with the same content using `copy-block.ts`. After copying:
|
|
31
|
+
- You own the copy - changes don't sync
|
|
32
|
+
- Use `--label` flag if you already have a block with that label
|
|
33
|
+
- Best for: One-time migration, forking an agent
|
|
34
|
+
|
|
35
|
+
### 3. Share (Linked Blocks)
|
|
36
|
+
|
|
37
|
+
Attaches the same block to multiple agents using `attach-block.ts`. After sharing:
|
|
38
|
+
- All agents see the same block content
|
|
39
|
+
- Changes by any agent are visible to all others
|
|
40
|
+
- Can be read-only (target can read but not modify)
|
|
41
|
+
- Best for: Shared knowledge bases, synchronized state
|
|
42
|
+
|
|
43
|
+
**Note:** You cannot have two blocks with the same label. When copying, use `--label` to rename if needed.
|
|
44
|
+
|
|
45
|
+
## Workflow
|
|
46
|
+
|
|
47
|
+
### Step 1: Identify Source Agent
|
|
48
|
+
|
|
49
|
+
Ask the user for the source agent's ID (e.g., `agent-abc123`).
|
|
50
|
+
|
|
51
|
+
If they don't know the ID, load the **finding-agents** skill to search:
|
|
52
|
+
```
|
|
53
|
+
Skill({ command: "load", skills: ["finding-agents"] })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Example: "What's the ID of the agent you want to migrate memory from?"
|
|
57
|
+
|
|
58
|
+
### Step 2: View Source Agent's Blocks
|
|
59
|
+
|
|
60
|
+
Inspect what memory blocks the source agent has:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx ts-node scripts/get-agent-blocks.ts --agent-id <source-agent-id>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This shows each block's ID, label, description, and value.
|
|
67
|
+
|
|
68
|
+
### Step 3: Migrate Blocks
|
|
69
|
+
|
|
70
|
+
For each block you want to migrate, choose copy or share:
|
|
71
|
+
|
|
72
|
+
**To Copy (create independent block):**
|
|
73
|
+
```bash
|
|
74
|
+
npx ts-node scripts/copy-block.ts --block-id <block-id> [--label <new-label>]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Use `--label` if you already have a block with that label (e.g., `--label project-imported`).
|
|
78
|
+
|
|
79
|
+
**To Share (attach existing block):**
|
|
80
|
+
```bash
|
|
81
|
+
npx ts-node scripts/attach-block.ts --block-id <block-id>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Add `--read-only` flag to share to make this agent unable to modify the block.
|
|
85
|
+
|
|
86
|
+
Note: These scripts automatically target the current agent (you) for safety.
|
|
87
|
+
|
|
88
|
+
## Script Reference
|
|
89
|
+
|
|
90
|
+
All scripts are located in the `scripts/` directory and output raw API responses (JSON).
|
|
91
|
+
|
|
92
|
+
| Script | Purpose | Args |
|
|
93
|
+
|--------|---------|------|
|
|
94
|
+
| `get-agent-blocks.ts` | Get blocks from an agent | `--agent-id` |
|
|
95
|
+
| `copy-block.ts` | Copy block to current agent | `--block-id`, optional `--label` |
|
|
96
|
+
| `attach-block.ts` | Attach existing block to current agent | `--block-id`, optional `--read-only` |
|
|
97
|
+
|
|
98
|
+
## Authentication
|
|
99
|
+
|
|
100
|
+
The bundled scripts automatically use the same authentication as Letta Code:
|
|
101
|
+
- Keychain/secrets storage
|
|
102
|
+
- `~/.config/letta/settings.json` fallback
|
|
103
|
+
- `LETTA_API_KEY` environment variable
|
|
104
|
+
|
|
105
|
+
You can also make direct API calls using the Letta SDK if you have the API key available.
|
|
106
|
+
|
|
107
|
+
## Example: Migrating Project Memory
|
|
108
|
+
|
|
109
|
+
Scenario: You're a new agent and want to inherit memory from an existing agent "ProjectX-v1".
|
|
110
|
+
|
|
111
|
+
1. **Get source agent ID from user:**
|
|
112
|
+
User provides: `agent-abc123`
|
|
113
|
+
|
|
114
|
+
2. **List its blocks:**
|
|
115
|
+
```bash
|
|
116
|
+
npx ts-node scripts/get-agent-blocks.ts --agent-id agent-abc123
|
|
117
|
+
# Shows: project (block-def456), human (block-ghi789), persona (block-jkl012)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. **Copy project knowledge to yourself:**
|
|
121
|
+
```bash
|
|
122
|
+
# If you don't have a 'project' block yet:
|
|
123
|
+
npx ts-node scripts/copy-block.ts --block-id block-def456
|
|
124
|
+
|
|
125
|
+
# If you already have 'project', use --label to rename:
|
|
126
|
+
npx ts-node scripts/copy-block.ts --block-id block-def456 --label project-v1
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
4. **Optionally share human preferences (read-only):**
|
|
130
|
+
```bash
|
|
131
|
+
npx ts-node scripts/attach-block.ts --block-id block-ghi789 --read-only
|
|
132
|
+
```
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Attach Block - Attaches an existing memory block to an agent (sharing)
|
|
4
|
+
*
|
|
5
|
+
* This script is standalone and can be run outside the CLI process.
|
|
6
|
+
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
|
|
7
|
+
* It reads agent ID from LETTA_AGENT_ID env var or --agent-id arg.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only]
|
|
11
|
+
*
|
|
12
|
+
* This attaches an existing block to another agent, making it shared.
|
|
13
|
+
* Changes to the block will be visible to all agents that have it attached.
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* --agent-id Target agent ID (overrides LETTA_AGENT_ID env var)
|
|
17
|
+
* --read-only Target agent can read but not modify the block
|
|
18
|
+
*
|
|
19
|
+
* Output:
|
|
20
|
+
* Raw API response from the attach operation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
|
|
29
|
+
// (ES module imports don't respect NODE_PATH, but require does)
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const Letta = require("@letta-ai/letta-client")
|
|
32
|
+
.default as typeof import("@letta-ai/letta-client").default;
|
|
33
|
+
type LettaClient = InstanceType<typeof Letta>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get API key from env var or settings file
|
|
37
|
+
*/
|
|
38
|
+
function getApiKey(): string {
|
|
39
|
+
if (process.env.LETTA_API_KEY) {
|
|
40
|
+
return process.env.LETTA_API_KEY;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const settingsPath = join(homedir(), ".letta", "settings.json");
|
|
44
|
+
try {
|
|
45
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
46
|
+
if (settings.env?.LETTA_API_KEY) {
|
|
47
|
+
return settings.env.LETTA_API_KEY;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Settings file doesn't exist or is invalid
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(
|
|
54
|
+
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get agent ID from CLI arg, env var, or throw
|
|
60
|
+
*/
|
|
61
|
+
function getAgentId(cliArg?: string): string {
|
|
62
|
+
if (cliArg) return cliArg;
|
|
63
|
+
if (process.env.LETTA_AGENT_ID) {
|
|
64
|
+
return process.env.LETTA_AGENT_ID;
|
|
65
|
+
}
|
|
66
|
+
throw new Error(
|
|
67
|
+
"No agent ID provided. Use --agent-id or ensure LETTA_AGENT_ID env var is set.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a Letta client with auth from env/settings
|
|
73
|
+
*/
|
|
74
|
+
function createClient(): LettaClient {
|
|
75
|
+
return new Letta({ apiKey: getApiKey() });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Attach an existing block to the current agent (sharing it)
|
|
80
|
+
* @param client - Letta client instance
|
|
81
|
+
* @param blockId - The block ID to attach
|
|
82
|
+
* @param readOnly - Whether this agent should have read-only access
|
|
83
|
+
* @param targetAgentId - Optional target agent ID (defaults to current agent)
|
|
84
|
+
* @returns API response from the attach operation
|
|
85
|
+
*/
|
|
86
|
+
export async function attachBlock(
|
|
87
|
+
client: LettaClient,
|
|
88
|
+
blockId: string,
|
|
89
|
+
readOnly = false,
|
|
90
|
+
targetAgentId?: string,
|
|
91
|
+
): Promise<Awaited<ReturnType<typeof client.agents.blocks.attach>>> {
|
|
92
|
+
// Get current agent ID (the agent calling this script) or use provided ID
|
|
93
|
+
const currentAgentId = getAgentId(targetAgentId);
|
|
94
|
+
|
|
95
|
+
const result = await client.agents.blocks.attach(blockId, {
|
|
96
|
+
agent_id: currentAgentId,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// If read-only is requested, update the block's read_only flag for this agent
|
|
100
|
+
// Note: This may require a separate API call depending on how read_only works
|
|
101
|
+
if (readOnly) {
|
|
102
|
+
// The read_only flag is per-block, not per-agent attachment
|
|
103
|
+
// For now, we'll note this in the output
|
|
104
|
+
console.warn(
|
|
105
|
+
"Note: read_only flag is set on the block itself, not per-agent. " +
|
|
106
|
+
"Use the block update API to set read_only if needed.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseArgs(args: string[]): {
|
|
114
|
+
blockId: string;
|
|
115
|
+
readOnly: boolean;
|
|
116
|
+
agentId?: string;
|
|
117
|
+
} {
|
|
118
|
+
const blockIdIndex = args.indexOf("--block-id");
|
|
119
|
+
const agentIdIndex = args.indexOf("--agent-id");
|
|
120
|
+
const readOnly = args.includes("--read-only");
|
|
121
|
+
|
|
122
|
+
if (blockIdIndex === -1 || blockIdIndex + 1 >= args.length) {
|
|
123
|
+
throw new Error("Missing required argument: --block-id <block-id>");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
blockId: args[blockIdIndex + 1] as string,
|
|
128
|
+
readOnly,
|
|
129
|
+
agentId:
|
|
130
|
+
agentIdIndex !== -1 && agentIdIndex + 1 < args.length
|
|
131
|
+
? (args[agentIdIndex + 1] as string)
|
|
132
|
+
: undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// CLI entry point - check if this file is being run directly
|
|
137
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
138
|
+
if (isMainModule) {
|
|
139
|
+
(async () => {
|
|
140
|
+
try {
|
|
141
|
+
const { blockId, readOnly, agentId } = parseArgs(process.argv.slice(2));
|
|
142
|
+
const client = createClient();
|
|
143
|
+
const result = await attachBlock(client, blockId, readOnly, agentId);
|
|
144
|
+
console.log(JSON.stringify(result, null, 2));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error(
|
|
147
|
+
"Error:",
|
|
148
|
+
error instanceof Error ? error.message : String(error),
|
|
149
|
+
);
|
|
150
|
+
if (
|
|
151
|
+
error instanceof Error &&
|
|
152
|
+
error.message.includes("Missing required argument")
|
|
153
|
+
) {
|
|
154
|
+
console.error(
|
|
155
|
+
"\nUsage: npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only]",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
}
|