contextzip 0.2.0__tar.gz → 0.2.2__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 (24) hide show
  1. {contextzip-0.2.0 → contextzip-0.2.2}/PKG-INFO +19 -2
  2. {contextzip-0.2.0 → contextzip-0.2.2}/README.md +18 -1
  3. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/__init__.py +1 -1
  4. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/cli.py +254 -42
  5. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/PKG-INFO +19 -2
  6. {contextzip-0.2.0 → contextzip-0.2.2}/pyproject.toml +1 -1
  7. {contextzip-0.2.0 → contextzip-0.2.2}/LICENSE +0 -0
  8. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/clipboard.py +0 -0
  9. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/detector.py +0 -0
  10. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/filters.py +0 -0
  11. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/git.py +0 -0
  12. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/packager.py +0 -0
  13. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/__init__.py +0 -0
  14. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/base.py +0 -0
  15. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/go.py +0 -0
  16. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/node.py +0 -0
  17. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/python.py +0 -0
  18. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip/rules/rust.py +0 -0
  19. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/SOURCES.txt +0 -0
  20. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/dependency_links.txt +0 -0
  21. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/entry_points.txt +0 -0
  22. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/requires.txt +0 -0
  23. {contextzip-0.2.0 → contextzip-0.2.2}/contextzip.egg-info/top_level.txt +0 -0
  24. {contextzip-0.2.0 → contextzip-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
@@ -112,6 +112,8 @@ contextzip [OPTIONS]
112
112
  |---|---|
113
113
  | `-i`, `--include PATH` | Only include files under this path. Repeatable. |
114
114
  | `-e`, `--exclude PATTERN` | Extra exclusion patterns (gitignore syntax). Repeatable. |
115
+ | `exclude` | Subcommand: exclude specific files/patterns. `contextzip exclude CHANGELOG.md LICENSE .github/` |
116
+ | `include` | Subcommand: include only specific paths. `contextzip include src/ app/` |
115
117
  | `--git-changes` | Only include files reported by git as modified, staged, or untracked. |
116
118
  | `-n`, `--dry-run` | Preview what would be included, no ZIP created. |
117
119
  | `-o`, `--output FILE` | Custom output path for the ZIP file. |
@@ -140,6 +142,21 @@ contextzip --include src --include app
140
142
  contextzip --exclude "*.log" --exclude "*.sqlite" --exclude "tests/"
141
143
  ```
142
144
 
145
+ **Exclude files using the subcommand (space-separated, no repetition):**
146
+ ```bash
147
+ contextzip exclude CHANGELOG.md CONTRIBUTING.md LICENSE .github/
148
+ ```
149
+
150
+ **Exclude with flags:**
151
+ ```bash
152
+ contextzip exclude CHANGELOG.md --dry-run --verbose
153
+ ```
154
+
155
+ **Include only specific directories using the subcommand:**
156
+ ```bash
157
+ contextzip include src/ app/
158
+ ```
159
+
143
160
  **Only package files changed in git:**
144
161
  ```bash
145
162
  contextzip --git-changes
@@ -234,7 +251,7 @@ contextzip surfaces issues before they waste your time:
234
251
  contextzip/
235
252
  ├── contextzip/
236
253
  │ ├── __init__.py # version string
237
- │ ├── cli.py # Click entry point, all flags, rich output
254
+ │ ├── cli.py # Click entry point, all flags, subcommands (exclude, include), rich output
238
255
  │ ├── detector.py # framework/language detection engine
239
256
  │ ├── filters.py # pathspec-based file filtering + ResolveResult
240
257
  │ ├── git.py # git status parsing + changed-file detection
@@ -84,6 +84,8 @@ contextzip [OPTIONS]
84
84
  |---|---|
85
85
  | `-i`, `--include PATH` | Only include files under this path. Repeatable. |
86
86
  | `-e`, `--exclude PATTERN` | Extra exclusion patterns (gitignore syntax). Repeatable. |
87
+ | `exclude` | Subcommand: exclude specific files/patterns. `contextzip exclude CHANGELOG.md LICENSE .github/` |
88
+ | `include` | Subcommand: include only specific paths. `contextzip include src/ app/` |
87
89
  | `--git-changes` | Only include files reported by git as modified, staged, or untracked. |
88
90
  | `-n`, `--dry-run` | Preview what would be included, no ZIP created. |
89
91
  | `-o`, `--output FILE` | Custom output path for the ZIP file. |
@@ -112,6 +114,21 @@ contextzip --include src --include app
112
114
  contextzip --exclude "*.log" --exclude "*.sqlite" --exclude "tests/"
113
115
  ```
114
116
 
117
+ **Exclude files using the subcommand (space-separated, no repetition):**
118
+ ```bash
119
+ contextzip exclude CHANGELOG.md CONTRIBUTING.md LICENSE .github/
120
+ ```
121
+
122
+ **Exclude with flags:**
123
+ ```bash
124
+ contextzip exclude CHANGELOG.md --dry-run --verbose
125
+ ```
126
+
127
+ **Include only specific directories using the subcommand:**
128
+ ```bash
129
+ contextzip include src/ app/
130
+ ```
131
+
115
132
  **Only package files changed in git:**
116
133
  ```bash
117
134
  contextzip --git-changes
@@ -206,7 +223,7 @@ contextzip surfaces issues before they waste your time:
206
223
  contextzip/
207
224
  ├── contextzip/
208
225
  │ ├── __init__.py # version string
209
- │ ├── cli.py # Click entry point, all flags, rich output
226
+ │ ├── cli.py # Click entry point, all flags, subcommands (exclude, include), rich output
210
227
  │ ├── detector.py # framework/language detection engine
211
228
  │ ├── filters.py # pathspec-based file filtering + ResolveResult
212
229
  │ ├── git.py # git status parsing + changed-file detection
@@ -1,3 +1,3 @@
1
1
  """contextzip — intelligent codebase packager for AI tools."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
@@ -1,5 +1,27 @@
1
1
  """
2
2
  cli.py — contextzip entry point. Phases 1–5 complete.
3
+
4
+ Command surface
5
+ ───────────────
6
+ Main command (unchanged, fully backwards-compatible):
7
+ contextzip [OPTIONS]
8
+ -i / --include PATH only include files under these paths (repeatable)
9
+ -e / --exclude PATTERN extra exclusion patterns (repeatable)
10
+ -n / --dry-run preview without creating ZIP
11
+ -o / --output FILE custom output path
12
+ --no-clipboard skip clipboard / folder-open step
13
+ --no-gitignore ignore project .gitignore
14
+ --git-changes only package files changed in git
15
+ -v / --verbose show every file decision
16
+
17
+ Subcommands (new — all modifier flags available on each):
18
+ contextzip exclude PATTERN… [OPTIONS]
19
+ contextzip include PATH… [OPTIONS]
20
+
21
+ Both subcommands accept the same modifier flags as the main command so that
22
+ the flags naturally follow the verb, matching the git / docker / cargo UX:
23
+ contextzip exclude CHANGELOG.md --dry-run --verbose
24
+ contextzip include src/ app/ --output ~/Desktop/out.zip
3
25
  """
4
26
 
5
27
  from __future__ import annotations
@@ -30,55 +52,88 @@ console = Console()
30
52
 
31
53
 
32
54
  # ---------------------------------------------------------------------------
33
- # CLI
55
+ # Shared modifier-flag decorator
34
56
  # ---------------------------------------------------------------------------
57
+ # Defined once so the main command and every subcommand declare exactly the
58
+ # same flags without duplicating help strings.
59
+
60
+ def _modifier_options(f):
61
+ """
62
+ Attach all run-modifier flags to a command.
63
+
64
+ Applied to both the main command and every subcommand so that flags
65
+ always follow the verb — matching the git / docker / cargo convention:
66
+ contextzip exclude CHANGELOG.md --dry-run --verbose
67
+ """
68
+ decorators = [
69
+ click.option(
70
+ "--dry-run", "-n",
71
+ is_flag=True, default=False,
72
+ help="Show what would be included without creating the ZIP.",
73
+ ),
74
+ click.option(
75
+ "--output", "-o",
76
+ default=None, metavar="FILE",
77
+ help="Output ZIP path. Defaults to <project>_context_<timestamp>.zip in temp dir.",
78
+ ),
79
+ click.option(
80
+ "--no-clipboard",
81
+ is_flag=True, default=False,
82
+ help="Skip clipboard / folder-open step after creating the ZIP.",
83
+ ),
84
+ click.option(
85
+ "--no-gitignore",
86
+ is_flag=True, default=False,
87
+ help="Ignore the project's .gitignore file (use only built-in rules).",
88
+ ),
89
+ click.option(
90
+ "--git-changes",
91
+ is_flag=True, default=False,
92
+ help=(
93
+ "Only include files that git reports as modified, added, or untracked. "
94
+ "Requires the project to be inside a git repository."
95
+ ),
96
+ ),
97
+ click.option(
98
+ "--verbose", "-v",
99
+ is_flag=True, default=False,
100
+ help="Show every included and excluded file.",
101
+ ),
102
+ ]
103
+ for dec in reversed(decorators):
104
+ f = dec(f)
105
+ return f
106
+
35
107
 
36
- @click.command(context_settings={"help_option_names": ["-h", "--help"]})
108
+ # ---------------------------------------------------------------------------
109
+ # CLI group
110
+ # ---------------------------------------------------------------------------
111
+
112
+ @click.group(
113
+ invoke_without_command=True,
114
+ context_settings={"help_option_names": ["-h", "--help"]},
115
+ )
37
116
  @click.option(
38
117
  "--include", "-i",
39
118
  multiple=True, metavar="PATH",
40
- help="Only include files under these paths (relative to project root). "
41
- "Repeatable: --include src --include app",
119
+ help=(
120
+ "Only include files under these paths (relative to project root). "
121
+ "Repeatable: --include src --include app | or use: contextzip include src app"
122
+ ),
42
123
  )
43
124
  @click.option(
44
125
  "--exclude", "-e",
45
126
  multiple=True, metavar="PATTERN",
46
- help="Extra exclusion patterns on top of auto-rules (gitignore syntax). "
47
- "Repeatable: --exclude '*.log' --exclude temp.js",
48
- )
49
- @click.option(
50
- "--dry-run", "-n",
51
- is_flag=True, default=False,
52
- help="Show what would be included without creating the ZIP.",
53
- )
54
- @click.option(
55
- "--output", "-o",
56
- default=None, metavar="FILE",
57
- help="Output ZIP path. Defaults to <project>_context_<timestamp>.zip in temp dir.",
58
- )
59
- @click.option(
60
- "--no-clipboard",
61
- is_flag=True, default=False,
62
- help="Skip clipboard / folder-open step after creating the ZIP.",
63
- )
64
- @click.option(
65
- "--no-gitignore",
66
- is_flag=True, default=False,
67
- help="Ignore the project's .gitignore file (use only built-in rules).",
68
- )
69
- @click.option(
70
- "--git-changes",
71
- is_flag=True, default=False,
72
- help="Only include files that git reports as modified, added, or untracked. "
73
- "Requires the project to be inside a git repository.",
74
- )
75
- @click.option(
76
- "--verbose", "-v",
77
- is_flag=True, default=False,
78
- help="Show every included and excluded file.",
127
+ help=(
128
+ "Extra exclusion patterns on top of auto-rules (gitignore syntax). "
129
+ "Repeatable: -e '*.log' -e CHANGELOG.md | or use: contextzip exclude CHANGELOG.md *.log"
130
+ ),
79
131
  )
132
+ @_modifier_options
80
133
  @click.version_option(version=__version__, prog_name="contextzip")
134
+ @click.pass_context
81
135
  def main(
136
+ ctx: click.Context,
82
137
  include: tuple[str, ...],
83
138
  exclude: tuple[str, ...],
84
139
  dry_run: bool,
@@ -94,8 +149,132 @@ def main(
94
149
 
95
150
  Run from your project root to produce a smart, lightweight ZIP
96
151
  ready to paste directly into Claude, ChatGPT, or any AI interface.
152
+
153
+ \b
154
+ SUBCOMMANDS
155
+ contextzip exclude CHANGELOG.md LICENSE .github/
156
+ contextzip include src/ app/
157
+
158
+ Both subcommands accept the same flags as the main command:
159
+ contextzip exclude CHANGELOG.md --dry-run --verbose
160
+ """
161
+ # A subcommand was invoked — let it handle everything.
162
+ if ctx.invoked_subcommand is not None:
163
+ return
164
+
165
+ _run(
166
+ extra_exclude=list(exclude),
167
+ include_only=list(include),
168
+ dry_run=dry_run,
169
+ output=output,
170
+ no_clipboard=no_clipboard,
171
+ no_gitignore=no_gitignore,
172
+ git_changes=git_changes,
173
+ verbose=verbose,
174
+ )
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # Subcommand: exclude
179
+ # ---------------------------------------------------------------------------
180
+
181
+ @main.command("exclude")
182
+ @click.argument("patterns", nargs=-1, required=True, metavar="PATTERN…")
183
+ @_modifier_options
184
+ def cmd_exclude(
185
+ patterns: tuple[str, ...],
186
+ dry_run: bool,
187
+ output: str | None,
188
+ no_clipboard: bool,
189
+ no_gitignore: bool,
190
+ git_changes: bool,
191
+ verbose: bool,
192
+ ) -> None:
193
+ """
194
+ Exclude specific files or patterns and package everything else.
195
+
196
+ \b
197
+ EXAMPLES
198
+ contextzip exclude CHANGELOG.md CONTRIBUTING.md LICENSE
199
+ contextzip exclude .github/ tests/ '*.log'
200
+ contextzip exclude CHANGELOG.md --dry-run --verbose
201
+ contextzip exclude .github/ CHANGELOG.md --output ~/Desktop/out.zip
202
+
203
+ Patterns follow gitignore syntax. Folders are matched with or without
204
+ a trailing slash: both '.github' and '.github/' work.
205
+ """
206
+ _run(
207
+ extra_exclude=list(patterns),
208
+ include_only=None,
209
+ dry_run=dry_run,
210
+ output=output,
211
+ no_clipboard=no_clipboard,
212
+ no_gitignore=no_gitignore,
213
+ git_changes=git_changes,
214
+ verbose=verbose,
215
+ )
216
+
217
+
218
+ # ---------------------------------------------------------------------------
219
+ # Subcommand: include
220
+ # ---------------------------------------------------------------------------
221
+
222
+ @main.command("include")
223
+ @click.argument("paths", nargs=-1, required=True, metavar="PATH…")
224
+ @_modifier_options
225
+ def cmd_include(
226
+ paths: tuple[str, ...],
227
+ dry_run: bool,
228
+ output: str | None,
229
+ no_clipboard: bool,
230
+ no_gitignore: bool,
231
+ git_changes: bool,
232
+ verbose: bool,
233
+ ) -> None:
97
234
  """
235
+ Package only the specified paths and skip everything else.
236
+
237
+ \b
238
+ EXAMPLES
239
+ contextzip include src/ app/
240
+ contextzip include src/ app/ --dry-run
241
+ contextzip include src/ --output ~/Desktop/out.zip --verbose
98
242
 
243
+ Paths are matched as exact prefixes at directory boundaries:
244
+ 'src' matches 'src/index.ts' but not 'src2/index.ts'.
245
+ """
246
+ _run(
247
+ extra_exclude=None,
248
+ include_only=list(paths),
249
+ dry_run=dry_run,
250
+ output=output,
251
+ no_clipboard=no_clipboard,
252
+ no_gitignore=no_gitignore,
253
+ git_changes=git_changes,
254
+ verbose=verbose,
255
+ )
256
+
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # Core execution logic (shared by main command + all subcommands)
260
+ # ---------------------------------------------------------------------------
261
+
262
+ def _run(
263
+ *,
264
+ extra_exclude: list[str] | None,
265
+ include_only: list[str] | None,
266
+ dry_run: bool,
267
+ output: str | None,
268
+ no_clipboard: bool,
269
+ no_gitignore: bool,
270
+ git_changes: bool,
271
+ verbose: bool,
272
+ ) -> None:
273
+ """
274
+ All actual work lives here. The main command and every subcommand
275
+ delegate to this function after collecting their arguments/flags,
276
+ keeping the CLI surface thin and the logic testable in isolation.
277
+ """
99
278
  project_dir = Path(os.getcwd()).resolve()
100
279
 
101
280
  # ── Header ───────────────────────────────────────────────────────────────
@@ -153,10 +332,15 @@ def main(
153
332
  and gitignore_path.is_file()
154
333
  )
155
334
 
335
+ normalized_exclude = (
336
+ [_normalize_pattern(p) for p in extra_exclude]
337
+ if extra_exclude else []
338
+ )
339
+
156
340
  with console.status("[cyan]Building exclusion rules…[/]", spinner="dots"):
157
341
  spec = build_spec(
158
342
  rule_modules=detection.rule_modules,
159
- extra_exclude=list(exclude) if exclude else None,
343
+ extra_exclude=normalized_exclude if normalized_exclude else None,
160
344
  gitignore_path=gitignore_path,
161
345
  )
162
346
 
@@ -169,7 +353,7 @@ def main(
169
353
  resolved = resolve_files(
170
354
  project_dir=project_dir,
171
355
  spec=spec,
172
- include_only=list(include) if include else None,
356
+ include_only=include_only if include_only else None,
173
357
  )
174
358
 
175
359
  # ── File scan summary ────────────────────────────────────────────────────
@@ -189,7 +373,8 @@ def main(
189
373
  if len(resolved.large_files) > 5:
190
374
  console.print(f" [dim]… and {len(resolved.large_files) - 5} more[/]")
191
375
  console.print(
192
- f" [dim] Use --exclude to drop them if unneeded.[/]"
376
+ " [dim] Use [cyan]-e PATTERN[/] or [cyan]contextzip exclude PATTERN[/] "
377
+ "to drop them if unneeded.[/]"
193
378
  )
194
379
 
195
380
  # ── Warnings: binary files ───────────────────────────────────────────────
@@ -232,7 +417,7 @@ def main(
232
417
  if not resolved.included:
233
418
  console.print(
234
419
  "\n[red]Nothing to package.[/] All files were excluded — "
235
- "try [cyan]--include[/] to override."
420
+ "try [cyan]contextzip include PATH[/] or [cyan]-i PATH[/] to override."
236
421
  )
237
422
  return
238
423
 
@@ -272,6 +457,33 @@ def main(
272
457
  _print_clipboard_result(cb)
273
458
 
274
459
 
460
+ # ---------------------------------------------------------------------------
461
+ # Normalisation
462
+ # ---------------------------------------------------------------------------
463
+
464
+ def _normalize_pattern(p: str) -> str:
465
+ """
466
+ Canonicalise a user-supplied exclusion pattern.
467
+
468
+ Rules applied in order:
469
+ 1. Strip a leading ``./`` or ``.\\`` so that ``./CHANGELOG.md``
470
+ and ``CHANGELOG.md`` are treated identically.
471
+ 2. Replace every backslash with a forward slash for cross-platform
472
+ consistency (Windows paths entered on the CLI).
473
+ 3. Collapse ``folder/*`` → ``folder/`` so that gitignore-style
474
+ directory globs work as expected.
475
+ """
476
+ # 1. Strip leading ./ or .\
477
+ if p.startswith("./") or p.startswith(".\\"):
478
+ p = p[2:]
479
+ # 2. Normalise path separators
480
+ p = p.replace("\\", "/")
481
+ # 3. folder/* → folder/
482
+ if p.endswith("/*"):
483
+ p = p[:-1]
484
+ return p
485
+
486
+
275
487
  # ---------------------------------------------------------------------------
276
488
  # Display helpers
277
489
  # ---------------------------------------------------------------------------
@@ -436,4 +648,4 @@ def _human_size(n: int) -> str:
436
648
  if n < 1024:
437
649
  return f"{n:.0f} {unit}"
438
650
  n /= 1024
439
- return f"{n:.1f} TB"
651
+ return f"{n:.1f} TB"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
@@ -112,6 +112,8 @@ contextzip [OPTIONS]
112
112
  |---|---|
113
113
  | `-i`, `--include PATH` | Only include files under this path. Repeatable. |
114
114
  | `-e`, `--exclude PATTERN` | Extra exclusion patterns (gitignore syntax). Repeatable. |
115
+ | `exclude` | Subcommand: exclude specific files/patterns. `contextzip exclude CHANGELOG.md LICENSE .github/` |
116
+ | `include` | Subcommand: include only specific paths. `contextzip include src/ app/` |
115
117
  | `--git-changes` | Only include files reported by git as modified, staged, or untracked. |
116
118
  | `-n`, `--dry-run` | Preview what would be included, no ZIP created. |
117
119
  | `-o`, `--output FILE` | Custom output path for the ZIP file. |
@@ -140,6 +142,21 @@ contextzip --include src --include app
140
142
  contextzip --exclude "*.log" --exclude "*.sqlite" --exclude "tests/"
141
143
  ```
142
144
 
145
+ **Exclude files using the subcommand (space-separated, no repetition):**
146
+ ```bash
147
+ contextzip exclude CHANGELOG.md CONTRIBUTING.md LICENSE .github/
148
+ ```
149
+
150
+ **Exclude with flags:**
151
+ ```bash
152
+ contextzip exclude CHANGELOG.md --dry-run --verbose
153
+ ```
154
+
155
+ **Include only specific directories using the subcommand:**
156
+ ```bash
157
+ contextzip include src/ app/
158
+ ```
159
+
143
160
  **Only package files changed in git:**
144
161
  ```bash
145
162
  contextzip --git-changes
@@ -234,7 +251,7 @@ contextzip surfaces issues before they waste your time:
234
251
  contextzip/
235
252
  ├── contextzip/
236
253
  │ ├── __init__.py # version string
237
- │ ├── cli.py # Click entry point, all flags, rich output
254
+ │ ├── cli.py # Click entry point, all flags, subcommands (exclude, include), rich output
238
255
  │ ├── detector.py # framework/language detection engine
239
256
  │ ├── filters.py # pathspec-based file filtering + ResolveResult
240
257
  │ ├── git.py # git status parsing + changed-file detection
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "contextzip"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Intelligently package your codebase for AI tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes