boost-skill-cli 1.0.32__tar.gz → 1.0.33__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 (107) hide show
  1. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/_version.py +2 -2
  3. boost_skill_cli-1.0.33/boost_cli/cliparse.py +31 -0
  4. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/pkg.py +13 -13
  5. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  6. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/SOURCES.txt +2 -0
  7. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/design-roadmap.html +2 -2
  8. boost_skill_cli-1.0.33/tests/unit/test_cliparse.py +43 -0
  9. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/.gitignore +0 -0
  10. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/CLAUDE.md +0 -0
  11. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/CONTRIBUTING.md +0 -0
  12. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/LICENSE +0 -0
  13. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/MANIFEST.in +0 -0
  14. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/Makefile +0 -0
  15. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/README.md +0 -0
  16. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/SECURITY.md +0 -0
  17. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost +0 -0
  18. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/__init__.py +0 -0
  19. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/__main__.py +0 -0
  20. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/cli.py +0 -0
  21. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/__init__.py +0 -0
  22. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/bmad.py +0 -0
  23. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/configuration.py +0 -0
  24. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/discovery.py +0 -0
  25. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/hooks.py +0 -0
  26. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/info.py +0 -0
  27. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/intelligence.py +0 -0
  28. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/quality.py +0 -0
  29. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/taps.py +0 -0
  30. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/commands/team.py +0 -0
  31. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/__init__.py +0 -0
  32. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/agents.py +0 -0
  33. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/ai.py +0 -0
  34. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/catalog.py +0 -0
  35. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/claude_settings.py +0 -0
  36. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/config.py +0 -0
  37. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/dense.py +0 -0
  38. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/embed.py +0 -0
  39. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/frontmatter.py +0 -0
  40. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/gitutil.py +0 -0
  41. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/journal.py +0 -0
  42. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/lockfile.py +0 -0
  43. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/logs.py +0 -0
  44. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/output.py +0 -0
  45. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/paths.py +0 -0
  46. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/policy.py +0 -0
  47. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/rag.py +0 -0
  48. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/registry.py +0 -0
  49. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/store.py +0 -0
  50. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/core/util.py +0 -0
  51. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/data/registries.json +0 -0
  52. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_cli/errors.py +0 -0
  53. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  54. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  55. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/requires.txt +0 -0
  56. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/boost_skill_cli.egg-info/top_level.txt +0 -0
  57. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/.claude/settings.local.json +0 -0
  58. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/DEBUGGING.md +0 -0
  59. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/demo.gif +0 -0
  60. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/demo.tape +0 -0
  61. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/index.html +0 -0
  62. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/rag-architecture.md +0 -0
  63. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/docs/roadmap.html +0 -0
  64. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/pyproject.toml +0 -0
  65. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/scripts/build_registries.py +0 -0
  66. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/scripts/mutation_gate.py +0 -0
  67. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/setup.cfg +0 -0
  68. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/style/README.md +0 -0
  69. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/style/boost.css +0 -0
  70. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/style/boost.js +0 -0
  71. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/style/demo.html +0 -0
  72. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/conftest.py +0 -0
  73. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_bmad.py +0 -0
  74. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_configuration.py +0 -0
  75. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_discovery.py +0 -0
  76. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_hooks.py +0 -0
  77. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_info.py +0 -0
  78. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_intelligence.py +0 -0
  79. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_pkg.py +0 -0
  80. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_quality.py +0 -0
  81. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_taps.py +0 -0
  82. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_cli_team.py +0 -0
  83. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/functional/test_everyday_loop.py +0 -0
  84. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/make_fixture.py +0 -0
  85. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/smoke.sh +0 -0
  86. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_agents.py +0 -0
  87. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_ai.py +0 -0
  88. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_catalog.py +0 -0
  89. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_claude_settings.py +0 -0
  90. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_config.py +0 -0
  91. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_dense.py +0 -0
  92. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_embed.py +0 -0
  93. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_errors_and_cli_table.py +0 -0
  94. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_frontmatter.py +0 -0
  95. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_gitutil.py +0 -0
  96. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_journal.py +0 -0
  97. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_lockfile.py +0 -0
  98. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_logs.py +0 -0
  99. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_mutation_hardening.py +0 -0
  100. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_output.py +0 -0
  101. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_paths.py +0 -0
  102. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_policy.py +0 -0
  103. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_rag.py +0 -0
  104. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_registry.py +0 -0
  105. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_store.py +0 -0
  106. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_util.py +0 -0
  107. {boost_skill_cli-1.0.32 → boost_skill_cli-1.0.33}/tests/unit/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.32
3
+ Version: 1.0.33
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.0.32'
22
- __version_tuple__ = version_tuple = (1, 0, 32)
21
+ __version__ = version = '1.0.33'
22
+ __version_tuple__ = version_tuple = (1, 0, 33)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,31 @@
1
+ """A branded ArgumentParser.
2
+
3
+ Argparse's default failure output is a raw, unstyled ``usage:`` block with a
4
+ bare ``prog: error: …`` line — nothing like boost's branded top-level dispatch
5
+ (which routes through :func:`output.err` and offers a "did you mean"). This
6
+ subclass routes a parser's own errors through the same Aurora output layer, so
7
+ a wrong invocation looks as designed as a right one.
8
+
9
+ Adopt it via :func:`parser` in place of ``argparse.ArgumentParser``. Sub-parsers
10
+ created with ``add_subparsers()`` inherit this class automatically, so a single
11
+ top-level swap brands a command's whole tree.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import sys
17
+
18
+ from .core import output as out
19
+
20
+
21
+ class BoostArgumentParser(argparse.ArgumentParser):
22
+ def error(self, message: str): # noqa: D401 - argparse hook
23
+ """Print a branded error + dimmed usage, then exit 2 (argparse's code)."""
24
+ out.err(message)
25
+ sys.stderr.write(out.c(self.format_usage(), out.DIM))
26
+ self.exit(2)
27
+
28
+
29
+ def parser(*args, **kwargs) -> BoostArgumentParser:
30
+ """Drop-in replacement for ``argparse.ArgumentParser(...)``."""
31
+ return BoostArgumentParser(*args, **kwargs)
@@ -2,7 +2,6 @@
2
2
  reinstall, bundle, import, migrate, pin, unpin, snapshot, export."""
3
3
  from __future__ import annotations
4
4
 
5
- import argparse
6
5
  import io
7
6
  import json
8
7
  import os
@@ -15,6 +14,7 @@ from datetime import datetime, timezone
15
14
  from pathlib import Path
16
15
  from typing import Dict, List, Optional
17
16
 
17
+ from .. import cliparse
18
18
  from ..core import (agents, catalog, gitutil, journal, lockfile, paths,
19
19
  registry, store, util)
20
20
  from ..core import output as out
@@ -83,7 +83,7 @@ def _boostfile_text(skills: Dict[str, dict], via: str = "boost bundle dump") ->
83
83
  # ── install ──────────────────────────────────────────────────────────────
84
84
 
85
85
  def cmd_install(argv: List[str]) -> int:
86
- ap = argparse.ArgumentParser(prog="boost install",
86
+ ap = cliparse.parser(prog="boost install",
87
87
  description="Install a skill from a tap registry")
88
88
  ap.add_argument("names", nargs="+", metavar="NAME",
89
89
  help="skill name, optionally qualified as tap:skill")
@@ -155,7 +155,7 @@ def cmd_install(argv: List[str]) -> int:
155
155
  # ── uninstall ────────────────────────────────────────────────────────────
156
156
 
157
157
  def cmd_uninstall(argv: List[str]) -> int:
158
- ap = argparse.ArgumentParser(
158
+ ap = cliparse.parser(
159
159
  prog="boost uninstall",
160
160
  description="Remove an installed skill, rule, workflow, or config")
161
161
  ap.add_argument("names", nargs="+", metavar="NAME")
@@ -194,7 +194,7 @@ _PLAN_LABELS = [
194
194
 
195
195
 
196
196
  def cmd_sync(argv: List[str]) -> int:
197
- ap = argparse.ArgumentParser(
197
+ ap = cliparse.parser(
198
198
  prog="boost sync",
199
199
  description="Reconcile installed skills & symlinks against the lock file")
200
200
  ap.add_argument("--diff", action="store_true",
@@ -257,7 +257,7 @@ def cmd_sync(argv: List[str]) -> int:
257
257
  # ── update ───────────────────────────────────────────────────────────────
258
258
 
259
259
  def cmd_update(argv: List[str]) -> int:
260
- ap = argparse.ArgumentParser(prog="boost update",
260
+ ap = cliparse.parser(prog="boost update",
261
261
  description="Sync taps or update installed skills")
262
262
  ap.add_argument("tap", nargs="?", metavar="TAP", help="refresh only this tap")
263
263
  ap.add_argument("--taps-only", action="store_true",
@@ -317,7 +317,7 @@ def cmd_update(argv: List[str]) -> int:
317
317
  # ── reinstall ────────────────────────────────────────────────────────────
318
318
 
319
319
  def cmd_reinstall(argv: List[str]) -> int:
320
- ap = argparse.ArgumentParser(prog="boost reinstall",
320
+ ap = cliparse.parser(prog="boost reinstall",
321
321
  description="Reinstall a skill or all skills (force)")
322
322
  ap.add_argument("names", nargs="*", metavar="NAME")
323
323
  ap.add_argument("--all", action="store_true",
@@ -369,7 +369,7 @@ def cmd_reinstall(argv: List[str]) -> int:
369
369
  # ── bundle ───────────────────────────────────────────────────────────────
370
370
 
371
371
  def cmd_bundle(argv: List[str]) -> int:
372
- ap = argparse.ArgumentParser(prog="boost bundle",
372
+ ap = cliparse.parser(prog="boost bundle",
373
373
  description="Export/install skill sets via a Boostfile")
374
374
  ap.add_argument("action", choices=("dump", "install"))
375
375
  ap.add_argument("file", nargs="?", metavar="FILE",
@@ -481,7 +481,7 @@ def _bundle_install(file: Optional[str]) -> int:
481
481
  # ── import ───────────────────────────────────────────────────────────────
482
482
 
483
483
  def cmd_import(argv: List[str]) -> int:
484
- ap = argparse.ArgumentParser(
484
+ ap = cliparse.parser(
485
485
  prog="boost import",
486
486
  description="Import skills from a GitHub URL or local path")
487
487
  ap.add_argument("source", metavar="URL_OR_PATH")
@@ -552,7 +552,7 @@ def _import_root(root: Path, name: Optional[str], do_all: bool,
552
552
  # ── migrate ──────────────────────────────────────────────────────────────
553
553
 
554
554
  def cmd_migrate(argv: List[str]) -> int:
555
- ap = argparse.ArgumentParser(
555
+ ap = cliparse.parser(
556
556
  prog="boost migrate",
557
557
  description="Migrate skills between agents or from Skills CLI")
558
558
  ap.add_argument("--from", dest="src", metavar="AGENT",
@@ -625,7 +625,7 @@ def cmd_migrate(argv: List[str]) -> int:
625
625
  # ── pin / unpin ──────────────────────────────────────────────────────────
626
626
 
627
627
  def cmd_pin(argv: List[str]) -> int:
628
- ap = argparse.ArgumentParser(prog="boost pin",
628
+ ap = cliparse.parser(prog="boost pin",
629
629
  description="Pin a skill to its current version")
630
630
  ap.add_argument("name", metavar="NAME")
631
631
  args = ap.parse_args(argv)
@@ -633,7 +633,7 @@ def cmd_pin(argv: List[str]) -> int:
633
633
 
634
634
 
635
635
  def cmd_unpin(argv: List[str]) -> int:
636
- ap = argparse.ArgumentParser(prog="boost unpin",
636
+ ap = cliparse.parser(prog="boost unpin",
637
637
  description="Allow a pinned skill to update again")
638
638
  ap.add_argument("name", metavar="NAME")
639
639
  args = ap.parse_args(argv)
@@ -663,7 +663,7 @@ def _set_pin(name: str, pinned: bool) -> int:
663
663
  # ── snapshot ─────────────────────────────────────────────────────────────
664
664
 
665
665
  def cmd_snapshot(argv: List[str]) -> int:
666
- ap = argparse.ArgumentParser(prog="boost snapshot",
666
+ ap = cliparse.parser(prog="boost snapshot",
667
667
  description="Save & restore whole skill environments")
668
668
  ap.add_argument("action", choices=("save", "list", "restore"))
669
669
  ap.add_argument("arg", nargs="?", metavar="LABEL|ID",
@@ -784,7 +784,7 @@ def _snapshot_restore(snap_id: str) -> int:
784
784
  # ── export ───────────────────────────────────────────────────────────────
785
785
 
786
786
  def cmd_export(argv: List[str]) -> int:
787
- ap = argparse.ArgumentParser(
787
+ ap = cliparse.parser(
788
788
  prog="boost export",
789
789
  description="Package skills as shareable zip/tar archives")
790
790
  ap.add_argument("names", nargs="*", metavar="NAME",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.32
3
+ Version: 1.0.33
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -13,6 +13,7 @@ boost_cli/__init__.py
13
13
  boost_cli/__main__.py
14
14
  boost_cli/_version.py
15
15
  boost_cli/cli.py
16
+ boost_cli/cliparse.py
16
17
  boost_cli/errors.py
17
18
  boost_cli/commands/__init__.py
18
19
  boost_cli/commands/bmad.py
@@ -84,6 +85,7 @@ tests/unit/test_agents.py
84
85
  tests/unit/test_ai.py
85
86
  tests/unit/test_catalog.py
86
87
  tests/unit/test_claude_settings.py
88
+ tests/unit/test_cliparse.py
87
89
  tests/unit/test_config.py
88
90
  tests/unit/test_dense.py
89
91
  tests/unit/test_embed.py
@@ -436,11 +436,11 @@
436
436
  </article>
437
437
 
438
438
  <article class="ritem" data-track="commands" data-impact="med">
439
- <div class="ritem-top"><span class="rid">BOOST-D25</span><span class="rstatus proposed">Proposed</span></div>
439
+ <div class="ritem-top"><span class="rid">BOOST-D25</span><span class="rstatus progress">In progress</span></div>
440
440
  <h4>Branded usage &amp; error output</h4>
441
441
  <p>Grading confirmed this hits <b>every command with a required argument</b> — <code>boost uninstall</code>, <code>boost snapshot</code> and more all dump raw, unstyled <code>argparse</code>: a multi-line usage block with a bare <code>error:</code>, nothing like the branded top-level dispatch (which already routes through <code>Error:</code> + a difflib "did you mean"). The durable fix is a shared <code>BoostArgumentParser</code> subclass whose <code>error()</code>/usage route through the Aurora output layer, adopted across the ~50 per-command parsers — a broad, test-first sweep, so logged rather than rushed.</p>
442
442
  <div class="rmeta"><span class="m-cat">ux</span><span>Complexity L</span><span class="m-imp med">Impact Med</span><span class="m-wow">★★★☆☆</span></div>
443
- <div class="rref"><code>core · new BoostArgumentParser · commands/*.py</code></div>
443
+ <div class="rref"><code>cliparse.py (BoostArgumentParser) · pkg.py done · 9 files to go</code></div>
444
444
  </article>
445
445
 
446
446
  </div>
@@ -0,0 +1,43 @@
1
+ """Unit tests: boost_cli/cliparse.BoostArgumentParser — branded errors."""
2
+ from __future__ import annotations
3
+
4
+ import pytest
5
+
6
+ from boost_cli import cliparse
7
+
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def plain(monkeypatch):
11
+ monkeypatch.setenv("NO_COLOR", "1")
12
+
13
+
14
+ def test_parser_returns_boost_subclass():
15
+ p = cliparse.parser(prog="boost demo")
16
+ assert isinstance(p, cliparse.BoostArgumentParser)
17
+
18
+
19
+ def test_error_is_branded_and_exits_2(capsys):
20
+ p = cliparse.parser(prog="boost demo")
21
+ p.add_argument("name")
22
+ with pytest.raises(SystemExit) as exc:
23
+ p.parse_args([]) # missing required positional
24
+ assert exc.value.code == 2
25
+ err = capsys.readouterr().err
26
+ assert err.startswith("Error: ") # branded, not bare "usage:"
27
+ assert "the following arguments are required" in err
28
+ assert "usage: boost demo" in err # usage still shown, below
29
+
30
+
31
+ def test_valid_args_do_not_error(capsys):
32
+ p = cliparse.parser(prog="boost demo")
33
+ p.add_argument("--flag", action="store_true")
34
+ ns = p.parse_args(["--flag"])
35
+ assert ns.flag is True
36
+ assert capsys.readouterr().err == ""
37
+
38
+
39
+ def test_subparsers_inherit_branding():
40
+ p = cliparse.parser(prog="boost demo")
41
+ sub = p.add_subparsers()
42
+ child = sub.add_parser("go")
43
+ assert isinstance(child, cliparse.BoostArgumentParser)
File without changes