napkinstack 0.1.0__tar.gz → 0.2.0__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 (34) hide show
  1. napkinstack-0.2.0/PKG-INFO +142 -0
  2. napkinstack-0.2.0/README.md +127 -0
  3. {napkinstack-0.1.0 → napkinstack-0.2.0}/pyproject.toml +2 -2
  4. {napkinstack-0.1.0 → napkinstack-0.2.0}/pyproject.toml.orig +2 -2
  5. napkinstack-0.2.0/src/napkinstack/__init__.py +5 -0
  6. napkinstack-0.2.0/src/napkinstack/cli.py +104 -0
  7. napkinstack-0.2.0/src/napkinstack/doctor.py +280 -0
  8. {napkinstack-0.1.0 → napkinstack-0.2.0}/src/napkinstack/fitness/boundaries.py +46 -46
  9. napkinstack-0.2.0/src/napkinstack/fitness/manifests.py +198 -0
  10. napkinstack-0.2.0/src/napkinstack/fitness/pr_scope.sh +79 -0
  11. napkinstack-0.2.0/src/napkinstack/modules.py +133 -0
  12. napkinstack-0.2.0/src/napkinstack/project.py +170 -0
  13. napkinstack-0.2.0/src/napkinstack/skills.py +159 -0
  14. napkinstack-0.2.0/src/napkinstack/templates/module/AGENTS.md +36 -0
  15. napkinstack-0.2.0/src/napkinstack/templates/module/MANIFEST.yaml +77 -0
  16. napkinstack-0.2.0/src/napkinstack/templates/module/README.md +27 -0
  17. napkinstack-0.1.0/PKG-INFO +0 -139
  18. napkinstack-0.1.0/README.md +0 -124
  19. napkinstack-0.1.0/src/napkinstack/__init__.py +0 -5
  20. napkinstack-0.1.0/src/napkinstack/cli.py +0 -103
  21. napkinstack-0.1.0/src/napkinstack/doctor.py +0 -278
  22. napkinstack-0.1.0/src/napkinstack/fitness/manifests.py +0 -198
  23. napkinstack-0.1.0/src/napkinstack/fitness/pr_scope.sh +0 -79
  24. napkinstack-0.1.0/src/napkinstack/modules.py +0 -132
  25. napkinstack-0.1.0/src/napkinstack/project.py +0 -170
  26. napkinstack-0.1.0/src/napkinstack/skills.py +0 -159
  27. napkinstack-0.1.0/src/napkinstack/templates/module/AGENTS.md +0 -36
  28. napkinstack-0.1.0/src/napkinstack/templates/module/MANIFEST.yaml +0 -77
  29. napkinstack-0.1.0/src/napkinstack/templates/module/README.md +0 -27
  30. {napkinstack-0.1.0 → napkinstack-0.2.0}/LICENSE +0 -0
  31. {napkinstack-0.1.0 → napkinstack-0.2.0}/src/napkinstack/fitness/__init__.py +0 -0
  32. {napkinstack-0.1.0 → napkinstack-0.2.0}/src/napkinstack/templates/module/docs/adr/.gitkeep +0 -0
  33. {napkinstack-0.1.0 → napkinstack-0.2.0}/src/napkinstack/templates/module/src/.gitkeep +0 -0
  34. {napkinstack-0.1.0 → napkinstack-0.2.0}/src/napkinstack/templates/module/tests/.gitkeep +0 -0
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: napkinstack
3
+ Version: 0.2.0
4
+ Summary: Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Dist: pyyaml>=6.0
8
+ Requires-Dist: copier==9.18.2
9
+ Requires-Dist: pre-commit==4.6.2
10
+ Requires-Python: >=3.12
11
+ Project-URL: Homepage, https://github.com/NapkinStack/engineering-os
12
+ Project-URL: Source, https://github.com/NapkinStack/engineering-os
13
+ Project-URL: Issues, https://github.com/NapkinStack/engineering-os/issues
14
+ Description-Content-Type: text/markdown
15
+
16
+ # NapkinStack
17
+
18
+ An engineering framework for several teams and their agents working in one repository:
19
+ modules, contracts, guardrails in CI. On the Django or Rails model, one command creates
20
+ the project, which then receives new versions on demand; no application stack is imposed.
21
+ Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
22
+
23
+ > **Status: v0.1.0, the first published version** ([PyPI](https://pypi.org/project/napkinstack/)).
24
+ > A first pilot project, private, puts it to the test before the rest. Tracking:
25
+ > [roadmap](docs/governance/plans/2026-09-15-engine-v0.1.0.md),
26
+ > [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
27
+
28
+ ## A project's journey
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ I["Install<br/>uv tool install"]:::cmd --> N["nstack init"]:::cmd
33
+ N --> G["Publish on GitHub<br/>apply the checklist"]:::human
34
+ G --> D["nstack doctor<br/>read-only"]:::cmd
35
+ D --> M["nstack new-module"]:::cmd
36
+ M --> W["Work in pull requests<br/>the team and its agent"]:::human
37
+ W --> U["nstack update<br/>merged branch"]:::cmd
38
+ U --> P["PR reviewed<br/>validated by CI"]:::human
39
+ P -->|"next version"| U
40
+
41
+ classDef cmd fill:#1f2937,color:#fff
42
+ classDef human fill:#065f46,color:#fff
43
+ ```
44
+
45
+ **Legend** — grey: a NapkinStack command · green: the team's action. Decision:
46
+ [PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md).
47
+
48
+ The project owns its skeleton and adapts it freely. Every new version reaches it on
49
+ demand, merged with its adaptations; the conflicts are left to the team.
50
+
51
+ ```mermaid
52
+ flowchart LR
53
+ V1["Skeleton v0.1<br/>common base"]:::ref --> F{"Three-way<br/>merge"}
54
+ V2["Skeleton v0.2<br/>NapkinStack fixes"]:::ns --> F
55
+ PR["Project<br/>the team's adaptations"]:::team --> F
56
+ F -->|"different lines"| B["Update branch<br/>fixes + adaptations"]:::ok
57
+ F -->|"same line changed"| X["Conflict marked<br/>commit refused"]:::ko
58
+
59
+ classDef ref fill:#374151,color:#fff
60
+ classDef ns fill:#1e3a8a,color:#fff
61
+ classDef team fill:#065f46,color:#fff
62
+ classDef ok fill:#065f46,color:#fff
63
+ classDef ko fill:#7c2d12,color:#fff
64
+ ```
65
+
66
+ **Legend** — grey: the version the project came from · blue: the new version · green: the
67
+ team's work and the accepted result · red: a conflict left to the team.
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ uv tool install napkinstack --with-executables-from pre-commit # prerequisites: uv and git
73
+ nstack init my-project
74
+ ```
75
+
76
+ Each project then pins its version and changes it through `nstack update`. Every published
77
+ version carries a provenance attestation, visible on PyPI, tying it to the workflow and
78
+ the commit of this repository
79
+ ([ADR-0002](docs/adr/0002-distribute-napkinstack-on-pypi.md)).
80
+
81
+ ## The commands
82
+
83
+ | Command | Role |
84
+ |---|---|
85
+ | `nstack init <folder>` | Creates the project: skeleton, git repository, initial commit, GitHub checklist |
86
+ | `nstack doctor` | Checks the workstation and the GitHub settings, read-only |
87
+ | `nstack new-module <name> <organisation>/<team> <criticality>` | Creates a module, with no imposed stack |
88
+ | `nstack check`, `test`, `bootstrap` `[module]`; `nstack run <module>` | Run the commands declared in the module's manifest |
89
+ | `nstack fitness` | Manifests, boundaries between modules, skills |
90
+ | `nstack pr-scope` | One PR = one module, review budget |
91
+ | `nstack skills` | Exposes the playbooks as skills for the agent |
92
+ | `nstack update` | Lays the new version on a branch to review |
93
+
94
+ **Prerequisites**: uv and git. The guardrails really block on a public GitHub repository,
95
+ or on a private one under the Team or Pro plan; on a private repository on the Free plan
96
+ CI informs without blocking
97
+ ([a clarification of PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md)).
98
+
99
+ **AI**: NapkinStack embeds none. The team's agent (Claude Code, Codex, Copilot…) reads the
100
+ kernel and the playbooks, runs the commands, and CI accepts or refuses its proposals
101
+ exactly as it would any other contributor's.
102
+
103
+ ## This repository
104
+
105
+ ```mermaid
106
+ flowchart LR
107
+ S["skeleton/<br/>the project skeleton"]:::shipped -->|"copier.yml"| P["A team's project"]:::project
108
+ E["src/napkinstack/<br/>the nstack engine"]:::shipped -.->|"pinned version"| P
109
+ A["PRODUCT.md · docs/governance/<br/>platform/ · this repository's CI"]:::internal
110
+
111
+ classDef shipped fill:#1e3a8a,color:#fff
112
+ classDef project fill:#065f46,color:#fff
113
+ classDef internal fill:#374151,color:#fff
114
+ ```
115
+
116
+ **Legend** — blue: shipped to projects · green: a generated project, which owns its
117
+ skeleton · grey: developing NapkinStack itself, never copied (PDR-0001 R6). Solid line:
118
+ generation; dotted: a versioned dependency.
119
+
120
+ | Path | Role |
121
+ |---|---|
122
+ | `skeleton/` | What every project receives: kernel, playbooks, handbook, CI, hooks |
123
+ | `copier.yml` | The questions asked at creation (a Copier template, ADR-0001) |
124
+ | `src/napkinstack/` | The engine, the `nstack` command |
125
+ | `platform/` | The engine module's envelope: manifest, runbook, tests |
126
+ | `PRODUCT.md`, `docs/governance/` | The working context for NapkinStack itself |
127
+ | `docs/adr/`, `docs/pdr/` | NapkinStack's decisions |
128
+
129
+ ## Developing NapkinStack
130
+
131
+ ```bash
132
+ uv sync # prerequisite: uv
133
+ uv run pre-commit install
134
+ uv run nstack fitness # this repository's guardrails
135
+ uv run bash platform/tests/run.sh # the oracle: every guardrail proves it can fail
136
+ uv run nstack init /tmp/trial --source . --ref HEAD # a trial project from the working tree
137
+ uv run nstack doctor --root /tmp/trial # workstation and GitHub settings, read-only
138
+ ```
139
+
140
+ Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md), after [`PRODUCT.md`](PRODUCT.md).
141
+
142
+ Licence: [MIT](LICENSE).
@@ -0,0 +1,127 @@
1
+ # NapkinStack
2
+
3
+ An engineering framework for several teams and their agents working in one repository:
4
+ modules, contracts, guardrails in CI. On the Django or Rails model, one command creates
5
+ the project, which then receives new versions on demand; no application stack is imposed.
6
+ Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
7
+
8
+ > **Status: v0.1.0, the first published version** ([PyPI](https://pypi.org/project/napkinstack/)).
9
+ > A first pilot project, private, puts it to the test before the rest. Tracking:
10
+ > [roadmap](docs/governance/plans/2026-09-15-engine-v0.1.0.md),
11
+ > [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
12
+
13
+ ## A project's journey
14
+
15
+ ```mermaid
16
+ flowchart LR
17
+ I["Install<br/>uv tool install"]:::cmd --> N["nstack init"]:::cmd
18
+ N --> G["Publish on GitHub<br/>apply the checklist"]:::human
19
+ G --> D["nstack doctor<br/>read-only"]:::cmd
20
+ D --> M["nstack new-module"]:::cmd
21
+ M --> W["Work in pull requests<br/>the team and its agent"]:::human
22
+ W --> U["nstack update<br/>merged branch"]:::cmd
23
+ U --> P["PR reviewed<br/>validated by CI"]:::human
24
+ P -->|"next version"| U
25
+
26
+ classDef cmd fill:#1f2937,color:#fff
27
+ classDef human fill:#065f46,color:#fff
28
+ ```
29
+
30
+ **Legend** — grey: a NapkinStack command · green: the team's action. Decision:
31
+ [PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md).
32
+
33
+ The project owns its skeleton and adapts it freely. Every new version reaches it on
34
+ demand, merged with its adaptations; the conflicts are left to the team.
35
+
36
+ ```mermaid
37
+ flowchart LR
38
+ V1["Skeleton v0.1<br/>common base"]:::ref --> F{"Three-way<br/>merge"}
39
+ V2["Skeleton v0.2<br/>NapkinStack fixes"]:::ns --> F
40
+ PR["Project<br/>the team's adaptations"]:::team --> F
41
+ F -->|"different lines"| B["Update branch<br/>fixes + adaptations"]:::ok
42
+ F -->|"same line changed"| X["Conflict marked<br/>commit refused"]:::ko
43
+
44
+ classDef ref fill:#374151,color:#fff
45
+ classDef ns fill:#1e3a8a,color:#fff
46
+ classDef team fill:#065f46,color:#fff
47
+ classDef ok fill:#065f46,color:#fff
48
+ classDef ko fill:#7c2d12,color:#fff
49
+ ```
50
+
51
+ **Legend** — grey: the version the project came from · blue: the new version · green: the
52
+ team's work and the accepted result · red: a conflict left to the team.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ uv tool install napkinstack --with-executables-from pre-commit # prerequisites: uv and git
58
+ nstack init my-project
59
+ ```
60
+
61
+ Each project then pins its version and changes it through `nstack update`. Every published
62
+ version carries a provenance attestation, visible on PyPI, tying it to the workflow and
63
+ the commit of this repository
64
+ ([ADR-0002](docs/adr/0002-distribute-napkinstack-on-pypi.md)).
65
+
66
+ ## The commands
67
+
68
+ | Command | Role |
69
+ |---|---|
70
+ | `nstack init <folder>` | Creates the project: skeleton, git repository, initial commit, GitHub checklist |
71
+ | `nstack doctor` | Checks the workstation and the GitHub settings, read-only |
72
+ | `nstack new-module <name> <organisation>/<team> <criticality>` | Creates a module, with no imposed stack |
73
+ | `nstack check`, `test`, `bootstrap` `[module]`; `nstack run <module>` | Run the commands declared in the module's manifest |
74
+ | `nstack fitness` | Manifests, boundaries between modules, skills |
75
+ | `nstack pr-scope` | One PR = one module, review budget |
76
+ | `nstack skills` | Exposes the playbooks as skills for the agent |
77
+ | `nstack update` | Lays the new version on a branch to review |
78
+
79
+ **Prerequisites**: uv and git. The guardrails really block on a public GitHub repository,
80
+ or on a private one under the Team or Pro plan; on a private repository on the Free plan
81
+ CI informs without blocking
82
+ ([a clarification of PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md)).
83
+
84
+ **AI**: NapkinStack embeds none. The team's agent (Claude Code, Codex, Copilot…) reads the
85
+ kernel and the playbooks, runs the commands, and CI accepts or refuses its proposals
86
+ exactly as it would any other contributor's.
87
+
88
+ ## This repository
89
+
90
+ ```mermaid
91
+ flowchart LR
92
+ S["skeleton/<br/>the project skeleton"]:::shipped -->|"copier.yml"| P["A team's project"]:::project
93
+ E["src/napkinstack/<br/>the nstack engine"]:::shipped -.->|"pinned version"| P
94
+ A["PRODUCT.md · docs/governance/<br/>platform/ · this repository's CI"]:::internal
95
+
96
+ classDef shipped fill:#1e3a8a,color:#fff
97
+ classDef project fill:#065f46,color:#fff
98
+ classDef internal fill:#374151,color:#fff
99
+ ```
100
+
101
+ **Legend** — blue: shipped to projects · green: a generated project, which owns its
102
+ skeleton · grey: developing NapkinStack itself, never copied (PDR-0001 R6). Solid line:
103
+ generation; dotted: a versioned dependency.
104
+
105
+ | Path | Role |
106
+ |---|---|
107
+ | `skeleton/` | What every project receives: kernel, playbooks, handbook, CI, hooks |
108
+ | `copier.yml` | The questions asked at creation (a Copier template, ADR-0001) |
109
+ | `src/napkinstack/` | The engine, the `nstack` command |
110
+ | `platform/` | The engine module's envelope: manifest, runbook, tests |
111
+ | `PRODUCT.md`, `docs/governance/` | The working context for NapkinStack itself |
112
+ | `docs/adr/`, `docs/pdr/` | NapkinStack's decisions |
113
+
114
+ ## Developing NapkinStack
115
+
116
+ ```bash
117
+ uv sync # prerequisite: uv
118
+ uv run pre-commit install
119
+ uv run nstack fitness # this repository's guardrails
120
+ uv run bash platform/tests/run.sh # the oracle: every guardrail proves it can fail
121
+ uv run nstack init /tmp/trial --source . --ref HEAD # a trial project from the working tree
122
+ uv run nstack doctor --root /tmp/trial # workstation and GitHub settings, read-only
123
+ ```
124
+
125
+ Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md), after [`PRODUCT.md`](PRODUCT.md).
126
+
127
+ Licence: [MIT](LICENSE).
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.1.0"
4
- description = "Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même dépôt : modules, contrats, garde-fous en CI."
3
+ version = "0.2.0"
4
+ description = "Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI."
5
5
  requires-python = ">=3.12"
6
6
  license = "MIT"
7
7
  license-files = ["LICENSE"]
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.1.0"
4
- description = "Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même dépôt : modules, contrats, garde-fous en CI."
3
+ version = "0.2.0"
4
+ description = "Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI."
5
5
  requires-python = ">=3.12"
6
6
  license = "MIT"
7
7
  license-files = ["LICENSE"]
@@ -0,0 +1,5 @@
1
+ """NapkinStack - engine: fitness functions, skills and module scaffolding."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("napkinstack")
@@ -0,0 +1,104 @@
1
+ """Single entry point `nstack` (workstream C1, PDR-0001)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ from napkinstack import __version__, doctor, modules, skills
11
+ from napkinstack.fitness import boundaries, manifests
12
+
13
+ PACKAGE = Path(__file__).resolve().parent
14
+
15
+
16
+ def _root(value: str) -> Path:
17
+ root = Path(value).resolve()
18
+ if not root.is_dir():
19
+ raise argparse.ArgumentTypeError(f"root not found: {value}")
20
+ return root
21
+
22
+
23
+ def _script(relative: str, *args: str, root: Path) -> int:
24
+ env = {**os.environ, "NSTACK_ROOT": str(root)}
25
+ return subprocess.run(["bash", str(PACKAGE / relative), *args], cwd=root, env=env).returncode
26
+
27
+
28
+ def _fitness(root: Path) -> int:
29
+ results = [manifests.run(root), boundaries.run(root), skills.run(root, check_only=True)]
30
+ return 1 if any(results) else 0
31
+
32
+
33
+ def _add(sub, name: str, help_: str, func) -> argparse.ArgumentParser:
34
+ parser = sub.add_parser(name, help=help_)
35
+ parser.add_argument("--root", type=_root, default=Path.cwd(),
36
+ help="project root (default: current folder)")
37
+ parser.set_defaults(func=func)
38
+ return parser
39
+
40
+
41
+ def _init(args: argparse.Namespace) -> int:
42
+ from napkinstack import project # Copier is only loaded for init and update
43
+
44
+ answers = {"project_name": args.project_name, "github_repo": args.github_repo,
45
+ "owner_team": args.owner_team}
46
+ return project.init(args.destination, answers, args.source or project.SOURCE,
47
+ args.ref or project.default_ref())
48
+
49
+
50
+ def _update(args: argparse.Namespace) -> int:
51
+ from napkinstack import project
52
+
53
+ return project.update(args.root, args.ref or project.default_ref())
54
+
55
+
56
+ def build_parser() -> argparse.ArgumentParser:
57
+ parser = argparse.ArgumentParser(prog="nstack", description="NapkinStack engine.")
58
+ parser.add_argument("--version", action="version", version=f"nstack {__version__}")
59
+ sub = parser.add_subparsers(dest="command", required=True, metavar="command")
60
+ _add(sub, "manifests", "manifests, lifecycles, deprecations (M1-M9)",
61
+ lambda a: manifests.run(a.root))
62
+ _add(sub, "boundaries", "declared graph against real graph (B1-B5)",
63
+ lambda a: boundaries.run(a.root))
64
+ sk = _add(sub, "skills", "generates or checks the skills (S1-S4)",
65
+ lambda a: skills.run(a.root, check_only=a.check))
66
+ sk.add_argument("--check", action="store_true", help="check without writing")
67
+ _add(sub, "fitness", "manifests + boundaries + skills",
68
+ lambda a: _fitness(a.root))
69
+ _add(sub, "doctor", "diagnoses the workstation and the GitHub settings, read-only (PDR-0001)",
70
+ lambda a: doctor.run(a.root))
71
+ nm = _add(sub, "new-module", "creates a module and its guardrails, with no imposed stack",
72
+ lambda a: modules.create(a.root, a.name, a.owner, a.criticality))
73
+ nm.add_argument("name", help="module name, kebab-case")
74
+ nm.add_argument("owner", help="GitHub team, organisation/team")
75
+ nm.add_argument("criticality", choices=["prototype", "standard", "high", "critical"])
76
+ for verb, help_text in (("bootstrap", "prepares one module, or all of them (commands.bootstrap)"),
77
+ ("check", "format, lint, types of one module, or all (commands.check)"),
78
+ ("test", "tests of one module, or of all of them (commands.test)")):
79
+ vb = _add(sub, verb, help_text, lambda a, v=verb: modules.run_verb(a.root, v, a.module))
80
+ vb.add_argument("module", nargs="?", help="module name (default: all)")
81
+ rn = _add(sub, "run", "starts a module locally (commands.run)",
82
+ lambda a: modules.run_verb(a.root, "run", a.module))
83
+ rn.add_argument("module")
84
+ ps = _add(sub, "pr-scope", "one PR = one module, review budget (P1-P2)",
85
+ lambda a: _script("fitness/pr_scope.sh", a.base, root=a.root))
86
+ ps.add_argument("--base", default="origin/main")
87
+ ini = sub.add_parser("init", help="creates a project from the skeleton (PDR-0001)")
88
+ ini.add_argument("destination", type=Path, help="project folder, missing or empty")
89
+ ini.add_argument("--project-name", help="project name (asked when absent)")
90
+ ini.add_argument("--github-repo", help="GitHub repository, organisation/name (asked when absent)")
91
+ ini.add_argument("--owner-team", help="GitHub team owning the foundation, organisation/team "
92
+ "(asked when absent)")
93
+ ini.add_argument("--source", help="template: URL or path (default: the NapkinStack repository)")
94
+ ini.add_argument("--ref", help="skeleton version, tag vX.Y.Z (default: the one of nstack)")
95
+ ini.set_defaults(func=_init)
96
+ up = _add(sub, "update", "merges a NapkinStack version onto a branch to review (PDR-0001)",
97
+ _update)
98
+ up.add_argument("--ref", help="target version, tag vX.Y.Z (default: the one of nstack)")
99
+ return parser
100
+
101
+
102
+ def main(argv: list[str] | None = None) -> int:
103
+ args = build_parser().parse_args(argv)
104
+ return args.func(args)