AQIT 0.1.0
Loading...
Searching...
No Matches
license_acceptance.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2# This file is part of the Aquin Engine. Unauthorized copying, modification,
3# distribution, or use of this file, via any medium, is strictly prohibited.
4# Proprietary and confidential. See LICENSE for terms.
5
6"""First-run AQIT license acceptance gate."""
7
8from __future__ import annotations
9
10import hashlib
11import os
12import sys
13from datetime import datetime, timezone
14from typing import Any
15
16from aquin.auth_config import load_config, save_config
17from aquin.license_text import LICENSE_URL, read_license_text
18
19# Only `aquin license` (read terms) bypasses the gate. Everything else requires acceptance.
20_LICENSE_EXEMPT_CMDS = frozenset({"license"})
21
22
23def current_license_hash() -> str:
24 return hashlib.sha256(read_license_text().encode("utf-8")).hexdigest()
25
26
27def _accepted_record(cfg: dict[str, Any]) -> dict[str, Any] | None:
28 block = cfg.get("license_accepted")
29 return block if isinstance(block, dict) else None
30
32def is_license_accepted() -> bool:
33 record = _accepted_record(load_config())
34 if not record:
35 return False
36 return str(record.get("hash") or "").strip() == current_license_hash()
37
38
39def record_license_acceptance() -> None:
40 cfg = load_config()
41 cfg["license_accepted"] = {
42 "hash": current_license_hash(),
43 "accepted_at": datetime.now(timezone.utc).isoformat(),
44 }
45 save_config(cfg)
46
47
48def _env_auto_accept() -> bool:
49 return os.environ.get("AQUIN_LICENSE_ACCEPTED", "").strip().lower() in {"1", "true", "yes"}
50
51
52def ensure_license_accepted(*, cmd: str | None) -> None:
53 if cmd in _LICENSE_EXEMPT_CMDS:
54 return
56 return
59 return
60 _prompt_and_accept(context="first_use")
61
62
63def accept_license_cli() -> None:
64 """Non-interactive acceptance via `aquin --accept-license`."""
66 print("License already accepted.")
67 return
69 print("License accepted.")
70
71
72def _non_tty_message() -> None:
73 print(
74 "The AQIT license has not been accepted on this machine.\n"
75 "The CLI is unusable until you accept.\n"
76 f"Read the license: {LICENSE_URL}\n"
77 "Or run: aqit license --plain\n"
78 "Then run any aqit command interactively and accept when prompted, or run:\n"
79 " aqit --accept-license\n"
80 "after reviewing the terms (non-interactive / CI: set AQUIN_LICENSE_ACCEPTED=1).",
81 file=sys.stderr,
82 )
83
84
85def _prompt_and_accept(*, context: str = "first_use", exit_on_decline: bool = True) -> bool:
86 if not sys.stdin.isatty():
88 if exit_on_decline:
89 sys.exit(1)
90 return False
91
92 from aquin.license_display import prompt_license_acceptance
93
94 if not prompt_license_acceptance(context=context):
95 if exit_on_decline:
96 print("License not accepted. The CLI is unusable until you accept.")
97 sys.exit(1)
98 print("License not accepted. The CLI is unusable until you accept.")
99 return False
100
102 from aquin.ascii_layout import center, rule, width
103
104 print()
105 w = width()
106 print(rule("=", w))
107 print(center("LICENSE ACCEPTED", w))
108 print(rule("=", w))
109 print()
110 return True
dict[str, Any]|None _accepted_record(dict[str, Any] cfg)
bool _prompt_and_accept(*, str context="first_use", bool exit_on_decline=True)
None ensure_license_accepted(*, str|None cmd)