@pippit-dev/cli 1.0.29 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/checksums.txt +6 -6
- package/dist/checksums.txt +6 -6
- package/package.json +3 -2
- package/skills/xyq-marketing-skill/SKILL.md +51 -0
- package/skills/xyq-marketing-skill/examples/product-video.md +45 -0
- package/skills/xyq-marketing-skill/references/api.md +63 -0
- package/skills/xyq-marketing-skill/scripts/marketing.js +268 -0
- package/skills/xyq-nest-skill/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -4,20 +4,44 @@
|
|
|
4
4
|
|
|
5
5
|
## 技能列表
|
|
6
6
|
|
|
7
|
-
本仓库在 `skills/`
|
|
7
|
+
本仓库在 `skills/` 目录下包含三个智能体技能:
|
|
8
8
|
|
|
9
9
|
| 技能 | 说明 | 路径 |
|
|
10
10
|
|-------|-------------|------|
|
|
11
11
|
| `xyq-short-drama-skill` | 短剧工作流技能,支持提交创作任务、上传参考文件、查询进度、列出会话文件和下载产物。 | `skills/short-drama/` |
|
|
12
12
|
| `xyq-skill` | 图片生成与参考图编辑、视频生成、视频超分与擦字幕、异步结果交付、个人 Canvas 编辑、积分查询及登录授权。 | `skills/xyq-nest-skill/` |
|
|
13
|
+
| `xyq-marketing-skill` | 商品图文营销一键成片:剧情广告、品牌大片、达人带货,含素材上传、进度查询与媒体下载。 | `skills/xyq-marketing-skill/` |
|
|
13
14
|
|
|
14
15
|
### 技能路由
|
|
15
16
|
|
|
16
17
|
- 图片生成与参考图编辑、视频生成(含首尾帧和参考素材)、视频超分、擦字幕、结果查询、个人 Canvas 编辑、积分和授权由 `xyq-skill` 处理。
|
|
17
18
|
- 短剧生成、续写、改写、人物设定、分集创作和短剧会话文件处理使用 `xyq-short-drama-skill`。
|
|
19
|
+
- 商品图文营销成片、剧情广告、品牌大片和达人带货视频使用 `xyq-marketing-skill`;由公开营销 API 完成创作编排。
|
|
18
20
|
|
|
19
21
|
需要补充、选择或确认时,使用宿主实际暴露且当前模式允许的工具:Codex 的 `request_user_input` / `request_user_input_async`、WorkBuddy 的 `ask_user_question`;不可用时用普通聊天。
|
|
20
22
|
|
|
23
|
+
## 小云雀营销成片技能
|
|
24
|
+
|
|
25
|
+
入口:[skills/xyq-marketing-skill/SKILL.md](skills/xyq-marketing-skill/SKILL.md)。使用 [官网](https://xyq.jianying.com/cli?tab=api) 已开放的营销 API,支持素材上传、营销视频提交、结果查询与下载、积分查询。安装器会从 `skills/` 自动安装该 Skill,也可以单独安装:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx skills add Pippit-dev/cli --skill xyq-marketing-skill
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
需要 Node.js 16+,使用 Skill 内自包含脚本,无额外 npm 依赖。脚本从当前进程读取 `XYQ_ACCESS_KEY`;API 与 CLI 可使用同一 Access Key,但 CLI 浏览器登录不会设置此环境变量。密钥在本机安全配置,不写入请求 JSON 或命令参数。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 从仓库根目录执行;营销请求字段见接口契约,默认只预览
|
|
35
|
+
node skills/xyq-marketing-skill/scripts/marketing.js generate --request request.json --dry-run
|
|
36
|
+
# 用户已要求真实生成时提交,然后用返回的真实 ID 取回媒体
|
|
37
|
+
node skills/xyq-marketing-skill/scripts/marketing.js generate --request request.json --execute
|
|
38
|
+
node skills/xyq-marketing-skill/scripts/marketing.js query --thread-id THREAD_ID --run-id RUN_ID --wait --output-dir ./results
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
参数与错误处理见 [接口契约](skills/xyq-marketing-skill/references/api.md),完整示例见 [商品图到营销视频](skills/xyq-marketing-skill/examples/product-video.md)。这是 Skill 脚本入口,不是新增的 Go CLI 子命令。营销 API 未公开团队切换字段,不宣称支持团队空间切换。
|
|
42
|
+
|
|
43
|
+
维护后运行 `node scripts/marketing-skill.test.js` 和 `node scripts/skills.test.js`,验证请求、上传、任务状态、下载与 Skill 引用。测试使用本地模拟服务,不会创建真实付费任务。
|
|
44
|
+
|
|
21
45
|
## 小云雀图片、视频与媒体处理技能
|
|
22
46
|
|
|
23
47
|
入口:[skills/xyq-nest-skill/SKILL.md](skills/xyq-nest-skill/SKILL.md)。普通生成请求也直接使用对应 CLI;素材路径交给命令内部上传,异步查询自动下载,最后通过宿主交付真实媒体附件。
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
236acda02cdf1fb8f46e235d17344c15b64c8d865189cab2a3896ce8b40ee8a2 pippit-tool-cli-1.0.30-darwin-amd64.tar.gz
|
|
2
|
+
1b74233c5754c10ed285e419edfb74ee06b23794700a4afd3aafc4551696d413 pippit-tool-cli-1.0.30-darwin-arm64.tar.gz
|
|
3
|
+
659ed0f1a712db8d882f8f5cc67a003fc5ead39c0fb93fe89157036b0757149e pippit-tool-cli-1.0.30-linux-amd64.tar.gz
|
|
4
|
+
6f1279236f297f55d24663bc66ee77033e71b6f669d0b5f74db6eaf3a5d2385a pippit-tool-cli-1.0.30-linux-arm64.tar.gz
|
|
5
|
+
07e4bb60b54ff3c7e0ddeeacc83fc6ee00ca207116786a5522a4a61f91c27d2e pippit-tool-cli-1.0.30-windows-amd64.zip
|
|
6
|
+
b7172468aef11434ccb22f6e6262663599327b8c93f60b6fb788267fdbf876f9 pippit-tool-cli-1.0.30-windows-arm64.zip
|
package/dist/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
236acda02cdf1fb8f46e235d17344c15b64c8d865189cab2a3896ce8b40ee8a2 pippit-tool-cli-1.0.30-darwin-amd64.tar.gz
|
|
2
|
+
1b74233c5754c10ed285e419edfb74ee06b23794700a4afd3aafc4551696d413 pippit-tool-cli-1.0.30-darwin-arm64.tar.gz
|
|
3
|
+
659ed0f1a712db8d882f8f5cc67a003fc5ead39c0fb93fe89157036b0757149e pippit-tool-cli-1.0.30-linux-amd64.tar.gz
|
|
4
|
+
6f1279236f297f55d24663bc66ee77033e71b6f669d0b5f74db6eaf3a5d2385a pippit-tool-cli-1.0.30-linux-arm64.tar.gz
|
|
5
|
+
07e4bb60b54ff3c7e0ddeeacc83fc6ee00ca207116786a5522a4a61f91c27d2e pippit-tool-cli-1.0.30-windows-amd64.zip
|
|
6
|
+
b7172468aef11434ccb22f6e6262663599327b8c93f60b6fb788267fdbf876f9 pippit-tool-cli-1.0.30-windows-arm64.zip
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pippit-dev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "Pippit CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pippit-tool-cli": "scripts/run.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node scripts/install.js",
|
|
10
|
+
"test:marketing-skill": "node scripts/marketing-skill.test.js",
|
|
10
11
|
"prepack": "node scripts/prepare-canvas-runtime.js",
|
|
11
12
|
"prepare:canvas-runtime": "node scripts/prepare-canvas-runtime.js",
|
|
12
|
-
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
13
|
+
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/marketing-skill.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
13
14
|
"verify:canvas-runtime": "node scripts/prepare-canvas-runtime.js --check-only",
|
|
14
15
|
"test:canvas-runtime": "node scripts/canvas-command-runtime.test.js",
|
|
15
16
|
"test:canvas-operations": "node scripts/canvas-command-canvas-operations.test.js && node scripts/canvas-command-scene3d-operations.test.js && node scripts/canvas-command-timeline-operations.test.js"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xyq-marketing-skill
|
|
3
|
+
description: 使用小云雀公开营销 API,根据商品图文生成剧情广告、品牌大片或达人带货营销视频;上传素材、提交营销成片、查询进度、下载交付结果及查询积分。用户要求小云雀营销一键成片或接入营销 API 时使用。
|
|
4
|
+
user-invocable: true
|
|
5
|
+
metadata:
|
|
6
|
+
{"openclaw": {"emoji": "🛍️", "requires": {"bins": ["node"], "env": ["XYQ_ACCESS_KEY"]}}}
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 小云雀营销成片
|
|
10
|
+
|
|
11
|
+
营销一键成片使用独立的公开 API,由服务端编排创作。剧情广告、品牌大片和达人带货由用户的自然语言指令表达,不虚构 `mode`、`template` 等参数。普通模型直出、画布编辑和短剧创作应使用各自已有 Skill;不要把营销成片替换成一次普通生视频。
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
本 Skill 自包含,只需要 Node.js 16+,不依赖其它 Skill 或 npm 安装。脚本位置为 `"{baseDir}/scripts/marketing.js"`;下面示例中的相对路径从本 Skill 目录执行。
|
|
16
|
+
|
|
17
|
+
API 和 CLI 可以使用同一个 Access Key,但脚本只读取当前进程的 `XYQ_ACCESS_KEY`。CLI 浏览器登录保存的凭据不会自动变成环境变量,不读取系统钥匙串或 CLI 凭据文件。缺少变量时,引导用户在 [官网 API 页](https://xyq.jianying.com/cli?tab=api) 管理 Access Key 并在本机安全配置,不能让用户在聊天中发送密钥,也不要将密钥写入请求文件、命令参数或日志。
|
|
18
|
+
|
|
19
|
+
先按 [接口契约](references/api.md) 准备请求;需要完整执行示例时读 [商品图到营销视频](examples/product-video.md)。
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
1. 确认用户要生成的营销内容与商品素材。保留用户原始指令,不主动扩写或加入未经提供的卖点。仅查询、接入咨询或估算不提交生成;明确要求生成即可执行,不重复询问已给出的授权。必要的补充信息用宿主当前允许的 `request_user_input_async`、`request_user_input` 或 `ask_user_question` 询问,没有工具时用普通聊天。
|
|
24
|
+
2. 有本地商品素材时逐个上传,保留返回的 `data.pippit_asset_id`。远程素材先取得用户授权使用的本地文件;不能把 URL 或路径放进 `asset_ids`。用户已提供有效资产 ID 时直接复用。无素材的纯文字请求无需上传。
|
|
25
|
+
3. 写入 UTF-8 JSON 请求文件。`message` 保留用户指令;`general_agent_settings.video_model` 必填,不能传 `{}`。用户指定模型时原样使用;未指定时询问,或在用户已明确授权“你决定”等选择范围内选定并说明。其它选项按用户给定或已授权的偏好设置,不静默换模型。`thread_id` 仅在继续已有营销会话时传入真实 ID。
|
|
26
|
+
4. 先预览校验,已获生成授权后使用 `--execute` 提交。立即保存响应并展示真实 `data.web_thread_link`,保留 `data.run.thread_id`、`data.run.run_id`。缺少网页链接时只报告实际返回信息,不自行拼接链接。
|
|
27
|
+
5. 用当前 thread/run 查询到结束并下载媒体。遇到确认或问卷时沿用已有授权;缺少必要选择再问用户。确认后继续同一 thread,并取得最新 run_id 再查询:旧 Run 可永久保持等待交互状态。可通过宿主浏览器查看已返回的网页链接;安装了 `pippit-tool-cli` 时,也可用 `get-thread --thread-id THREAD_ID` 读取各 Run 的真实 ID。不要用旧 Run 重复确认或重新生成。提交成功、网页链接或进度链接都不等于交付完成。
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node scripts/marketing.js upload --file /path/to/product.png
|
|
31
|
+
node scripts/marketing.js generate --request /path/to/marketing-request.json --dry-run
|
|
32
|
+
node scripts/marketing.js generate --request /path/to/marketing-request.json --execute
|
|
33
|
+
node scripts/marketing.js query --thread-id THREAD_ID --run-id RUN_ID --wait --max-wait 900 --output-dir /path/to/results
|
|
34
|
+
node scripts/marketing.js balance
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
生成默认是离线预览;预览不需要密钥。上传、查询、余额是实际 API 请求。`--timeout` 设置单请求总时限(秒,默认 60)。`--wait` 由脚本每 10 秒查询,默认最多 900 秒;不再叠加其它轮询器。没有 `--wait` 时只查询一次。脚本输出逐行 JSON,查询下载前先输出服务端响应,再逐个输出已下载文件,最后输出含 `downloaded_files` 的响应。
|
|
38
|
+
|
|
39
|
+
## Completion and Recovery
|
|
40
|
+
|
|
41
|
+
- `ret` 为字符串或数字 `0` 才是 API 业务成功。查询 `data.run_state`:1/2/7 为已提交或进行中;8 为程序中断(可能等待工具回调),有时限地继续查询;3 为本轮完成;4/5 为失败/取消;6/9 为等待用户交互。不能把退出码 0 的单次进行中查询当作生成完成。
|
|
42
|
+
- 退出码 4:6=`InputRequired`,9=`HITL_Interrupt`(人工交互中断,当前 Run 的终态)。输出原始响应、`run_state_name`、`action_required=true` 和下一步说明,立即停止轮询。9 不表示视频失败或完成;在同一会话处理确认后查询最新 Run。
|
|
43
|
+
- 退出码 5:未知或未指定状态。保留原始响应和任务 ID,停止自动轮询并诊断,不重提生成。
|
|
44
|
+
- 退出码 2:生成失败、取消或成功却没有视频。展示 `fail_reason` 或“任务成功但未返回视频”,不编造产物、不自动重提。成功但仅有图片时,原始响应仍包含图片链接,可如实交付为部分产物。
|
|
45
|
+
- 退出码 3:等待预算耗尽。保留任务 ID 和最后状态;可继续用相同查询命令等待。网络错误、状态格式异常、业务错误或参数错误退出码 1,先诊断,不自动重发生成。提交超时可能已经创建任务;没有 ID 时报告结果不明确,先查官网任务或联系支持核对 `log_id`。
|
|
46
|
+
- 下载失败时,原始响应和已下载文件记录已输出,可用相同 ID 重新查询。脚本只新建文件,不覆盖已有结果;下载不带 API 鉴权头。生产 API 固定使用 `https://xyq.jianying.com`,不跟随鉴权请求重定向。
|
|
47
|
+
- 检查本地媒体可打开、内容类型和时长等与交付相符;成功下载仅证明取得了字节,不能宣称创意质量已验收。将每个最终视频/图片通过宿主的附件或媒体预览能力展示,URL/路径作为补充。若当前宿主无法展示附件,明确说明并提供可用本地文件或下载链接。
|
|
48
|
+
|
|
49
|
+
## Scope
|
|
50
|
+
|
|
51
|
+
本 Skill 仅使用正式公开营销接口;不宣称支持团队空间切换。鉴权范围由用户配置的 Access Key 和服务端决定,公开请求没有 `TeamID` 字段,不自行加入团队字段或跨账号复用资产/任务 ID。沉浸式短片、火山引擎服务和现有 CLI 的来源统计参数不在此脚本范围;不要把 `--source` 等未公开字段传给营销接口。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 商品图到营销视频
|
|
2
|
+
|
|
3
|
+
用户示例:“用这张保温杯商品图做一个 15 秒竖屏达人带货视频,不要字幕,用 Seedance 2.0 VIP,1080p。”
|
|
4
|
+
|
|
5
|
+
1. 确认当前进程已配置 `XYQ_ACCESS_KEY`,然后上传用户给出的真实文件:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node scripts/marketing.js upload --file /path/to/cup.png
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
2. 将返回的 `data.pippit_asset_id` 填入 UTF-8 请求文件。下面的 `ACTUAL_UPLOADED_ASSET_ID` 是占位说明,不能原样提交;不另补用户没选的模型或分辨率。
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"message": "用这张保温杯商品图做一个 15 秒竖屏达人带货视频,不要字幕,用 Seedance 2.0 VIP,1080p。",
|
|
16
|
+
"asset_ids": ["ACTUAL_UPLOADED_ASSET_ID"],
|
|
17
|
+
"general_agent_settings": {
|
|
18
|
+
"ratio": 3,
|
|
19
|
+
"duration_start": 15,
|
|
20
|
+
"duration_end": 15,
|
|
21
|
+
"show_subtitle": false,
|
|
22
|
+
"video_model": "seedance2.0_vision",
|
|
23
|
+
"video_resolution": "720p"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
3. 预览后提交;用户已经要求生成,执行时不再索要相同授权。
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
node scripts/marketing.js generate --request /path/to/request.json --dry-run
|
|
32
|
+
node scripts/marketing.js generate --request /path/to/request.json --execute
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. 保存真实响应中的 thread/run ID,展示返回的网页链接,然后等待和下载:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node scripts/marketing.js query --thread-id ACTUAL_THREAD_ID --run-id ACTUAL_RUN_ID --wait --output-dir /path/to/results
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
5. 若查询退出码为 4、`run_state=6/9`,打开提交时返回的网页查看待确认项,沿用用户已有授权;缺少必要选择时再问用户。确认后取同一 thread 的最新 run_id,重新执行查询命令,不重发生成请求,也不再查已中断的旧 Run。
|
|
42
|
+
|
|
43
|
+
6. 读取最终 `run_state` 和 `downloaded_files`,确认视频可打开并将实际本地文件作为媒体交付。用户只要“看之前那条生成好了没”时,从第 4 步开始,不能重新上传或提交。
|
|
44
|
+
|
|
45
|
+
错误、空结果和超时按 [完成与恢复规则](../SKILL.md#completion-and-recovery) 处理。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Marketing API Contract
|
|
2
|
+
|
|
3
|
+
来源:[官网 API 入口](https://xyq.jianying.com/cli?tab=api) 链接的 [正式接口文档](https://bytedance.larkoffice.com/docx/CQOYdJNLioLz6fxRzKXcCsKLnJh)。2026-09-22 核对 revision 597。以下仅整理本 Skill 使用的字段;模型可用性、积分和权限以服务端实际响应为准。
|
|
4
|
+
|
|
5
|
+
## Endpoints
|
|
6
|
+
|
|
7
|
+
Base URL:`https://xyq.jianying.com`。全部使用 POST,认证为 `Authorization: Bearer <Access Key>`,`Accept: application/json`。
|
|
8
|
+
|
|
9
|
+
| 操作 | 路径 | 请求体 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| 上传单文件 | `/api/biz/v1/skill/upload_file` | multipart/form-data,字段 `file`,boundary 由脚本生成 |
|
|
12
|
+
| 营销成片 | `/api/biz/v1/agent/submit_marketing_run` | JSON,字段见下表 |
|
|
13
|
+
| 查询结果 | `/api/biz/v1/agent/query_generate_video_result` | JSON:真实 `thread_id`、`run_id` |
|
|
14
|
+
| 查询积分 | `/api/biz/v1/skill/get_credit_balance` | JSON:`{}` |
|
|
15
|
+
|
|
16
|
+
## Generate Request
|
|
17
|
+
|
|
18
|
+
| 字段 | 类型 | 使用规则 |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `message` | string | 必填,非空的原始创作指令 |
|
|
21
|
+
| `asset_ids` | string[] | 可选,上传返回的真实 `data.pippit_asset_id` |
|
|
22
|
+
| `general_agent_settings` | object | 必传,且必须包含非空 `video_model` |
|
|
23
|
+
| `thread_id` | string | 可选,只在继续已有营销会话时使用 |
|
|
24
|
+
|
|
25
|
+
设置块必须传对象,且 `video_model` 必填。2026-09-22 实际调用及服务端 `validateMarketingGeneralAgentSettings` 均确认:`{}` 或仅有比例/时长会返回 `ret=2`、缺少 `video_model`。不再按文档中的默认策略说明发送空对象;预览和提交都先本地校验。用户未指定模型时先询问,已有明确选择授权时按授权选择,不隐式降级。
|
|
26
|
+
|
|
27
|
+
| 设置字段 | 类型 | 含义 |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `ratio` | int32 | `2=16:9`、`3=9:16`、`4=4:3`、`5=3:4`、`6=1:1`;不是比例字符串 |
|
|
30
|
+
| `duration_start` / `duration_end` | int32 | 正整数秒;精确时长两者相等;下限不能大于上限 |
|
|
31
|
+
| `show_subtitle` | bool | 是否展示字幕;显式 `false` 必须保留 |
|
|
32
|
+
| `video_model` | string | 必填;模型原始标识,保留大小写 |
|
|
33
|
+
| `video_resolution` | string | 分辨率偏好,如 `480p`、`720p`、`1080p` |
|
|
34
|
+
|
|
35
|
+
核对时文档列出 VIP 模型 `seedance2.0_fast_vision`、`seedance2.0_vision`、`Seedance_2.0_mini`,非 VIP 模型 `Seedance_2.0_mini_lite`;注明 `1080p` 目前仅支持 `seedance2.0_vision`。脚本不固化模型白名单,不自动降级或代换用户选择;若服务端拒绝,保留错误并说明参数或权限差异。
|
|
36
|
+
|
|
37
|
+
## Responses
|
|
38
|
+
|
|
39
|
+
HTTP 成功还需检查 `ret` 为 `"0"` 或 `0`;失败保留 `errmsg` 和 `log_id`。
|
|
40
|
+
|
|
41
|
+
- 上传:`data.pippit_asset_id`。
|
|
42
|
+
- 提交:`data.run.thread_id`、`data.run.run_id`、`data.run.state`;可有 `data.web_thread_link`。
|
|
43
|
+
- 查询:`data.thread_id`、`data.run_id`、`data.run_state`(兼容数字/字符串);可有 `video_urls`、`image_urls`、`fail_reason: {code, message}`。
|
|
44
|
+
- 积分:`data.total_remain_amount` 为数字字符串,零余额为 `"0"`,不可丢失或转为浮点数。
|
|
45
|
+
|
|
46
|
+
服务端 `RunState` 状态枚举(2026-09-22 按生成协议代码核实):
|
|
47
|
+
|
|
48
|
+
| 值 | 名称 | 查询处理 |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| 0 | Unspecified | 未指定;保留响应并停止诊断 |
|
|
51
|
+
| 1 | Submitted | 已提交;继续查询 |
|
|
52
|
+
| 2 | Working | 处理中;继续查询 |
|
|
53
|
+
| 3 | Completed | 当前 Run 完成;检查真实媒体列表 |
|
|
54
|
+
| 4 | Failed | 失败;返回原因 |
|
|
55
|
+
| 5 | Canceled | 已取消 |
|
|
56
|
+
| 6 | InputRequired | 等待补充输入;停止轮询并处理交互 |
|
|
57
|
+
| 7 | Generating | 生成中;继续查询 |
|
|
58
|
+
| 8 | Interrupt | 程序中断,例如等待工具回调;有时限地继续查询 |
|
|
59
|
+
| 9 | HITL_Interrupt | 当前 Run 因人工交互中断结束;处理确认后查询同一 thread 的最新 Run |
|
|
60
|
+
|
|
61
|
+
查询接口透传 Run 状态;仅在 3 时提取媒体,4/5 时提取失败原因,其它状态返回空媒体数组。因此 9 的空 `video_urls` 不能解释为生成失败。状态 3 也不保证有视频;以真实媒体列表判断交付。未知数值保留原始响应并停止,不自动重提或无期限轮询。
|
|
62
|
+
|
|
63
|
+
一条 thread 可以有多个 Run。问卷、达人图或工具执行确认会产生后续 Run,原 `run_id` 不会自动变成最新运行。确认后从网页或可用的 `pippit-tool-cli get-thread --thread-id THREAD_ID` 读取新的真实 run_id,再传给营销查询命令;不要猜 ID 或为查询创建新 Run。
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Documented Xiaoyunque marketing API; Node.js >= 16, no dependencies.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { Readable } = require('stream');
|
|
8
|
+
const { pipeline } = require('stream/promises');
|
|
9
|
+
|
|
10
|
+
const BASE = 'https://xyq.jianying.com';
|
|
11
|
+
const PATHS = {
|
|
12
|
+
generate: '/api/biz/v1/agent/submit_marketing_run',
|
|
13
|
+
upload: '/api/biz/v1/skill/upload_file',
|
|
14
|
+
query: '/api/biz/v1/agent/query_generate_video_result',
|
|
15
|
+
balance: '/api/biz/v1/skill/get_credit_balance',
|
|
16
|
+
};
|
|
17
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
18
|
+
const nonempty = value => typeof value === 'string' && value.trim().length > 0;
|
|
19
|
+
const RUN_STATES = Object.freeze({ 0: 'Unspecified', 1: 'Submitted', 2: 'Working', 3: 'Completed', 4: 'Failed', 5: 'Canceled', 6: 'InputRequired', 7: 'Generating', 8: 'Interrupt', 9: 'HITL_Interrupt' });
|
|
20
|
+
const WAIT_STATES = ['1', '2', '7', '8'];
|
|
21
|
+
const INPUT_STATES = ['6', '9'];
|
|
22
|
+
function requireValue(condition, message) { if (!condition) throw new Error(message); }
|
|
23
|
+
function onlyKeys(value, keys) {
|
|
24
|
+
requireValue(object(value), '请求字段必须是 JSON 对象');
|
|
25
|
+
requireValue(Object.keys(value).every(key => keys.includes(key)), '存在未支持的字段,请核对接口文档');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validate(body) {
|
|
29
|
+
onlyKeys(body, ['message', 'asset_ids', 'thread_id', 'general_agent_settings']);
|
|
30
|
+
requireValue(nonempty(body.message), 'message 必须为用户的非空创作指令');
|
|
31
|
+
if ('thread_id' in body) requireValue(nonempty(body.thread_id), 'thread_id 必须为非空字符串');
|
|
32
|
+
if ('asset_ids' in body) requireValue(Array.isArray(body.asset_ids) && body.asset_ids.every(nonempty), 'asset_ids 必须为素材 ID 字符串数组');
|
|
33
|
+
const settings = body.general_agent_settings;
|
|
34
|
+
onlyKeys(settings, ['ratio', 'duration_start', 'duration_end', 'show_subtitle', 'video_model', 'video_resolution']);
|
|
35
|
+
requireValue(nonempty(settings.video_model), 'general_agent_settings.video_model 必填;请先取得用户模型选择或按已授权的选择范围配置模型');
|
|
36
|
+
if ('ratio' in settings) requireValue([2, 3, 4, 5, 6].includes(settings.ratio), 'ratio 必须为 2/3/4/5/6 的整数枚举');
|
|
37
|
+
for (const key of ['duration_start', 'duration_end']) {
|
|
38
|
+
if (key in settings) requireValue(Number.isInteger(settings[key]) && settings[key] > 0 && settings[key] <= 2147483647, `${key} 必须为正整数秒数(int32)`);
|
|
39
|
+
}
|
|
40
|
+
if ('duration_start' in settings && 'duration_end' in settings) requireValue(settings.duration_start <= settings.duration_end, '时长下限不能大于上限');
|
|
41
|
+
if ('show_subtitle' in settings) requireValue(typeof settings.show_subtitle === 'boolean', 'show_subtitle 必须为布尔值');
|
|
42
|
+
for (const key of ['video_model', 'video_resolution']) {
|
|
43
|
+
if (key in settings) requireValue(nonempty(settings[key]), `${key} 必须为非空字符串`);
|
|
44
|
+
}
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkResponse(action, result, body) {
|
|
49
|
+
requireValue(object(result) && (result.ret === '0' || result.ret === 0),
|
|
50
|
+
`API 失败:ret=${result && result.ret} errmsg=${result && result.errmsg || ''} log_id=${result && result.log_id || ''}`);
|
|
51
|
+
const data = result.data;
|
|
52
|
+
requireValue(object(data), 'API 响应缺少 data');
|
|
53
|
+
if (action === 'generate') requireValue(object(data.run) && nonempty(data.run.thread_id) && nonempty(data.run.run_id), '提交响应缺少任务 ID;结果不明确,禁止自动重提');
|
|
54
|
+
if (action === 'upload') requireValue(nonempty(data.pippit_asset_id), '上传响应缺少 pippit_asset_id');
|
|
55
|
+
if (action === 'balance') requireValue(typeof data.total_remain_amount === 'string' && /^\d+$/.test(data.total_remain_amount), '余额响应缺少字符串 total_remain_amount');
|
|
56
|
+
if (action === 'query') {
|
|
57
|
+
requireValue(data.thread_id === body.thread_id && data.run_id === body.run_id, '返回任务 ID 与请求不一致');
|
|
58
|
+
requireValue((typeof data.run_state === 'number' || typeof data.run_state === 'string') && /^\d+$/.test(String(data.run_state)), 'run_state 缺失或格式异常');
|
|
59
|
+
for (const key of ['video_urls', 'image_urls']) {
|
|
60
|
+
if (key in data) requireValue(Array.isArray(data[key]) && data[key].every(nonempty), `${key} 格式异常`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createClient({ key = process.env.XYQ_ACCESS_KEY, request = https.request, timeout = 60000 } = {}) {
|
|
67
|
+
function open(url, method, headers, body) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const target = new URL(url);
|
|
70
|
+
requireValue(target.protocol === 'https:' && !target.username && !target.password, '仅支持无内嵌凭据的 HTTPS URL');
|
|
71
|
+
const req = request(target, { method, headers }, res => {
|
|
72
|
+
// Keep the total deadline active through response consumption.
|
|
73
|
+
res.once('end', () => clearTimeout(timer));
|
|
74
|
+
res.once('close', () => clearTimeout(timer));
|
|
75
|
+
resolve(res);
|
|
76
|
+
});
|
|
77
|
+
const timer = setTimeout(() => req.destroy(new Error('请求超时;提交结果可能不明确,请勿自动重提')), timeout);
|
|
78
|
+
req.once('error', error => { clearTimeout(timer); reject(error); });
|
|
79
|
+
if (body && typeof body.pipe === 'function') {
|
|
80
|
+
body.once('error', error => req.destroy(error));
|
|
81
|
+
req.once('close', () => body.destroy());
|
|
82
|
+
body.pipe(req);
|
|
83
|
+
} else req.end(body);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function api(action, body, headers = { 'Content-Type': 'application/json' }) {
|
|
88
|
+
requireValue(nonempty(key) && !/[\r\n]/.test(key), '请在本机环境设置 XYQ_ACCESS_KEY,不要在聊天中发送密钥;CLI 登录不会设置此变量');
|
|
89
|
+
const wire = headers['Content-Type'] === 'application/json' ? JSON.stringify(body) : body;
|
|
90
|
+
const res = await open(BASE + PATHS[action], 'POST', { ...headers, Accept: 'application/json', Authorization: `Bearer ${key}` }, wire);
|
|
91
|
+
// Never follow API redirects or automatically retry a POST.
|
|
92
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
93
|
+
res.resume();
|
|
94
|
+
throw new Error(`HTTP ${res.statusCode};请求未重试,生成结果可能不明确`);
|
|
95
|
+
}
|
|
96
|
+
const chunks = [];
|
|
97
|
+
let size = 0;
|
|
98
|
+
for await (const chunk of res) {
|
|
99
|
+
size += chunk.length;
|
|
100
|
+
requireValue(size <= 8 * 1024 * 1024, 'API 响应超出 8 MiB 限制');
|
|
101
|
+
chunks.push(chunk);
|
|
102
|
+
}
|
|
103
|
+
let result;
|
|
104
|
+
try { result = JSON.parse(Buffer.concat(chunks).toString('utf8')); }
|
|
105
|
+
catch (_) { throw new Error('API 未返回有效 JSON;提交结果可能不明确,请勿自动重提'); }
|
|
106
|
+
return checkResponse(action, result, body);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function upload(file) {
|
|
110
|
+
const stat = await fs.promises.stat(file);
|
|
111
|
+
requireValue(stat.isFile() && stat.size > 0 && stat.size < 500000000, '上传需要非空文件且小于 500 MB');
|
|
112
|
+
const boundary = 'xyq-' + crypto.randomBytes(16).toString('hex');
|
|
113
|
+
const name = path.basename(file).replace(/["\r\n\\]/g, '_');
|
|
114
|
+
const mime = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.mp4': 'video/mp4', '.mp3': 'audio/mpeg', '.wav': 'audio/wav' }[path.extname(file).toLowerCase()] || 'application/octet-stream';
|
|
115
|
+
const head = Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${name}"\r\nContent-Type: ${mime}\r\n\r\n`);
|
|
116
|
+
const tail = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
117
|
+
const body = Readable.from((async function* () {
|
|
118
|
+
yield head;
|
|
119
|
+
for await (const chunk of fs.createReadStream(file)) yield chunk;
|
|
120
|
+
yield tail;
|
|
121
|
+
})());
|
|
122
|
+
return api('upload', body, { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': head.length + stat.size + tail.length });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function download(url, destination) {
|
|
126
|
+
// Media requests never receive the API key, including across redirects.
|
|
127
|
+
let res;
|
|
128
|
+
for (let redirects = 0; ; redirects++) {
|
|
129
|
+
res = await open(url, 'GET', {}, undefined);
|
|
130
|
+
if (![301, 302, 303, 307, 308].includes(res.statusCode)) break;
|
|
131
|
+
res.resume();
|
|
132
|
+
requireValue(redirects < 5 && res.headers.location, '媒体重定向次数过多或缺少 Location');
|
|
133
|
+
url = new URL(res.headers.location, url).href;
|
|
134
|
+
}
|
|
135
|
+
if (res.statusCode !== 200) { res.resume(); throw new Error(`媒体下载 HTTP ${res.statusCode}`); }
|
|
136
|
+
if (/text\/html|application\/json/i.test(res.headers['content-type'] || '')) {
|
|
137
|
+
res.resume(); throw new Error('下载返回网页或 JSON,未取得媒体');
|
|
138
|
+
}
|
|
139
|
+
// Exclusive creation preserves existing user files and permits safe recovery.
|
|
140
|
+
let output;
|
|
141
|
+
try { output = await fs.promises.open(destination, 'wx'); }
|
|
142
|
+
catch (error) { res.destroy(); throw error; }
|
|
143
|
+
try {
|
|
144
|
+
await pipeline(res, fs.createWriteStream(destination, { fd: output.fd, autoClose: false }));
|
|
145
|
+
await output.close();
|
|
146
|
+
requireValue((await fs.promises.stat(destination)).size > 0, '下载文件为空');
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await output.close().catch(() => {});
|
|
149
|
+
await fs.promises.unlink(destination).catch(() => {});
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
return path.resolve(destination);
|
|
153
|
+
}
|
|
154
|
+
return { api, upload, download };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const HELP = `小云雀营销 Skill(Node.js >= 16)
|
|
158
|
+
node marketing.js generate --request request.json [--dry-run | --execute]
|
|
159
|
+
node marketing.js upload --file product.png
|
|
160
|
+
node marketing.js query --thread-id ID --run-id ID [--wait] [--max-wait 900] [--output-dir DIR]
|
|
161
|
+
node marketing.js balance
|
|
162
|
+
所有 API 调用从环境读取 XYQ_ACCESS_KEY;generate 默认仅预览。
|
|
163
|
+
--timeout 秒数:单请求总时限,默认 60;--max-wait:轮询总时限,默认 900。
|
|
164
|
+
query 输出 API 原始响应;有 --output-dir 时成功结果附带 downloaded_files。
|
|
165
|
+
退出码:0 成功/单次查询进行中;1 输入或接口错误;2 生成失败/取消/无视频;3 等待超时;4 等待用户交互;5 未知或未指定状态。
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
function parseArgs(argv) {
|
|
169
|
+
const [action, ...rest] = argv;
|
|
170
|
+
requireValue(Object.hasOwnProperty.call(PATHS, action), '操作必须是 generate/upload/query/balance');
|
|
171
|
+
const options = {};
|
|
172
|
+
const flags = ['execute', 'dry-run', 'wait'];
|
|
173
|
+
const allowed = { generate: ['request', 'execute', 'dry-run'], upload: ['file'], query: ['thread-id', 'run-id', 'wait', 'max-wait', 'output-dir'], balance: [] }[action].concat('timeout');
|
|
174
|
+
for (let i = 0; i < rest.length; i++) {
|
|
175
|
+
const name = rest[i].replace(/^--/, '');
|
|
176
|
+
requireValue(rest[i].startsWith('--') && allowed.includes(name) && !(name in options), `无效或重复参数:${rest[i]}`);
|
|
177
|
+
if (flags.includes(name)) options[name] = true;
|
|
178
|
+
else {
|
|
179
|
+
requireValue(nonempty(rest[i + 1]) && !rest[i + 1].startsWith('--'), `${name} 缺少值`);
|
|
180
|
+
options[name] = rest[++i];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
requireValue(!(options.execute && options['dry-run']), '--execute 和 --dry-run 不能同时使用');
|
|
184
|
+
for (const [name, fallback, max] of [['timeout', 60, 1800], ['max-wait', 900, 86400]]) {
|
|
185
|
+
const value = options[name] === undefined ? fallback : Number(options[name]);
|
|
186
|
+
requireValue(Number.isInteger(value) && value > 0 && value <= max, `${name} 必须为 1 至 ${max} 的整数秒数`);
|
|
187
|
+
options[name] = value;
|
|
188
|
+
}
|
|
189
|
+
return { action, options };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function main(argv, { env = process.env, out = console.log, clientFactory = createClient, now = Date.now, pause = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
|
|
193
|
+
if (!argv.length || argv.includes('--help') || argv.includes('-h')) { out(HELP); return 0; }
|
|
194
|
+
const { action, options } = parseArgs(argv);
|
|
195
|
+
let body = {};
|
|
196
|
+
if (action === 'generate') {
|
|
197
|
+
requireValue(options.request, 'generate 需要 --request JSON 文件');
|
|
198
|
+
body = validate(JSON.parse(await fs.promises.readFile(options.request, 'utf8')));
|
|
199
|
+
if (!options.execute) { out(JSON.stringify({ dry_run: true, url: BASE + PATHS.generate, body })); return 0; }
|
|
200
|
+
}
|
|
201
|
+
if (action === 'query') {
|
|
202
|
+
requireValue(nonempty(options['thread-id']) && nonempty(options['run-id']), 'query 需要 thread-id 和 run-id');
|
|
203
|
+
body = { thread_id: options['thread-id'], run_id: options['run-id'] };
|
|
204
|
+
}
|
|
205
|
+
if (action === 'upload') requireValue(nonempty(options.file), 'upload 需要 --file');
|
|
206
|
+
const client = clientFactory({ key: env.XYQ_ACCESS_KEY, timeout: options.timeout * 1000 });
|
|
207
|
+
let result;
|
|
208
|
+
const deadline = now() + options['max-wait'] * 1000;
|
|
209
|
+
do {
|
|
210
|
+
// A poll request cannot overrun the remaining wait budget.
|
|
211
|
+
const pollClient = action === 'query' && options.wait
|
|
212
|
+
? clientFactory({ key: env.XYQ_ACCESS_KEY, timeout: Math.min(options.timeout * 1000, Math.max(1, deadline - now())) }) : client;
|
|
213
|
+
result = action === 'upload' ? await client.upload(options.file) : await pollClient.api(action, body);
|
|
214
|
+
if (action !== 'query' || !options.wait || !WAIT_STATES.includes(String(result.data.run_state))) break;
|
|
215
|
+
if (now() >= deadline) {
|
|
216
|
+
out(JSON.stringify({ ...result, wait_timed_out: true })); return 3;
|
|
217
|
+
}
|
|
218
|
+
await pause(Math.min(10000, deadline - now()));
|
|
219
|
+
if (now() >= deadline) { out(JSON.stringify({ ...result, wait_timed_out: true })); return 3; }
|
|
220
|
+
} while (true);
|
|
221
|
+
if (action === 'query') {
|
|
222
|
+
const state = String(result.data.run_state);
|
|
223
|
+
if (INPUT_STATES.includes(state)) {
|
|
224
|
+
out(JSON.stringify({ ...result, run_state_name: RUN_STATES[state], action_required: true,
|
|
225
|
+
next_step: '查看同一会话的确认或问卷;沿用已有授权,缺少必要选择时再询问用户。确认后取得同一 thread 的最新 run_id 再查询,旧 Run 可保持此状态。不要重复生成。' }));
|
|
226
|
+
return 4;
|
|
227
|
+
}
|
|
228
|
+
if (!(state in RUN_STATES) || state === '0') {
|
|
229
|
+
out(JSON.stringify({ ...result, run_state_name: RUN_STATES[state] || 'Unknown', unknown_state: true,
|
|
230
|
+
next_step: '停止自动轮询,保留任务 ID 和原始响应,核实服务端状态含义;不要重新提交生成。' }));
|
|
231
|
+
return 5;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Print IDs and URLs before downloads so a partial download failure remains resumable.
|
|
235
|
+
out(JSON.stringify(result));
|
|
236
|
+
if (action === 'query') {
|
|
237
|
+
const state = String(result.data.run_state);
|
|
238
|
+
if (['4', '5'].includes(state)) return 2;
|
|
239
|
+
if (state === '3') {
|
|
240
|
+
if (!(result.data.video_urls || []).length) return 2;
|
|
241
|
+
if (options['output-dir']) {
|
|
242
|
+
await fs.promises.mkdir(options['output-dir'], { recursive: true });
|
|
243
|
+
const downloaded = [];
|
|
244
|
+
for (const [field, ext] of [['video_urls', '.mp4'], ['image_urls', '.jpg']]) {
|
|
245
|
+
for (const [index, url] of (result.data[field] || []).entries()) {
|
|
246
|
+
const suffix = path.extname(new URL(url).pathname);
|
|
247
|
+
const mediaExt = /^\.(mp4|mov|webm|mkv|jpg|jpeg|png|webp|gif)$/i.test(suffix) ? suffix : ext;
|
|
248
|
+
const file = path.join(options['output-dir'], `${field}-${index + 1}-${crypto.randomBytes(4).toString('hex')}${mediaExt}`);
|
|
249
|
+
downloaded.push({ url, path: await client.download(url, file) });
|
|
250
|
+
out(JSON.stringify({ downloaded_file: downloaded[downloaded.length - 1] }));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
out(JSON.stringify({ ...result, downloaded_files: downloaded }));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
main(process.argv.slice(2)).then(code => { process.exitCode = code; }).catch(error => {
|
|
262
|
+
const key = process.env.XYQ_ACCESS_KEY;
|
|
263
|
+
const message = key ? error.message.split(key).join('[REDACTED]') : error.message;
|
|
264
|
+
console.error(JSON.stringify({ error: message, note: '请求未自动重试;已有任务请按原 thread_id/run_id 继续查询。' }));
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
module.exports = { BASE, PATHS, validate, checkResponse, createClient, parseArgs, main };
|
|
@@ -10,6 +10,8 @@ metadata:
|
|
|
10
10
|
|
|
11
11
|
通过 CLI 完成生成、处理、结果下载与媒体交付。支持下表中的操作;不提供多轮会话续写或自动拆分剧本、分镜并编排成片的能力。复杂需求先确认能由所列命令完成的具体操作,不承诺未覆盖的流程。
|
|
12
12
|
|
|
13
|
+
商品图文营销一键成片(剧情广告、品牌大片、达人带货)由独立的 `xyq-marketing-skill` 提供,不用普通生视频代替营销编排;未安装该 Skill 时先说明当前能力边界。
|
|
14
|
+
|
|
13
15
|
## 开始执行
|
|
14
16
|
|
|
15
17
|
1. 画布任务运行 `node "{baseDir}/scripts/ensure-cli.js" --canvas`,其他任务运行 `node "{baseDir}/scripts/ensure-cli.js"`。保存返回的 `cli_path`;Canvas 还需保存 `canvas_entry`。文档中的 `pippit-tool-cli` 替换为带引号的 `cli_path`;画布语义命令按模块说明通过 Node 入口执行。同一任务复用,安装细节见 [安装说明](scripts/install.md)。
|