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:
122
examples/release-notes/cli.ts
Executable file
122
examples/release-notes/cli.ts
Executable 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);
|
||||
227
examples/release-notes/generator.ts
Normal file
227
examples/release-notes/generator.ts
Normal 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');
|
||||
}
|
||||
16
examples/release-notes/types.ts
Normal file
16
examples/release-notes/types.ts
Normal 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',
|
||||
};
|
||||
Reference in New Issue
Block a user