{"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"}}}}