contextzip 0.3.4__tar.gz → 0.3.5__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 (45) hide show
  1. {contextzip-0.3.4 → contextzip-0.3.5}/PKG-INFO +37 -44
  2. {contextzip-0.3.4 → contextzip-0.3.5}/README.md +36 -43
  3. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/__init__.py +1 -1
  4. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/cli.py +58 -222
  5. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/cli_display.py +8 -56
  6. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/cli_onboard.py +0 -59
  7. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/clipboard.py +0 -86
  8. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/config.py +24 -19
  9. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/detector.py +122 -12
  10. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/git.py +0 -131
  11. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/packager.py +83 -18
  12. contextzip-0.3.5/contextzip/project_config.py +93 -0
  13. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/PKG-INFO +37 -44
  14. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/SOURCES.txt +1 -5
  15. {contextzip-0.3.4 → contextzip-0.3.5}/pyproject.toml +1 -1
  16. contextzip-0.3.4/contextzip/brief.py +0 -280
  17. contextzip-0.3.4/contextzip/claude_artifacts.py +0 -150
  18. contextzip-0.3.4/contextzip/claude_export.py +0 -73
  19. contextzip-0.3.4/contextzip/code_changes.py +0 -257
  20. contextzip-0.3.4/contextzip/markers.py +0 -61
  21. {contextzip-0.3.4 → contextzip-0.3.5}/LICENSE +0 -0
  22. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/ai/__init__.py +0 -0
  23. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/ai/gemini.py +0 -0
  24. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/ai/heuristic.py +0 -0
  25. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/ai/selector.py +0 -0
  26. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/api.py +0 -0
  27. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/cli_ai.py +0 -0
  28. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/error_parser.py +0 -0
  29. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/filters.py +0 -0
  30. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/__init__.py +0 -0
  31. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/base.py +0 -0
  32. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/errors/__init__.py +0 -0
  33. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/errors/node.py +0 -0
  34. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/errors/python.py +0 -0
  35. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/go.py +0 -0
  36. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/node.py +0 -0
  37. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/python.py +0 -0
  38. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/ruby.py +0 -0
  39. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/rules/rust.py +0 -0
  40. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip/watcher.py +0 -0
  41. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/dependency_links.txt +0 -0
  42. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/entry_points.txt +0 -0
  43. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/requires.txt +0 -0
  44. {contextzip-0.3.4 → contextzip-0.3.5}/contextzip.egg-info/top_level.txt +0 -0
  45. {contextzip-0.3.4 → contextzip-0.3.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
@@ -47,13 +47,13 @@ contextzip eliminates that entirely. Run it from your project root — it detect
47
47
 
48
48
  ## Features
49
49
 
50
- - **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each
50
+ - **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each. Detection isn't limited to the project root: a shallow, bounded scan of subdirectories means a monorepo (`frontend/` + `backend/`, etc.) gets every ecosystem it contains detected and excluded correctly, not just whatever sits at the top level
51
51
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
52
52
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
53
53
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
54
54
  - **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
- - **End-of-day / handoff prompts** — `contextzip eod` and `contextzip handoff` turn a Claude/ChatGPT conversation export plus today's code changes into a paste-ready prompt, copied straight to your clipboard
56
- - **Persistent workspace** — all generated ZIPs land in `.contextzip/` at your project root, discoverable, reusable, and git-ignored automatically
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.json`
56
+ - **Persistent workspace** — all generated ZIPs land in `.contextzip/`, discoverable, reusable, and git-ignored automatically
57
57
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
58
58
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
59
59
  - **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
@@ -118,7 +118,7 @@ contextzip [OPTIONS]
118
118
  | `--no-clipboard` | Skip the clipboard / folder-open step |
119
119
  | `--no-gitignore` | Ignore the project's `.gitignore` |
120
120
 
121
- **Subcommands:** `exclude`, `include`, `watch`, `config`, `eod`, `handoff` — run `contextzip --help` for full details.
121
+ **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
122
122
 
123
123
  ---
124
124
 
@@ -244,60 +244,53 @@ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Y
244
244
 
245
245
  ---
246
246
 
247
- ## End-of-day reports and chat handoffs
248
-
249
- If you work through a problem in a Claude or ChatGPT conversation and need to either (a) summarize what you did for an end-of-day report, or (b) continue the same work in a fresh chat after hitting a usage limit, `eod` and `handoff` build the prompt for you — contextzip does no summarizing itself; that's left to whichever AI tool you paste the result into.
250
-
251
- ```bash
252
- contextzip eod
253
- contextzip handoff
254
- ```
247
+ ## What gets excluded
255
248
 
256
- **Setup:** export your conversation (any markdown export works) and drop the `.md` file into `exports/` at your project root. Both commands pick the most recently modified file there automatically.
249
+ contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
257
250
 
258
- **What gets built:**
251
+ **Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` working folder.
259
252
 
260
- - The conversation itself — pasted directly into the prompt if it's short, or referenced as an attachment if it's long enough that inlining it would burn through the next chat's context budget
261
- - Whatever code changed, resolved per file in priority order:
262
- 1. **Not pushed** — diffed against your upstream branch, plus the complete current file
263
- 2. **Diverged from Claude** — diffed against the version Claude last produced (if you've set up a session key — see below), plus the complete current codebase file
264
- 3. **Since last run** — diffed against a per-branch checkpoint that `eod`/`handoff` remember automatically, plus the complete current file
265
- - New, untracked files are included as full content (there's nothing to diff them against)
266
- - `eod` ends the prompt with a flat instruction to produce a work-log table; `handoff` frames it as continuing the project in a new chat
253
+ **By framework:**
267
254
 
268
- The result is copied straight to your clipboard, and also saved to `.contextzip/` if you want to look it over first.
255
+ | Stack | Additional exclusions |
256
+ |---|---|
257
+ | Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
258
+ | Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
259
+ | Rust | `target/`, `Cargo.lock`, `*.rlib` |
260
+ | Go | `vendor/`, `go.sum`, `bin/` |
269
261
 
270
- **Optional — diffing against Claude's own version (case 2):** if you give contextzip your Claude.ai session key, `eod`/`handoff` will fetch the files Claude actually produced and compare them against your codebase, catching drift even after everything's pushed and in sync.
262
+ Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied. Marker files don't have to sit at the project root: contextzip also does a shallow, bounded scan of subdirectories (2 levels deep, skipping `node_modules/`, `.venv/`, `.git/`, and other dependency/build dirs it would never want to look inside anyway), so a layout like
271
263
 
272
- ```bash
273
- contextzip config --set-session-key
274
264
  ```
275
-
276
- This is best-effort by design — it depends on an undocumented Claude.ai endpoint, so a missing key, an expired cookie, or the endpoint changing shape just skips this check with a warning rather than failing the whole command.
277
-
278
- ```bash
279
- contextzip eod --dry-run # preview without advancing the checkpoint
280
- contextzip handoff --no-fetch # skip the Claude-artifact fetch for this run
265
+ root/
266
+ frontend/package.json
267
+ backend/requirements.txt
281
268
  ```
282
269
 
283
- ---
270
+ detects both Node.js and Python from `root/`, without either marker existing at the top level. Run with default output (not `--dry-run --verbose`) and you'll see which subdirectory each ecosystem came from, e.g. `Next.js (frontend/) + FastAPI (backend/)`.
284
271
 
285
- ## What gets excluded
272
+ ---
286
273
 
287
- contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
274
+ ## Workspace location
288
275
 
289
- **Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` and `exports/` working folders.
276
+ 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:
290
277
 
291
- **By framework:**
278
+ 1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
279
+ 2. **A committed `.contextzip.json` at the project root** — team-shared, applies to everyone who clones the repo:
280
+ ```json
281
+ { "workspace_location": "git-root" }
282
+ ```
283
+ 3. **Your personal config** — a per-machine default that doesn't get committed:
284
+ ```bash
285
+ contextzip config --set-workspace cwd # always use ./.contextzip wherever you run it
286
+ contextzip config --set-workspace git-root # back to the default
287
+ contextzip config --set-workspace ~/zips # a fixed custom location, anywhere
288
+ contextzip config --reset-workspace # clear your personal override
289
+ ```
292
290
 
293
- | Stack | Additional exclusions |
294
- |---|---|
295
- | Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
296
- | Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
297
- | Rust | `target/`, `Cargo.lock`, `*.rlib` |
298
- | Go | `vendor/`, `go.sum`, `bin/` |
291
+ 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.
299
292
 
300
- Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied.
293
+ 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.
301
294
 
302
295
  ---
303
296
 
@@ -18,13 +18,13 @@ contextzip eliminates that entirely. Run it from your project root — it detect
18
18
 
19
19
  ## Features
20
20
 
21
- - **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each
21
+ - **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each. Detection isn't limited to the project root: a shallow, bounded scan of subdirectories means a monorepo (`frontend/` + `backend/`, etc.) gets every ecosystem it contains detected and excluded correctly, not just whatever sits at the top level
22
22
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
23
23
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
24
24
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
25
25
  - **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
- - **End-of-day / handoff prompts** — `contextzip eod` and `contextzip handoff` turn a Claude/ChatGPT conversation export plus today's code changes into a paste-ready prompt, copied straight to your clipboard
27
- - **Persistent workspace** — all generated ZIPs land in `.contextzip/` at your project root, discoverable, reusable, and git-ignored automatically
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.json`
27
+ - **Persistent workspace** — all generated ZIPs land in `.contextzip/`, discoverable, reusable, and git-ignored automatically
28
28
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
29
29
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
30
30
  - **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
@@ -89,7 +89,7 @@ contextzip [OPTIONS]
89
89
  | `--no-clipboard` | Skip the clipboard / folder-open step |
90
90
  | `--no-gitignore` | Ignore the project's `.gitignore` |
91
91
 
92
- **Subcommands:** `exclude`, `include`, `watch`, `config`, `eod`, `handoff` — run `contextzip --help` for full details.
92
+ **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
93
93
 
94
94
  ---
95
95
 
@@ -215,60 +215,53 @@ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Y
215
215
 
216
216
  ---
217
217
 
218
- ## End-of-day reports and chat handoffs
219
-
220
- If you work through a problem in a Claude or ChatGPT conversation and need to either (a) summarize what you did for an end-of-day report, or (b) continue the same work in a fresh chat after hitting a usage limit, `eod` and `handoff` build the prompt for you — contextzip does no summarizing itself; that's left to whichever AI tool you paste the result into.
221
-
222
- ```bash
223
- contextzip eod
224
- contextzip handoff
225
- ```
218
+ ## What gets excluded
226
219
 
227
- **Setup:** export your conversation (any markdown export works) and drop the `.md` file into `exports/` at your project root. Both commands pick the most recently modified file there automatically.
220
+ contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
228
221
 
229
- **What gets built:**
222
+ **Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` working folder.
230
223
 
231
- - The conversation itself — pasted directly into the prompt if it's short, or referenced as an attachment if it's long enough that inlining it would burn through the next chat's context budget
232
- - Whatever code changed, resolved per file in priority order:
233
- 1. **Not pushed** — diffed against your upstream branch, plus the complete current file
234
- 2. **Diverged from Claude** — diffed against the version Claude last produced (if you've set up a session key — see below), plus the complete current codebase file
235
- 3. **Since last run** — diffed against a per-branch checkpoint that `eod`/`handoff` remember automatically, plus the complete current file
236
- - New, untracked files are included as full content (there's nothing to diff them against)
237
- - `eod` ends the prompt with a flat instruction to produce a work-log table; `handoff` frames it as continuing the project in a new chat
224
+ **By framework:**
238
225
 
239
- The result is copied straight to your clipboard, and also saved to `.contextzip/` if you want to look it over first.
226
+ | Stack | Additional exclusions |
227
+ |---|---|
228
+ | Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
229
+ | Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
230
+ | Rust | `target/`, `Cargo.lock`, `*.rlib` |
231
+ | Go | `vendor/`, `go.sum`, `bin/` |
240
232
 
241
- **Optional — diffing against Claude's own version (case 2):** if you give contextzip your Claude.ai session key, `eod`/`handoff` will fetch the files Claude actually produced and compare them against your codebase, catching drift even after everything's pushed and in sync.
233
+ Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied. Marker files don't have to sit at the project root: contextzip also does a shallow, bounded scan of subdirectories (2 levels deep, skipping `node_modules/`, `.venv/`, `.git/`, and other dependency/build dirs it would never want to look inside anyway), so a layout like
242
234
 
243
- ```bash
244
- contextzip config --set-session-key
245
235
  ```
246
-
247
- This is best-effort by design — it depends on an undocumented Claude.ai endpoint, so a missing key, an expired cookie, or the endpoint changing shape just skips this check with a warning rather than failing the whole command.
248
-
249
- ```bash
250
- contextzip eod --dry-run # preview without advancing the checkpoint
251
- contextzip handoff --no-fetch # skip the Claude-artifact fetch for this run
236
+ root/
237
+ frontend/package.json
238
+ backend/requirements.txt
252
239
  ```
253
240
 
254
- ---
241
+ detects both Node.js and Python from `root/`, without either marker existing at the top level. Run with default output (not `--dry-run --verbose`) and you'll see which subdirectory each ecosystem came from, e.g. `Next.js (frontend/) + FastAPI (backend/)`.
255
242
 
256
- ## What gets excluded
243
+ ---
257
244
 
258
- contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
245
+ ## Workspace location
259
246
 
260
- **Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` and `exports/` working folders.
247
+ 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:
261
248
 
262
- **By framework:**
249
+ 1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
250
+ 2. **A committed `.contextzip.json` at the project root** — team-shared, applies to everyone who clones the repo:
251
+ ```json
252
+ { "workspace_location": "git-root" }
253
+ ```
254
+ 3. **Your personal config** — a per-machine default that doesn't get committed:
255
+ ```bash
256
+ contextzip config --set-workspace cwd # always use ./.contextzip wherever you run it
257
+ contextzip config --set-workspace git-root # back to the default
258
+ contextzip config --set-workspace ~/zips # a fixed custom location, anywhere
259
+ contextzip config --reset-workspace # clear your personal override
260
+ ```
263
261
 
264
- | Stack | Additional exclusions |
265
- |---|---|
266
- | Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
267
- | Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
268
- | Rust | `target/`, `Cargo.lock`, `*.rlib` |
269
- | Go | `vendor/`, `go.sum`, `bin/` |
262
+ 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.
270
263
 
271
- Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied.
264
+ 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.
272
265
 
273
266
  ---
274
267
 
@@ -1,6 +1,6 @@
1
1
  """contextzip — intelligent codebase packager for AI tools."""
2
2
 
3
- __version__ = "0.3.4"
3
+ __version__ = "0.3.5"
4
4
 
5
5
  from contextzip.api import (
6
6
  FileCollection,
@@ -15,19 +15,16 @@ from contextzip import __version__
15
15
  from contextzip.config import (
16
16
  get_api_key,
17
17
  delete_api_key,
18
- get_session_key,
19
- delete_session_key,
20
18
  config_path,
21
19
  diagnose_api_key,
20
+ save_workspace_location,
21
+ delete_workspace_location,
22
22
  )
23
23
  from contextzip.detector import detect
24
24
  from contextzip.filters import build_spec, resolve_files, resolve_files_from_git
25
25
  from contextzip.git import get_changed_files, GitError
26
26
  from contextzip.packager import create_zip
27
- from contextzip.clipboard import (
28
- handle as clipboard_handle,
29
- handle_text as clipboard_handle_text,
30
- )
27
+ from contextzip.clipboard import handle as clipboard_handle
31
28
 
32
29
  from contextzip.cli_display import (
33
30
  print_detection,
@@ -37,14 +34,13 @@ from contextzip.cli_display import (
37
34
  print_package_result,
38
35
  print_zip_write_warnings,
39
36
  print_clipboard_result,
40
- print_brief_result,
41
37
  )
42
38
  from contextzip.cli_ai import (
43
39
  normalize_pattern,
44
40
  run_ai_selection,
45
41
  run_ai_selection_preview,
46
42
  )
47
- from contextzip.cli_onboard import onboard_api_key, onboard_session_key
43
+ from contextzip.cli_onboard import onboard_api_key
48
44
 
49
45
  console = Console()
50
46
 
@@ -377,166 +373,6 @@ def cmd_watch(command: tuple[str, ...]) -> None:
377
373
  raise SystemExit(exit_code)
378
374
 
379
375
 
380
- @main.command("eod")
381
- @click.option(
382
- "--exports-dir",
383
- default=None,
384
- metavar="DIR",
385
- help="Folder containing conversation exports (default: ./exports).",
386
- )
387
- @click.option(
388
- "--no-clipboard",
389
- is_flag=True,
390
- default=False,
391
- help="Skip copying the prompt to the clipboard.",
392
- )
393
- @click.option(
394
- "--no-fetch",
395
- is_flag=True,
396
- default=False,
397
- help="Don't auto-fetch Claude's artifact files even if a session key is configured.",
398
- )
399
- @click.option(
400
- "--dry-run",
401
- "-n",
402
- is_flag=True,
403
- default=False,
404
- help="Build the prompt without advancing the eod marker.",
405
- )
406
- def cmd_eod(
407
- exports_dir: str | None, no_clipboard: bool, no_fetch: bool, dry_run: bool
408
- ) -> None:
409
- """
410
- Build a paste-ready end-of-day prompt from today's conversation + code changes.
411
-
412
- \b
413
- EXAMPLES
414
- contextzip eod
415
- contextzip eod --dry-run
416
- contextzip eod --exports-dir ~/claude-exports
417
-
418
- Picks the most recently modified .md file in exports/, packages whatever
419
- code changed since the last time eod ran (diffs for files that already
420
- existed, full content for new ones), and copies a ready-to-paste prompt
421
- to your clipboard. contextzip does no summarizing itself — that's the
422
- job of whatever AI tool you paste the prompt into.
423
- """
424
- _run_brief("eod", exports_dir, no_clipboard, no_fetch, dry_run)
425
-
426
-
427
- @main.command("handoff")
428
- @click.option(
429
- "--exports-dir",
430
- default=None,
431
- metavar="DIR",
432
- help="Folder containing conversation exports (default: ./exports).",
433
- )
434
- @click.option(
435
- "--no-clipboard",
436
- is_flag=True,
437
- default=False,
438
- help="Skip copying the prompt to the clipboard.",
439
- )
440
- @click.option(
441
- "--no-fetch",
442
- is_flag=True,
443
- default=False,
444
- help="Don't auto-fetch Claude's artifact files even if a session key is configured.",
445
- )
446
- @click.option(
447
- "--dry-run",
448
- "-n",
449
- is_flag=True,
450
- default=False,
451
- help="Build the prompt without advancing the handoff marker.",
452
- )
453
- def cmd_handoff(
454
- exports_dir: str | None, no_clipboard: bool, no_fetch: bool, dry_run: bool
455
- ) -> None:
456
- """
457
- Build a paste-ready prompt to continue this project in a fresh chat.
458
-
459
- \b
460
- EXAMPLES
461
- contextzip handoff
462
- contextzip handoff --dry-run
463
-
464
- Use this when you've hit a usage limit and need to start a new chat
465
- without losing context: picks the latest conversation export, packages
466
- the code changed since the last handoff (or eod), and copies a prompt
467
- ready to paste into the new chat.
468
- """
469
- _run_brief("handoff", exports_dir, no_clipboard, no_fetch, dry_run)
470
-
471
-
472
- def _run_brief(
473
- kind: str,
474
- exports_dir: str | None,
475
- no_clipboard: bool,
476
- no_fetch: bool,
477
- dry_run: bool,
478
- ) -> None:
479
- """Shared implementation for cmd_eod and cmd_handoff — see brief.py for the actual logic."""
480
- from contextzip.brief import run_brief, BriefError
481
-
482
- project_dir = Path(os.getcwd()).resolve()
483
-
484
- console.print()
485
- console.print(
486
- Panel.fit(
487
- f"[bold cyan]contextzip[/] [dim]{kind}[/]\n"
488
- f"[dim]Project:[/] [white]{project_dir}[/]",
489
- border_style="cyan",
490
- padding=(0, 2),
491
- )
492
- )
493
- console.print()
494
-
495
- exports_path = Path(exports_dir).resolve() if exports_dir else None
496
-
497
- try:
498
- with console.status(f"[cyan]Building {kind} prompt…[/]", spinner="dots"):
499
- result = run_brief(
500
- kind=kind,
501
- project_dir=project_dir,
502
- exports_dir=exports_path,
503
- auto_fetch_artifacts=not no_fetch,
504
- dry_run=dry_run,
505
- )
506
- except BriefError as exc:
507
- console.print(
508
- Panel.fit(
509
- f"[red]{exc}[/]",
510
- border_style="red",
511
- padding=(0, 2),
512
- )
513
- )
514
- raise SystemExit(1)
515
-
516
- print_brief_result(result)
517
-
518
- if dry_run:
519
- console.print()
520
- console.print(
521
- Panel.fit(
522
- "[yellow]Dry run:[/] the prompt and any changes zip above were "
523
- "still written so you can review them, but the checkpoint was "
524
- "[bold]not[/] advanced.\n"
525
- "[dim]Run again without --dry-run once you're happy with this — "
526
- "it'll pick up the same files.[/]",
527
- border_style="yellow",
528
- padding=(0, 2),
529
- )
530
- )
531
- return
532
-
533
- if not no_clipboard:
534
- console.print()
535
- with console.status("[cyan]Preparing clipboard…[/]", spinner="dots"):
536
- cb = clipboard_handle_text(result.prompt_text, result.prompt_path)
537
- print_clipboard_result(cb)
538
-
539
-
540
376
  @main.command("config")
541
377
  @click.option(
542
378
  "--reset-key",
@@ -545,16 +381,17 @@ def _run_brief(
545
381
  help="Clear the stored Gemini API key and re-run the setup prompt.",
546
382
  )
547
383
  @click.option(
548
- "--set-session-key",
549
- is_flag=True,
550
- default=False,
551
- help="Set up the Claude.ai session key used by eod/handoff's case-2 fetching.",
384
+ "--set-workspace",
385
+ default=None,
386
+ metavar="LOCATION",
387
+ help='Set your personal default for where .contextzip/ lives: "git-root", '
388
+ '"cwd", or a path.',
552
389
  )
553
390
  @click.option(
554
- "--reset-session-key",
391
+ "--reset-workspace",
555
392
  is_flag=True,
556
393
  default=False,
557
- help="Clear the stored Claude session key and re-run its setup prompt.",
394
+ help="Clear your personal workspace location override.",
558
395
  )
559
396
  @click.option(
560
397
  "--show-key-path",
@@ -564,8 +401,8 @@ def _run_brief(
564
401
  )
565
402
  def cmd_config(
566
403
  reset_key: bool,
567
- set_session_key: bool,
568
- reset_session_key: bool,
404
+ set_workspace: str | None,
405
+ reset_workspace: bool,
569
406
  show_key_path: bool,
570
407
  ) -> None:
571
408
  """
@@ -573,25 +410,45 @@ def cmd_config(
573
410
 
574
411
  \b
575
412
  EXAMPLES
576
- contextzip config --reset-key # clear Gemini key, re-onboard
577
- contextzip config --set-session-key # set up the Claude session key
578
- contextzip config --reset-session-key # clear it and re-run setup
579
- contextzip config --show-key-path # print config file location
413
+ contextzip config --reset-key # clear Gemini key, re-onboard
414
+ contextzip config --set-workspace cwd # always use ./.contextzip here
415
+ contextzip config --set-workspace git-root # back to the default
416
+ contextzip config --set-workspace ~/zips # a fixed custom location
417
+ contextzip config --reset-workspace # clear the personal override
418
+ contextzip config --show-key-path # print config file location
419
+
420
+ \b
421
+ A workspace location can also be pinned for the whole team by committing
422
+ a .contextzip.json file at the project root:
423
+ {"workspace_location": "git-root"}
424
+ Project config (if present) takes priority over this personal setting —
425
+ see the README for the full precedence order.
580
426
  """
581
427
  if show_key_path:
582
428
  console.print(f"\n [dim]Config file:[/] [cyan]{config_path()}[/]\n")
583
429
  return
584
430
 
585
- if set_session_key or reset_session_key:
586
- if reset_session_key:
587
- removed = delete_session_key()
431
+ if set_workspace is not None or reset_workspace:
432
+ if reset_workspace:
433
+ removed = delete_workspace_location()
588
434
  if removed:
589
435
  console.print(
590
- f"\n [green]✓[/] Session key removed from [dim]{config_path()}[/]"
436
+ f"\n [green]✓[/] Workspace override removed from "
437
+ f"[dim]{config_path()}[/]"
591
438
  )
592
439
  else:
593
- console.print("\n [dim]No session key was stored.[/]")
594
- onboard_session_key()
440
+ console.print("\n [dim]No personal workspace override was set.[/]")
441
+ if set_workspace is None:
442
+ return
443
+
444
+ if set_workspace is not None:
445
+ save_workspace_location(set_workspace)
446
+ console.print(
447
+ f"\n [green]✓[/] Personal workspace location set to "
448
+ f"[cyan]{set_workspace}[/] in [dim]{config_path()}[/]\n"
449
+ f" [dim]A project-level .contextzip.json, if present, still "
450
+ f"takes priority over this.[/]"
451
+ )
595
452
  return
596
453
 
597
454
  if reset_key:
@@ -645,46 +502,25 @@ def cmd_config(
645
502
  )
646
503
 
647
504
  console.print()
648
- session_key = get_session_key()
649
- session_from_env = bool(os.environ.get("CLAUDE_SESSION_KEY", "").strip())
650
-
651
- if session_key:
652
- masked = (
653
- session_key[:6] + "…" + session_key[-4:]
654
- if len(session_key) > 10
655
- else "****"
656
- )
657
- source = (
658
- "[dim](from environment variable)[/]"
659
- if session_from_env
660
- else f"[dim]({config_path()})[/]"
661
- )
662
- console.print(
663
- Panel(
664
- f" [dim]Claude session key:[/] [green]{masked}[/] {source}\n\n"
665
- " [dim]Used by [cyan]eod[/]/[cyan]handoff[/] for case-2 fetching. "
666
- "Run [cyan]contextzip config --reset-session-key[/] to change it.[/]",
667
- title="[bold]contextzip config[/]",
668
- border_style="cyan",
669
- padding=(0, 2),
670
- )
671
- )
672
- else:
673
- console.print(
674
- Panel(
675
- " No Claude session key configured.\n\n"
676
- " [dim]Optional — only needed for eod/handoff's diverged-from-Claude "
677
- "check.[/]\n"
678
- " Run [cyan]contextzip config --set-session-key[/] to set one up.",
679
- title="[bold]contextzip config[/]",
680
- border_style="yellow",
681
- padding=(0, 2),
682
- )
505
+
506
+ from contextzip.packager import _resolve_workspace_location
507
+
508
+ ws_location, ws_source = _resolve_workspace_location(Path(os.getcwd()).resolve())
509
+ console.print(
510
+ Panel(
511
+ f" [dim]Workspace location:[/] [green]{ws_location}[/] [dim]({ws_source})[/]\n\n"
512
+ " [dim]Run [cyan]contextzip config --set-workspace <location>[/] to set "
513
+ "a personal default,\n"
514
+ " or commit a [cyan].contextzip.json[/] at the project root to share "
515
+ "one with your team.[/]",
516
+ title="[bold]contextzip config[/]",
517
+ border_style="cyan",
518
+ padding=(0, 2),
683
519
  )
520
+ )
684
521
  console.print()
685
522
 
686
523
 
687
- # ---------------------------------------------------------------------------
688
524
  # Core execution logic (shared by main command + all subcommands)
689
525
  # ---------------------------------------------------------------------------
690
526