원클릭으로
coding-patterns
Architecture and design patterns for lettactl. Reference when building new features, displays, or commands.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Architecture and design patterns for lettactl. Reference when building new features, displays, or commands.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Thorough code review covering architecture, code quality, and performance. Use when asked to "review this plan", "review code", "audit architecture", "check code quality", or "review for performance". Walks through issues interactively with tradeoff analysis and opinionated recommendations.
Manage Letta AI agent fleets with kubectl-style CLI
Use when releasing features - covers issues, branches, tests, commits, PRs, versioning, and publishing
Use when deploying agents from YAML configuration files
Use when managing individual agents - creating, updating, deleting, listing, or describing
Use when performing bulk operations like cleanup, delete-all, or bulk messaging
| name | coding-patterns |
| description | Architecture and design patterns for lettactl. Reference when building new features, displays, or commands. |
All UX/display code lives in src/lib/ux/display/. Commands never format output directly — they transform data into typed interfaces and call display functions.
For any list of entries shown chronologically (messages, archival memory, etc.), use the shared entry-list.ts module:
import { displayEntryList, EntryListItem } from './entry-list';
const items: EntryListItem[] = data.map(d => ({
metaLine: chalk.dim(d.timestamp) + purple(` [${d.label}]`),
content: d.text,
}));
return displayEntryList('Title (N)', items, optionalNote);
metaLine: pre-formatted metadata (timestamp, role, tags, score, etc.)content: text body — truncated for summary views, full for --full viewsshouldUseFancyUx()Used by: messages.ts, archival.ts
For detail views and structured data (describe command output), use box.ts:
import { createBox, createBoxWithRows, BoxRow } from '../box';
// Key-value box
const rows: BoxRow[] = [
{ key: 'ID', value: data.id },
{ key: 'Name', value: data.name },
];
lines.push(...createBox('Title', rows, width));
// Free-form rows box
const items = data.map(d => chalk.white(d.name));
lines.push(...createBoxWithRows('Section (N)', items, width));
Used by: details.ts (describe command)
For resource listings (get agents, get blocks, etc.), use the table helpers in resources.ts:
Used by: resources.ts (get command list views)
| View Type | Pattern | Example |
|---|---|---|
| Chronological entries | Entry List | messages, archival memory |
| Resource listing | Table | get agents, get blocks, get tools |
| Single resource detail | Box | describe agent, describe block |
| Full content dump | Entry List (--full) | get archival --full |
Every display file follows the same structure:
AgentData, ArchivalEntryData)shouldUseFancyUx()New display modules go in src/lib/ux/display/ with a re-export in index.ts.
Command (get.ts, describe.ts)
→ Fetches raw data from LettaClientWrapper
→ Transforms to typed display interface
→ Calls OutputFormatter adapter (output-formatter.ts)
→ Calls display function (display/*.ts)
→ Returns formatted string
→ output() to stdout
Commands never import chalk or format strings. OutputFormatter adapters transform raw SDK responses to typed display data.
All SDK calls go through src/lib/letta-client.ts. Never call the SDK directly from commands. Wrapper methods handle:
Each command lives in its own directory under src/commands/<command>/:
src/commands/
├── get/
│ ├── index.ts # Re-exports, router logic
│ ├── types.ts # Interfaces, constants (SUPPORTED_RESOURCES, etc.)
│ ├── agents.ts # Handler for `get agents`
│ ├── blocks.ts # Handler for `get blocks`
│ └── ... # One file per resource type
├── delete/
│ ├── index.ts # Router + re-exports (deleteAgentWithCleanup for SDK)
│ ├── types.ts # DELETE_SUPPORTED_RESOURCES, options interfaces
│ ├── agent.ts # Single agent deletion
│ ├── mcp-server.ts # Single MCP server deletion
│ ├── all-agents.ts # Bulk delete handlers
│ └── ...
└── messages/
├── index.ts # Re-exports all commands
├── types.ts # ListOptions, SendOptions, etc.
├── list.ts # listMessagesCommand
├── send.ts # sendMessageCommand
├── utils.ts # Shared helpers (getMessageContent, formatElapsedTime)
└── ...
| File | Purpose |
|---|---|
index.ts | Re-exports public API, router logic if needed |
types.ts | Interfaces, option types, constants like SUPPORTED_RESOURCES |
<handler>.ts | One file per subcommand or resource type |
utils.ts | Shared helpers used by multiple handlers |
For commands with multiple subcommands (get, describe, delete), the index.ts acts as a router:
// src/commands/get/index.ts
import { getAgents } from './agents';
import { getBlocks } from './blocks';
import { SUPPORTED_RESOURCES } from './types';
export default async function getCommand(resource: string, name: string, options: GetOptions, command: any) {
if (!SUPPORTED_RESOURCES.includes(resource)) {
throw new Error(`Unsupported resource: ${resource}`);
}
switch (resource) {
case 'agents': return getAgents(name, options, command);
case 'blocks': return getBlocks(name, options, command);
// ...
}
}
For single-function commands (health, context, export), keep it simple:
// src/commands/health/index.ts
export { healthCommand } from './health';
// src/commands/health/health.ts
export async function healthCommand(options: HealthOptions, command: any) {
// implementation
}
When a function needs to be available to the SDK (like deleteAgentWithCleanup), re-export it from index.ts:
// src/commands/delete/index.ts
export { deleteAgentWithCleanup } from './agent';
src/commands/<cmd>/): one directory per command, router + handlerssrc/lib/ux/display/): all formatting, one file per domainsrc/lib/letta-client.ts): all SDK callssrc/lib/agent-resolver.ts): agent name → ID resolutionsrc/lib/): validators, error handling, resource utilities--no-ux: plain output, no colors/boxes (CI/CD mode)--no-spinner: disable loading spinners-o json: JSON output (bypasses all display formatting)--full: show full content instead of truncated (entry list views)--short: truncate content more aggressively (block content views)--query <text>: semantic search (archival memory)