@letta-ai/letta-code 0.11.2-next.2 → 0.11.2-next.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.11.2-next.2",
3
+ "version": "0.11.2-next.4",
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": {
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: acquiring-skills
3
+ description: Guide for safely discovering and installing skills from external repositories. Use when a user asks for something where a specialized skill likely exists (browser testing, PDF processing, document generation, etc.) and you want to bootstrap your understanding rather than starting from scratch.
4
+ ---
5
+
6
+ # Acquiring New Skills
7
+
8
+ This skill teaches you how to safely discover and install skills from external sources.
9
+
10
+ ## SAFETY - READ THIS FIRST
11
+
12
+ Skills can contain:
13
+ - **Markdown files** (.md) - Risk: prompt injection, misleading instructions
14
+ - **Scripts** (Python, TypeScript, Bash) - Risk: malicious code execution
15
+
16
+ ### Trusted Sources (no user approval needed for download)
17
+ - `https://github.com/letta-ai/skills` - Letta's community skills
18
+ - `https://github.com/anthropics/skills` - Anthropic's official skills
19
+
20
+ ### Untrusted Sources (ALWAYS verify with user)
21
+ For ANY source other than letta-ai or anthropics:
22
+ 1. Ask the user before downloading
23
+ 2. Explain where the skill comes from
24
+ 3. Get explicit approval
25
+
26
+ ### Script Safety
27
+ Even for skills from trusted sources, ALWAYS:
28
+ 1. Read and inspect any scripts before executing them
29
+ 2. Understand what the script does
30
+ 3. Be wary of network calls, file operations, or system commands
31
+
32
+ ## When to Use This Skill
33
+
34
+ **DO use** when:
35
+ - User asks for something where a skill likely exists (e.g., "help me test this webapp", "generate a PDF report")
36
+ - You think "there's probably a skill that would bootstrap my understanding"
37
+ - User explicitly asks about available skills or extending capabilities
38
+
39
+ **DON'T use** for:
40
+ - General coding tasks you can already handle
41
+ - Simple bug fixes or feature implementations
42
+ - Tasks where you have sufficient knowledge
43
+
44
+ ## Ask Before Searching (Interactive Mode)
45
+
46
+ If you recognize a task that might have an associated skill, **ask the user first**:
47
+
48
+ > "This sounds like something where a community skill might help (e.g., webapp testing with Playwright). Would you like me to look for available skills in the Letta or Anthropic repositories? This might take a minute, or I can start coding right away if you prefer."
49
+
50
+ The user may prefer to start immediately rather than wait for skill discovery.
51
+
52
+ Only proceed with skill acquisition if the user agrees.
53
+
54
+ ## Skill Repositories
55
+
56
+ | Repository | Description |
57
+ |------------|-------------|
58
+ | https://github.com/letta-ai/skills | Community skills for Letta agents |
59
+ | https://github.com/anthropics/skills | Anthropic's official Agent Skills |
60
+
61
+ Browse these repositories to discover available skills. Check the README for skill listings.
62
+
63
+ ## Installation Locations
64
+
65
+ | Location | Path | When to Use |
66
+ |----------|------|-------------|
67
+ | **Global** | `~/.letta/skills/<skill>/` | General-purpose skills useful across projects |
68
+ | **Project** | `.skills/<skill>/` | Project-specific skills |
69
+
70
+ **Rule**: If useful across multiple projects, install globally. If project-specific, install in `.skills/`.
71
+
72
+ ## How to Download Skills
73
+
74
+ Skills are directories containing SKILL.md and optionally scripts/, references/, examples/.
75
+
76
+ ### Method: Clone to /tmp, then copy
77
+
78
+ ```bash
79
+ # 1. Clone the repo (shallow)
80
+ git clone --depth 1 https://github.com/anthropics/skills /tmp/skills-temp
81
+
82
+ # 2. Copy the skill to your skills directory
83
+ # For global:
84
+ cp -r /tmp/skills-temp/skills/webapp-testing ~/.letta/skills/
85
+ # For project:
86
+ cp -r /tmp/skills-temp/skills/webapp-testing .skills/
87
+
88
+ # 3. Cleanup
89
+ rm -rf /tmp/skills-temp
90
+ ```
91
+
92
+ ### Alternative: rsync (preserves permissions)
93
+
94
+ ```bash
95
+ git clone --depth 1 https://github.com/anthropics/skills /tmp/skills-temp
96
+ rsync -av /tmp/skills-temp/skills/webapp-testing/ ~/.letta/skills/webapp-testing/
97
+ rm -rf /tmp/skills-temp
98
+ ```
99
+
100
+ ## Registering New Skills
101
+
102
+ After downloading, refresh the skills list:
103
+
104
+ ```
105
+ Skill(command: "refresh")
106
+ ```
107
+
108
+ This scans `~/.letta/skills/` and `.skills/` and updates your `skills` memory block.
109
+
110
+ ## Complete Example
111
+
112
+ User asks: "Can you help me test my React app's UI?"
113
+
114
+ 1. **Recognize opportunity**: Browser/webapp testing - likely has a skill
115
+ 2. **Ask user**: "Would you like me to look for webapp testing skills, or start coding right away?"
116
+ 3. **If user agrees, find skill**: Check anthropics/skills for webapp-testing
117
+ 4. **Download** (trusted source):
118
+ ```bash
119
+ git clone --depth 1 https://github.com/anthropics/skills /tmp/skills-temp
120
+ cp -r /tmp/skills-temp/skills/webapp-testing ~/.letta/skills/
121
+ rm -rf /tmp/skills-temp
122
+ ```
123
+ 5. **Refresh**: `Skill(command: "refresh")`
124
+ 6. **Inspect scripts**: Read any .py or .ts files before using them
125
+ 7. **Load**: `Skill(command: "load", skills: ["webapp-testing"])`
126
+ 8. **Use**: Follow the skill's instructions for the user's task
@@ -263,7 +263,7 @@ When creating a new skill from scratch, always run the `init-skill.ts` script. T
263
263
  Usage:
264
264
 
265
265
  ```bash
266
- npx ts-node scripts/init-skill.ts <skill-name> --path <output-directory>
266
+ npx tsx <SKILL_DIR>/scripts/init-skill.ts <skill-name> --path <output-directory>
267
267
  ```
268
268
 
269
269
  The script:
@@ -335,13 +335,13 @@ Write instructions for using the skill and its bundled resources.
335
335
  Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements:
336
336
 
337
337
  ```bash
338
- npx ts-node scripts/package-skill.ts <path/to/skill-folder>
338
+ npx tsx <SKILL_DIR>/scripts/package-skill.ts <path/to/skill-folder>
339
339
  ```
340
340
 
341
341
  Optional output directory specification:
342
342
 
343
343
  ```bash
344
- npx ts-node scripts/package-skill.ts <path/to/skill-folder> ./dist
344
+ npx tsx <SKILL_DIR>/scripts/package-skill.ts <path/to/skill-folder> ./dist
345
345
  ```
346
346
 
347
347
  The packaging script will:
@@ -18,7 +18,7 @@ This skill helps you find other agents on the same Letta server.
18
18
  ## Script Usage
19
19
 
20
20
  ```bash
21
- npx ts-node scripts/find-agents.ts [options]
21
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts [options]
22
22
  ```
23
23
 
24
24
  ### Options
@@ -39,7 +39,7 @@ npx ts-node scripts/find-agents.ts [options]
39
39
  Agents created by Letta Code are tagged with `origin:letta-code`. To find only Letta Code agents:
40
40
 
41
41
  ```bash
42
- npx ts-node scripts/find-agents.ts --tags "origin:letta-code"
42
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --tags "origin:letta-code"
43
43
  ```
44
44
 
45
45
  This is useful when the user is looking for agents they've worked with in Letta Code CLI sessions.
@@ -49,39 +49,39 @@ This is useful when the user is looking for agents they've worked with in Letta
49
49
  If the user has agents created outside Letta Code (via ADE, SDK, etc.), search without the tag filter:
50
50
 
51
51
  ```bash
52
- npx ts-node scripts/find-agents.ts
52
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts
53
53
  ```
54
54
 
55
55
  ## Examples
56
56
 
57
57
  **List all agents (up to 20):**
58
58
  ```bash
59
- npx ts-node scripts/find-agents.ts
59
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts
60
60
  ```
61
61
 
62
62
  **Find agent by exact name:**
63
63
  ```bash
64
- npx ts-node scripts/find-agents.ts --name "ProjectX-v1"
64
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --name "ProjectX-v1"
65
65
  ```
66
66
 
67
67
  **Search agents by name (fuzzy):**
68
68
  ```bash
69
- npx ts-node scripts/find-agents.ts --query "project"
69
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --query "project"
70
70
  ```
71
71
 
72
72
  **Find only Letta Code agents:**
73
73
  ```bash
74
- npx ts-node scripts/find-agents.ts --tags "origin:letta-code"
74
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --tags "origin:letta-code"
75
75
  ```
76
76
 
77
77
  **Find agents with multiple tags:**
78
78
  ```bash
79
- npx ts-node scripts/find-agents.ts --tags "frontend,production" --match-all-tags
79
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --tags "frontend,production" --match-all-tags
80
80
  ```
81
81
 
82
82
  **Include memory blocks in results:**
83
83
  ```bash
84
- npx ts-node scripts/find-agents.ts --query "project" --include-blocks
84
+ npx tsx <SKILL_DIR>/scripts/find-agents.ts --query "project" --include-blocks
85
85
  ```
86
86
 
87
87
  ## Output
@@ -29,7 +29,6 @@ Best for: Extracting sections, cleaning up messy content, selective migration.
29
29
 
30
30
  Creates new blocks with the same content using `copy-block.ts`. After copying:
31
31
  - You own the copy - changes don't sync
32
- - Use `--label` flag if you already have a block with that label
33
32
  - Best for: One-time migration, forking an agent
34
33
 
35
34
  ### 3. Share (Linked Blocks)
@@ -40,7 +39,31 @@ Attaches the same block to multiple agents using `attach-block.ts`. After sharin
40
39
  - Can be read-only (target can read but not modify)
41
40
  - Best for: Shared knowledge bases, synchronized state
42
41
 
43
- **Note:** You cannot have two blocks with the same label. When copying, use `--label` to rename if needed.
42
+ ## Handling Duplicate Label Errors
43
+
44
+ **You cannot have two blocks with the same label.** If you try to copy/attach a block and you already have one with that label, you'll get a `duplicate key value violates unique constraint` error.
45
+
46
+ **Solutions:**
47
+
48
+ 1. **Use `--label` (copy only):** Rename the block when copying:
49
+ ```bash
50
+ npx tsx <SKILL_DIR>/scripts/copy-block.ts --block-id <id> --label project-imported
51
+ ```
52
+
53
+ 2. **Use `--override` (copy or attach):** Automatically detach your existing block first:
54
+ ```bash
55
+ npx tsx <SKILL_DIR>/scripts/copy-block.ts --block-id <id> --override
56
+ npx tsx <SKILL_DIR>/scripts/attach-block.ts --block-id <id> --override
57
+ ```
58
+ If the operation fails, the original block is automatically reattached.
59
+
60
+ 3. **Manual detach first:** Use the `memory` tool to detach your existing block:
61
+ ```
62
+ memory(agent_state, "delete", path="/memories/<label>")
63
+ ```
64
+ Then run the copy/attach script.
65
+
66
+ **Note:** `attach-block.ts` does NOT support `--label` because attached blocks keep their original label (they're shared, not copied).
44
67
 
45
68
  ## Workflow
46
69
 
@@ -60,7 +83,7 @@ Example: "What's the ID of the agent you want to migrate memory from?"
60
83
  Inspect what memory blocks the source agent has:
61
84
 
62
85
  ```bash
63
- npx ts-node scripts/get-agent-blocks.ts --agent-id <source-agent-id>
86
+ npx tsx <SKILL_DIR>/scripts/get-agent-blocks.ts --agent-id <source-agent-id>
64
87
  ```
65
88
 
66
89
  This shows each block's ID, label, description, and value.
@@ -71,14 +94,14 @@ For each block you want to migrate, choose copy or share:
71
94
 
72
95
  **To Copy (create independent block):**
73
96
  ```bash
74
- npx ts-node scripts/copy-block.ts --block-id <block-id> [--label <new-label>]
97
+ npx tsx <SKILL_DIR>/scripts/copy-block.ts --block-id <block-id> [--label <new-label>]
75
98
  ```
76
99
 
77
100
  Use `--label` if you already have a block with that label (e.g., `--label project-imported`).
78
101
 
79
102
  **To Share (attach existing block):**
80
103
  ```bash
81
- npx ts-node scripts/attach-block.ts --block-id <block-id>
104
+ npx tsx <SKILL_DIR>/scripts/attach-block.ts --block-id <block-id>
82
105
  ```
83
106
 
84
107
  Add `--read-only` flag to share to make this agent unable to modify the block.
@@ -92,8 +115,8 @@ All scripts are located in the `scripts/` directory and output raw API responses
92
115
  | Script | Purpose | Args |
93
116
  |--------|---------|------|
94
117
  | `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` |
118
+ | `copy-block.ts` | Copy block to current agent | `--block-id`, optional `--label`, `--override` |
119
+ | `attach-block.ts` | Attach existing block to current agent | `--block-id`, optional `--read-only`, `--override` |
97
120
 
98
121
  ## Authentication
99
122
 
@@ -113,20 +136,20 @@ Scenario: You're a new agent and want to inherit memory from an existing agent "
113
136
 
114
137
  2. **List its blocks:**
115
138
  ```bash
116
- npx ts-node scripts/get-agent-blocks.ts --agent-id agent-abc123
139
+ npx tsx <SKILL_DIR>/scripts/get-agent-blocks.ts --agent-id agent-abc123
117
140
  # Shows: project (block-def456), human (block-ghi789), persona (block-jkl012)
118
141
  ```
119
142
 
120
143
  3. **Copy project knowledge to yourself:**
121
144
  ```bash
122
145
  # If you don't have a 'project' block yet:
123
- npx ts-node scripts/copy-block.ts --block-id block-def456
146
+ npx tsx <SKILL_DIR>/scripts/copy-block.ts --block-id block-def456
124
147
 
125
148
  # If you already have 'project', use --label to rename:
126
- npx ts-node scripts/copy-block.ts --block-id block-def456 --label project-v1
149
+ npx tsx <SKILL_DIR>/scripts/copy-block.ts --block-id block-def456 --label project-v1
127
150
  ```
128
151
 
129
152
  4. **Optionally share human preferences (read-only):**
130
153
  ```bash
131
- npx ts-node scripts/attach-block.ts --block-id block-ghi789 --read-only
154
+ npx tsx <SKILL_DIR>/scripts/attach-block.ts --block-id block-ghi789 --read-only
132
155
  ```
@@ -7,7 +7,7 @@
7
7
  * It reads agent ID from LETTA_AGENT_ID env var or --agent-id arg.
8
8
  *
9
9
  * Usage:
10
- * npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only]
10
+ * npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only] [--override]
11
11
  *
12
12
  * This attaches an existing block to another agent, making it shared.
13
13
  * Changes to the block will be visible to all agents that have it attached.
@@ -15,6 +15,8 @@
15
15
  * Options:
16
16
  * --agent-id Target agent ID (overrides LETTA_AGENT_ID env var)
17
17
  * --read-only Target agent can read but not modify the block
18
+ * --override If you already have a block with the same label, detach it first
19
+ * (on error, the original block is reattached)
18
20
  *
19
21
  * Output:
20
22
  * Raw API response from the attach operation
@@ -75,49 +77,109 @@ function createClient(): LettaClient {
75
77
  return new Letta({ apiKey: getApiKey() });
76
78
  }
77
79
 
80
+ interface AttachBlockResult {
81
+ attachResult: Awaited<ReturnType<LettaClient["agents"]["blocks"]["attach"]>>;
82
+ detachedBlock?: Awaited<ReturnType<LettaClient["blocks"]["retrieve"]>>;
83
+ }
84
+
78
85
  /**
79
86
  * Attach an existing block to the current agent (sharing it)
80
87
  * @param client - Letta client instance
81
88
  * @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)
89
+ * @param options - readOnly, targetAgentId, override (detach existing block with same label)
84
90
  * @returns API response from the attach operation
85
91
  */
86
92
  export async function attachBlock(
87
93
  client: LettaClient,
88
94
  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
95
+ options?: { readOnly?: boolean; targetAgentId?: string; override?: boolean },
96
+ ): Promise<AttachBlockResult> {
97
+ const currentAgentId = getAgentId(options?.targetAgentId);
98
+ let detachedBlock:
99
+ | Awaited<ReturnType<LettaClient["blocks"]["retrieve"]>>
100
+ | undefined;
101
+
102
+ // If override is requested, check for existing block with same label and detach it
103
+ if (options?.override) {
104
+ // Get the block we're trying to attach to find its label
105
+ const sourceBlock = await client.blocks.retrieve(blockId);
106
+ const sourceLabel = sourceBlock.label;
107
+
108
+ // Get current agent's blocks to check for label conflict
109
+ const currentBlocksResponse =
110
+ await client.agents.blocks.list(currentAgentId);
111
+ // The response may be paginated or an array depending on SDK version
112
+ const currentBlocks = Array.isArray(currentBlocksResponse)
113
+ ? currentBlocksResponse
114
+ : (currentBlocksResponse as { items?: unknown[] }).items || [];
115
+ const conflictingBlock = currentBlocks.find(
116
+ (b: { label?: string }) => b.label === sourceLabel,
117
+ );
118
+
119
+ if (conflictingBlock) {
120
+ console.error(
121
+ `Detaching existing block with label "${sourceLabel}" (${conflictingBlock.id})...`,
122
+ );
123
+ detachedBlock = conflictingBlock;
124
+ try {
125
+ await client.agents.blocks.detach(conflictingBlock.id, {
126
+ agent_id: currentAgentId,
127
+ });
128
+ } catch (detachError) {
129
+ throw new Error(
130
+ `Failed to detach existing block "${sourceLabel}": ${detachError instanceof Error ? detachError.message : String(detachError)}`,
131
+ );
132
+ }
133
+ }
134
+ }
135
+
136
+ // Attempt to attach the new block
137
+ let attachResult: Awaited<ReturnType<typeof client.agents.blocks.attach>>;
138
+ try {
139
+ attachResult = await client.agents.blocks.attach(blockId, {
140
+ agent_id: currentAgentId,
141
+ });
142
+ } catch (attachError) {
143
+ // If attach failed and we detached a block, try to reattach it
144
+ if (detachedBlock) {
145
+ console.error(
146
+ `Attach failed, reattaching original block "${detachedBlock.label}"...`,
147
+ );
148
+ try {
149
+ await client.agents.blocks.attach(detachedBlock.id, {
150
+ agent_id: currentAgentId,
151
+ });
152
+ console.error("Original block reattached successfully.");
153
+ } catch {
154
+ console.error(
155
+ `WARNING: Failed to reattach original block! Block ID: ${detachedBlock.id}`,
156
+ );
157
+ }
158
+ }
159
+ throw attachError;
160
+ }
161
+
162
+ // If read-only is requested, note the limitation
163
+ if (options?.readOnly) {
104
164
  console.warn(
105
165
  "Note: read_only flag is set on the block itself, not per-agent. " +
106
166
  "Use the block update API to set read_only if needed.",
107
167
  );
108
168
  }
109
169
 
110
- return result;
170
+ return { attachResult, detachedBlock };
111
171
  }
112
172
 
113
173
  function parseArgs(args: string[]): {
114
174
  blockId: string;
115
175
  readOnly: boolean;
176
+ override: boolean;
116
177
  agentId?: string;
117
178
  } {
118
179
  const blockIdIndex = args.indexOf("--block-id");
119
180
  const agentIdIndex = args.indexOf("--agent-id");
120
181
  const readOnly = args.includes("--read-only");
182
+ const override = args.includes("--override");
121
183
 
122
184
  if (blockIdIndex === -1 || blockIdIndex + 1 >= args.length) {
123
185
  throw new Error("Missing required argument: --block-id <block-id>");
@@ -126,6 +188,7 @@ function parseArgs(args: string[]): {
126
188
  return {
127
189
  blockId: args[blockIdIndex + 1] as string,
128
190
  readOnly,
191
+ override,
129
192
  agentId:
130
193
  agentIdIndex !== -1 && agentIdIndex + 1 < args.length
131
194
  ? (args[agentIdIndex + 1] as string)
@@ -138,9 +201,15 @@ const isMainModule = import.meta.url === `file://${process.argv[1]}`;
138
201
  if (isMainModule) {
139
202
  (async () => {
140
203
  try {
141
- const { blockId, readOnly, agentId } = parseArgs(process.argv.slice(2));
204
+ const { blockId, readOnly, override, agentId } = parseArgs(
205
+ process.argv.slice(2),
206
+ );
142
207
  const client = createClient();
143
- const result = await attachBlock(client, blockId, readOnly, agentId);
208
+ const result = await attachBlock(client, blockId, {
209
+ readOnly,
210
+ override,
211
+ targetAgentId: agentId,
212
+ });
144
213
  console.log(JSON.stringify(result, null, 2));
145
214
  } catch (error) {
146
215
  console.error(
@@ -152,7 +221,7 @@ if (isMainModule) {
152
221
  error.message.includes("Missing required argument")
153
222
  ) {
154
223
  console.error(
155
- "\nUsage: npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only]",
224
+ "\nUsage: npx tsx attach-block.ts --block-id <block-id> [--agent-id <agent-id>] [--read-only] [--override]",
156
225
  );
157
226
  }
158
227
  process.exit(1);