Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/andyleimc-source_hap-auto-maker.json
T

1 line
488 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "andyleimc-source/hap-auto-maker", "files": {"setup.py": {"a4e0ebaa3014a2ac103a4ad0df481cd1f5474923": {"t": 1785674054.1468544, "n": 17097, "h": "0914769c1be0d562", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 交互式列表选择 ---\n\ndef _select_interactive(items: list, title: str = \"\", current_idx: int = 0) -> int:\n \"\"\"\n 用上/下方向键浏览列表,Enter 确认,数字键直接跳转。\n 返回 0-based 索引。items 为字符串列表。\n \"\"\"\n import tty\n import termios\n\n idx = max(0, min(current_idx, len(items) - 1))\n n = len(items)\n\n def _render(first=False):\n if not first:\n # 上移 n 行,回到行首,清空到屏幕底部\n # raw 模式下 \\n 只是 LF(不含 CR),必须加 \\r 才能回到列 0\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n for i, item in enumerate(items):\n marker = \"▶\" if i == idx else \" \"\n num = str(i + 1)\n line = f\" {marker} {num}. {item}\"\n # raw 模式下必须用 \\r\\n,否则下一行从当前列开始,导致排版错乱\n sys.stdout.write(line + \"\\r\\n\")\n sys.stdout.flush()\n\n if title:\n print(f\"\\n {title}\")\n _render(first=True)\n\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n while True:\n ch = sys.stdin.read(1)\n if ch == '\\x1b':\n # 读取完整转义序列(最多再读 2 个字符)\n seq = sys.stdin.read(1)\n if seq == '[':\n direction = sys.stdin.read(1)\n if direction == 'A': # 上箭头\n idx = (idx - 1) % n\n elif direction == 'B': # 下箭头\n idx = (idx + 1) % n\n # 其他转义序列忽略\n elif ch in ('\\r', '\\n'):\n break\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n elif ch.isdigit() and ch != '0':\n d = int(ch) - 1\n if 0 <= d < n:\n idx = d\n _render()\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n # 确认选中:擦除列表,只显示选中结果\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n sys.stdout.write(f\" ✔ {idx + 1}. {items[idx]}\\r\\n\")\n sys.stdout.flush()\n return idx\n\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n provider_labels = [label for _, label in PROVIDERS]\n default_provider_idx = next(\n (i for i, (key, _) in enumerate(PROVIDERS) if key == old_p),\n 0\n )\n p_idx = _select_interactive(provider_labels, title=\"选择 AI 供应商(↑↓ 移动,Enter 确认,数字键跳转):\", current_idx=default_provider_idx)\n provider, provider_label = PROVIDERS[p_idx]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n # DeepSeek:交互页仅展示推理模型,避免误选 deepseek-chat。\n if provider == \"deepseek\":\n models = [m for m in models if m == \"deepseek-reasoner\"]\n if not models:\n models = [\"deepseek-reasoner\"]\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n default_model_idx = next((i for i, m in enumerate(models) if m == old_model), 0)\n print(f\"\\n 可用模型(共 {len(models)} 个):\")\n m_idx = _select_interactive(models, current_idx=default_model_idx)\n selected_model = models[m_idx]\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "f634377dd39dc4f71e68f94be35259c75fc35751": {"t": 1785773585.2955513, "n": 17097, "h": "0914769c1be0d562", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 交互式列表选择 ---\n\ndef _select_interactive(items: list, title: str = \"\", current_idx: int = 0) -> int:\n \"\"\"\n 用上/下方向键浏览列表,Enter 确认,数字键直接跳转。\n 返回 0-based 索引。items 为字符串列表。\n \"\"\"\n import tty\n import termios\n\n idx = max(0, min(current_idx, len(items) - 1))\n n = len(items)\n\n def _render(first=False):\n if not first:\n # 上移 n 行,回到行首,清空到屏幕底部\n # raw 模式下 \\n 只是 LF(不含 CR),必须加 \\r 才能回到列 0\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n for i, item in enumerate(items):\n marker = \"▶\" if i == idx else \" \"\n num = str(i + 1)\n line = f\" {marker} {num}. {item}\"\n # raw 模式下必须用 \\r\\n,否则下一行从当前列开始,导致排版错乱\n sys.stdout.write(line + \"\\r\\n\")\n sys.stdout.flush()\n\n if title:\n print(f\"\\n {title}\")\n _render(first=True)\n\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n while True:\n ch = sys.stdin.read(1)\n if ch == '\\x1b':\n # 读取完整转义序列(最多再读 2 个字符)\n seq = sys.stdin.read(1)\n if seq == '[':\n direction = sys.stdin.read(1)\n if direction == 'A': # 上箭头\n idx = (idx - 1) % n\n elif direction == 'B': # 下箭头\n idx = (idx + 1) % n\n # 其他转义序列忽略\n elif ch in ('\\r', '\\n'):\n break\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n elif ch.isdigit() and ch != '0':\n d = int(ch) - 1\n if 0 <= d < n:\n idx = d\n _render()\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n # 确认选中:擦除列表,只显示选中结果\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n sys.stdout.write(f\" ✔ {idx + 1}. {items[idx]}\\r\\n\")\n sys.stdout.flush()\n return idx\n\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n provider_labels = [label for _, label in PROVIDERS]\n default_provider_idx = next(\n (i for i, (key, _) in enumerate(PROVIDERS) if key == old_p),\n 0\n )\n p_idx = _select_interactive(provider_labels, title=\"选择 AI 供应商(↑↓ 移动,Enter 确认,数字键跳转):\", current_idx=default_provider_idx)\n provider, provider_label = PROVIDERS[p_idx]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n # DeepSeek:交互页仅展示推理模型,避免误选 deepseek-chat。\n if provider == \"deepseek\":\n models = [m for m in models if m == \"deepseek-reasoner\"]\n if not models:\n models = [\"deepseek-reasoner\"]\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n default_model_idx = next((i for i, m in enumerate(models) if m == old_model), 0)\n print(f\"\\n 可用模型(共 {len(models)} 个):\")\n m_idx = _select_interactive(models, current_idx=default_model_idx)\n selected_model = models[m_idx]\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "540963226372d36ea7ba785c170d3b8fc4a79617": {"t": 1785773587.9728425, "n": 17031, "h": "b318fff45eadb3f9", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 交互式列表选择 ---\n\ndef _select_interactive(items: list, title: str = \"\", current_idx: int = 0) -> int:\n \"\"\"\n 用上/下方向键浏览列表,Enter 确认,数字键直接跳转。\n 返回 0-based 索引。items 为字符串列表。\n \"\"\"\n import tty\n import termios\n\n idx = max(0, min(current_idx, len(items) - 1))\n n = len(items)\n\n def _render(first=False):\n if not first:\n # 上移 n 行,回到行首,清空到屏幕底部\n # raw 模式下 \\n 只是 LF(不含 CR),必须加 \\r 才能回到列 0\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n for i, item in enumerate(items):\n marker = \"▶\" if i == idx else \" \"\n num = str(i + 1)\n line = f\" {marker} {num}. {item}\"\n # raw 模式下必须用 \\r\\n,否则下一行从当前列开始,导致排版错乱\n sys.stdout.write(line + \"\\r\\n\")\n sys.stdout.flush()\n\n if title:\n print(f\"\\n {title}\")\n _render(first=True)\n\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n while True:\n ch = sys.stdin.read(1)\n if ch == '\\x1b':\n # 读取完整转义序列(最多再读 2 个字符)\n seq = sys.stdin.read(1)\n if seq == '[':\n direction = sys.stdin.read(1)\n if direction == 'A': # 上箭头\n idx = (idx - 1) % n\n elif direction == 'B': # 下箭头\n idx = (idx + 1) % n\n # 其他转义序列忽略\n elif ch in ('\\r', '\\n'):\n break\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n elif ch.isdigit() and ch != '0':\n d = int(ch) - 1\n if 0 <= d < n:\n idx = d\n _render()\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n # 确认选中:擦除列表,只显示选中结果\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n sys.stdout.write(f\" ✔ {idx + 1}. {items[idx]}\\r\\n\")\n sys.stdout.flush()\n return idx\n\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n provider_labels = [label for _, label in PROVIDERS]\n default_provider_idx = next(\n (i for i, (key, _) in enumerate(PROVIDERS) if key == old_p),\n 0\n )\n p_idx = _select_interactive(provider_labels, title=\"选择 AI 供应商(↑↓ 移动,Enter 确认,数字键跳转):\", current_idx=default_provider_idx)\n provider, provider_label = PROVIDERS[p_idx]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n # DeepSeek:隐藏 deepseek-reasoner(运行时自动切换,用户无需手动选择)\n if provider == \"deepseek\":\n models = [m for m in models if m != \"deepseek-reasoner\"]\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n default_model_idx = next((i for i, m in enumerate(models) if m == old_model), 0)\n print(f\"\\n 可用模型(共 {len(models)} 个):\")\n m_idx = _select_interactive(models, current_idx=default_model_idx)\n selected_model = models[m_idx]\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "1a5888791711012fc1a958b770907246748768e2": {"t": 1785773589.8479161, "n": 16869, "h": "5934b4f380fbb53a", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 交互式列表选择 ---\n\ndef _select_interactive(items: list, title: str = \"\", current_idx: int = 0) -> int:\n \"\"\"\n 用上/下方向键浏览列表,Enter 确认,数字键直接跳转。\n 返回 0-based 索引。items 为字符串列表。\n \"\"\"\n import tty\n import termios\n\n idx = max(0, min(current_idx, len(items) - 1))\n n = len(items)\n\n def _render(first=False):\n if not first:\n # 上移 n 行,回到行首,清空到屏幕底部\n # raw 模式下 \\n 只是 LF(不含 CR),必须加 \\r 才能回到列 0\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n for i, item in enumerate(items):\n marker = \"▶\" if i == idx else \" \"\n num = str(i + 1)\n line = f\" {marker} {num}. {item}\"\n # raw 模式下必须用 \\r\\n,否则下一行从当前列开始,导致排版错乱\n sys.stdout.write(line + \"\\r\\n\")\n sys.stdout.flush()\n\n if title:\n print(f\"\\n {title}\")\n _render(first=True)\n\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n while True:\n ch = sys.stdin.read(1)\n if ch == '\\x1b':\n # 读取完整转义序列(最多再读 2 个字符)\n seq = sys.stdin.read(1)\n if seq == '[':\n direction = sys.stdin.read(1)\n if direction == 'A': # 上箭头\n idx = (idx - 1) % n\n elif direction == 'B': # 下箭头\n idx = (idx + 1) % n\n # 其他转义序列忽略\n elif ch in ('\\r', '\\n'):\n break\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n elif ch.isdigit() and ch != '0':\n d = int(ch) - 1\n if 0 <= d < n:\n idx = d\n _render()\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n # 确认选中:擦除列表,只显示选中结果\n sys.stdout.write(f\"\\x1b[{n}A\\r\\x1b[0J\")\n sys.stdout.write(f\" ✔ {idx + 1}. {items[idx]}\\r\\n\")\n sys.stdout.flush()\n return idx\n\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n provider_labels = [label for _, label in PROVIDERS]\n default_provider_idx = next(\n (i for i, (key, _) in enumerate(PROVIDERS) if key == old_p),\n 0\n )\n p_idx = _select_interactive(provider_labels, title=\"选择 AI 供应商(↑↓ 移动,Enter 确认,数字键跳转):\", current_idx=default_provider_idx)\n provider, provider_label = PROVIDERS[p_idx]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n default_model_idx = next((i for i, m in enumerate(models) if m == old_model), 0)\n print(f\"\\n 可用模型(共 {len(models)} 个):\")\n m_idx = _select_interactive(models, current_idx=default_model_idx)\n selected_model = models[m_idx]\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "6d5ce92af38abb6f1ae3be020bf4d5601f3b107a": {"t": 1785773591.055964, "n": 16756, "h": "7a4d39f8d450ab10", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 交互式列表选择 ---\n\ndef _select_interactive(items: list, title: str = \"\", current_idx: int = 0) -> int:\n \"\"\"\n 用上/下方向键浏览列表,Enter 确认,数字键直接跳转。\n 返回 0-based 索引。items 为字符串列表。\n \"\"\"\n import tty\n import termios\n\n idx = max(0, min(current_idx, len(items) - 1))\n n = len(items)\n\n def _render(first=False):\n if not first:\n # 移到渲染区起点(上移 n 行)并清空到屏幕底部\n sys.stdout.write(f\"\\x1b[{n}A\\x1b[0J\")\n for i, item in enumerate(items):\n marker = \"▶\" if i == idx else \" \"\n num = str(i + 1)\n line = f\" {marker} {num}. {item}\"\n sys.stdout.write(line + \"\\n\")\n sys.stdout.flush()\n\n if title:\n print(f\"\\n {title}\")\n _render(first=True)\n\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n while True:\n ch = sys.stdin.read(1)\n if ch == '\\x1b':\n # 读取完整转义序列(最多再读 2 个字符)\n seq = sys.stdin.read(1)\n if seq == '[':\n direction = sys.stdin.read(1)\n if direction == 'A': # 上箭头\n idx = (idx - 1) % n\n elif direction == 'B': # 下箭头\n idx = (idx + 1) % n\n # 其他转义序列忽略\n elif ch in ('\\r', '\\n'):\n break\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n elif ch.isdigit() and ch != '0':\n d = int(ch) - 1\n if 0 <= d < n:\n idx = d\n _render()\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n # 确认选中:显示结果行\n sys.stdout.write(f\"\\x1b[{n}A\\x1b[0J\")\n sys.stdout.write(f\" ✔ {idx + 1}. {items[idx]}\\n\")\n sys.stdout.flush()\n return idx\n\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n provider_labels = [label for _, label in PROVIDERS]\n default_provider_idx = next(\n (i for i, (key, _) in enumerate(PROVIDERS) if key == old_p),\n 0\n )\n p_idx = _select_interactive(provider_labels, title=\"选择 AI 供应商(↑↓ 移动,Enter 确认,数字键跳转):\", current_idx=default_provider_idx)\n provider, provider_label = PROVIDERS[p_idx]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n default_model_idx = next((i for i, m in enumerate(models) if m == old_model), 0)\n print(f\"\\n 可用模型(共 {len(models)} 个):\")\n m_idx = _select_interactive(models, current_idx=default_model_idx)\n selected_model = models[m_idx]\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "f833417af878b8614b1737a5bdd4fd330c548870": {"t": 1785773592.2409556, "n": 15424, "h": "368850f5dd809e6b", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符,再用 sys.stdin.readline() 读取输入。\n # 不能用 input(prompt_str):readline 用字节数计算 CJK 光标位置,导致错位乱码。\n # 不能用 print()+input():input() 无参数仍调用 readline,方向键/Ctrl+Z 会产生\n # 转义序列 ^[[B / ^Z 回显到输入行。\n # sys.stdin.readline() 完全绕过 readline,使用终端 cooked mode 读取,无上述问题。\n print(prompt_str, end='', flush=True)\n val = sys.stdin.readline().rstrip('\\n')\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n print(\"\\n 可用 AI 供应商:\")\n for i, (key, label) in enumerate(PROVIDERS, 1):\n marker = \" [当前]\" if key == old_p else \"\"\n print(f\" {i}. {label}{marker}\")\n\n valid_choices = [str(i) for i in range(1, len(PROVIDERS) + 1)]\n default_choice = next(\n (str(i) for i, (key, _) in enumerate(PROVIDERS, 1) if key == old_p),\n \"1\"\n )\n p_choice = ask(\n \"选择 AI 平台 (输入编号)\",\n default=default_choice,\n required=True,\n choices=valid_choices,\n )\n provider, provider_label = PROVIDERS[int(p_choice) - 1]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n print(f\"\\n 可用模型 ({len(models)} 个):\")\n for i, m in enumerate(models, 1):\n print(f\" {i}. {m}\")\n model_input = ask(\n \"选择模型 (输入编号或模型名)\",\n default=\"1\",\n required=True,\n )\n if model_input.isdigit():\n idx = int(model_input) - 1\n if 0 <= idx < len(models):\n selected_model = models[idx]\n else:\n print(f\" ⚠️ 编号超出范围,请手动输入模型名。\")\n selected_model = ask(\"模型名称\", default=\"\", required=True)\n else:\n selected_model = model_input.strip()\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try:\n import openai # noqa: F401\n except ImportError:\n print(\" 📦 正在安装缺少的依赖 openai...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"--break-system-packages\", \"openai\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "b0413448fcfb3d7feccdce500918b9cb5f54d17d": {"t": 1785773593.648521, "n": 15140, "h": "a86accb127b4d944", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n # 用 print 显示提示符(正确处理 CJK 双宽字符),再用 input() 读取输入\n # 若直接 input(prompt_str),readline 用字节数计算光标位置,CJK 字符\n # 每个占 3 字节但显示 2 列,导致光标错位、乱码、无法输入。\n print(prompt_str, end='', flush=True)\n val = input()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n print(\"\\n 可用 AI 供应商:\")\n for i, (key, label) in enumerate(PROVIDERS, 1):\n marker = \" [当前]\" if key == old_p else \"\"\n print(f\" {i}. {label}{marker}\")\n\n valid_choices = [str(i) for i in range(1, len(PROVIDERS) + 1)]\n default_choice = next(\n (str(i) for i, (key, _) in enumerate(PROVIDERS, 1) if key == old_p),\n \"1\"\n )\n p_choice = ask(\n \"选择 AI 平台 (输入编号)\",\n default=default_choice,\n required=True,\n choices=valid_choices,\n )\n provider, provider_label = PROVIDERS[int(p_choice) - 1]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n print(f\"\\n 可用模型 ({len(models)} 个):\")\n for i, m in enumerate(models, 1):\n print(f\" {i}. {m}\")\n model_input = ask(\n \"选择模型 (输入编号或模型名)\",\n default=\"1\",\n required=True,\n )\n if model_input.isdigit():\n idx = int(model_input) - 1\n if 0 <= idx < len(models):\n selected_model = models[idx]\n else:\n print(f\" ⚠️ 编号超出范围,请手动输入模型名。\")\n selected_model = ask(\"模型名称\", default=\"\", required=True)\n else:\n selected_model = model_input.strip()\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: pass\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "fa50c7756ece7718d423900d8099dc98c6655530": {"t": 1785773594.8655639, "n": 14940, "h": "c19d25978921db1b", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH,\n load_ai_config, list_models,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n print(\"\\n 可用 AI 供应商:\")\n for i, (key, label) in enumerate(PROVIDERS, 1):\n marker = \" [当前]\" if key == old_p else \"\"\n print(f\" {i}. {label}{marker}\")\n\n valid_choices = [str(i) for i in range(1, len(PROVIDERS) + 1)]\n default_choice = next(\n (str(i) for i, (key, _) in enumerate(PROVIDERS, 1) if key == old_p),\n \"1\"\n )\n p_choice = ask(\n \"选择 AI 平台 (输入编号)\",\n default=default_choice,\n required=True,\n choices=valid_choices,\n )\n provider, provider_label = PROVIDERS[int(p_choice) - 1]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n print(f\"\\n 可用模型 ({len(models)} 个):\")\n for i, m in enumerate(models, 1):\n print(f\" {i}. {m}\")\n model_input = ask(\n \"选择模型 (输入编号或模型名)\",\n default=\"1\",\n required=True,\n )\n if model_input.isdigit():\n idx = int(model_input) - 1\n if 0 <= idx < len(models):\n selected_model = models[idx]\n else:\n print(f\" ⚠️ 编号超出范围,请手动输入模型名。\")\n selected_model = ask(\"模型名称\", default=\"\", required=True)\n else:\n selected_model = model_input.strip()\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: pass\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "6f94f609b96b4968cefc9f25cdfc29095083c502": {"t": 1785773596.187514, "n": 14980, "h": "5a6ded21955bcd27", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH, PROVIDER_BASE_URLS,\n load_ai_config, list_models, normalize_provider,\n default_base_url_for_provider)\n existing = {}\n try:\n existing = load_ai_config()\n except Exception:\n pass\n\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n\n # 供应商菜单\n PROVIDERS = [\n (\"gemini\", \"Gemini (Google)\"),\n (\"deepseek\", \"DeepSeek\"),\n (\"minimax\", \"MiniMax\"),\n (\"kimi\", \"Kimi (Moonshot)\"),\n (\"zhipu\", \"智谱 GLM\"),\n (\"doubao\", \"豆包 (Doubao/Volcengine)\"),\n (\"qwen\", \"千问 (Qwen/Alibaba)\"),\n ]\n old_p = existing.get(\"provider\", \"\")\n print(\"\\n 可用 AI 供应商:\")\n for i, (key, label) in enumerate(PROVIDERS, 1):\n marker = \" [当前]\" if key == old_p else \"\"\n print(f\" {i}. {label}{marker}\")\n\n valid_choices = [str(i) for i in range(1, len(PROVIDERS) + 1)]\n default_choice = next(\n (str(i) for i, (key, _) in enumerate(PROVIDERS, 1) if key == old_p),\n \"1\"\n )\n p_choice = ask(\n \"选择 AI 平台 (输入编号)\",\n default=default_choice,\n required=True,\n choices=valid_choices,\n )\n provider, provider_label = PROVIDERS[int(p_choice) - 1]\n provider_changed = provider != old_p\n\n # API Key\n existing_key = existing.get(\"api_key\", \"\")\n # 仅对 Gemini/DeepSeek 做 key 格式校验\n key_mismatch = (\n (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n )\n key = ask(\n f\"{provider_label} API Key\",\n default=\"\" if (provider_changed or key_mismatch) else existing_key,\n required=True,\n )\n\n # 确定 base_url\n base_url = default_base_url_for_provider(provider)\n\n # 拉取可用模型列表\n print(f\"\\n 正在从 {provider_label} API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n print(f\"\\n 可用模型 ({len(models)} 个):\")\n for i, m in enumerate(models, 1):\n print(f\" {i}. {m}\")\n model_input = ask(\n \"选择模型 (输入编号或模型名)\",\n default=\"1\",\n required=True,\n )\n if model_input.isdigit():\n idx = int(model_input) - 1\n if 0 <= idx < len(models):\n selected_model = models[idx]\n else:\n print(f\" ⚠️ 编号超出范围,请手动输入模型名。\")\n selected_model = ask(\"模型名称\", default=\"\", required=True)\n else:\n selected_model = model_input.strip()\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", \"\") if not provider_changed else \"\"\n selected_model = ask(\"模型名称\", default=old_model, required=True)\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(\n json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\"\n )\n # 向后兼容:Gemini 同步写入 gemini_auth.json\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2),\n encoding=\"utf-8\",\n )\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider_label}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: pass\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "d8e4f0a72c0854ed53073477794bd7ac230e2d10": {"t": 1785773597.5921013, "n": 14799, "h": "30ed49774b647f67", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import (AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL,\n DEFAULT_GEMINI_MODEL, DEFAULT_DEEPSEEK_MODEL,\n load_ai_config, list_models, normalize_provider)\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n provider_changed = provider != old_p\n # 检查已存 key 是否属于当前 provider(格式不匹配则视为错误 key,强制重填)\n existing_key = existing.get(\"api_key\", \"\")\n key_mismatch = (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or \\\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n key = ask(f\"{provider.title()} API Key\", default=\"\" if (provider_changed or key_mismatch) else existing_key, required=True)\n\n # 根据供应商确定默认模型和 base_url\n default_model = DEFAULT_GEMINI_MODEL if provider == \"gemini\" else DEFAULT_DEEPSEEK_MODEL\n base_url = DEFAULT_DEEPSEEK_BASE_URL if provider == \"deepseek\" else \"\"\n\n # 拉取可用模型列表供用户选择\n print(\"\\n 正在从 API 拉取可用模型列表...\")\n models = list_models(provider, key, base_url)\n\n if models:\n # 找到默认模型在列表中的位置\n default_idx = None\n for i, m in enumerate(models):\n if m == default_model:\n default_idx = i + 1\n break\n print(f\"\\n 可用模型 ({len(models)} 个):\")\n for i, m in enumerate(models):\n marker = \" [推荐]\" if m == default_model else \"\"\n print(f\" {i+1}. {m}{marker}\")\n hint = f\"默认: {default_model}\" if default_idx else f\"推荐: {default_model}\"\n model_choice = ask(\n f\"选择模型 (输入编号或模型名)\",\n default=str(default_idx) if default_idx else default_model,\n required=True,\n hint=hint,\n )\n # 解析用户输入:编号或模型名\n if model_choice.isdigit():\n idx = int(model_choice) - 1\n if 0 <= idx < len(models):\n selected_model = models[idx]\n else:\n print(f\" ⚠️ 编号超出范围,使用默认模型: {default_model}\")\n selected_model = default_model\n else:\n selected_model = model_choice.strip()\n else:\n print(\" ⚠️ 无法拉取模型列表,请手动输入模型名称。\")\n old_model = existing.get(\"model\", default_model) if not provider_changed else default_model\n selected_model = ask(\"模型名称\", default=old_model, required=True, hint=f\"推荐: {default_model}\")\n\n data = {\n \"provider\": provider,\n \"api_key\": key,\n \"model\": selected_model,\n \"base_url\": base_url,\n }\n\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\":\n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": selected_model}, indent=2), encoding=\"utf-8\")\n print(f\"\\n ✔ AI 平台配置已完成 (供应商: {provider}, 模型: {selected_model})。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: pass\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "96c12ff88e22008da8e43fcb002ebfab58eae9ca": {"t": 1785773599.8945532, "n": 13449, "h": "93025edf967a5727", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config, TIER_MODELS\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n provider_changed = provider != old_p\n # 检查已存 key 是否属于当前 provider(格式不匹配则视为错误 key,强制重填)\n existing_key = existing.get(\"api_key\", \"\")\n key_mismatch = (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or \\\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n key = ask(f\"{provider.title()} API Key\", default=\"\" if (provider_changed or key_mismatch) else existing_key, required=True)\n \n # 根据新架构,不再让用户手动选择模型,而是展示档位映射\n print(\"\\n 已启用模型档位自动适配 (Tier Mapping):\")\n tiers = TIER_MODELS.get(provider, {})\n print(f\" - 🚀 FAST (极速档): {ljust_cjk(tiers.get('fast', 'N/A'), 20)}\")\n print(f\" - 🧠 REASONING (推理档): {ljust_cjk(tiers.get('reasoning', 'N/A'), 20)}\")\n \n data = {\n \"provider\": provider, \n \"api_key\": key, \n \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"\n }\n # 保持兼容性,默认写入 fast 档位模型作为备选\n data[\"model\"] = tiers.get(\"fast\", \"\")\n \n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": \n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": data[\"model\"]}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已完成 (已根据厂商自动适配模型档位)。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: pass\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "7d9cbce0d08d8291aeec2e3a0da3d4b269c458bd": {"t": 1785773601.401303, "n": 13470, "h": "6e675d3c8e5b2732", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config, TIER_MODELS\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n provider_changed = provider != old_p\n # 检查已存 key 是否属于当前 provider(格式不匹配则视为错误 key,强制重填)\n existing_key = existing.get(\"api_key\", \"\")\n key_mismatch = (provider == \"gemini\" and existing_key.startswith(\"sk-\")) or \\\n (provider == \"deepseek\" and existing_key.startswith(\"AIza\"))\n key = ask(f\"{provider.title()} API Key\", default=\"\" if (provider_changed or key_mismatch) else existing_key, required=True)\n \n # 根据新架构,不再让用户手动选择模型,而是展示档位映射\n print(\"\\n 已启用模型档位自动适配 (Tier Mapping):\")\n tiers = TIER_MODELS.get(provider, {})\n print(f\" - 🚀 FAST (极速档): {ljust_cjk(tiers.get('fast', 'N/A'), 20)}\")\n print(f\" - 🧠 REASONING (推理档): {ljust_cjk(tiers.get('reasoning', 'N/A'), 20)}\")\n \n data = {\n \"provider\": provider, \n \"api_key\": key, \n \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"\n }\n # 保持兼容性,默认写入 fast 档位模型作为备选\n data[\"model\"] = tiers.get(\"fast\", \"\")\n \n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": \n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": data[\"model\"]}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已完成 (已根据厂商自动适配模型档位)。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "1395576168eb69edb8a51cee46c5c289d2cf05cb": {"t": 1785773602.785938, "n": 13206, "h": "f4e2ff70694b81a5", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config, TIER_MODELS\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n provider_changed = provider != old_p\n key = ask(f\"{provider.title()} API Key\", default=\"\" if provider_changed else existing.get(\"api_key\", \"\"), required=True)\n \n # 根据新架构,不再让用户手动选择模型,而是展示档位映射\n print(\"\\n 已启用模型档位自动适配 (Tier Mapping):\")\n tiers = TIER_MODELS.get(provider, {})\n print(f\" - 🚀 FAST (极速档): {ljust_cjk(tiers.get('fast', 'N/A'), 20)}\")\n print(f\" - 🧠 REASONING (推理档): {ljust_cjk(tiers.get('reasoning', 'N/A'), 20)}\")\n \n data = {\n \"provider\": provider, \n \"api_key\": key, \n \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"\n }\n # 保持兼容性,默认写入 fast 档位模型作为备选\n data[\"model\"] = tiers.get(\"fast\", \"\")\n \n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": \n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": data[\"model\"]}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已完成 (已根据厂商自动适配模型档位)。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "5672455811aeb141aa1d23e1f3a08196bca665b7": {"t": 1785773605.8960347, "n": 13137, "h": "e3b065041f74223b", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config, TIER_MODELS\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n key = ask(f\"{provider.title()} API Key\", default=existing.get(\"api_key\", \"\"), required=True)\n \n # 根据新架构,不再让用户手动选择模型,而是展示档位映射\n print(\"\\n 已启用模型档位自动适配 (Tier Mapping):\")\n tiers = TIER_MODELS.get(provider, {})\n print(f\" - 🚀 FAST (极速档): {ljust_cjk(tiers.get('fast', 'N/A'), 20)}\")\n print(f\" - 🧠 REASONING (推理档): {ljust_cjk(tiers.get('reasoning', 'N/A'), 20)}\")\n \n data = {\n \"provider\": provider, \n \"api_key\": key, \n \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"\n }\n # 保持兼容性,默认写入 fast 档位模型作为备选\n data[\"model\"] = tiers.get(\"fast\", \"\")\n \n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": \n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": data[\"model\"]}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已完成 (已根据厂商自动适配模型档位)。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\")])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"--break-system-packages\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "a671d2465d3814427685b7e83ff8883ea280d3bd": {"t": 1785773607.671239, "n": 13124, "h": "b60d3ea27abff301", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config, TIER_MODELS\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n key = ask(f\"{provider.title()} API Key\", default=existing.get(\"api_key\", \"\"), required=True)\n \n # 根据新架构,不再让用户手动选择模型,而是展示档位映射\n print(\"\\n 已启用模型档位自动适配 (Tier Mapping):\")\n tiers = TIER_MODELS.get(provider, {})\n print(f\" - 🚀 FAST (极速档): {ljust_cjk(tiers.get('fast', 'N/A'), 20)}\")\n print(f\" - 🧠 REASONING (推理档): {ljust_cjk(tiers.get('reasoning', 'N/A'), 20)}\")\n \n data = {\n \"provider\": provider, \n \"api_key\": key, \n \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"\n }\n # 保持兼容性,默认写入 fast 档位模型作为备选\n data[\"model\"] = tiers.get(\"fast\", \"\")\n \n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": \n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": data[\"model\"]}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已完成 (已根据厂商自动适配模型档位)。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "1a707543e8628f2b8370deae89caf98b20f3744e": {"t": 1785773608.8495135, "n": 13186, "h": "c37be3e78ab7f261", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"deepseek\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"1\" if old_p==\"gemini\" else \"2\", required=True, hint=\"Gemini\" if old_p==\"gemini\" else \"DeepSeek\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n key = ask(f\"{provider.title()} API Key\", default=existing.get(\"api_key\", \"\"), required=True)\n opts = {\"gemini\": {\"1\": (\"gemini-2.0-flash\", \"响应极快\"), \"2\": (\"gemini-2.0-pro-exp-02-05\", \"逻辑顶尖\")},\n \"deepseek\": {\"1\": (\"deepseek-chat\", \"通用对话\"), \"2\": (\"deepseek-reasoner\", \"深度推理\")}}[provider]\n print(\"\\n 可用模型清单:\")\n for k, v in opts.items(): print(f\" {k}. {ljust_cjk(v[0], 25)} -> {v[1]}\")\n m_choice = ask(\"请选择模型序号\", default=\"1\", required=True, hint=existing.get(\"model\", \"\") if provider==old_p else \"\", choices=list(opts.keys()))\n model = opts.get(m_choice, opts[\"1\"])[0]\n data = {\"provider\": provider, \"api_key\": key, \"model\": model, \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"}\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": model}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已保存。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "4611fafbe6578740d464898fb1f73b3bed496892": {"t": 1785773610.1808598, "n": 13188, "h": "c8586e76e6292159", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False, choices: list = None) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n \n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n \n if choices and final not in choices:\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:无效输入,请从 {choices} 中选择。\")\n continue\n \n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"gemini\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"2\" if old_p==\"deepseek\" else \"1\", required=True, hint=\"DeepSeek\" if old_p==\"deepseek\" else \"Gemini\", choices=[\"1\", \"2\"])\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n key = ask(f\"{provider.title()} API Key\", default=existing.get(\"api_key\", \"\"), required=True)\n opts = {\"gemini\": {\"1\": (\"gemini-2.0-flash\", \"响应极快\"), \"2\": (\"gemini-2.0-pro-exp-02-05\", \"逻辑顶尖\")},\n \"deepseek\": {\"1\": (\"deepseek-chat\", \"通用对话\"), \"2\": (\"deepseek-reasoner\", \"深度推理\")}}[provider]\n print(\"\\n 可用模型清单:\")\n for k, v in opts.items(): print(f\" {k}. {ljust_cjk(v[0], 25)} -> {v[1]}\")\n m_choice = ask(\"请选择模型序号\", default=\"1\", required=True, hint=existing.get(\"model\", \"\") if provider==old_p else \"\", choices=list(opts.keys()))\n model = opts.get(m_choice, opts[\"1\"])[0]\n data = {\"provider\": provider, \"api_key\": key, \"model\": model, \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"}\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": model}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已保存。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n \n valid_indices = [str(i) for i in range(1, len(groups) + 1)]\n choice_list = valid_indices + [\"n\", \"N\"]\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname, choices=choice_list)\n \n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "536b29f19f702c6ab08c925742192abb4e022383": {"t": 1785773611.7828386, "n": 12784, "h": "c29dbfad25e5975a", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 初始化工具 (稳健修复版)\n=====================================\n主要改进:\n1. 修复 f-string 中海象运算符导致的 NameError。\n2. 保持 CJK 宽度感知对齐算法。\n3. 保持密码实时遮码输入 (*)。\n4. 保持账号密码记忆与脱敏显示。\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport os\nimport re\nimport argparse\nimport unicodedata\nimport tty\nimport termios\nfrom pathlib import Path\n\n# 路径常量\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\", \"YOUR_HAP_SECRET_KEY\", \"YOUR_HAP_PROJECT_ID\", \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\", \"YOUR_GEMINI_MODEL\", \"YOUR_DEEPSEEK_API_KEY\", \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\", \"your-password\", \"OPTIONAL_PRECOMPUTED_SIGN\", \"\", None\n}\n\n# --- 核心对齐算法 ---\n\ndef get_display_width(s):\n width = 0\n for char in s:\n if unicodedata.east_asian_width(char) in ('W', 'F'): width += 2\n else: width += 1\n return width\n\ndef ljust_cjk(s, width):\n return s + ' ' * max(0, width - get_display_width(s))\n\ndef rjust_cjk(s, width):\n return ' ' * max(0, width - get_display_width(s)) + s\n\ndef center_cjk(s, width):\n pad = max(0, width - get_display_width(s))\n left = pad // 2\n return ' ' * left + s + ' ' * (pad - left)\n\n# --- 视觉辅助 ---\n\ndef print_box(title: str):\n width = 66\n print(\"\\n┌\" + \"─\" * (width-2) + \"┐\")\n print(f\"│{center_cjk(title, width-2)}│\")\n print(\"└\" + \"─\" * (width-2) + \"┘\")\n\ndef show_banner():\n print(\"\\n\" + \"═\" * 66)\n print(center_cjk(\"🚀 HAP Auto 自动化环境配置中心\", 66))\n print(\"═\" * 66)\n print(\"\\n💡 指令速查 (Command Cheat Sheet):\")\n cmds = [\n (\"python3 setup.py\", \"引导式全量安装 (向导模式)\"),\n (\"python3 setup.py --menu\", \"管理模式 (查看状态 & 增量修改)\"),\n (\"python3 setup.py --init\", \"彻底重置 (清空配置并从头开始)\")\n ]\n for cmd, desc in cmds:\n print(f\" {ljust_cjk(cmd, 25)} -> {desc}\")\n print(\"-\" * 66)\n\ndef show_footer():\n width = 66\n print(\"\\n\" + \"╔\" + \"═\" * (width-2) + \"╗\")\n print(\"║\" + center_cjk(\"✨ 配置引导已圆满完成!\", width-2) + \"║\")\n print(\"╠\" + \"═\" * (width-2) + \"╣\")\n print(f\"║ {ljust_cjk('💡 修改或检查: python3 setup.py --menu', width-4)} ║\")\n print(f\"║ {ljust_cjk('🔥 彻底清空重置: python3 setup.py --init', width-4)} ║\")\n print(f\"║ {ljust_cjk('🚀 启动主流程: python3 scripts/run_app_pipeline.py', width-4)} ║\")\n print(\"╚\" + \"═\" * (width-2) + \"╝\\n\")\n\n# --- 工具函数 ---\n\ndef _mask(val: str, show: int = 4) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"未填写\"\n return s[:show] + \"****\" if len(s) > show else s\n\ndef _truncate(val: str, max_len: int = 20) -> str:\n s = str(val or \"\").strip()\n if not s or s in _PLACEHOLDERS or \"YOUR_\" in s: return \"\"\n return s[:max_len-3] + \"...\" if len(s) > max_len else s\n\ndef _is_valid(val: str) -> bool:\n s = str(val or \"\").strip()\n return bool(s) and s not in _PLACEHOLDERS and \"YOUR_\" not in s\n\ndef _load_json(path: Path) -> dict:\n if not path.exists(): return {}\n try: return json.loads(path.read_text(encoding=\"utf-8\"))\n except: return {}\n\ndef get_password_masked(prompt):\n \"\"\"实时遮码输入 (*)\"\"\"\n print(prompt, end='', flush=True)\n password = \"\"\n while True:\n fd = sys.stdin.fileno()\n old_settings = termios.tcgetattr(fd)\n try:\n tty.setraw(fd)\n ch = sys.stdin.read(1)\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n \n if ch in ('\\r', '\\n'):\n print()\n break\n elif ch == '\\x7f': # Backspace\n if len(password) > 0:\n password = password[:-1]\n sys.stdout.write('\\b \\b')\n sys.stdout.flush()\n elif ch == '\\x03': # Ctrl+C\n raise KeyboardInterrupt\n else:\n password += ch\n sys.stdout.write('*')\n sys.stdout.flush()\n return password\n\ndef ask(label: str, default: str = \"\", required: bool = False, hint: str = \"\", is_pwd: bool = False) -> str:\n prefix = \"[必填]\" if required else \"[可选]\"\n clean_def = str(default).strip()\n is_placeholder = clean_def in _PLACEHOLDERS or \"YOUR_\" in clean_def\n \n # 确定显示值\n display_hint = hint if hint else (_mask(clean_def) if (len(clean_def) > 15 or required) else clean_def)\n hint_str = f\"(当前: {display_hint})\" if (display_hint and not is_placeholder) else \"\"\n \n # 修复对齐:先计算再组合\n p_prefix = ljust_cjk(prefix, 8)\n p_label = ljust_cjk(label, 32)\n p_hint = rjust_cjk(hint_str, 22)\n prompt_str = f\" {p_prefix}{p_label}{p_hint} : \"\n \n while True:\n if is_pwd:\n val = get_password_masked(prompt_str).strip()\n else:\n val = input(prompt_str).strip()\n val = val.strip(\"\\\"'\").strip(\"\\u200b\\u200c\\u200d\\ufeff\")\n \n final = val if val else (default if not is_placeholder else \"\")\n if required and not _is_valid(final):\n print(f\" {ljust_cjk('', 40)} ⚠️ 错误:此项必填。\")\n continue\n return final\n\n# --- 状态检测 ---\n\ndef get_existing_login():\n path = CRED_DIR / \"login_credentials.py\"\n if path.exists():\n text = path.read_text(encoding=\"utf-8\")\n acc = re.search(r'^LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text, re.M)\n pwd = re.search(r'^LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text, re.M)\n return (acc.group(1) if acc else \"\", pwd.group(1) if pwd else \"\")\n return (\"\", \"\")\n\ndef get_status_ai():\n try:\n from ai_utils import AI_CONFIG_PATH\n conf = _load_json(AI_CONFIG_PATH)\n if _is_valid(conf.get(\"api_key\")):\n return f\"✅ 已配置 ({conf.get('provider','').title()}: {_truncate(conf.get('model',''), 15)})\"\n except: pass\n return \"❌ 待配置\"\n\ndef get_status_hap():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n ak, sk = _is_valid(conf.get(\"app_key\")), _is_valid(conf.get(\"secret_key\"))\n if ak and sk: return f\"✅ 已配置 (Key: {_mask(conf.get('app_key'))})\"\n if ak or sk: return \"⚠️ 部分配置\"\n return \"❌ 待配置\"\n\ndef get_status_login():\n acc, pwd = get_existing_login()\n if _is_valid(acc) and _is_valid(pwd): return f\"✅ 已配置 ({_truncate(acc)})\"\n return \"❌ 待配置\"\n\ndef get_status_group():\n conf = _load_json(CRED_DIR / \"organization_auth.json\")\n gid = conf.get(\"group_ids\")\n if _is_valid(gid): return f\"✅ 已选中 (ID: {_mask(gid, 5)})\"\n return \"❌ 未选择\"\n\n# --- 步骤执行 ---\n\ndef step_ai(force=True):\n from ai_utils import AI_CONFIG_PATH, DEFAULT_DEEPSEEK_BASE_URL, load_ai_config\n existing = {}\n try: existing = load_ai_config()\n except: pass\n print_box(\"第 1 步:配置 AI 平台 (AI Provider)\")\n old_p = existing.get(\"provider\", \"gemini\")\n p_choice = ask(\"AI 平台 (1=Gemini, 2=DeepSeek)\", default=\"2\" if old_p==\"deepseek\" else \"1\", required=True, hint=\"DeepSeek\" if old_p==\"deepseek\" else \"Gemini\")\n provider = \"deepseek\" if p_choice == \"2\" else \"gemini\"\n key = ask(f\"{provider.title()} API Key\", default=existing.get(\"api_key\", \"\"), required=True)\n opts = {\"gemini\": {\"1\": (\"gemini-2.0-flash\", \"响应极快\"), \"2\": (\"gemini-2.0-pro-exp-02-05\", \"逻辑顶尖\")},\n \"deepseek\": {\"1\": (\"deepseek-chat\", \"通用对话\"), \"2\": (\"deepseek-reasoner\", \"深度推理\")}}[provider]\n print(\"\\n 可用模型清单:\")\n for k, v in opts.items(): print(f\" {k}. {ljust_cjk(v[0], 25)} -> {v[1]}\")\n m_choice = ask(\"请选择模型序号\", default=\"1\", required=True, hint=existing.get(\"model\", \"\") if provider==old_p else \"\")\n model = opts.get(m_choice, opts[\"1\"])[0]\n data = {\"provider\": provider, \"api_key\": key, \"model\": model, \"base_url\": DEFAULT_DEEPSEEK_BASE_URL if provider==\"deepseek\" else \"\"}\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding=\"utf-8\")\n if provider == \"gemini\": (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps({\"api_key\": key, \"model\": model}, indent=2), encoding=\"utf-8\")\n print(\"\\n ✔ AI 平台配置已保存。\")\n\ndef step_org_auth(force=True):\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json(dst)\n print_box(\"第 2 步:配置 HAP 组织密钥 (HAP API)\")\n res = {}\n res[\"app_key\"] = ask(\"App Key\", default=existing.get(\"app_key\",\"\"), required=True)\n res[\"secret_key\"] = ask(\"Secret Key\", default=existing.get(\"secret_key\",\"\"), required=True)\n res[\"project_id\"] = ask(\"组织 Project ID (编号)\", default=existing.get(\"project_id\",\"\"), required=True)\n res[\"owner_id\"] = ask(\"用户 Owner ID (个人编号)\", default=existing.get(\"owner_id\",\"\"), required=True)\n res[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(res, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(\"\\n ✔ HAP 组织密钥已同步。\")\n\ndef step_login_and_auth(force=True):\n login_dst = CRED_DIR / \"login_credentials.py\"\n existing_acc, existing_pwd = get_existing_login()\n print_box(\"第 3 步:配置登录账号 (Login Account)\")\n acc = ask(\"登录邮箱或手机号\", default=existing_acc, required=True)\n pwd = ask(\"登录密码 (明道云密码)\", default=existing_pwd, required=True, is_pwd=True)\n content = f'# -*- coding: utf-8 -*-\\nLOGIN_ACCOUNT = \"{acc}\"\\nLOGIN_PASSWORD = \"{pwd}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n'\n login_dst.write_text(content, encoding=\"utf-8\")\n print(\"\\n ✔ 登录凭据已保存。\")\n print(\" 🔄 正在启动自动登录并同步浏览器 Session...\")\n try:\n subprocess.call([sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"])\n print(\" ✔ Session 认证同步完成。\")\n except Exception as e: print(f\" ⚠️ 同步异常: {e}\")\n\ndef step_group_init():\n print_box(\"第 4 步:选择应用工作分组 (App Group)\")\n if \"✅\" not in get_status_hap():\n print(\" ⚠️ 错误:请先完成第 2 步。\")\n return\n try:\n from list_groups import get_groups\n from local_config import save_local_group_id, load_local_group_id\n from create_group import create_group\n groups = get_groups()\n if not groups:\n print(\"\\n ⚠️ 未发现应用分组。\")\n if ask(\"是否现在创建一个新分组?(y/N)\", default=\"y\", required=True) == \"y\":\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_Test\", required=True)\n gid = create_group(name)\n return\n return\n curr_gid = load_local_group_id()\n curr_gname = next((g['name'] for g in groups if g['groupId'] == curr_gid), \"\")\n print(\"\\n 现有工作分组清单:\")\n for i, g in enumerate(groups, 1): print(f\" {i}. {ljust_cjk(g['name'], 30)}\")\n print(f\" n. {ljust_cjk('[新建应用分组]', 30)}\")\n idx_str = ask(\"请输入分组序号 (n 新建)\", default=\"1\", required=True, hint=curr_gname)\n if idx_str.lower() == 'n':\n name = ask(\"请输入新分组名称\", default=\"AutoHAP_New\", required=True)\n gid = create_group(name)\n return\n idx = int(idx_str) - 1\n if 0 <= idx < len(groups):\n gid = groups[idx]['groupId']\n save_local_group_id(gid)\n print(f\"\\n ✔ 已选中分组: {groups[idx]['name']}\")\n except Exception as e: print(f\" ❌ 分组操作失败: {e}\")\n\n# --- 主逻辑 ---\n\ndef wizard():\n show_banner()\n step_ai(force=True)\n step_org_auth(force=True)\n step_login_and_auth(force=True)\n step_group_init()\n show_footer()\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化工具\")\n parser.add_argument(\"--menu\", action=\"store_true\", help=\"管理模式\")\n parser.add_argument(\"--init\", action=\"store_true\", help=\"清空重置\")\n args = parser.parse_args()\n\n if args.init:\n if input(\"\\n🚨 警告:这会彻底清空所有配置。确认吗?(y/N): \").lower() == 'y':\n for f in [CRED_DIR / \"ai_auth.json\", CRED_DIR / \"organization_auth.json\", CRED_DIR / \"login_credentials.py\", \n CRED_DIR / \"gemini_auth.json\", CRED_DIR / \"auth_config.py\"]:\n if f.exists(): f.unlink()\n print(\" ✔ 已清理。\")\n wizard()\n return\n\n if args.menu:\n show_banner()\n while True:\n print(\"\\n\" + \"╔\" + \"═\" * 64 + \"╗\")\n print(\"║\" + center_cjk(\"⚙️ HAP Auto 配置管理中心 (Management)\", 64) + \"║\")\n print(\"╚\" + \"═\" * 64 + \"╝\")\n print(f\" 1. 修改 AI 平台配置 -> {get_status_ai()}\")\n print(f\" 2. 修改 HAP 组织密钥 -> {get_status_hap()}\")\n print(f\" 3. 修改明道云登录账号 -> {get_status_login()}\")\n print(f\" 4. 重新选择/新建分组 -> {get_status_group()}\")\n print(\" q. 保存并退出管理模式\")\n c = input(\"\\n请选择序号: \").strip().lower()\n if c == '1': step_ai()\n elif c == '2': step_org_auth()\n elif c == '3': step_login_and_auth()\n elif c == '4': step_group_init()\n elif c == 'q': break\n return\n\n wizard()\n\nif __name__ == \"__main__\":\n try:\n try: import requests\n except: subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"requests\", \"openai\", \"playwright\"])\n main()\n except KeyboardInterrupt: print(\"\\n👋 配置流程已中断。\")\n"}, "6350e41a79ae655b4d8f1332cd36e2b707864b3e": {"t": 1785773615.4010248, "n": 18237, "h": "96c2186070757c48", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n python3 setup.py --show # 查看当前配置,并可按提示修改 AI 配置\n\"\"\"\n\nimport argparse\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\nfrom ai_utils import (\n AI_CONFIG_PATH,\n DEFAULT_DEEPSEEK_BASE_URL,\n default_model_for_provider,\n load_ai_config,\n mask_secret,\n)\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"YOUR_DEEPSEEK_API_KEY\",\n \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef _looks_like_prompt_artifact(val: str) -> bool:\n \"\"\"识别误写入配置文件的交互提示文本,避免作为默认值继续回显。\"\"\"\n text = str(val or \"\").strip()\n if not text:\n return False\n markers = (\n \"请选择 AI 平台\",\n \"Deepseek API Key\",\n \"Gemini API Key\",\n \"获取地址:\",\n \"1. Gemini\",\n \"2. DeepSeek\",\n \"[2]: 2\",\n )\n return any(marker in text for marker in markers)\n\n\ndef _ask_with_validator(prompt: str, default: str = \"\", validator=None, error_message: str = \"输入无效,请重试。\") -> str:\n \"\"\"带校验的输入;校验失败时提示并重新输入。\"\"\"\n while True:\n val = ask(prompt, default=default)\n if validator is None or validator(val):\n return val\n print(f\" ⚠ {error_message}\")\n\n\ndef _is_non_artifact_text(val: str) -> bool:\n text = str(val or \"\").strip()\n return bool(text) and not _looks_like_prompt_artifact(text)\n\n\ndef _is_valid_ai_key(provider: str, val: str) -> bool:\n text = str(val or \"\").strip()\n if not text or _looks_like_prompt_artifact(text):\n return False\n if provider == \"deepseek\":\n return text.startswith(\"sk-\")\n return len(text) >= 16\n\n\ndef _is_valid_ai_model_choice(choice: str, model_options: dict) -> bool:\n return str(choice or \"\").strip() in model_options\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"openai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef _write_ai_config(data: dict) -> None:\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n if data.get(\"provider\") == \"gemini\":\n legacy = {\n \"api_key\": data.get(\"api_key\", \"\"),\n \"model\": data.get(\"model\", default_model_for_provider(\"gemini\")),\n }\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps(legacy, indent=2, ensure_ascii=False) + \"\\n\",\n encoding=\"utf-8\",\n )\n\n\ndef _load_existing_ai_config() -> dict:\n try:\n return load_ai_config()\n except Exception:\n return {}\n\n\ndef step_ai(force=False):\n \"\"\"配置统一 AI 平台\"\"\"\n dst = AI_CONFIG_PATH\n existing = _load_existing_ai_config()\n if dst.exists() and not force:\n return\n\n old_provider = existing.get(\"provider\", \"gemini\")\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", default_model_for_provider(old_provider))\n old_base_url = existing.get(\"base_url\", \"\")\n\n print(\"\\n🔑 [2/4] 配置 AI 平台\")\n print(\" 1. Gemini\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n print(\" 2. DeepSeek\")\n print(\" 获取地址: https://platform.deepseek.com/api_keys\")\n\n provider_default = \"1\" if old_provider == \"gemini\" else \"2\"\n provider_choice = _ask_with_validator(\n \" 请选择 AI 平台(1=Gemini, 2=DeepSeek)\",\n default=provider_default,\n validator=lambda x: x in {\"1\", \"2\"},\n error_message=\"请输入 1 或 2。\",\n )\n provider = \"deepseek\" if provider_choice == \"2\" else \"gemini\"\n default_model = old_model if provider == old_provider and old_model else default_model_for_provider(provider)\n\n if old_key and old_key not in _PLACEHOLDERS and provider == old_provider:\n key = _ask_with_validator(\n f\" {provider.title()} API Key [{_mask(old_key)}]\",\n validator=lambda x: _is_valid_ai_key(provider, x) or (x == \"\" and _is_valid_ai_key(provider, old_key)),\n error_message=\"API Key 格式不正确,或输入内容看起来像提示文本。\",\n ) or old_key\n else:\n key = _ask_with_validator(\n f\" 请输入 {provider.title()} API Key\",\n validator=lambda x: _is_valid_ai_key(provider, x),\n error_message=\"API Key 格式不正确,或输入内容看起来像提示文本。\",\n )\n\n if provider == \"gemini\":\n model_options = {\n \"1\": (\"gemini-2.5-flash\", \"响应更快,适合日常生成\"),\n \"2\": (\"gemini-2.5-pro\", \"能力更强,适合复杂任务\"),\n }\n base_url = \"\"\n else:\n model_options = {\n \"1\": (\"deepseek-chat\", \"通用对话模型,速度更快,适合大多数场景\"),\n \"2\": (\"deepseek-reasoner\", \"推理模型,适合复杂分析、多步推导\"),\n }\n base_url = old_base_url or DEFAULT_DEEPSEEK_BASE_URL\n\n print(\" 请选择模型:\")\n for option, (model_name, description) in model_options.items():\n print(f\" {option}. {model_name}:{description}\")\n\n default_model_choice = next(\n (option for option, (model_name, _) in model_options.items() if model_name == default_model),\n \"1\",\n )\n model_choice = _ask_with_validator(\n \" 请输入模型序号\",\n default=default_model_choice,\n validator=lambda x: _is_valid_ai_model_choice(x, model_options),\n error_message=\"请输入上面列出的模型序号。\",\n )\n model = model_options[model_choice][0]\n data = {\n \"provider\": provider,\n \"api_key\": key or (\"YOUR_GEMINI_API_KEY\" if provider == \"gemini\" else \"YOUR_DEEPSEEK_API_KEY\"),\n \"model\": model or default_model_for_provider(provider),\n \"base_url\": base_url,\n }\n _write_ai_config(data)\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n # group_ids 不在此处询问,由 step_group_init() 通过下拉选择写入\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n ]\n\n # 如果已有配置,先展示当前值\n valid_existing = {\n key: (\"\" if _looks_like_prompt_artifact(existing.get(key, \"\")) else existing.get(key, \"\"))\n for key, *_ in fields\n }\n\n if valid_existing and any(valid_existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = valid_existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = valid_existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = _ask_with_validator(\n f\" {name} [{_mask(default)}]\",\n validator=lambda x: x == \"\" or _is_non_artifact_text(x),\n error_message=\"输入内容看起来像提示文本,请重新输入真实值;直接回车可保留当前值。\",\n )\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val\n else:\n results[key] = raw_input\n else:\n results[key] = _ask_with_validator(\n f\" {name}\",\n default=default,\n validator=lambda x: (bool(str(x or \"\").strip()) and not _looks_like_prompt_artifact(x)) or (default and x == default),\n error_message=\"输入内容看起来像提示文本,或为空,请重新输入真实值。\",\n )\n\n # 写入(group_ids 保留已有值,由 step_group_init 下拉选择后覆盖)\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n ai = _load_existing_ai_config()\n result[\"ai_provider\"] = ai.get(\"provider\", \"gemini\")\n result[\"ai_api_key\"] = ai.get(\"api_key\", \"\")\n result[\"ai_model\"] = ai.get(\"model\", default_model_for_provider(result[\"ai_provider\"]))\n result[\"ai_base_url\"] = ai.get(\"base_url\", \"\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef _sync_group_id_to_org_auth(group_id: str) -> None:\n \"\"\"将选中的分组 ID 同步写入 organization_auth.json 的 group_ids 字段。\"\"\"\n org_dst = CRED_DIR / \"organization_auth.json\"\n data = _load_json_safe(org_dst)\n data[\"group_ids\"] = group_id\n org_dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n\n\ndef step_group_init(force=False):\n \"\"\"应用分组初始化引导:选择或新建\"\"\"\n # 导入本地配置处理\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id, save_local_group_id\n from list_groups import get_groups\n except ImportError:\n print(\" ⚠️ 无法加载 HAP 基础模块,跳过分组初始化。\")\n return\n\n # 如果已经有选中分组且不是强制模式,跳过\n current_group_id = load_local_group_id()\n if current_group_id and not force:\n return\n\n print(\"\\n📁 [Extra] 初始化应用工作分组\")\n groups = get_groups()\n if not groups:\n print(\" ⚠️ 未能获取到分组列表,请检查网络或授权。\")\n return\n\n print(\" 请选择一个应用分组:\")\n for i, g in enumerate(groups, 1):\n mark = \" (当前)\" if g[\"groupId\"] == current_group_id else \"\"\n print(f\" {i}. {g['name']}{mark}\")\n print(f\" n. 新建分组\")\n\n choice = ask(\" 请输入序号\", default=\"1\")\n if choice.lower() == \"n\":\n new_name = ask(\" 请输入新分组名称\")\n if new_name:\n try:\n from create_group import create_group\n new_group = create_group(new_name)\n selected_group_id = new_group[\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 分组已创建并选中: {new_name}\")\n except Exception as e:\n print(f\" ❌ 创建分组失败: {e}\")\n else:\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(groups):\n selected_group_id = groups[idx][\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 已选中分组: {groups[idx]['name']}\")\n except (ValueError, IndexError):\n print(\" ⚠️ 无效选择。\")\n\n\ndef show_config():\n \"\"\"查看当前所有配置内容\"\"\"\n conf = _read_all_config()\n print(\"\\n\" + \"=\" * 60)\n print(\"📋 HAP Auto 当前完整配置\")\n print(\"=\" * 60)\n\n print(\"\\n[AI]\")\n print(f\" Provider: {conf['ai_provider']}\")\n print(f\" API Key: {mask_secret(conf['ai_api_key'])}\")\n print(f\" Model: {conf['ai_model']}\")\n print(f\" Base URL: {conf['ai_base_url'] or '(默认)'}\")\n\n print(\"\\n[HAP Organization]\")\n print(f\" AppKey: {_mask(conf['app_key'])}\")\n print(f\" SecretKey: {_mask(conf['secret_key'])}\")\n print(f\" ProjectID: {conf['project_id']}\")\n print(f\" OwnerID: {conf['owner_id']}\")\n print(f\" GroupIDs: {conf['group_ids'] or '(未指定)'}\")\n\n print(\"\\n[Login Credentials]\")\n print(f\" Account: {conf['account']}\")\n print(f\" Password: {_mask(conf['password'])}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"提示:可运行 python3 setup.py --force 重新初始化全部配置\")\n print(\"=\" * 60 + \"\\n\")\n\n if sys.stdin.isatty():\n choice = ask(\"是否立即修改 AI 配置?(y/N)\", default=\"N\").strip().lower()\n if choice in {\"y\", \"yes\"}:\n step_ai(force=True)\n print(\"\\nAI 配置已更新,最新结果如下:\")\n refreshed = _read_all_config()\n print(f\" Provider: {refreshed['ai_provider']}\")\n print(f\" API Key: {mask_secret(refreshed['ai_api_key'])}\")\n print(f\" Model: {refreshed['ai_model']}\")\n print(f\" Base URL: {refreshed['ai_base_url'] or '(默认)'}\")\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化与配置工具\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重跑初始化步骤\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看当前配置,并可修改 AI 配置\")\n args = parser.parse_args()\n\n if args.show:\n show_config()\n return\n\n print(\"\\n\" + \"🚀\" * 20)\n print(\" HAP Auto 自动化环境初始化\")\n print(\"🚀\" * 20)\n\n try:\n step_install_deps()\n step_ai(args.force)\n step_org_auth(args.force)\n step_login_and_auth(args.force)\n step_group_init(args.force)\n\n print(\"\\n\" + \"✨\" * 20)\n print(\" 所有配置已完成!\")\n print(\" 现在你可以运行主流程:python3 scripts/run_app_pipeline.py\")\n print(\"✨\" * 20 + \"\\n\")\n\n except KeyboardInterrupt:\n print(\"\\n\\n👋 已由用户取消。\")\n except Exception as e:\n print(f\"\\n\\n❌ 初始化过程中发生错误: {e}\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "2648506a04ac0d059d27858e31910508368a9fcf": {"t": 1785773616.5766137, "n": 15167, "h": "9b01134e63be36dc", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n python3 setup.py --show # 查看当前配置,并可按提示修改 AI 配置\n\"\"\"\n\nimport argparse\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\nsys.path.insert(0, str(BASE_DIR / \"scripts\" / \"hap\"))\n\nfrom ai_utils import (\n AI_CONFIG_PATH,\n DEFAULT_DEEPSEEK_BASE_URL,\n default_model_for_provider,\n load_ai_config,\n mask_secret,\n)\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"YOUR_DEEPSEEK_API_KEY\",\n \"YOUR_DEEPSEEK_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"openai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef _write_ai_config(data: dict) -> None:\n AI_CONFIG_PATH.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n if data.get(\"provider\") == \"gemini\":\n legacy = {\n \"api_key\": data.get(\"api_key\", \"\"),\n \"model\": data.get(\"model\", default_model_for_provider(\"gemini\")),\n }\n (CRED_DIR / \"gemini_auth.json\").write_text(\n json.dumps(legacy, indent=2, ensure_ascii=False) + \"\\n\",\n encoding=\"utf-8\",\n )\n\n\ndef _load_existing_ai_config() -> dict:\n try:\n return load_ai_config()\n except Exception:\n return {}\n\n\ndef step_ai(force=False):\n \"\"\"配置统一 AI 平台\"\"\"\n dst = AI_CONFIG_PATH\n existing = _load_existing_ai_config()\n if dst.exists() and not force:\n return\n\n old_provider = existing.get(\"provider\", \"gemini\")\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", default_model_for_provider(old_provider))\n old_base_url = existing.get(\"base_url\", \"\")\n\n print(\"\\n🔑 [2/4] 配置 AI 平台\")\n print(\" 1. Gemini\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n print(\" 2. DeepSeek\")\n print(\" 获取地址: https://platform.deepseek.com/api_keys\")\n\n provider_default = \"1\" if old_provider == \"gemini\" else \"2\"\n provider_choice = ask(\" 请选择 AI 平台(1=Gemini, 2=DeepSeek)\", default=provider_default)\n provider = \"deepseek\" if provider_choice == \"2\" else \"gemini\"\n default_model = old_model if provider == old_provider and old_model else default_model_for_provider(provider)\n\n if old_key and old_key not in _PLACEHOLDERS and provider == old_provider:\n key = ask(f\" {provider.title()} API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(f\" 请输入 {provider.title()} API Key\")\n\n if provider == \"gemini\":\n print(\" 推荐模型: gemini-2.5-flash(更快)或 gemini-2.5-pro(更强)\")\n base_url = \"\"\n else:\n print(\" 推荐模型: deepseek-chat(通用)\")\n base_url = old_base_url or DEFAULT_DEEPSEEK_BASE_URL\n\n model = ask(\" 模型名称\", default=default_model)\n data = {\n \"provider\": provider,\n \"api_key\": key or (\"YOUR_GEMINI_API_KEY\" if provider == \"gemini\" else \"YOUR_DEEPSEEK_API_KEY\"),\n \"model\": model or default_model_for_provider(provider),\n \"base_url\": base_url,\n }\n _write_ai_config(data)\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n # group_ids 不在此处询问,由 step_group_init() 通过下拉选择写入\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入(group_ids 保留已有值,由 step_group_init 下拉选择后覆盖)\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n ai = _load_existing_ai_config()\n result[\"ai_provider\"] = ai.get(\"provider\", \"gemini\")\n result[\"ai_api_key\"] = ai.get(\"api_key\", \"\")\n result[\"ai_model\"] = ai.get(\"model\", default_model_for_provider(result[\"ai_provider\"]))\n result[\"ai_base_url\"] = ai.get(\"base_url\", \"\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef _sync_group_id_to_org_auth(group_id: str) -> None:\n \"\"\"将选中的分组 ID 同步写入 organization_auth.json 的 group_ids 字段。\"\"\"\n org_dst = CRED_DIR / \"organization_auth.json\"\n data = _load_json_safe(org_dst)\n data[\"group_ids\"] = group_id\n org_dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n\n\ndef step_group_init(force=False):\n \"\"\"应用分组初始化引导:选择或新建\"\"\"\n # 导入本地配置处理\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id, save_local_group_id\n from list_groups import get_groups\n except ImportError:\n print(\" ⚠️ 无法加载 HAP 基础模块,跳过分组初始化。\")\n return\n\n # 如果已经有选中分组且不是强制模式,跳过\n current_group_id = load_local_group_id()\n if current_group_id and not force:\n return\n\n print(\"\\n📁 [Extra] 初始化应用工作分组\")\n groups = get_groups()\n if not groups:\n print(\" ⚠️ 未能获取到分组列表,请检查网络或授权。\")\n return\n\n print(\" 请选择一个应用分组:\")\n for i, g in enumerate(groups, 1):\n mark = \" (当前)\" if g[\"groupId\"] == current_group_id else \"\"\n print(f\" {i}. {g['name']}{mark}\")\n print(f\" n. 新建分组\")\n\n choice = ask(\" 请输入序号\", default=\"1\")\n if choice.lower() == \"n\":\n new_name = ask(\" 请输入新分组名称\")\n if new_name:\n try:\n from create_group import create_group\n new_group = create_group(new_name)\n selected_group_id = new_group[\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 分组已创建并选中: {new_name}\")\n except Exception as e:\n print(f\" ❌ 创建分组失败: {e}\")\n else:\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(groups):\n selected_group_id = groups[idx][\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 已选中分组: {groups[idx]['name']}\")\n except (ValueError, IndexError):\n print(\" ⚠️ 无效选择。\")\n\n\ndef show_config():\n \"\"\"查看当前所有配置内容\"\"\"\n conf = _read_all_config()\n print(\"\\n\" + \"=\" * 60)\n print(\"📋 HAP Auto 当前完整配置\")\n print(\"=\" * 60)\n\n print(\"\\n[AI]\")\n print(f\" Provider: {conf['ai_provider']}\")\n print(f\" API Key: {mask_secret(conf['ai_api_key'])}\")\n print(f\" Model: {conf['ai_model']}\")\n print(f\" Base URL: {conf['ai_base_url'] or '(默认)'}\")\n\n print(\"\\n[HAP Organization]\")\n print(f\" AppKey: {_mask(conf['app_key'])}\")\n print(f\" SecretKey: {_mask(conf['secret_key'])}\")\n print(f\" ProjectID: {conf['project_id']}\")\n print(f\" OwnerID: {conf['owner_id']}\")\n print(f\" GroupIDs: {conf['group_ids'] or '(未指定)'}\")\n\n print(\"\\n[Login Credentials]\")\n print(f\" Account: {conf['account']}\")\n print(f\" Password: {_mask(conf['password'])}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"提示:可运行 python3 setup.py --force 重新初始化全部配置\")\n print(\"=\" * 60 + \"\\n\")\n\n if sys.stdin.isatty():\n choice = ask(\"是否立即修改 AI 配置?(y/N)\", default=\"N\").strip().lower()\n if choice in {\"y\", \"yes\"}:\n step_ai(force=True)\n print(\"\\nAI 配置已更新,最新结果如下:\")\n refreshed = _read_all_config()\n print(f\" Provider: {refreshed['ai_provider']}\")\n print(f\" API Key: {mask_secret(refreshed['ai_api_key'])}\")\n print(f\" Model: {refreshed['ai_model']}\")\n print(f\" Base URL: {refreshed['ai_base_url'] or '(默认)'}\")\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"HAP Auto 初始化与配置工具\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重跑初始化步骤\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看当前配置,并可修改 AI 配置\")\n args = parser.parse_args()\n\n if args.show:\n show_config()\n return\n\n print(\"\\n\" + \"🚀\" * 20)\n print(\" HAP Auto 自动化环境初始化\")\n print(\"🚀\" * 20)\n\n try:\n step_install_deps()\n step_ai(args.force)\n step_org_auth(args.force)\n step_login_and_auth(args.force)\n step_group_init(args.force)\n\n print(\"\\n\" + \"✨\" * 20)\n print(\" 所有配置已完成!\")\n print(\" 现在你可以运行主流程:python3 scripts/run_app_pipeline.py\")\n print(\"✨\" * 20 + \"\\n\")\n\n except KeyboardInterrupt:\n print(\"\\n\\n👋 已由用户取消。\")\n except Exception as e:\n print(f\"\\n\\n❌ 初始化过程中发生错误: {e}\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "300ebbdaf7b0eac316e320f87b69d85c1049c2f2": {"t": 1785773618.7557702, "n": 12481, "h": "bb32ccfc96cb534a", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n # 默认始终使用 gemini-2.5-flash(推荐,速度更快)\n default_model = \"gemini-2.5-flash\"\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n print(f\" 推荐模型: gemini-2.5-flash(更快)或 gemini-2.5-pro(更强)\")\n model = ask(\" Gemini 模型名称\", default=default_model)\n\n data = {\n \"api_key\": key or \"YOUR_GEMINI_API_KEY\",\n \"model\": model or \"YOUR_GEMINI_MODEL\"\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n # group_ids 不在此处询问,由 step_group_init() 通过下拉选择写入\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入(group_ids 保留已有值,由 step_group_init 下拉选择后覆盖)\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = existing.get(\"group_ids\", \"\")\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-flash\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef _sync_group_id_to_org_auth(group_id: str) -> None:\n \"\"\"将选中的分组 ID 同步写入 organization_auth.json 的 group_ids 字段。\"\"\"\n org_dst = CRED_DIR / \"organization_auth.json\"\n data = _load_json_safe(org_dst)\n data[\"group_ids\"] = group_id\n org_dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n\n\ndef step_group_init(force=False):\n \"\"\"应用分组初始化引导:选择或新建\"\"\"\n # 导入本地配置处理\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id, save_local_group_id\n from list_groups import get_groups\n except ImportError:\n print(\" ⚠️ 无法加载 HAP 基础模块,跳过分组初始化。\")\n return\n\n # 如果已经有选中分组且不是强制模式,跳过\n current_group_id = load_local_group_id()\n if current_group_id and not force:\n return\n\n print(\"\\n📁 [Extra] 初始化应用工作分组\")\n groups = get_groups()\n if not groups:\n print(\" ⚠️ 未能获取到分组列表,请检查网络或授权。\")\n return\n\n print(\" 请选择一个应用分组:\")\n for i, g in enumerate(groups, 1):\n mark = \" (当前)\" if g[\"groupId\"] == current_group_id else \"\"\n print(f\" {i}. {g['name']}{mark}\")\n print(f\" n. 新建分组\")\n\n choice = ask(\" 请输入序号\", default=\"1\")\n if choice.lower() == \"n\":\n new_name = ask(\" 请输入新分组名称\")\n if new_name:\n try:\n from create_group import create_group\n new_group = create_group(new_name)\n selected_group_id = new_group[\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 分组已创建并选中: {new_name}\")\n except Exception as e:\n print(f\" ❌ 创建分组失败: {e}\")\n else:\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(groups):\n selected_group_id = groups[idx][\"groupId\"]\n save_local_group_id(selected_group_id)\n _sync_group_id_to_org_auth(selected_group_id)\n print(f\" ✔ 已选中分组: {groups[idx]['name']}\")\n except (ValueError, IndexError):\n print(\" ⚠️ 无效选择。\")\n\n\ndef show_config():\n \"\"\"查看当前所有配置内容\"\"\"\n conf = _read_all_config()\n print(\"\\n\" + \"=\" * 60)\n print(\"📋 HAP Auto 当前完整配置\")\n print(\"=\" * 60)\n\n print(\"\\n[Gemini AI]\")\n print(f\" API Key: {_mask(conf['gemini_api_key'])}\")\n print(f\" Model: {conf['gemini_model']}\")\n\n print(\"\\n[HAP Organization]\")\n print(f\" AppKey: {_mask(conf['app_key'])}\")\n print(f\" SecretKey: {_mask(conf['secret_key'])}\")\n print(f\" ProjectID: {conf['project_id']}\")\n print(f\" OwnerID: {conf['owner_id']}\")\n print(f\" GroupIDs: {conf['group_ids'] or '(未指定)'}\")\n\n print(\"\\n[Login Credentials]\")\n print(f\" Account: {conf['account']}\")\n print(f\" Password: {_mask(conf['password'])}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"提示:如需修改,请运行 python3 setup.py --force\")\n print(\"=\" * 60 + \"\\n\")\n\n\ndef main():\n # 参数处理\n force = \"--force\" in sys.argv\n show = \"--show\" in sys.argv\n\n if show:\n show_config()\n return\n\n print(\"\\n\" + \"🚀\" * 20)\n print(\" HAP Auto 自动化环境初始化\")\n print(\"🚀\" * 20)\n\n try:\n step_install_deps()\n step_gemini(force)\n step_org_auth(force)\n step_login_and_auth(force)\n step_group_init(force)\n\n print(\"\\n\" + \"✨\" * 20)\n print(\" 所有配置已完成!\")\n print(\" 现在你可以运行主流程:python3 scripts/run_app_pipeline.py\")\n print(\"✨\" * 20 + \"\\n\")\n\n except KeyboardInterrupt:\n print(\"\\n\\n👋 已由用户取消。\")\n except Exception as e:\n print(f\"\\n\\n❌ 初始化过程中发生错误: {e}\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "429eff95548061f6fa902e38a3a18099fde219fa": {"t": 1785773620.0126157, "n": 11848, "h": "ce447c9667133aee", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", \"gemini-2.5-pro\")\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n model = ask(\" 请输入 Gemini 模型名称\", default=old_model)\n\n data = {\n \"api_key\": key or \"YOUR_GEMINI_API_KEY\",\n \"model\": model or \"YOUR_GEMINI_MODEL\"\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-pro\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef step_group_init(force=False):\n \"\"\"应用分组初始化引导:选择或新建\"\"\"\n # 导入本地配置处理\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id, save_local_group_id\n from list_groups import get_groups\n except ImportError:\n print(\" ⚠️ 无法加载 HAP 基础模块,跳过分组初始化。\")\n return\n\n # 如果已经有选中分组且不是强制模式,跳过\n current_group_id = load_local_group_id()\n if current_group_id and not force:\n return\n\n print(\"\\n📁 [Extra] 初始化应用工作分组\")\n groups = get_groups()\n if not groups:\n print(\" ⚠️ 未能获取到分组列表,请检查网络或授权。\")\n return\n\n print(\" 请选择一个应用分组:\")\n for i, g in enumerate(groups, 1):\n mark = \" (当前)\" if g[\"groupId\"] == current_group_id else \"\"\n print(f\" {i}. {g['name']}{mark}\")\n print(f\" n. 新建分组\")\n\n choice = ask(\" 请输入序号\", default=\"1\")\n if choice.lower() == \"n\":\n new_name = ask(\" 请输入新分组名称\")\n if new_name:\n try:\n from create_group import create_group\n new_group = create_group(new_name)\n save_local_group_id(new_group[\"groupId\"])\n print(f\" ✔ 分组已创建并选中: {new_name}\")\n except Exception as e:\n print(f\" ❌ 创建分组失败: {e}\")\n else:\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(groups):\n save_local_group_id(groups[idx][\"groupId\"])\n print(f\" ✔ 已选中分组: {groups[idx]['name']}\")\n except (ValueError, IndexError):\n print(\" ⚠️ 无效选择。\")\n\n\ndef show_config():\n \"\"\"查看当前所有配置内容\"\"\"\n conf = _read_all_config()\n print(\"\\n\" + \"=\" * 60)\n print(\"📋 HAP Auto 当前完整配置\")\n print(\"=\" * 60)\n\n print(\"\\n[Gemini AI]\")\n print(f\" API Key: {_mask(conf['gemini_api_key'])}\")\n print(f\" Model: {conf['gemini_model']}\")\n\n print(\"\\n[HAP Organization]\")\n print(f\" AppKey: {_mask(conf['app_key'])}\")\n print(f\" SecretKey: {_mask(conf['secret_key'])}\")\n print(f\" ProjectID: {conf['project_id']}\")\n print(f\" OwnerID: {conf['owner_id']}\")\n print(f\" GroupIDs: {conf['group_ids'] or '(未指定)'}\")\n\n print(\"\\n[Login Credentials]\")\n print(f\" Account: {conf['account']}\")\n print(f\" Password: {_mask(conf['password'])}\")\n\n print(\"\\n\" + \"=\" * 60)\n print(\"提示:如需修改,请运行 python3 setup.py --force\")\n print(\"=\" * 60 + \"\\n\")\n\n\ndef main():\n # 参数处理\n force = \"--force\" in sys.argv\n show = \"--show\" in sys.argv\n\n if show:\n show_config()\n return\n\n print(\"\\n\" + \"🚀\" * 20)\n print(\" HAP Auto 自动化环境初始化\")\n print(\"🚀\" * 20)\n\n try:\n step_install_deps()\n step_gemini(force)\n step_org_auth(force)\n step_login_and_auth(force)\n step_group_init(force)\n\n print(\"\\n\" + \"✨\" * 20)\n print(\" 所有配置已完成!\")\n print(\" 现在你可以运行主流程:python3 scripts/run_app_pipeline.py\")\n print(\"✨\" * 20 + \"\\n\")\n\n except KeyboardInterrupt:\n print(\"\\n\\n👋 已由用户取消。\")\n except Exception as e:\n print(f\"\\n\\n❌ 初始化过程中发生错误: {e}\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "cb229e9ebdbe06479ab58870c40019ff328f48e0": {"t": 1785773621.7040026, "n": 17871, "h": "17cc137de810fb9f", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nimport hashlib\nimport time\nfrom pathlib import Path\n\nimport requests\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", \"gemini-2.5-pro\")\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if old_model and old_model not in _PLACEHOLDERS:\n model = ask(f\" 默认模型 [当前: {old_model}, 回车保留]\", default=old_model)\n else:\n model = ask(\" 请输入默认模型 (建议 gemini-2.5-pro)\", default=\"gemini-2.5-pro\")\n\n if not key:\n print(\" ⚠️ API Key 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n \n config_data = {\"api_key\": key, \"model\": model}\n dst.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n # 自动尝试获取 project_id\n if key == \"project_id\" and results.get(\"app_key\") and results.get(\"secret_key\"):\n if not existing.get(\"project_id\") or existing.get(\"project_id\") in _PLACEHOLDERS:\n print(\"\\n 🔍 正在尝试根据 app_key 和 secret_key 自动获取 project_id...\")\n try:\n ak = results[\"app_key\"]\n sk = results[\"secret_key\"]\n ts = int(time.time())\n sign = hashlib.md5(f\"{ak}{sk}{ts}\".encode(\"utf-8\")).hexdigest()\n url = \"https://api.mingdao.com/v3/app\"\n resp = requests.get(url, headers={\"HAP-Appkey\": ak, \"HAP-Sign\": sign}, params={\"timestamp\": ts}, timeout=10)\n data = resp.json()\n if data.get(\"success\"):\n pid = data.get(\"data\", {}).get(\"organizationId\")\n if pid:\n print(f\" ✅ 自动探测到 project_id: {pid}\")\n existing[\"project_id\"] = pid\n except Exception as e:\n print(f\" ⚠️ 自动获取 project_id 失败: {e}\")\n\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-pro\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef step_group_init(force=False):\n \"\"\"应用分组初始化引导:选择或新建\"\"\"\n # 导入本地配置处理\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id, save_local_group_id\n from list_groups import get_groups\n except ImportError:\n print(\"\\n ⚠️ 无法加载分组管理脚本,跳过分组配置。\")\n return\n\n current_gid = load_local_group_id()\n if current_gid and not force:\n return\n\n print(\"\\n📂 [5/5] 配置默认应用分组\")\n print(\" 新创建的应用将默认归属于此分组。\")\n\n try:\n groups = get_groups()\n \n if not groups:\n print(\" 目前组织下没有任何应用分组,请创建一个。\")\n from create_group import create_group\n create_group()\n return\n\n print(\"\\n 请选择一个现有的分组,或输入 '+' 创建新分组:\")\n for i, g in enumerate(groups, 1):\n name = g.get(\"name\", \"Unknown\")\n gid = g.get(\"groupId\", \"Unknown\")\n status = \"⭐ (当前)\" if gid == current_gid else \"\"\n print(f\" {i}. {name:<20} ({gid}) {status}\")\n print(\" +. 创建新分组\")\n\n choice = ask(\" 请选择\", default=\"1\" if not current_gid else \"\").strip()\n \n if choice == \"+\":\n from create_group import create_group\n create_group()\n elif choice.isdigit():\n idx = int(choice) - 1\n if 0 <= idx < len(groups):\n selected = groups[idx]\n save_local_group_id(selected[\"groupId\"])\n print(f\" ✅ 已将 '{selected['name']}' 设为默认分组。\")\n else:\n print(\" ❌ 编号无效,跳过分组设置。\")\n else:\n print(\" 已保留当前设置。\")\n\n except Exception as e:\n print(f\" ❌ 分组配置失败: {e}\")\n\n\ndef manage_groups_menu():\n \"\"\"二级菜单:应用分组管理\"\"\"\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n from local_config import load_local_group_id\n \n while True:\n current_gid = load_local_group_id()\n print(\"\\n\" + \"=\"*40)\n print(\" 📂 应用分组管理\")\n print(f\" 当前默认分组 ID: {current_gid or '(未设置)'}\")\n print(\"-\" * 40)\n print(\" 1. 切换默认分组 (从组织已有列表中选择)\")\n print(\" 2. 新建应用分组 (在 HAP 中创建并设为默认)\")\n print(\" 3. 彻底删除分组 (从 HAP 组织中移除)\")\n print(\" 0. 返回上级菜单\")\n print(\"=\"*40)\n \n choice = input(\"请选择操作 [0-3]: \").strip()\n \n if choice == \"1\":\n subprocess.run([sys.executable, str(BASE_DIR / \"scripts\" / \"hap\" / \"switch_group.py\")])\n elif choice == \"2\":\n subprocess.run([sys.executable, str(BASE_DIR / \"scripts\" / \"hap\" / \"create_group.py\")])\n elif choice == \"3\":\n subprocess.run([sys.executable, str(BASE_DIR / \"scripts\" / \"hap\" / \"delete_group.py\")])\n elif choice == \"0\" or not choice:\n break\n else:\n print(\"❌ 无效选择。\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看并修改已有配置(选择性修改,无需全部重新输入)\")\n args = parser.parse_args()\n\n if args.show:\n while True:\n cfg = _read_all_config()\n # 临时展示本地分组 ID\n sys.path.append(str(BASE_DIR / \"scripts\" / \"hap\"))\n try:\n from local_config import load_local_group_id\n local_gid = load_local_group_id()\n except:\n local_gid = \"Error\"\n\n items = [\n (\"account\", \"登录账号\", False),\n (\"password\", \"登录密码\", True),\n (\"gemini_api_key\", \"Gemini API Key\", True),\n (\"gemini_model\", \"Gemini 模型\", False),\n (\"app_key\", \"app_key\", True),\n (\"secret_key\", \"secret_key\", True),\n (\"project_id\", \"project_id\", False),\n (\"owner_id\", \"owner_id\", False),\n ]\n\n print(\"\\n📋 当前核心配置:\")\n print(\"-\" * 50)\n for idx, (key, name, sensitive) in enumerate(items, 1):\n val = cfg.get(key, \"\")\n display = _display_val(val, sensitive)\n print(f\" {idx}. {name:18s} = {display}\")\n \n print(f\" G. [应用分组管理] (当前默认 ID: {local_gid or '(未设置)'})\")\n print(\"-\" * 50)\n print(\"\\n输入编号修改核心配置(多个用逗号隔开),输入 'G' 进入分组管理,直接回车退出:\")\n \n choice = input(\"修改项: \").strip().upper()\n if not choice:\n break\n \n if choice == \"G\":\n manage_groups_menu()\n continue\n\n # 处理数字选择逻辑(复用原有逻辑但精简)\n indices = set()\n for part in choice.replace(\",\", \",\").split(\",\"):\n part = part.strip()\n if part.isdigit():\n indices.add(int(part))\n \n if indices:\n # 这里为了简洁,直接调用修改逻辑(实际可进一步重构优化)\n # 为了不破坏原有逻辑,我们调用一个修改函数\n _update_config_by_indices(cfg, items, indices)\n \n return\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n step_group_init(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/run_app_pipeline.py\")\n print(\"=\" * 60)\n\ndef _update_config_by_indices(cfg, items, indices):\n \"\"\"提取的配置更新逻辑\"\"\"\n changed_gemini = False\n changed_org = False\n changed_login = False\n\n for idx, (key, name, sensitive) in enumerate(items, 1):\n if idx not in indices:\n continue\n old_val = cfg.get(key, \"\")\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n \n if sensitive and default:\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n new_val = old_val\n else:\n new_val = raw_input\n else:\n new_val = ask(f\" {name}\", default=default)\n \n cfg[key] = new_val\n if key in (\"gemini_api_key\", \"gemini_model\"): changed_gemini = True\n elif key in (\"account\", \"password\"): changed_login = True\n else: changed_org = True\n\n if changed_gemini:\n data = {\"api_key\": cfg.get(\"gemini_api_key\") or \"YOUR_GEMINI_API_KEY\", \"model\": cfg.get(\"gemini_model\") or \"gemini-2.5-pro\"}\n (CRED_DIR / \"gemini_auth.json\").write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n if changed_org:\n data = {\n \"app_key\": cfg.get(\"app_key\") or \"YOUR_HAP_APP_KEY\",\n \"secret_key\": cfg.get(\"secret_key\") or \"YOUR_HAP_SECRET_KEY\",\n \"project_id\": cfg.get(\"project_id\") or \"YOUR_HAP_PROJECT_ID\",\n \"owner_id\": cfg.get(\"owner_id\") or \"YOUR_HAP_OWNER_ID\",\n \"group_ids\": cfg.get(\"group_ids\") or \"\"\n }\n (CRED_DIR / \"organization_auth.json\").write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n if changed_login:\n account = cfg.get(\"account\") or \"your-account@example.com\"\n password = cfg.get(\"password\") or \"your-password\"\n (CRED_DIR / \"login_credentials.py\").write_text(\n f'LOGIN_ACCOUNT = \"{account}\"\\nLOGIN_PASSWORD = \"{password}\"\\nLOGIN_URL = \"https://www.mingdao.com/login\"\\n', encoding=\"utf-8\"\n )\n print(\"\\n✅ 配置已更新。\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "350fc2ac3fc87d177966878e6d8a16d875a49895": {"t": 1785773643.264594, "n": 14469, "h": "70f3c13eeccda6fc", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", \"gemini-2.5-pro\")\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if old_model and old_model not in _PLACEHOLDERS:\n model = ask(f\" 默认模型 [当前: {old_model}, 回车保留]\", default=old_model)\n else:\n model = ask(\" 请输入默认模型 (建议 gemini-2.5-pro)\", default=\"gemini-2.5-pro\")\n\n if not key:\n print(\" ⚠️ API Key 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n \n config_data = {\"api_key\": key, \"model\": model}\n dst.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-pro\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef show_config():\n \"\"\"显示所有已有配置,让用户选择要修改的项\"\"\"\n cfg = _read_all_config()\n\n # 配置项定义:(key, 显示名, 是否敏感)\n items = [\n (\"account\", \"登录账号\", False),\n (\"password\", \"登录密码\", True),\n (\"gemini_api_key\", \"Gemini API Key\", True),\n (\"gemini_model\", \"Gemini 模型\", False),\n (\"app_key\", \"app_key\", True),\n (\"secret_key\", \"secret_key\", True),\n (\"project_id\", \"project_id\", False),\n (\"owner_id\", \"owner_id\", False),\n (\"group_ids\", \"group_ids\", False),\n ]\n\n print(\"\\n📋 当前所有配置:\")\n print(\"-\" * 50)\n for idx, (key, name, sensitive) in enumerate(items, 1):\n val = cfg.get(key, \"\")\n display = _display_val(val, sensitive)\n print(f\" {idx}. {name:18s} = {display}\")\n print(\"-\" * 50)\n print(\"\\n输入要修改的编号(多个用逗号分隔,如 1,3,5),直接回车跳过:\")\n print(\"(提示:输入 '-' 可将该项清空)\")\n choice = input(\"修改项: \").strip()\n if not choice:\n print(\"未修改任何配置。\")\n return\n\n # 解析选择\n indices = set()\n for part in choice.replace(\",\", \",\").split(\",\"):\n part = part.strip()\n if part.isdigit():\n indices.add(int(part))\n\n if not indices:\n print(\"未识别到有效编号,未修改任何配置。\")\n return\n\n changed_gemini = False\n changed_org = False\n changed_login = False\n\n for idx, (key, name, sensitive) in enumerate(items, 1):\n if idx not in indices:\n continue\n old_val = cfg.get(key, \"\")\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n \n # 统一使用 ask 且手动处理其内部逻辑,以准确支持 '-' 清空\n if sensitive and default:\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # ask 内部对于 '-' 会返回 \"\",但我们这里需要区分:\n # 1. 输入为空 -> 保留默认 (ask 返回 default)\n # 2. 输入为 '-' -> 清空 (ask 返回 \"\")\n # 但 ask 此时其实拿不到 default 参数,所以若输入为空它会返回 \"\"。\n # 为了严谨,我们直接在 callsite 判定:\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n new_val = old_val # 回车保留\n else:\n new_val = raw_input # 可能是新值也可能是清空后的 \"\"\n else:\n new_val = ask(f\" {name}\", default=default)\n \n cfg[key] = new_val\n\n if key in (\"gemini_api_key\", \"gemini_model\"):\n changed_gemini = True\n elif key in (\"account\", \"password\"):\n changed_login = True\n else:\n changed_org = True\n\n # 写回修改过的文件\n if changed_gemini:\n dst = CRED_DIR / \"gemini_auth.json\"\n data = {\n \"api_key\": cfg.get(\"gemini_api_key\") or \"YOUR_GEMINI_API_KEY\",\n \"model\": cfg.get(\"gemini_model\") or \"gemini-2.5-pro\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 gemini_auth.json\")\n\n if changed_org:\n dst = CRED_DIR / \"organization_auth.json\"\n data = {\n \"app_key\": cfg.get(\"app_key\") or \"YOUR_HAP_APP_KEY\",\n \"secret_key\": cfg.get(\"secret_key\") or \"YOUR_HAP_SECRET_KEY\",\n \"project_id\": cfg.get(\"project_id\") or \"YOUR_HAP_PROJECT_ID\",\n \"owner_id\": cfg.get(\"owner_id\") or \"YOUR_HAP_OWNER_ID\",\n \"group_ids\": cfg.get(\"group_ids\") or \"\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 organization_auth.json\")\n\n if changed_login:\n login_dst = CRED_DIR / \"login_credentials.py\"\n account = cfg.get(\"account\") or \"your-account@example.com\"\n password = cfg.get(\"password\") or \"your-password\"\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account}\"\\n'\n f'LOGIN_PASSWORD = \"{password}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已更新 login_credentials.py\")\n\n # 自动刷新认证\n if account not in _PLACEHOLDERS and password not in _PLACEHOLDERS:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n print(\"\\n✅ 配置修改完成!\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看并修改已有配置(选择性修改,无需全部重新输入)\")\n args = parser.parse_args()\n\n if args.show:\n print(\"=\" * 60)\n print(\" HAP Auto — 查看 / 修改配置\")\n print(\"=\" * 60)\n show_config()\n return\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/run_app_pipeline.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "43cd45ac5a65dd729d0e705909b48c8e93b5e9a8": {"t": 1785773644.4584582, "n": 14684, "h": "bc6ab8402a93748c", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n \n # 特殊规则:输入 '-' 表示清除该项内容(设为空字符串)\n if val == \"-\":\n return \"\"\n \n return val if val else default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", \"gemini-2.5-pro\")\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if old_model and old_model not in _PLACEHOLDERS:\n model = ask(f\" 默认模型 [当前: {old_model}, 回车保留]\", default=old_model)\n else:\n model = ask(\" 请输入默认模型 (建议 gemini-2.5-pro)\", default=\"gemini-2.5-pro\")\n\n if not key:\n print(\" ⚠️ API Key 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n \n config_data = {\"api_key\": key, \"model\": model}\n dst.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # 同 show_config: 空字符串回车保留,'-' 清空\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n results[key] = old_val \n else:\n results[key] = raw_input\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-pro\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef show_config():\n \"\"\"显示所有已有配置,让用户选择要修改的项\"\"\"\n cfg = _read_all_config()\n\n # 配置项定义:(key, 显示名, 是否敏感)\n items = [\n (\"account\", \"登录账号\", False),\n (\"password\", \"登录密码\", True),\n (\"gemini_api_key\", \"Gemini API Key\", True),\n (\"gemini_model\", \"Gemini 模型\", False),\n (\"app_key\", \"app_key\", True),\n (\"secret_key\", \"secret_key\", True),\n (\"project_id\", \"project_id\", False),\n (\"owner_id\", \"owner_id\", False),\n (\"group_ids\", \"group_ids\", False),\n ]\n\n print(\"\\n📋 当前所有配置:\")\n print(\"-\" * 50)\n for idx, (key, name, sensitive) in enumerate(items, 1):\n val = cfg.get(key, \"\")\n display = _display_val(val, sensitive)\n print(f\" {idx}. {name:18s} = {display}\")\n print(\"-\" * 50)\n print(\"\\n输入要修改的编号(多个用逗号分隔,如 1,3,5),直接回车跳过:\")\n print(\"(提示:输入 '-' 可将该项清空)\")\n choice = input(\"修改项: \").strip()\n if not choice:\n print(\"未修改任何配置。\")\n return\n\n # 解析选择\n indices = set()\n for part in choice.replace(\",\", \",\").split(\",\"):\n part = part.strip()\n if part.isdigit():\n indices.add(int(part))\n\n if not indices:\n print(\"未识别到有效编号,未修改任何配置。\")\n return\n\n changed_gemini = False\n changed_org = False\n changed_login = False\n\n for idx, (key, name, sensitive) in enumerate(items, 1):\n if idx not in indices:\n continue\n old_val = cfg.get(key, \"\")\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n \n # 统一使用 ask 且手动处理其内部逻辑,以准确支持 '-' 清空\n if sensitive and default:\n raw_input = ask(f\" {name} [{_mask(default)}]\")\n # ask 内部对于 '-' 会返回 \"\",但我们这里需要区分:\n # 1. 输入为空 -> 保留默认 (ask 返回 default)\n # 2. 输入为 '-' -> 清空 (ask 返回 \"\")\n # 但 ask 此时其实拿不到 default 参数,所以若输入为空它会返回 \"\"。\n # 为了严谨,我们直接在 callsite 判定:\n if raw_input == \"\" and old_val and old_val not in _PLACEHOLDERS:\n new_val = old_val # 回车保留\n else:\n new_val = raw_input # 可能是新值也可能是清空后的 \"\"\n else:\n new_val = ask(f\" {name}\", default=default)\n \n cfg[key] = new_val\n\n if key in (\"gemini_api_key\", \"gemini_model\"):\n changed_gemini = True\n elif key in (\"account\", \"password\"):\n changed_login = True\n else:\n changed_org = True\n\n # 写回修改过的文件\n if changed_gemini:\n dst = CRED_DIR / \"gemini_auth.json\"\n data = {\n \"api_key\": cfg.get(\"gemini_api_key\") or \"YOUR_GEMINI_API_KEY\",\n \"model\": cfg.get(\"gemini_model\") or \"gemini-2.5-pro\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 gemini_auth.json\")\n\n if changed_org:\n dst = CRED_DIR / \"organization_auth.json\"\n data = {\n \"app_key\": cfg.get(\"app_key\") or \"YOUR_HAP_APP_KEY\",\n \"secret_key\": cfg.get(\"secret_key\") or \"YOUR_HAP_SECRET_KEY\",\n \"project_id\": cfg.get(\"project_id\") or \"YOUR_HAP_PROJECT_ID\",\n \"owner_id\": cfg.get(\"owner_id\") or \"YOUR_HAP_OWNER_ID\",\n \"group_ids\": cfg.get(\"group_ids\") or \"\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 organization_auth.json\")\n\n if changed_login:\n login_dst = CRED_DIR / \"login_credentials.py\"\n account = cfg.get(\"account\") or \"your-account@example.com\"\n password = cfg.get(\"password\") or \"your-password\"\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account}\"\\n'\n f'LOGIN_PASSWORD = \"{password}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已更新 login_credentials.py\")\n\n # 自动刷新认证\n if account not in _PLACEHOLDERS and password not in _PLACEHOLDERS:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"auth\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/auth/refresh_auth.py\")\n\n print(\"\\n✅ 配置修改完成!\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看并修改已有配置(选择性修改,无需全部重新输入)\")\n args = parser.parse_args()\n\n if args.show:\n print(\"=\" * 60)\n print(\" HAP Auto — 查看 / 修改配置\")\n print(\"=\" * 60)\n show_config()\n return\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "26ef916fd6dfe91e0967579b0fa3058a7da60008": {"t": 1785773645.761413, "n": 13850, "h": "339e4b7db3afffea", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"YOUR_GEMINI_MODEL\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n return val or default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n old_model = existing.get(\"model\", \"gemini-2.5-pro\")\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API & Model\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n key = ask(f\" Gemini API Key [{_mask(old_key)}]\") or old_key\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if old_model and old_model not in _PLACEHOLDERS:\n model = ask(f\" 默认模型 [当前: {old_model}, 回车保留]\", default=old_model)\n else:\n model = ask(\" 请输入默认模型 (建议 gemini-2.5-pro)\", default=\"gemini-2.5-pro\")\n\n if not key:\n print(\" ⚠️ API Key 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n \n config_data = {\"api_key\": key, \"model\": model}\n dst.write_text(json.dumps(config_data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n results[key] = ask(f\" {name} [{_mask(default)}]\") or default\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n result[\"gemini_model\"] = gemini.get(\"model\", \"gemini-2.5-pro\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef show_config():\n \"\"\"显示所有已有配置,让用户选择要修改的项\"\"\"\n cfg = _read_all_config()\n\n # 配置项定义:(key, 显示名, 是否敏感)\n items = [\n (\"account\", \"登录账号\", False),\n (\"password\", \"登录密码\", True),\n (\"gemini_api_key\", \"Gemini API Key\", True),\n (\"gemini_model\", \"Gemini 模型\", False),\n (\"app_key\", \"app_key\", True),\n (\"secret_key\", \"secret_key\", True),\n (\"project_id\", \"project_id\", False),\n (\"owner_id\", \"owner_id\", False),\n (\"group_ids\", \"group_ids\", False),\n ]\n\n print(\"\\n📋 当前所有配置:\")\n print(\"-\" * 50)\n for idx, (key, name, sensitive) in enumerate(items, 1):\n val = cfg.get(key, \"\")\n display = _display_val(val, sensitive)\n print(f\" {idx}. {name:18s} = {display}\")\n print(\"-\" * 50)\n print(\"\\n输入要修改的编号(多个用逗号分隔,如 1,3,5),直接回车跳过:\")\n choice = input(\"修改项: \").strip()\n if not choice:\n print(\"未修改任何配置。\")\n return\n\n # 解析选择\n indices = set()\n for part in choice.replace(\",\", \",\").split(\",\"):\n part = part.strip()\n if part.isdigit():\n indices.add(int(part))\n\n if not indices:\n print(\"未识别到有效编号,未修改任何配置。\")\n return\n\n changed_gemini = False\n changed_org = False\n changed_login = False\n\n for idx, (key, name, sensitive) in enumerate(items, 1):\n if idx not in indices:\n continue\n old_val = cfg.get(key, \"\")\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if sensitive and default:\n new_val = ask(f\" {name} [{_mask(default)}]\") or default\n else:\n new_val = ask(f\" {name}\", default=default)\n cfg[key] = new_val\n\n if key in (\"gemini_api_key\", \"gemini_model\"):\n changed_gemini = True\n elif key in (\"account\", \"password\"):\n changed_login = True\n else:\n changed_org = True\n\n # 写回修改过的文件\n if changed_gemini:\n dst = CRED_DIR / \"gemini_auth.json\"\n data = {\n \"api_key\": cfg.get(\"gemini_api_key\") or \"YOUR_GEMINI_API_KEY\",\n \"model\": cfg.get(\"gemini_model\") or \"gemini-2.5-pro\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 gemini_auth.json\")\n\n if changed_org:\n dst = CRED_DIR / \"organization_auth.json\"\n data = {\n \"app_key\": cfg.get(\"app_key\") or \"YOUR_HAP_APP_KEY\",\n \"secret_key\": cfg.get(\"secret_key\") or \"YOUR_HAP_SECRET_KEY\",\n \"project_id\": cfg.get(\"project_id\") or \"YOUR_HAP_PROJECT_ID\",\n \"owner_id\": cfg.get(\"owner_id\") or \"YOUR_HAP_OWNER_ID\",\n \"group_ids\": cfg.get(\"group_ids\") or \"\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 organization_auth.json\")\n\n if changed_login:\n login_dst = CRED_DIR / \"login_credentials.py\"\n account = cfg.get(\"account\") or \"your-account@example.com\"\n password = cfg.get(\"password\") or \"your-password\"\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account}\"\\n'\n f'LOGIN_PASSWORD = \"{password}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已更新 login_credentials.py\")\n\n # 自动刷新认证\n if account not in _PLACEHOLDERS and password not in _PLACEHOLDERS:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n\n print(\"\\n✅ 配置修改完成!\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看并修改已有配置(选择性修改,无需全部重新输入)\")\n args = parser.parse_args()\n\n if args.show:\n print(\"=\" * 60)\n print(\" HAP Auto — 查看 / 修改配置\")\n print(\"=\" * 60)\n show_config()\n return\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "68e147c814a351adcfbd107fc2d0b3fd6881e474": {"t": 1785773647.2163584, "n": 13324, "h": "3f4eee4b2a99692f", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n return val or default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API Key\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n print(f\" 当前值: {_mask(old_key)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n key = ask(\" Gemini API Key\", default=old_key)\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if not key:\n print(\" ⚠️ 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n dst.write_text(json.dumps({\"api_key\": key}, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n results[key] = ask(f\" {name} [{_mask(default)}]\") or default\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/refresh_auth.py\")\n\n\ndef _read_all_config() -> dict:\n \"\"\"读取所有配置文件,返回统一的 dict\"\"\"\n import re as _re\n result = {}\n\n # Gemini\n gemini = _load_json_safe(CRED_DIR / \"gemini_auth.json\")\n result[\"gemini_api_key\"] = gemini.get(\"api_key\", \"\")\n\n # 组织密钥\n org = _load_json_safe(CRED_DIR / \"organization_auth.json\")\n for k in (\"app_key\", \"secret_key\", \"project_id\", \"owner_id\", \"group_ids\"):\n result[k] = org.get(k, \"\")\n\n # 登录凭据\n login_dst = CRED_DIR / \"login_credentials.py\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n m = _re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n result[\"account\"] = m.group(1) if m else \"\"\n m = _re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n result[\"password\"] = m.group(1) if m else \"\"\n except Exception:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n else:\n result[\"account\"] = \"\"\n result[\"password\"] = \"\"\n\n return result\n\n\ndef show_config():\n \"\"\"显示所有已有配置,让用户选择要修改的项\"\"\"\n cfg = _read_all_config()\n\n # 配置项定义:(key, 显示名, 是否敏感)\n items = [\n (\"account\", \"登录账号\", False),\n (\"password\", \"登录密码\", True),\n (\"gemini_api_key\", \"Gemini API Key\", True),\n (\"app_key\", \"app_key\", True),\n (\"secret_key\", \"secret_key\", True),\n (\"project_id\", \"project_id\", False),\n (\"owner_id\", \"owner_id\", False),\n (\"group_ids\", \"group_ids\", False),\n ]\n\n print(\"\\n📋 当前所有配置:\")\n print(\"-\" * 50)\n for idx, (key, name, sensitive) in enumerate(items, 1):\n val = cfg.get(key, \"\")\n display = _display_val(val, sensitive)\n print(f\" {idx}. {name:18s} = {display}\")\n print(\"-\" * 50)\n print(\"\\n输入要修改的编号(多个用逗号分隔,如 1,3,5),直接回车跳过:\")\n choice = input(\"修改项: \").strip()\n if not choice:\n print(\"未修改任何配置。\")\n return\n\n # 解析选择\n indices = set()\n for part in choice.replace(\",\", \",\").split(\",\"):\n part = part.strip()\n if part.isdigit():\n indices.add(int(part))\n\n if not indices:\n print(\"未识别到有效编号,未修改任何配置。\")\n return\n\n changed_gemini = False\n changed_org = False\n changed_login = False\n\n for idx, (key, name, sensitive) in enumerate(items, 1):\n if idx not in indices:\n continue\n old_val = cfg.get(key, \"\")\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if sensitive and default:\n new_val = ask(f\" {name} [{_mask(default)}]\") or default\n else:\n new_val = ask(f\" {name}\", default=default)\n cfg[key] = new_val\n\n if key == \"gemini_api_key\":\n changed_gemini = True\n elif key in (\"account\", \"password\"):\n changed_login = True\n else:\n changed_org = True\n\n # 写回修改过的文件\n if changed_gemini:\n dst = CRED_DIR / \"gemini_auth.json\"\n val = cfg[\"gemini_api_key\"] or \"YOUR_GEMINI_API_KEY\"\n dst.write_text(json.dumps({\"api_key\": val}, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 gemini_auth.json\")\n\n if changed_org:\n dst = CRED_DIR / \"organization_auth.json\"\n data = {\n \"app_key\": cfg.get(\"app_key\") or \"YOUR_HAP_APP_KEY\",\n \"secret_key\": cfg.get(\"secret_key\") or \"YOUR_HAP_SECRET_KEY\",\n \"project_id\": cfg.get(\"project_id\") or \"YOUR_HAP_PROJECT_ID\",\n \"owner_id\": cfg.get(\"owner_id\") or \"YOUR_HAP_OWNER_ID\",\n \"group_ids\": cfg.get(\"group_ids\") or \"\",\n }\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已更新 organization_auth.json\")\n\n if changed_login:\n login_dst = CRED_DIR / \"login_credentials.py\"\n account = cfg.get(\"account\") or \"your-account@example.com\"\n password = cfg.get(\"password\") or \"your-password\"\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account}\"\\n'\n f'LOGIN_PASSWORD = \"{password}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已更新 login_credentials.py\")\n\n # 自动刷新认证\n if account not in _PLACEHOLDERS and password not in _PLACEHOLDERS:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n\n print(\"\\n✅ 配置修改完成!\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n parser.add_argument(\"--show\", action=\"store_true\", help=\"查看并修改已有配置(选择性修改,无需全部重新输入)\")\n args = parser.parse_args()\n\n if args.show:\n print(\"=\" * 60)\n print(\" HAP Auto — 查看 / 修改配置\")\n print(\"=\" * 60)\n show_config()\n return\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "9cc87ac4659bd0a7a90bda94dab864bc77efd20d": {"t": 1785773648.4322917, "n": 8243, "h": "5a369fff42ed1b8c", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py # 首次初始化(已有配置自动跳过)\n python3 setup.py --force # 强制重新初始化(显示当前值,回车保留,输入新值覆盖)\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n# 占位符列表,匹配到这些值视为\"未填写\"\n_PLACEHOLDERS = {\n \"YOUR_HAP_APP_KEY\",\n \"YOUR_HAP_SECRET_KEY\",\n \"YOUR_HAP_PROJECT_ID\",\n \"YOUR_HAP_OWNER_ID\",\n \"YOUR_GEMINI_API_KEY\",\n \"your-account@example.com\",\n \"your-password\",\n \"OPTIONAL_PRECOMPUTED_SIGN\",\n}\n\n\ndef _mask(val: str, show: int = 4) -> str:\n \"\"\"对敏感值做脱敏显示:前 show 位明文 + ****\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n if len(val) <= show:\n return val\n return val[:show] + \"****\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n return val or default\n\n\ndef _load_json_safe(path: Path) -> dict:\n \"\"\"安全读取 JSON,文件不存在或格式错误返回空 dict\"\"\"\n if not path.exists():\n return {}\n try:\n return json.loads(path.read_text(encoding=\"utf-8\"))\n except Exception:\n return {}\n\n\ndef _display_val(val: str, sensitive: bool = False) -> str:\n \"\"\"用于展示当前值:敏感字段脱敏,占位符显示为 (未填写)\"\"\"\n if not val or val in _PLACEHOLDERS:\n return \"(未填写)\"\n return _mask(val) if sensitive else val\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n existing = _load_json_safe(dst)\n old_key = existing.get(\"api_key\", \"\")\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔑 [2/4] 配置 Gemini API Key\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n\n if old_key and old_key not in _PLACEHOLDERS:\n print(f\" 当前值: {_mask(old_key)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n key = ask(\" Gemini API Key\", default=old_key)\n else:\n key = ask(\" 请输入你的 Gemini API Key\")\n\n if not key:\n print(\" ⚠️ 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n dst.write_text(json.dumps({\"api_key\": key}, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n existing = _load_json_safe(dst)\n\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n\n # 定义字段:(json_key, 显示名, 提示文字, 是否敏感, 是否必填)\n fields = [\n (\"app_key\", \"app_key\", None, True, True),\n (\"secret_key\", \"secret_key\", None, True, True),\n (\"project_id\", \"project_id\", \"获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\", False, True),\n (\"owner_id\", \"owner_id\", \"获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\", False, True),\n (\"group_ids\", \"group_ids\", \"获取 group_ids: 在明道云中点击某个应用分组,地址栏中 groupId=xxx 的 xxx 部分(可选,留空不指定分组)\", False, False),\n ]\n\n # 如果已有配置,先展示当前值\n if existing and any(existing.get(f[0], \"\") not in (\"\", *_PLACEHOLDERS) for f in fields):\n print(\"\\n 📋 当前配置:\")\n for key, name, _, sensitive, _ in fields:\n val = existing.get(key, \"\")\n print(f\" {name:12s} = {_display_val(val, sensitive)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\\n\")\n\n results = {}\n for key, name, hint, sensitive, required in fields:\n if hint:\n print(f\"\\n {hint}\")\n old_val = existing.get(key, \"\")\n # 占位符不作为默认值展示\n default = old_val if old_val and old_val not in _PLACEHOLDERS else \"\"\n if default and sensitive:\n # 敏感字段显示脱敏值作为提示\n results[key] = ask(f\" {name} [{_mask(default)}]\") or default\n else:\n results[key] = ask(f\" {name}\", default=default)\n\n # 写入\n data = {}\n data[\"app_key\"] = results[\"app_key\"] or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = results[\"secret_key\"] or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = results[\"project_id\"] or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = results[\"owner_id\"] or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = results[\"group_ids\"] or \"\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n\n # 尝试读取已有账号\n old_account = \"\"\n old_password = \"\"\n if login_dst.exists():\n try:\n text = login_dst.read_text(encoding=\"utf-8\")\n import re\n m = re.search(r'LOGIN_ACCOUNT\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_account = m.group(1)\n m = re.search(r'LOGIN_PASSWORD\\s*=\\s*\"(.+?)\"', text)\n if m:\n old_password = m.group(1)\n except Exception:\n pass\n\n if old_account and old_account not in _PLACEHOLDERS:\n print(f\" 当前账号: {old_account}\")\n print(f\" 当前密码: {_mask(old_password)}\")\n print(\" (直接回车保留当前值,输入新值则覆盖)\")\n account = ask(\" 登录邮箱/手机号\", default=old_account)\n password = ask(f\" 登录密码 [{_mask(old_password)}]\") or old_password\n else:\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/refresh_auth.py\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化(保留已有值,回车跳过,输入新值覆盖)\")\n args = parser.parse_args()\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n if args.force:\n print(\" (--force 模式:显示当前配置,直接回车保留,输入新值覆盖)\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "2939aea8ee5902c91e1c6f3bb83d52547b8610f1": {"t": 1785773650.0712173, "n": 5280, "h": "169e14827b0eef47", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n # 清理 copy-paste 常见的多余字符:引号、零宽空格等\n val = val.strip(\"\\\"'\"\"''\").strip(\"\\u200b\\u200c\\u200d\\ufeff\").strip()\n return val or default\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n print(\"\\n🔑 [2/4] 配置 Gemini API Key\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n key = ask(\" 请输入你的 Gemini API Key\")\n if not key:\n print(\" ⚠️ 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n dst.write_text(json.dumps({\"api_key\": key}, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n app_key = ask(\" app_key\")\n secret_key = ask(\" secret_key\")\n print(\"\\n 获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\")\n project_id = ask(\" project_id\")\n print(\"\\n 获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\")\n owner_id = ask(\" owner_id\")\n print(\"\\n 获取 group_ids: 在明道云中创建或找到一个应用分组,点击该分组后地址栏中 groupId=xxx 的 xxx 部分\")\n print(\" (可选,留空则创建应用时不指定分组)\")\n group_ids = ask(\" group_ids\")\n # 读取 example 模板获取完整结构\n example = CRED_DIR / \"organization_auth.example.json\"\n if example.exists():\n data = json.loads(example.read_text(encoding=\"utf-8\"))\n else:\n data = {}\n data[\"app_key\"] = app_key or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = secret_key or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = project_id or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = owner_id or \"YOUR_HAP_OWNER_ID\"\n data[\"group_ids\"] = group_ids or \"\"\n data.pop(\"sign\", None) # 移除旧模板中无用的 sign 字段\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/refresh_auth.py\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化,覆盖已有配置\")\n args = parser.parse_args()\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "54f44c20e900c0371c53432292e0916fa062b38a": {"t": 1785773651.5893111, "n": 4930, "h": "fec5fe698fd796d5", "c": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nHAP Auto 一键初始化脚本\n=======================\n首次克隆后只需运行此脚本,按提示填写即可完成全部配置。\n\n用法:\n python3 setup.py\n\"\"\"\n\nimport json\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent\nCRED_DIR = BASE_DIR / \"config\" / \"credentials\"\n\n\ndef ask(prompt: str, default: str = \"\") -> str:\n hint = f\" [{default}]\" if default else \"\"\n val = input(f\"{prompt}{hint}: \").strip()\n return val or default\n\n\ndef step_install_deps():\n \"\"\"安装 Python 依赖 + Playwright 浏览器\"\"\"\n print(\"\\n📦 [1/4] 安装 Python 依赖...\")\n subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n \"requests\", \"google-genai\", \"playwright\", \"prompt-toolkit\"])\n print(\"🌐 安装 Playwright Chromium...\")\n subprocess.check_call([sys.executable, \"-m\", \"playwright\", \"install\", \"chromium\"])\n\n\ndef step_gemini(force=False):\n \"\"\"配置 Gemini API Key\"\"\"\n dst = CRED_DIR / \"gemini_auth.json\"\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n print(\"\\n🔑 [2/4] 配置 Gemini API Key\")\n print(\" 获取地址: https://aistudio.google.com/apikey\")\n key = ask(\" 请输入你的 Gemini API Key\")\n if not key:\n print(\" ⚠️ 未填写,稍后可手动编辑 config/credentials/gemini_auth.json\")\n key = \"YOUR_GEMINI_API_KEY\"\n dst.write_text(json.dumps({\"api_key\": key}, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_org_auth(force=False):\n \"\"\"配置 HAP 组织级密钥\"\"\"\n dst = CRED_DIR / \"organization_auth.json\"\n if dst.exists() and not force:\n print(f\"\\n✅ {dst.name} 已存在,跳过(需重新配置请加上 --force)\")\n return\n print(\"\\n🏢 [3/4] 配置 HAP 组织级密钥\")\n print(\" 获取路径: 组织管理 → 集成 → 其他 → 开放接口 → 查看密钥\")\n print(\" 快捷地址: https://www.mingdao.com/admin/integrationothers/<你的组织ID>\")\n app_key = ask(\" app_key\")\n secret_key = ask(\" secret_key\")\n print(\"\\n 获取 project_id: 组织管理 → 组织 → 组织信息 → 编号(ID)\")\n project_id = ask(\" project_id\")\n print(\"\\n 获取 owner_id: 点击群聊中个人头像,地址栏中 https://www.mingdao.com/user_xxx 的 xxx 部分\")\n owner_id = ask(\" owner_id\")\n # 读取 example 模板获取完整结构\n example = CRED_DIR / \"organization_auth.example.json\"\n if example.exists():\n data = json.loads(example.read_text(encoding=\"utf-8\"))\n else:\n data = {}\n data[\"app_key\"] = app_key or \"YOUR_HAP_APP_KEY\"\n data[\"secret_key\"] = secret_key or \"YOUR_HAP_SECRET_KEY\"\n data[\"project_id\"] = project_id or \"YOUR_HAP_PROJECT_ID\"\n data[\"owner_id\"] = owner_id or \"YOUR_HAP_OWNER_ID\"\n dst.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n print(f\" ✔ 已写入 {dst.name}\")\n\n\ndef step_login_and_auth(force=False):\n \"\"\"配置登录凭据 → 自动刷新 auth_config.py\"\"\"\n login_dst = CRED_DIR / \"login_credentials.py\"\n auth_dst = CRED_DIR / \"auth_config.py\"\n\n if login_dst.exists() and auth_dst.exists() and not force:\n print(f\"\\n✅ login_credentials.py & auth_config.py 已存在,跳过(需重新配置请加上 --force)\")\n return\n\n print(\"\\n🔐 [4/4] 配置明道云登录账号\")\n account = ask(\" 登录邮箱/手机号\")\n password = ask(\" 登录密码\")\n\n # 写入 login_credentials.py\n login_dst.write_text(\n '# -*- coding: utf-8 -*-\\n'\n '\"\"\"\\n本地登录账号配置(自动生成,请勿提交到 Git)。\\n\"\"\"\\n\\n'\n f'LOGIN_ACCOUNT = \"{account or \"your-account@example.com\"}\"\\n'\n f'LOGIN_PASSWORD = \"{password or \"your-password\"}\"\\n'\n f'LOGIN_URL = \"https://www.mingdao.com/login\"\\n',\n encoding=\"utf-8\",\n )\n print(f\" ✔ 已写入 login_credentials.py\")\n\n # 先从模板复制 auth_config.py(refresh_auth 需要文件已存在才能正则替换)\n if not auth_dst.exists():\n example = CRED_DIR / \"auth_config.example.py\"\n if example.exists():\n auth_dst.write_text(example.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n\n # 自动刷新认证\n if account and password:\n print(\"\\n🔄 自动登录并获取认证信息...\")\n try:\n subprocess.check_call(\n [sys.executable, str(BASE_DIR / \"scripts\" / \"refresh_auth.py\"), \"--headless\"],\n cwd=str(BASE_DIR),\n )\n except subprocess.CalledProcessError:\n print(\" ⚠️ 自动登录失败,请稍后手动运行: python3 scripts/refresh_auth.py\")\n else:\n print(\" ⚠️ 未填写账号密码,跳过自动认证。稍后手动运行: python3 scripts/refresh_auth.py\")\n\n\ndef main():\n import argparse\n parser = argparse.ArgumentParser(description=\"HAP Auto 一键初始化脚本\")\n parser.add_argument(\"--force\", action=\"store_true\", help=\"强制重新初始化,覆盖已有配置\")\n args = parser.parse_args()\n\n print(\"=\" * 60)\n print(\" HAP Auto — 一键初始化\")\n print(\"=\" * 60)\n\n step_install_deps()\n step_gemini(force=args.force)\n step_org_auth(force=args.force)\n step_login_and_auth(force=args.force)\n\n print(\"\\n\" + \"=\" * 60)\n print(\"🎉 初始化完成!现在可以运行:\")\n print()\n print(\" # 对话式创建应用(推荐)\")\n print(\" python3 scripts/hap/agent_collect_requirements.py\")\n print(\"=\" * 60)\n\n\nif __name__ == \"__main__\":\n main()\n"}}}}