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.
Files changed (34) hide show
  1. lekha_poth_cli-2.1.0/.gitignore +13 -0
  2. lekha_poth_cli-2.1.0/PKG-INFO +109 -0
  3. lekha_poth_cli-2.1.0/README.md +93 -0
  4. lekha_poth_cli-2.1.0/pyproject.toml +36 -0
  5. lekha_poth_cli-2.1.0/scripts/generate_catalog_v2.py +121 -0
  6. lekha_poth_cli-2.1.0/scripts/generate_spec.py +595 -0
  7. lekha_poth_cli-2.1.0/skills/lp-cli/SKILL.md +340 -0
  8. lekha_poth_cli-2.1.0/skills/lp-cli/evals/evals.json +108 -0
  9. lekha_poth_cli-2.1.0/skills/lp-cli/references/admin.md +1112 -0
  10. lekha_poth_cli-2.1.0/skills/lp-cli/references/auth-account.md +545 -0
  11. lekha_poth_cli-2.1.0/skills/lp-cli/references/chats.md +111 -0
  12. lekha_poth_cli-2.1.0/skills/lp-cli/references/legacy.md +458 -0
  13. lekha_poth_cli-2.1.0/skills/lp-cli/references/library.md +597 -0
  14. lekha_poth_cli-2.1.0/skills/lp-cli/references/public-content.md +354 -0
  15. lekha_poth_cli-2.1.0/skills/lp-cli/references/vocab.md +320 -0
  16. lekha_poth_cli-2.1.0/skills/lp-cli/scripts/generate_references.py +742 -0
  17. lekha_poth_cli-2.1.0/src/lekha_poth_cli/__init__.py +6 -0
  18. lekha_poth_cli-2.1.0/src/lekha_poth_cli/__main__.py +4 -0
  19. lekha_poth_cli-2.1.0/src/lekha_poth_cli/app.py +287 -0
  20. lekha_poth_cli-2.1.0/src/lekha_poth_cli/client.py +149 -0
  21. lekha_poth_cli-2.1.0/src/lekha_poth_cli/config.py +145 -0
  22. lekha_poth_cli-2.1.0/src/lekha_poth_cli/endpoints.json +5821 -0
  23. lekha_poth_cli-2.1.0/src/lekha_poth_cli/errors.py +75 -0
  24. lekha_poth_cli-2.1.0/src/lekha_poth_cli/media.py +94 -0
  25. lekha_poth_cli-2.1.0/src/lekha_poth_cli/output.py +35 -0
  26. lekha_poth_cli-2.1.0/src/lekha_poth_cli/register.py +326 -0
  27. lekha_poth_cli-2.1.0/src/lekha_poth_cli/spec.py +58 -0
  28. lekha_poth_cli-2.1.0/tests/test_catalog_v2.py +39 -0
  29. lekha_poth_cli-2.1.0/tests/test_config.py +28 -0
  30. lekha_poth_cli-2.1.0/tests/test_coverage.py +149 -0
  31. lekha_poth_cli-2.1.0/tests/test_errors.py +132 -0
  32. lekha_poth_cli-2.1.0/tests/test_references_current.py +32 -0
  33. lekha_poth_cli-2.1.0/tests/test_spec_fields.py +174 -0
  34. lekha_poth_cli-2.1.0/uv.lock +372 -0
@@ -0,0 +1,13 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .DS_Store
9
+ .env
10
+ config.json
11
+
12
+ # skill-creator eval workspace (snapshots, run outputs) — local only
13
+ skills/*-workspace/
@@ -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())