contextzip 0.3.7__tar.gz → 0.3.8__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.
Files changed (46) hide show
  1. {contextzip-0.3.7 → contextzip-0.3.8}/PKG-INFO +87 -94
  2. {contextzip-0.3.7 → contextzip-0.3.8}/README.md +86 -93
  3. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/__init__.py +7 -1
  4. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/api.py +81 -0
  5. contextzip-0.3.8/contextzip/applier.py +496 -0
  6. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/cli.py +145 -0
  7. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/cli_display.py +92 -0
  8. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/packager.py +47 -0
  9. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/PKG-INFO +87 -94
  10. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/SOURCES.txt +1 -0
  11. {contextzip-0.3.7 → contextzip-0.3.8}/pyproject.toml +1 -1
  12. {contextzip-0.3.7 → contextzip-0.3.8}/LICENSE +0 -0
  13. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/ai/__init__.py +0 -0
  14. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/ai/gemini.py +0 -0
  15. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/ai/heuristic.py +0 -0
  16. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/ai/selector.py +0 -0
  17. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/cli_ai.py +0 -0
  18. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/cli_onboard.py +0 -0
  19. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/clipboard.py +0 -0
  20. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/config.py +0 -0
  21. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/detector.py +0 -0
  22. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/error_parser.py +0 -0
  23. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/filters.py +0 -0
  24. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/git.py +0 -0
  25. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/project_config.py +0 -0
  26. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/__init__.py +0 -0
  27. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/base.py +0 -0
  28. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/errors/__init__.py +0 -0
  29. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/errors/node.py +0 -0
  30. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/errors/python.py +0 -0
  31. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/go.py +0 -0
  32. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/node.py +0 -0
  33. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/python.py +0 -0
  34. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/ruby.py +0 -0
  35. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/rules/rust.py +0 -0
  36. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/watcher.py +0 -0
  37. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/webui/__init__.py +0 -0
  38. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/webui/assets.py +0 -0
  39. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/webui/persist.py +0 -0
  40. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/webui/server.py +0 -0
  41. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip/webui/suggestions.py +0 -0
  42. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/dependency_links.txt +0 -0
  43. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/entry_points.txt +0 -0
  44. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/requires.txt +0 -0
  45. {contextzip-0.3.7 → contextzip-0.3.8}/contextzip.egg-info/top_level.txt +0 -0
  46. {contextzip-0.3.7 → contextzip-0.3.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
@@ -43,6 +43,8 @@ Every AI session starts the same way: hunt down the relevant files, manually ski
43
43
 
44
44
  contextzip eliminates that entirely. Run it from your project root — it detects your stack, applies smart exclusions, produces a lean ZIP, and opens your file manager with the archive already selected. One `Ctrl+C` and you're done.
45
45
 
46
+ And when the AI tool hands you a ZIP back with the changes, `contextzip apply-zip` closes the loop — no manual unzip-and-hope, no losing track of what actually changed.
47
+
46
48
  ---
47
49
 
48
50
  ## Features
@@ -51,10 +53,10 @@ contextzip eliminates that entirely. Run it from your project root — it detect
51
53
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
52
54
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
53
55
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
56
+ - **Apply AI-returned changes back** — `contextzip apply-zip` takes the ZIP an AI tool hands you back and writes it into your project safely: diffed against what was actually sent, backed up before anything is overwritten, and never silently deleting anything
54
57
  - **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
55
- - **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip/config.json`
56
- - **Visual config UI** — `contextzip config --ui` opens a local browser tab to set include/exclude by clicking through your actual file tree, with live counts and one-click suggestions for PDFs, media, and other non-code files — nothing leaves your machine
57
- - **Persistent workspace** — all generated ZIPs land in `.contextzip/output/`, discoverable, reusable, and git-ignored automatically
58
+ - **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip.json`
59
+ - **Persistent workspace** — all generated ZIPs land in `.contextzip/`, discoverable, reusable, and git-ignored automatically
58
60
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
59
61
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
60
62
  - **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
@@ -96,7 +98,7 @@ contextzip will:
96
98
 
97
99
  1. Detect your framework (e.g. `Next.js + Node.js`)
98
100
  2. Apply the appropriate exclusion rules
99
- 3. Create a compressed ZIP in `.contextzip/output/` at your project root
101
+ 3. Create a compressed ZIP in `.contextzip/` at your project root
100
102
  4. Open your file manager with the ZIP selected and ready to copy
101
103
 
102
104
  ---
@@ -107,8 +109,6 @@ contextzip will:
107
109
  contextzip [OPTIONS]
108
110
  ```
109
111
 
110
- > `cz` is a shorthand alias for `contextzip` — both commands are identical and support every option and subcommand shown below (e.g. `cz --git-changes`, `cz exclude node_modules`).
111
-
112
112
  | Option | Description |
113
113
  |---|---|
114
114
  | `-p`, `--prompt TEXT` | Describe your task in plain English — Gemini selects only the relevant files |
@@ -121,7 +121,7 @@ contextzip [OPTIONS]
121
121
  | `--no-clipboard` | Skip the clipboard / folder-open step |
122
122
  | `--no-gitignore` | Ignore the project's `.gitignore` |
123
123
 
124
- **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
124
+ **Subcommands:** `exclude`, `include`, `apply-zip`, `watch`, `config` — run `contextzip --help` for full details.
125
125
 
126
126
  ---
127
127
 
@@ -148,6 +148,9 @@ contextzip --prompt "Refactor auth middleware" --dry-run
148
148
 
149
149
  # Save to a custom path
150
150
  contextzip --output ~/Desktop/project-context.zip
151
+
152
+ # Apply the ZIP an AI tool handed back
153
+ contextzip apply-zip
151
154
  ```
152
155
 
153
156
  ---
@@ -157,7 +160,7 @@ contextzip --output ~/Desktop/project-context.zip
157
160
  contextzip is also usable as a library. All CLI capabilities are available as plain Python functions — no Click, no Rich output, no `SystemExit`.
158
161
 
159
162
  ```python
160
- from contextzip import get_git_changes, get_files, create_zip
163
+ from contextzip import get_git_changes, get_files, create_zip, apply_zip
161
164
 
162
165
  # Get changed files and use them directly
163
166
  collection = get_git_changes()
@@ -173,6 +176,10 @@ with open(pkg.zip_path, "rb") as f:
173
176
  collection = get_files(include=["src/"], exclude=["tests/"])
174
177
  pkg = create_zip(collection, output="/tmp/upload.zip")
175
178
  print(f"{pkg.file_count} files, {pkg.compressed_bytes} bytes")
179
+
180
+ # Apply a ZIP an AI tool returned (auto-detects from .contextzip/inbox/)
181
+ result = apply_zip()
182
+ print(f"Wrote {len(result.written)} files, backup at {result.backup_dir}")
176
183
  ```
177
184
 
178
185
  | Function | Description |
@@ -180,9 +187,10 @@ print(f"{pkg.file_count} files, {pkg.compressed_bytes} bytes")
180
187
  | `get_git_changes(path?)` | Modified, added, and untracked files from git |
181
188
  | `get_files(path?, include?, exclude?)` | All project files after exclusion rules |
182
189
  | `create_zip(collection, output?)` | Write a `FileCollection` to a ZIP archive |
190
+ | `apply_zip(zip_path?, project_dir?, manifest?)` | Apply an AI-returned ZIP back into the project |
183
191
  | `detect_ecosystem(path?)` | Detect framework and confidence level |
184
192
 
185
- All functions default `path` to `Path.cwd()`. Errors raise typed exceptions (`NotARepositoryError`, `GitNotFoundError`, `NoFilesError`, etc.) rather than exiting.
193
+ All functions default `path` to `Path.cwd()`. Errors raise typed exceptions (`NotARepositoryError`, `GitNotFoundError`, `NoFilesError`, `ZipNotFoundError`, etc.) rather than exiting.
186
194
 
187
195
  ---
188
196
 
@@ -212,6 +220,61 @@ contextzip config --reset-key # clear and re-run setup
212
220
 
213
221
  ---
214
222
 
223
+ ## Applying AI-returned changes
224
+
225
+ Once an AI tool has made its edits and handed you back a ZIP, `contextzip apply-zip` writes those changes into your project — safely.
226
+
227
+ ```bash
228
+ # Drop the AI's returned zip into .contextzip/inbox/, then:
229
+ contextzip apply-zip
230
+
231
+ # Or point at it directly, wherever it landed:
232
+ contextzip apply-zip ~/Downloads/fixed-project.zip
233
+
234
+ # Preview first, with full per-file detail:
235
+ contextzip apply-zip --dry-run --verbose
236
+
237
+ # Skip the confirmation prompt (e.g. in a script):
238
+ contextzip apply-zip --yes
239
+ ```
240
+
241
+ ### How it knows what's safe to apply
242
+
243
+ Every ZIP `contextzip` creates gets a small manifest written next to it in `.contextzip/output/` — a hash of each included file, taken at zip-time. This manifest is **never added to the ZIP itself**. It stays local, so it's never uploaded and never visible to whatever AI tool the ZIP is pasted into — nothing for a model, or a curious teammate, to notice or ask about.
244
+
245
+ When you run `apply-zip`, it diffs the returned ZIP against that manifest and classifies every file:
246
+
247
+ | Status | Meaning | Applied automatically? |
248
+ |---|---|---|
249
+ | **New** | Wasn't part of the original ZIP | Yes |
250
+ | **Modified** | Was sent, content changed, and your local copy hasn't moved since | Yes |
251
+ | **Unchanged** | Identical to what's already on disk | Skipped — nothing to do |
252
+ | **Drifted** | Your local file changed (or was deleted) since you zipped it | No — flagged, asks first |
253
+ | **Untracked** | In the returned ZIP but has no baseline to compare against | No — flagged, asks first |
254
+
255
+ The common case — you zip some files, the AI edits them, nothing else touched your project meanwhile — applies straight through with just a summary printed, no prompt. Anything that could clobber your own work, or introduce a path you didn't expect, stops and asks before writing.
256
+
257
+ If a ZIP arrives with no matching manifest at all (e.g. it wasn't produced by a `contextzip` round trip), every already-existing path is treated as untracked and you'll be asked to confirm the whole batch.
258
+
259
+ ### What it does and doesn't do
260
+
261
+ - **Only adds and modifies files.** Deletions are never inferred from a ZIP's contents — a file that's simply missing from the returned ZIP is left alone.
262
+ - **Backs up before overwriting.** Every file about to be touched is copied into `.contextzip/backups/<timestamp>/` first, preserving its relative path.
263
+ - **Rejects unsafe paths outright.** Any ZIP entry that would resolve outside your project (`../../etc/passwd`-style path traversal) is refused before anything is written — no override flag, no exceptions.
264
+ - **Archives what it consumes.** Once applied, the ZIP is moved to `.contextzip/inbox/applied/<timestamp>-name.zip` — it won't be picked up again by accident, and stays around as an audit trail. ZIPs applied via an explicit path outside the inbox are left where you put them.
265
+
266
+ ### Finding the right ZIP and manifest
267
+
268
+ ```bash
269
+ contextzip apply-zip # exactly one *.zip in .contextzip/inbox/ → uses it
270
+ contextzip apply-zip path/to/fix.zip # explicit path always overrides the inbox
271
+ contextzip apply-zip --manifest path/to/codebase.manifest.json # override auto-detection
272
+ ```
273
+
274
+ If `.contextzip/inbox/` has more than one ZIP and you don't specify which, `apply-zip` lists them and asks you to be explicit rather than guessing. Manifest auto-detection picks the most recently created `*.manifest.json` under `.contextzip/output/` — correct for the normal one-round-trip-at-a-time flow; pass `--manifest` explicitly if you've generated several ZIPs before getting a response back.
275
+
276
+ ---
277
+
215
278
  ## Terminal error watcher
216
279
 
217
280
  The `watch` command wraps your dev server, buffers its output, and packages a debug-ready ZIP the moment you spot an error — no manual file hunting, no copy-pasting stack traces.
@@ -279,7 +342,7 @@ detects both Node.js and Python from `root/`, without either marker existing at
279
342
  By default, `.contextzip/` is created at your git root — that's `_find_git_root()` walking up from the current directory until it finds a `.git` folder; outside a repo, it falls back to the current directory. This can be overridden two ways, in order of priority:
280
343
 
281
344
  1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
282
- 2. **A committed `.contextzip/config.json` at the project root** — team-shared, applies to everyone who clones the repo:
345
+ 2. **A committed `.contextzip.json` at the project root** — team-shared, applies to everyone who clones the repo:
283
346
  ```json
284
347
  { "workspace_location": "git-root" }
285
348
  ```
@@ -293,95 +356,25 @@ By default, `.contextzip/` is created at your git root — that's `_find_git_roo
293
356
 
294
357
  Accepted values are `"git-root"`, `"cwd"`, or any path (absolute, or relative to the git root). Run `contextzip config` with no flags to see which value is currently active and where it came from.
295
358
 
296
- This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip/config.json` instead if you'd rather each subproject keep its own.
359
+ This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip.json` instead if you'd rather each subproject keep its own.
297
360
 
298
- ---
299
-
300
- ## Project configuration
301
-
302
- Every project gets a `.contextzip/` workspace at the project root:
361
+ **Workspace layout:**
303
362
 
304
363
  ```
305
364
  .contextzip/
306
- ├── config.json # project preferences — commit this
307
- ├── .gitignore # keeps output/ untracked, config.json trackable
308
- └── output/ # generated ZIPs — never committed
309
- ├── codebase.zip
310
- ├── changes.zip (--git-changes)
311
- └── debug-context.zip (contextzip watch)
365
+ config.json # team-shared preferences (committed)
366
+ .gitignore # ignores everything below except itself + config.json
367
+ output/
368
+ codebase.zip # what you generate and send out
369
+ codebase.manifest.json # local-only — never uploaded, used by apply-zip
370
+ inbox/
371
+ <ai-returned>.zip # drop AI-returned zips here for apply-zip to pick up
372
+ applied/
373
+ <timestamp>-name.zip # archived after a successful apply-zip
374
+ backups/
375
+ <timestamp>/ # pre-overwrite copies, one folder per apply-zip run
312
376
  ```
313
377
 
314
- `.contextzip/` is **not** committed to Git by default — `.contextzip/.gitignore` is written automatically the first time you run contextzip in a repo, and it ignores everything in the folder *except* `config.json` and itself. That's deliberate: `output/` is per-machine, disposable scratch space, while `config.json` holds team-shared preferences you'll usually want everyone on the same page about.
315
-
316
- `config.json` currently supports:
317
-
318
- ```json
319
- {
320
- "workspace_location": "git-root",
321
- "scan_depth": null,
322
- "always_include": [],
323
- "always_exclude": [],
324
- "ai": {
325
- "enabled": true,
326
- "provider": "gemini",
327
- "max_files": 10
328
- }
329
- }
330
- ```
331
-
332
- All keys are optional — start with just the ones you need.
333
-
334
- | Key | What it does |
335
- | --- | --- |
336
- | `workspace_location` | Same as `contextzip config --set-workspace`, but team-shared. See [Workspace location](#workspace-location). |
337
- | `scan_depth` | Reserved for a future bounded-depth scan mode. |
338
- | `always_include` | A standing "force include" list (gitwildmatch patterns). Files matching it are packaged even if an auto-rule or `.gitignore` would otherwise exclude them — e.g. `["docs/architecture.md"]` to always pull in a doc that lives in an otherwise-excluded folder. |
339
- | `always_exclude` | A standing exclusion list, applied on every run without retyping `-e`/`--exclude`. |
340
- | `ai.enabled` | Set to `false` to disable `--prompt` entirely for this project; contextzip will refuse with a clear message instead of silently ignoring it. |
341
- | `ai.provider` | Reserved for future AI providers. Only `"gemini"` is currently supported. |
342
- | `ai.max_files` | Caps how many files `--prompt` mode may return, for both the Gemini and keyword-heuristic paths. |
343
-
344
- `always_include`/`always_exclude` are additive on top of `--include`/`--exclude` for that run — an explicit `contextzip include PATH` still has the final say over what's actually packaged. Persistent preferences belong in `config.json` rather than an ever-growing list of CLI flags; CLI flags stay for one-off, explicit actions (`--dry-run`, `--prompt "…"`, `--output FILE`, etc.).
345
-
346
- You don't have to hand-write `config.json` — see [Visual config UI](#visual-config-ui) below for a point-and-click way to produce it.
347
-
348
- ### Deprecation: `.contextzip.json`
349
-
350
- Earlier versions of contextzip read a `.contextzip.json` file at the project root for `workspace_location`/`scan_depth`. That file is now **deprecated** in favor of `.contextzip/config.json` — contextzip still reads it automatically if `.contextzip/config.json` doesn't exist yet (so nothing breaks), and prints a one-time reminder to migrate. To migrate, just move its contents into the `"workspace_location"`/`"scan_depth"` keys of a new `.contextzip/config.json` and delete the old file.
351
-
352
- `~/.config/contextzip/config.json` (your personal, per-machine config — API key, personal workspace override) is unrelated and unaffected by any of this.
353
-
354
- ---
355
-
356
- ## Visual config UI
357
-
358
- ```
359
- contextzip config --ui
360
- ```
361
-
362
- Opens a local browser tab for setting `always_include`/`always_exclude` by clicking through your actual file tree instead of hand-writing patterns — live file counts and packed size update as you go, and one-click chips suggest excluding things like PDFs, office docs, fonts, media, or anything over 1MB that isn't already covered by contextzip's default rules.
363
-
364
- It's also offered automatically the **first time** you run `contextzip` in a project that has no config at all:
365
-
366
- ```
367
- $ contextzip
368
- ╭─────────────────────────╮
369
- │ contextzip v0.3.5 │
370
- ╰─────────────────────────╯
371
- ...
372
- ╭─ First run ─────────────────────────────────────╮
373
- │ No project config found yet. │
374
- │ contextzip can open a local browser tab... │
375
- ╰───────────────────────────────────────────────────╯
376
- Set up include/exclude visually now? [Y/n]:
377
- ```
378
-
379
- Decline once and it won't ask again (run `contextzip config --ui` any time you want it). It's also skipped automatically for non-interactive runs, when `CI` is set, or when `--prompt`/`--output` are passed — it only ever offers on a plain, interactive, first-ever run.
380
-
381
- **Nothing about your project leaves your machine.** The server binds to `127.0.0.1` only (never `0.0.0.0`), every request needs a random per-session token embedded in the URL (the same approach Jupyter Notebook uses), and the page itself makes no calls anywhere except back to that local server — no CDN scripts, no web fonts, no analytics. Saving writes straight to `.contextzip/config.json` on disk and the server shuts itself down; if you close the tab without saving, it also shuts down after a short idle period so a forgotten session doesn't linger as an open port.
382
-
383
- If you accept the first-run offer, the same `contextzip` invocation picks up whatever you saved and finishes packaging immediately — no need to run it again.
384
-
385
378
  ---
386
379
 
387
380
  ## Contributing
@@ -14,6 +14,8 @@ Every AI session starts the same way: hunt down the relevant files, manually ski
14
14
 
15
15
  contextzip eliminates that entirely. Run it from your project root — it detects your stack, applies smart exclusions, produces a lean ZIP, and opens your file manager with the archive already selected. One `Ctrl+C` and you're done.
16
16
 
17
+ And when the AI tool hands you a ZIP back with the changes, `contextzip apply-zip` closes the loop — no manual unzip-and-hope, no losing track of what actually changed.
18
+
17
19
  ---
18
20
 
19
21
  ## Features
@@ -22,10 +24,10 @@ contextzip eliminates that entirely. Run it from your project root — it detect
22
24
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
23
25
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
24
26
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
27
+ - **Apply AI-returned changes back** — `contextzip apply-zip` takes the ZIP an AI tool hands you back and writes it into your project safely: diffed against what was actually sent, backed up before anything is overwritten, and never silently deleting anything
25
28
  - **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
26
- - **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip/config.json`
27
- - **Visual config UI** — `contextzip config --ui` opens a local browser tab to set include/exclude by clicking through your actual file tree, with live counts and one-click suggestions for PDFs, media, and other non-code files — nothing leaves your machine
28
- - **Persistent workspace** — all generated ZIPs land in `.contextzip/output/`, discoverable, reusable, and git-ignored automatically
29
+ - **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip.json`
30
+ - **Persistent workspace** — all generated ZIPs land in `.contextzip/`, discoverable, reusable, and git-ignored automatically
29
31
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
30
32
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
31
33
  - **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
@@ -67,7 +69,7 @@ contextzip will:
67
69
 
68
70
  1. Detect your framework (e.g. `Next.js + Node.js`)
69
71
  2. Apply the appropriate exclusion rules
70
- 3. Create a compressed ZIP in `.contextzip/output/` at your project root
72
+ 3. Create a compressed ZIP in `.contextzip/` at your project root
71
73
  4. Open your file manager with the ZIP selected and ready to copy
72
74
 
73
75
  ---
@@ -78,8 +80,6 @@ contextzip will:
78
80
  contextzip [OPTIONS]
79
81
  ```
80
82
 
81
- > `cz` is a shorthand alias for `contextzip` — both commands are identical and support every option and subcommand shown below (e.g. `cz --git-changes`, `cz exclude node_modules`).
82
-
83
83
  | Option | Description |
84
84
  |---|---|
85
85
  | `-p`, `--prompt TEXT` | Describe your task in plain English — Gemini selects only the relevant files |
@@ -92,7 +92,7 @@ contextzip [OPTIONS]
92
92
  | `--no-clipboard` | Skip the clipboard / folder-open step |
93
93
  | `--no-gitignore` | Ignore the project's `.gitignore` |
94
94
 
95
- **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
95
+ **Subcommands:** `exclude`, `include`, `apply-zip`, `watch`, `config` — run `contextzip --help` for full details.
96
96
 
97
97
  ---
98
98
 
@@ -119,6 +119,9 @@ contextzip --prompt "Refactor auth middleware" --dry-run
119
119
 
120
120
  # Save to a custom path
121
121
  contextzip --output ~/Desktop/project-context.zip
122
+
123
+ # Apply the ZIP an AI tool handed back
124
+ contextzip apply-zip
122
125
  ```
123
126
 
124
127
  ---
@@ -128,7 +131,7 @@ contextzip --output ~/Desktop/project-context.zip
128
131
  contextzip is also usable as a library. All CLI capabilities are available as plain Python functions — no Click, no Rich output, no `SystemExit`.
129
132
 
130
133
  ```python
131
- from contextzip import get_git_changes, get_files, create_zip
134
+ from contextzip import get_git_changes, get_files, create_zip, apply_zip
132
135
 
133
136
  # Get changed files and use them directly
134
137
  collection = get_git_changes()
@@ -144,6 +147,10 @@ with open(pkg.zip_path, "rb") as f:
144
147
  collection = get_files(include=["src/"], exclude=["tests/"])
145
148
  pkg = create_zip(collection, output="/tmp/upload.zip")
146
149
  print(f"{pkg.file_count} files, {pkg.compressed_bytes} bytes")
150
+
151
+ # Apply a ZIP an AI tool returned (auto-detects from .contextzip/inbox/)
152
+ result = apply_zip()
153
+ print(f"Wrote {len(result.written)} files, backup at {result.backup_dir}")
147
154
  ```
148
155
 
149
156
  | Function | Description |
@@ -151,9 +158,10 @@ print(f"{pkg.file_count} files, {pkg.compressed_bytes} bytes")
151
158
  | `get_git_changes(path?)` | Modified, added, and untracked files from git |
152
159
  | `get_files(path?, include?, exclude?)` | All project files after exclusion rules |
153
160
  | `create_zip(collection, output?)` | Write a `FileCollection` to a ZIP archive |
161
+ | `apply_zip(zip_path?, project_dir?, manifest?)` | Apply an AI-returned ZIP back into the project |
154
162
  | `detect_ecosystem(path?)` | Detect framework and confidence level |
155
163
 
156
- All functions default `path` to `Path.cwd()`. Errors raise typed exceptions (`NotARepositoryError`, `GitNotFoundError`, `NoFilesError`, etc.) rather than exiting.
164
+ All functions default `path` to `Path.cwd()`. Errors raise typed exceptions (`NotARepositoryError`, `GitNotFoundError`, `NoFilesError`, `ZipNotFoundError`, etc.) rather than exiting.
157
165
 
158
166
  ---
159
167
 
@@ -183,6 +191,61 @@ contextzip config --reset-key # clear and re-run setup
183
191
 
184
192
  ---
185
193
 
194
+ ## Applying AI-returned changes
195
+
196
+ Once an AI tool has made its edits and handed you back a ZIP, `contextzip apply-zip` writes those changes into your project — safely.
197
+
198
+ ```bash
199
+ # Drop the AI's returned zip into .contextzip/inbox/, then:
200
+ contextzip apply-zip
201
+
202
+ # Or point at it directly, wherever it landed:
203
+ contextzip apply-zip ~/Downloads/fixed-project.zip
204
+
205
+ # Preview first, with full per-file detail:
206
+ contextzip apply-zip --dry-run --verbose
207
+
208
+ # Skip the confirmation prompt (e.g. in a script):
209
+ contextzip apply-zip --yes
210
+ ```
211
+
212
+ ### How it knows what's safe to apply
213
+
214
+ Every ZIP `contextzip` creates gets a small manifest written next to it in `.contextzip/output/` — a hash of each included file, taken at zip-time. This manifest is **never added to the ZIP itself**. It stays local, so it's never uploaded and never visible to whatever AI tool the ZIP is pasted into — nothing for a model, or a curious teammate, to notice or ask about.
215
+
216
+ When you run `apply-zip`, it diffs the returned ZIP against that manifest and classifies every file:
217
+
218
+ | Status | Meaning | Applied automatically? |
219
+ |---|---|---|
220
+ | **New** | Wasn't part of the original ZIP | Yes |
221
+ | **Modified** | Was sent, content changed, and your local copy hasn't moved since | Yes |
222
+ | **Unchanged** | Identical to what's already on disk | Skipped — nothing to do |
223
+ | **Drifted** | Your local file changed (or was deleted) since you zipped it | No — flagged, asks first |
224
+ | **Untracked** | In the returned ZIP but has no baseline to compare against | No — flagged, asks first |
225
+
226
+ The common case — you zip some files, the AI edits them, nothing else touched your project meanwhile — applies straight through with just a summary printed, no prompt. Anything that could clobber your own work, or introduce a path you didn't expect, stops and asks before writing.
227
+
228
+ If a ZIP arrives with no matching manifest at all (e.g. it wasn't produced by a `contextzip` round trip), every already-existing path is treated as untracked and you'll be asked to confirm the whole batch.
229
+
230
+ ### What it does and doesn't do
231
+
232
+ - **Only adds and modifies files.** Deletions are never inferred from a ZIP's contents — a file that's simply missing from the returned ZIP is left alone.
233
+ - **Backs up before overwriting.** Every file about to be touched is copied into `.contextzip/backups/<timestamp>/` first, preserving its relative path.
234
+ - **Rejects unsafe paths outright.** Any ZIP entry that would resolve outside your project (`../../etc/passwd`-style path traversal) is refused before anything is written — no override flag, no exceptions.
235
+ - **Archives what it consumes.** Once applied, the ZIP is moved to `.contextzip/inbox/applied/<timestamp>-name.zip` — it won't be picked up again by accident, and stays around as an audit trail. ZIPs applied via an explicit path outside the inbox are left where you put them.
236
+
237
+ ### Finding the right ZIP and manifest
238
+
239
+ ```bash
240
+ contextzip apply-zip # exactly one *.zip in .contextzip/inbox/ → uses it
241
+ contextzip apply-zip path/to/fix.zip # explicit path always overrides the inbox
242
+ contextzip apply-zip --manifest path/to/codebase.manifest.json # override auto-detection
243
+ ```
244
+
245
+ If `.contextzip/inbox/` has more than one ZIP and you don't specify which, `apply-zip` lists them and asks you to be explicit rather than guessing. Manifest auto-detection picks the most recently created `*.manifest.json` under `.contextzip/output/` — correct for the normal one-round-trip-at-a-time flow; pass `--manifest` explicitly if you've generated several ZIPs before getting a response back.
246
+
247
+ ---
248
+
186
249
  ## Terminal error watcher
187
250
 
188
251
  The `watch` command wraps your dev server, buffers its output, and packages a debug-ready ZIP the moment you spot an error — no manual file hunting, no copy-pasting stack traces.
@@ -250,7 +313,7 @@ detects both Node.js and Python from `root/`, without either marker existing at
250
313
  By default, `.contextzip/` is created at your git root — that's `_find_git_root()` walking up from the current directory until it finds a `.git` folder; outside a repo, it falls back to the current directory. This can be overridden two ways, in order of priority:
251
314
 
252
315
  1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
253
- 2. **A committed `.contextzip/config.json` at the project root** — team-shared, applies to everyone who clones the repo:
316
+ 2. **A committed `.contextzip.json` at the project root** — team-shared, applies to everyone who clones the repo:
254
317
  ```json
255
318
  { "workspace_location": "git-root" }
256
319
  ```
@@ -264,95 +327,25 @@ By default, `.contextzip/` is created at your git root — that's `_find_git_roo
264
327
 
265
328
  Accepted values are `"git-root"`, `"cwd"`, or any path (absolute, or relative to the git root). Run `contextzip config` with no flags to see which value is currently active and where it came from.
266
329
 
267
- This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip/config.json` instead if you'd rather each subproject keep its own.
330
+ This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip.json` instead if you'd rather each subproject keep its own.
268
331
 
269
- ---
270
-
271
- ## Project configuration
272
-
273
- Every project gets a `.contextzip/` workspace at the project root:
332
+ **Workspace layout:**
274
333
 
275
334
  ```
276
335
  .contextzip/
277
- ├── config.json # project preferences — commit this
278
- ├── .gitignore # keeps output/ untracked, config.json trackable
279
- └── output/ # generated ZIPs — never committed
280
- ├── codebase.zip
281
- ├── changes.zip (--git-changes)
282
- └── debug-context.zip (contextzip watch)
336
+ config.json # team-shared preferences (committed)
337
+ .gitignore # ignores everything below except itself + config.json
338
+ output/
339
+ codebase.zip # what you generate and send out
340
+ codebase.manifest.json # local-only — never uploaded, used by apply-zip
341
+ inbox/
342
+ <ai-returned>.zip # drop AI-returned zips here for apply-zip to pick up
343
+ applied/
344
+ <timestamp>-name.zip # archived after a successful apply-zip
345
+ backups/
346
+ <timestamp>/ # pre-overwrite copies, one folder per apply-zip run
283
347
  ```
284
348
 
285
- `.contextzip/` is **not** committed to Git by default — `.contextzip/.gitignore` is written automatically the first time you run contextzip in a repo, and it ignores everything in the folder *except* `config.json` and itself. That's deliberate: `output/` is per-machine, disposable scratch space, while `config.json` holds team-shared preferences you'll usually want everyone on the same page about.
286
-
287
- `config.json` currently supports:
288
-
289
- ```json
290
- {
291
- "workspace_location": "git-root",
292
- "scan_depth": null,
293
- "always_include": [],
294
- "always_exclude": [],
295
- "ai": {
296
- "enabled": true,
297
- "provider": "gemini",
298
- "max_files": 10
299
- }
300
- }
301
- ```
302
-
303
- All keys are optional — start with just the ones you need.
304
-
305
- | Key | What it does |
306
- | --- | --- |
307
- | `workspace_location` | Same as `contextzip config --set-workspace`, but team-shared. See [Workspace location](#workspace-location). |
308
- | `scan_depth` | Reserved for a future bounded-depth scan mode. |
309
- | `always_include` | A standing "force include" list (gitwildmatch patterns). Files matching it are packaged even if an auto-rule or `.gitignore` would otherwise exclude them — e.g. `["docs/architecture.md"]` to always pull in a doc that lives in an otherwise-excluded folder. |
310
- | `always_exclude` | A standing exclusion list, applied on every run without retyping `-e`/`--exclude`. |
311
- | `ai.enabled` | Set to `false` to disable `--prompt` entirely for this project; contextzip will refuse with a clear message instead of silently ignoring it. |
312
- | `ai.provider` | Reserved for future AI providers. Only `"gemini"` is currently supported. |
313
- | `ai.max_files` | Caps how many files `--prompt` mode may return, for both the Gemini and keyword-heuristic paths. |
314
-
315
- `always_include`/`always_exclude` are additive on top of `--include`/`--exclude` for that run — an explicit `contextzip include PATH` still has the final say over what's actually packaged. Persistent preferences belong in `config.json` rather than an ever-growing list of CLI flags; CLI flags stay for one-off, explicit actions (`--dry-run`, `--prompt "…"`, `--output FILE`, etc.).
316
-
317
- You don't have to hand-write `config.json` — see [Visual config UI](#visual-config-ui) below for a point-and-click way to produce it.
318
-
319
- ### Deprecation: `.contextzip.json`
320
-
321
- Earlier versions of contextzip read a `.contextzip.json` file at the project root for `workspace_location`/`scan_depth`. That file is now **deprecated** in favor of `.contextzip/config.json` — contextzip still reads it automatically if `.contextzip/config.json` doesn't exist yet (so nothing breaks), and prints a one-time reminder to migrate. To migrate, just move its contents into the `"workspace_location"`/`"scan_depth"` keys of a new `.contextzip/config.json` and delete the old file.
322
-
323
- `~/.config/contextzip/config.json` (your personal, per-machine config — API key, personal workspace override) is unrelated and unaffected by any of this.
324
-
325
- ---
326
-
327
- ## Visual config UI
328
-
329
- ```
330
- contextzip config --ui
331
- ```
332
-
333
- Opens a local browser tab for setting `always_include`/`always_exclude` by clicking through your actual file tree instead of hand-writing patterns — live file counts and packed size update as you go, and one-click chips suggest excluding things like PDFs, office docs, fonts, media, or anything over 1MB that isn't already covered by contextzip's default rules.
334
-
335
- It's also offered automatically the **first time** you run `contextzip` in a project that has no config at all:
336
-
337
- ```
338
- $ contextzip
339
- ╭─────────────────────────╮
340
- │ contextzip v0.3.5 │
341
- ╰─────────────────────────╯
342
- ...
343
- ╭─ First run ─────────────────────────────────────╮
344
- │ No project config found yet. │
345
- │ contextzip can open a local browser tab... │
346
- ╰───────────────────────────────────────────────────╯
347
- Set up include/exclude visually now? [Y/n]:
348
- ```
349
-
350
- Decline once and it won't ask again (run `contextzip config --ui` any time you want it). It's also skipped automatically for non-interactive runs, when `CI` is set, or when `--prompt`/`--output` are passed — it only ever offers on a plain, interactive, first-ever run.
351
-
352
- **Nothing about your project leaves your machine.** The server binds to `127.0.0.1` only (never `0.0.0.0`), every request needs a random per-session token embedded in the URL (the same approach Jupyter Notebook uses), and the page itself makes no calls anywhere except back to that local server — no CDN scripts, no web fonts, no analytics. Saving writes straight to `.contextzip/config.json` on disk and the server shuts itself down; if you close the tab without saving, it also shuts down after a short idle period so a forgotten session doesn't linger as an open port.
353
-
354
- If you accept the first-run offer, the same `contextzip` invocation picks up whatever you saved and finishes packaging immediately — no need to run it again.
355
-
356
349
  ---
357
350
 
358
351
  ## Contributing
@@ -1,6 +1,6 @@
1
1
  """contextzip — intelligent codebase packager for AI tools."""
2
2
 
3
- __version__ = "0.3.7"
3
+ __version__ = "0.3.8"
4
4
 
5
5
  from contextzip.api import (
6
6
  FileCollection,
@@ -9,26 +9,32 @@ from contextzip.api import (
9
9
  GitNotFoundError,
10
10
  GitCommandError,
11
11
  NoFilesError,
12
+ ZipNotFoundError,
12
13
  get_git_changes,
13
14
  get_files,
14
15
  create_zip,
16
+ apply_zip,
15
17
  detect_ecosystem,
16
18
  )
17
19
  from contextzip.packager import PackageResult
20
+ from contextzip.applier import ApplyResult
18
21
 
19
22
  __all__ = [
20
23
  # Functions
21
24
  "get_git_changes",
22
25
  "get_files",
23
26
  "create_zip",
27
+ "apply_zip",
24
28
  "detect_ecosystem",
25
29
  # Data types
26
30
  "FileCollection",
27
31
  "PackageResult",
32
+ "ApplyResult",
28
33
  # Exceptions
29
34
  "ContextzipError",
30
35
  "NotARepositoryError",
31
36
  "GitNotFoundError",
32
37
  "GitCommandError",
33
38
  "NoFilesError",
39
+ "ZipNotFoundError",
34
40
  ]