|
54 | 54 | from __future__ import annotations |
55 | 55 |
|
56 | 56 | import argparse |
| 57 | +import os |
57 | 58 | import sys |
58 | 59 | from collections.abc import Sequence |
59 | 60 | from pathlib import Path |
60 | 61 | from typing import Final |
61 | 62 |
|
| 63 | +_CREDENTIALS_FILE: Final[Path] = Path.home() / ".config" / "leadforge" / "credentials" |
| 64 | + |
| 65 | + |
| 66 | +def _read_token_from_credentials_file() -> str | None: |
| 67 | + """Read HF_TOKEN from ``~/.config/leadforge/credentials`` if present. |
| 68 | +
|
| 69 | + The file uses ``KEY=VALUE`` lines; blank lines and lines starting with |
| 70 | + ``#`` are ignored. Returns the first value for ``HF_TOKEN`` found, or |
| 71 | + ``None`` if the file doesn't exist or the key isn't set. |
| 72 | + """ |
| 73 | + if not _CREDENTIALS_FILE.exists(): |
| 74 | + return None |
| 75 | + try: |
| 76 | + for raw_line in _CREDENTIALS_FILE.read_text(encoding="utf-8").splitlines(): |
| 77 | + line = raw_line.strip() |
| 78 | + if not line or line.startswith("#"): |
| 79 | + continue |
| 80 | + if "=" in line: |
| 81 | + key, _, value = line.partition("=") |
| 82 | + if key.strip() == "HF_TOKEN": |
| 83 | + token = value.strip() |
| 84 | + if token and not token.startswith("REPLACE_WITH"): |
| 85 | + return token |
| 86 | + except OSError: |
| 87 | + pass |
| 88 | + return None |
| 89 | + |
| 90 | + |
| 91 | +def _resolve_token(cli_token: str | None) -> str | None: |
| 92 | + """Return the best available HF token. |
| 93 | +
|
| 94 | + Priority order (highest first): |
| 95 | + 1. ``--token`` CLI argument |
| 96 | + 2. ``HF_TOKEN`` / ``HUGGING_FACE_HUB_TOKEN`` env var |
| 97 | + 3. ``~/.config/leadforge/credentials`` file |
| 98 | + 4. ``None`` — falls through to ``huggingface_hub``'s own credential cache |
| 99 | + """ |
| 100 | + if cli_token: |
| 101 | + return cli_token |
| 102 | + for env_key in ("HF_TOKEN", "HUGGING_FACE_HUB_TOKEN"): |
| 103 | + value = os.environ.get(env_key) |
| 104 | + if value: |
| 105 | + return value |
| 106 | + file_token = _read_token_from_credentials_file() |
| 107 | + if file_token: |
| 108 | + print( |
| 109 | + f" token: read from {_CREDENTIALS_FILE}", |
| 110 | + file=sys.stderr, |
| 111 | + ) |
| 112 | + return file_token |
| 113 | + return None |
| 114 | + |
| 115 | + |
62 | 116 | # Make ``scripts/`` importable regardless of invocation style. |
63 | 117 | sys.path.insert(0, str(Path(__file__).resolve().parent)) |
64 | 118 |
|
@@ -306,10 +360,11 @@ def main(argv: Sequence[str] | None = None) -> int: |
306 | 360 | args = _parse_args(argv) |
307 | 361 | release_dir: Path = args.release_dir.resolve() |
308 | 362 | variant: str = args.variant |
| 363 | + token: str | None = _resolve_token(args.token) |
309 | 364 |
|
310 | 365 | # --- Go-public shortcut ------------------------------------------------- |
311 | 366 | if args.go_public: |
312 | | - _go_public(variant, token=args.token) |
| 367 | + _go_public(variant, token=token) |
313 | 368 | return 0 |
314 | 369 |
|
315 | 370 | # --- Pre-flight --------------------------------------------------------- |
@@ -342,7 +397,7 @@ def main(argv: Sequence[str] | None = None) -> int: |
342 | 397 | _upload( |
343 | 398 | upload_dir, |
344 | 399 | variant, |
345 | | - token=args.token, |
| 400 | + token=token, |
346 | 401 | private=args.private, |
347 | 402 | commit_message=args.commit_message, |
348 | 403 | ) |
|
0 commit comments