repocxt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- repocxt-0.1.0/CHANGELOG.md +28 -0
- repocxt-0.1.0/LICENSE +21 -0
- repocxt-0.1.0/MANIFEST.in +4 -0
- repocxt-0.1.0/PKG-INFO +218 -0
- repocxt-0.1.0/README.md +189 -0
- repocxt-0.1.0/pyproject.toml +59 -0
- repocxt-0.1.0/setup.cfg +4 -0
- repocxt-0.1.0/src/ctx/__init__.py +8 -0
- repocxt-0.1.0/src/ctx/__main__.py +6 -0
- repocxt-0.1.0/src/ctx/binary.py +37 -0
- repocxt-0.1.0/src/ctx/cli.py +189 -0
- repocxt-0.1.0/src/ctx/ignore.py +74 -0
- repocxt-0.1.0/src/ctx/language.py +80 -0
- repocxt-0.1.0/src/ctx/py.typed +0 -0
- repocxt-0.1.0/src/ctx/render.py +110 -0
- repocxt-0.1.0/src/ctx/tokens.py +29 -0
- repocxt-0.1.0/src/ctx/walker.py +128 -0
- repocxt-0.1.0/src/repocxt.egg-info/PKG-INFO +218 -0
- repocxt-0.1.0/src/repocxt.egg-info/SOURCES.txt +27 -0
- repocxt-0.1.0/src/repocxt.egg-info/dependency_links.txt +1 -0
- repocxt-0.1.0/src/repocxt.egg-info/entry_points.txt +2 -0
- repocxt-0.1.0/src/repocxt.egg-info/requires.txt +10 -0
- repocxt-0.1.0/src/repocxt.egg-info/top_level.txt +1 -0
- repocxt-0.1.0/tests/conftest.py +59 -0
- repocxt-0.1.0/tests/test_cli.py +260 -0
- repocxt-0.1.0/tests/test_ignore.py +171 -0
- repocxt-0.1.0/tests/test_render.py +228 -0
- repocxt-0.1.0/tests/test_tokens.py +48 -0
- repocxt-0.1.0/tests/test_walker.py +254 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-11-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `ctx` CLI with a `root` argument (defaults to the current directory) and an
|
|
13
|
+
`-o/--output` flag for writing the context file instead of stdout.
|
|
14
|
+
- Repository walker that honors `.gitignore` and `.ctxignore` at every level of
|
|
15
|
+
the tree, following git semantics, and always skips `.git`, `.svn`, `.hg`,
|
|
16
|
+
`.ctx`, `__pycache__`, and `.mypy_cache`.
|
|
17
|
+
- `--include` and `--exclude` filter flags using pathspec glob syntax
|
|
18
|
+
(`exclude` wins over `include`), plus `--max-size` to skip large files.
|
|
19
|
+
- Binary-file detection via NUL-byte sniffing; binary files are listed in the
|
|
20
|
+
context document but their content is omitted.
|
|
21
|
+
- Token estimation with no external tokenizer dependency; accurate enough to
|
|
22
|
+
budget context window fits and slightly conservative.
|
|
23
|
+
- Markdown renderer emitting a directory tree, a files/bytes/tokens summary,
|
|
24
|
+
and one fenced block per source file with a language hint.
|
|
25
|
+
- `--max-tokens` to stop early once the running token estimate exceeds a limit,
|
|
26
|
+
`--no-tree` to omit the ASCII tree, and `--json` for a machine-readable
|
|
27
|
+
summary.
|
|
28
|
+
- `python -m ctx` support, `ctx --version`, and a `py.typed` marker.
|
repocxt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kamran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
repocxt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repocxt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Package a repository into a single, LLM-ready context file with a tree, file contents, and a token estimate.
|
|
5
|
+
Author: Kamran
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: llm,context,cli,code,ai,prompt
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: pathspec>=1.1
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
25
|
+
Provides-Extra: build
|
|
26
|
+
Requires-Dist: build>=1.0; extra == "build"
|
|
27
|
+
Requires-Dist: twine>=4.0; extra == "build"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# ctx
|
|
31
|
+
|
|
32
|
+
Package a repository into a single, LLM-ready context file.
|
|
33
|
+
|
|
34
|
+
`ctx` walks a directory and emits one markdown file containing an ASCII tree of
|
|
35
|
+
the repo, the contents of every source file (fenced with language hints), and a
|
|
36
|
+
token estimate — so you can paste a whole repository into an LLM prompt, or
|
|
37
|
+
point an agent at it, and the model sees structure and code together.
|
|
38
|
+
|
|
39
|
+
## What it looks like
|
|
40
|
+
|
|
41
|
+
Running `ctx` on this repo (tree trimmed for the README; the header counts are real):
|
|
42
|
+
|
|
43
|
+
````text
|
|
44
|
+
# repo — context
|
|
45
|
+
|
|
46
|
+
- files: **26** · **67763 bytes** · ~**19876 tokens**
|
|
47
|
+
|
|
48
|
+
## Tree
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
├── .github/
|
|
52
|
+
│ ├── workflows/
|
|
53
|
+
│ │ ├── ci.yml
|
|
54
|
+
│ │ └── release.yml
|
|
55
|
+
│ └── FUNDING.yml
|
|
56
|
+
├── src/
|
|
57
|
+
│ └── ctx/
|
|
58
|
+
│ ├── __init__.py
|
|
59
|
+
│ ├── __main__.py
|
|
60
|
+
│ ├── ...
|
|
61
|
+
│ └── walker.py
|
|
62
|
+
├── tests/
|
|
63
|
+
│ ├── conftest.py
|
|
64
|
+
│ ├── test_cli.py
|
|
65
|
+
│ ├── test_ignore.py
|
|
66
|
+
│ ├── test_render.py
|
|
67
|
+
│ ├── test_tokens.py
|
|
68
|
+
│ └── test_walker.py
|
|
69
|
+
├── .gitignore
|
|
70
|
+
├── CHANGELOG.md
|
|
71
|
+
├── CONTRIBUTING.md
|
|
72
|
+
├── LICENSE
|
|
73
|
+
├── MANIFEST.in
|
|
74
|
+
├── pyproject.toml
|
|
75
|
+
└── README.md
|
|
76
|
+
```
|
|
77
|
+
````
|
|
78
|
+
|
|
79
|
+
## Features
|
|
80
|
+
|
|
81
|
+
- **One file out.** Directory tree, file contents, and a token estimate in a
|
|
82
|
+
single markdown document — ready to paste into a prompt.
|
|
83
|
+
- **Gitignore-aware.** Follows `.gitignore` (and `.ctxignore`) at every level
|
|
84
|
+
of the tree, with git's semantics: deeper rule files win.
|
|
85
|
+
- **Binary-safe.** Binary files are detected and listed, but their content is
|
|
86
|
+
omitted instead of corrupting the document.
|
|
87
|
+
- **Token-aware.** `--max-tokens` stops early once the running estimate crosses
|
|
88
|
+
the limit, so the output fits your context window.
|
|
89
|
+
- **Filterable.** `--include` / `--exclude` with repeatable pathspec globs, and
|
|
90
|
+
`--max-size` to skip files that are too big.
|
|
91
|
+
- **Offline and light.** The only runtime dependency is `pathspec`.
|
|
92
|
+
|
|
93
|
+
## Install
|
|
94
|
+
|
|
95
|
+
Requires Python 3.10+. The package is published on PyPI as `repocxt`; the
|
|
96
|
+
command you run is `ctx`.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pipx install repocxt
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Or install from a checkout directly:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pipx install .
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or with plain pip:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install repocxt
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
However you install it, you get a `ctx` command (it also runs as
|
|
115
|
+
`python -m ctx`).
|
|
116
|
+
|
|
117
|
+
## Usage
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
usage: ctx [-h] [-o FILE] [--include GLOB] [--exclude GLOB] [--max-size BYTES]
|
|
121
|
+
[--max-tokens N] [--no-tree] [--json] [--version]
|
|
122
|
+
[root]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- `root` — directory to pack. Defaults to the current directory. The header
|
|
126
|
+
uses the directory's name (or `repo` when the name is empty, e.g. `.`).
|
|
127
|
+
- `-o FILE, --output FILE` — write to a file instead of stdout. `-o -` means
|
|
128
|
+
stdout explicitly.
|
|
129
|
+
- `--include GLOB` / `--exclude GLOB` — keep or drop paths matching a glob
|
|
130
|
+
(repeatable; pathspec syntax). Both are applied after ignore filtering, and
|
|
131
|
+
`--exclude` wins over `--include`.
|
|
132
|
+
- `--max-size BYTES` — skip files larger than this many bytes.
|
|
133
|
+
- `--max-tokens N` — stop once the emitted text reaches N tokens.
|
|
134
|
+
- `--no-tree` — omit the ASCII tree section; just the files.
|
|
135
|
+
- `--json` — print a machine-readable summary (file list, counts, bytes,
|
|
136
|
+
tokens) instead of the document.
|
|
137
|
+
|
|
138
|
+
### With LLMs
|
|
139
|
+
|
|
140
|
+
The output is self-contained, so driving an LLM is two steps:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# write it to a file for an agent that can read files
|
|
144
|
+
ctx -o ctx.md
|
|
145
|
+
|
|
146
|
+
# or just paste stdout straight into the prompt
|
|
147
|
+
ctx | head -c 20000
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Point the model at `ctx.md` ("this is my whole repo, read it") or paste it
|
|
151
|
+
directly — the tree tells it the layout, the fenced blocks give it the code,
|
|
152
|
+
and the token estimate tells you whether it fits.
|
|
153
|
+
|
|
154
|
+
## How it decides what to include
|
|
155
|
+
|
|
156
|
+
- **Gitignore semantics.** `.gitignore` files are read at every directory and
|
|
157
|
+
applied to relative paths; a deeper `.gitignore`'s decision wins over a
|
|
158
|
+
shallower one's, and later lines override earlier ones. A private
|
|
159
|
+
`.ctxignore` file is honored the same way, for rules you do not want to
|
|
160
|
+
commit to git.
|
|
161
|
+
- **Always-skipped directories.** `.git`, `.svn`, `.hg`, `.ctx`,
|
|
162
|
+
`__pycache__`, and `.mypy_cache` are always skipped.
|
|
163
|
+
- **Binary omission.** A file is treated as binary when its first 8 KB contain
|
|
164
|
+
a NUL byte — images, archives, compiled objects. It is still listed in the
|
|
165
|
+
document, but with a note instead of content.
|
|
166
|
+
- **Files, not directories.** Only regular files are packed; empty directories
|
|
167
|
+
and directory symlinks do not appear in the tree.
|
|
168
|
+
|
|
169
|
+
## Philosophy
|
|
170
|
+
|
|
171
|
+
Offline-first. There is no telemetry, no network call, and no tokenizer — token
|
|
172
|
+
counts come from a small local heuristic that errs high, which is the safe
|
|
173
|
+
direction for context budgeting. The dependency list stays minimal
|
|
174
|
+
(`pathspec` is the only runtime dependency) and the output is plain markdown
|
|
175
|
+
you can read, diff, and paste anywhere.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git clone <your fork>/ctx
|
|
181
|
+
cd ctx
|
|
182
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
183
|
+
pip install -e ".[dev]"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
- Run tests: `python -m pytest`
|
|
187
|
+
- Lint: `python -m ruff check .` (config and dev extras live in `pyproject.toml`)
|
|
188
|
+
|
|
189
|
+
No need to install to poke at the CLI:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
export PYTHONPATH="$PWD/src"
|
|
193
|
+
python3 -m ctx --help
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Testing & compatibility
|
|
197
|
+
|
|
198
|
+
Honest status of what is and isn't verified:
|
|
199
|
+
|
|
200
|
+
- **Tested on:** Linux, Python 3.12 — 106 automated tests pass (`pytest`),
|
|
201
|
+
`ruff` clean, wheel + sdist build clean, `twine check` passes. Edge cases
|
|
202
|
+
covered by tests: nested/overriding `.gitignore`, negation, directory-only
|
|
203
|
+
patterns, filenames with spaces, undecodable and binary files, oversized
|
|
204
|
+
files, symlinks (not followed for dirs), backtick-fences inside file bodies,
|
|
205
|
+
and `--max-tokens` / `--json` behavior.
|
|
206
|
+
- **Expected but not yet verified:** macOS and Windows, and Python 3.10–3.11.
|
|
207
|
+
The code is pure Python + `pathspec` with no OS-specific calls, so it
|
|
208
|
+
*should* run anywhere, but only Linux/3.12 has been exercised. The GitHub
|
|
209
|
+
Actions matrix (`ci.yml`) runs 3.10–3.12 on Linux for every push; wider
|
|
210
|
+
platform testing is welcome via PRs / CI runs.
|
|
211
|
+
- **Not verified at all:** any real PyPI publication (needs a maintainer token).
|
|
212
|
+
|
|
213
|
+
If you hit a platform you use that isn't green, open an issue with your OS,
|
|
214
|
+
Python version, and the error — that's the fastest way this list gets shorter.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
[MIT](LICENSE) © 2026 Kamran
|
repocxt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ctx
|
|
2
|
+
|
|
3
|
+
Package a repository into a single, LLM-ready context file.
|
|
4
|
+
|
|
5
|
+
`ctx` walks a directory and emits one markdown file containing an ASCII tree of
|
|
6
|
+
the repo, the contents of every source file (fenced with language hints), and a
|
|
7
|
+
token estimate — so you can paste a whole repository into an LLM prompt, or
|
|
8
|
+
point an agent at it, and the model sees structure and code together.
|
|
9
|
+
|
|
10
|
+
## What it looks like
|
|
11
|
+
|
|
12
|
+
Running `ctx` on this repo (tree trimmed for the README; the header counts are real):
|
|
13
|
+
|
|
14
|
+
````text
|
|
15
|
+
# repo — context
|
|
16
|
+
|
|
17
|
+
- files: **26** · **67763 bytes** · ~**19876 tokens**
|
|
18
|
+
|
|
19
|
+
## Tree
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
├── .github/
|
|
23
|
+
│ ├── workflows/
|
|
24
|
+
│ │ ├── ci.yml
|
|
25
|
+
│ │ └── release.yml
|
|
26
|
+
│ └── FUNDING.yml
|
|
27
|
+
├── src/
|
|
28
|
+
│ └── ctx/
|
|
29
|
+
│ ├── __init__.py
|
|
30
|
+
│ ├── __main__.py
|
|
31
|
+
│ ├── ...
|
|
32
|
+
│ └── walker.py
|
|
33
|
+
├── tests/
|
|
34
|
+
│ ├── conftest.py
|
|
35
|
+
│ ├── test_cli.py
|
|
36
|
+
│ ├── test_ignore.py
|
|
37
|
+
│ ├── test_render.py
|
|
38
|
+
│ ├── test_tokens.py
|
|
39
|
+
│ └── test_walker.py
|
|
40
|
+
├── .gitignore
|
|
41
|
+
├── CHANGELOG.md
|
|
42
|
+
├── CONTRIBUTING.md
|
|
43
|
+
├── LICENSE
|
|
44
|
+
├── MANIFEST.in
|
|
45
|
+
├── pyproject.toml
|
|
46
|
+
└── README.md
|
|
47
|
+
```
|
|
48
|
+
````
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- **One file out.** Directory tree, file contents, and a token estimate in a
|
|
53
|
+
single markdown document — ready to paste into a prompt.
|
|
54
|
+
- **Gitignore-aware.** Follows `.gitignore` (and `.ctxignore`) at every level
|
|
55
|
+
of the tree, with git's semantics: deeper rule files win.
|
|
56
|
+
- **Binary-safe.** Binary files are detected and listed, but their content is
|
|
57
|
+
omitted instead of corrupting the document.
|
|
58
|
+
- **Token-aware.** `--max-tokens` stops early once the running estimate crosses
|
|
59
|
+
the limit, so the output fits your context window.
|
|
60
|
+
- **Filterable.** `--include` / `--exclude` with repeatable pathspec globs, and
|
|
61
|
+
`--max-size` to skip files that are too big.
|
|
62
|
+
- **Offline and light.** The only runtime dependency is `pathspec`.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Requires Python 3.10+. The package is published on PyPI as `repocxt`; the
|
|
67
|
+
command you run is `ctx`.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pipx install repocxt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or install from a checkout directly:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pipx install .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or with plain pip:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install repocxt
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
However you install it, you get a `ctx` command (it also runs as
|
|
86
|
+
`python -m ctx`).
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
usage: ctx [-h] [-o FILE] [--include GLOB] [--exclude GLOB] [--max-size BYTES]
|
|
92
|
+
[--max-tokens N] [--no-tree] [--json] [--version]
|
|
93
|
+
[root]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `root` — directory to pack. Defaults to the current directory. The header
|
|
97
|
+
uses the directory's name (or `repo` when the name is empty, e.g. `.`).
|
|
98
|
+
- `-o FILE, --output FILE` — write to a file instead of stdout. `-o -` means
|
|
99
|
+
stdout explicitly.
|
|
100
|
+
- `--include GLOB` / `--exclude GLOB` — keep or drop paths matching a glob
|
|
101
|
+
(repeatable; pathspec syntax). Both are applied after ignore filtering, and
|
|
102
|
+
`--exclude` wins over `--include`.
|
|
103
|
+
- `--max-size BYTES` — skip files larger than this many bytes.
|
|
104
|
+
- `--max-tokens N` — stop once the emitted text reaches N tokens.
|
|
105
|
+
- `--no-tree` — omit the ASCII tree section; just the files.
|
|
106
|
+
- `--json` — print a machine-readable summary (file list, counts, bytes,
|
|
107
|
+
tokens) instead of the document.
|
|
108
|
+
|
|
109
|
+
### With LLMs
|
|
110
|
+
|
|
111
|
+
The output is self-contained, so driving an LLM is two steps:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# write it to a file for an agent that can read files
|
|
115
|
+
ctx -o ctx.md
|
|
116
|
+
|
|
117
|
+
# or just paste stdout straight into the prompt
|
|
118
|
+
ctx | head -c 20000
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Point the model at `ctx.md` ("this is my whole repo, read it") or paste it
|
|
122
|
+
directly — the tree tells it the layout, the fenced blocks give it the code,
|
|
123
|
+
and the token estimate tells you whether it fits.
|
|
124
|
+
|
|
125
|
+
## How it decides what to include
|
|
126
|
+
|
|
127
|
+
- **Gitignore semantics.** `.gitignore` files are read at every directory and
|
|
128
|
+
applied to relative paths; a deeper `.gitignore`'s decision wins over a
|
|
129
|
+
shallower one's, and later lines override earlier ones. A private
|
|
130
|
+
`.ctxignore` file is honored the same way, for rules you do not want to
|
|
131
|
+
commit to git.
|
|
132
|
+
- **Always-skipped directories.** `.git`, `.svn`, `.hg`, `.ctx`,
|
|
133
|
+
`__pycache__`, and `.mypy_cache` are always skipped.
|
|
134
|
+
- **Binary omission.** A file is treated as binary when its first 8 KB contain
|
|
135
|
+
a NUL byte — images, archives, compiled objects. It is still listed in the
|
|
136
|
+
document, but with a note instead of content.
|
|
137
|
+
- **Files, not directories.** Only regular files are packed; empty directories
|
|
138
|
+
and directory symlinks do not appear in the tree.
|
|
139
|
+
|
|
140
|
+
## Philosophy
|
|
141
|
+
|
|
142
|
+
Offline-first. There is no telemetry, no network call, and no tokenizer — token
|
|
143
|
+
counts come from a small local heuristic that errs high, which is the safe
|
|
144
|
+
direction for context budgeting. The dependency list stays minimal
|
|
145
|
+
(`pathspec` is the only runtime dependency) and the output is plain markdown
|
|
146
|
+
you can read, diff, and paste anywhere.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone <your fork>/ctx
|
|
152
|
+
cd ctx
|
|
153
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
154
|
+
pip install -e ".[dev]"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- Run tests: `python -m pytest`
|
|
158
|
+
- Lint: `python -m ruff check .` (config and dev extras live in `pyproject.toml`)
|
|
159
|
+
|
|
160
|
+
No need to install to poke at the CLI:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
export PYTHONPATH="$PWD/src"
|
|
164
|
+
python3 -m ctx --help
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Testing & compatibility
|
|
168
|
+
|
|
169
|
+
Honest status of what is and isn't verified:
|
|
170
|
+
|
|
171
|
+
- **Tested on:** Linux, Python 3.12 — 106 automated tests pass (`pytest`),
|
|
172
|
+
`ruff` clean, wheel + sdist build clean, `twine check` passes. Edge cases
|
|
173
|
+
covered by tests: nested/overriding `.gitignore`, negation, directory-only
|
|
174
|
+
patterns, filenames with spaces, undecodable and binary files, oversized
|
|
175
|
+
files, symlinks (not followed for dirs), backtick-fences inside file bodies,
|
|
176
|
+
and `--max-tokens` / `--json` behavior.
|
|
177
|
+
- **Expected but not yet verified:** macOS and Windows, and Python 3.10–3.11.
|
|
178
|
+
The code is pure Python + `pathspec` with no OS-specific calls, so it
|
|
179
|
+
*should* run anywhere, but only Linux/3.12 has been exercised. The GitHub
|
|
180
|
+
Actions matrix (`ci.yml`) runs 3.10–3.12 on Linux for every push; wider
|
|
181
|
+
platform testing is welcome via PRs / CI runs.
|
|
182
|
+
- **Not verified at all:** any real PyPI publication (needs a maintainer token).
|
|
183
|
+
|
|
184
|
+
If you hit a platform you use that isn't green, open an issue with your OS,
|
|
185
|
+
Python version, and the error — that's the fastest way this list gets shorter.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
[MIT](LICENSE) © 2026 Kamran
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "repocxt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Package a repository into a single, LLM-ready context file with a tree, file contents, and a token estimate."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Kamran" }]
|
|
14
|
+
keywords = ["llm", "context", "cli", "code", "ai", "prompt"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
"Topic :: Software Development :: Build Tools",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pathspec>=1.1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=7.4",
|
|
33
|
+
"pytest-cov>=4.1",
|
|
34
|
+
"ruff>=0.4",
|
|
35
|
+
]
|
|
36
|
+
build = [
|
|
37
|
+
"build>=1.0",
|
|
38
|
+
"twine>=4.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
ctx = "ctx.cli:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
ctx = ["py.typed"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
target-version = "py39"
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM", "C4"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
addopts = "-q"
|
repocxt-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Heuristic detection of binary files.
|
|
2
|
+
|
|
3
|
+
No file signature database: a file is treated as binary when its first chunk
|
|
4
|
+
contains a NUL byte, which is reliable for the formats programmers actually
|
|
5
|
+
commit (images, archives, compiled objects, fonts, audio). UTF-16 text and some
|
|
6
|
+
encodings can contain NUL, but they are vanishingly rare in repository trees.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
_SNIFF_BYTES = 8192
|
|
14
|
+
_NUL = b"\x00"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_nul_in_text(text: str) -> bool:
|
|
18
|
+
"""True when decoded ``text`` still contains a NUL character.
|
|
19
|
+
|
|
20
|
+
``is_binary`` sniffs only the first chunk, so a NUL past that window would
|
|
21
|
+
otherwise slip through; checking the decoded text is the definitive gate.
|
|
22
|
+
"""
|
|
23
|
+
return _NUL.decode("ascii") in text
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_binary(path: Path) -> bool:
|
|
27
|
+
"""Return True when ``path`` looks like a binary file.
|
|
28
|
+
|
|
29
|
+
Reads at most :data:`_SNIFF_BYTES` bytes. Files shorter than that are judged
|
|
30
|
+
on their whole content. A missing file answers False (the caller handles it).
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
with path.open("rb") as handle:
|
|
34
|
+
chunk = handle.read(_SNIFF_BYTES)
|
|
35
|
+
except OSError:
|
|
36
|
+
return False
|
|
37
|
+
return _NUL in chunk
|