@ictechgy/context-guard 0.4.0

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 (71) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +4 -0
  4. package/README.ko.md +353 -0
  5. package/README.md +353 -0
  6. package/context-guard-kit/README.md +76 -0
  7. package/context-guard-kit/benchmark_runner.py +1898 -0
  8. package/context-guard-kit/claude_transcript_cost_audit.py +1591 -0
  9. package/context-guard-kit/context_compress.py +543 -0
  10. package/context-guard-kit/context_escrow.py +919 -0
  11. package/context-guard-kit/context_guard_cli.py +149 -0
  12. package/context-guard-kit/context_guard_diet.py +1036 -0
  13. package/context-guard-kit/context_pack.py +929 -0
  14. package/context-guard-kit/failed_attempt_nudge.py +567 -0
  15. package/context-guard-kit/guard_large_read.py +690 -0
  16. package/context-guard-kit/hook_secret_patterns.py +43 -0
  17. package/context-guard-kit/read_symbol.py +483 -0
  18. package/context-guard-kit/rewrite_bash_for_token_budget.py +501 -0
  19. package/context-guard-kit/sanitize_output.py +725 -0
  20. package/context-guard-kit/settings.example.json +67 -0
  21. package/context-guard-kit/setup_wizard.py +1724 -0
  22. package/context-guard-kit/statusline.sh +362 -0
  23. package/context-guard-kit/statusline_merged.sh +157 -0
  24. package/context-guard-kit/tool_schema_pruner.py +837 -0
  25. package/context-guard-kit/trim_command_output.py +1098 -0
  26. package/docs/distribution.md +55 -0
  27. package/package.json +70 -0
  28. package/packaging/homebrew/context-guard.rb.template +34 -0
  29. package/plugins/context-guard/.claude-plugin/plugin.json +41 -0
  30. package/plugins/context-guard/LICENSE +201 -0
  31. package/plugins/context-guard/NOTICE +4 -0
  32. package/plugins/context-guard/README.ko.md +135 -0
  33. package/plugins/context-guard/README.md +135 -0
  34. package/plugins/context-guard/bin/claude-read-symbol +6 -0
  35. package/plugins/context-guard/bin/claude-sanitize-output +6 -0
  36. package/plugins/context-guard/bin/claude-token-artifact +6 -0
  37. package/plugins/context-guard/bin/claude-token-audit +6 -0
  38. package/plugins/context-guard/bin/claude-token-bench +6 -0
  39. package/plugins/context-guard/bin/claude-token-diet +6 -0
  40. package/plugins/context-guard/bin/claude-token-failed-nudge +6 -0
  41. package/plugins/context-guard/bin/claude-token-guard-read +6 -0
  42. package/plugins/context-guard/bin/claude-token-rewrite-bash +6 -0
  43. package/plugins/context-guard/bin/claude-token-setup +6 -0
  44. package/plugins/context-guard/bin/claude-token-statusline +6 -0
  45. package/plugins/context-guard/bin/claude-token-statusline-merged +6 -0
  46. package/plugins/context-guard/bin/claude-trim-output +6 -0
  47. package/plugins/context-guard/bin/context-guard +149 -0
  48. package/plugins/context-guard/bin/context-guard-artifact +919 -0
  49. package/plugins/context-guard/bin/context-guard-audit +1591 -0
  50. package/plugins/context-guard/bin/context-guard-bench +1898 -0
  51. package/plugins/context-guard/bin/context-guard-compress +543 -0
  52. package/plugins/context-guard/bin/context-guard-diet +1036 -0
  53. package/plugins/context-guard/bin/context-guard-failed-nudge +567 -0
  54. package/plugins/context-guard/bin/context-guard-guard-read +690 -0
  55. package/plugins/context-guard/bin/context-guard-pack +929 -0
  56. package/plugins/context-guard/bin/context-guard-read-symbol +483 -0
  57. package/plugins/context-guard/bin/context-guard-rewrite-bash +501 -0
  58. package/plugins/context-guard/bin/context-guard-sanitize-output +725 -0
  59. package/plugins/context-guard/bin/context-guard-setup +1724 -0
  60. package/plugins/context-guard/bin/context-guard-statusline +362 -0
  61. package/plugins/context-guard/bin/context-guard-statusline-merged +157 -0
  62. package/plugins/context-guard/bin/context-guard-tool-prune +837 -0
  63. package/plugins/context-guard/bin/context-guard-trim-output +1098 -0
  64. package/plugins/context-guard/brief/README.md +65 -0
  65. package/plugins/context-guard/brief/brief-mode.lite.md +29 -0
  66. package/plugins/context-guard/brief/brief-mode.standard.md +31 -0
  67. package/plugins/context-guard/brief/brief-mode.ultra.md +32 -0
  68. package/plugins/context-guard/lib/hook_secret_patterns.py +43 -0
  69. package/plugins/context-guard/skills/audit/SKILL.md +39 -0
  70. package/plugins/context-guard/skills/optimize/SKILL.md +48 -0
  71. package/plugins/context-guard/skills/setup/SKILL.md +40 -0
@@ -0,0 +1,135 @@
1
+ # ContextGuard
2
+
3
+ ContextGuard is a local-first context-hygiene toolkit for AI coding and tool agents. It ships as a Claude Code plugin first, then extends the same project-local guardrails to other agents through plain local helper commands and advisory brief-mode rule snippets.
4
+
5
+ Start with `/context-guard:setup`. Setup is explicit, project-local, and reversible: it merges recommended project settings, prints a read-only context hygiene scan, does not mutate global Claude settings, and does not configure external AI offload.
6
+
7
+ ## Token-waste paths it targets
8
+
9
+ ContextGuard is a local context-hygiene layer, not a provider prompt cache or semantic answer cache. Its helpers reduce avoidable context bloat before it enters an agent conversation: large file reads are steered toward search/symbol/line-range slices, long command output can be trimmed or digested, large logs can be stored as local artifact receipts, secret-like values are redacted best-effort, repeated Bash failures trigger a strategy nudge, cache-friendly prompt layout can be audited from bounded redacted segment hashes, and audit/benchmark evidence stays tied to your own tasks.
10
+
11
+ ## Rebrand note
12
+
13
+ Claude Code does not alias the old `/claude-token-optimizer:*` plugin slash-command namespace. Use `/context-guard:*` after installing this plugin.
14
+
15
+ Legacy local CLI wrappers (`claude-token-*`, `claude-read-symbol`, `claude-trim-output`, and `claude-sanitize-output`) remain in `bin/` so existing automation can migrate gradually.
16
+
17
+ ## Skills
18
+
19
+ After installation, use these skills inside Claude Code:
20
+
21
+ ```text
22
+ /context-guard:setup
23
+ /context-guard:optimize
24
+ /context-guard:audit
25
+ ```
26
+
27
+ | Skill | Purpose |
28
+ | --- | --- |
29
+ | `/context-guard:setup` | First-time project setup wizard. |
30
+ | `/context-guard:optimize` | Inspect and tune context guardrails. |
31
+ | `/context-guard:audit` | Audit local Claude transcript token/cost hotspots. |
32
+
33
+ ## Helper commands and PATH
34
+
35
+ The canonical command is `context-guard`; backwards-compatible helper commands keep the `context-guard-*` prefix. Claude Code plugin skills can call the packaged helpers, but your normal shell may not automatically add the plugin `bin/` directory to `PATH`.
36
+
37
+ For Codex or other terminal-first agents, install the npm package or run it one-off with npx. Installation is passive and does not write configuration.
38
+
39
+ ```bash
40
+ npm install -g @ictechgy/context-guard
41
+ context-guard setup --agent codex --scope project --with-init --with-skill --plan
42
+ npx @ictechgy/context-guard --version
43
+ ```
44
+
45
+ From this repository root, run helpers by path:
46
+
47
+ ```bash
48
+ ./plugins/context-guard/bin/context-guard-setup --plan
49
+ ./plugins/context-guard/bin/context-guard-diet scan . --json
50
+ ```
51
+
52
+ For local development, add the plugin bin directory to your current shell:
53
+
54
+ ```bash
55
+ export PATH="$PWD/plugins/context-guard/bin:$PATH"
56
+ context-guard-setup --plan
57
+ ```
58
+
59
+ Common helpers:
60
+
61
+ ```bash
62
+ context-guard-audit ~/.claude/projects --top 20 --recommend
63
+ context-guard-setup
64
+ context-guard-diet scan . --json
65
+ context-guard-artifact store --command "long-command" --json < large.log
66
+ context-guard-artifact get <artifact_id> --lines 1:80
67
+ context-guard-compress --json < large-output.txt
68
+ context-guard-trim-output --max-lines 120 -- npm test
69
+ context-guard-read-symbol path/to/file.py TargetSymbol
70
+ context-guard-sanitize-output -- rg -n "TOKEN|SECRET" .
71
+ context-guard-sanitize-output -- git diff
72
+ context-guard-pack build --root . --source 'path=README.md,priority=100,lines=1:80' --budget-bytes 12000 --json
73
+ context-guard-pack slice --root . --path README.md --lines 1:40 --json
74
+ context-guard-tool-prune select --catalog tools.json --query "review failing tests" --top 5 --budget-bytes 12000 --json
75
+ context-guard-tool-prune get <receipt_id> --tool read_file --json
76
+ context-guard-statusline
77
+ context-guard-statusline-merged
78
+ ```
79
+
80
+ ## What the helpers do
81
+
82
+ - **Setup wizard** merges `.claude/settings.json` instead of replacing it, then prints a read-only `context-guard-diet scan` summary. Use `--no-diet-scan` when automation needs setup output without the post-apply scan.
83
+ - **Context hygiene scanner** checks missing `permissions.deny` guardrails, Bash trim hook/statusline setup, broad read allows, high default model/effort, many MCP servers, large or secret-like agent rule files, and advisory context-exclusion recommendations for bulky/sensitive local paths. Its `--top` cap applies to both context-like files and context-exclusion recommendations.
84
+ - **Large-read guard and symbol reader** guide the agent from search to symbol slices to small line ranges before attempting a whole-file read. Supported source slices include Python, JavaScript/TypeScript, Go, and Rust.
85
+ - **Artifact store** saves large sanitized command output under `.context-guard/artifacts` by default and returns compact receipts or exact requested slices. JSON receipts include line-numbered top errors, duplicate-line groups, and sanitized bounded suggested queries. In suggested `--lines START:END` queries, `--max-lines` is only the returned-line cap for that selected range, not a wider selector. `get` and `list` can also read legacy `.claude-token-optimizer/artifacts` receipts.
86
+ - **Budgeted context packer** assembles prioritized local file evidence into a rendered byte-budgeted Markdown pack with included/partial/omitted source metadata, bounded `.context-guard/packs` receipts, exact sanitized `slice` commands when safe, and `retrieval_omitted_reason` when a path/root should not be echoed. Token counts are estimated `chars_div_4` proxies, not measured provider-token savings.
87
+ - **Tool/MCP schema pruner** ranks local tool catalogs into bounded top-k advisory reports while preserving full sanitized schema fallback through compact receipts and payload integrity checks.
88
+ - **Conservative compressor** classifies sanitized stdin as JSON, diff, log, search output, code, or prose and shrinks it with observed byte evidence plus estimated token proxies.
89
+ - **Output trimmer** preserves the wrapped command exit code, trims long logs, and can emit `--digest markdown` or `--digest json` summaries with runner failure facts, sanitized failure signatures, duplicate-line groups, and suggested next queries.
90
+ - **Sanitizer** redacts common credential patterns, private key blocks, auth headers, credential URLs, and sensitive-looking paths from search, diff, and log output.
91
+ - **Statusline** displays compact model/context/cost signals and, when transcript data is available, cache-read and cache-reuse signals.
92
+ - **Transcript audit** aggregates usage/cost/cache buckets, flags likely token hotspots, and exposes `cache_friendliness` prompt-layout findings from bounded redacted segment hashes without printing raw prompt text.
93
+ - **Repeated-failure nudge** warns after repeated Bash failures so the agent switches strategy instead of retrying the same context-heavy path.
94
+ - **Benchmark helper** records matched baseline/variant runs with real token and cost fields, separate byte-reduction proxy evidence, diagnostic `wall_time_seconds`, `provider_cached_tokens`, and provider-cache availability telemetry.
95
+
96
+ ## Brief mode (advisory)
97
+
98
+ Brief mode ships agent-neutral, advisory rule snippets that ask a coding agent to cut filler while preserving evidence: file paths, commands, command output and errors, code blocks, verification status, changed files, known gaps, and caveats. It is best-effort guidance, not enforcement, and does **not** guarantee any token or cost savings.
99
+
100
+ Three deterministic levels — `lite`, `standard`, `ultra` — live under [`brief/`](brief/). Each is a single marker-delimited block you install into an agent's rule/instruction file (such as `AGENTS.md`, `CLAUDE.md`, a Cursor rules file, or Copilot instructions) and remove by deleting the block. See [`brief/README.md`](brief/README.md).
101
+
102
+ ## Conservative claims
103
+
104
+ These helpers reduce common sources of context bloat, but they do not guarantee a fixed percentage savings. Use `context-guard-bench --ledger-jsonl ... --report-json ...` when you need measured before/after evidence for your own tasks; token-savings claims require `primary_tokens_measured` on both matched sides, and wall-time/provider-cache fields are diagnostic telemetry, not standalone savings proof. Audit `cache_friendliness` findings are heuristic layout signals, not billing authority. Benchmark CSV schemas are strict, so start a new CSV or migrate the header after helper upgrades.
105
+
106
+ ContextGuard also does not send work to external AI providers to save model tokens. All helper commands run locally.
107
+
108
+ Future learned, multimodal, and self-hosted optimization ideas are tracked only in [`../../research/experimental-token-reduction-radar.md`](../../research/experimental-token-reduction-radar.md). That radar is not a shipped runtime feature and does not claim hosted API savings without provider-measured matched-task evidence.
109
+
110
+ Cross-agent rule snippets are advisory: the target agent may ignore them, so measure actual before/after behavior when you need a savings claim.
111
+
112
+ ## Local test before publishing
113
+
114
+ From the marketplace repository root:
115
+
116
+ ```bash
117
+ claude --plugin-dir ./plugins/context-guard
118
+ ```
119
+
120
+ Then run inside Claude Code:
121
+
122
+ ```text
123
+ /context-guard:setup
124
+ ```
125
+
126
+ Marketplace installation test:
127
+
128
+ ```text
129
+ /plugin marketplace add ./
130
+ /plugin install context-guard@context-guard
131
+ ```
132
+
133
+ ## License
134
+
135
+ Copyright 2026 jinhongan. Licensed under the Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-read-symbol`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-read-symbol" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-sanitize-output`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-sanitize-output" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-artifact`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-artifact" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-audit`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-audit" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-bench`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-bench" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-diet`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-diet" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-failed-nudge`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-failed-nudge" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-guard-read`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-guard-read" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-rewrite-bash`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-rewrite-bash" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-setup`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-setup" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-statusline`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-statusline" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-statusline-merged`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-statusline-merged" "$@"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env sh
2
+ # Legacy ContextGuard compatibility wrapper. Prefer `context-guard-trim-output`.
3
+ set -eu
4
+ script_dir=$(dirname "$0")
5
+ script_dir=$(cd "$script_dir" && pwd)
6
+ exec "$script_dir/context-guard-trim-output" "$@"
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """Canonical ContextGuard command dispatcher.
3
+
4
+ The npm/Homebrew-friendly ``context-guard`` command is intentionally passive:
5
+ installation only exposes commands on PATH. Any project or user configuration is
6
+ performed later through explicit subcommands such as ``context-guard setup``.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import subprocess
14
+ import sys
15
+ from typing import NoReturn
16
+
17
+ COMMAND_NAME = "context-guard"
18
+ PACKAGE_NAME = "@ictechgy/context-guard"
19
+
20
+ HELPER_SUBCOMMANDS: dict[str, tuple[str, ...]] = {
21
+ "setup": ("context-guard-setup",),
22
+ "audit": ("context-guard-audit",),
23
+ "diet": ("context-guard-diet",),
24
+ "scan": ("context-guard-diet", "scan"),
25
+ "trim-output": ("context-guard-trim-output",),
26
+ "trim": ("context-guard-trim-output",),
27
+ "sanitize-output": ("context-guard-sanitize-output",),
28
+ "sanitize": ("context-guard-sanitize-output",),
29
+ "artifact": ("context-guard-artifact",),
30
+ "pack": ("context-guard-pack",),
31
+ "tool-prune": ("context-guard-tool-prune",),
32
+ "compress": ("context-guard-compress",),
33
+ "bench": ("context-guard-bench",),
34
+ "read-symbol": ("context-guard-read-symbol",),
35
+ "rewrite-bash": ("context-guard-rewrite-bash",),
36
+ "guard-read": ("context-guard-guard-read",),
37
+ "failed-nudge": ("context-guard-failed-nudge",),
38
+ "statusline": ("context-guard-statusline",),
39
+ "statusline-merged": ("context-guard-statusline-merged",),
40
+ }
41
+
42
+
43
+ def _script_dir() -> Path:
44
+ return Path(__file__).resolve().parent
45
+
46
+
47
+ def _candidate_roots() -> list[Path]:
48
+ script_dir = _script_dir()
49
+ roots = [script_dir.parent, script_dir.parent.parent, Path.cwd()]
50
+ # When run from context-guard-kit in a checkout, the repo root is one level up.
51
+ if script_dir.name == "context-guard-kit":
52
+ roots.insert(0, script_dir.parent)
53
+ return list(dict.fromkeys(roots))
54
+
55
+
56
+ def _load_json(path: Path) -> dict[str, object] | None:
57
+ try:
58
+ data = json.loads(path.read_text(encoding="utf-8"))
59
+ except (OSError, json.JSONDecodeError):
60
+ return None
61
+ return data if isinstance(data, dict) else None
62
+
63
+
64
+ def project_version() -> str:
65
+ candidates: list[Path] = []
66
+ for root in _candidate_roots():
67
+ candidates.extend(
68
+ [
69
+ root / ".claude-plugin" / "plugin.json",
70
+ root / "plugins" / "context-guard" / ".claude-plugin" / "plugin.json",
71
+ root / "package.json",
72
+ ]
73
+ )
74
+ for candidate in candidates:
75
+ data = _load_json(candidate)
76
+ version = data.get("version") if data else None
77
+ if isinstance(version, str) and version.strip():
78
+ return version.strip()
79
+ return "0.0.0+unknown"
80
+
81
+
82
+ def print_help() -> None:
83
+ version = project_version()
84
+ commands = "\n".join(f" {name}" for name in sorted(HELPER_SUBCOMMANDS))
85
+ sys.stdout.write(
86
+ f"ContextGuard {version}\n"
87
+ f"\n"
88
+ f"Usage:\n"
89
+ f" {COMMAND_NAME} --version\n"
90
+ f" {COMMAND_NAME} <subcommand> [args...]\n"
91
+ f"\n"
92
+ f"Install examples:\n"
93
+ f" npm install -g {PACKAGE_NAME}\n"
94
+ f" npx {PACKAGE_NAME} setup --agent codex --scope project --plan\n"
95
+ f"\n"
96
+ f"Common subcommands:\n"
97
+ f"{commands}\n"
98
+ f"\n"
99
+ f"Run '{COMMAND_NAME} <subcommand> --help' for helper-specific options.\n"
100
+ f"Installing ContextGuard never writes configuration; use 'setup' explicitly.\n"
101
+ )
102
+
103
+
104
+ def helper_path(name: str) -> Path | None:
105
+ script_dir = _script_dir()
106
+ candidates = [
107
+ script_dir / name,
108
+ script_dir.parent / "plugins" / "context-guard" / "bin" / name,
109
+ script_dir.parent.parent / "plugins" / "context-guard" / "bin" / name,
110
+ ]
111
+ for candidate in candidates:
112
+ if candidate.is_file() and os.access(candidate, os.X_OK):
113
+ return candidate
114
+ return None
115
+
116
+
117
+ def fail(message: str, code: int = 2) -> NoReturn:
118
+ sys.stderr.write(f"{COMMAND_NAME}: {message}\n")
119
+ raise SystemExit(code)
120
+
121
+
122
+ def run_helper(command: str, argv: list[str]) -> int:
123
+ mapping = HELPER_SUBCOMMANDS[command]
124
+ helper = helper_path(mapping[0])
125
+ if helper is None:
126
+ fail(
127
+ f"could not find helper {mapping[0]!r}; reinstall {PACKAGE_NAME} "
128
+ "or run from a complete ContextGuard checkout."
129
+ )
130
+ proc = subprocess.run([str(helper), *mapping[1:], *argv])
131
+ return int(proc.returncode)
132
+
133
+
134
+ def main(argv: list[str] | None = None) -> int:
135
+ args = list(sys.argv[1:] if argv is None else argv)
136
+ if not args or args[0] in {"-h", "--help", "help"}:
137
+ print_help()
138
+ return 0
139
+ if args[0] in {"-V", "--version", "version"}:
140
+ print(project_version())
141
+ return 0
142
+ command = args.pop(0).strip().lower()
143
+ if command not in HELPER_SUBCOMMANDS:
144
+ fail(f"unknown subcommand {command!r}. Run '{COMMAND_NAME} --help'.")
145
+ return run_helper(command, args)
146
+
147
+
148
+ if __name__ == "__main__":
149
+ raise SystemExit(main())