@pippit-dev/cli 0.0.20 → 0.0.22

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 CHANGED
@@ -198,13 +198,13 @@ pippit-tool-cli --version
198
198
  pippit-tool-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
199
199
  pippit-tool-cli short-drama +upload-file --path ./reference.doc
200
200
  pippit-tool-cli short-drama +get-thread --thread-id thread_123 --run-id run_456
201
- pippit-tool-cli short-drama +download-result --output-path ./thread_123/results/result.mp4 --url URL
201
+ pippit-tool-cli short-drama +download-result --output-path ./thread_123/results/result.mp4 --url URL --updated-at 1779716734
202
202
  ```
203
203
 
204
204
  `+submit-run`: 输出 `thread_id`、`run_id` 和 `web_thread_link`;其中 `--message` 为必填参数。
205
205
  `+get-thread`: 请求中带 `version=v2`,并输出 `readable_text`。
206
206
  `+upload-file`: 输出返回的 `asset_id`。 当前仅支持 `.doc`、`.docx` 和 `.txt` 文件。
207
- `+download-result`: 会把结果 URL 下载到 `--output-path` 指定的文件路径。
207
+ `+download-result`: 会把结果 URL 下载到 `--output-path` 指定的文件路径;传入 `--updated-at` 后,如果本地文件早于该时间戳会覆盖更新,否则跳过。
208
208
 
209
209
  短剧命令的错误日志会追加写入本地每日日志文件:`~/.pippit_tool_cli/logs/yyyy-mm-dd.log`。日志路径会基于当前用户主目录和系统路径分隔符生成,因此可在 macOS、Linux 和 Windows 上使用。
210
210
 
package/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- 200e40ca3a4ef54a76860bcbe807233fc548d5686eea8b4bb5069f4d393e6fd2 pippit-tool-cli-0.0.20-darwin-amd64.tar.gz
2
- 2371e7b72c39b8b590604afe1abdba34e583b69ad787a78913b306de0145401b pippit-tool-cli-0.0.20-darwin-arm64.tar.gz
3
- 1df765184d0f7061c15e2829c7792f7982eeb711da534f3883488efc34b02b5d pippit-tool-cli-0.0.20-linux-amd64.tar.gz
4
- d5e0d1fcd774cc7d05ab3c749fff79e7f3507e8f92c0c5abaed4ef884ff46f20 pippit-tool-cli-0.0.20-linux-arm64.tar.gz
5
- 0ccfa68f9fd289b680756ba664a82d68af6636c1337368946c53fc58d74f1f2a pippit-tool-cli-0.0.20-windows-amd64.zip
6
- 2fac1102a7e0fbd75fae736636a3c2e9a2559b7a1a297530f18fa66adc5e11c7 pippit-tool-cli-0.0.20-windows-arm64.zip
1
+ 2de4679cc459fe3c4d674fb25f00c8df8fa84e98f80a2fd01f5357d07c663c3e pippit-tool-cli-0.0.22-darwin-amd64.tar.gz
2
+ 9a63d12f5f0534994c01f57caba16ec0d31a141afbe342cf8f42f7e954270633 pippit-tool-cli-0.0.22-darwin-arm64.tar.gz
3
+ 61d20c1df72d556b0bd7157c77cdfe3e4c91bc0d85d8924efb0d49c4d6b36ef2 pippit-tool-cli-0.0.22-linux-amd64.tar.gz
4
+ 76407ca98a7f885e06cab745eb09a8e5225bbeaae2226ad4c63c3c403bbdec5d pippit-tool-cli-0.0.22-linux-arm64.tar.gz
5
+ 3db90d5c2b5a6a0ba964dd51424ffbc0eaf6dffb3497b5be226d16534504e8e6 pippit-tool-cli-0.0.22-windows-amd64.zip
6
+ b3a69cb5e43a21c037e30e5f6af53c1806de5f24e0724ddbbcbd005255cd20ce pippit-tool-cli-0.0.22-windows-arm64.zip
@@ -104,11 +104,15 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
104
104
  Short: "Download a generated result URL",
105
105
  Args: cobra.NoArgs,
106
106
  RunE: withErrorLog("short-drama +download-result", func() map[string]string {
107
- return map[string]string{
107
+ fields := map[string]string{
108
108
  "output_path": opts.OutputPath,
109
109
  "has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
110
110
  "workers": strconv.Itoa(opts.Workers),
111
111
  }
112
+ if opts.UpdatedAt > 0 {
113
+ fields["updated_at"] = strconv.FormatInt(opts.UpdatedAt, 10)
114
+ }
115
+ return fields
112
116
  }, func(cmd *cobra.Command, _ []string) error {
113
117
  opts.OutputPath = strings.TrimSpace(opts.OutputPath)
114
118
  if opts.OutputPath == "" {
@@ -133,6 +137,7 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
133
137
  cmd.SetErr(stderr)
134
138
  cmd.Flags().StringVar(&opts.URL, "url", "", "URL to download")
135
139
  cmd.Flags().StringVar(&opts.OutputPath, "output-path", "", "local output file path")
140
+ cmd.Flags().Int64Var(&opts.UpdatedAt, "updated-at", 0, "remote file update time as a Unix timestamp")
136
141
  cmd.Flags().IntVar(&opts.Workers, "workers", 5, "parallel download workers")
137
142
  return cmd
138
143
  }
@@ -408,6 +408,111 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
408
408
  assertFileContent(t, outputPath, "existing-data")
409
409
  }
410
410
 
411
+ func TestShortDramaDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T) {
412
+ serverCalled := false
413
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
414
+ serverCalled = true
415
+ t.Fatal("server should not receive request when target file is fresh")
416
+ }))
417
+ defer server.Close()
418
+
419
+ chdirTemp(t)
420
+ outputPath := filepath.Join("results", "cover.jpeg")
421
+ if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
422
+ t.Fatalf("MkdirAll(): %v", err)
423
+ }
424
+ if err := os.WriteFile(outputPath, []byte("existing-data"), 0o644); err != nil {
425
+ t.Fatalf("WriteFile(): %v", err)
426
+ }
427
+ remoteUpdatedAt := int64(1779716734)
428
+ localUpdatedAt := time.Unix(remoteUpdatedAt+10, 0)
429
+ if err := os.Chtimes(outputPath, localUpdatedAt, localUpdatedAt); err != nil {
430
+ t.Fatalf("Chtimes(): %v", err)
431
+ }
432
+
433
+ var stdout, stderr bytes.Buffer
434
+ root := NewRootCommand(&stdout, &stderr)
435
+ root.SetArgs([]string{
436
+ "short-drama", "+download-result",
437
+ "--output-path", outputPath,
438
+ "--updated-at", "1779716734",
439
+ "--url", server.URL + "/image",
440
+ })
441
+
442
+ if err := root.Execute(); err != nil {
443
+ t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
444
+ }
445
+ if serverCalled {
446
+ t.Fatal("server was called, want fresh existing file to skip download")
447
+ }
448
+
449
+ got := decodeJSON(t, stdout.Bytes())
450
+ alreadyExist, ok := got["already_exist"].([]any)
451
+ if !ok || len(alreadyExist) != 1 || alreadyExist[0] != outputPath {
452
+ t.Fatalf("already_exist = %#v, want existing output path", got["already_exist"])
453
+ }
454
+ assertFileContent(t, outputPath, "existing-data")
455
+ }
456
+
457
+ func TestShortDramaDownloadResultOverwritesStaleExistingFile(t *testing.T) {
458
+ serverCalled := false
459
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
460
+ serverCalled = true
461
+ if r.URL.Path != "/image" {
462
+ t.Fatalf("path = %s, want /image", r.URL.Path)
463
+ }
464
+ _, _ = w.Write([]byte("new-data"))
465
+ }))
466
+ defer server.Close()
467
+
468
+ chdirTemp(t)
469
+ outputPath := filepath.Join("results", "cover.jpeg")
470
+ if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
471
+ t.Fatalf("MkdirAll(): %v", err)
472
+ }
473
+ if err := os.WriteFile(outputPath, []byte("existing-data"), 0o644); err != nil {
474
+ t.Fatalf("WriteFile(): %v", err)
475
+ }
476
+ remoteUpdatedAt := int64(1779716734)
477
+ staleUpdatedAt := time.Unix(remoteUpdatedAt-10, 0)
478
+ if err := os.Chtimes(outputPath, staleUpdatedAt, staleUpdatedAt); err != nil {
479
+ t.Fatalf("Chtimes(): %v", err)
480
+ }
481
+
482
+ var stdout, stderr bytes.Buffer
483
+ root := NewRootCommand(&stdout, &stderr)
484
+ root.SetArgs([]string{
485
+ "short-drama", "+download-result",
486
+ "--output-path", outputPath,
487
+ "--updated-at", "1779716734",
488
+ "--url", server.URL + "/image",
489
+ })
490
+
491
+ if err := root.Execute(); err != nil {
492
+ t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
493
+ }
494
+ if !serverCalled {
495
+ t.Fatal("server was not called, want stale existing file to be downloaded")
496
+ }
497
+
498
+ got := decodeJSON(t, stdout.Bytes())
499
+ downloaded, ok := got["downloaded"].([]any)
500
+ if !ok || len(downloaded) != 1 || downloaded[0] != outputPath {
501
+ t.Fatalf("downloaded = %#v, want output path", got["downloaded"])
502
+ }
503
+ if _, ok := got["overwritten"]; ok {
504
+ t.Fatalf("overwritten should not be returned: %#v", got)
505
+ }
506
+ assertFileContent(t, outputPath, "new-data")
507
+ info, err := os.Stat(outputPath)
508
+ if err != nil {
509
+ t.Fatalf("Stat(%s): %v", outputPath, err)
510
+ }
511
+ if info.ModTime().Unix() != remoteUpdatedAt {
512
+ t.Fatalf("mtime = %d, want %d", info.ModTime().Unix(), remoteUpdatedAt)
513
+ }
514
+ }
515
+
411
516
  func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
412
517
  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
413
518
  if r.URL.Path != "/meta.json" {
@@ -513,6 +618,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
513
618
  defer server.Close()
514
619
 
515
620
  chdirTemp(t)
621
+ clearDailyErrorLog(t)
516
622
  var stdout, stderr bytes.Buffer
517
623
  root := NewRootCommand(&stdout, &stderr)
518
624
  root.SetArgs([]string{
@@ -528,6 +634,17 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
528
634
  if !strings.Contains(err.Error(), "all 1 download(s) failed") {
529
635
  t.Fatalf("error = %q, want all-failed message", err)
530
636
  }
637
+ logs := readDailyErrorLog(t)
638
+ if len(logs) != 1 {
639
+ t.Fatalf("logs = %#v, want one error log", logs)
640
+ }
641
+ fields, ok := logs[0]["fields"].(map[string]any)
642
+ if !ok {
643
+ t.Fatalf("fields = %#v, want object", logs[0]["fields"])
644
+ }
645
+ if _, ok := fields["updated_at"]; ok {
646
+ t.Fatalf("updated_at should be omitted when --updated-at is not provided: %#v", fields)
647
+ }
531
648
  }
532
649
 
533
650
  func TestShortDramaDownloadResultOutputPath(t *testing.T) {
@@ -684,7 +801,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
684
801
  if body["page_size"] != float64(10) {
685
802
  t.Fatalf("page_size = %v, want 10", body["page_size"])
686
803
  }
687
- _, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"total":1,"files":[{"file_name":"ignored.png","file_path":"results/images/cover.png","download_url":"https://example.com/cover.png"}]}}`))
804
+ _, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"total":1,"files":[{"file_name":"ignored.png","file_path":"results/images/cover.png","download_url":"https://example.com/cover.png","updated_at":1779716734}]}}`))
688
805
  }))
689
806
  defer server.Close()
690
807
 
@@ -727,6 +844,9 @@ func TestShortDramaListThreadFile(t *testing.T) {
727
844
  if file["download_url"] != "https://example.com/cover.png" {
728
845
  t.Fatalf("download_url = %v, want returned url", file["download_url"])
729
846
  }
847
+ if file["updated_at"] != float64(1779716734) {
848
+ t.Fatalf("updated_at = %v, want 1779716734", file["updated_at"])
849
+ }
730
850
  }
731
851
 
732
852
  func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
@@ -23,6 +23,7 @@ import (
23
23
  type DownloadResultOptions struct {
24
24
  URL string `json:"url"`
25
25
  OutputPath string `json:"output_path"`
26
+ UpdatedAt int64 `json:"updated_at,omitempty"`
26
27
  Workers int `json:"workers"`
27
28
  }
28
29
 
@@ -45,8 +46,9 @@ const (
45
46
  )
46
47
 
47
48
  type downloadTask struct {
48
- url string
49
- filepath string
49
+ url string
50
+ filepath string
51
+ updatedAt int64
50
52
  }
51
53
 
52
54
  type downloadTaskResult struct {
@@ -67,15 +69,16 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
67
69
  if outputPath == "" {
68
70
  return nil, fmt.Errorf("output_path is required")
69
71
  }
70
- // check if the output path exists and is a file
71
72
  if info, err := os.Stat(outputPath); err == nil {
72
73
  if info.IsDir() {
73
74
  return nil, fmt.Errorf("output path %q is a directory", outputPath)
74
75
  }
75
- return &DownloadResultResponse{
76
- OutputPath: outputPath,
77
- AlreadyExist: []string{outputPath},
78
- }, nil
76
+ if shouldSkipExistingFile(info, opts.UpdatedAt) {
77
+ return &DownloadResultResponse{
78
+ OutputPath: outputPath,
79
+ AlreadyExist: []string{outputPath},
80
+ }, nil
81
+ }
79
82
  } else if !os.IsNotExist(err) {
80
83
  return nil, fmt.Errorf("stat output path: %w", err)
81
84
  }
@@ -93,8 +96,9 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
93
96
  }
94
97
  tasks := []downloadTask{
95
98
  {
96
- url: rawURL,
97
- filepath: outputPath,
99
+ url: rawURL,
100
+ filepath: outputPath,
101
+ updatedAt: opts.UpdatedAt,
98
102
  },
99
103
  }
100
104
 
@@ -117,7 +121,7 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
117
121
  for task := range taskCh {
118
122
  resultCh <- downloadTaskResult{
119
123
  filepath: task.filepath,
120
- err: downloadFileWithRetry(ctx, runner.Client, task.url, task.filepath),
124
+ err: downloadFileWithRetry(ctx, runner.Client, task.url, task.filepath, task.updatedAt),
121
125
  }
122
126
  }
123
127
  }()
@@ -167,7 +171,14 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Run
167
171
  return result, nil
168
172
  }
169
173
 
170
- func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, targetPath string) error {
174
+ func shouldSkipExistingFile(info os.FileInfo, updatedAt int64) bool {
175
+ if updatedAt <= 0 {
176
+ return true
177
+ }
178
+ return info.ModTime().Unix() >= updatedAt
179
+ }
180
+
181
+ func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, targetPath string, updatedAt int64) error {
171
182
  var lastErr error
172
183
  for attempt := 0; attempt <= maxDownloadRetries; attempt++ {
173
184
  if attempt > 0 {
@@ -180,7 +191,7 @@ func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, ta
180
191
  case <-timer.C:
181
192
  }
182
193
  }
183
- err := downloadFile(ctx, client, rawURL, targetPath)
194
+ err := downloadFile(ctx, client, rawURL, targetPath, updatedAt)
184
195
  if err == nil {
185
196
  return nil
186
197
  }
@@ -203,7 +214,7 @@ func isRetryableError(err error) bool {
203
214
  return false
204
215
  }
205
216
 
206
- func downloadFile(ctx context.Context, client Client, rawURL string, targetPath string) error {
217
+ func downloadFile(ctx context.Context, client Client, rawURL string, targetPath string, updatedAt int64) error {
207
218
  var resp *http.Response
208
219
  if err := client.SendRequest(ctx, rawURL, nil, &resp); err != nil {
209
220
  return err
@@ -211,6 +222,12 @@ func downloadFile(ctx context.Context, client Client, rawURL string, targetPath
211
222
  defer resp.Body.Close()
212
223
 
213
224
  tmpPath := targetPath + ".tmp"
225
+ tempActive := true
226
+ defer func() {
227
+ if tempActive {
228
+ _ = os.Remove(tmpPath)
229
+ }
230
+ }()
214
231
  out, err := os.Create(tmpPath)
215
232
  if err != nil {
216
233
  return fmt.Errorf("create temp file: %w", err)
@@ -218,16 +235,18 @@ func downloadFile(ctx context.Context, client Client, rawURL string, targetPath
218
235
  _, copyErr := io.Copy(out, resp.Body)
219
236
  closeErr := out.Close()
220
237
  if copyErr != nil {
221
- _ = os.Remove(tmpPath)
222
238
  return fmt.Errorf("write temp file: %w", copyErr)
223
239
  }
224
240
  if closeErr != nil {
225
- _ = os.Remove(tmpPath)
226
241
  return fmt.Errorf("close temp file: %w", closeErr)
227
242
  }
228
243
  if err := os.Rename(tmpPath, targetPath); err != nil {
229
- _ = os.Remove(tmpPath)
230
244
  return fmt.Errorf("replace target file: %w", err)
231
245
  }
246
+ tempActive = false
247
+ if updatedAt > 0 {
248
+ modTime := time.Unix(updatedAt, 0)
249
+ _ = os.Chtimes(targetPath, time.Now(), modTime)
250
+ }
232
251
  return nil
233
252
  }
@@ -22,6 +22,7 @@ type ListThreadFileOptions struct {
22
22
  type ThreadFile struct {
23
23
  FilePath string `json:"file_path"`
24
24
  DownloadURL string `json:"download_url"`
25
+ UpdatedAt *int64 `json:"updated_at,omitempty"`
25
26
  }
26
27
 
27
28
  // ListThreadFileResult is the JSON envelope printed by `pippit-tool-cli short-drama +list-thread-file`.
@@ -45,6 +46,7 @@ type listThreadFileResponse struct {
45
46
  type threadFileResponse struct {
46
47
  FilePath string `json:"file_path"`
47
48
  DownloadURL string `json:"download_url"`
49
+ UpdatedAt *int64 `json:"updated_at,omitempty"`
48
50
  }
49
51
 
50
52
  func ListThreadFile(ctx context.Context, opts *ListThreadFileOptions, runner *Runner) (*ListThreadFileResult, error) {
@@ -81,6 +83,7 @@ func ListThreadFile(ctx context.Context, opts *ListThreadFileOptions, runner *Ru
81
83
  files = append(files, &ThreadFile{
82
84
  FilePath: threadFilePath(opts.ThreadID, file.FilePath),
83
85
  DownloadURL: file.DownloadURL,
86
+ UpdatedAt: file.UpdatedAt,
84
87
  })
85
88
  }
86
89
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
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": "go test ./... && go vet ./..."
10
+ "test": "node scripts/version-check.test.js && go test ./... && go vet ./..."
11
11
  },
12
12
  "os": [
13
13
  "darwin",
@@ -7,7 +7,7 @@ const { DEFAULT_PKG } = require("./skills");
7
7
  const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
8
8
 
9
9
  function defaultCacheFile() {
10
- return path.join(os.homedir(), ".pippit-cli", "version-check.json");
10
+ return path.join(os.homedir(), ".pippit_tool_cli", "version-check.json");
11
11
  }
12
12
 
13
13
  function currentVersion() {
@@ -91,6 +91,7 @@ function maybeWarnNewVersion(args = [], opts = {}) {
91
91
  module.exports = {
92
92
  CHECK_INTERVAL_MS,
93
93
  compareSemver,
94
+ defaultCacheFile,
94
95
  maybeWarnNewVersion,
95
96
  parseSemver,
96
97
  };
@@ -121,7 +121,8 @@ pippit-tool-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1
121
121
  ```json
122
122
  {
123
123
  "file_path": "./{thread-id}/路径/文件名", // 文件完整路径,包含文件名
124
- "download_url": "https://..." // URL
124
+ "download_url": "https://...", // URL
125
+ "updated_at": 1779716734 // 文件更新时间,Unix 秒级时间戳
125
126
  }
126
127
  ```
127
128
 
@@ -131,10 +132,10 @@ pippit-tool-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1
131
132
 
132
133
  ```bash
133
134
  # 下载文件资源到指定文件路径
134
- pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH
135
+ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH --updated-at UPDATED_AT
135
136
  ```
136
137
 
137
- `FILE_PATH` 必须直接使用 `+list-thread-file` 返回的完整 `file_path`,包含文件名,不要取父目录。`+download-result` 负责把会话产生的文件通过 URL 下载到该目标文件路径;如果目标文件已存在,跳过下载。
138
+ `FILE_PATH` 必须直接使用 `+list-thread-file` 返回的完整 `file_path`,包含文件名,不要取父目录。`UPDATED_AT` 使用同一文件对象返回的 `updated_at`;如果没有 `updated_at`,可省略 `--updated-at`。`+download-result` 负责把会话产生的文件通过 URL 下载到该目标文件路径;如果目标文件已存在且本地修改时间不早于 `updated_at`,跳过下载;如果本地文件早于 `updated_at`,覆盖更新。
138
139
 
139
140
  ## 典型工作流
140
141
 
@@ -151,7 +152,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
151
152
  - 如果任务仍在进行中:展示可读进展,继续查询
152
153
  - 如果后端 Agent 提出问题:从 readable_text 中提取问题并展示,等待用户回复
153
154
  5. 检查 `list-thread-file` 返回的 files:
154
- - 对每个文件取 file_path、download_url
155
+ - 对每个文件取 file_path、download_url、updated_at
155
156
  - 将 file_path 作为本地目标文件路径,包含文件名
156
157
  - 有 download_url 的重要资产:加入本轮下载队列
157
158
  - 不判断 file_path 在本地是否已存在,是否跳过由 +download-result 内部处理
@@ -159,6 +160,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
159
160
  6. 对重要资产,立即调用 +download-result 并行下载资源:
160
161
  - 使用第 5 步获取的 download_url 作为 --url
161
162
  - 使用第 5 步获取的完整 file_path 作为 --output-path
163
+ - 如果第 5 步返回 updated_at,作为 --updated-at 传入
162
164
  - 剧本设计、场景设计、场景图、人物角色设计、人物图、最终视频产物都属于重要资产
163
165
  7. 查询或下载失败时,不要直接放弃;记录失败项,并在后续轮询中主动重试
164
166
  8. 只有会话进展已处理,且已发现的重要资产均已下载或明确重试失败后,才向用户汇总最终结果
@@ -249,7 +251,8 @@ Thread: thread_...
249
251
  "files": [
250
252
  {
251
253
  "file_path": "./{thread-id}/{file_path}/{file_name}",
252
- "download_url": "https://..."
254
+ "download_url": "https://...",
255
+ "updated_at": 1779716734
253
256
  }
254
257
  ],
255
258
  "total": 1,
@@ -274,12 +277,12 @@ Thread: thread_...
274
277
 
275
278
  ### 获取会话文件
276
279
 
277
- 从 `+list-thread-file` 的 `files` 中逐个读取文件元信息:`file_path`、`file_name`、`download_url`。重点识别剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物等重要资产。
280
+ 从 `+list-thread-file` 的 `files` 中逐个读取文件元信息:`file_path`、`file_name`、`download_url`、`updated_at`。重点识别剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物等重要资产。
278
281
 
279
282
  ```
280
283
  1. 有download_url的重要资产
281
- → 记录该file_path和URL
282
- → 使用 +download-result 将URL资源下载到该file_path
284
+ → 记录该file_path、URLupdated_at
285
+ → 使用 +download-result 将URL资源下载到该file_path;有updated_at时传入--updated-at
283
286
  2. 本轮total达到200
284
287
  → 下一轮page-num加1,继续查询新一页结果
285
288
  3. 本轮total未达到200
@@ -293,7 +296,7 @@ Thread: thread_...
293
296
 
294
297
  对带 `download_url` 的重要资产调用下载工具,可并行。重要资产必须主动下载,不要等用户再次要求,也不要在调用下载工具前先检查本地文件是否存在。
295
298
 
296
- 1. 调用 `pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH`。
299
+ 1. 调用 `pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH --updated-at UPDATED_AT`;如果文件对象没有 `updated_at`,省略 `--updated-at`。
297
300
  2. 下载完成后,向用户展示本地文件路径;如果某个文件下载失败,记录失败项并在后续轮询中重试,不阻塞已成功落盘的文件展示。
298
301
 
299
302
  ## 向用户展示内容
@@ -335,4 +338,4 @@ Thread: thread_...
335
338
  - 单次创作会话中(相同 `thread_id`),`+submit-run` 只支持绑定一个剧本文件。不要在同一 `thread_id` 下重复上传并追加第二个剧本 `asset_id`;用户给多个剧本时,先让用户选择一个,或分别开启新的创作会话。
336
339
  - `+list-thread-file` 只需要 `thread_id`;分页参数使用 `--page-num 1 --page-size 200` 起步,`total` 达到 200 时下一轮递增 `page-num`。
337
340
  - `+list-thread-file` 和 `+download-result` 是两个不同的 CLI 指令:前者获取会话文件元信息,后者下载 URL 资源并写入到本地目标文件路径。
338
- - `+download-result` 接收 `--url`、`--output-path`、`--workers`;`--output-path` 必须是包含文件名的目标文件路径。
341
+ - `+download-result` 接收 `--url`、`--output-path`、`--updated-at`、`--workers`;`--output-path` 必须是包含文件名的目标文件路径。