Metadata-Version: 2.5
Name: expln-cli
Version: 0.2.0
Summary: expln corpus administration CLI — your firm's admin uploads, syncs, and manages the document corpus on your own expln node
Maintainer-email: expln <support@expln.xyz>
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: pypdf>=4.0
Requires-Dist: pypng>=0.20220715
Requires-Dist: qrcode>=8.0
Description-Content-Type: text/markdown

# expln-cli — client corpus administration

The tool the **client firm's admin** runs on their own machine to manage the
drawer corpus on their expln node: upload, list, find, delete, `rsync`-style
folder sync, and a smoke-test `ask`. Server side: the orchestrator's
`/admin/*` API (`ORCH_ADMIN_KEY`-gated), reached over the node's public
hostname (Cloudflare Tunnel) — nothing runs on the firm's servers, no VPN.
Design + decisions: `docs/decisions/plan-admin-corpus-management.md`;
client-facing instructions: `docs/client-admin-guide.md`.

**This is proprietary software** (`LICENSE`), published on PyPI so the firms
running an expln node install a verifiable artifact under a name nobody else can
claim — not as an invitation to use it. Without a node and an admin key it does
nothing: the `/admin/*` surface answers 404 on a node that has not opted in.

## Installing

```
pipx install expln-cli==0.2.0
expln --version
```

Your operator gives you the version and its SHA-256 at onboarding; verify the
checksum before the first run (`client-admin-guide.md` step 1 has the commands
for each platform). Install **only** this exact name — no other `expln*` package
on any index is ours, whatever it claims.

```
uv run expln configure                # node URL + admin key (or EXPLN_URL / EXPLN_ADMIN_KEY)
uv run expln drawers | list | find "Maier" | rm <doc_id> | ask "…"
uv run expln put oib-rl2.pdf --drawer national_codes --date 2023-04
uv run expln sync /path/to/tree --map Normen=national_codes --dry-run
uv run expln rules set rules.json     # scan-app rule set (the firm's numbers)
uv run expln spatial-keys add anna@firma.at   # scan-app key + pairing QR (list/show/revoke)
uv run expln guide --write ~/expln        # AGENTS.md + CLAUDE.md for the admin's coding assistant
```

`src/expln_cli/guide.md` is the agent-facing operating guide, shipped in the
wheel and tested against the verb tree (`tests/test_guide.py`): every leaf
verb must be taught, every verb and flag it names must exist. Design:
`docs/decisions/plan-agent-driven-admin.md`. What the test cannot see is a
verb whose *behaviour* changed under an unchanged name — that update to
`guide.md` is the author's, in the same commit — and the behavioural gate
for the text is hand-driven: `gate/` (README, `reset.sh`, `verify.sh`,
fixtures), run against the local dev stack when the guide or a verb changes.

Sync semantics (rsync-flavored, guardrails first):

- identity = normalized relative path (forward slashes, NFC, casefolded) →
  path-keyed doc id; change detection = content hash. Duplicate files at two
  paths stay two documents; a changed file replaces in place.
- only documents this tool ingested (they carry `source_path`) in drawers the
  current mapping targets are ever touched — founder-CLI ingests are invisible.
- additive by default; `--delete` enables removals, behind an empty-source
  refusal and a mass-deletion confirmation threshold. `--dry-run` prints the
  full plan + an OCR cost preview (local pypdf page count, ~$4/1000 pages).
- unchanged files skip (no re-OCR/re-embed), so re-runs are cheap and a run
  that died mid-way is fixed by re-running.

Dev: `uv run pytest` (pure reconcile/scan logic), `uv run ruff check . && uv run
ruff format --check .`.

Windows is the real deployment target. Releases are cut by
`deploy/release-cli.sh` (build → checksum → publish, one founder act from a
clean checkout — the sibling of `release-image.sh`), and the sdist is an
allowlist, so `gate/` never leaves the repo. Build a PyInstaller .exe only when
a customer demands it.
