Codebuff is a tool for editing codebases via natural language instruction to Buffy, an expert AI programming assistant.
- Developer Productivity: Reduce time and effort for common programming tasks
- Learning and Adaptation: Develop a system that learns from user interactions
- Focus on power users: Make expert software engineers move even faster
- TypeScript: Primary programming language
- Bun: Package manager and runtime
- WebSockets: Real-time communication between client and server
- LLMs: Multiple providers (Anthropic, OpenAI, Gemini, etc.) for various coding tasks
- LLM Integration: Processes natural language instructions and generates code changes
- WebSocket Server: Handles real-time communication between client and backend
- File Management: Reads, parses, and modifies project files
- Action Handling: Processes various client and server actions
- Knowledge Management: Handles creation, updating, and organization of knowledge files
- Terminal Command Execution: Allows running shell commands in user's terminal
- Client connects to WebSocket server
- Client sends user input and file context to server
- Server processes input using LLMs
- Server streams response chunks back to client
- Client receives and displays response in real-time
- Server sends file changes to client for application
- Tools are defined in
backend/src/tools.tsand implemented innpm-app/src/tool-handlers.ts - Available tools: read_files, write_file, str_replace, run_terminal_command, code_search, browser_logs, spawn_agents, web_search, read_docs, run_file_change_hooks, and others
- Backend uses tool calls to request additional information or perform actions
- Client-side handles tool calls and sends results back to server
- LLM-based Agents: Traditional agents defined in
backend/src/templates/using prompts and LLM models - Programmatic Agents: Custom agents using JavaScript/TypeScript generator functions in
.agents/templates/ - Dynamic Agent Templates: User-defined agents in TypeScript files with
handleStepsgenerator functions - Agent templates define available tools, spawnable sub-agents, and execution behavior
- Programmatic agents allow complex orchestration logic, conditional flows, and iterative refinement
- Generator functions execute in secure QuickJS sandbox for safety
- Both types integrate seamlessly through the same tool execution system
- ESC key to toggle menu or stop AI response
- CTRL+C to exit the application
Codebuff supports shell shims for direct command invocation without the codebuff prefix.
- Cross-platform: Works on Windows (CMD/PowerShell), macOS, and Linux (bash/zsh/fish)
- Store integration: Uses fully qualified agent IDs from the agent store
- Easy management: Install, update, list, and uninstall shims via CLI commands### Quick Start (Recommended)
# One-step setup: install and add to PATH automatically
codebuff shims install codebuff/base-lite@1.0.0
# Use immediately in current session (follow the printed instruction)
eval "$(codebuff shims env)"
# Now use direct commands!
base-lite "fix this bug" # Works right away!- Use Bun for all package management operations
- Run commands with
buninstead ofnpm(e.g.,bun installnotnpm install) - Use
bun runfor script execution
- Use
bun run clean-tsto remove all TypeScript build artifacts (.tsbuildinfo files and .next cache) - This resolves infinite loop issues in the typechecker caused by corrupted or stale build cache
- Typechecker infinite loops are often caused by stale .tsbuildinfo files or circular project references
- Always clean build state when encountering persistent type errors or infinite loops
- The monorepo structure with project references can sometimes create dependency cycles
- The
debug.tsfile provides logging functionality for debugging - Error messages are logged to console and debug log files
- WebSocket errors are caught and logged in server and client code
- Project uses environment variables for sensitive information (API keys)
- WebSocket connections should be secured in production (WSS)
- User input is validated and sanitized before processing
- File operations are restricted to project directory
- Prefer specific imports over import * to make dependencies explicit
- Exception: When mocking modules with many internal dependencies (like isomorphic-git), use import * to avoid listing every internal function
Always use spyOn() instead of mock.module() for function and method mocking.
- When mocking modules is required (for the purposes of overriding constants instead of functions), use the wrapper functions found in
@codebuff/common/testing/mock-modules.ts.mockModuleis a drop-in replacement formock.module, but the module should be the absolute module path (e.g.,@codebuff/common/dbinstead of../db).- Make sure to call
clearMockedModules()inafterAllto restore the original module implementations.
Preferred approach:
// ✅ Good: Use spyOn for clear, explicit mocking
import { spyOn, beforeEach, afterEach } from 'bun:test'
import * as analytics from '../analytics'
beforeEach(() => {
// Spy on module functions
spyOn(analytics, 'trackEvent').mockImplementation(() => {})
spyOn(analytics, 'initAnalytics').mockImplementation(() => {})
// Spy on global functions like Date.now and setTimeout
spyOn(Date, 'now').mockImplementation(() => 1234567890)
spyOn(global, 'setTimeout').mockImplementation((callback, delay) => {
// Custom timeout logic for tests
return 123 as any
})
})
afterEach(() => {
// Restore all mocks
mock.restore()
})Real examples from our codebase:
// From main-prompt.test.ts - Mocking LLM APIs
spyOn(aisdk, 'promptAiSdk').mockImplementation(() =>
Promise.resolve('Test response'),
)
spyOn(aisdk, 'promptAiSdkStream').mockImplementation(async function* () {
yield 'Test response'
})
// From rage-detector.test.ts - Mocking Date
spyOn(Date, 'now').mockImplementation(() => currentTime)
// From run-agent-step-tools.test.ts - Mocking imported modules
spyOn(websocketAction, 'requestFiles').mockImplementation(
async (ws: any, paths: string[]) => {
const results: Record<string, string | null> = {}
paths.forEach((p) => {
if (p === 'src/auth.ts') {
results[p] = 'export function authenticate() { return true; }'
} else {
results[p] = null
}
})
return results
},
)Use mock.module() only for entire module replacement:
// ✅ Good: Use mock.module for replacing entire modules
mock.module('../util/logger', () => ({
logger: {
debug: () => {},
error: () => {},
info: () => {},
warn: () => {},
},
withLoggerContext: async (context: any, fn: () => Promise<any>) => fn(),
}))
// ✅ Good: Mock entire module with multiple exports using anonymous function
mock.module('../services/api-client', () => ({
fetchUserData: jest.fn().mockResolvedValue({ id: 1, name: 'Test User' }),
updateUserProfile: jest.fn().mockResolvedValue({ success: true }),
deleteUser: jest.fn().mockResolvedValue(true),
ApiError: class MockApiError extends Error {
constructor(
message: string,
public status: number,
) {
super(message)
}
},
API_ENDPOINTS: {
USERS: '/api/users',
PROFILES: '/api/profiles',
},
}))Benefits of spyOn:
- Easier to restore original functionality with
mock.restore() - Clearer test isolation
- Doesn't interfere with global state (mock.module carries over from test file to test file, which is super bad and unintuitive.)
- Simpler debugging when mocks fail
Extract duplicative mock state to beforeEach for cleaner tests:
// ✅ Good: Extract common mock objects to beforeEach
describe('My Tests', () => {
let mockFileContext: ProjectFileContext
let mockAgentTemplate: DynamicAgentTemplate
beforeEach(() => {
// Setup common mock data
mockFileContext = {
projectRoot: '/test',
cwd: '/test',
// ... other properties
}
mockAgentTemplate = {
id: 'test-agent',
version: '1.0.0',
// ... other properties
}
})
test('should work with mock data', () => {
const agentTemplate = {
'test-agent': {
...mockAgentTemplate,
handleSteps: 'custom function',
} as any, // Use type assertion when needed
}
const fileContext = {
...mockFileContext,
agentTemplates: agentTemplate,
}
// ... test logic
})
})Benefits:
- Reduces code duplication across tests
- Makes tests more maintainable
- Ensures consistent mock data structure
- Easier to update mock data in one place
Important constants are centralized in common/src/constants.ts:
CREDITS_REFERRAL_BONUS: Credits awarded for successful referral- Credit limits for different user types
IMPORTANT: Referral codes must be applied through the npm-app CLI, not through the web interface.
- Web onboarding flow shows instructions for entering codes in CLI
- Users must type their referral code in the Codebuff terminal after login
- Auto-redemption during web login was removed to prevent abuse
- The
handleReferralCodefunction innpm-app/src/client.tshandles CLI redemption - The
redeemReferralCodefunction inweb/src/app/api/referrals/helpers.tsprocesses the actual credit granting
Problem: NextAuth doesn't preserve referral codes through OAuth flow because:
- NextAuth generates its own state parameter for CSRF/PKCE protection
- Custom state parameters are ignored/overwritten
- OAuth callback URLs don't always survive the round trip
Solution: Multi-layer approach implemented in SignInButton and ReferralRedirect components:
- Primary: Use absolute callback URLs with referral codes for better NextAuth preservation
- Fallback: Store referral codes in localStorage before OAuth starts
- Recovery: ReferralRedirect component on home page catches missed referrals and redirects to onboard page
This project uses Infisical for secret management. All secrets are injected at runtime.
The release mechanism uses the CODEBUFF_GITHUB_TOKEN environment variable directly. The old complex GitHub App token generation system has been removed in favor of using a simple personal access token or the infisical-managed token.
To run any service locally, use the exec runner script from root package.json, which wraps commands with infisical run --.
Example: bun run exec -- bun --cwd backend dev
Environment variables are defined and validated in packages/internal/src/env.ts. This module provides type-safe env objects for use throughout the monorepo.
The .bin/bun script automatically wraps bun commands with infisical when secrets are needed. It prevents nested infisical calls by checking for NEXT_PUBLIC_INFISICAL_UP environment variable, ensuring infisical runs only once at the top level while nested bun commands inherit the environment variables.
Worktree Support: The wrapper automatically detects and loads .env.worktree files when present, allowing worktrees to override Infisical environment variables (like ports) for local development. This enables multiple worktrees to run simultaneously on different ports without conflicts.
The wrapper also loads environment variables in the correct precedence order:
- Infisical secrets are loaded first (if needed)
.env.worktreeis loaded second to override any conflicting variables- This ensures worktree-specific overrides (like custom ports) always take precedence over cached Infisical defaults
The wrapper looks for .env.worktree in the project root directory, making it work consistently regardless of the current working directory when bun commands are executed.
Performance Optimizations: The wrapper uses --silent flag with Infisical to reduce CLI output overhead and sets INFISICAL_DISABLE_UPDATE_CHECK=true to skip version checks for faster startup times.
Infisical Caching: The wrapper implements robust caching of environment variables in .infisical-cache with a 15-minute TTL (configurable via INFISICAL_CACHE_TTL). This reduces startup time from ~1.2s to ~0.16s (87% improvement). The cache uses infisical export which outputs secrets directly in KEY='value' format, ensuring ONLY Infisical-managed secrets are cached (no system environment variables). Multi-line secrets like RSA private keys are handled correctly using source command. Cache automatically invalidates when .infisical.json is modified or after TTL expires. Uses subshell execution to avoid changing the main shell's working directory.
Session Validation: The wrapper detects expired Infisical sessions using infisical export with a robust 10-second timeout implementation that works cross-platform (macOS and Linux). Uses background processes with polling to prevent hanging on interactive prompts. Valid sessions output environment variables in KEY='value' format, while expired sessions either output interactive prompts or timeout. Provides clear error messages directing users to run infisical login.
A Python package skeleton exists in python-app. Currently a placeholder that suggests installing the npm version.
Codebuff provides starter templates for initializing new projects:
codebuff --create <template> [project-name]Templates are maintained in the codebuff community repo. Each directory corresponds to a template usable with the --create flag.
Important: When adding database indexes or schema changes, modify the schema file directly (common/src/db/schema.ts) using Drizzle's index syntax, then run the migration generation script to create the actual migration files.
Do NOT write migration SQL files directly. The proper workflow is:
- Update
common/src/db/schema.tswith new indexes using Drizzle syntax - Run the migration generation script to create the SQL migration files
- Apply the migrations using the deployment process
Example of adding performance indexes:
index('idx_table_optimized')
.on(table.column1, table.column2)
.where(sql`${table.status} = 'completed'`)