feat: add basic utility examples with agent persistence

Three practical examples showing core SDK capabilities:

- **bug-fixer**: Find and fix bugs, remembers codebase patterns
- **release-notes**: Generate release notes from git commits
- **file-organizer**: Organize directories, learns preferences

Each example uses agent persistence (getOrCreateAgent pattern) so the
agent remembers context across sessions - the key Letta differentiator.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
This commit is contained in:
Cameron Pfiffer
2026-01-27 15:51:27 -08:00
parent d5bbce6dec
commit 3b4811f035
9 changed files with 1137 additions and 0 deletions

122
examples/release-notes/cli.ts Executable file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bun
/**
* Release Notes Generator CLI
*
* Generate release notes from git commits.
*
* Usage:
* bun cli.ts v1.0.0 # Notes from v1.0.0 to HEAD
* bun cli.ts v1.0.0 v1.1.0 # Notes from v1.0.0 to v1.1.0
* bun cli.ts v1.0.0 -o RELEASE.md # Output to file
* bun cli.ts --status # Show agent status
* bun cli.ts --reset # Reset agent
*/
import { parseArgs } from 'node:util';
import {
loadState,
saveState,
getOrCreateAgent,
generateReleaseNotes,
showStatus,
reset,
} from './generator.js';
async function main() {
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
output: { type: 'string', short: 'o' },
status: { type: 'boolean', default: false },
reset: { type: 'boolean', default: false },
help: { type: 'boolean', short: 'h', default: false },
},
allowPositionals: true,
});
if (values.help) {
printHelp();
return;
}
if (values.reset) {
await reset();
return;
}
const state = await loadState();
if (values.status) {
await showStatus(state);
return;
}
if (positionals.length === 0) {
console.log('Error: Please specify a git ref (tag, branch, or commit).\n');
console.log('Examples:');
console.log(' bun cli.ts v1.0.0 # From v1.0.0 to HEAD');
console.log(' bun cli.ts v1.0.0 v1.1.0 # From v1.0.0 to v1.1.0');
console.log(' bun cli.ts HEAD~10 # Last 10 commits');
return;
}
const fromRef = positionals[0];
const toRef = positionals[1] || 'HEAD';
// Get or create the agent
const agent = await getOrCreateAgent(state);
// Save agent ID if new
if (!state.agentId && agent.agentId) {
state.agentId = agent.agentId;
await saveState(state);
console.log(`\x1b[90m[Agent: ${agent.agentId}]\x1b[0m`);
console.log(`\x1b[90m[→ https://app.letta.com/agents/${agent.agentId}]\x1b[0m\n`);
}
await generateReleaseNotes(agent, state, fromRef, toRef, values.output);
agent.close();
}
function printHelp() {
console.log(`
📝 Release Notes Generator
Generate release notes from git commits. Remembers your formatting preferences.
USAGE:
bun cli.ts <from-ref> [to-ref] Generate notes for commit range
bun cli.ts <from-ref> -o FILE Output to file
bun cli.ts --status Show agent status
bun cli.ts --reset Reset agent (forget preferences)
bun cli.ts -h, --help Show this help
ARGUMENTS:
from-ref Starting point (tag, branch, commit SHA)
to-ref Ending point (default: HEAD)
OPTIONS:
-o, --output FILE Write release notes to file
EXAMPLES:
bun cli.ts v1.0.0 # Changes since v1.0.0
bun cli.ts v1.0.0 v1.1.0 # Changes between two tags
bun cli.ts HEAD~20 # Last 20 commits
bun cli.ts v1.0.0 -o CHANGELOG.md # Write to file
CATEGORIES:
The agent automatically categorizes commits:
- 🚀 Features
- 🐛 Bug Fixes
- 💥 Breaking Changes
- 📚 Documentation
- 🔧 Maintenance
PERSISTENCE:
The agent learns your formatting preferences over time.
`);
}
main().catch(console.error);

View File

@@ -0,0 +1,227 @@
/**
* Release Notes Generator Agent
*
* A persistent agent that generates release notes from git commits.
* Remembers past releases and learns your formatting preferences.
*/
import { readFile, writeFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createSession, resumeSession, type Session } from '../../src/index.js';
import { ReleaseNotesState, DEFAULT_CONFIG } from './types.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const STATE_FILE = join(__dirname, 'state.json');
// ANSI colors
const COLORS = {
agent: '\x1b[35m', // Magenta
system: '\x1b[90m', // Gray
success: '\x1b[32m', // Green
reset: '\x1b[0m',
};
const SYSTEM_PROMPT = `You are a release notes generator. Your job is to create clear, useful release notes from git commits.
## Your Capabilities
- Run git commands to analyze commits
- Read files to understand changes
- Write release notes to files
## Your Approach
1. **Get commits** - Use git log to get commits in the specified range
2. **Categorize** - Group changes into:
- 🚀 Features (new functionality)
- 🐛 Bug Fixes
- 💥 Breaking Changes
- 📚 Documentation
- 🔧 Maintenance/Chores
3. **Summarize** - Write human-readable descriptions, not raw commit messages
4. **Format** - Use clean markdown with consistent style
## Memory
You remember past releases. Use this to:
- Maintain consistent formatting
- Reference version numbers correctly
- Avoid duplicating content from past releases
## Output Format
\`\`\`markdown
# Release Notes - vX.Y.Z
## 🚀 Features
- Feature description
## 🐛 Bug Fixes
- Fix description
## 💥 Breaking Changes
- What changed and migration steps
## 📚 Documentation
- Doc updates
## 🔧 Maintenance
- Chores, refactors, deps
\`\`\`
Skip empty sections. Be concise but informative.`;
/**
* Load state from disk
*/
export async function loadState(): Promise<ReleaseNotesState> {
if (existsSync(STATE_FILE)) {
const data = await readFile(STATE_FILE, 'utf-8');
return JSON.parse(data);
}
return {
agentId: null,
releasesGenerated: 0,
};
}
/**
* Save state to disk
*/
export async function saveState(state: ReleaseNotesState): Promise<void> {
await writeFile(STATE_FILE, JSON.stringify(state, null, 2));
}
/**
* Get or create the release notes agent
*/
export async function getOrCreateAgent(state: ReleaseNotesState): Promise<Session> {
if (state.agentId) {
console.log(`${COLORS.system}Resuming release notes agent...${COLORS.reset}`);
return resumeSession(state.agentId, {
model: DEFAULT_CONFIG.model,
allowedTools: ['Bash', 'Read', 'Write', 'Glob'],
permissionMode: 'bypassPermissions',
});
}
console.log(`${COLORS.system}Creating new release notes agent...${COLORS.reset}`);
const session = await createSession({
model: DEFAULT_CONFIG.model,
systemPrompt: SYSTEM_PROMPT,
memory: [
{
label: 'release-history',
value: `# Release History
## Past Releases
(Releases I've generated)
## Formatting Preferences
(Learned from feedback)
## Project Context
(What this project does)`,
description: 'History of releases and formatting preferences',
},
],
allowedTools: ['Bash', 'Read', 'Write', 'Glob'],
permissionMode: 'bypassPermissions',
});
return session;
}
/**
* Stream output with color
*/
function createStreamPrinter(): (text: string) => void {
return (text: string) => {
process.stdout.write(`${COLORS.agent}${text}${COLORS.reset}`);
};
}
/**
* Send a message and get response
*/
export async function chat(
session: Session,
message: string,
onOutput?: (text: string) => void
): Promise<string> {
await session.send(message);
let response = '';
const printer = onOutput || createStreamPrinter();
for await (const msg of session.receive()) {
if (msg.type === 'assistant') {
response += msg.content;
printer(msg.content);
} else if (msg.type === 'tool_call') {
console.log(`\n${COLORS.system}[${msg.name}]${COLORS.reset}`);
} else if (msg.type === 'tool_result') {
console.log(`${COLORS.system}[done]${COLORS.reset}\n`);
}
}
return response;
}
/**
* Generate release notes
*/
export async function generateReleaseNotes(
session: Session,
state: ReleaseNotesState,
fromRef: string,
toRef: string = 'HEAD',
outputFile?: string
): Promise<void> {
console.log(`\n${COLORS.system}Generating release notes for ${fromRef}..${toRef}...${COLORS.reset}\n`);
let prompt = `Generate release notes for the commits between ${fromRef} and ${toRef}.
Steps:
1. Run \`git log ${fromRef}..${toRef} --oneline\` to see the commits
2. If needed, read specific files to understand changes better
3. Categorize and summarize the changes
4. Output clean, formatted release notes`;
if (outputFile) {
prompt += `\n\nWrite the release notes to: ${outputFile}`;
}
await chat(session, prompt, createStreamPrinter());
// Update count
state.releasesGenerated++;
await saveState(state);
console.log(`\n\n${COLORS.success}Release notes generated.${COLORS.reset}`);
console.log(`${COLORS.system}Total releases generated: ${state.releasesGenerated}${COLORS.reset}\n`);
}
/**
* Show status
*/
export async function showStatus(state: ReleaseNotesState): Promise<void> {
console.log('\n📝 Release Notes Generator Status\n');
console.log(`Agent: ${state.agentId || '(not created yet)'}`);
if (state.agentId) {
console.log(` → https://app.letta.com/agents/${state.agentId}`);
}
console.log(`Releases generated: ${state.releasesGenerated}`);
console.log('');
}
/**
* Reset state
*/
export async function reset(): Promise<void> {
if (existsSync(STATE_FILE)) {
const fs = await import('node:fs/promises');
await fs.unlink(STATE_FILE);
}
console.log('\n🗑 Release notes generator reset. Agent forgotten.\n');
}

View File

@@ -0,0 +1,16 @@
/**
* Release Notes Generator Types
*/
export interface ReleaseNotesState {
agentId: string | null;
releasesGenerated: number;
}
export interface ReleaseNotesConfig {
model: string;
}
export const DEFAULT_CONFIG: ReleaseNotesConfig = {
model: 'sonnet',
};