ctxpack
Dependency-free repo-to-prompt pack builder.
ctxpack is the missing bridge between raw repository scanning and AI-ready context. It takes a local project, respects ignore rules, token-budgets the output, and emits two clean artifacts:
ctxpack.context.json— machine-readable inventory for agents/toolsctxpack.context.md— human-readable prompt pack for pasting into an LLM
No dependencies. No network. Secrets excluded by default.
Features
- 📁 Recursively scans the current directory
- 🚫 Respects
.ctxignore(gitignore-style patterns) - 🪶 Skips binary and overly large files by default
- 🔒 Excludes secrets by default:
.env,.env.*,*.pem,*.key,*.p12,*.pfx - 🧮 Estimates token usage (approx
chars / 4— not a model tokenizer count) - ✂️ Respects a max estimated token budget (
--budget) and truncates gracefully - ⚙️ Simple configuration via optional
ctxpack.json
Installation
No installation required. Just download the single file:
curl -O https://raw.githubusercontent.com/billybox1926-jpg/ctxpack/main/ctxpack.py
chmod +x ctxpack.py
Or install it to get the ctxpack command on your PATH:
pip install ctxpack-cli
ctxpack --version
The PyPI distribution is named
ctxpack-clibecausectxpackwas already taken by an unrelated project. The installed command is stillctxpack.
Usage
Initialize a project
Create default .ctxignore and ctxpack.json files in your current directory:
python ctxpack.py init
Pack a repository
Scan the current directory and generate context files:
python ctxpack.py pack
Advanced options
# Set a specific token budget
python ctxpack.py pack --budget 12000
# Ignore ctxpack.json settings and use CLI defaults/flags only
python ctxpack.py pack --no-config --budget 4000
Configuration
.ctxignore
Uses ctxignore patterns — a tested subset of gitignore syntax. Lines starting with # are comments. Blank lines are ignored.
Supported pattern types:
| Pattern | Matches | Example |
|---|---|---|
foo |
Exact path at any depth | build/ matches build/, src/build/ |
foo/ |
Directory and everything inside | venv/ skips venv/lib/x.py |
foo/** |
Directory and everything inside | node_modules/** skips node_modules/pkg/x.js |
*.ext |
Files with extension at any depth | *.log skips debug.log and logs/debug.log |
/foo |
Exact path at the scan root only | /build/ skips build/ but NOT src/build/ |
** |
Spans path segments | **/.aws/** skips .aws/ and nested/.aws/ |
!foo |
Negation — re-includes a previous exclusion | *.pem then !fixture.pem |
\*, \[, etc. |
Escaped wildcard/bracket (literal) | file\*.txt matches the literal file*.txt |
Pattern precedence: Patterns are processed in order. The last matching pattern wins — so !foo can override an earlier foo.
Include vs. exclude: --include patterns restrict to specific files. --exclude patterns remove files. Excludes always override includes.
Not supported: Character classes ([...]), trailing whitespace significance, or full regex.
# Ignore virtual environments
venv/
.venv/
# Ignore build artifacts at the root only
/build/
/dist/
# Ignore all log files anywhere
*.log
# But keep the main log file
!important.log
# Allow template env files
!.env.example
Default secret exclusions: ctxpack excludes these by default (not shown in generated .ctxignore):
.env, .env.*, *.pem, *.key, *.p12, *.pfx, *.crt, *.cer, *.jks, *.keystore, *.gpg, *.asc, **/.aws/**, **/.ssh/**, **/.netrc, **/.npmrc, **/.pypirc
ctxpack.json
Optional configuration file. Created via python ctxpack.py init.
{
"budget_tokens": 8000,
"ignore_file": ".ctxignore",
"include_binary": false
}
Token Budget Semantics
How token estimation works
ctxpack uses a script-aware heuristic to estimate token count:
- Latin/ASCII text: ~4 characters per token, approximating typical LLM tokenization for English text and code.
- CJK text (Chinese, Japanese kana, Korean hangul): ~1.5 characters per token, since CJK scripts tokenize far less densely than Latin text (typically 1-2 tokens per character). Mixed text is estimated per-script and summed.
Key details:
- Empty content = 0 tokens: Files with no content contribute zero tokens
- Minimum 1 token: Any non-empty file gets at least 1 token estimate
- Truncation marker overhead: When files are truncated, the truncation message (
...[TRUNCATED by ctxpack to fit budget]...) accounts for ~11 tokens
Budget enforcement behavior
When the total estimated tokens exceed the budget:
- Files are processed in sorted path order
- Files that fit entirely within remaining budget are included as-is
- The first file that would exceed the budget is truncated (not dropped), with a truncation marker appended
- Remaining files are marked as omitted (empty content, listed in output)
This ensures:
- No silent drops: Every discovered file appears in the output (either full, truncated, or omitted)
- Budget never exceeded: The truncation marker's token cost is reserved before slicing
- Transparent about missing content: Omitted files are listed with their original size/token estimates
Edge cases
| Scenario | Behavior |
|---|---|
| Empty repository | Outputs header only, 0 tokens used |
| Single file > budget | File truncated to fit budget + marker |
| Exact budget match | All files included without truncation |
| Very small budget (< 20 tokens) | First file may be truncated immediately or omitted |
Limitations
- This is a rough estimate, not an exact token count. Actual LLM tokenizers (e.g., tiktoken, sentencepiece) may vary by ±20-30%
- Code with many symbols, non-English text, or unusual formatting may have different actual token counts
- For critical workflows, verify actual token usage with your target model's tokenizer
Generated Artifacts: To Commit or Not?
Short answer: Generally no. Generated *.context.* files are ephemeral artifacts meant for immediate use, not long-term storage.
Recommended practice
- ❌ Do not commit
*.context.jsonor*.context.mdfiles to your repository - ✅ Do add them to
.gitignore(they're already in the default template fromctxpack init) - ✅ Do regenerate them fresh whenever you need to share context with an LLM
Why not commit generated packs?
- Stale content: Context packs become outdated as soon as your code changes
- Noise in history: Frequent regeneration creates churn in git history
- Repository bloat: Large context packs can significantly increase repo size
- False sense of accuracy: Old packs may misrepresent current project state
When might you commit a pack?
Rare exceptions where committing might make sense:
- 📦 Release artifacts: Including a context pack with a tagged release to capture exact state at release time
- 🔍 Debugging aid: Committing a specific pack to help reproduce and debug an issue
- 📚 Documentation example: Sample packs in
examples/directories (like this repo'sexamples/sample.context.*)
If you do commit a generated pack, consider:
- Adding a timestamp/generation note in comments
- Using git LFS for large files
- Setting up automated cleanup for stale packs
Default behavior
The ctxpack init command adds these patterns to your .gitignore:
# Generated context packs (ephemeral artifacts)
*.context.json
*.context.md
This keeps your repository clean while allowing you to generate fresh packs on demand.
Output Examples
The examples/ directory contains static sample outputs generated by ctxpack. These are committed as documentation references:
To generate your own artifacts from the current project:
python ctxpack.py pack --output-dir ./out
This writes ctxpack.context.md and ctxpack.context.json into ./out/. Generated artifacts are gitignored by default.
Generated Artifact Policy
Generated context artifacts are not committed to the repository. Files matching *.context.md and *.context.json are excluded via .gitignore.
This policy prevents:
- Stale snapshots that diverge from current source
- Noisy diffs from regenerated output
- Accidental publication of sensitive material captured at generation time
The examples/ directory holds static reference fixtures that are intentionally committed.
Security
Secret-safe by default
ctxpack excludes credential-bearing files by default so they never reach the generated pack. The built-in ignore policy covers:
- Local env files:
.env,.env.*(but not.env.example, the conventional template) - Private keys & certificates:
*.pem,*.key,*.p12,*.pfx,*.crt,*.cer - Keystores:
*.jks,*.keystore - GPG / signing material:
*.gpg,*.asc - Credential directories:
**/.aws/**,**/.ssh/** - Auth dotfiles:
**/.netrc,**/.npmrc,**/.pypirc
These defaults are applied automatically; you do not need to list them in .ctxignore. Custom .ctxignore entries are merged with these defaults and can add further exclusions.
Security boundary: Default secret exclusions are just patterns. Explicit .ctxignore negation patterns (e.g., !.env) can override these defaults. Users should treat custom re-inclusion rules as an explicit security opt-in — the tool cannot protect against intentional overrides in project configuration files.
To opt a specific secret file back in (e.g., a test fixture), add a negation pattern to .ctxignore:
!important/test-fixture.pem
Strict mode
If you want the default secret exclusions to be non-overridable, pass --strict-secrets:
python ctxpack.py pack --strict-secrets
In strict mode, .ctxignore and CLI negation patterns cannot re-include any secret file (.env, *.pem, *.key, *.gpg, credential directories, etc.). The built-in .env.example template carve-out still applies, so conventional example files remain packable.
Path privacy
Generated packs use a privacy-preserving . representation for the project root by default. To include the absolute path (e.g., for debugging), use:
python ctxpack.py pack --show-absolute-paths
This prevents accidentally leaking local usernames, directory structures, or project locations when sharing context packs.
License
MIT License. See LICENSE for details.
Release Process
Versioning
ctxpack uses Semantic Versioning:
- MAJOR: incompatible API changes
- MINOR: backwards-compatible functionality additions
- PATCH: backwards-compatible bug fixes
Building a Release
# Clean build
rm -rf dist/
python -m build
# Verify artifacts
twine check dist/*
Release Checklist
- All tests pass (
pytest -v) - Lint checks pass (
ruff check .) - Version bumped in
pyproject.toml - CHANGELOG updated (if applicable)
- Git tag created (
git tag v0.2.0) - CI green on all jobs (test, lint, security, packaging)
CI Release Validation
The packaging CI job validates:
- sdist and wheel build successfully
- CLI works after installation from wheel
- All tests pass against installed package
Metadata
Release files for ctxpack-cli 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ctxpack_cli-1.0.0.tar.gz | 38.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ctxpack_cli-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.0 kB
Release files / ctxpack_cli-1.0.0.tar.gz
| Download URL | ctxpack_cli-1.0.0.tar.gz |
|---|---|
| Size | 38.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a589bc1f5bd5c10bdc48c7c5e308057d3352d1bd41379c23ac116c38b10a5794
|
|
BLAKE2b-256 checksum How to use checksums |
a7ee16c8022fc4ed73a68ed78ba51950258bf19f30b11ea8854daddc4984a39c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / ctxpack_cli-1.0.0-py3-none-any.whl
| Download URL | ctxpack_cli-1.0.0-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5c3fbcec5352331d1008ac8fac0411a0454f2e5a2855b30aad2c4c75e97e4a7
|
|
BLAKE2b-256 checksum How to use checksums |
ec024233de30c21475cb5509132aa1e8e656a3f6358cd61d816b458a378d5a02
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|