Skill: wxjhhhh/tekan (version 1.0.3)

--- name: tekan-skill description: "生成、编辑、协作。一个工具包接入所有主流 AI 模型。只需描述你的创意，即可生成视频、图片和数字人——零手动操作。当用户提到以下任何内容时使用此技能：特看视频、生成视频或图片、数字人、口型同步、文字转语音、TTS、声音克隆、去除背景、商品模特图、图片转视频、文字转视频、AI 图片编辑，或任何创意内容生成工作流——即使他们没有明确说出具体工具名或'特看视频'。" metadata: tags: tekan, 特看视频, avatar, video, image, voice, ai, api, i2v, t2v, omni, text2image, image_edit, tts, voice_clone, board requires: bins: [python3] --- # 特看视频 AI Skill > [特看视频](https://video.tekan.cn) API 的模块化 Python 工具包。 ✨ **生成 · 编辑 · 协作 —— 一站搞定** ✨ - 🧠 **全主流模型**：一个工具包无缝接入全球顶级 AI 视频、图片、语音模型。 - 🗣️ **描述即创作**：告诉 Agent 你想要什么——从数字人到商品合成图，你的提示词直接生成成品。 - ⚡ **零手动操作**：无需手动上传，无需繁琐调整，一切自动完成并同步到你的在线看板。 ## 功能概览 > 你不需要了解任何 API 细节。只需描述你想要什么——Agent 会阅读下方技术文档并自动处理一切。 **单项任务——一句话，一个结果：** - 描述一张图片，几秒钟内生成——或一次批量生成一整套 - 上传一张人像和一段文案，获得带口型同步的数字人视频 - 将任意静态图片变成视频片段 - 纯文字描述即可生成视频 - 去除背景、编辑图片、替换场景——全靠描述即可完成 - 一句话把你的产品放到模特身上 - 克隆你的声音，或从数百种声音中选择进行文字转语音 - 在网页看板上整理所有成果，便于预览和分享 **组合工作流——自由串联各项能力：** 这些能力可以任意组合。例如，告诉 Agent"用这张照片做一套完整的产品发布素材"，它会将去除背景、商品展示、数字人视频和图片生成串联成一条流水线——全在一次对话中完成。更多示例： - 为故事板的每个场景生成图片，然后全部转换为视频片段 - 撰写脚本，用 3 种语言制作 TTS 配音，为每种语言创建数字人视频 - 从一个风格参考出发，批量生产一周的品牌风格统一的社交媒体图片和视频 - 上传人像 + 克隆声音，然后仅凭文字脚本生成无限量数字人节目 - 将一篇文章转化为多段教育视频系列，配以 AI 插图和主持人解说 ## 执行规则 > **始终使用 `scripts/` 中的 Python 脚本。** 这些脚本处理了身份验证、S3 文件上传、自动轮询、超时恢复和结构化错误处理——如果绕过它们直接用 `curl` 或 HTTP 调用，会丢失所有这些功能，而且仅在身份验证环节就可能失败。 ## 前置条件 - **Python 3.10+** - 积分余额充足 — 参见 [references/user.md](references/user.md) 查询余额 ```bash pip install -r {baseDir}/scripts/requirements.txt ``` ## 认证规则（必读） > **严禁向用户索要 API Key、UID 或任何凭证。用户没有这些东西，也无法自行获取。** > **授权链接只能通过运行 `auth.py login` 获得，用户没有其他渠道获取此链接。** **Agent 必须遵守：** 1. **永远不要** 向用户索要任何密钥或凭证 2. **永远不要** 让用户自己运行命令 4. **永远不要** 跳过引导消息或自行编写安装结果摘要 — 必须使用下方模板 详细说明参见 [references/auth.md](references/auth.md)。 ## 安装完成与登录引导（强制流程） > **安装依赖完成后，Agent 必须严格按以下步骤执行，不可跳过、不可改写、不可自行编写替代文案。** ### 步骤 1 — 运行 `auth.py login` ```bash python {baseDir}/scripts/auth.py login ``` 命令会输出授权链接（格式如 `URL: https://...`），并尝试自动打开浏览器（无浏览器的环境下会静默跳过）。**无论浏览器是否打开，都必须从输出中提取这个 URL**，下一步要用。 ### 步骤 2 — 发送引导消息（强制，原样使用以下模板） > **⚠️ 极其重要 — 登录链接输出规则（必须逐字遵守）：** > 1. 从步骤 1 的命令输出中找到 `URL: https://...` 那一行，提取完整的 URL > 2. 将下方模板中的 `<LOGIN_URL>`（含尖括号）**整个替换**为该真实 URL，填入 Markdown 链接的圆括号内 > 3. 最终输出格式必须是 `[👉 点击此处完成登录授权](https://实际链接)` > 4. **禁止**把 URL 单独裸露粘贴、**禁止**省略方括号文字部分、**禁止**用 `<` `>` 尖括号包裹 URL > 5. 必须保持 `[描述文字](完整URL)` 的完整结构，缺任何一部分都会导致链接失效 >**⚠️ 微信平台发送方式强制规则** - 登录引导消息必须通过 message(action=send) 工具发送，不可作为普通回复文本直接输出。 - 原因：如微信 在普通回复中会吞掉 Markdown 方括号 [] 符号，导致链接格式损坏。 - 通过 message 工具发送可以确保格式完整传递。 > ✅ 正确示例： > ``` > [👉 点击此处完成登录授权](https://api.tekan.cn/oauth/authorize?code=abc123) > ``` > ❌ 错误示例（裸 URL，没有方括号包裹）： > ``` > https://api.tekan.cn/oauth/authorize?code=abc123 > ``` > ❌ 错误示例（丢失了方括号描述文字）： > ``` > (https://api.tekan.cn/oauth/authorize?code=abc123) > ``` > ❌ 错误示例（占位符未替换）： > ``` > [👉 点击此处完成登录授权](<LOGIN_URL>) > ``` 将 `<LOGIN_URL>` 替换为步骤 1 获得的实际授权链接后，**原样发送以下消息给用户**： ``` 安装完成，特看视频技能已连接到你的智能助手。 点击下方链接完成登录，登录后将解锁以下能力： [👉 点击此处完成登录授权](<LOGIN_URL>) 🎬 视频生成 文字转视频、图片转视频、参考视频生成，自动配音配乐。 视频模型：地表最强模型S2.0-白名单版（支持上传真人图） · 拟真世界模型 V2 · 可灵 V3 · 电影级画质模型 V3.1 · Vidu Q3 Pro · 万象 V2.6 🖼 AI 图片生成与编辑 文字生图、AI 修图、风格转换，最高支持 4K。 图片模型：全能图片模型 V2 · Seedream 5.0 · 强语义理解模型 V1.5 · 照片级写实模型 V4 · 强上下文一致性模型 pro 🧑 口播数字人 上传一张照片 + 文案，自动生成真人口播视频，支持多语种。 ✂ 背景移除 一键抠图，产品图、人像、任意图片秒去背景。 🔺 产品模特图 把你的产品图放到模特身上，自动生成带货展示图。 🎙 语音与配音 文字转语音、声音克隆，支持多语种配音输出。 登录完成后回我一句"好了"，我马上继续。 ``` **禁止行为：** - 不可省略模板中的任何部分 - 不可用自己的话概括或改写 - 不可在用户授权前展示能力列表的简化版本 - `<LOGIN_URL>` 必须替换为真实 URL（保留 `[👉 点击此处完成登录授权](...)` 的完整 Markdown 链接结构），不可留空或写占位符 - **禁止**把 URL 裸露粘贴（不带方括号描述文字），**禁止**省略 `[...]()` 中的任何部分 ### 步骤 3 — 等待用户授权 用户在浏览器中完成登录和授权后，会回复"好了"。此时 `auth.py login` 应已自动检测到授权并保存凭证到 `~/.tekan/credentials.json`。 ### 步骤 4 — 验证登录状态 ```bash python {baseDir}/scripts/auth.py status ``` 确认输出包含 `Logged in` 后，告知用户"登录成功，所有功能已解锁"，即可开始正常使用。 ## Agent 工作流规则 > **以下规则适用于所有生成模块（avatar4, video_gen, ai_image, remove_bg, product_avatar, text2voice）。** 1. **始终从 `run` 开始** — 它会提交任务并自动轮询直到完成。这是默认且正确的选择，几乎适用于所有场景。 2. **Agent 负责轮询循环** — 用户期望无需干预的体验，因此 Agent 应持续轮询直到任务完成或超时，而不是让用户手动检查状态。 3. **`query` 仅用于恢复** — `query` 仅在 `run` 已超时且你有 `taskId` 需要恢复时有用，或用户主动提供了已有的 `taskId`。对新请求直接用 `query` 会失败，因为没有任务可轮询。 4. **`query` 持续轮询** — 它每隔 `--interval` 秒检查一次状态，直到状态为 `success` 或 `fail`，或 `--timeout` 到期。不会只检查一次就停止。 5. **如果 `query` 也超时**（退出码 2），增加 `--timeout` 并使用相同的 `taskId` 重试。除非任务确实失败，否则不要重新提交。 ``` 决策树： → 新请求？ 使用 `run` → run 超时了？ 使用 `query --task-id <id>` → query 也超时了？ 使用 `query --task-id <id> --timeout 1200` → 任务状态=fail？ 用 `run` 重新提交 ``` **任务状态：** | 状态 | 说明 | |--------|-------------| | `init` | 任务已排队，等待处理 | | `running` | 任务正在处理中 | | `success` | 任务成功完成 | | `fail` | 任务失败 | ## 看板 ID 协议（强制） > **每个生成任务都必须包含 `--board-id`。缺少看板 ID 会导致用户无法在网页上查看和编辑结果。** 1. **会话开始（必须）** — 在提交第一个任务之前，**必须先**运行 `board.py list --default -q` 获取默认看板 ID（"My First Board"）。每个会话只需执行一次。**不可跳过此步骤。** 2. **传递给所有任务（必须）** — 为每个生成命令添加 `--board-id <id>`（`avatar4.py`、`video_gen.py`、`ai_image.py`、`product_avatar.py`、`text2voice.py`）。 3. **任务完成后（必须）** — 从任务结果中提取 `boardTaskId`，按下方「视频/图片结果模板」用 Markdown 链接展示项目链接（勿粘贴裸 URL，避免长链接被截断无法点击）。**每次任务完成都必须展示此链接**，告诉用户可以点击查看和编辑结果。 4. **用户想要新看板** — 运行 `board.py create --name "..."` 并将返回的看板 ID 用于后续任务。 5. **用户指定了看板** — 使用用户提供的看板 ID 而非默认值。 6. **忘记了看板 ID？** — 再次运行 `board.py list --default -q`。 ``` 会话流程： 1. BOARD_ID = $(board.py list --default -q) 2. avatar4.py run --board-id $BOARD_ID ... 3. video_gen.py run --board-id $BOARD_ID ... 4. (结果中显示包含 boardTaskId 的编辑链接) ``` ## 模块列表 | 模块 | 脚本 | 参考文档 | 说明 | |--------|--------|-----------|-------------| | Auth | `scripts/auth.py` | [auth.md](references/auth.md) | OAuth 2.0 设备授权流程 — 浏览器登录，保存凭证 | | Avatar4 | `scripts/avatar4.py` | [avatar4.md](references/avatar4.md) | 从照片生成数字人视频；`list-captions` 查看字幕样式 | | Video Gen | `scripts/video_gen.py` | [video_gen.md](references/video_gen.md) | 图片转视频、文字转视频、Omni 参考（从参考视频/图片/音频/文字生成视频） | | AI Image | `scripts/ai_image.py` | [ai_image.md](references/ai_image.md) | 文字生图和 AI 图片编辑（10+ 模型） | | Remove BG | `scripts/remove_bg.py` | [remove_bg.md](references/remove_bg.md) | 去除图片背景 — 商品模特图工作流的第一步 | | Product Avatar | `scripts/product_avatar.py` | [product_avatar.md](references/product_avatar.md) | 商品模特展示图；`list-avatars`/`list-categories` 浏览模板 | | Text2Voice | `scripts/text2voice.py` | [text2voice.md](references/text2voice.md) | 文字转语音音频 | | Voice | `scripts/voice.py` | [voice.md](references/voice.md) | 声音列表/搜索、声音克隆、删除自定义声音 | | Board | `scripts/board.py` | [board.md](references/board.md) | 看板管理 — 整理结果，在网页上查看/编辑 | | User | `scripts/user.py` | [user.md](references/user.md) | 积分余额和使用记录 | | 模型名称映射 | — | [model_mapping.md](references/model_mapping.md) | 模型 API 名称与用户展示名称的映射规则 | > **请阅读各模块的参考文档了解用法、选项和代码示例。** > 本地文件（图片/音频/视频）作为参数传入时会自动上传——无需手动上传步骤。 --- ## 创作指南 > **核心原则：** 从用户意图出发，而非从 API 出发。 > 分析用户想要实现什么，然后选择合适的工具、模型和参数。 > > **语言规则（强制）：** 用户用什么语言沟通，生成内容就用什么语言。用户说中文，则 `--text`（TTS 文案）、`--prompt`（提示词）、脚本内容等全部使用中文。除非用户明确要求使用其他语言。 ### 第一步 — 意图分析 每次用户请求内容时，识别以下维度： | 维度 | 自问 | 兜底方案 | |-----------|-------------|----------| | **输出类型** | 图片？视频？音频？组合？ | 必须询问 | | **用途** | 营销？教育？社交媒体？个人？ | 通用社交媒体 | | **源素材** | 用户有什么？缺什么？ | 必须询问 | | **风格/调性** | 专业？休闲？活泼？权威？ | 专业且友好 | | **时长** | 输出应该多长？ | 片段 5–15 秒，数字人 30–60 秒 | | **语言** | 什么语言？需要字幕吗？ | **必须**匹配用户的语言（中文用户 → 中文提示词/文案/脚本） | | **渠道** | 将在哪里发布？ | 通用 | ### 第二步 — 工具选择 ``` 用户需要什么？ │ ├─ 真人对着镜头说话（数字人）？ │ → avatar4 或 video_gen 的原生音频模型 │ ├─ 把一张图片变成视频片段？ │ → video_gen --type i2v │ ├─ 纯文字生成视频？ │ → video_gen --type t2v │ ├─ 基于参考素材生成新视频（风格迁移、编辑）？ │ → video_gen --type omni │ ├─ 从文字提示生成图片？ │ → ai_image --type text2image │ ├─ 编辑/修改现有图片？ │ → ai_image --type image_edit │ ├─ 去除图片背景（如商品抠图）？ │ → remove_bg │ ├─ 将商品放到模特/数字人场景中？ │ → product_avatar（如果商品有背景，先用 remove_bg） │ → product_avatar list-avatars 浏览公共模板 │ ├─ 浏览可用的字幕样式？ │ → avatar4 list-captions │ ├─ 文字转语音？ │ → text2voice │ ├─ 查找声音 / 列出可用声音？ │ → voice list │ ├─ 从音频样本克隆声音？ │ → voice clone │ ├─ 删除自定义声音？ │ → voice delete │ ├─ 管理看板 / 在网页上查看结果？ │ → board (list, create, detail, tasks) │ ├─ 组合需求（如数字人 + 产品片段）？ │ → 使用配方（见第三步） │ └─ 超出当前能力范围？ → 见下方能力边界 ``` **快速路由参考表：** | 用户说… | 脚本和类型 | |----------------------------------------------|-------------| | "用这张照片和文字做一个数字人视频" | `avatar4.py`（直接传入本地图片路径） | | "用这张照片和我的录音生成视频" | `avatar4.py`（传入本地图片 + 音频路径） | | "把这张图片变成视频 / 图片转视频" | `video_gen.py --type i2v`（传入本地图片路径） | | "生成一个关于…的视频" | `video_gen.py --type t2v` | | "参考这张图片的风格生成新视频" | `video_gen.py --type omni` | | "生成一张图片 / 文字生图" | `ai_image.py --type text2image` | | "修改这张图片 / 换背景" | `ai_image.py --type image_edit` | | "去除图片背景 / 抠图" | `remove_bg.py` | | "把这个产品放到模特图上" | `product_avatar.py`（如果产品有背景，先用 `remove_bg.py`） | | "有哪些商品模特模板？" | `product_avatar.py list-avatars` | | "有哪些字幕样式？" | `avatar4.py list-captions` | | "把这段文字转成语音 / 音频" | `text2voice.py` | | "有哪些可用的声音？/ 找一个女声" | `voice.py list --gender female` | | "用这段录音克隆声音" | `voice.py clone --audio <file>` | | "删除这个自定义声音" | `voice.py delete --voice-id <id>` | | "查看我的看板 / 看看生成了什么" | `board.py list` 或 `board.py tasks --board-id <id>` | | "创建一个新看板" | `board.py create --name "..."` | | "看看我还剩多少积分" | `user.py credit` | > **视频模型选择** — 参见 [references/video_gen.md](references/video_gen.md) § 模型推荐。 > **图片模型建议：** 所有图片任务默认使用**全能图片模型 V2** — 综合最强模型，画质最佳，支持 14 种宽高比、最高 4K、编辑时支持 14 张参考图。参见 [references/ai_image.md](references/ai_image.md) § 模型推荐。 > **商品模特图工作流：** 为获得最佳效果，使用两步流程：先用 `remove_bg.py` 获取 `bgRemovedImageFileId`，再用 `product_avatar.py` 配合 `--product-image-no-bg`。使用 `product_avatar.py list-avatars` 浏览公共模板并获取 `avatarId`。参见 [references/product_avatar.md](references/product_avatar.md) § 完整工作流。 > **avatar4 字幕样式：** 使用 `avatar4.py list-captions` 查看可用字幕样式，然后通过 `--caption` 传入 `captionId`。 > **数字人建议 — avatar4 vs video_gen 原生音频：** > 部分 video_gen 模型（如地表最强模型S2.0-白名单版（支持上传真人图）、可灵 V3、电影级画质模型 V3.1）支持原生音频，可以生成比 avatar4 **画质更好**的数字人视频。但它们**最大时长更短**（5–15 秒）且**价格明显更高**。Avatar4 支持单段最长 120 秒，成本低得多。 > **经验法则：** 目标视频时长小于 15 秒时，默认使用 video_gen 原生音频模型；否则默认使用 avatar4。但你应该始终向用户说明各自优缺点并征求偏好。 ### 第三步 — 简单 vs 复杂 **简单请求** — 用户需求明确，素材就绪 → 直接根据参考文档处理。 **复杂请求** — 用户给出的是*目标*（如"做一个推广视频"、"解释 AI 的工作原理"），而非直接的 API 指令。遵循以下通用工作流： 1. **拆解与确认：** 向用户询问目标受众、核心信息、期望时长，以及他们目前有哪些素材（照片、脚本）。 2. **确定路线：** - *有人像照片 + 需要解说* → 使用 `avatar4`（数字人）。 - *有产品/参考照片* → 使用 `video_gen --type i2v` 或 `omni`。 - *没有素材，纯视觉概念* → 使用 `video_gen --type t2v`。 - *两者都需要* → 规划混合方案（数字人解说 + B-roll 插入）。 3. **组织内容：** - 撰写结构化脚本（开头吸引 → 主体/讲解 → 行动号召）。 - 在 TTS 脚本中添加 `<break time="0.5s"/>` 标签以实现自然节奏。 - 对于视觉内容，撰写详细提示词，涵盖主体 + 动作 + 光照 + 镜头。 4. **处理长内容（>120 秒）：** 如果脚本超过单个 `avatar4` 任务的 120 秒限制，在自然句子边界处将其分成逻辑段落（如每段 60 秒）。使用 `submit` 命令并行提交任务，确保各段参数（声音/模型）一致，并按顺序交付。 --- ## 执行前协议 > 每个生成任务前都遵循此协议。 1. **估算费用** — 视频任务使用 `video_gen.py estimate-cost`，图片任务使用 `ai_image.py estimate-cost`；avatar4 费用取决于视频时长；product_avatar 固定 0.5 积分；text2voice 固定 0.1 积分 2. **验证参数** — 确保模型、宽高比、分辨率和时长兼容（使用 `list-models` 检查） 3. **询问缺失的关键参数** — 如果用户未指定影响输出的重要参数，在执行前询问。各模块关键参数： - **video_gen**：时长、宽高比、模型 - **ai_image**：宽高比、分辨率、模型、图片数量 - **avatar4**：（通常由输入决定，但如未指定声音则需确认） - **text2voice**：声音选择 - 这些参数显著影响输出质量和积分消耗，与用户确认可以避免浪费积分和令人失望的结果。 4. **首次提交前确认** — 在会话中首次执行生成任务前，展示方案（工具、模型、参数、费用估算）并在一条消息中询问：(a) 是否执行？(b) 逐个确认后续任务，还是本次会话剩余任务自动执行？如果用户一开始就说"直接做"，视为自动执行。即使在自动执行模式下，仍需询问缺失的关键参数。 ## 模型名称展示规则 > **严格遵守：对外展示用中文名，构造命令用 API 名。** 完整映射表见 [references/model_mapping.md](references/model_mapping.md)。 1. **与用户沟通时**（推荐模型、报告结果、确认方案）：使用映射后的**展示名称**，绝不暴露 API 真名。 2. **构造命令时**（`--model` 参数）：使用 **API 名称**。 3. **用户说出展示名称时**：自动识别并映射为 API 名称执行。 4. **禁止使用的模型**：`Lite`、`Pro`、`Plus`、`Best` — 不推荐、不使用、不提及。 **关键映射速查：** | API 名称 → `--model` 参数 | 展示名称 → 对用户说 | |---|---| | `Standard` | 地表最强模型S2.0-白名单版（支持上传真人图） | | `Fast` | 地表最强模型S2.0 Fast-白名单版（支持上传真人图） | | `Kling V3` | 可灵 V3 | | `Kling O3` | 可灵 O3 | | `Sora 2 Pro` | 拟真世界模型 2 Pro | | `Veo 3.1` | 电影级画质模型 V3.1 | | `Veo 3.1 Fast` | 电影级画质模型 V3.1 fast | | `Nano Banana 2` | 全能图片模型 V2 | | `GPT Image 1.5` | 强语义理解模型 V1.5 | | `Kontext-Pro` | 强上下文一致性模型 pro | | `Imagen 4` | 照片级写实模型 V4 | --- ## Agent 行为协议 ### 执行期间 1. **直接传入本地路径** — 脚本会在提交任务前自动将本地文件上传到 S3 2. **并行化独立步骤** — 相互独立的生成任务可以并发执行 3. **保持跨段一致性** — 生成多段内容时，使用相同的参数 ### 执行完成后（必须全部执行） > **模型名称必须使用展示名称，不得直接输出 API 名称。** 映射规则参见 [model_mapping.md](references/model_mapping.md)。 任务完成后，Agent **必须**按照以下模板向用户展示结果。将占位符替换为实际值。 **视频结果模板：** ``` 🎬 视频已生成完成 [👉 点击播放/下载视频](<VIDEO_URL>) · 时长：<DURATION> · 画幅：<ASPECT_RATIO> · 模型：<MODEL_NAME> · 消耗：<COST> credits [🔗 项目链接（查看/编辑/下载）](https://video.tekan.cn/board/<BOARD_ID>?boardResultId=<BOARD_TASK_ID>) 不满意的话可以告诉我，我帮你调整后重新生成。 ``` **图片结果模板：** ``` 🖼 图片已生成完成 [👉 点击查看/下载图片](<IMAGE_URL>) · 分辨率：<RESOLUTION> · 模型：<MODEL_NAME> · 消耗：<COST> credits [🔗 项目链接（查看/编辑/下载）](https://video.tekan.cn/board/<BOARD_ID>?boardResultId=<BOARD_TASK_ID>) 不满意的话可以告诉我，我帮你调整后重新生成。 ``` **模板使用规则：** 1. `<MODEL_NAME>` 必须使用展示名称（例如：显示「地表最强模型S2.0-白名单版（支持上传真人图）」而非"Standard"，显示"强语义理解模型 V1.5"而非"GPT Image 1.5"） 2. **资源链接与项目链接必须使用方括号 Markdown 格式** `[说明文字](完整URL)`，禁止单独粘贴裸 URL，以免长链接在对话中被截断导致无法点击 3. `<BOARD_ID>` 和 `<BOARD_TASK_ID>` 从任务返回结果中提取，**项目链接不可省略** 4. 多个输出时逐个编号展示 5. 其他类型任务（数字人、TTS、去背景等）参照以上格式，按实际字段调整 ### 错误处理 参见 [references/error_handling.md](references/error_handling.md) 了解错误码、任务级失败和恢复决策树。 --- ## 能力边界 | 能力 | 状态 | 脚本 | |---------------------------------|--------------|----------------------------------------------------------| | 照片数字人 / 口播视频 | 可用 | `scripts/avatar4.py` | | 字幕样式 | 可用 | `scripts/avatar4.py list-captions` | | 积分管理 | 可用 | `scripts/user.py` | | 图片转视频 (i2v) | 可用 | `scripts/video_gen.py --type i2v` | | 文字转视频 (t2v) | 可用 | `scripts/video_gen.py --type t2v` | | Omni 参考视频 | 可用 | `scripts/video_gen.py --type omni` | | 文字生图 | 可用 | `scripts/ai_image.py --type text2image` | | 图片编辑 | 可用 | `scripts/ai_image.py --type image_edit` | | 去除背景 | 可用 | `scripts/remove_bg.py` | | 商品模特图 / 图片替换 | 可用 | `scripts/product_avatar.py` | | 商品模特模板 | 可用 | `scripts/product_avatar.py list-avatars` / `list-categories` | | 文字转语音 (TTS) | 可用 | `scripts/text2voice.py` | | 声音列表 / 搜索 | 可用 | `scripts/voice.py list` | | 声音克隆 | 可用 | `scripts/voice.py clone` | | 删除自定义声音 | 可用 | `scripts/voice.py delete` | | 看板管理 | 可用 | `scripts/board.py` | | 看板任务浏览 | 可用 | `scripts/board.py tasks` / `task-detail` | | 营销视频 (m2v) | 无模块 | 建议使用 [特看视频](https://video.tekan.cn) 网页端 | > 承诺一个没有对应模块的能力会导致工作流中途卡住，损害用户信任。如果请求超出上表范围，请建议用户使用[特看视频](https://video.tekan.cn)网页端。

path: LICENSE.txt
content: 
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

path: README.md
content: # 特看视频 AI Skill

一个 AI Agent 技能，让你用自然语言描述即可生成专业视频、图片、数字人和音频——无需手动操作，零学习成本。

本技能遵循 [Agent Skills 规范](https://agentskills.io/specification)，兼容任何支持该规范的 AI 编程助手（Cursor、Claude Code 等）。

## 你能做什么？

安装技能后，直接用自然语言告诉 AI 你需要什么。以下是一些示例：

### 一句话创作

| 你说… | 你得到… |
|------------|------------|
| "用不同风格生成 5 张产品图" | 5 张 AI 生成的图片，并行批量生产 |
| "做一个 10 秒的猫在日落沙滩散步的视频" | 一段 AI 生成的视频片段 |
| "用这张照片做一个我介绍新产品的视频" | 一段带口型同步的数字人视频 |
| "把这瓶水放到时尚模特身上" | 一张模特手持你产品的合成图 |
| "用温柔的女声朗读这段话" | 一段文字转语音音频文件 |
| "用这段录音克隆我的声音" | 一个可用于未来任何配音的自定义声音 |
| "去掉这张产品照片的背景" | 一张干净的抠图 PNG |
| "把这张照片的背景换成热带沙滩" | 一张 AI 编辑后的图片 |
| "做一个模仿这段参考视频风格的新视频" | 一段风格迁移视频 |

### 组合工作流

真正的强大之处在于将这些能力串联在一起。描述一个目标，AI 就会为你编排整条流水线：

**电商产品发布套件** — "我有一张产品照片，帮我做一整套营销素材。" AI 会去除背景、生成多个姿势的商品模特展示图、创建一段数字人口播视频介绍产品，并批量生成不同宽高比的社交媒体海报。一次对话，一套完整的营销素材库。

**教育视频系列** — "把这篇关于气候变化的文章做成视频系列。" AI 为每个段落编写脚本、为关键概念生成插图、创建数字人主持人视频，并用选定的声音制作配音。一篇文章生成多段视频。

**多语言内容** — "把这份产品演示脚本做成英文、日文和西班牙文版本。" AI 用各语言合适的声音生成文字转语音，然后用相同的人像但不同的音轨创建数字人视频。一份脚本即刻生成多语言内容。

**故事板转视频** — "我要一个 30 秒的动画广告，包含这 4 个场景。" AI 为每个场景生成一张图片、将每张图片转换为视频片段，交付一组按顺序排列、可直接剪辑的视频。从文字描述到动态画面，一次会话搞定。

**品牌风格统一批量内容** — "生成本周 10 条社交媒体帖子，全部用我们的品牌风格。" AI 先创建一张确定视觉风格的参考图，然后用相同风格批量生产不同主题的变体。整个内容日历风格一致。

**数字代言人** — "我想当频道的门面，但不想每期都拍摄。" 上传一张人像并克隆你的声音，之后只需提供每期的脚本。AI 会生成你的形象和声音的数字人视频，配以 AI 生成的插图作为画面素材。

**带旁白的产品演示** — "用这些截图演示这个 App 的功能。" AI 根据你的功能描述生成旁白、将每张截图转换为视频片段，并创建数字人开场/结尾片段。从静态截图生成精美的产品演示。

### 使用方式

1. **安装** — `npx skills add topviewai/skill`
2. **登录** — 运行 `python scripts/auth.py login`（仅需一次）
3. **创作** — 用自然语言告诉 AI 你想要什么。它会自动选择合适的工具、模型和参数。

**你的想象力是唯一的限制** — 以上示例只是起点。你可以自由组合图片生成、视频创作、数字人、声音克隆、去除背景等能力，构建你能想到的任何工作流。用你自己的话描述创意愿景，AI 会找到实现它的方法。

## 安装

```bash
npx skills add topviewai/skill
```

## 可用模块

技能底层使用以下模块。你不需要直接调用它们——只需描述你想要什么，AI Agent 会自动选择合适的工具。

| 模块 | 脚本 | 说明 |
|--------|--------|-------------|
| [Avatar4](references/avatar4.md) | `scripts/avatar4.py` | 从照片生成数字人视频 |
| [Video Gen](references/video_gen.md) | `scripts/video_gen.py` | 图片转视频、文字转视频、Omni 参考视频生成 |
| [AI Image](references/ai_image.md) | `scripts/ai_image.py` | 文字生图和 AI 图片编辑（10+ 模型） |
| [Remove BG](references/remove_bg.md) | `scripts/remove_bg.py` | 去除图片背景 |
| [Product Avatar](references/product_avatar.md) | `scripts/product_avatar.py` | 商品模特展示图，支持模板浏览 |
| [Text2Voice](references/text2voice.md) | `scripts/text2voice.py` | 文字转语音音频 |
| [Voice](references/voice.md) | `scripts/voice.py` | 声音列表/搜索、声音克隆、删除自定义声音 |
| [Board](references/board.md) | `scripts/board.py` | 看板管理 — 整理和在网页上查看结果 |
| [User](references/user.md) | `scripts/user.py` | 积分余额和使用记录 |

## 配置

需要 Python 3.10+ 和特看视频账号。通过设备授权流程登录：

```bash
pip install -r scripts/requirements.txt
python scripts/auth.py login
```

登录后凭证自动保存到本地，所有模块自动加载。

## 许可证

参见 [LICENSE.txt](LICENSE.txt)。


path: references/ai_image.md
content: # AI Image 模块

从文字提示生成图片，或使用 AI 模型编辑现有图片。

## 支持的任务类型

| 类型 | 说明 | 必需参数 |
|------|-------------|-----------------|
| `text2image` | **文字生图** — 从文字提示生成图片 | `--model`, `--prompt`, `--aspect-ratio` |
| `image_edit` | **图片编辑** — 通过提示词 + 参考图片编辑图片 | `--model`, `--prompt`, `--aspect-ratio`, `--input-images` |

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |
| `list-models` | 查看模型、约束条件和支持的宽高比 | 否 |
| `estimate-cost` | 执行前估算积分费用 | 否 |

## 用法

```bash
python {baseDir}/scripts/ai_image.py <subcommand> --type <text2image|image_edit> [options]
```

## 示例

### 列出模型

```bash
python {baseDir}/scripts/ai_image.py list-models --type text2image
python {baseDir}/scripts/ai_image.py list-models --type image_edit --json
```

### 文字生图

```bash
python {baseDir}/scripts/ai_image.py run \
  --type text2image \
  --model "Nano Banana 2" \
  --prompt "黄昏时分的未来都市天际线，霓虹灯倒映在潮湿的街道上" \
  --aspect-ratio "16:9" \
  --resolution "2K" \
  --count 2
```

固定价格模型（无需指定分辨率）：

```bash
python {baseDir}/scripts/ai_image.py run \
  --type text2image \
  --model "GPT Image 1.5" \
  --prompt "一只猫的水彩画" \
  --aspect-ratio "1:1"
```

### 图片编辑

```bash
python {baseDir}/scripts/ai_image.py run \
  --type image_edit \
  --model "Nano Banana 2" \
  --prompt "将背景替换为雪山风景" \
  --aspect-ratio "auto" \
  --resolution "2K" \
  --input-images photo.jpg
```

多图参考：

```bash
python {baseDir}/scripts/ai_image.py run \
  --type image_edit \
  --model "Nano Banana 2" \
  --prompt "融合两张图片的风格" \
  --aspect-ratio "1:1" \
  --resolution "2K" \
  --input-images style.jpg content.jpg \
  --count 2
```

### 费用估算

```bash
python {baseDir}/scripts/ai_image.py estimate-cost \
  --type text2image --model "Nano Banana 2" --resolution "2K" --count 2
```

### 下载结果

```bash
python {baseDir}/scripts/ai_image.py run \
  --type text2image --model "Nano Banana 2" \
  --prompt "北极光" --aspect-ratio "16:9" --resolution "2K" \
  --output-dir ./results
```

## 选项

| 选项 | 说明 |
|--------|-------------|
| `--type` | `text2image` 或 `image_edit`（必需） |
| `--model` | 模型 **display name**（必需） |
| `--prompt` | 文字提示词（必需） |
| `--aspect-ratio` | 宽高比（必需），如 `"16:9"`、`"1:1"`、`"auto"` |
| `--resolution` | `"512p"`、`"1K"`、`"2K"`、`"4K"` — 取决于模型 |
| `--count` | 图片数量（1-4，默认：1） |
| `--board-id` | 看板 ID |
| `--input-images` | 参考图片 fileId 或本地路径，多个用空格分隔（仅 image_edit）。例如 `--input-images photo.jpg` 或 `--input-images style.jpg content.jpg` |
| `--timeout` | 最大轮询时间（默认：300） |
| `--interval` | 轮询间隔（默认：3） |
| `--output-dir` | 将结果下载到指定目录 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制 stderr 状态消息 |

## 模型推荐

> **全能图片模型 V2 是所有图片任务的首选推荐。**
> 综合画质最佳，支持 14 种宽高比、最高 4K、编辑时支持 14 张参考图。
>
> 向用户展示模型时使用展示名称，构造命令时使用 API 名称。完整映射见 [model_mapping.md](model_mapping.md)。

| 使用场景 | 推荐模型（展示名） | 对应 API 名称 | 原因 |
|----------|--------------------|---|-----|
| **综合最佳（默认）** | **全能图片模型 V2** | `Nano Banana 2` | 综合最强模型 |
| **低预算** | Seedream 4.0 (0.15/张) | `Seedream 4.0` | 最低费用 |
| **无需指定分辨率** | 强语义理解模型 V1.5, 强上下文一致性模型 pro | `GPT Image 1.5`, `Kontext-Pro` | 无需 resolution 参数 |
| **自动宽高比** | Seedream 5.0, Seedream 4.5 | `Seedream 5.0`, `Seedream 4.5` | 支持 `auto` ratio |

**默认选择：**
- text2image → 全能图片模型 V2（API: `Nano Banana 2`）
- image_edit → 全能图片模型 V2（API: `Nano Banana 2`）

## 注意事项

- `aspectRatio` 始终必需；image_edit 模型额外支持 `"auto"`
- `resolution` 部分模型必需，部分模型禁止 — 通过 `list-models` 检查
- **照片级写实模型 V4**（API: `Imagen 4`）仅支持 text2image，不支持 image_edit


path: references/auth.md
content: # Auth 模块

使用 OAuth 2.0 设备授权流程完成认证。
只需运行一次 — 凭证会保存在本地，其他所有模块自动加载。

## 子命令

| 子命令 | 说明 |
|------------|-------------|
| `login`  | 完整流程：初始化 → 输出链接并尝试打开浏览器 → 轮询 → 保存凭证 |
| `poll`   | 恢复之前中断的登录（仅用于恢复） |
| `logout` | 删除已保存的凭证文件 |
| `status` | 显示当前登录状态（uid、email、name、遮蔽的 api_key） |

## 用法

```bash
python {baseDir}/scripts/auth.py <subcommand>
```

## `login` — 完整登录流程

```bash
python {baseDir}/scripts/auth.py login
```

此命令处理**整个登录流程**：
1. 调用远程 OAuth 服务器启动设备会话
2. 输出授权链接（`URL: https://...`），并尝试自动打开浏览器（无浏览器环境下静默跳过）
3. 自动轮询直到用户授权（或会话过期）
4. 成功后将凭证保存到 `~/.tekan/credentials.json`

**关键：无论浏览器是否成功打开，Agent 都必须从命令输出中提取授权链接，并在引导消息中以 `[👉 点击此处完成登录授权](实际URL)` 的 Markdown 链接格式提供给用户**；详见主 SKILL 中「安装完成与登录引导」步骤 2 的详细规则和正反示例。

### 完整流程

```
Agent 运行：  python auth.py login
                 ↓ 输出授权链接（URL: https://...），尝试打开浏览器（可能静默失败）
Agent 发送引导消息：以 [👉 点击此处完成登录授权](URL) 格式展示可点击登录链接
                 ↓ 命令持续自动轮询
                 ↓ 用户在浏览器中登录并点击授权
                 ↓ 命令检测到授权通过，保存凭证
凭证已保存到 ~/.tekan/credentials.json ✓
```

## `poll` — 恢复中断的登录

```bash
python {baseDir}/scripts/auth.py poll
```

**仅在** `login` 被中断时使用（如轮询期间终端被关闭）。
从 `~/.tekan/pending_device.json` 读取待处理的会话并恢复轮询。

## `status` — 检查登录状态

```bash
python {baseDir}/scripts/auth.py status
```

已登录时的输出：

```
Logged in
  uid:         NsDAaOPF4jLuAie4ewyg
  name:        John Doe
  email:       [REDACTED_SECRET]
  [REDACTED_SECRET]
  charge_type: pro
  authorized:  2026-03-04T10:00:00+00:00
  file:        /Users/you/.tekan/credentials.json
```

## `logout` — 删除凭证

```bash
python {baseDir}/scripts/auth.py logout
```

## 凭证文件

保存位置：`~/.tekan/credentials.json`。文件权限设置为 `0600`（仅所有者可读写）。

## 凭证加载优先级

所有模块通过 `shared/config.py` 加载凭证：

1. `~/.tekan/credentials.json`（由 `auth.py login` 设置）
2. 报错 — 提示运行 `auth.py login`

## Agent 规则

- **永远不要** 让用户自己运行 `python auth.py login` 或其他任何命令。
- **始终** 自己运行 `auth.py login`，从输出中提取授权链接，并以 `[👉 点击此处完成登录授权](实际URL)` 格式展示给用户（与 SKILL 登录模板一致，禁止裸贴 URL）。


path: references/avatar4.md
content: # Avatar4 模块

从一张照片生成逼真的数字人口播视频。

## 使用场景

当你需要从一张人像照片创建口播视频时 — 适用于营销、教育、产品演示或社交媒体内容。

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |
| `list-captions` | 查看可用的字幕样式，用于 `--caption` | 否 |

## 用法

```bash
python {baseDir}/scripts/avatar4.py <subcommand> [options]
```

## 示例

### `run` — 完整流程（默认）

```bash
python {baseDir}/scripts/avatar4.py run \
  --image <fileId> \
  --text "大家好，欢迎来到我的频道！" \
  --voice <voiceId>
```

本地图片（自动上传）：

```bash
python {baseDir}/scripts/avatar4.py run \
  --image /path/to/photo.png \
  --text "欢迎使用特看视频。" \
  --voice LaaHTrXZCVOQmB1wZUhnwmbPTAWDFtW6
```

音频驱动模式：

```bash
python {baseDir}/scripts/avatar4.py run \
  --image /path/to/photo.png \
  --audio /path/to/recording.mp3
```

下载结果：

```bash
python {baseDir}/scripts/avatar4.py run \
  --image <fileId> --text "你好！" --voice <voiceId> \
  --output result.mp4
```

### `submit` — 批量

```bash
T1=$(python {baseDir}/scripts/avatar4.py submit \
  --image img1.png --text "第一段口播内容" --voice <id> -q)
T2=$(python {baseDir}/scripts/avatar4.py submit \
  --image img2.png --text "第二段口播内容" --voice <id> -q)

python {baseDir}/scripts/avatar4.py query --task-id "$T1"
python {baseDir}/scripts/avatar4.py query --task-id "$T2"
```

### `query` — 恢复

```bash
python {baseDir}/scripts/avatar4.py query --task-id <taskId> --timeout 1200
```

## 选项

### `run` 和 `submit`

| 选项 | 说明 |
|--------|-------------|
| `--image ID` | 图片 fileId 或本地文件路径（必需） |
| `--text TEXT` | 数字人朗读的 TTS 文本（与 `--voice` 配合使用） |
| `--voice ID` | 声音 ID（使用 `--text` 时必需） |
| `--audio ID` | 音频 fileId 或本地路径，用于音频驱动模式（`--text` 的替代方案） |
| `--mode MODE` | `avatar4`（默认）或 `avatar4Fast` |
| `--motion TEXT` | 自定义动作描述（最多 600 字符） |
| `--caption ID` | 字幕样式 ID |
| `--save-avatar` | 将该图片保存为可复用的自定义数字人 |
| `--notice-url URL` | 完成通知的 Webhook URL |

### 轮询（`run` 和 `query`）

| 选项 | 说明 |
|--------|-------------|
| `--timeout SECS` | 最大轮询时间，单位秒（默认：600） |
| `--interval SECS` | 轮询间隔，单位秒（默认：5） |

### 全局

| 选项 | 说明 |
|--------|-------------|
| `--output FILE` | 将结果视频下载到本地路径 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制 stderr 状态消息 |

## 模式对比

| 模式 | 最大时长 | 速度 | 画质 |
|------|-------------|-------|---------|
| `avatar4` | 120 秒 | 慢 | 最佳 |
| `avatar4Fast` | 120 秒 | 快 | 良好 |

## 字幕样式发现

使用 `list-captions` 查找可用的字幕样式，然后将 `captionId` 传给 `--caption`：

```bash
# 列出所有字幕样式
python {baseDir}/scripts/avatar4.py list-captions

# 在生成任务中使用 captionId
python {baseDir}/scripts/avatar4.py run \
  --image photo.png --text "你好！" --voice <voiceId> \
  --caption <captionId>
```

每个字幕条目包含一个 `thumbnail` URL 用于视觉预览。

## 输出

`run` 和 `query` 输出视频 URL。配合 `--json` 可获取完整 API 响应。


path: references/board.md
content: # Board 模块

管理看板，用于组织生成的结果（图片、视频、音频）。用户可通过看板链接在网页上查看和编辑结果。

## 使用场景

- **会话首次任务前** — 运行 `list --default -q` 获取默认看板 ID
- **用户想要新看板** — 运行 `create --name "..."`
- **用户想浏览结果** — 运行 `tasks --board-id <id>`
- **用户需要看板管理** — list、detail、update、delete

## 子命令

| 子命令 | 说明 |
|------------|-------------|
| `list` | 列出看板（分页）。使用 `--default` 供 Agent 自动发现 |
| `create` | 创建新看板 |
| `detail` | 获取看板详情（成员、分享令牌） |
| `update` | 重命名看板 |
| `delete` | 删除看板 |
| `tasks` | 列出看板中的任务（支持筛选） |
| `task-detail` | 获取单个任务的完整详情 |

## 用法

```bash
python {baseDir}/scripts/board.py <subcommand> [options]
```

## 示例

### 获取默认看板 ID（Agent 启动时）

```bash
BOARD_ID=$(python {baseDir}/scripts/board.py list --default -q)
```

Agent 应在会话开始时运行一次，之后所有任务复用该看板 ID。

### 列出所有看板

```bash
python {baseDir}/scripts/board.py list
```

输出（制表符分隔）：

```
board_123456    My First Board    5 tasks [default]
board_789012    Campaign Q3       12 tasks
```

### 创建新看板

```bash
python {baseDir}/scripts/board.py create --name "Campaign Q3"
```

输出新看板 ID。用户明确要求新看板时使用此命令。

### 查看看板详情

```bash
python {baseDir}/scripts/board.py detail --board-id <boardId>
```

### 重命名看板

```bash
python {baseDir}/scripts/board.py update --board-id <boardId> --name "Campaign Q4"
```

### 删除看板

```bash
python {baseDir}/scripts/board.py delete --board-id <boardId>
```

### 列出看板中的任务

```bash
python {baseDir}/scripts/board.py tasks --board-id <boardId>
```

带筛选条件：

```bash
python {baseDir}/scripts/board.py tasks --board-id <boardId> \
  --media-type video \
  --sort-field gmtCreate --sort-order desc \
  --page 1 --size 50
```

### 获取任务详情

```bash
python {baseDir}/scripts/board.py task-detail --task-id <taskId>
```

显示完整任务信息，包括编辑链接：

```
Task: task_abc123
  boardTaskId: bt_xyz789
  boardId:     board_abc123
  status:      success
  tool:        text2video
  cost:        10 credits
  edit: https://video.tekan.cn/board/board_abc123?boardResultId=bt_xyz789
```

## 选项

### `list`

| 选项 | 说明 |
|--------|-------------|
| `--default` | 仅输出默认看板 ID（供 Agent 自动发现） |
| `--page N` | 页码（默认：1） |
| `--size N` | 每页条数（默认：20） |

### `create`

| 选项 | 说明 |
|--------|-------------|
| `--name TEXT` | 看板名称，最长 200 字符（必需） |

### `detail`

| 选项 | 说明 |
|--------|-------------|
| `--board-id ID` | 看板 ID（必需） |

### `update`

| 选项 | 说明 |
|--------|-------------|
| `--board-id ID` | 看板 ID（必需） |
| `--name TEXT` | 新名称，最长 200 字符（必需） |

### `delete`

| 选项 | 说明 |
|--------|-------------|
| `--board-id ID` | 看板 ID（必需） |

### `tasks`

| 选项 | 说明 |
|--------|-------------|
| `--board-id ID` | 看板 ID（必需） |
| `--media-type` | 筛选：`image` / `video` |
| `--rating N` | 按评分筛选：0-3 |
| `--tool-category` | 按工具类别筛选 |
| `--tool-type` | 按工具类型筛选 |
| `--sort-field` | 排序字段：`gmtCreate` / `sortWeight` |
| `--sort-order` | `asc` / `desc` |
| `--page N` | 页码 |
| `--size N` | 每页条数 |

### `task-detail`

| 选项 | 说明 |
|--------|-------------|
| `--task-id ID` | 任务 ID（必需） |

### 全局

| 选项 | 说明 |
|--------|-------------|
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

## 看板网页 URL

在网页上查看和编辑看板结果：

```
https://video.tekan.cn/board/{boardId}
```

查看特定结果：

```
https://video.tekan.cn/board/{boardId}?boardResultId={boardTaskId}
```

## Agent 协议

1. **会话开始**：运行 `board.py list --default -q` 获取默认看板 ID
2. **传递给所有任务**：为每个生成命令添加 `--board-id <id>`
3. **任务完成后**：如果响应中包含 `boardTaskId`，向用户展示编辑链接
4. **用户想要新看板**：运行 `board.py create --name "..."` 并使用返回的 ID
5. **用户指定看板**：使用用户提供的看板 ID


path: references/error_handling.md
content: # 错误处理指南

发生错误时，应诊断并采取行动 — 永远不要直接向用户展示原始错误信息。

## API 错误码

| 错误码 | 含义 | 处理方式 |
|------|---------|--------|
| `4100` | 积分不足 | 告知用户余额不足。显示当前余额，建议到[特看视频](https://video.tekan.cn/subscription?action=recharge)充值。 |
| `4007` | 存在未完成的任务 | 上一个任务仍在运行。等待 30 秒后重试，或询问用户是否要取消上一个任务。 |
| `4000` / `4001` / `4003` | 参数错误 | 检查模型支持的参数（宽高比、分辨率、时长）。修正后重试。 |
| `5003` | 服务器繁忙 | 等待 10-30 秒后重试。如果持续出现，建议尝试其他模型。 |
| `5000` / `5001` | 内部服务器错误 | 重试一次。如果持续出现，建议用户稍后再试或使用网页端。 |
| `6001` | 安全问题 | 内容可能触发了安全过滤器。建议修改提示词。 |

## 任务级失败

| 情况 | 处理方式 |
|-----------|--------|
| 任务 `status: fail` | 读取 `errorMsg`。常见原因：模型服务不可用（尝试其他模型）、内容策略违规（修改提示词）。 |
| 任务超时（退出码 2） | 任务可能仍在运行。使用 `query --task-id <id> --timeout 1200` 恢复轮询。不要重新提交 — 那会浪费积分。 |
| 部分成功（`generatingCount > 1`） | 部分子任务可能失败而其他成功。检查每个视频的 `status`。仅对失败的重试。 |
| 上传失败 | 检查文件格式（必须是 png/jpg/jpeg/bmp/webp/mp3/wav/m4a/mp4/avi/mov）和文件大小。重试一次。 |

## 恢复决策树

```
发生错误
├─ API 错误码？
│  ├─ 4100 → 告知用户：积分不足
│  ├─ 4007 → 等待后重试
│  ├─ 400x → 修正参数，重试
│  └─ 500x → 重试一次，然后建议稍后再试
├─ 任务状态 = fail？
│  ├─ "Model service unavailable" → 尝试其他模型
│  ├─ 内容策略违规 → 修改提示词
│  └─ 未知原因 → 使用相同参数重试一次
└─ 超时？
   → 使用 `query --task-id <id>` 恢复轮询（不要重新提交）
```


path: references/model_mapping.md
content: # 模型名称映射规则

本文件定义了 API 模型名称与用户展示名称之间的映射关系。

## 核心规则

1. **构造命令时**：始终使用 `API 名称`（即 `--model` 参数的值）
2. **与用户沟通时**：始终使用 `展示名称`（中文名）
3. **用户说出展示名称时**：自动识别并映射为对应的 API 名称
4. **标记为「禁止使用」的模型**：不得向用户推荐，也不得主动使用

---

## 禁止使用的模型

以下模型不得推荐、不得使用。即使 `list-models` 返回了它们，也应忽略。

| API 名称 | 原因 |
|----------|------|
| `Lite` | 已下线 |
| `Pro` | 已下线 |
| `Plus` | 已下线 |
| `Best` | 已下线 |

---

## 视频模型映射

### 图生视频 / 文生视频 / Omni 参考

| API 名称 | 展示名称 | 供应商展示名 |
|----------|---------|-------------|
| `Standard` | 地表最强模型S2.0-白名单版（支持上传真人图） | seedance |
| `Fast` | 地表最强模型S2.0 Fast-白名单版（支持上传真人图） | seedance |
| `Seedance 1.5 Pro` | Seedance 1.5 Pro | seedance |
| `Seedance 1.0 Pro` | Seedance 1.0 Pro | seedance |
| `Seedance 1.0 Pro Fast` | Seedance 1.0 Pro Fast | seedance |
| `Kling V3` | 可灵 V3 | 可灵 |
| `Kling V3 Reference Video` | 可灵 V3 Reference Video | 可灵 |
| `Kling O3` | 可灵 O3 | 可灵 |
| `Kling O3 Reference-to-Video` | 可灵 O3 Reference-to-Video | 可灵 |
| `Kling 2.6` | 可灵 2.6 | 可灵 |
| `Kling O1 Reference-to-Video` | 可灵 O1 Reference-to-Video | 可灵 |
| `Kling 2.5 Turbo Pro` | 可灵 2.5 Turbo Pro | 可灵 |
| `Kling 2.5 Turbo Std` | 可灵 2.5 Turbo Std | 可灵 |
| `Sora 2` | 拟真世界模型 V2 | 拟真世界模型 |
| `Sora 2 Pro` | 拟真世界模型 2 Pro | 拟真世界模型 |
| `Veo 3.1` | 电影级画质模型 V3.1 | 电影级画质模型 |
| `Veo 3.1 Reference to video` | 电影级画质模型 V3.1 Reference to video | 电影级画质模型 |
| `Veo 3.1 Fast` | 电影级画质模型 V3.1 fast | 电影级画质模型 |
| `Veo 3.1 Fast Reference to video` | 电影级画质模型 V3.1 fast Reference to video | 电影级画质模型 |
| `MiniMax-Hailuo-02` | 海螺 V2 | 海螺 |
| `MiniMax-Hailuo-2.3` | 海螺 V2.3 | 海螺 |
| `MiniMax-Hailuo-2.3-Fast` | 海螺-2.3-Fast | 海螺 |
| `Vidu Q3 Pro` | Vidu Q3 Pro | Vidu |
| `Vidu Q2 Reference to Video` | Vidu Q2 Reference to Video | Vidu |
| `Vidu Q2` | Vidu Q2 | Vidu |
| `Wan 2.6` | 万象 V2.6 | 万象 |

### 视频编辑

| API 名称 | 展示名称 | 供应商展示名 |
|----------|---------|-------------|
| `Standard` | 地表最强模型S2.0-白名单版（支持上传真人图） | seedance |
| `Fast` | 地表最强模型S2.0 Fast-白名单版（支持上传真人图） | seedance |
| `Kling O3 Video-Edit` | 可灵 O3 Video-Edit | 可灵 |
| `Kling O1 Video-Edit` | 可灵 O1 Video-Edit | 可灵 |

### 动态控制

| API 名称 | 展示名称 | 供应商展示名 |
|----------|---------|-------------|
| `Kling Motion Control Pro V3` | 可灵 Motion Control Pro V3 | 可灵 |
| `Kling Motion Control Std V3` | 可灵 Motion Control Std V3 | 可灵 |
| `Kling Motion Control Pro` | 可灵 Motion Control Pro | 可灵 |
| `Kling Motion Control Std` | 可灵 Motion Control Std | 可灵 |

---

## 图片模型映射

适用于文生图 (text2image) 和图像编辑 (image_edit)。

| API 名称 | 展示名称 | 供应商展示名 |
|----------|---------|-------------|
| `Nano Banana 2` | 全能图片模型 V2 | 全能图片模型 |
| `Nano Banana Pro` | 全能图片模型 Pro | 全能图片模型 |
| `Nano Banana` | 全能图片模型 | 全能图片模型 |
| `Seedream 5.0` | Seedream 5.0 | Seedream |
| `Seedream 4.5` | Seedream 4.5 | Seedream |
| `Seedream 4.0` | Seedream 4.0 | Seedream |
| `GPT Image 1.5` | 强语义理解模型 V1.5 | 强语义理解模型 |
| `Kontext-Pro` | 强上下文一致性模型 pro | 强上下文一致性模型 |
| `Imagen 4` | 照片级写实模型 V4 | 照片级写实模型 |


path: references/product_avatar.md
content: # Product Avatar 模块

在数字人（模特）场景中替换商品图片 — 自动将你的产品放到模特手中。

## 使用场景

当你需要将产品放入数字人/模特场景中 — 例如让模特手持、佩戴或展示你的产品。

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |
| `list-categories` | 列出模特分类（用于筛选 `list-avatars`） | 否 |
| `list-avatars` | 浏览公共模特模板 | 否 |

## 用法

```bash
python {baseDir}/scripts/product_avatar.py <subcommand> [options]
```

## 完整工作流（去除背景 → 替换）

推荐的 Product Avatar V3 流程：

```
第 1 步：productImage → remove_bg.py → bgRemovedImageFileId
第 2 步：bgRemovedImageFileId + avatarId → product_avatar.py → resultImage
```

```bash
# 第 1 步 — 去除产品背景
python {baseDir}/scripts/remove_bg.py run --image product.png --json
# 记录输出中的 bgRemovedImageFileId

# 第 2 步 — 浏览可用模特并选择一个
python {baseDir}/scripts/product_avatar.py list-avatars --gender female

# 第 3 步 — 将产品替换到模特场景中
python {baseDir}/scripts/product_avatar.py run \
  --product-image-no-bg <bgRemovedImageFileId> \
  --avatar-id <avatarId>
```

如果你已经有去除背景的产品图片或自定义模板图片，可以跳过第 1 步直接进入第 2/3 步。

## 浏览模板

### 列出分类

```bash
python {baseDir}/scripts/product_avatar.py list-categories
```

### 浏览模特

```bash
# 所有模特
python {baseDir}/scripts/product_avatar.py list-avatars

# 按性别筛选
python {baseDir}/scripts/product_avatar.py list-avatars --gender female

# 按分类筛选
python {baseDir}/scripts/product_avatar.py list-avatars --category-ids <id1>,<id2>

# 按最新排序
python {baseDir}/scripts/product_avatar.py list-avatars --sort Newest
```

### `list-avatars` 选项

| 选项 | 说明 |
|--------|-------------|
| `--gender` | `male` 或 `female` |
| `--category-ids` | 分类 ID，逗号分隔（来自 `list-categories`） |
| `--ethnicity-ids` | 族裔 ID，逗号分隔 |
| `--sort` | `Popularity`（默认）或 `Newest` |
| `--page` | 页码（默认：1） |
| `--size` | 每页条数（默认：20） |

## 示例

### 自动模式（模特优先）

```bash
python {baseDir}/scripts/product_avatar.py run \
  --product-image product.png \
  --template-image template.png \
  --mode auto \
  --keep-target model
```

### 自动模式（产品优先）

```bash
python {baseDir}/scripts/product_avatar.py run \
  --product-image product.png \
  --template-image template.png \
  --mode auto \
  --keep-target product
```

### 手动模式 V4 (banana_pro)

```bash
python {baseDir}/scripts/product_avatar.py run \
  --product-image product.png \
  --template-image template.png \
  --mode manual \
  --version v4 \
  --location '[[10.5, 20.0], [30.5, 40.0]]'
```

### 使用已去背的产品图（来自 remove_bg.py）

```bash
python {baseDir}/scripts/product_avatar.py run \
  --product-image-no-bg <bgRemovedImageFileId> \
  --avatar-id <avatarId>
```

### 使用公共模特（来自 list-avatars）

```bash
python {baseDir}/scripts/product_avatar.py run \
  --product-image product.png \
  --avatar-id <avatarId>
```

## 选项

### `run` 和 `submit`

| 选项 | 说明 |
|--------|-------------|
| `--product-image` | 产品图片 fileId 或本地路径 |
| `--product-image-no-bg` | 已去背的产品图片 fileId 或本地路径 |
| `--template-image` | 模板/模特图片 fileId 或本地路径 |
| `--avatar-id` | 模特 ID（公共或私有数字人） |
| `--face-image` | 用户面部图片 fileId 或本地路径 |
| `--prompt` | 图片编辑提示词 |
| `--mode` | `auto`（自动）或 `manual`（基于坐标） |
| `--keep-target` | `model`（默认）或 `product`；仅用于自动模式 |
| `--version` | `v3`（默认）或 `v4`（banana_pro，支持手动模式） |
| `--location` | 产品坐标，JSON 二维数组 |
| `--product-size` | 产品尺寸（枚举值） |
| `--project-id` | 项目 ID |
| `--board-id` | 看板 ID |
| `--notice-url` | Webhook URL |

### 轮询 / 全局

| 选项 | 说明 |
|--------|-------------|
| `--timeout SECS` | 最大轮询时间（默认：600） |
| `--interval SECS` | 轮询间隔（默认：5） |
| `--output FILE` | 将结果下载到本地路径 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

## 模式对比

| 模式 | 版本 | 说明 |
|------|---------|-------------|
| `auto` + `model` | v3/v4 | 自动放置，保留模特姿势（默认） |
| `auto` + `product` | v3/v4 | 自动放置，保留产品外观 |
| `manual` | 仅 v4 | 基于坐标的手动放置（banana_pro） |

## 费用

固定：每任务 **0.5 积分**。失败的任务会退还积分。


path: references/remove_bg.md
content: # Remove Background 模块

去除产品图片的背景。通常是商品模特图工作流的第一步 — 输出的 `bgRemovedImageFileId` 直接传给 `product_avatar.py --product-image-no-bg`。

## 使用场景

- 当你需要一张透明/去背的产品图片，然后放入模特场景。
- 作为 Product Avatar V3 流程的第 1 步：**去除背景 → 替换产品图片**。

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |

## 用法

```bash
python {baseDir}/scripts/remove_bg.py <subcommand> [options]
```

## 示例

### `run` — 完整流程（默认）

```bash
python {baseDir}/scripts/remove_bg.py run --image product.png
```

下载结果图片：

```bash
python {baseDir}/scripts/remove_bg.py run --image product.png --output product_nobg.png
```

### 商品模特图工作流（两步）

```bash
# 第 1 步：去除背景 — 记录输出中的 bgRemovedImageFileId
python {baseDir}/scripts/remove_bg.py run --image product.png --json

# 第 2 步：将 fileId 传给 product_avatar
python {baseDir}/scripts/product_avatar.py run \
  --product-image-no-bg <bgRemovedImageFileId> \
  --avatar-id <avatarId>
```

### `query` — 恢复

```bash
python {baseDir}/scripts/remove_bg.py query --task-id <taskId> --timeout 600
```

## 选项

### `run` 和 `submit`

| 选项 | 说明 |
|--------|-------------|
| `--image` | 产品图片 fileId 或本地路径（必需） |
| `--notice-url URL` | 完成通知的 Webhook URL |

### 轮询（`run` 和 `query`）

| 选项 | 说明 |
|--------|-------------|
| `--timeout SECS` | 最大轮询时间，单位秒（默认：300） |
| `--interval SECS` | 轮询间隔，单位秒（默认：3） |

### 全局

| 选项 | 说明 |
|--------|-------------|
| `--output FILE` | 将结果图片下载到本地路径 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制 stderr 状态消息 |

## 输出字段

| 字段 | 说明 |
|-------|-------------|
| `bgRemovedImageFileId` | 去背图片的 FileId — 传给 `product_avatar.py --product-image-no-bg` |
| `bgRemovedImagePath` | 去背图片的 URL |
| `bgRemovedImageWidth/Height` | 结果图片尺寸 |
| `maskImageFileId` | 蒙版图片的 FileId |
| `maskImagePath` | 蒙版图片的 URL |
| `costCredit` | 消耗的积分 |

## 任务状态

`init` → `running` → `success` 或 `fail`


path: references/text2voice.md
content: # Text2Voice 模块

将文字转换为语音音频，支持自定义声音、语速、情绪和发音规则。

## 使用场景

当你需要从文字生成语音音频 — 用于配音、旁白、TTS 预览，或作为 avatar4 视频生成的音频输入。

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |

## 用法

```bash
python {baseDir}/scripts/text2voice.py <subcommand> [options]
```

## 示例

### 基础用法

```bash
python {baseDir}/scripts/text2voice.py run \
  --text "你好，欢迎使用文本转音频功能。" \
  --voice-id voice-888
```

### 调整语速和情绪

```bash
python {baseDir}/scripts/text2voice.py run \
  --text "今天天气真好！" \
  --voice-id voice-888 \
  --speed 1.2 \
  --emotion happy
```

### 使用发音规则

```bash
python {baseDir}/scripts/text2voice.py run \
  --text "行不行？你行行行。" \
  --voice-id voice-888 \
  --pron-rules '[{"oldStr":"行","newStr":"xing"}]'
```

### 下载音频

```bash
python {baseDir}/scripts/text2voice.py run \
  --text "你好，欢迎收看！" \
  --voice-id voice-888 \
  --output result.mp3
```

### 批量

```bash
T1=$(python {baseDir}/scripts/text2voice.py submit \
  --text "第一段内容" --voice-id voice-888 -q)
T2=$(python {baseDir}/scripts/text2voice.py submit \
  --text "第二段内容" --voice-id voice-888 -q)

python {baseDir}/scripts/text2voice.py query --task-id "$T1"
python {baseDir}/scripts/text2voice.py query --task-id "$T2"
```

## 选项

### `run` 和 `submit`

| 选项 | 说明 |
|--------|-------------|
| `--text TEXT` | 要转换的文字（必需） |
| `--voice-id ID` | 声音 ID（必需） |
| `--name NAME` | 声音名称标签 |
| `--speed FLOAT` | 语速（1.0 = 正常） |
| `--emotion NAME` | 情绪：`happy`、`sad`、`angry` 等 |
| `--origin-voice-file ID` | 原始声音文件 fileId 或本地路径 |
| `--pron-rules JSON` | 发音规则：`[{"oldStr":"行","newStr":"xing"}]` |
| `--board-id ID` | 看板 ID |
| `--notice-url URL` | Webhook URL |

### 轮询 / 全局

| 选项 | 说明 |
|--------|-------------|
| `--timeout SECS` | 最大轮询时间（默认：300） |
| `--interval SECS` | 轮询间隔（默认：3） |
| `--output FILE` | 将音频下载到本地路径 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

## 费用

固定：每任务 **0.1 积分**。失败的任务会退还积分。

## 输出

```
status: success  cost: 0.1 credits  duration: 12.5s
  audio: https://example.com/audio.mp3
```

配合 `--json` 可获取声音元数据（语言、性别、年龄、口音、时长）。


path: references/user.md
content: # User 模块

查询积分余额（tekan-video 配额）和查看权益消费记录。

## 使用场景

- 在运行生成任务前，确认积分余额是否充足。
- 在运行任务后，查看使用记录和费用。

## API 端点

| 端点 | 方法 | 说明 |
|----------|--------|-------------|
| `/user/benefit/tekan-video/quota` | GET | 查询积分余额（返回数字） |
| `/user/benefit/list` | POST | 查询权益消费记录（分页） |

## 用法

```bash
python {baseDir}/scripts/user.py <command> [options]
```

## 命令

### `credit` — 查询余额

调用 `GET /user/benefit/tekan-video/quota`。返回当前 tekan-video 积分配额（数字）。

```bash
python {baseDir}/scripts/user.py credit
```

输出：

```
Credit balance: 1000
```

配合 `--json`：

```bash
python {baseDir}/scripts/user.py credit --json
```

### `logs` — 权益消费记录

调用 `POST /user/benefit/list`，传入 JSON 请求体。支持按权益类型、权益 ID、权益组 ID 和时间范围筛选。

```bash
python {baseDir}/scripts/user.py logs
```

带筛选条件：

```bash
python {baseDir}/scripts/user.py logs \
  --type video_avatar \
  --benefit-ids 1,2,3 \
  --benefit-group-ids 10,20 \
  --start "2024-01-01 00:00:00" \
  --end "2024-12-31 23:59:59" \
  --page 1 \
  --size 50
```

## 选项

### 全局

| 选项 | 说明 |
|--------|-------------|
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

### `logs`

| 选项 | 说明 |
|--------|-------------|
| `--type TYPE` | 按权益类型筛选 |
| `--benefit-ids IDS` | 按权益 ID 筛选，逗号分隔 |
| `--benefit-group-ids IDS` | 按权益组 ID 筛选，逗号分隔 |
| `--start TIME` | 开始时间（`yyyy-MM-dd HH:mm:ss`） |
| `--end TIME` | 结束时间（`yyyy-MM-dd HH:mm:ss`） |
| `--page N` | 页码（默认：1） |
| `--size N` | 每页条数（默认：20） |


path: references/video_gen.md
content: # Video Generation 模块

从图片、文字提示或参考素材生成视频。

## 支持的任务类型

| 类型 | 说明 | 必需参数 |
|------|-------------|-----------------|
| `i2v` | **图片转视频 V2** — 从首帧/尾帧图片生成视频 | `--first-frame` 或 `--ref-images` |
| `t2v` | **文字转视频** — 纯文字提示生成视频 | `--model`, `--prompt` |
| `omni` | **Omni 参考** — 从参考图片/视频 + 提示词生成视频 | `--model`, `--prompt` |

## 子命令

| 子命令 | 使用场景 | 轮询？ |
|------------|-------------|--------|
| `run` | **默认。** 新请求，从提交到完成 | 是 — 等待至完成 |
| `submit` | 批量：提交多个任务不等待 | 否 — 立即退出 |
| `query` | 恢复：对已有的 `taskId` 继续轮询 | 是 — 等待至完成 |
| `list-models` | 查看模型、约束条件和音频支持 | 否 |
| `estimate-cost` | 执行前估算积分费用 | 否 |

## 用法

```bash
python {baseDir}/scripts/video_gen.py <subcommand> --type <i2v|t2v|omni> [options]
```

## 示例

### 列出模型

```bash
python {baseDir}/scripts/video_gen.py list-models --type t2v
python {baseDir}/scripts/video_gen.py list-models --type i2v --json
```

### 图片转视频 (i2v)

```bash
python {baseDir}/scripts/video_gen.py run \
  --type i2v \
  --first-frame <fileId_or_local_path> \
  --prompt "产品在纯白背景上缓缓旋转" \
  --model "Seedance 1.5 Pro" \
  --resolution 1080 \
  --duration 5
```

使用首帧 + 尾帧：

```bash
python {baseDir}/scripts/video_gen.py run \
  --type i2v \
  --first-frame <fileId> \
  --end-frame <fileId> \
  --prompt "场景之间平滑过渡" \
  --resolution 1080
```

### 文字转视频 (t2v)

```bash
python {baseDir}/scripts/video_gen.py run \
  --type t2v \
  --model "Seedance 1.5 Pro" \
  --prompt "未来都市夜景，霓虹灯倒映在潮湿的街道上" \
  --aspect-ratio "16:9" \
  --resolution 1080 \
  --duration 5 \
  --sound on
```

### Omni 参考

```bash
python {baseDir}/scripts/video_gen.py run \
  --type omni \
  --model "Standard" \
  --prompt "将 <<<Image1>>> 的风格应用到 <<<Video1>>> 的运动中" \
  --input-images '[{"fileId":"file_style","name":"Image1"}]' \
  --input-videos '[{"fileId":"file_motion","name":"Video1"}]' \
  --aspect-ratio "9:16" \
  --resolution 720
```

### 费用估算

```bash
python {baseDir}/scripts/video_gen.py estimate-cost \
  --model "Seedance 1.5 Pro" --resolution 1080 --duration 5 --sound on --count 2
```

### 恢复 / 批量

```bash
TASK_ID=$(python {baseDir}/scripts/video_gen.py submit \
  --type t2v --model "Seedance 1.5 Pro" --prompt "海面上的日落" -q)

python {baseDir}/scripts/video_gen.py query \
  --type t2v --task-id <taskId> --timeout 1200
```

## 通用选项

| 选项 | 说明 |
|--------|-------------|
| `--type` | 任务类型：`i2v`、`t2v`、`omni`（必需） |
| `--model` | 模型 **display name**（t2v/omni 必需） |
| `--prompt` | 文字提示词（t2v/omni 必需） |
| `--aspect-ratio` | 宽高比，如 `"16:9"` |
| `--resolution` | `480`、`540`、`720`、`768`、`1080` 或 `2160` |
| `--duration` | 视频时长，单位秒 |
| `--sound` | 原生音频：`"on"` / `"off"` |
| `--count` | 视频数量（1-4） |
| `--board-id` | 看板 ID |
| `--timeout` | 最大轮询时间（默认：600） |
| `--interval` | 轮询间隔（默认：5） |
| `--output-dir` | 将结果视频下载到指定目录 |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

### 仅 i2v

| 选项 | 说明 |
|--------|-------------|
| `--first-frame` | 首帧图片：fileId 或本地路径。例如 `--first-frame abc123` 或 `--first-frame photo.png` |
| `--end-frame` | 尾帧图片：fileId 或本地路径。例如 `--end-frame def456` 或 `--end-frame end.jpg` |
| `--ref-images` | 参考图片（多图参考，>=2）：多个 fileId 或本地路径，用空格分隔。例如 `--ref-images img1.png img2.png` |

### 仅 omni

| 选项 | 说明 |
|--------|-------------|
| `--input-images` | 输入图片的 JSON 数组。每项包含 `fileId`（fileId 或本地路径，本地文件会自动上传）和 `name`（在 prompt 中以 `<<<Name>>>` 引用）。例如 `--input-images '[{"fileId":"abc123","name":"Image1"}]'` 或使用本地文件：`--input-images '[{"fileId":"/path/to/photo.jpg","name":"Image1"}]'` |
| `--input-videos` | 输入视频的 JSON 数组，格式同 `--input-images`。例如 `--input-videos '[{"fileId":"vid123","name":"Video1"}]'` |
| `--internet-search` | 启用互联网搜索（仅 Standard/Fast） |

## 原生音频 (`--sound`)

- 仅 `nativeAudio=True` 的模型支持此功能 — 通过 `list-models` 检查
- 开启音频可能会增加费用，取决于模型

## Omni 提示词语法

使用 `<<<ImageN>>>` 或 `<<<VideoN>>>` 引用输入素材：

```
"Apply the color style from <<<Image1>>> to <<<Video1>>>"
```

## 模型推荐

> **说明：** 地表最强模型S2.0-白名单版（支持上传真人图）和地表最强模型S2.0 Fast-白名单版（支持上传真人图）是顶级模型，具有业界领先的视觉质量、原生音频和最长 15 秒时长。支持所有三种任务类型（i2v、t2v、omni）。追求最佳画质用前者；追求速度用 Fast 版本。
>
> 向用户展示模型时使用展示名称，构造命令时使用 API 名称。完整映射见 [model_mapping.md](model_mapping.md)。

**按优先级：**

| 优先级 | 推荐模型（展示名） | 对应 API 名称 | 原因 |
|----------|--------------------|----|-----|
| **最佳画质** | 地表最强模型S2.0-白名单版（支持上传真人图）, 可灵 O3, 电影级画质模型 V3.1, 拟真世界模型 2 Pro | `Standard`, `Kling O3`, `Veo 3.1`, `Sora 2 Pro` | 顶级视觉保真度 |
| **快速出片** | 地表最强模型S2.0 Fast-白名单版（支持上传真人图）, Seedance 1.0 Pro Fast, 电影级画质模型 V3.1 fast | `Fast`, `Seedance 1.0 Pro Fast`, `Veo 3.1 Fast` | 更快、更低费用 |
| **长片段 (10s+)** | 地表最强模型S2.0-白名单版（支持上传真人图）/ 地表最强模型S2.0 Fast-白名单版（支持上传真人图）(15s), 可灵 V3/O3 (15s), Vidu Q3 Pro (16s) | `Standard`/`Fast`, `Kling V3`/`Kling O3`, `Vidu Q3 Pro` | 支持更长时长 |
| **4K** | 电影级画质模型 V3.1, 电影级画质模型 V3.1 fast | `Veo 3.1`, `Veo 3.1 Fast` | 唯一支持 2160p 的模型 |
| **原生音频** | 地表最强模型S2.0-白名单版（支持上传真人图）/ 地表最强模型S2.0 Fast-白名单版（支持上传真人图）, 可灵 O3/V3, 电影级画质模型 V3.1, Vidu Q3 Pro | `Standard`/`Fast`, `Kling O3`/`Kling V3`, `Veo 3.1`, `Vidu Q3 Pro` | 环境声音 |

**按发布渠道：**

| 渠道 | 宽高比 | 推荐模型（展示名） | 对应 API 名称 |
|---------|-------------|-------------|---|
| TikTok / Reels | 9:16 | 地表最强模型S2.0-白名单版（支持上传真人图）, 可灵 V3 | `Standard`, `Kling V3` |
| YouTube | 16:9 | 地表最强模型S2.0-白名单版（支持上传真人图）, 可灵 O3, 电影级画质模型 V3.1 | `Standard`, `Kling O3`, `Veo 3.1` |
| Instagram | 3:4 或 1:1 | 地表最强模型S2.0-白名单版（支持上传真人图）, Seedance 1.5 Pro | `Standard`, `Seedance 1.5 Pro` |

**默认选择**（用户无偏好时）：
- t2v → 地表最强模型S2.0-白名单版（支持上传真人图）（API: `Standard`）
- i2v → 地表最强模型S2.0-白名单版（支持上传真人图）或可灵 V3（API: `Standard` 或 `Kling V3`）
- omni → 地表最强模型S2.0-白名单版（支持上传真人图）（API: `Standard`）

## 提示词技巧

**结构：** 主体 + 动作 + 环境 + 风格 + 镜头

**镜头关键词：** "static shot"、"slow pan left"、"dolly forward"、"tracking shot"、"orbit around"、"zoom in"、"crane shot rising"、"shallow depth of field"


path: references/voice.md
content: # Voice 模块

列出/搜索可用声音，从音频样本克隆自定义声音，删除已克隆的声音。

## 使用场景

- **text2voice 之前** — 使用 `list` 查找合适的 voiceId（用户未指定声音时）
- **自定义声音** — 使用 `clone` 创建一个听起来像特定人的声音
- **清理** — 使用 `delete` 删除用户不再需要的自定义声音

## 子命令

| 子命令 | 说明 | 轮询？ |
|------------|-------------|--------|
| `list` | 搜索/浏览可用声音（系统 + 自定义） | 否 |
| `clone` | **默认。** 从音频克隆声音：提交 + 轮询至完成 | 是 |
| `clone-submit` | 仅提交克隆任务，输出 taskId | 否 |
| `clone-query` | 对已有的克隆 taskId 轮询至完成 | 是 |
| `delete` | 删除自定义（克隆的）声音 | 否 |

## 用法

```bash
python {baseDir}/scripts/voice.py <subcommand> [options]
```

## 示例

### 列出声音

浏览所有英语声音：

```bash
python {baseDir}/scripts/voice.py list --language en
```

按性别和风格筛选：

```bash
python {baseDir}/scripts/voice.py list --language en --gender female --style UGC
```

列出中文男声：

```bash
python {baseDir}/scripts/voice.py list --language zh-CN --gender male
```

仅显示自定义（克隆的）声音：

```bash
python {baseDir}/scripts/voice.py list --custom
```

完整 JSON 输出（带分页）：

```bash
python {baseDir}/scripts/voice.py list --language ja --page 1 --size 50 --json
```

### 克隆声音

从本地音频文件：

```bash
python {baseDir}/scripts/voice.py clone \
  --audio recording.mp3 \
  --name "My Brand Voice"
```

带参考文本（提升克隆质量）：

```bash
python {baseDir}/scripts/voice.py clone \
  --audio sample.wav \
  --name "Brand Voice" \
  --text "欢迎使用特看视频，全能 AI 视频创作平台。"
```

调整语速：

```bash
python {baseDir}/scripts/voice.py clone \
  --audio voice_sample.mp3 \
  --name "Fast Narrator" \
  --speed 1.1
```

### 克隆批量 / 恢复

```bash
T1=$(python {baseDir}/scripts/voice.py clone-submit \
  --audio sample1.mp3 --name "Voice A" -q)
T2=$(python {baseDir}/scripts/voice.py clone-submit \
  --audio sample2.mp3 --name "Voice B" -q)

python {baseDir}/scripts/voice.py clone-query --task-id "$T1"
python {baseDir}/scripts/voice.py clone-query --task-id "$T2"
```

### 删除自定义声音

```bash
python {baseDir}/scripts/voice.py delete --voice-id <voiceId>
```

## 选项

### `list`

| 选项 | 说明 |
|--------|-------------|
| `--language CODE` | 语言代码：`en`、`zh-CN`、`ja`、`ko`、`fr`、`de` 等 |
| `--gender` | `male` / `female` |
| `--age` | `Young` / `Middle Age` / `Old` |
| `--style` | `UGC` / `Advertisement` / `Cartoon_And_Animals` / `Influencer` |
| `--accent` | 口音筛选：`American`、`British`、`Chinese` 等 |
| `--custom` | 仅显示自定义（克隆的）声音 |
| `--page N` | 页码（默认：1） |
| `--size N` | 每页条数（默认：20） |

### `clone` 和 `clone-submit`

| 选项 | 说明 |
|--------|-------------|
| `--audio FILE` | 音频文件（fileId 或本地路径）。必需。格式：mp3/wav，10 秒-5 分钟，<10MB |
| `--name NAME` | 克隆声音的名称（默认为 taskId） |
| `--text TEXT` | 与音频内容匹配的参考文本（可提升克隆质量） |
| `--speed FLOAT` | 语速 0.8-1.2（默认：1.0） |
| `--notice-url URL` | Webhook URL |

### `delete`

| 选项 | 说明 |
|--------|-------------|
| `--voice-id ID` | 要删除的声音 voiceId（必需） |

### 轮询 / 全局

| 选项 | 说明 |
|--------|-------------|
| `--timeout SECS` | 最大轮询时间（默认：300） |
| `--interval SECS` | 轮询间隔（默认：5） |
| `--json` | 输出完整 JSON 响应 |
| `-q, --quiet` | 静默模式，抑制状态消息 |

## 声音克隆音频要求

- **格式：** mp3 或 wav
- **时长：** 10 秒到 5 分钟
- **大小：** 10MB 以内
- **质量：** 清晰的语音、最少的背景噪音可获得最佳效果

## 输出

### `list` — 制表符分隔（默认）

```
voiceId    voiceName    language    gender    age    style    accent    demoAudioUrl
```

### `clone` — 结构化输出

```
status: success
  voiceId:   abc123def456
  voiceName: My Brand Voice
  demoAudio: https://example.com/demo.mp3
```

### 支持的语言

`en`, `ar`, `bg`, `hr`, `cs`, `da`, `nl`, `fil`, `fi`, `fr`, `de`, `el`, `hi`, `hu`, `id`, `it`, `ja`, `ko`, `ms`, `nb`, `pl`, `pt`, `ro`, `ru`, `zh-CN`, `sk`, `es`, `sv`, `zh-Hant`, `tr`, `uk`, `vi`, `th`

### 支持的口音

African, American, Argentinian, Australian, Brazilian, British, Chinese, Colombian, Dutch, Eastern European, Filipino, French, German, Indian, Indonesian, Italian, Japanese, Kazakh, Korean, Malay, Mexican, Middle Eastern, North African, Polish, Portuguese, Russian, Singaporean, Spanish, Swiss, Taiwanese, Thai, Turkish, Vietnamese


path: scripts/remove_bg.py
content: #!/usr/bin/env python3
"""Remove background from an image using Tekan Common Task API.

## AGENT INSTRUCTIONS — READ FIRST
- Default workflow: ALWAYS use `run` (submit + auto-poll).
  Do NOT ask the user to run query manually.
- Only use `query` when `run` has already timed out and a taskId exists,
  or when the user explicitly provides a taskId to resume.
- When using `query`, keep polling (default timeout=300s) until
  status is 'success' or 'fail'. Do NOT stop after a single check.
- Never hand a pending taskId back to the user and say "check it later".
  Always poll to completion within the timeout window.

The output `bgRemovedImageFileId` can be passed directly to
product_avatar.py --product-image-no-bg for the Product Avatar workflow.

Subcommands:
    run     Submit task AND poll until done — DEFAULT, use this first
    submit  Submit only, print taskId, exit — use for parallel batch jobs
    query   Poll an existing taskId until done (or timeout) — use for recovery

Usage:
    python remove_bg.py run    --image <fileId|path> [options]
    python remove_bg.py submit --image <fileId|path> [options]
    python remove_bg.py query  --task-id <taskId> [--timeout 300] [--interval 3] [options]
"""

import argparse
import json as json_mod
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

from shared.client import TopviewClient, TopviewError, to_tekan_credit, convert_result_credits
from shared.upload import resolve_local_file

SUBMIT_PATH = "/v1/common_task/remove_background/submit"
QUERY_PATH = "/v1/common_task/remove_background/query"

DEFAULT_TIMEOUT = 300
DEFAULT_INTERVAL = 3


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def build_body(args, file_id: str) -> dict:
    body = {"productImageFileId": file_id}
    if args.notice_url:
        body["noticeUrl"] = args.notice_url
    return body


def do_submit(client: TopviewClient, body: dict, quiet: bool) -> str:
    if not quiet:
        print("Submitting remove-background task...", file=sys.stderr)
    result = client.post(SUBMIT_PATH, json=body)
    task_id = result["taskId"]
    if not quiet:
        print(f"Task submitted. taskId: {task_id}", file=sys.stderr)
    return task_id


def do_poll(client: TopviewClient, task_id: str, timeout: float, interval: float,
            quiet: bool) -> dict:
    if not quiet:
        print(
            f"Polling task {task_id} (timeout={timeout}s, interval={interval}s)...",
            file=sys.stderr,
        )
    return client.poll_task(
        QUERY_PATH,
        task_id,
        interval=interval,
        timeout=timeout,
        verbose=not quiet,
    )


def download_file(url: str, output: str, quiet: bool) -> None:
    import requests as req

    if not quiet:
        print(f"Downloading result to {output}...", file=sys.stderr)

    resp = req.get(url, stream=True)
    resp.raise_for_status()

    with open(output, "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)

    if not quiet:
        size_kb = os.path.getsize(output) / 1024
        print(f"Downloaded: {output} ({size_kb:.1f} KB)", file=sys.stderr)


def print_result(result: dict, args) -> None:
    convert_result_credits(result)
    if args.json:
        print(json_mod.dumps(result, indent=2, ensure_ascii=False))
        return

    status = result.get("status", "unknown")
    cost = result.get("costCredit", 0)
    file_id = result.get("bgRemovedImageFileId", "")
    image_url = result.get("bgRemovedImagePath", "")
    w = result.get("bgRemovedImageWidth", "")
    h = result.get("bgRemovedImageHeight", "")

    print(f"status: {status}  cost: {cost} credits")
    if file_id:
        print(f"  bgRemovedImageFileId: {file_id}")
    if image_url:
        print(f"  bgRemovedImagePath:   {image_url}")
        if args.output:
            download_file(image_url, args.output, args.quiet)
    if w and h:
        print(f"  dimensions:           {w}x{h}")

    mask_id = result.get("maskImageFileId", "")
    mask_url = result.get("maskImagePath", "")
    if mask_id:
        print(f"  maskImageFileId:      {mask_id}")
    if mask_url:
        print(f"  maskImagePath:        {mask_url}")


# ---------------------------------------------------------------------------
# Argument definitions
# ---------------------------------------------------------------------------

def add_submit_args(p):
    p.add_argument("--image", required=True,
                   help="Product image fileId or local path")
    p.add_argument("--notice-url", default=None,
                   help="Webhook URL for task completion notification")


def add_poll_args(p):
    p.add_argument("--timeout", type=float, default=DEFAULT_TIMEOUT,
                   help=f"Max polling time in seconds (default: {DEFAULT_TIMEOUT})")
    p.add_argument("--interval", type=float, default=DEFAULT_INTERVAL,
                   help=f"Polling interval in seconds (default: {DEFAULT_INTERVAL})")


def add_output_args(p):
    p.add_argument("--output", default=None,
                   help="Download result image to this local path")
    p.add_argument("--json", action="store_true",
                   help="Output full JSON response")
    p.add_argument("-q", "--quiet", action="store_true",
                   help="Suppress status messages on stderr")


# ---------------------------------------------------------------------------
# Subcommand handlers
# ---------------------------------------------------------------------------

def cmd_run(args, parser):
    """Submit task then poll until done — full flow (default)."""
    client = TopviewClient()
    client.guard_credit(0, "remove_bg")
    file_id = resolve_local_file(args.image, quiet=args.quiet, client=client)
    body = build_body(args, file_id)
    task_id = do_submit(client, body, args.quiet)
    result = do_poll(client, task_id, args.timeout, args.interval, args.quiet)
    print_result(result, args)


def cmd_submit(args, parser):
    """Submit task only — print taskId and exit immediately."""
    client = TopviewClient()
    client.guard_credit(0, "remove_bg")
    file_id = resolve_local_file(args.image, quiet=args.quiet, client=client)
    body = build_body(args, file_id)
    task_id = do_submit(client, body, args.quiet)
    print(task_id)


def cmd_query(args, parser):
    """Poll an existing task by taskId until done or timeout."""
    client = TopviewClient()
    try:
        result = do_poll(
            client, args.task_id, args.timeout, args.interval, args.quiet,
        )
        print_result(result, args)
    except TimeoutError as e:
        if not args.quiet:
            print(f"Timeout reached: {e}", file=sys.stderr)
            print("Fetching last known status...", file=sys.stderr)
        last = client.get(QUERY_PATH, params={"taskId": args.task_id})
        status = last.get("status", "unknown")
        task_id = last.get("taskId", args.task_id)
        if args.json:
            print(json_mod.dumps(last, indent=2, ensure_ascii=False))
        else:
            print(f"status: {status}  taskId: {task_id}", file=sys.stderr)
        sys.exit(2)


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        description="Tekan Remove Background — remove image background for product avatar workflow.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
AGENT WORKFLOW RULES:
  1. ALWAYS start with `run` — it submits and polls automatically.
  2. Only use `query` if `run` timed out and you have a taskId to resume.
  3. `query` polls continuously (not once) until done or --timeout.
  4. NEVER hand a pending taskId back to the user — always poll to completion.

Examples:
  # Remove background from a local image
  python remove_bg.py run --image product.png

  # Remove background and download result
  python remove_bg.py run --image product.png --output product_nobg.png

  # Use in Product Avatar workflow:
  #   1. Remove background
  FILE_ID=$(python remove_bg.py run --image product.png -q | grep bgRemovedImageFileId | awk '{print $2}')
  #   2. Replace into avatar scene
  python product_avatar.py run --product-image-no-bg $FILE_ID --avatar-id <avatarId>

  # Batch: submit without waiting
  python remove_bg.py submit --image product.png -q

  # Recovery: poll existing task
  python remove_bg.py query --task-id <taskId> --timeout 600
""",
    )

    sub = parser.add_subparsers(dest="subcommand")
    sub.required = True

    # -- run (default full flow) --
    p_run = sub.add_parser("run", help="[DEFAULT] Submit task and poll until done")
    add_submit_args(p_run)
    add_poll_args(p_run)
    add_output_args(p_run)

    # -- submit only --
    p_submit = sub.add_parser("submit", help="Submit task only, print taskId and exit")
    add_submit_args(p_submit)
    add_output_args(p_submit)

    # -- query / poll existing task --
    p_query = sub.add_parser("query", help="Poll existing taskId until done or timeout")
    p_query.add_argument("--task-id", required=True,
                         help="taskId returned by 'submit' or a previous 'run'")
    add_poll_args(p_query)
    add_output_args(p_query)

    args = parser.parse_args()

    handlers = {
        "run": (cmd_run, p_run),
        "submit": (cmd_submit, p_submit),
        "query": (cmd_query, p_query),
    }

    fn, p = handlers[args.subcommand]
    fn(args, p)


if __name__ == "__main__":
    main()


path: scripts/requirements.txt
content: requests>=2.28.0


path: scripts/shared/__init__.py
content: from .config import load_config
from .client import TopviewClient


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)