@pippit-dev/cli 0.0.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/cmd/auth/auth.go +144 -0
- package/cmd/root.go +42 -0
- package/cmd/short_drama/short_drama.go +196 -0
- package/cmd/short_drama_test.go +587 -0
- package/cmd/update/update.go +124 -0
- package/internal/auth/manager.go +146 -0
- package/internal/common/access_key.go +29 -0
- package/internal/common/client.go +137 -0
- package/internal/common/download_results.go +243 -0
- package/internal/common/list_thread_file.go +104 -0
- package/internal/common/runner.go +21 -0
- package/internal/common/upload_file.go +49 -0
- package/internal/config/config.go +72 -0
- package/internal/config/config_test.go +49 -0
- package/internal/short_drama/get_thread.go +137 -0
- package/internal/short_drama/mock.go +47 -0
- package/internal/short_drama/submit_run.go +81 -0
- package/package.json +45 -0
- package/scripts/install-wizard.js +71 -0
- package/scripts/install.js +170 -0
- package/scripts/platform.js +28 -0
- package/scripts/run.js +62 -0
- package/scripts/skills.js +32 -0
- package/skills/short-drama/SKILL.md +280 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { isWindows, run } = require("./platform");
|
|
8
|
+
const { installSkillsFromRoot } = require("./skills");
|
|
9
|
+
|
|
10
|
+
const VERSION = require("../package.json").version.replace(/-.*$/, "");
|
|
11
|
+
const REPO = "Pippit-dev/cli";
|
|
12
|
+
const NAME = "pippit-cli";
|
|
13
|
+
const ROOT = path.join(__dirname, "..");
|
|
14
|
+
const BIN_DIR = path.join(ROOT, "bin");
|
|
15
|
+
|
|
16
|
+
const PLATFORM_MAP = {
|
|
17
|
+
darwin: "darwin",
|
|
18
|
+
linux: "linux",
|
|
19
|
+
win32: "windows",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ARCH_MAP = {
|
|
23
|
+
x64: "amd64",
|
|
24
|
+
arm64: "arm64",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
28
|
+
const arch = ARCH_MAP[process.arch];
|
|
29
|
+
const archiveExt = isWindows ? ".zip" : ".tar.gz";
|
|
30
|
+
const archiveName = `${NAME}-${VERSION}-${platform}-${arch}${archiveExt}`;
|
|
31
|
+
const releaseURL = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
32
|
+
const dest = path.join(BIN_DIR, NAME + (isWindows ? ".exe" : ""));
|
|
33
|
+
|
|
34
|
+
function download(url, destPath) {
|
|
35
|
+
const args = [
|
|
36
|
+
"--fail",
|
|
37
|
+
"--location",
|
|
38
|
+
"--silent",
|
|
39
|
+
"--show-error",
|
|
40
|
+
"--connect-timeout",
|
|
41
|
+
"10",
|
|
42
|
+
"--max-time",
|
|
43
|
+
"120",
|
|
44
|
+
"--max-redirs",
|
|
45
|
+
"3",
|
|
46
|
+
"--output",
|
|
47
|
+
destPath,
|
|
48
|
+
];
|
|
49
|
+
if (isWindows) {
|
|
50
|
+
args.unshift("--ssl-revoke-best-effort");
|
|
51
|
+
}
|
|
52
|
+
args.push(url);
|
|
53
|
+
run("curl", args);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function expectedChecksum(name) {
|
|
57
|
+
const checksumsPath = path.join(ROOT, "checksums.txt");
|
|
58
|
+
if (!fs.existsSync(checksumsPath)) {
|
|
59
|
+
console.warn("[WARN] checksums.txt not found, skipping checksum verification");
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
for (const line of fs.readFileSync(checksumsPath, "utf8").split("\n")) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed) continue;
|
|
65
|
+
const idx = trimmed.indexOf(" ");
|
|
66
|
+
if (idx === -1) continue;
|
|
67
|
+
const hash = trimmed.slice(0, idx);
|
|
68
|
+
const file = trimmed.slice(idx + 2);
|
|
69
|
+
if (file === name) return hash;
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Checksum entry not found for ${name}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function verifyChecksum(filePath, expectedHash) {
|
|
75
|
+
if (!expectedHash) return;
|
|
76
|
+
const hash = crypto.createHash("sha256");
|
|
77
|
+
const fd = fs.openSync(filePath, "r");
|
|
78
|
+
try {
|
|
79
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
80
|
+
let bytesRead;
|
|
81
|
+
while ((bytesRead = fs.readSync(fd, buf, 0, buf.length, null)) > 0) {
|
|
82
|
+
hash.update(buf.subarray(0, bytesRead));
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
fs.closeSync(fd);
|
|
86
|
+
}
|
|
87
|
+
const actual = hash.digest("hex");
|
|
88
|
+
if (actual.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
89
|
+
throw new Error(`Checksum mismatch for ${path.basename(filePath)}: expected ${expectedHash}, got ${actual}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractZipWindows(archivePath, destDir) {
|
|
94
|
+
const env = {
|
|
95
|
+
...process.env,
|
|
96
|
+
PIPPIT_CLI_ARCHIVE: archivePath,
|
|
97
|
+
PIPPIT_CLI_DEST: destDir,
|
|
98
|
+
};
|
|
99
|
+
const ps = [
|
|
100
|
+
"-NoProfile",
|
|
101
|
+
"-ExecutionPolicy",
|
|
102
|
+
"Bypass",
|
|
103
|
+
"-Command",
|
|
104
|
+
"$ErrorActionPreference='Stop';Expand-Archive -LiteralPath $env:PIPPIT_CLI_ARCHIVE -DestinationPath $env:PIPPIT_CLI_DEST -Force",
|
|
105
|
+
];
|
|
106
|
+
run("powershell.exe", ps, { env });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractArchive(archivePath, destDir) {
|
|
110
|
+
if (isWindows) {
|
|
111
|
+
extractZipWindows(archivePath, destDir);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
run("tar", ["-xzf", archivePath, "-C", destDir]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function install() {
|
|
118
|
+
if (!platform || !arch) {
|
|
119
|
+
throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
123
|
+
|
|
124
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pippit-cli-"));
|
|
125
|
+
const archivePath = path.join(tmpDir, archiveName);
|
|
126
|
+
try {
|
|
127
|
+
download(releaseURL, archivePath);
|
|
128
|
+
verifyChecksum(archivePath, expectedChecksum(archiveName));
|
|
129
|
+
extractArchive(archivePath, tmpDir);
|
|
130
|
+
|
|
131
|
+
const extracted = path.join(tmpDir, NAME + (isWindows ? ".exe" : ""));
|
|
132
|
+
fs.copyFileSync(extracted, dest);
|
|
133
|
+
fs.chmodSync(dest, 0o755);
|
|
134
|
+
|
|
135
|
+
if (process.env.PIPPIT_CLI_SKIP_SKILLS !== "1") {
|
|
136
|
+
installSkillsFromRoot(ROOT);
|
|
137
|
+
}
|
|
138
|
+
console.log(`${NAME} v${VERSION} installed successfully`);
|
|
139
|
+
} finally {
|
|
140
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (require.main === module) {
|
|
145
|
+
const isNpxPostinstall =
|
|
146
|
+
process.env.npm_command === "exec" && !process.env.PIPPIT_CLI_RUN;
|
|
147
|
+
|
|
148
|
+
if (isNpxPostinstall) {
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
install();
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error(`Failed to install ${NAME}: ${err.message || err}`);
|
|
156
|
+
console.error(
|
|
157
|
+
"\nTry:\n" +
|
|
158
|
+
" npm install -g @pippit-dev/cli\n" +
|
|
159
|
+
` node "${path.join(__dirname, "install.js")}"\n`
|
|
160
|
+
);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
archiveName,
|
|
167
|
+
expectedChecksum,
|
|
168
|
+
install,
|
|
169
|
+
verifyChecksum,
|
|
170
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { execFileSync } = require("child_process");
|
|
2
|
+
|
|
3
|
+
const isWindows = process.platform === "win32";
|
|
4
|
+
|
|
5
|
+
function execCmd(cmd, args, opts = {}) {
|
|
6
|
+
if (isWindows) {
|
|
7
|
+
return execFileSync("cmd.exe", ["/c", cmd, ...args], opts);
|
|
8
|
+
}
|
|
9
|
+
return execFileSync(cmd, args, opts);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function run(cmd, args, opts = {}) {
|
|
13
|
+
return execCmd(cmd, args, { stdio: "inherit", ...opts });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function runSilent(cmd, args, opts = {}) {
|
|
17
|
+
return execCmd(cmd, args, {
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
...opts,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
execCmd,
|
|
25
|
+
isWindows,
|
|
26
|
+
run,
|
|
27
|
+
runSilent,
|
|
28
|
+
};
|
package/scripts/run.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
8
|
+
const bin = path.join(__dirname, "..", "bin", "pippit-cli" + ext);
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
const oldBin = bin + ".old";
|
|
12
|
+
function restoreOldBinary() {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(bin)) {
|
|
15
|
+
fs.rmSync(bin, { force: true });
|
|
16
|
+
}
|
|
17
|
+
fs.renameSync(oldBin, bin);
|
|
18
|
+
return true;
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (process.platform === "win32" && fs.existsSync(oldBin)) {
|
|
25
|
+
if (!fs.existsSync(bin)) {
|
|
26
|
+
restoreOldBinary();
|
|
27
|
+
} else {
|
|
28
|
+
try {
|
|
29
|
+
execFileSync(bin, ["--help"], { stdio: "ignore", timeout: 10000 });
|
|
30
|
+
fs.rmSync(oldBin, { force: true });
|
|
31
|
+
} catch (_) {
|
|
32
|
+
restoreOldBinary();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Match the lark-cli install entry: `npx @pippit-dev/cli@latest install`
|
|
38
|
+
// should run the JS setup flow before the native binary exists.
|
|
39
|
+
if (args[0] === "install") {
|
|
40
|
+
require("./install-wizard.js");
|
|
41
|
+
} else {
|
|
42
|
+
if (!fs.existsSync(bin)) {
|
|
43
|
+
try {
|
|
44
|
+
execFileSync(process.execPath, [path.join(__dirname, "install.js")], {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
env: { ...process.env, PIPPIT_CLI_RUN: "true" },
|
|
47
|
+
});
|
|
48
|
+
} catch (_) {
|
|
49
|
+
console.error(
|
|
50
|
+
"\nFailed to prepare pippit-cli binary.\n" +
|
|
51
|
+
"Make sure Go is installed and available in PATH, then retry.\n"
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
execFileSync(bin, args, { stdio: "inherit" });
|
|
59
|
+
} catch (e) {
|
|
60
|
+
process.exit(e.status || 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { run, runSilent } = require("./platform");
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PKG = "@pippit-dev/cli";
|
|
6
|
+
|
|
7
|
+
function installSkillsFromRoot(root, opts = {}) {
|
|
8
|
+
const source = path.resolve(root);
|
|
9
|
+
const skillsDir = path.join(source, "skills");
|
|
10
|
+
if (!fs.existsSync(skillsDir)) {
|
|
11
|
+
throw new Error(`skills directory not found: ${skillsDir}`);
|
|
12
|
+
}
|
|
13
|
+
run("npx", ["-y", "skills", "add", source, "-g", "-y"], {
|
|
14
|
+
timeout: opts.timeout || 120000,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function globalPackageRoot(pkg = DEFAULT_PKG) {
|
|
19
|
+
const npmRoot = runSilent("npm", ["root", "-g"], { timeout: 15000 }).toString().trim();
|
|
20
|
+
return path.join(npmRoot, ...pkg.split("/"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function installGlobalPackageSkills(pkg = DEFAULT_PKG, opts = {}) {
|
|
24
|
+
installSkillsFromRoot(globalPackageRoot(pkg), opts);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
DEFAULT_PKG,
|
|
29
|
+
globalPackageRoot,
|
|
30
|
+
installGlobalPackageSkills,
|
|
31
|
+
installSkillsFromRoot,
|
|
32
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pippit-short-drama-skill
|
|
3
|
+
description: 使用 pippit-cli 的短剧场景能力提交和查询短剧创作任务。覆盖短剧生成、续写、改写、剧情扩展、人物设定、分集草稿、世界观设定、会话文件获取、文件资源下载等创作场景。当用户要求创作短剧、写短剧剧本、续写故事、修改剧情、补充角色设定、查询短剧任务进展、获取短剧会话文件或下载短剧文件资源,或提到 pippit-cli short-drama / 小云雀短剧时触发。
|
|
4
|
+
user-invocable: true
|
|
5
|
+
metadata:
|
|
6
|
+
{
|
|
7
|
+
"openclaw":
|
|
8
|
+
{
|
|
9
|
+
"emoji": "📖",
|
|
10
|
+
"requires":
|
|
11
|
+
{
|
|
12
|
+
"bins": ["pippit-cli"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# 小云雀短剧创作
|
|
19
|
+
|
|
20
|
+
通过 `pippit-cli short-drama` 命令提交短剧创作任务、上传参考文件,并查询任务进展,获取会话产物文件。
|
|
21
|
+
|
|
22
|
+
短剧场景面向剧情、人物、分集与画面化叙事创作,用户的原始需求通过 `--message` 发送给后端 Agent。后端 Agent 负责理解任务、编排流程和生成内容;用户侧 Agent 只负责提交任务、查询进展和展示结果。
|
|
23
|
+
|
|
24
|
+
## 功能
|
|
25
|
+
|
|
26
|
+
1. **提交短剧 Run 任务** - 创建新会话或向已有会话发送短剧创作需求。
|
|
27
|
+
2. **查询会话进展** - 根据 `thread_id`、`run_id`、`after_seq` 拉取短剧任务消息列表。
|
|
28
|
+
3. **上传文件** - 上传短剧相关参考文件,得到文件 ID,供后续任务引用。
|
|
29
|
+
4. **获取会话文件** - 根据 `thread_id` 拉取会话文件列表,得到 `file_path`、`file_name`、`download_url`。
|
|
30
|
+
5. **下载文件资源** - 使用文件列表中的 `download_url` 下载资源,并按 `file_path` 写入用户本地目录。
|
|
31
|
+
|
|
32
|
+
## 前置要求
|
|
33
|
+
|
|
34
|
+
需要已安装 `pippit-cli`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx @pippit-dev/cli@latest install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 使用方法
|
|
41
|
+
|
|
42
|
+
### 1. 提交短剧任务
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 创建新会话并提交短剧创作需求
|
|
46
|
+
pippit-cli short-drama +submit-run --message "创作一个赛博朋克短剧开头"
|
|
47
|
+
|
|
48
|
+
# 向已有会话追加新的短剧需求
|
|
49
|
+
pippit-cli short-drama +submit-run --message "继续写下一集,重点描写主角的逃亡" --thread-id THREAD_ID
|
|
50
|
+
|
|
51
|
+
# 携带已上传文件 ID 提交任务
|
|
52
|
+
pippit-cli short-drama +submit-run --message "参考这个大纲写第一集" --asset-ids ASSET_ID
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. 查询短剧任务进展
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 查询会话消息列表
|
|
59
|
+
pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq 0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> `thread_id` 和 `run_id` 由 `+submit-run` 返回。`after-seq` 用于增量拉取消息,首次查询可使用 `0`。
|
|
63
|
+
|
|
64
|
+
### 3. 上传文件
|
|
65
|
+
|
|
66
|
+
当用户提供短剧大纲、人物设定、世界观设定、已有分集或剧本等本地文件路径时,可先上传文件。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pippit-cli short-drama +upload-file --path /path/to/outline.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. 获取会话文件
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 获取会话文件列表
|
|
76
|
+
pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 100
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`+list-thread-file` 返回的每个文件对象包含:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"file_path": "./{thread-id}/路径/文件名", // 文件完整路径,包含文件名
|
|
84
|
+
"download_url": "https://..." // URL
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`+list-thread-file` 只负责获取会话文件列表,不负责下载文件。
|
|
89
|
+
|
|
90
|
+
### 5. 下载文件资源
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 下载文件资源到指定目录
|
|
94
|
+
pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-dir FILE_PATH
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`+download-result` 负责把会话产生的文件,通过URL下载到 `file_path`目录中,如果目标文件已存在,跳过下载。
|
|
98
|
+
|
|
99
|
+
## 典型工作流
|
|
100
|
+
|
|
101
|
+
### 场景 1:用户要求生成短剧内容
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
1. pippit-cli short-drama +submit-run --message "用户的原始短剧需求"
|
|
105
|
+
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
106
|
+
2. 立即将 web_thread_link 展示给用户
|
|
107
|
+
3. 并行发起:
|
|
108
|
+
a. pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq SEQUENCE
|
|
109
|
+
b. pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 100
|
|
110
|
+
4. 检查 `get-thread` 返回的 messages:
|
|
111
|
+
- 如果任务仍在进行中:展示过程消息,继续查询
|
|
112
|
+
- 如果后端 Agent 提出问题:展示问题,等待用户回复
|
|
113
|
+
5. 解析 `list-thread-file` 返回的 files,只获取文件元信息:
|
|
114
|
+
- 对每个文件取 file_path、file_name、download_url
|
|
115
|
+
- 按 file_path 在用户本地环境构建目录
|
|
116
|
+
- 如果在工作目录中 file_name 已存在:跳过下载
|
|
117
|
+
6. 对缺失的本地文件,调用 +download-result 并行下载资源:
|
|
118
|
+
- 使用第 5 步获取的 download_url 作为 --url
|
|
119
|
+
- 使用第 5 步获取的 file_path 作为 --output-dir
|
|
120
|
+
7. 如用户继续追加需求,使用同一 thread_id 再次 submit-run
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 场景 2:用户提供参考文件要求创作
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
1. pippit-cli short-drama +upload-file --path /path/to/file
|
|
127
|
+
→ 拿到 file_id
|
|
128
|
+
2. pippit-cli short-drama +submit-run --message "用户的原始短剧需求" --asset-ids file_id
|
|
129
|
+
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
130
|
+
3. 后续同场景 1 的并行查询和文件下载流程
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 场景 3:在已有短剧会话中续写或修改
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
1. pippit-cli short-drama +submit-run --message "用户的新需求" --thread-id THREAD_ID
|
|
137
|
+
→ 拿到新的 run_id 和 web_thread_link
|
|
138
|
+
2. 继续按场景 1 展示进展、处理用户补充问题、获取新增会话文件列表,并按需下载新增文件资源
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 轮询策略
|
|
142
|
+
|
|
143
|
+
- **间隔**:每 10 秒查询一次。
|
|
144
|
+
- **增量拉取**:首次使用 `--after-seq 0`,后续根据已读消息进度调整 `after-seq`。
|
|
145
|
+
- **并行查询**:每次 `+submit-run` 返回 `thread_id` 后,同时发起 `+get-thread` 和 `+list-thread-file`;会话信息展示流程保持不变,`+list-thread-file` 只用于获取文件元信息。
|
|
146
|
+
- **文件下载**:解析 `+list-thread-file` 的结果后,只有目标本地文件不存在时才调用 `+download-result` 下载资源。
|
|
147
|
+
- **用户确认**:如果消息中出现需要用户确认、补充设定或回答问题的内容,先展示给用户,等待用户回复。
|
|
148
|
+
- **超时**:如果长时间无结果,告知用户任务仍在生成中,可稍后通过 `web_thread_link` 查看。
|
|
149
|
+
- **错误处理**:单次查询失败可重试;连续失败时停止轮询并向用户说明错误。
|
|
150
|
+
|
|
151
|
+
## 输出格式
|
|
152
|
+
|
|
153
|
+
**+submit-run** 返回:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"thread_id": "thread_...",
|
|
158
|
+
"run_id": "run_...",
|
|
159
|
+
"web_thread_link": "https://xyq.jianying.com/..."
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**+get-thread** 返回:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"messages": [
|
|
168
|
+
{
|
|
169
|
+
"id": "message_...",
|
|
170
|
+
"role": "assistant",
|
|
171
|
+
"content": [
|
|
172
|
+
{
|
|
173
|
+
"type": "text",
|
|
174
|
+
"data": {}
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**+upload-file** 返回:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"scene": "short-drama",
|
|
187
|
+
"file_id": "file_...",
|
|
188
|
+
"status": "uploaded",
|
|
189
|
+
"uploaded_at": "2026-05-19T00:00:00Z",
|
|
190
|
+
"request": {
|
|
191
|
+
"path": "/path/to/file",
|
|
192
|
+
"file_name": "file.md"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**+list-thread-file** 返回:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"files": [
|
|
202
|
+
{
|
|
203
|
+
"file_path": "./{thread-id}/{file_path}/{file_name}",
|
|
204
|
+
"download_url": "https://..."
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"total": 1
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**+download-result** 返回:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"output_dir": "/path/to/output-dir",
|
|
216
|
+
"downloaded": ["/path/to/output-dir/01.md"],
|
|
217
|
+
"total": 1
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## 会话文件与资源下载
|
|
222
|
+
|
|
223
|
+
先用 `+list-thread-file` 获取会话文件列表,再按需用 `+download-result` 并行下载文件资源。
|
|
224
|
+
|
|
225
|
+
### 获取会话文件
|
|
226
|
+
|
|
227
|
+
从 `+list-thread-file` 的 `files` 中逐个读取文件元信息:`file_path`、`download_url`。
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
1. file_path在本地已存在
|
|
231
|
+
→ 跳过
|
|
232
|
+
2. file_path在本地不存在
|
|
233
|
+
→ 记录该file_path和URL
|
|
234
|
+
→ 多个待下载的文件 → 使用 +download-result 工具并行下载文件到本地
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 并行下载文件资源
|
|
238
|
+
|
|
239
|
+
只对目标路径不存在的文件调用下载工具,可并行:
|
|
240
|
+
|
|
241
|
+
1. 调用 `pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-dir FILE_PATH`。
|
|
242
|
+
2. 下载完成后,向用户展示本地文件路径;如果某个文件下载失败,只报告该文件错误,不阻塞已成功落盘的文件展示。
|
|
243
|
+
|
|
244
|
+
## 向用户展示内容
|
|
245
|
+
|
|
246
|
+
- 任务提交后:立即展示 `web_thread_link`。
|
|
247
|
+
- 任务进行中:展示后端 Agent 返回的过程消息。
|
|
248
|
+
- 需要用户补充信息时:原样展示后端 Agent 的问题,等待用户回复。
|
|
249
|
+
- 任务完成后:展示短剧内容、分集草稿、设定说明或其他结果信息。
|
|
250
|
+
- 获取会话文件后:展示或记录文件元信息,不把它当成已下载结果。
|
|
251
|
+
- 文件资源下载后:展示已落盘的本地文件路径;已存在而跳过下载的文件也要标明。
|
|
252
|
+
|
|
253
|
+
## 核心原则:用户侧不做创作,只做传话
|
|
254
|
+
|
|
255
|
+
你(用户侧 Agent)的职责是传递用户需求和展示后端结果,不是替后端 Agent 创作短剧。
|
|
256
|
+
|
|
257
|
+
你要做的只有三件事:
|
|
258
|
+
|
|
259
|
+
1. **上传**:如果用户给了本地参考文件,先调用 `+upload-file`。
|
|
260
|
+
2. **提交任务**:把用户原始短剧需求和文件 ID 通过 `+submit-run` 发给后端。
|
|
261
|
+
3. **传话、取文件、下载资源**:根据 `+get-thread` 返回的消息展示进展、问题和结果;根据 `+list-thread-file` 获取文件列表;再根据 `download_url` 调用 `+download-result` 把缺失资源下载到用户本地。
|
|
262
|
+
|
|
263
|
+
**不要做的事:**
|
|
264
|
+
|
|
265
|
+
- 不要替用户扩写、润色、翻译 prompt。
|
|
266
|
+
- 不要自行编排剧情、人物关系、世界观或分集大纲后再提交。
|
|
267
|
+
- 不要把用户的一个需求拆成多次 `+submit-run`,除非用户明确要求分多次处理。
|
|
268
|
+
- 不要将自己编写的短剧内容混入后端返回结果。
|
|
269
|
+
|
|
270
|
+
后端 Agent 会负责理解短剧任务、组织创作流程和生成内容。用户侧 Agent 越俎代庖会降低结果一致性。
|
|
271
|
+
|
|
272
|
+
## 注意事项
|
|
273
|
+
|
|
274
|
+
- `--message` 是用户的原始短剧需求,不能为空。
|
|
275
|
+
- 查询进展时优先使用 `+submit-run` 返回的 `thread_id` 和 `run_id`。
|
|
276
|
+
- `--after-seq` 用于增量拉取消息,首次查询可设置为 `0`。
|
|
277
|
+
- `+upload-file` 当前用于短剧场景文件上传链路,上传后将返回可传给 `+submit-run` 的文件 ID。
|
|
278
|
+
- `+list-thread-file` 只需要 `thread_id`;分页参数默认 `--page-num 1 --page-size 100`。
|
|
279
|
+
- `+list-thread-file` 和 `+download-result` 是两个不同的 CLI 指令:前者获取会话文件元信息,后者下载 URL 资源并写入到本地目录。
|
|
280
|
+
- `+download-result` 接收 `--url`、`--output-dir`、`--workers`。
|