mirror of
https://github.com/akadeepesh/contextzip.git
synced 2026-10-20 08:36:20 +02:00
feat(cli): wire per-mode output, auto-cleanup, and redaction into the run
- Main run, apply-zip, watch, and config converted from Panel-based messaging to ok/warn/err/info. - Wire write_scan_report()/write_apply_report() into the main packaging flow and apply-zip; print a dim 'Full report: ...' line. - Compute run mode (standard/git-changes/prompt) once and pass it through to create_zip() and the report, replacing the old git_changes boolean. - Pass limits.redact_secrets into create_zip() and print a one-line redaction summary when anything was caught. - Auto-run .contextzip/ cleanup after every successful command (main run and apply-zip) per the new cleanup.* project settings. - Drop the '.gitignore patterns applied' line entirely (dead weight, no signal) and remove the now-unused used_gitignore variable. - Clipboard step no longer wraps in a status spinner; folder-opened no longer prints anything.
This commit is contained in:
+69
-12
@@ -54,6 +54,7 @@ from contextzip.cli_display import (
|
||||
print_clipboard_result,
|
||||
print_apply_plan,
|
||||
print_apply_result,
|
||||
print_auto_cleanup,
|
||||
)
|
||||
from contextzip.report import write_scan_report, write_apply_report
|
||||
from contextzip.applier import (
|
||||
@@ -71,6 +72,7 @@ from contextzip.cli_ai import (
|
||||
run_ai_selection_preview,
|
||||
)
|
||||
from contextzip.cli_onboard import onboard_api_key
|
||||
from contextzip import cleanup as cleanup_mod
|
||||
|
||||
console = Console()
|
||||
|
||||
@@ -434,6 +436,9 @@ def cmd_apply_zip(
|
||||
report_path = write_apply_report(project_dir=project_dir, plan=plan, result=result)
|
||||
print_report_hint(report_path)
|
||||
|
||||
# ── Auto-cleanup ─────────────────────────────────────────────────────────
|
||||
_auto_cleanup(project_dir, project_cfg)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Subcommand: watch
|
||||
@@ -461,7 +466,7 @@ def cmd_watch(command: tuple[str, ...]) -> None:
|
||||
|
||||
[D] package debug context [S] skip
|
||||
|
||||
Pressing D immediately writes .contextzip/output/debug-context.zip containing:
|
||||
Pressing D immediately writes .contextzip/output/watch/debug-context.zip containing:
|
||||
· prompt.txt auto-generated task description
|
||||
· terminal-error.txt the cleaned error output
|
||||
· source-files.zip source files referenced in the stack trace
|
||||
@@ -634,7 +639,7 @@ def cmd_config(
|
||||
if key:
|
||||
masked = key[:8] + "…" + key[-4:] if len(key) > 12 else "****"
|
||||
source = "environment variable" if from_env else str(config_path())
|
||||
ok(f"Gemini API key configured", f"{masked} · {source}")
|
||||
ok("Gemini API key configured", f"{masked} · {source}")
|
||||
else:
|
||||
warn("No Gemini API key configured")
|
||||
info('Run contextzip --prompt "your task" to set one up.')
|
||||
@@ -652,7 +657,9 @@ def cmd_config(
|
||||
|
||||
project_cfg = load_project_config(cwd)
|
||||
if has_legacy_project_config(cwd):
|
||||
warn("Using the deprecated .contextzip.json — move it to .contextzip/config.json")
|
||||
warn(
|
||||
"Using the deprecated .contextzip.json — move it to .contextzip/config.json"
|
||||
)
|
||||
else:
|
||||
always_include = ", ".join(project_cfg.always_include) or "none"
|
||||
always_exclude = ", ".join(project_cfg.always_exclude) or "none"
|
||||
@@ -663,7 +670,14 @@ def cmd_config(
|
||||
ok(f"Project config: {project_config_path(cwd)}{cfg_note}")
|
||||
info(f"always_include: {always_include}")
|
||||
info(f"always_exclude: {always_exclude}")
|
||||
info(f"ai: enabled={ai.enabled} provider={ai.provider} max_files={ai.max_files}")
|
||||
info(
|
||||
f"ai: enabled={ai.enabled} provider={ai.provider} max_files={ai.max_files}"
|
||||
)
|
||||
cleanup_cfg = project_cfg.cleanup
|
||||
info(
|
||||
f"cleanup: enabled={cleanup_cfg.enabled} "
|
||||
f"keep_recent={cleanup_cfg.keep_recent}"
|
||||
)
|
||||
|
||||
console.print()
|
||||
|
||||
@@ -687,7 +701,9 @@ def _enforce_ai_config(ai_cfg) -> None:
|
||||
|
||||
if not is_known_ai_provider(ai_cfg.provider):
|
||||
err(f"Unsupported AI provider: {ai_cfg.provider}")
|
||||
info('Only "gemini" is currently supported — update ai.provider in .contextzip/config.json.')
|
||||
info(
|
||||
'Only "gemini" is currently supported — update ai.provider in .contextzip/config.json.'
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
@@ -743,6 +759,41 @@ def _maybe_offer_config_ui(
|
||||
return launch_config_ui(project_dir, detection, con=console)
|
||||
|
||||
|
||||
def _auto_cleanup(project_dir: Path, project_cfg) -> None:
|
||||
"""
|
||||
Automatically, silently prune the .contextzip/ workspace after a
|
||||
successful command — no confirmation, no dry-run, no separate command
|
||||
to remember. Every zip is trivially reproducible by re-running
|
||||
contextzip, so this is deliberately brutal rather than cautious: it
|
||||
keeps only the `cleanup.keep_recent` most recent zip/manifest/report
|
||||
set per mode folder, the most recent `cleanup.keep_recent` backup
|
||||
folder(s), and the most recent `cleanup.keep_recent` archived
|
||||
applied-zip(s) — everything else is deleted immediately, every run.
|
||||
|
||||
Gated on `cleanup.enabled` (default True) — set to False in
|
||||
.contextzip/config.json to turn this off entirely.
|
||||
|
||||
Scanning + deleting only ever touches .contextzip/ metadata (a
|
||||
handful of small files), never the project itself, so this stays
|
||||
fast even on large projects — it does not rescan or re-read any
|
||||
project source file.
|
||||
"""
|
||||
if not project_cfg.cleanup.enabled:
|
||||
return
|
||||
try:
|
||||
plan = cleanup_mod.scan(
|
||||
project_dir,
|
||||
keep_recent=project_cfg.cleanup.keep_recent,
|
||||
applied_zip_retention=project_cfg.applied_zip_retention,
|
||||
)
|
||||
if plan.is_empty:
|
||||
return
|
||||
result = cleanup_mod.execute(plan)
|
||||
except OSError:
|
||||
return
|
||||
print_auto_cleanup(result)
|
||||
|
||||
|
||||
def _run(
|
||||
*,
|
||||
extra_exclude: list[str] | None,
|
||||
@@ -766,7 +817,9 @@ def _run(
|
||||
# ── Project config (.contextzip/config.json) ────────────────────────────
|
||||
project_cfg = load_project_config(project_dir)
|
||||
if has_legacy_project_config(project_dir):
|
||||
warn("Found the deprecated .contextzip.json — move it to .contextzip/config.json")
|
||||
warn(
|
||||
"Found the deprecated .contextzip.json — move it to .contextzip/config.json"
|
||||
)
|
||||
|
||||
# ── Detection ────────────────────────────────────────────────────────────
|
||||
with console.status("[cyan]Detecting project ecosystem…[/]", spinner="dots"):
|
||||
@@ -818,9 +871,7 @@ def _run(
|
||||
# from project config — both are additive, persistent behavior comes
|
||||
# from config.json so it doesn't need to be re-typed every run.
|
||||
normalized_exclude = [normalize_pattern(p) for p in extra_exclude or []]
|
||||
normalized_exclude += [
|
||||
normalize_pattern(p) for p in project_cfg.always_exclude
|
||||
]
|
||||
normalized_exclude += [normalize_pattern(p) for p in project_cfg.always_exclude]
|
||||
|
||||
with console.status("[cyan]Building exclusion rules…[/]", spinner="dots"):
|
||||
spec = build_spec(
|
||||
@@ -914,13 +965,16 @@ def _run(
|
||||
|
||||
if not selected_paths:
|
||||
err("AI selection returned no files.")
|
||||
info("Try a more specific prompt, or run without --prompt to package the full project.")
|
||||
info(
|
||||
"Try a more specific prompt, or run without --prompt to package the full project."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
resolved.included = selected_paths
|
||||
|
||||
# ── Create ZIP ───────────────────────────────────────────────────────────
|
||||
output_path = Path(output).resolve() if output else None
|
||||
run_mode = "git-changes" if git_changes else ("prompt" if prompt else "standard")
|
||||
|
||||
try:
|
||||
result = create_zip(
|
||||
@@ -928,7 +982,7 @@ def _run(
|
||||
project_dir=project_dir,
|
||||
output_path=output_path,
|
||||
console=console,
|
||||
git_changes=git_changes,
|
||||
mode=run_mode,
|
||||
prompt_txt=prompt_txt,
|
||||
)
|
||||
except Exception as exc:
|
||||
@@ -948,7 +1002,7 @@ def _run(
|
||||
project_dir=project_dir,
|
||||
detection=detection,
|
||||
resolved=resolved,
|
||||
mode="git-changes" if git_changes else ("prompt" if prompt else "standard"),
|
||||
mode=run_mode,
|
||||
ai_prompt=prompt,
|
||||
ai_selected=resolved.included if prompt else None,
|
||||
large_file_warn_bytes=large_file_warn_bytes,
|
||||
@@ -959,3 +1013,6 @@ def _run(
|
||||
if not no_clipboard:
|
||||
cb = clipboard_handle(result.zip_path)
|
||||
print_clipboard_result(cb)
|
||||
|
||||
# ── Auto-cleanup ─────────────────────────────────────────────────────────
|
||||
_auto_cleanup(project_dir, project_cfg)
|
||||
|
||||
Reference in New Issue
Block a user