Turn any codebase into a structured, AI-friendly format that fits in context windows.
π copytree.dev
Who is this for?
- Engineers sharing code context with AI tools (Claude, ChatGPT, etc.)
- Code reviewers capturing diffs and changes
- Documentation writers exporting structured code snippets
npm install -g copytree# Copy a file reference to the clipboard (default; XML format)
copytree
# Copy the output text itself to the clipboard instead
copytree --clipboard
# Save to file
copytree -o project-structure.xml
# Structure and metadata, without file bodies
copytree --no-content
# Copy from GitHub repository
copytree https://github.com/user/repo
# Write the export to stdout
copytree --stdout
# Note: destination behaviour β exactly one of these
# - default (no flags): writes a temp file and copies a file reference, so pasting
# into an agent hands over a file to read rather than inline context
# - -y/--clipboard: copies the output text itself
# - --stdout: writes the export to stdout
# - -o/--output: writes the export to a file
# - --reference: the default, stated explicitlyFour read-only commands answer the questions a copy cannot:
copytree plan . # exactly which files, in what order, at what size
copytree inspect . # structure, active rules, effective profile and budgets
copytree explain docs/README.md # why one path is in, or out, down to the rule and line
copytree ignore context . # a content-free inventory for authoring .copytreeignoreNone of them read file contents, write an export, or touch the clipboard.
- One ignore engine - Nested
.gitignoreat every depth,.git/info/exclude, your global gitignore,.copytreeignore, and.copytreeinclude, all applied through a single path used by both the CLI and the SDK. Nogit check-ignoresubprocess, and no git repository required - Scoped copy - Copy one folder with the whole repository's rules, using literal paths rather than globs
- Budgets that bind - Per-file size gate, total size, file count, and character budget, all applied in a defined order and always reported when they truncate
- Exclusion accounting - Every run can answer "what didn't make it, and why", down to the ignore file and line number
- Extension-first binary handling - A 3 GB
.mp4costs onestat, not an open and a read - Multiple Output Formats - XML (default), Markdown, JSON, NDJSON, SARIF, tree view, each versioned so downstream prompts can detect a change
- Git Integration - Filter by modified files, branch diffs, staged changes
- Token estimates - "Copied 47 files (~78k tokens)" is the number that decides whether you paste it
- Secrets Detection - Prevent accidental exposure of API keys and credentials
- Electron Ready - Works in Electron β₯34 main processes for desktop apps
Repeatable options take one value per occurrence: --include a --include b.
--format <xml|markdown|json|ndjson|sarif|tree>β Output format (default: xml)--profile <name>β Use a file-selection profile;--no-profileskips auto-discovery--scope <path>β Copy only this literal path (not a glob), with root-anchored ignore rules--include <glob>β Narrow the selection; a file must match at least one-x, --exclude <pattern>β Add a Git-style exclusion rule--force-include <glob>β Override ordinary excludes and the size gate--no-contentβ Structure and metadata, without file bodies--stdoutβ Write the export to stdout-v, --verboseβ Show run detail: selection summary, size and duration-q, --quietβ Say nothing about a successful run; failures still go to stderr-y, --clipboardβ Copy the output text itself, instead of a file reference--size-gate <size>/--no-size-gateβ Per-file size gate, applied before opening (default 256KB)--max-total-size <size>/--max-files <n>/--max-chars <n>β Bound the whole context--line-numbersβ Add line numbers to text content--git-statusβ Attach Git status to selected files--binary <default|omit|comment|placeholder|base64>β Binary/document policy--secrets <redact|fail|off>β Secret policy (default: redact)--dedupeβ Remove content-identical files--sort <path|size|modified|name|extension|depth>/--order <asc|desc>β Selection order, which also decides which files survive a budget
Full reference: docs/cli/copytree-reference.md, generated from the
command schema. copytree help --format json emits the same schema for machines.
# Only modified files
copytree -m
copytree --modified
# Compare with main branch
copytree -c main
copytree --changed main
# Include JS/TS files, exclude node_modules
copytree --include "**/*.js" --include "**/*.ts" --exclude "node_modules"
# Copy GitHub folder to XML
copytree https://github.com/user/repo/tree/main/src -o repo-src.xml
# Send the export to stdout (great for CI or a pipe)
copytree --stdout --format markdown > output.md
copytree --stdout --format json | jq .
# One folder, but with the whole repository's ignore rules
copytree --scope src/panels/file-browser
# Several entries at once; paths are literal, so no glob escaping
copytree --scope "src/[draft]" package.json
# Bound the context, and keep the recently-touched files when it bites
# (budgets keep the head of the sorted list, so ask for newest-first)
copytree --max-total-size 2MB --sort modified --order desc
# Preview the exact selection, with the reason each file was dropped
copytree plan .
copytree plan . --explain
# Structure only, no file bodies
copytree --no-content
# Different output formats
copytree --format json -o structure.json
copytree --format xml -o structure.xml
copytree --format markdown -o structure.mdProfiles control which files are included and how they're processed.
Create a custom profile:
# .copytree/my-profile.yml
name: my-profile
include: ['src/**/*.js', 'README.md']
exclude: ['**/*.test.js']
output:
format: markdownUse your profile:
# Use a profile by name
copytree --profile my-profile -o summary.md
# A .copytree.yml in the project is auto-discovered; skip it with
copytree --no-profileCopyTree uses hard-coded defaults to keep things simple. Configuration is managed through:
User preferences are data, not code, in the conventional location for your platform:
- Linux:
$XDG_CONFIG_HOME/copytree/config.yaml(or~/.config/copytree/config.yaml) - macOS:
~/Library/Application Support/CopyTree/config.yaml - Windows:
%APPDATA%\CopyTree\config.yaml
# config.yaml
copytree:
maxFileSize: 10485760 # 10MB
includeHidden: false
respectGitignore: trueThe legacy ~/.copytree/*.js directory is still read, with a warning. Executing JavaScript from a
home directory inside a host process is inappropriate for an embedder and unreproducible for
everyone, so prefer the data file β copytree config migrate --write converts an existing setup
and leaves the legacy directory untouched. Run copytree config show --sources to see which file
each effective value came from, and copytree config validate to check them.
Project-specific selection belongs in a profile (.copytree.yml) or an ignore file, not in user
configuration.
Use .copytreeignore in your project directory for custom exclusions (uses .gitignore syntax):
# .copytreeignore
node_modules/
*.log
.env
dist/Use .copytreeinclude to force-include specific files that would otherwise be excluded (e.g., hidden files):
# .copytreeinclude
.example/**
.github/**
config/**Note: .copytreeinclude patterns have the highest precedence and will override all other exclusion rules, including .gitignore, .copytreeignore, and profile excludes.
CopyTree is designed to be embedded, not just run. The SDK and the CLI share one selection path, so
what you get from copy() is what copytree would have printed.
import { copy, ConfigManager } from 'copytree';
// SDK calls are hermetic by default: packaged defaults only, so the same
// inputs produce the same context on every machine. Build a ConfigManager
// yourself only to reuse one across calls (loading parses every config file
// and compiles the schema), or to opt in to the user's own configuration with
// `ConfigManager.create()`.
const config = await ConfigManager.create({ userConfig: false });
const result = await copy(repoRoot, {
scope: ['src/panels/file-browser'], // literal paths, dirs or files
maxTotalSize: 2_000_000,
charLimit: 400_000,
explain: true,
config,
});
console.log(result.output); // formatted context
console.log(result.outputFormatVersion); // 'copytree-xml@1'
console.log(result.stats.estimatedTokens); // ~78000
console.log(result.stats.excluded.byReason); // { gitignore: 380, sizeGate: 1, ... }
if (result.stats.truncated) {
console.warn(`Dropped ${result.stats.truncatedCount} files (${result.stats.truncatedBy})`);
}Preview before you commit. A dry run reads nothing and formats nothing, but selects the same
files, in the same order, under the same budgets as the real run β the same guarantee
copytree plan gives on the command line:
const plan = await copy(repoRoot, { dryRun: true, config });
plan.manifest.forEach(({ path, size, outcome }) => {
// outcome: 'included' | 'structure-only' | 'binary-placeholder' | 'truncated'
console.log(outcome, path, size);
});Stream when the output is large. copyStream() takes the same options and still gives you the
numbers; chunks never split a code point, so they can go straight to a PTY or socket:
import { copyStream } from 'copytree';
let summary;
for await (const chunk of copyStream(repoRoot, { config, onComplete: (r) => (summary = r) })) {
pty.write(chunk);
}
console.log(`${summary.stats.totalFiles} files, ~${summary.stats.estimatedTokens} tokens`);Errors are typed. Switch on error.code, never on the message:
import { ERROR_CODES } from 'copytree';
try {
await copy(repoRoot, { scope: [selectedPath], config });
} catch (error) {
switch (error.code) {
case ERROR_CODES.SCOPE_OUTSIDE_ROOT:
return showOutsideProjectWarning();
case ERROR_CODES.PATH_NOT_FOUND:
return showMissingPathWarning();
case ERROR_CODES.ABORTED:
return; // user cancelled
default:
throw error;
}
}"No files matched" is not an error. An empty folder or a fully-ignored scope returns a valid
empty result with stats.noFilesMatched === true.
Full types ship with the package (types/index.d.ts).
- Node.js 22.12+ (required by engines in package.json)
For detailed guides, see the docs/ directory:
- Getting Started - Introduction and quick start
- CLI Reference - Complete command options
- Architecture - Pipeline and system design
- Troubleshooting - Common issues and solutions
- Electron Integration - Using CopyTree in Electron apps
| Command | What it does |
|---|---|
copytree [path] |
Copy, using a file reference by default |
copytree copy [path] |
The explicit form of the default operation |
copytree plan [path] |
Preview the exact selection without reading contents |
copytree inspect [path] |
Inspect structure, rules, profile and budgets |
copytree explain <entry...> |
Explain why paths are included or excluded |
copytree ignore context [path] |
Build context for authoring .copytreeignore |
copytree ignore check [path] |
Validate ignore rules and show their effect |
copytree ignore init [path] |
Print or write a conservative starter file |
copytree config show|validate|migrate |
Inspect, validate or migrate configuration |
copytree cache status|clear|gc |
Inspect or manage caches and reference files |
copytree doctor |
Check CopyTree, clipboard, Git and effective policies |
copytree completion <shell> |
Generate shell completion code |
copytree debug profile [path] |
Capture CPU and/or heap profiles |
The colon spellings (config:validate, cache:clear) still parse and name their replacement.
Note: CopyTree automatically creates required directories (e.g.,
~/.copytree/cache/) on first use. Temporary reference files land in<temp>/copytree/<project>/and are reclaimed bycopytree cache gcon a seven-day retention policy.
Large file errors
β Raise --size-gate, or --force-include the specific paths you need
Binary files
β --binary <default|omit|comment|placeholder|base64> states the policy explicitly
Memory issues
β Lower --max-total-size, or narrow the run with --scope
Slow performance β Enable caching, add more exclusion patterns
Profile not found
β copytree inspect . --view profile shows the effective profile and where it was read from
Git errors
β Ensure directory is a git repository with git status
# Check the installation, clipboard, Git and effective policies
copytree doctor
# Verbose run detail
copytree --verbose
# Inspect, validate or migrate configuration
copytree config show --sources
copytree config validate
copytree config migrate
# Caches and temporary reference files
copytree cache status
copytree cache clear
copytree cache gc
# CPU or heap profiling of a real copy
copytree debug profile . --type cpuFor more solutions, see the Troubleshooting Guide.
git clone https://github.com/gregpriday/copytree.git
cd copytree
npm install
npm link # Makes 'copytree' available globallynpm test # Run all tests
npm test -- --watch # Watch mode
npm run test:coverage # Coverage report
npm run lint # Lint code
npm run format # Format codeFor detailed testing information, see tests/README.md.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure tests pass (
npm test) and coverage meets thresholds (80%) - Run linting (
npm run lint) and formatting (npm run format) - Commit your changes
- Push to your branch
- Open a Pull Request
See our Testing Guide for more details on writing and running tests.
CopyTree is optimized for large codebases:
- Chunked output - The formatted document is never assembled as one string, so output can start reaching a file or a pipe before the last file is rendered. This is not a memory bound: the selection and its content are still resident.
- Parallel file processing - Faster for many files
- Smart caching - Avoid redundant transformations
- Configurable limits -
--max-total-size,--max-filesand--size-gateare what actually bound memory
Performance targets:
- Process 10,000 files in < 30 seconds
- Memory usage < 500MB for large projects
- Support projects up to 100MB total size
MIT License. See LICENSE file for details.
See CHANGELOG.md for version history and release notes.