#!/usr/bin/env python3 """Issue a Hound licence token. The token is an Ed25519 signature over the canonical licence text, wrapped in the same signed-blob JSON as a definitions pack, then base64-encoded to a single line a customer can paste into: sudo hound license install Signed with the same key as definition packs and release manifests, and verified by the agent against the same compiled-in public key. There is no licence server and no phone-home: the token is the entire entitlement. Usage: tools/issue-license.py --tier pro --holder "Ada L " \ --expires 2027-08-21 tools/issue-license.py --tier fleet --holder "Some Shop" --seats 25 \ --expires 2027-08-21 """ import argparse, base64, json, pathlib, sys KEY = pathlib.Path.home() / "agents/hound/.secrets/defs-signing.key" KEY_ID = "hound-2026" # Must match hound_api::license::License::canonical exactly. def canonical(tier, holder, expires, seats): return f"hound-license-v1\ntier={tier}\nholder={holder}\nexpires={expires}\nseats={seats}\n" def main(): ap = argparse.ArgumentParser() ap.add_argument("--tier", required=True, choices=["pro", "fleet"]) ap.add_argument("--holder", required=True, help="display name, e.g. 'Ada L '") ap.add_argument("--expires", required=True, help="ISO date, e.g. 2027-08-21; the agent falls back to Free after this") ap.add_argument("--seats", type=int, default=0, help="Fleet seat count (0 for Pro)") ap.add_argument("--key", type=pathlib.Path, default=KEY) a = ap.parse_args() if len(a.expires) != 10 or a.expires[4] != "-" or a.expires[7] != "-": sys.exit(f"--expires must be an ISO date (YYYY-MM-DD), got {a.expires!r}") if "\n" in a.holder or "=" not in canonical("x", a.holder, "", 0).splitlines()[2]: sys.exit("--holder must be a single line") if a.tier == "fleet" and a.seats < 3: sys.exit("Fleet licences have a 3-seat minimum") if not a.key.is_file(): sys.exit(f"no signing key at {a.key}") try: from nacl.signing import SigningKey except ImportError: sys.exit("pip install pynacl") seed = a.key.read_bytes() if len(seed) == 64: seed = seed[:32] sk = SigningKey(seed) payload = canonical(a.tier, a.holder, a.expires, a.seats).encode() sig = sk.sign(payload).signature signed = { "payload": base64.b64encode(payload).decode(), "signature": base64.b64encode(sig).decode(), "key_id": KEY_ID, } token = base64.b64encode(json.dumps(signed).encode()).decode() print(f"# {a.tier} · {a.holder} · expires {a.expires}" + (f" · {a.seats} seats" if a.seats else ""), file=sys.stderr) print(f"# install with: sudo hound license install ", file=sys.stderr) print(token) if __name__ == "__main__": main()