lekha-poth-cli 2.1.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.
- lekha_poth_cli-2.1.0/.gitignore +13 -0
- lekha_poth_cli-2.1.0/PKG-INFO +109 -0
- lekha_poth_cli-2.1.0/README.md +93 -0
- lekha_poth_cli-2.1.0/pyproject.toml +36 -0
- lekha_poth_cli-2.1.0/scripts/generate_catalog_v2.py +121 -0
- lekha_poth_cli-2.1.0/scripts/generate_spec.py +595 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/SKILL.md +340 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/evals/evals.json +108 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/admin.md +1112 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/auth-account.md +545 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/chats.md +111 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/legacy.md +458 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/library.md +597 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/public-content.md +354 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/references/vocab.md +320 -0
- lekha_poth_cli-2.1.0/skills/lp-cli/scripts/generate_references.py +742 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/__init__.py +6 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/__main__.py +4 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/app.py +287 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/client.py +149 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/config.py +145 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/endpoints.json +5821 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/errors.py +75 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/media.py +94 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/output.py +35 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/register.py +326 -0
- lekha_poth_cli-2.1.0/src/lekha_poth_cli/spec.py +58 -0
- lekha_poth_cli-2.1.0/tests/test_catalog_v2.py +39 -0
- lekha_poth_cli-2.1.0/tests/test_config.py +28 -0
- lekha_poth_cli-2.1.0/tests/test_coverage.py +149 -0
- lekha_poth_cli-2.1.0/tests/test_errors.py +132 -0
- lekha_poth_cli-2.1.0/tests/test_references_current.py +32 -0
- lekha_poth_cli-2.1.0/tests/test_spec_fields.py +174 -0
- lekha_poth_cli-2.1.0/uv.lock +372 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lekha-poth-cli
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Production Typer CLI covering every Lekha Poth HTTP API endpoint.
|
|
5
|
+
Author: Hermes Agent
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: httpx>=0.27.0
|
|
9
|
+
Requires-Dist: pydantic-settings>=2.4.0
|
|
10
|
+
Requires-Dist: pydantic>=2.7.0
|
|
11
|
+
Requires-Dist: rich>=13.7.0
|
|
12
|
+
Requires-Dist: typer>=0.15.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Lekha Poth CLI (`lp`) v2
|
|
18
|
+
|
|
19
|
+
Production Typer + Pydantic client for **every** Lekha Poth HTTP endpoint,
|
|
20
|
+
generated from `backend/src/app/api` on `main` (kept in sync — currently
|
|
21
|
+
140 routes as of product v0.16.2 / `6d2c833`; regenerate with
|
|
22
|
+
`generate_spec.py` after any route change, guarded in CI by
|
|
23
|
+
`backend/tests/unit/test_agent_spec_current.py`).
|
|
24
|
+
|
|
25
|
+
Replaces the old stdlib `scripts/agent/lp.py` reader CLI.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv venv
|
|
31
|
+
uv pip install -e ~/.hermes/skills/software-development/lekha-poth/lpcli
|
|
32
|
+
# or: uv tool install -e that-path
|
|
33
|
+
lp --help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The wrapper `~/.local/bin/lp` should point at this package.
|
|
37
|
+
|
|
38
|
+
## Configure (once)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
lp config set --api-base "$LEKHA_POTH_API_BASE" \
|
|
42
|
+
--site-url "$LEKHA_POTH_SITE_URL" \
|
|
43
|
+
--api-key lpak_…
|
|
44
|
+
# hosted production example:
|
|
45
|
+
# --api-base https://api.lekhapoth.com/api/v1 --site-url https://lekhapoth.com
|
|
46
|
+
lp config set-defaults --output pretty --default-size 20
|
|
47
|
+
lp status
|
|
48
|
+
lp whoami
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Config file: `~/.config/lekha-poth/config.json` (mode `0600`).
|
|
52
|
+
Existing keys (`api_base`, `api_key`, `site_url`, `key_name`, `created_at`) are kept.
|
|
53
|
+
|
|
54
|
+
Env overrides: `LEKHA_POTH_API_BASE`, `LEKHA_POTH_API_KEY`, `LEKHA_POTH_SITE_URL`, `LEKHA_POTH_CONFIG`.
|
|
55
|
+
|
|
56
|
+
## Coverage
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
lp endpoints # every route, from endpoints.json
|
|
60
|
+
lp endpoints --admin
|
|
61
|
+
lp --help
|
|
62
|
+
lp admin items --help
|
|
63
|
+
lp items get --help # every query/body field is a flag
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Raw escape hatch:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
lp api request GET /items --param size=5 --param kind=text
|
|
70
|
+
lp api request GET /health --root
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Staff media (initiate → direct PUT → complete → poll):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
lp upload ./ep.mp4 --visibility public --role original --item-id UUID --wait
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Iron rules (from the live API)
|
|
80
|
+
|
|
81
|
+
- Wire JSON is **camelCase** except pagination: `{size, next_cursor, has_next}` (never `hasNext`, no `total` on public lists).
|
|
82
|
+
- Errors: `{"error": {code, message, details, request_id}}` — branch on `error.code`.
|
|
83
|
+
- `coverUrl` is stable or `null`. `assets[].url` may be presigned (~15 min); match assets by `role`.
|
|
84
|
+
- Legacy `/chapters` and `/series` exist only when `APP_LEGACY_API_ENABLED`.
|
|
85
|
+
- Never store presigned URLs. Never put `lpak_` in git, skills, or Hermes memory.
|
|
86
|
+
|
|
87
|
+
## Regenerating the route catalogue
|
|
88
|
+
|
|
89
|
+
`src/lekha_poth_cli/endpoints.json` drives every command, and it is **derived
|
|
90
|
+
from the live FastAPI app** — there is no intermediate artefact to keep in sync:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd backend
|
|
94
|
+
uv run python ../scripts/agent/lpcli/scripts/generate_spec.py # rewrite it
|
|
95
|
+
uv run python ../scripts/agent/lpcli/scripts/generate_spec.py --check # verify only
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`--check` writes nothing and exits non-zero when the tracked file is stale.
|
|
99
|
+
`backend/tests/unit/test_agent_spec_current.py` runs the same comparison in CI,
|
|
100
|
+
so adding a route to the API without regenerating fails the build.
|
|
101
|
+
|
|
102
|
+
A **new** route stops the generator with an error rather than inventing a
|
|
103
|
+
command name for it: the id is what a person types, so it is a decision. Add an
|
|
104
|
+
entry to `NEW_IDENTITY` in the generator and re-run. Existing ids, `auth` values
|
|
105
|
+
and hand-shortened flag names (`--status` for the parameter `status_filter`) are
|
|
106
|
+
preserved on every run — they are the public CLI surface.
|
|
107
|
+
|
|
108
|
+
Regenerating covers the **routes**. `docs/agent/*`, `agent/SKILL.md`,
|
|
109
|
+
`public/llms.txt` and `public/.well-known/agent.json` are still updated by hand.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Lekha Poth CLI (`lp`) v2
|
|
2
|
+
|
|
3
|
+
Production Typer + Pydantic client for **every** Lekha Poth HTTP endpoint,
|
|
4
|
+
generated from `backend/src/app/api` on `main` (kept in sync — currently
|
|
5
|
+
140 routes as of product v0.16.2 / `6d2c833`; regenerate with
|
|
6
|
+
`generate_spec.py` after any route change, guarded in CI by
|
|
7
|
+
`backend/tests/unit/test_agent_spec_current.py`).
|
|
8
|
+
|
|
9
|
+
Replaces the old stdlib `scripts/agent/lp.py` reader CLI.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv venv
|
|
15
|
+
uv pip install -e ~/.hermes/skills/software-development/lekha-poth/lpcli
|
|
16
|
+
# or: uv tool install -e that-path
|
|
17
|
+
lp --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The wrapper `~/.local/bin/lp` should point at this package.
|
|
21
|
+
|
|
22
|
+
## Configure (once)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
lp config set --api-base "$LEKHA_POTH_API_BASE" \
|
|
26
|
+
--site-url "$LEKHA_POTH_SITE_URL" \
|
|
27
|
+
--api-key lpak_…
|
|
28
|
+
# hosted production example:
|
|
29
|
+
# --api-base https://api.lekhapoth.com/api/v1 --site-url https://lekhapoth.com
|
|
30
|
+
lp config set-defaults --output pretty --default-size 20
|
|
31
|
+
lp status
|
|
32
|
+
lp whoami
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Config file: `~/.config/lekha-poth/config.json` (mode `0600`).
|
|
36
|
+
Existing keys (`api_base`, `api_key`, `site_url`, `key_name`, `created_at`) are kept.
|
|
37
|
+
|
|
38
|
+
Env overrides: `LEKHA_POTH_API_BASE`, `LEKHA_POTH_API_KEY`, `LEKHA_POTH_SITE_URL`, `LEKHA_POTH_CONFIG`.
|
|
39
|
+
|
|
40
|
+
## Coverage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
lp endpoints # every route, from endpoints.json
|
|
44
|
+
lp endpoints --admin
|
|
45
|
+
lp --help
|
|
46
|
+
lp admin items --help
|
|
47
|
+
lp items get --help # every query/body field is a flag
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Raw escape hatch:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
lp api request GET /items --param size=5 --param kind=text
|
|
54
|
+
lp api request GET /health --root
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Staff media (initiate → direct PUT → complete → poll):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
lp upload ./ep.mp4 --visibility public --role original --item-id UUID --wait
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Iron rules (from the live API)
|
|
64
|
+
|
|
65
|
+
- Wire JSON is **camelCase** except pagination: `{size, next_cursor, has_next}` (never `hasNext`, no `total` on public lists).
|
|
66
|
+
- Errors: `{"error": {code, message, details, request_id}}` — branch on `error.code`.
|
|
67
|
+
- `coverUrl` is stable or `null`. `assets[].url` may be presigned (~15 min); match assets by `role`.
|
|
68
|
+
- Legacy `/chapters` and `/series` exist only when `APP_LEGACY_API_ENABLED`.
|
|
69
|
+
- Never store presigned URLs. Never put `lpak_` in git, skills, or Hermes memory.
|
|
70
|
+
|
|
71
|
+
## Regenerating the route catalogue
|
|
72
|
+
|
|
73
|
+
`src/lekha_poth_cli/endpoints.json` drives every command, and it is **derived
|
|
74
|
+
from the live FastAPI app** — there is no intermediate artefact to keep in sync:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd backend
|
|
78
|
+
uv run python ../scripts/agent/lpcli/scripts/generate_spec.py # rewrite it
|
|
79
|
+
uv run python ../scripts/agent/lpcli/scripts/generate_spec.py --check # verify only
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`--check` writes nothing and exits non-zero when the tracked file is stale.
|
|
83
|
+
`backend/tests/unit/test_agent_spec_current.py` runs the same comparison in CI,
|
|
84
|
+
so adding a route to the API without regenerating fails the build.
|
|
85
|
+
|
|
86
|
+
A **new** route stops the generator with an error rather than inventing a
|
|
87
|
+
command name for it: the id is what a person types, so it is a decision. Add an
|
|
88
|
+
entry to `NEW_IDENTITY` in the generator and re-run. Existing ids, `auth` values
|
|
89
|
+
and hand-shortened flag names (`--status` for the parameter `status_filter`) are
|
|
90
|
+
preserved on every run — they are the public CLI surface.
|
|
91
|
+
|
|
92
|
+
Regenerating covers the **routes**. `docs/agent/*`, `agent/SKILL.md`,
|
|
93
|
+
`public/llms.txt` and `public/.well-known/agent.json` are still updated by hand.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lekha-poth-cli"
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
description = "Production Typer CLI covering every Lekha Poth HTTP API endpoint."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Hermes Agent" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"typer>=0.15.0",
|
|
15
|
+
"pydantic>=2.7.0",
|
|
16
|
+
"pydantic-settings>=2.4.0",
|
|
17
|
+
"httpx>=0.27.0",
|
|
18
|
+
"rich>=13.7.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
lp = "lekha_poth_cli.app:main"
|
|
23
|
+
lekha-poth = "lekha_poth_cli.app:main"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=8.0"]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/lekha_poth_cli"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
32
|
+
"src/lekha_poth_cli/endpoints.json" = "lekha_poth_cli/endpoints.json"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regenerate ``docs/agent/API_CATALOG_V2.md``'s route table from ``endpoints.json``.
|
|
3
|
+
|
|
4
|
+
Run from anywhere in the repo::
|
|
5
|
+
|
|
6
|
+
python3 scripts/agent/lpcli/scripts/generate_catalog_v2.py
|
|
7
|
+
python3 scripts/agent/lpcli/scripts/generate_catalog_v2.py --check
|
|
8
|
+
|
|
9
|
+
``--check`` writes nothing and exits non-zero when the tracked file is stale —
|
|
10
|
+
the same contract ``generate_spec.py`` uses, so this can guard CI the same way
|
|
11
|
+
(see ``backend/tests/unit/test_agent_spec_current.py``, which this script's
|
|
12
|
+
own currency should eventually join — #172).
|
|
13
|
+
|
|
14
|
+
Why this exists
|
|
15
|
+
----------------
|
|
16
|
+
``API_CATALOG_V2.md`` claims to be "Generated from FastAPI routers" and had no
|
|
17
|
+
generator. It was hand-maintained, drifted to 120 routes while the live app
|
|
18
|
+
served 132/114, and stayed wrong through several releases because nothing
|
|
19
|
+
could regenerate it and nothing checked it (#64). ``endpoints.json`` is
|
|
20
|
+
already the single source of truth for the CLI's route surface (regenerated
|
|
21
|
+
from the live app by ``generate_spec.py``) — this script derives the table
|
|
22
|
+
mechanically from it instead of transcribing by hand a second time.
|
|
23
|
+
|
|
24
|
+
What this script does NOT own
|
|
25
|
+
------------------------------
|
|
26
|
+
The free-text sections above and below the table (the intro, the field
|
|
27
|
+
documentation) are hand-written and preserved verbatim between the
|
|
28
|
+
``<!-- BEGIN:route-table -->`` / ``<!-- END:route-table -->`` markers this
|
|
29
|
+
script owns. Nothing here can generate prose explaining what ``coverUrl`` is.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import json
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
ROOT = Path(__file__).resolve().parents[4]
|
|
40
|
+
ENDPOINTS = ROOT / "scripts" / "agent" / "lpcli" / "src" / "lekha_poth_cli" / "endpoints.json"
|
|
41
|
+
CATALOG = ROOT / "docs" / "agent" / "API_CATALOG_V2.md"
|
|
42
|
+
|
|
43
|
+
BEGIN = "<!-- BEGIN:route-table -->"
|
|
44
|
+
END = "<!-- END:route-table -->"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _cli_command(endpoint_id: str) -> str:
|
|
48
|
+
"""``admin.characters.extract`` -> ``lp admin characters extract``."""
|
|
49
|
+
return "lp " + endpoint_id.replace(".", " ")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_table(endpoints: list[dict]) -> str:
|
|
53
|
+
on = len(endpoints)
|
|
54
|
+
off = sum(1 for e in endpoints if not e["deprecated"])
|
|
55
|
+
lines = [
|
|
56
|
+
BEGIN,
|
|
57
|
+
f"Total routes: **{on}** with the deprecated legacy aliases on "
|
|
58
|
+
f"(`APP_LEGACY_API_ENABLED=true`), **{off}** with them off (the "
|
|
59
|
+
"production default). `lp endpoints` is the live count; this table is "
|
|
60
|
+
"regenerated from the same source.",
|
|
61
|
+
"",
|
|
62
|
+
"| CLI | Method | Path | Auth | Deprecated |",
|
|
63
|
+
"|-----|--------|------|------|------------|",
|
|
64
|
+
]
|
|
65
|
+
for e in sorted(endpoints, key=lambda e: (e["path"], e["method"])):
|
|
66
|
+
deprecated = "yes" if e["deprecated"] else ""
|
|
67
|
+
lines.append(
|
|
68
|
+
f"| `{_cli_command(e['id'])}` | {e['method']} | `{e['path']}` | "
|
|
69
|
+
f"{e['auth']} | {deprecated} |"
|
|
70
|
+
)
|
|
71
|
+
lines.append(END)
|
|
72
|
+
return "\n".join(lines)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def render(endpoints: list[dict], existing: str) -> str:
|
|
76
|
+
table = build_table(endpoints)
|
|
77
|
+
start = existing.find(BEGIN)
|
|
78
|
+
end = existing.find(END)
|
|
79
|
+
if start == -1 or end == -1:
|
|
80
|
+
raise SystemExit(
|
|
81
|
+
f"{CATALOG.relative_to(ROOT)} is missing the {BEGIN!r}/{END!r} "
|
|
82
|
+
"markers this script owns — restore them (see the top of "
|
|
83
|
+
"API_CATALOG_V2.md in git history) before regenerating."
|
|
84
|
+
)
|
|
85
|
+
return existing[:start] + table + existing[end + len(END) :]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> int:
|
|
89
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
"--check", action="store_true", help="exit non-zero if the tracked file is stale"
|
|
92
|
+
)
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
|
|
95
|
+
if not ENDPOINTS.is_file():
|
|
96
|
+
raise SystemExit(
|
|
97
|
+
f"{ENDPOINTS.relative_to(ROOT)} is missing — regenerate it first:\n"
|
|
98
|
+
" cd backend && uv run python ../scripts/agent/lpcli/scripts/generate_spec.py"
|
|
99
|
+
)
|
|
100
|
+
endpoints = json.loads(ENDPOINTS.read_text(encoding="utf-8"))["endpoints"]
|
|
101
|
+
existing = CATALOG.read_text(encoding="utf-8")
|
|
102
|
+
updated = render(endpoints, existing)
|
|
103
|
+
|
|
104
|
+
if args.check:
|
|
105
|
+
if updated != existing:
|
|
106
|
+
print(f"{CATALOG.relative_to(ROOT)} is stale. Regenerate it:")
|
|
107
|
+
print(" python3 scripts/agent/lpcli/scripts/generate_catalog_v2.py")
|
|
108
|
+
return 1
|
|
109
|
+
print(f"{CATALOG.relative_to(ROOT)} is current.")
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
if updated == existing:
|
|
113
|
+
print(f"{CATALOG.relative_to(ROOT)} already current.")
|
|
114
|
+
return 0
|
|
115
|
+
CATALOG.write_text(updated, encoding="utf-8")
|
|
116
|
+
print(f"wrote {CATALOG.relative_to(ROOT)} ({len(endpoints)} endpoints)")
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
sys.exit(main())
|