I built ctxstash: pack a codebase into one LLM-ready file, with a token count
You're debugging something with ChatGPT, Claude, or Cursor, and you hit the wall every developer knows: the model needs to see your code. So you start the dance β open a file, copy, paste, type "and here's the other file
You're debugging something with ChatGPT, Claude, or Cursor, and you hit the wall every developer knows: the model needs to see your code. So you start the dance β open a file, copy, paste, type "and here's the other file", paste again, label it so the model doesn't get confusedβ¦ five files later you paste the whole thing and get back "this conversation is too long." Now you're trimming blind, with no idea how many tokens you actually sent.
I got tired of doing this by hand, so I built ctxstash: one command that walks a directory and emits a single, tidy Markdown document with every file fenced and language-tagged, a file-tree overview at the top, and an approximate token count so you know up front whether it'll fit.
npx ctxstash src > context.md
β packed 23 files Β· 142.3 KB Β· ~38,210 tokens
Paste context.md into the model. Done.
Why not just paste, or use an online tool?
- Pasting by hand is slow, and you lose the structure β the model can't tell where one file ends and the next begins.
- Online "paste your code" formatters mean shipping your source to someone else's server. Hard no for anything private.
- You can't see the token cost until the model rejects it, and by then you're guessing what to cut.
There's a great Python tool, files-to-prompt, that inspired this β but it's Python-only and doesn't estimate tokens. I wanted something with zero dependencies, a token estimate built in, and an identical build on both npm and PyPI so it doesn't matter which ecosystem you live in.
What it does
ctxstash . # pack the current dir to stdout
ctxstash src tests -o ctx.md # pack two dirs, write to a file
ctxstash . -i "*.ts,*.tsx" # only TypeScript
ctxstash . -e "*.test.js" # drop tests
ctxstash . --estimate # just tell me the token cost, pack nothing
ctxstash src --tree # just the file tree
--estimate is the one I reach for most β it answers "will this fit in the context window?" without producing a wall of text:
files 23
size 142.3 KB
~tokens 38,210 (estimate, ~4 chars/token)
largest by tokens
~6,210 src/bundle.ts
~3,180 src/core.ts
...
The packed output looks like this:
# Repository context
> Packed by ctxstash β 3 files, 4.1 KB, ~1,040 tokens (estimate).
## File tree
src/
core.ts
cli.ts
README.md
## Files
### src/core.ts
...fenced, language-tagged contents...
By default it skips the junk automatically β node_modules, .git, dist, lockfiles, minified bundles, and binary files (images, fonts, compiled artifacts) never end up in your context. The summary always goes to stderr, so ctxstash > context.md keeps the file clean while you still see the count.
Install
npx ctxstash . # Node β₯ 18, nothing to install
pip install ctxstash # Python β₯ 3.8
Both builds are zero-dependency and behavior-identical.
A few design choices, for the curious
- Two builds, one behavior. The Node and Python versions are kept byte-for-byte identical on the same input β same sort order, same fences, same output. There's a test that packs a directory with both and diffs the result.
- Token counts are honest estimates. It uses ~4 characters per token (OpenAI's rule of thumb), tokenizer-agnostic. It's great for "will this fit?", not for exact billing β and the output says so rather than pretending to be precise.
-
Collision-safe fences. If a file already contains a
```run (hello, every Markdown file), ctxstash wraps it in a longer fence so your context doesn't break mid-document. -
Binary sniffing, not just extensions. It checks for NUL bytes and control-byte density, so a stray binary with a
.txtname still gets skipped. -
Zero dependencies on purpose. No supply chain, no install dance β
npx/pip installand go.
Try it
npx ctxstash . --estimate
It's MIT-licensed and open source:
- npm: https://www.npmjs.com/package/ctxstash
- PyPI: https://pypi.org/project/ctxstash/
- GitHub (Node): https://github.com/jjdoor/ctxstash
- GitHub (Python): https://github.com/jjdoor/ctxstash-py
How are you feeding code to LLMs right now β manual copy-paste, a custom script, or something else? And what would make a tool like this actually fit your workflow? I'd genuinely like to know what to build next.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.