@pippit-dev/cli 0.0.7 → 0.0.9

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/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- b2cba39200b8114a21e496bca83d677fe2b478b23e374666a0a8cab2ad2e457e pippit-cli-0.0.7-darwin-amd64.tar.gz
2
- 44be0c9cb8dc98edec7519a704290c06dcfa966360c48bcad709f1e53d5a80d0 pippit-cli-0.0.7-darwin-arm64.tar.gz
3
- 175816681f3affd9eb20900c6e0cfbc035ec1f24eaf06dc6c41686b6cdea5ec7 pippit-cli-0.0.7-linux-amd64.tar.gz
4
- ebf0c0e9b4b2bbed872acce3f9cf07dd0b21c52ab7c11c62d6778f820c0479fa pippit-cli-0.0.7-linux-arm64.tar.gz
5
- 573c22cdbd208e9a9a541ae4f67cd922e63129995716d7b92e665d828cec8f49 pippit-cli-0.0.7-windows-amd64.zip
6
- 996d18e5ca27083ee8e9b200f39571c53ff8829f7fdc85c8ba7631778e7f3178 pippit-cli-0.0.7-windows-arm64.zip
1
+ 3f9cf3a1fabbc3d178c89c3d8044c18d86110faf7d26ffa469930319d13d68e6 pippit-cli-0.0.9-darwin-amd64.tar.gz
2
+ 5e14f8ce6034564d9646918e49f8f2d7b6178708518b528c3f95a0af67bd3cd5 pippit-cli-0.0.9-darwin-arm64.tar.gz
3
+ 91e90ab17b577b402c7676965b701df7d317b90a2e2b717929bdadce96158a71 pippit-cli-0.0.9-linux-amd64.tar.gz
4
+ dad9fe0a930c91c52036290a121d721e7a100e80700b1b90807988a577a79342 pippit-cli-0.0.9-linux-arm64.tar.gz
5
+ 62af305df2c5d5b6cfaa615f36cb0f014007ed35df8c4ffdf07b527e09993842 pippit-cli-0.0.9-windows-amd64.zip
6
+ c6d40c79e4bf777ad71caba29191a4fc3a25f34f9643157ce08ec270b8d6eb42 pippit-cli-0.0.9-windows-arm64.zip
@@ -164,8 +164,8 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
164
164
  if opts.ThreadID == "" {
165
165
  return fmt.Errorf("--thread-id is required")
166
166
  }
167
- if opts.PageSize <= 0 || opts.PageSize > 1000 {
168
- return fmt.Errorf("--page-size must be between 1 and 1000")
167
+ if opts.PageSize <= 0 || opts.PageSize > common.MaxListThreadFilePageSize {
168
+ return fmt.Errorf("--page-size must be between 1 and %d", common.MaxListThreadFilePageSize)
169
169
  }
170
170
  if opts.PageNum <= 0 {
171
171
  return fmt.Errorf("--page-num must be greater than 0")
@@ -182,7 +182,7 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
182
182
  cmd.SetErr(stderr)
183
183
  cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to list files for")
184
184
  cmd.Flags().IntVar(&opts.PageNum, "page-num", 1, "page number (1-based)")
185
- cmd.Flags().IntVar(&opts.PageSize, "page-size", 1000, "number of files per page (between 1 and 1000)")
185
+ cmd.Flags().IntVar(&opts.PageSize, "page-size", common.MaxListThreadFilePageSize, fmt.Sprintf("number of files per page (between 1 and %d)", common.MaxListThreadFilePageSize))
186
186
  return cmd
187
187
  }
188
188
 
@@ -258,11 +258,12 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
258
258
  assertFileContent(t, outputPath, "existing-data")
259
259
  }
260
260
 
261
- func TestShortDramaDownloadResultSkipsMetaJSON(t *testing.T) {
262
- serverCalled := false
261
+ func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
263
262
  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264
- serverCalled = true
265
- t.Fatal("server should not receive request for meta.json")
263
+ if r.URL.Path != "/meta.json" {
264
+ t.Fatalf("path = %s, want meta.json", r.URL.Path)
265
+ }
266
+ _, _ = w.Write([]byte(`{"ok":true}`))
266
267
  }))
267
268
  defer server.Close()
268
269
 
@@ -280,12 +281,6 @@ func TestShortDramaDownloadResultSkipsMetaJSON(t *testing.T) {
280
281
  if err := root.Execute(); err != nil {
281
282
  t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
282
283
  }
283
- if serverCalled {
284
- t.Fatal("server was called, want meta.json to skip download")
285
- }
286
- if _, err := os.Stat(outputPath); !os.IsNotExist(err) {
287
- t.Fatalf("meta.json stat err = %v, want file to be absent", err)
288
- }
289
284
 
290
285
  got := decodeJSON(t, stdout.Bytes())
291
286
  if got["output_path"] != outputPath {
@@ -294,10 +289,11 @@ func TestShortDramaDownloadResultSkipsMetaJSON(t *testing.T) {
294
289
  if _, ok := got["total"]; ok {
295
290
  t.Fatalf("total should not be returned: %#v", got)
296
291
  }
297
- skipped, ok := got["skipped"].([]any)
298
- if !ok || len(skipped) != 1 || skipped[0] != outputPath {
299
- t.Fatalf("skipped = %#v, want meta.json output path", got["skipped"])
292
+ downloaded, ok := got["downloaded"].([]any)
293
+ if !ok || len(downloaded) != 1 || downloaded[0] != outputPath {
294
+ t.Fatalf("downloaded = %#v, want meta.json output path", got["downloaded"])
300
295
  }
296
+ assertFileContent(t, outputPath, `{"ok":true}`)
301
297
  }
302
298
 
303
299
  func TestShortDramaDownloadResultRequiresOutputPath(t *testing.T) {
@@ -557,7 +553,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
557
553
  if got["total"] != float64(1) {
558
554
  t.Fatalf("total = %v, want 1", got["total"])
559
555
  }
560
- wantMessage := "<system-remind>\n- total is below 1000; continue querying with current --page-num 2\n</system-remind>"
556
+ wantMessage := "<system-remind>\n- total is below 200; continue querying with current --page-num 2\n</system-remind>"
561
557
  if got["message"] != wantMessage {
562
558
  t.Fatalf("message = %v, want current page hint", got["message"])
563
559
  }
@@ -586,7 +582,7 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
586
582
  if !strings.Contains(r.URL.Path, "list_thread_file") {
587
583
  t.Fatalf("path = %s, want list_thread_file path", r.URL.Path)
588
584
  }
589
- _, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"total":1000,"files":[]}}`))
585
+ _, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"total":200,"files":[]}}`))
590
586
  }))
591
587
  defer server.Close()
592
588
 
@@ -596,7 +592,7 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
596
592
  "short-drama", "+list-thread-file",
597
593
  "--thread-id", "thread_123",
598
594
  "--page-num", "2",
599
- "--page-size", "1000",
595
+ "--page-size", "200",
600
596
  })
601
597
 
602
598
  if err := root.Execute(); err != nil {
@@ -604,7 +600,7 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
604
600
  }
605
601
 
606
602
  got := decodeJSON(t, stdout.Bytes())
607
- wantMessage := "<system-remind>\n- total reached 1000; query the next page with --page-num 3\n</system-remind>"
603
+ wantMessage := "<system-remind>\n- total reached 200; query the next page with --page-num 3\n</system-remind>"
608
604
  if got["message"] != wantMessage {
609
605
  t.Fatalf("message = %v, want next page hint", got["message"])
610
606
  }
@@ -624,6 +620,24 @@ func TestShortDramaListThreadFileRequiresThreadID(t *testing.T) {
624
620
  }
625
621
  }
626
622
 
623
+ func TestShortDramaListThreadFileRejectsPageSizeAboveMax(t *testing.T) {
624
+ var stdout, stderr bytes.Buffer
625
+ root := NewRootCommand(&stdout, &stderr)
626
+ root.SetArgs([]string{
627
+ "short-drama", "+list-thread-file",
628
+ "--thread-id", "thread_123",
629
+ "--page-size", "201",
630
+ })
631
+
632
+ err := root.Execute()
633
+ if err == nil {
634
+ t.Fatal("Execute() error = nil, want validation error")
635
+ }
636
+ if !strings.Contains(err.Error(), "--page-size must be between 1 and 200") {
637
+ t.Fatalf("error = %q, want page-size validation", err)
638
+ }
639
+ }
640
+
627
641
  func newTestRootCommand(t *testing.T, stdout, stderr io.Writer, baseURL string) *cobra.Command {
628
642
  t.Helper()
629
643
  return newTestRootCommandWithAccessKey(t, stdout, stderr, baseURL, "test-token")
@@ -36,7 +36,6 @@ type DownloadResultResponse struct {
36
36
  OutputPath string `json:"output_path"`
37
37
  Downloaded []string `json:"downloaded"`
38
38
  AlreadyExist []string `json:"already_exist,omitempty"`
39
- Skipped []string `json:"skipped,omitempty"`
40
39
  Errors []*DownloadResultError `json:"errors,omitempty"`
41
40
  }
42
41
 
@@ -68,12 +67,6 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
68
67
  if outputPath == "" {
69
68
  return nil, fmt.Errorf("output_path is required")
70
69
  }
71
- if shouldSkipDownload(outputPath) {
72
- return &DownloadResultResponse{
73
- OutputPath: outputPath,
74
- Skipped: []string{outputPath},
75
- }, nil
76
- }
77
70
  // check if the output path exists and is a file
78
71
  if info, err := os.Stat(outputPath); err == nil {
79
72
  if info.IsDir() {
@@ -174,10 +167,6 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
174
167
  return result, nil
175
168
  }
176
169
 
177
- func shouldSkipDownload(outputPath string) bool {
178
- return filepath.Base(outputPath) == "meta.json"
179
- }
180
-
181
170
  func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, targetPath string) error {
182
171
  var lastErr error
183
172
  for attempt := 0; attempt <= maxDownloadRetries; attempt++ {
@@ -9,6 +9,8 @@ import (
9
9
  "github.com/Pippit-dev/pippit-cli/internal/config"
10
10
  )
11
11
 
12
+ const MaxListThreadFilePageSize = 200
13
+
12
14
  // ListThreadFileOptions is the command-facing request shape for listing thread files.
13
15
  type ListThreadFileOptions struct {
14
16
  ThreadID string `json:"thread_id"`
@@ -95,8 +97,10 @@ func listThreadFileMessage(total int64, pageNum int) string {
95
97
  pageNum = 1
96
98
  }
97
99
  var message string
98
- if total >= 1000 {
99
- message = fmt.Sprintf("total reached 1000; query the next page with --page-num %d", pageNum+1)
100
+ if total >= MaxListThreadFilePageSize {
101
+ message = fmt.Sprintf("total reached %d; query the next page with --page-num %d", MaxListThreadFilePageSize, pageNum+1)
102
+ } else {
103
+ message = fmt.Sprintf("total is below %d; continue querying with current --page-num %d", MaxListThreadFilePageSize, pageNum)
100
104
  }
101
105
  return fmt.Sprintf("%s\n- %s\n%s", start, message, end)
102
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-cli": "scripts/run.js"
@@ -35,6 +35,7 @@
35
35
  "scripts/platform.js",
36
36
  "scripts/run.js",
37
37
  "scripts/skills.js",
38
+ "scripts/version-check.js",
38
39
  "checksums.txt",
39
40
  "README.md",
40
41
  "LICENSE"
package/scripts/run.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { execFileSync } = require("child_process");
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
+ const { maybeWarnNewVersion } = require("./version-check");
6
7
 
7
8
  const ext = process.platform === "win32" ? ".exe" : "";
8
9
  const bin = path.join(__dirname, "..", "bin", "pippit-cli" + ext);
@@ -39,6 +40,8 @@ if (process.platform === "win32" && fs.existsSync(oldBin)) {
39
40
  if (args[0] === "install") {
40
41
  require("./install-wizard.js");
41
42
  } else {
43
+ maybeWarnNewVersion(args);
44
+
42
45
  if (!fs.existsSync(bin)) {
43
46
  try {
44
47
  execFileSync(process.execPath, [path.join(__dirname, "install.js")], {
@@ -0,0 +1,96 @@
1
+ const fs = require("fs");
2
+ const os = require("os");
3
+ const path = require("path");
4
+ const { runSilent } = require("./platform");
5
+ const { DEFAULT_PKG } = require("./skills");
6
+
7
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
8
+
9
+ function defaultCacheFile() {
10
+ return path.join(os.homedir(), ".pippit-cli", "version-check.json");
11
+ }
12
+
13
+ function currentVersion() {
14
+ return require("../package.json").version.replace(/-.*$/, "");
15
+ }
16
+
17
+ function parseSemver(version) {
18
+ const match = String(version || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
19
+ if (!match) return null;
20
+ return match.slice(1).map(Number);
21
+ }
22
+
23
+ function compareSemver(a, b) {
24
+ const parsedA = parseSemver(a);
25
+ const parsedB = parseSemver(b);
26
+ if (!parsedA || !parsedB) return 0;
27
+ for (let i = 0; i < 3; i++) {
28
+ const diff = parsedA[i] - parsedB[i];
29
+ if (diff !== 0) return diff;
30
+ }
31
+ return 0;
32
+ }
33
+
34
+ function readCache(cacheFile) {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(cacheFile, "utf8"));
37
+ } catch (_) {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function writeCache(cacheFile, data) {
43
+ try {
44
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
45
+ fs.writeFileSync(cacheFile, JSON.stringify(data), "utf8");
46
+ } catch (_) {
47
+ // Version checks must never block normal CLI commands.
48
+ }
49
+ }
50
+
51
+ function fetchLatestVersion(pkg = DEFAULT_PKG) {
52
+ return runSilent("npm", ["view", pkg, "version"], { timeout: 3000 }).toString().trim();
53
+ }
54
+
55
+ function shouldSkip(args, env) {
56
+ const cmd = args[0];
57
+ return (
58
+ env.PIPPIT_CLI_DISABLE_UPDATE_CHECK === "1" ||
59
+ env.CI ||
60
+ cmd === "install" ||
61
+ cmd === "update"
62
+ );
63
+ }
64
+
65
+ function maybeWarnNewVersion(args = [], opts = {}) {
66
+ const env = opts.env || process.env;
67
+ if (shouldSkip(args, env)) return;
68
+
69
+ const now = opts.now || Date.now();
70
+ const cacheFile = opts.cacheFile || defaultCacheFile();
71
+ const cache = readCache(cacheFile);
72
+ const cacheFresh = cache && now - cache.checkedAt < CHECK_INTERVAL_MS;
73
+
74
+ let latest = cacheFresh ? cache.latest : "";
75
+ if (!latest) {
76
+ try {
77
+ latest = (opts.fetchLatestVersion || fetchLatestVersion)(opts.pkg || DEFAULT_PKG);
78
+ writeCache(cacheFile, { latest, checkedAt: now });
79
+ } catch (_) {
80
+ return;
81
+ }
82
+ }
83
+
84
+ const current = opts.currentVersion || currentVersion();
85
+ if (compareSemver(latest, current) <= 0) return;
86
+
87
+ const warn = opts.warn || console.error;
88
+ warn(`[pippit-cli] New version available: ${current} -> ${latest}. Run: pippit-cli update`);
89
+ }
90
+
91
+ module.exports = {
92
+ CHECK_INTERVAL_MS,
93
+ compareSemver,
94
+ maybeWarnNewVersion,
95
+ parseSemver,
96
+ };
@@ -75,7 +75,7 @@ pippit-cli short-drama +upload-file --path /path/to/outline.md
75
75
 
76
76
  ```bash
77
77
  # 获取会话文件列表
78
- pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 1000
78
+ pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 200
79
79
  ```
80
80
 
81
81
  `+list-thread-file` 返回的每个文件对象包含:
@@ -108,7 +108,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
108
108
  2. 立即将 web_thread_link 展示给用户
109
109
  3. 并行发起,二者同等重要:
110
110
  a. pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq SEQUENCE
111
- b. pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num PAGE_NUM --page-size 1000
111
+ b. pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num PAGE_NUM --page-size 200
112
112
  4. 检查 `get-thread` 返回的 messages:
113
113
  - 如果任务仍在进行中:展示过程消息,继续查询
114
114
  - 如果后端 Agent 提出问题:展示问题,等待用户回复
@@ -117,7 +117,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
117
117
  - 将 file_path 作为本地目标文件路径,包含文件名
118
118
  - 有 download_url 的重要资产:加入本轮下载队列
119
119
  - 不判断 file_path 在本地是否已存在,是否跳过由 +download-result 内部处理
120
- - 如果本轮 total 达到 1000:下一轮将 PAGE_NUM 加 1,继续查询新一页文件
120
+ - 如果本轮 total 达到 200:下一轮将 PAGE_NUM 加 1,继续查询新一页文件
121
121
  6. 对重要资产,立即调用 +download-result 并行下载资源:
122
122
  - 使用第 5 步获取的 download_url 作为 --url
123
123
  - 使用第 5 步获取的完整 file_path 作为 --output-path
@@ -150,7 +150,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
150
150
  - **间隔**:每 10 秒查询一次。
151
151
  - **增量拉取**:首次使用 `--after-seq 0`,后续根据已读消息进度调整 `after-seq`。
152
152
  - **并行查询**:每次 `+submit-run` 返回 `thread_id` 后,同时发起 `+get-thread` 和 `+list-thread-file`;二者同等重要,不能只查询会话进展而忽略会话文件。
153
- - **文件分页**:`+list-thread-file` 使用 `--page-size 1000`。如果本轮返回的 `total` 达到 1000,下一轮使用 `--page-num` 加 1 查询新一页结果;如果未达到 1000,保持当前页继续轮询新增产物。
153
+ - **文件分页**:`+list-thread-file` 使用 `--page-size 200`。如果本轮返回的 `total` 达到 200,下一轮使用 `--page-num` 加 1 查询新一页结果;如果未达到 200,保持当前页继续轮询新增产物。
154
154
  - **重要资产识别**:每轮都检查 `+list-thread-file` 返回的文件。剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物都是重要资产。
155
155
  - **文件下载**:解析 `+list-thread-file` 的结果后,对带 `download_url` 的重要资产立即调用 `+download-result` 下载资源;不要在 `list-thread-file` 阶段检查文件是否已存在,存在性检查由下载工具内部处理。
156
156
  - **下载完成标准**:不要把文件元信息展示当成下载完成;必须拿到本地 `file_path`,或明确记录该文件在重试后仍下载失败。
@@ -163,7 +163,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
163
163
  一次短剧任务不能只以 `+get-thread` 返回完成消息作为结束条件。完成前必须同时检查:
164
164
 
165
165
  1. 已处理 `+get-thread` 返回的最新会话进展、用户确认问题和最终消息。
166
- 2. 已用 `--page-size 1000` 调用 `+list-thread-file` 获取会话文件列表;如果本轮 `total` 达到 1000,已在后续轮询中递增 `page-num` 查询新一页。
166
+ 2. 已用 `--page-size 200` 调用 `+list-thread-file` 获取会话文件列表;如果本轮 `total` 达到 200,已在后续轮询中递增 `page-num` 查询新一页。
167
167
  3. 对所有带 `download_url` 的重要资产,已调用 `+download-result` 下载到本地 `file_path`。
168
168
  4. 对查询失败或下载失败的资产,已在后续轮询中主动重试,并在最终回复中列出仍失败的文件或命令。
169
169
 
@@ -224,11 +224,11 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
224
224
  }
225
225
  ],
226
226
  "total": 1,
227
- "message": "<system-remind>\n- total reached 1000; query the next page with --page-num {page-num} + 1\n</system-remind>"
227
+ "message": "<system-remind>\n- total reached 200; query the next page with --page-num {page-num} + 1\n</system-remind>"
228
228
  }
229
229
  ```
230
230
 
231
- 当 `total` 达到 1000 时,`message` 会用 `<system-remind>` 提示下一轮将 `page-num` 加 1 查询新一页。
231
+ 当 `total` 达到 200 时,`message` 会用 `<system-remind>` 提示下一轮将 `page-num` 加 1 查询新一页。
232
232
 
233
233
  **+download-result** 返回:
234
234
 
@@ -251,9 +251,9 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
251
251
  1. 有download_url的重要资产
252
252
  → 记录该file_path和URL
253
253
  → 使用 +download-result 将URL资源下载到该file_path
254
- 2. 本轮total达到1000
254
+ 2. 本轮total达到200
255
255
  → 下一轮page-num加1,继续查询新一页结果
256
- 3. 本轮total未达到1000
256
+ 3. 本轮total未达到200
257
257
  → 后续轮询保持当前页,继续发现新增产物
258
258
  4. list-thread-file或download-result失败
259
259
  → 记录失败参数和错误
@@ -302,6 +302,6 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
302
302
  - 查询进展时优先使用 `+submit-run` 返回的 `thread_id` 和 `run_id`。
303
303
  - `--after-seq` 用于增量拉取消息,首次查询可设置为 `0`。
304
304
  - `+upload-file` 当前用于短剧场景文件上传链路,上传后将返回可传给 `+submit-run` 的文件 ID。
305
- - `+list-thread-file` 只需要 `thread_id`;分页参数使用 `--page-num 1 --page-size 1000` 起步,`total` 达到 1000 时下一轮递增 `page-num`。
305
+ - `+list-thread-file` 只需要 `thread_id`;分页参数使用 `--page-num 1 --page-size 200` 起步,`total` 达到 200 时下一轮递增 `page-num`。
306
306
  - `+list-thread-file` 和 `+download-result` 是两个不同的 CLI 指令:前者获取会话文件元信息,后者下载 URL 资源并写入到本地目标文件路径。
307
307
  - `+download-result` 接收 `--url`、`--output-path`、`--workers`;`--output-path` 必须是包含文件名的目标文件路径。