Major Updates: - ✅ Implemented 4 Claude Code Skills (2600+ lines) - code-review: Security, quality, type checking - test-generator: Auto-generate pytest tests - doc-updater: Auto-update README and API docs - refactor-assistant: Code smell detection - ✅ Implemented 2 Git Hooks (450+ lines) - pre-commit: Auto-format, lint, review - pre-push: Test suite and coverage check - 📚 Complete documentation (20+ files) - Skills READMEs with examples - Optimization roadmap and progress - TODO list with 22 tasks - Quick reference guide - 📊 Progress: 45% → 64% (Phase 1: 20% → 60%) - 💰 ROI: Saving 50.6 hours/month ≈ 6.3 person-days/month Next Steps: - API Designer Skill - Code templates library - Prompt library - GitHub Projects task tracking 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
254 lines
7.4 KiB
Python
Executable File
254 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
获取 GitHub Token 的通用工具
|
|
|
|
按优先级从多个来源尝试获取 token:
|
|
1. 命令行参数 --token
|
|
2. 环境变量 GITHUB_TOKEN
|
|
3. GitHub CLI (gh auth token)
|
|
4. macOS Keychain
|
|
5. Linux pass
|
|
6. .env 文件
|
|
|
|
使用方法:
|
|
# 获取 token 并输出
|
|
python scripts/get-github-token.py
|
|
|
|
# 获取 token 并设置为环境变量
|
|
export GITHUB_TOKEN=$(python scripts/get-github-token.py)
|
|
"""
|
|
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
|
|
class TokenSource:
|
|
"""Token 来源枚举"""
|
|
ARG = "command argument"
|
|
ENV = "environment variable"
|
|
GH_CLI = "GitHub CLI"
|
|
KEYCHAIN = "macOS Keychain"
|
|
PASS = "Linux pass"
|
|
DOTENV = ".env file"
|
|
ONE_PASSWORD = "1Password CLI"
|
|
|
|
|
|
def get_token_from_arg(args) -> tuple:
|
|
"""从命令行参数获取"""
|
|
if args and hasattr(args, 'token') and args.token:
|
|
return args.token, TokenSource.ARG
|
|
return None, None
|
|
|
|
|
|
def get_token_from_env() -> tuple:
|
|
"""从环境变量获取"""
|
|
token = os.getenv('GITHUB_TOKEN')
|
|
if token:
|
|
return token, TokenSource.ENV
|
|
return None, None
|
|
|
|
|
|
def get_token_from_gh_cli() -> tuple:
|
|
"""从 GitHub CLI 获取"""
|
|
try:
|
|
token = subprocess.check_output(
|
|
['gh', 'auth', 'token'],
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
if token and token.startswith(('ghp_', 'github_pat_')):
|
|
return token, TokenSource.GH_CLI
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
pass
|
|
return None, None
|
|
|
|
|
|
def get_token_from_keychain(service_name="GitHub Token - SDLC Management") -> tuple:
|
|
"""从 macOS Keychain 获取"""
|
|
if sys.platform != 'darwin':
|
|
return None, None
|
|
|
|
try:
|
|
whoami = subprocess.check_output(
|
|
['whoami'],
|
|
text=True
|
|
).strip()
|
|
|
|
token = subprocess.check_output(
|
|
['security', 'find-generic-password',
|
|
'-s', service_name,
|
|
'-a', whoami,
|
|
'-w'],
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
|
|
if token and token.startswith(('ghp_', 'github_pat_')):
|
|
return token, TokenSource.KEYCHAIN
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
return None, None
|
|
|
|
|
|
def get_token_from_pass(pass_path="github/token-sdlc") -> tuple:
|
|
"""从 pass (Linux 密码管理器) 获取"""
|
|
try:
|
|
token = subprocess.check_output(
|
|
['pass', pass_path],
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
|
|
if token and token.startswith(('ghp_', 'github_pat_')):
|
|
return token, TokenSource.PASS
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
pass
|
|
return None, None
|
|
|
|
|
|
def get_token_from_dotenv(env_file=".env") -> tuple:
|
|
"""从 .env 文件获取"""
|
|
# 尝试在当前目录和父目录查找 .env
|
|
paths = [
|
|
Path.cwd() / env_file,
|
|
Path.cwd().parent / env_file,
|
|
Path.home() / env_file
|
|
]
|
|
|
|
for path in paths:
|
|
if path.exists():
|
|
try:
|
|
with open(path, 'r') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line.startswith('GITHUB_TOKEN='):
|
|
token = line.split('=', 1)[1].strip('"\'')
|
|
if token and token.startswith(('ghp_', 'github_pat_')):
|
|
return token, f"{TokenSource.DOTENV} ({path})"
|
|
except Exception:
|
|
pass
|
|
|
|
return None, None
|
|
|
|
|
|
def get_token_from_1password(item_name="GitHub Token - SDLC") -> tuple:
|
|
"""从 1Password CLI 获取"""
|
|
try:
|
|
token = subprocess.check_output(
|
|
['op', 'item', 'get', item_name, '--fields', 'token'],
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
|
|
if token and token.startswith(('ghp_', 'github_pat_')):
|
|
return token, TokenSource.ONE_PASSWORD
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
pass
|
|
return None, None
|
|
|
|
|
|
def get_github_token(args=None, verbose=True):
|
|
"""
|
|
按优先级从多个来源获取 GitHub Token
|
|
|
|
优先级顺序:
|
|
1. 命令行参数 --token
|
|
2. 环境变量 GITHUB_TOKEN
|
|
3. GitHub CLI (gh auth token)
|
|
4. macOS Keychain
|
|
5. Linux pass
|
|
6. 1Password CLI
|
|
7. .env 文件
|
|
|
|
返回: (token, source) 或 (None, None)
|
|
"""
|
|
strategies = [
|
|
lambda: get_token_from_arg(args),
|
|
get_token_from_env,
|
|
get_token_from_gh_cli,
|
|
get_token_from_keychain,
|
|
get_token_from_pass,
|
|
get_token_from_1password,
|
|
get_token_from_dotenv,
|
|
]
|
|
|
|
for strategy in strategies:
|
|
token, source = strategy()
|
|
if token:
|
|
if verbose:
|
|
# 只显示 token 的前 10 个字符
|
|
masked_token = f"{token[:10]}...{token[-4:]}" if len(token) > 14 else "***"
|
|
print(f"✅ Token loaded from: {source}", file=sys.stderr)
|
|
print(f" Token: {masked_token}", file=sys.stderr)
|
|
return token, source
|
|
|
|
return None, None
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Get GitHub Token from various sources',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Token sources (in priority order):
|
|
1. Command line argument (--token)
|
|
2. Environment variable (GITHUB_TOKEN)
|
|
3. GitHub CLI (gh auth token)
|
|
4. macOS Keychain
|
|
5. Linux pass
|
|
6. 1Password CLI
|
|
7. .env file
|
|
|
|
Examples:
|
|
# Get token and print to stdout
|
|
python scripts/get-github-token.py
|
|
|
|
# Export to environment variable
|
|
export GITHUB_TOKEN=$(python scripts/get-github-token.py)
|
|
|
|
# Use with other scripts
|
|
python scripts/setup-project-fields.py --token $(python scripts/get-github-token.py)
|
|
|
|
# Provide token via argument
|
|
python scripts/get-github-token.py --token ghp_xxxxx
|
|
"""
|
|
)
|
|
|
|
parser.add_argument('--token', help='GitHub token (highest priority)')
|
|
parser.add_argument('--quiet', '-q', action='store_true',
|
|
help='Suppress status messages (only output token)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# 获取 token
|
|
token, source = get_github_token(args, verbose=not args.quiet)
|
|
|
|
if not token:
|
|
if not args.quiet:
|
|
print("\n❌ Error: GitHub token not found!", file=sys.stderr)
|
|
print("\nTry one of the following methods:", file=sys.stderr)
|
|
print(" 1. Set environment variable:", file=sys.stderr)
|
|
print(" export GITHUB_TOKEN='your_token'", file=sys.stderr)
|
|
print("\n 2. Login with GitHub CLI:", file=sys.stderr)
|
|
print(" gh auth login", file=sys.stderr)
|
|
print("\n 3. Store in macOS Keychain:", file=sys.stderr)
|
|
print(" security add-generic-password \\", file=sys.stderr)
|
|
print(" -s 'GitHub Token - SDLC Management' \\", file=sys.stderr)
|
|
print(" -a $(whoami) \\", file=sys.stderr)
|
|
print(" -w 'your_token'", file=sys.stderr)
|
|
print("\n 4. Create .env file:", file=sys.stderr)
|
|
print(" echo 'GITHUB_TOKEN=your_token' > .env", file=sys.stderr)
|
|
print("\nGet token at: https://github.com/settings/tokens/new", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# 输出 token 到 stdout(用于管道)
|
|
print(token)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|