Complete reference for CopyTree configuration system, including precedence rules, environment variables, and profile configuration.
CopyTree uses a simple two-level configuration system:
User config > Default config
Settings are loaded from:
- Built-in defaults from
config/*.jsfiles - User overrides from
~/.copytree/*.jsor~/.copytree/*.jsonfiles
The two levels are deeply merged, with user configuration overriding defaults.
# Default config (config/app.js)
maxFileSize: 10MB
# User config (~/.copytree/app.js)
maxFileSize: 20MB
# Effective value: 20MB (user config wins)Built-in defaults (lowest precedence):
{
maxFileSize: 10485760, // 10MB
maxTotalSize: 104857600, // 100MB
maxFileCount: 10000,
respectGitignore: true,
includeHidden: false,
followSymlinks: false,
defaultProfile: 'default',
cacheEnabled: true,
cacheTTL: 3600000 // 1 hour
}Global settings for all projects:
Location: ~/.copytree/*.js or ~/.copytree/*.json
JavaScript Format:
// ~/.copytree/app.js
export default {
maxFileSize: 20971520, // 20MB
maxTotalSize: 209715200, // 200MB
respectGitignore: true,
includeHidden: false,
// Default profile for all projects
defaultProfile: 'default',
// Cache settings
cache: {
enabled: true,
ttl: 3600000, // 1 hour
directory: '~/.copytree/cache'
}
};JSON Format:
{
"maxFileSize": 20971520,
"maxTotalSize": 209715200,
"respectGitignore": true,
"includeHidden": false,
"defaultProfile": "default",
"cache": {
"enabled": true,
"ttl": 3600000,
"directory": "~/.copytree/cache"
}
}Note: Configuration file names must match the config section name (e.g., app.js, ai.js).
| Config Key | CLI Flag | Type | Default | Description |
|---|---|---|---|---|
maxFileSize |
N/A | bytes | 10485760 | Memory-safety ceiling; nothing above it is ever read |
sizeGate |
--size-gate |
bytes | false | 262144 | Per-file gate applied from stat(), before opening |
maxTotalSize |
--max-total-size |
bytes | 104857600 | Total size budget across all files |
maxFileCount |
--max-files |
number | 10000 | Maximum number of files |
maxCharacterLimit |
--char-limit |
chars | 2000000 | Character budget across all file content |
defaultProfile |
--profile |
string | default |
Default profile to use |
respectGitignore |
N/A | boolean | true |
Respect gitignore rules (see below) |
gitignore.nested |
N/A | boolean | true |
Read .gitignore at every depth, not just the root |
gitignore.infoExclude |
N/A | boolean | true |
Read .git/info/exclude |
gitignore.globalExcludesFile |
N/A | boolean | true |
Read the user's global gitignore (core.excludesFile) |
includeHidden |
N/A | boolean | false |
Include hidden files |
followSymlinks |
--follow-symlinks |
boolean | false |
Follow symbolic links |
binaryExtensions |
N/A | object | see below | Extension groups classified as binary without opening the file |
exclusionReport.topN |
--explain |
number | 50 | How many of the largest exclusions to detail |
cache.enabled |
N/A | boolean | true |
Enable caching |
cache.ttl |
N/A | milliseconds | 3600000 | Cache time-to-live |
Three different things bound a run, and they are not interchangeable:
maxFileSize(10MB) is about memory. Nothing above it is ever read, and nothing lifts it.sizeGate(256KB) is about context. No single 256KB+ file belongs in a model's context window. It is decided fromstat(), so an oversized file is never opened. Onlyalways/.copytreeincludelifts it, and the override is reported.charLimittruncates after reading, at a line boundary, and marks the cut.
Set sizeGate: false (or pass --no-size-gate) to disable the gate.
One documented order, lowest precedence to highest. Last match wins, as in git.
- Config
globalExcludedDirectories/globalExcludedFiles - Global gitignore (
core.excludesFile) .git/info/exclude- Root
.gitignore - Nested
.gitignore(deepest last) - Root
.copytreeignore - Nested
.copytreeignore(deepest last) --excludepatterns.copytreeinclude/--always(highest: overrides everything above)
All of this is plain filesystem reads. CopyTree never shells out to git check-ignore and never
requires the target to be a git repository, so it works on a plain folder.
Excluded directories are pruned, not descended into. A pruned directory therefore counts as a single entry in the exclusion report, representing its whole subtree.
binaryExtensions groups extensions by category (video, audio, image, design, model3d,
font, archive, diskImage, package, executable, debug, database, mlWeights,
dataBlob, document, cert, other).
The extension is checked first: a file whose extension appears in any group is classified
straight from the path, with no open and no read. A 3 GB .mp4 costs the same as a 40 KB
.ts. Content sniffing (magic numbers, null bytes, non-printable ratio) only runs for extensions
that are not listed.
Groups are replaced wholesale by user config, so a project can override one category without restating the rest:
// ~/.copytree/copytree.js — treat .key as project text, not key material
module.exports = {
binaryExtensions: {
cert: ['.pem', '.der', '.crt', '.cer', '.p12', '.pfx', '.jks', '.gpg'],
},
};Source-code extensions are protected regardless of configuration. .ts is TypeScript far more
often than MPEG transport stream, and .html is source code in most repositories, so listing them
in a group has no effect. The full protected list is in config/copytree.js; the failure mode is
silent, which is why it is enforced in code rather than left to convention.
Text formats that are usually small and occasionally enormous (.json, .csv, .xml, .sql,
.log, .txt, .snap) are deliberately not classified by extension: a rule would be wrong in
both directions. They are bounded by sizeGate instead.
Profiles control file selection. See Your First Custom Profile for details.
1. CLI --profile flag
2. User config defaultProfile
3. Built-in default profile (automatic)
Example:
# 1. CLI flag (highest precedence)
copytree --profile api-docs
# 2. User config
# ~/.copytree/app.js
export default {
defaultProfile: 'api-docs'
};
# 3. Built-in default (automatic)
copytree # Uses built-in default profileWhen loading a profile by name, CopyTree searches in this order:
1. Project: .copytree/<name>.yml
2. User: ~/.copytree/profiles/<name>.yml
3. Built-in: <install>/profiles/<name>.yml
Transformers handle file loading and processing. The built-in transformers include:
- file-loader: Loads file content
- binary: Handles binary files with placeholder or base64 encoding
- streaming-file-loader: Streams large files (>10MB) for memory efficiency
# In profile file (.copytree/myprofile.yml)
transformers:
file-loader:
enabled: trueCheck your effective configuration:
# Validate syntax and values
copytree config:validate
# Show configuration with sources
copytree config:inspectExample output:
Configuration Validation: ✓ Valid
Effective Configuration:
maxFileSize: 20971520 (20MB)
Source: User config (~/.copytree/app.js)
maxTotalSize: 104857600 (100MB)
Source: Default config
defaultProfile: "myproject"
Source: User config (~/.copytree/app.js)
respectGitignore: true
Source: Default config
cache.enabled: true
Source: Default config
Valid configuration options:
# File size limits
maxFileSize: 10485760 # Maximum single file size (bytes)
maxTotalSize: 104857600 # Maximum total size of all files (bytes)
maxFileCount: 10000 # Maximum number of files to process
# File discovery
respectGitignore: true # Respect .gitignore rules
includeHidden: false # Include hidden files (starting with .)
followSymlinks: false # Follow symbolic links
# Default behavior
defaultProfile: "default" # Default profile to use
defaultFormat: "xml" # Default output format (xml, json, markdown, tree)
# Cache configuration
cache:
enabled: true # Enable caching
ttl: 3600000 # Cache time-to-live (milliseconds)
directory: "~/.copytree/cache" # Cache directory
# Additional exclusions (merged with profile)
additionalExclusions:
- "vendor/**"
- "storage/**"
- "*.log"
# Performance
streaming:
enabled: false # Enable streaming mode
threshold: 10485760 # Stream files larger than this (bytes)
# Debug
verbose: false # Enable verbose logging
debug: false # Enable debug loggingOptimize for large codebases:
# .copytree/config.yml
maxFileSize: 52428800 # 50MB
maxTotalSize: 1073741824 # 1GB
maxFileCount: 50000
streaming:
enabled: true
threshold: 10485760 # Stream files >10MB
cache:
enabled: true
ttl: 7200000 # 2 hoursStandardize settings via shared profile:
# .copytree/team-standard.yml (committed to repo)
name: team-standard
description: Team standard profile
include:
- "src/**"
- "docs/**"
exclude:
- "vendor/**"
- "storage/**"
- "coverage/**"
- "*.log"
transformers:
file-loader:
enabled: trueNote: Configuration files (not profiles) cannot be shared at the project level. Use profiles for team standardization.
Use CLI flags for CI environments:
# CI script
copytree --profile ci-minimal --displaySet personal preferences globally:
// ~/.copytree/app.js
export default {
defaultProfile: 'mydefault',
defaultFormat: 'markdown',
maxFileSize: 52428800, // 50MB
cache: {
enabled: true,
ttl: 14400000 // 4 hours
},
// Prefer hidden files for my workflow
includeHidden: true
};~/.copytree is a feature for a CLI and a hazard for an application. If you ship CopyTree inside a
product, the context an agent receives should not depend on a file outside the project, outside
your control, invisible in your UI, and different on every teammate's machine — and a .js file
there is arbitrary code executed in your process.
import { ConfigManager } from 'copytree';
const config = await ConfigManager.create({
userConfig: false, // skip ~/.copytree entirely
strict: true, // throw ERR_CONFIG_INVALID instead of continuing with a partial config
});
// A config that failed to load has no exclusion lists at all, which looks like
// success and is not. Check rather than assume.
if (!config.isDefaultsLoaded) {
throw new Error(config.getLoadErrors().map((e) => `${e.scope}: ${e.message}`).join('; '));
}configSources: ['defaults'] is equivalent to userConfig: false and reads more explicitly when
you want the source list spelled out.
A loaded instance is safe to reuse across concurrent copy() / scan() calls. Create one per
process or per project rather than one per call: loading parses every config file and compiles the
JSON schema, which is measurable startup cost. Only set() and reload() mutate an instance.
Nothing in the configuration or the pipeline consults process.cwd(). Everything resolves from the
package directory and the basePath you pass, so a worker thread whose working directory is an app
bundle gets the same answer as a shell in the repository.
Location: ~/.copytree/*.json
{
"maxFileSize": 20971520,
"defaultProfile": "myproject",
"cache": {
"enabled": true,
"ttl": 3600000
}
}Location: ~/.copytree/*.js
// ~/.copytree/app.js
export default {
maxFileSize: 20 * 1024 * 1024, // 20MB
defaultProfile: 'myproject',
cache: {
enabled: true,
ttl: 60 * 60 * 1000, // 1 hour
},
// Dynamic configuration
additionalExclusions: process.env.NODE_ENV === 'production'
? ['test/**', '**/*.test.js']
: [],
};Note: Configuration files must use ES module syntax (export default) and match the config section name.
See which settings are active and their sources:
copytree config:inspectEnsure configuration is valid:
copytree config:validateEnable verbose logging:
DEBUG=copytree:config copytree config:inspect"Configuration validation failed"
Check YAML/JSON syntax:
# Validate just the syntax
copytree config:validate
# Common issues:
# - Incorrect indentation (YAML)
# - Missing commas (JSON)
# - Invalid values (e.g., strings for numbers)"User config not loading"
Check file location and format:
# Must be in ~/.copytree/ directory
ls ~/.copytree/app.js
ls ~/.copytree/app.json
# Check file has proper ES module export (for .js files)
# Must use: export default { ... }
# Not: module.exports = { ... }
# File name must match config section
# app.js for app config
# ai.js for AI configCommit profiles to share team standards:
# .copytree/team-standard.yml (committed)
name: team-standard
description: Team standard profile
include:
- "src/**"
- "docs/**"
exclude:
- "vendor/**" # PHP dependencies
- "storage/**" # Laravel storage
- "coverage/**" # Test coverage
- "*.log"Keep personal settings in ~/.copytree/:
// ~/.copytree/app.js (not committed)
export default {
defaultFormat: 'markdown',
cache: {
ttl: 7200000 // I prefer longer cache
}
};# Try different settings without modifying config
copytree --profile test-profile --displayAdd comments to user config:
// ~/.copytree/app.js
export default {
// Prefer larger file size for my projects
maxFileSize: 20 * 1024 * 1024, // 20MB
// Always use my custom profile by default
defaultProfile: 'mydefault',
// Longer cache TTL for slower network
cache: {
enabled: true,
ttl: 4 * 60 * 60 * 1000 // 4 hours
}
};# Check configuration is valid
copytree config:validate
# Inspect effective configuration
copytree config:inspect- Your First Custom Profile - Creating custom profiles
- CLI Reference - Command-line options
- Troubleshooting Guide - Common issues and solutions