@pippit-dev/cli 1.0.0 → 1.0.2
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 +37 -5
- package/checksums.txt +6 -6
- package/cmd/download_result.go +57 -0
- package/cmd/generate_video/generate_video.go +48 -0
- package/cmd/generate_video/query_result.go +58 -0
- package/cmd/generate_video_test.go +468 -0
- package/cmd/get_thread.go +59 -0
- package/cmd/list_thread_file.go +51 -0
- package/cmd/root.go +30 -1
- package/cmd/short_drama/short_drama.go +12 -133
- package/cmd/short_drama_test.go +76 -49
- package/cmd/update/update.go +8 -8
- package/internal/common/client.go +29 -21
- package/internal/common/download_results.go +21 -15
- package/internal/common/error_log.go +62 -1
- package/internal/common/error_log_test.go +33 -3
- package/internal/common/get_thread.go +29 -15
- package/internal/common/get_thread_test.go +74 -0
- package/internal/common/json.go +18 -0
- package/internal/common/list_thread_file.go +7 -7
- package/internal/common/submit_run.go +29 -0
- package/internal/common/upload_file.go +22 -18
- package/internal/config/config.go +1 -1
- package/internal/generate_video/generate_video.go +199 -0
- package/internal/generate_video/query_result.go +291 -0
- package/internal/generate_video/query_result_test.go +31 -0
- package/internal/short_drama/submit_run.go +8 -28
- package/package.json +1 -1
- package/skills/short-drama/SKILL.md +34 -34
- package/skills/xyq-nest-skill/scripts/download_results.py +3 -1
- package/skills/xyq-nest-skill/scripts/upload_file.py +2 -2
- package/skills/xyq-nest-skill/scripts/xyq_common.py +4 -3
|
@@ -9,7 +9,6 @@ import (
|
|
|
9
9
|
|
|
10
10
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
11
11
|
"github.com/Pippit-dev/pippit-cli/internal/short_drama"
|
|
12
|
-
"github.com/bytedance/sonic"
|
|
13
12
|
"github.com/spf13/cobra"
|
|
14
13
|
)
|
|
15
14
|
|
|
@@ -24,9 +23,6 @@ func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command
|
|
|
24
23
|
cmd.SetErr(stderr)
|
|
25
24
|
cmd.AddCommand(newShortDramaSubmitRunCommand(stdout, stderr, runner))
|
|
26
25
|
cmd.AddCommand(newShortDramaUploadFileCommand(stdout, stderr, runner))
|
|
27
|
-
cmd.AddCommand(newShortDramaDownloadResultCommand(stdout, stderr, runner))
|
|
28
|
-
cmd.AddCommand(newShortDramaGetThreadCommand(stdout, stderr, runner))
|
|
29
|
-
cmd.AddCommand(newShortDramaListThreadFileCommand(stdout, stderr, runner))
|
|
30
26
|
return cmd
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -47,14 +43,14 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
|
|
|
47
43
|
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
48
44
|
|
|
49
45
|
if opts.Message == "" {
|
|
50
|
-
return fmt.Errorf("--message
|
|
46
|
+
return fmt.Errorf("缺少必填参数 --message")
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
result, err := short_drama.SubmitRun(cmd.Context(), &opts, runner)
|
|
54
50
|
if err != nil {
|
|
55
51
|
return err
|
|
56
52
|
}
|
|
57
|
-
return
|
|
53
|
+
return common.WriteJSON(stdout, result)
|
|
58
54
|
}),
|
|
59
55
|
}
|
|
60
56
|
cmd.SetOut(stdout)
|
|
@@ -82,12 +78,15 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
|
|
|
82
78
|
if opts.Path == "" {
|
|
83
79
|
return fmt.Errorf("--path is required")
|
|
84
80
|
}
|
|
81
|
+
if !isShortDramaUploadFile(opts.Path) {
|
|
82
|
+
return fmt.Errorf("短剧上传仅支持上传 .doc、.docx 和 .txt 文件")
|
|
83
|
+
}
|
|
85
84
|
|
|
86
85
|
result, err := common.UploadFile(cmd.Context(), opts, runner)
|
|
87
86
|
if err != nil {
|
|
88
87
|
return err
|
|
89
88
|
}
|
|
90
|
-
return
|
|
89
|
+
return common.WriteJSON(stdout, result)
|
|
91
90
|
}),
|
|
92
91
|
}
|
|
93
92
|
cmd.SetOut(stdout)
|
|
@@ -96,126 +95,6 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
|
|
|
96
95
|
return cmd
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
100
|
-
var opts common.DownloadResultOptions
|
|
101
|
-
|
|
102
|
-
cmd := &cobra.Command{
|
|
103
|
-
Use: "+download-result",
|
|
104
|
-
Short: "Download a generated result URL",
|
|
105
|
-
Args: cobra.NoArgs,
|
|
106
|
-
RunE: withErrorLog("short-drama +download-result", func() map[string]string {
|
|
107
|
-
fields := map[string]string{
|
|
108
|
-
"output_path": opts.OutputPath,
|
|
109
|
-
"has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
|
|
110
|
-
"workers": strconv.Itoa(opts.Workers),
|
|
111
|
-
}
|
|
112
|
-
if opts.UpdatedAt > 0 {
|
|
113
|
-
fields["updated_at"] = strconv.FormatInt(opts.UpdatedAt, 10)
|
|
114
|
-
}
|
|
115
|
-
return fields
|
|
116
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
117
|
-
opts.OutputPath = strings.TrimSpace(opts.OutputPath)
|
|
118
|
-
if opts.OutputPath == "" {
|
|
119
|
-
return fmt.Errorf("--output-path is required")
|
|
120
|
-
}
|
|
121
|
-
opts.URL = strings.TrimSpace(opts.URL)
|
|
122
|
-
if opts.URL == "" {
|
|
123
|
-
return fmt.Errorf("--url is required")
|
|
124
|
-
}
|
|
125
|
-
if opts.Workers <= 0 {
|
|
126
|
-
return fmt.Errorf("--workers must be greater than 0")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
result, err := common.DownloadResult(cmd.Context(), opts, runner)
|
|
130
|
-
if err != nil {
|
|
131
|
-
return err
|
|
132
|
-
}
|
|
133
|
-
return writeJSON(stdout, result)
|
|
134
|
-
}),
|
|
135
|
-
}
|
|
136
|
-
cmd.SetOut(stdout)
|
|
137
|
-
cmd.SetErr(stderr)
|
|
138
|
-
cmd.Flags().StringVar(&opts.URL, "url", "", "URL to download")
|
|
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")
|
|
141
|
-
cmd.Flags().IntVar(&opts.Workers, "workers", 5, "parallel download workers")
|
|
142
|
-
return cmd
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
146
|
-
var opts common.GetThreadOptions
|
|
147
|
-
|
|
148
|
-
cmd := &cobra.Command{
|
|
149
|
-
Use: "+get-thread",
|
|
150
|
-
Short: "Get a short drama thread detail",
|
|
151
|
-
Args: cobra.NoArgs,
|
|
152
|
-
RunE: withErrorLog("short-drama +get-thread", func() map[string]string {
|
|
153
|
-
return map[string]string{
|
|
154
|
-
"thread_id": opts.ThreadID,
|
|
155
|
-
"run_id": opts.RunID,
|
|
156
|
-
}
|
|
157
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
158
|
-
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
159
|
-
if opts.ThreadID == "" {
|
|
160
|
-
return fmt.Errorf("--thread-id is required")
|
|
161
|
-
}
|
|
162
|
-
opts.RunID = strings.TrimSpace(opts.RunID)
|
|
163
|
-
|
|
164
|
-
result, err := common.GetThread(cmd.Context(), &opts, runner)
|
|
165
|
-
if err != nil {
|
|
166
|
-
return err
|
|
167
|
-
}
|
|
168
|
-
_, err = fmt.Fprintln(stdout, result.ReadableText)
|
|
169
|
-
return err
|
|
170
|
-
}),
|
|
171
|
-
}
|
|
172
|
-
cmd.SetOut(stdout)
|
|
173
|
-
cmd.SetErr(stderr)
|
|
174
|
-
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to fetch")
|
|
175
|
-
cmd.Flags().StringVar(&opts.RunID, "run-id", "", "run ID to fetch")
|
|
176
|
-
return cmd
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
180
|
-
var opts common.ListThreadFileOptions
|
|
181
|
-
|
|
182
|
-
cmd := &cobra.Command{
|
|
183
|
-
Use: "+list-thread-file",
|
|
184
|
-
Short: "List files in a short drama thread",
|
|
185
|
-
Args: cobra.NoArgs,
|
|
186
|
-
RunE: withErrorLog("short-drama +list-thread-file", func() map[string]string {
|
|
187
|
-
return map[string]string{
|
|
188
|
-
"thread_id": opts.ThreadID,
|
|
189
|
-
"page_num": strconv.Itoa(opts.PageNum),
|
|
190
|
-
"page_size": strconv.Itoa(opts.PageSize),
|
|
191
|
-
}
|
|
192
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
193
|
-
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
194
|
-
if opts.ThreadID == "" {
|
|
195
|
-
return fmt.Errorf("--thread-id is required")
|
|
196
|
-
}
|
|
197
|
-
if opts.PageSize <= 0 || opts.PageSize > common.MaxListThreadFilePageSize {
|
|
198
|
-
return fmt.Errorf("--page-size must be between 1 and %d", common.MaxListThreadFilePageSize)
|
|
199
|
-
}
|
|
200
|
-
if opts.PageNum <= 0 {
|
|
201
|
-
return fmt.Errorf("--page-num must be greater than 0")
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
result, err := common.ListThreadFile(cmd.Context(), &opts, runner)
|
|
205
|
-
if err != nil {
|
|
206
|
-
return err
|
|
207
|
-
}
|
|
208
|
-
return writeJSON(stdout, result)
|
|
209
|
-
}),
|
|
210
|
-
}
|
|
211
|
-
cmd.SetOut(stdout)
|
|
212
|
-
cmd.SetErr(stderr)
|
|
213
|
-
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to list files for")
|
|
214
|
-
cmd.Flags().IntVar(&opts.PageNum, "page-num", 1, "page number (1-based)")
|
|
215
|
-
cmd.Flags().IntVar(&opts.PageSize, "page-size", common.MaxListThreadFilePageSize, fmt.Sprintf("number of files per page (between 1 and %d)", common.MaxListThreadFilePageSize))
|
|
216
|
-
return cmd
|
|
217
|
-
}
|
|
218
|
-
|
|
219
98
|
func withErrorLog(command string, fields func() map[string]string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
|
|
220
99
|
return func(cmd *cobra.Command, args []string) error {
|
|
221
100
|
err := run(cmd, args)
|
|
@@ -238,11 +117,11 @@ func fileNameForLog(path string) string {
|
|
|
238
117
|
return filepath.Base(path)
|
|
239
118
|
}
|
|
240
119
|
|
|
241
|
-
func
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return
|
|
120
|
+
func isShortDramaUploadFile(path string) bool {
|
|
121
|
+
switch strings.ToLower(filepath.Ext(path)) {
|
|
122
|
+
case ".doc", ".docx", ".txt":
|
|
123
|
+
return true
|
|
124
|
+
default:
|
|
125
|
+
return false
|
|
245
126
|
}
|
|
246
|
-
_, err = fmt.Fprintln(w, string(data))
|
|
247
|
-
return err
|
|
248
127
|
}
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -124,7 +124,12 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
|
|
|
124
124
|
}
|
|
125
125
|
got := stdout.String()
|
|
126
126
|
for _, want := range []string{
|
|
127
|
-
"Pippit CLI
|
|
127
|
+
"Pippit CLI generates videos",
|
|
128
|
+
"generate-video",
|
|
129
|
+
"download-result",
|
|
130
|
+
"get-thread",
|
|
131
|
+
"list-thread-file",
|
|
132
|
+
"query-result",
|
|
128
133
|
"short-drama",
|
|
129
134
|
"update",
|
|
130
135
|
"--version",
|
|
@@ -140,6 +145,28 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
|
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
func TestShortDramaDoesNotIncludeCommonThreadCommands(t *testing.T) {
|
|
149
|
+
var stdout, stderr bytes.Buffer
|
|
150
|
+
root := NewRootCommand(&stdout, &stderr)
|
|
151
|
+
cmd, _, err := root.Find([]string{"short-drama"})
|
|
152
|
+
if err != nil {
|
|
153
|
+
t.Fatalf("Find(short-drama) error = %v", err)
|
|
154
|
+
}
|
|
155
|
+
if cmd.CommandPath() != "pippit-tool-cli short-drama" {
|
|
156
|
+
t.Fatalf("CommandPath() = %q, want short-drama command", cmd.CommandPath())
|
|
157
|
+
}
|
|
158
|
+
removedCommands := map[string]bool{
|
|
159
|
+
"+download-result": true,
|
|
160
|
+
"+get-thread": true,
|
|
161
|
+
"+list-thread-file": true,
|
|
162
|
+
}
|
|
163
|
+
for _, child := range cmd.Commands() {
|
|
164
|
+
if removedCommands[child.Name()] {
|
|
165
|
+
t.Fatalf("short-drama child %s = %#v, want nil", child.Name(), child)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
143
170
|
func TestVersionFlagPrintsVersion(t *testing.T) {
|
|
144
171
|
var stdout, stderr bytes.Buffer
|
|
145
172
|
root := NewRootCommand(&stdout, &stderr)
|
|
@@ -162,7 +189,7 @@ func TestShortDramaSubmitRunRequiresMessage(t *testing.T) {
|
|
|
162
189
|
if err == nil {
|
|
163
190
|
t.Fatal("Execute() error = nil, want validation error")
|
|
164
191
|
}
|
|
165
|
-
if !strings.Contains(err.Error(), "--message
|
|
192
|
+
if !strings.Contains(err.Error(), "缺少必填参数 --message") {
|
|
166
193
|
t.Fatalf("error = %q, want message validation", err)
|
|
167
194
|
}
|
|
168
195
|
}
|
|
@@ -302,12 +329,12 @@ func TestShortDramaUploadFileRejectsUnsupportedFileType(t *testing.T) {
|
|
|
302
329
|
if err == nil {
|
|
303
330
|
t.Fatal("Execute() error = nil, want file type validation error")
|
|
304
331
|
}
|
|
305
|
-
if !strings.Contains(err.Error(), "
|
|
332
|
+
if !strings.Contains(err.Error(), "仅支持上传 .doc、.docx 和 .txt") {
|
|
306
333
|
t.Fatalf("error = %q, want unsupported type validation", err)
|
|
307
334
|
}
|
|
308
335
|
}
|
|
309
336
|
|
|
310
|
-
func
|
|
337
|
+
func TestDownloadResult(t *testing.T) {
|
|
311
338
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
312
339
|
if r.Header.Get("User-Agent") != "Pippit-CLI/1.0" {
|
|
313
340
|
t.Fatalf("User-Agent = %q, want Pippit-CLI/1.0", r.Header.Get("User-Agent"))
|
|
@@ -326,7 +353,7 @@ func TestShortDramaDownloadResult(t *testing.T) {
|
|
|
326
353
|
root := NewRootCommand(&stdout, &stderr)
|
|
327
354
|
outputPath := filepath.Join("results", "cover.jpeg")
|
|
328
355
|
root.SetArgs([]string{
|
|
329
|
-
"
|
|
356
|
+
"download-result",
|
|
330
357
|
"--output-path", outputPath,
|
|
331
358
|
"--workers", "2",
|
|
332
359
|
"--url", server.URL + "/image?filename=ignored.jpeg",
|
|
@@ -358,7 +385,7 @@ func TestShortDramaDownloadResult(t *testing.T) {
|
|
|
358
385
|
assertFileContent(t, wantFiles[0], "image-data")
|
|
359
386
|
}
|
|
360
387
|
|
|
361
|
-
func
|
|
388
|
+
func TestDownloadResultSkipsExistingFile(t *testing.T) {
|
|
362
389
|
serverCalled := false
|
|
363
390
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
364
391
|
serverCalled = true
|
|
@@ -378,7 +405,7 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
|
|
|
378
405
|
var stdout, stderr bytes.Buffer
|
|
379
406
|
root := NewRootCommand(&stdout, &stderr)
|
|
380
407
|
root.SetArgs([]string{
|
|
381
|
-
"
|
|
408
|
+
"download-result",
|
|
382
409
|
"--output-path", outputPath,
|
|
383
410
|
"--url", server.URL + "/image",
|
|
384
411
|
})
|
|
@@ -404,7 +431,7 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
|
|
|
404
431
|
assertFileContent(t, outputPath, "existing-data")
|
|
405
432
|
}
|
|
406
433
|
|
|
407
|
-
func
|
|
434
|
+
func TestDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T) {
|
|
408
435
|
serverCalled := false
|
|
409
436
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
410
437
|
serverCalled = true
|
|
@@ -429,7 +456,7 @@ func TestShortDramaDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T)
|
|
|
429
456
|
var stdout, stderr bytes.Buffer
|
|
430
457
|
root := NewRootCommand(&stdout, &stderr)
|
|
431
458
|
root.SetArgs([]string{
|
|
432
|
-
"
|
|
459
|
+
"download-result",
|
|
433
460
|
"--output-path", outputPath,
|
|
434
461
|
"--updated-at", "1779716734",
|
|
435
462
|
"--url", server.URL + "/image",
|
|
@@ -450,7 +477,7 @@ func TestShortDramaDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T)
|
|
|
450
477
|
assertFileContent(t, outputPath, "existing-data")
|
|
451
478
|
}
|
|
452
479
|
|
|
453
|
-
func
|
|
480
|
+
func TestDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
454
481
|
serverCalled := false
|
|
455
482
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
456
483
|
serverCalled = true
|
|
@@ -478,7 +505,7 @@ func TestShortDramaDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
|
478
505
|
var stdout, stderr bytes.Buffer
|
|
479
506
|
root := NewRootCommand(&stdout, &stderr)
|
|
480
507
|
root.SetArgs([]string{
|
|
481
|
-
"
|
|
508
|
+
"download-result",
|
|
482
509
|
"--output-path", outputPath,
|
|
483
510
|
"--updated-at", "1779716734",
|
|
484
511
|
"--url", server.URL + "/image",
|
|
@@ -509,7 +536,7 @@ func TestShortDramaDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
|
509
536
|
}
|
|
510
537
|
}
|
|
511
538
|
|
|
512
|
-
func
|
|
539
|
+
func TestDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
513
540
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
514
541
|
if r.URL.Path != "/meta.json" {
|
|
515
542
|
t.Fatalf("path = %s, want meta.json", r.URL.Path)
|
|
@@ -524,7 +551,7 @@ func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
|
524
551
|
var stdout, stderr bytes.Buffer
|
|
525
552
|
root := NewRootCommand(&stdout, &stderr)
|
|
526
553
|
root.SetArgs([]string{
|
|
527
|
-
"
|
|
554
|
+
"download-result",
|
|
528
555
|
"--output-path", outputPath,
|
|
529
556
|
"--url", server.URL + "/meta.json",
|
|
530
557
|
})
|
|
@@ -547,10 +574,10 @@ func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
|
547
574
|
assertFileContent(t, outputPath, `{"ok":true}`)
|
|
548
575
|
}
|
|
549
576
|
|
|
550
|
-
func
|
|
577
|
+
func TestDownloadResultRequiresOutputPath(t *testing.T) {
|
|
551
578
|
var stdout, stderr bytes.Buffer
|
|
552
579
|
root := NewRootCommand(&stdout, &stderr)
|
|
553
|
-
root.SetArgs([]string{"
|
|
580
|
+
root.SetArgs([]string{"download-result", "--url", "https://example.com/image.png"})
|
|
554
581
|
|
|
555
582
|
err := root.Execute()
|
|
556
583
|
if err == nil {
|
|
@@ -561,11 +588,11 @@ func TestShortDramaDownloadResultRequiresOutputPath(t *testing.T) {
|
|
|
561
588
|
}
|
|
562
589
|
}
|
|
563
590
|
|
|
564
|
-
func
|
|
591
|
+
func TestDownloadResultRejectsOutputDirFlag(t *testing.T) {
|
|
565
592
|
var stdout, stderr bytes.Buffer
|
|
566
593
|
root := NewRootCommand(&stdout, &stderr)
|
|
567
594
|
root.SetArgs([]string{
|
|
568
|
-
"
|
|
595
|
+
"download-result",
|
|
569
596
|
"--output-dir", filepath.Join("results", "image.png"),
|
|
570
597
|
"--url", "https://example.com/image.png",
|
|
571
598
|
})
|
|
@@ -574,15 +601,15 @@ func TestShortDramaDownloadResultRejectsOutputDirFlag(t *testing.T) {
|
|
|
574
601
|
if err == nil {
|
|
575
602
|
t.Fatal("Execute() error = nil, want unknown flag error")
|
|
576
603
|
}
|
|
577
|
-
if !strings.Contains(err.Error(), "
|
|
604
|
+
if !strings.Contains(err.Error(), "未知参数: --output-dir") {
|
|
578
605
|
t.Fatalf("error = %q, want output-dir rejection", err)
|
|
579
606
|
}
|
|
580
607
|
}
|
|
581
608
|
|
|
582
|
-
func
|
|
609
|
+
func TestDownloadResultRequiresURL(t *testing.T) {
|
|
583
610
|
var stdout, stderr bytes.Buffer
|
|
584
611
|
root := NewRootCommand(&stdout, &stderr)
|
|
585
|
-
root.SetArgs([]string{"
|
|
612
|
+
root.SetArgs([]string{"download-result", "--output-path", filepath.Join("results", "image.png")})
|
|
586
613
|
|
|
587
614
|
err := root.Execute()
|
|
588
615
|
if err == nil {
|
|
@@ -593,21 +620,21 @@ func TestShortDramaDownloadResultRequiresURL(t *testing.T) {
|
|
|
593
620
|
}
|
|
594
621
|
}
|
|
595
622
|
|
|
596
|
-
func
|
|
623
|
+
func TestDownloadResultRejectsInvalidScheme(t *testing.T) {
|
|
597
624
|
var stdout, stderr bytes.Buffer
|
|
598
625
|
root := NewRootCommand(&stdout, &stderr)
|
|
599
|
-
root.SetArgs([]string{"
|
|
626
|
+
root.SetArgs([]string{"download-result", "--output-path", filepath.Join("results", "image.png"), "--url", "file:///etc/passwd"})
|
|
600
627
|
|
|
601
628
|
err := root.Execute()
|
|
602
629
|
if err == nil {
|
|
603
630
|
t.Fatal("Execute() error = nil, want scheme validation error")
|
|
604
631
|
}
|
|
605
|
-
if !strings.Contains(err.Error(), "
|
|
632
|
+
if !strings.Contains(err.Error(), "仅支持 http 和 https") {
|
|
606
633
|
t.Fatalf("error = %q, want scheme validation", err)
|
|
607
634
|
}
|
|
608
635
|
}
|
|
609
636
|
|
|
610
|
-
func
|
|
637
|
+
func TestDownloadResultAllFailed(t *testing.T) {
|
|
611
638
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
612
639
|
w.WriteHeader(http.StatusNotFound)
|
|
613
640
|
}))
|
|
@@ -618,7 +645,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
|
|
|
618
645
|
var stdout, stderr bytes.Buffer
|
|
619
646
|
root := NewRootCommand(&stdout, &stderr)
|
|
620
647
|
root.SetArgs([]string{
|
|
621
|
-
"
|
|
648
|
+
"download-result",
|
|
622
649
|
"--output-path", filepath.Join("results", "missing.png"),
|
|
623
650
|
"--url", server.URL + "/notfound",
|
|
624
651
|
})
|
|
@@ -627,7 +654,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
|
|
|
627
654
|
if err == nil {
|
|
628
655
|
t.Fatal("Execute() error = nil, want all-failed error")
|
|
629
656
|
}
|
|
630
|
-
if !strings.Contains(err.Error(), "
|
|
657
|
+
if !strings.Contains(err.Error(), "全部 1 个下载任务失败") {
|
|
631
658
|
t.Fatalf("error = %q, want all-failed message", err)
|
|
632
659
|
}
|
|
633
660
|
logs := readDailyErrorLog(t)
|
|
@@ -643,7 +670,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
|
|
|
643
670
|
}
|
|
644
671
|
}
|
|
645
672
|
|
|
646
|
-
func
|
|
673
|
+
func TestDownloadResultOutputPath(t *testing.T) {
|
|
647
674
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
648
675
|
_, _ = w.Write([]byte("image-data"))
|
|
649
676
|
}))
|
|
@@ -655,7 +682,7 @@ func TestShortDramaDownloadResultOutputPath(t *testing.T) {
|
|
|
655
682
|
root := NewRootCommand(&stdout, &stderr)
|
|
656
683
|
outputPath := filepath.Join("custom", "nested", "cover.png")
|
|
657
684
|
root.SetArgs([]string{
|
|
658
|
-
"
|
|
685
|
+
"download-result",
|
|
659
686
|
"--output-path", outputPath,
|
|
660
687
|
"--url", server.URL + "/image.png",
|
|
661
688
|
})
|
|
@@ -671,7 +698,7 @@ func TestShortDramaDownloadResultOutputPath(t *testing.T) {
|
|
|
671
698
|
assertFileContent(t, outputPath, "image-data")
|
|
672
699
|
}
|
|
673
700
|
|
|
674
|
-
func
|
|
701
|
+
func TestGetThread(t *testing.T) {
|
|
675
702
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
676
703
|
if r.Method != http.MethodPost {
|
|
677
704
|
t.Fatalf("method = %s, want POST", r.Method)
|
|
@@ -709,7 +736,7 @@ func TestShortDramaGetThread(t *testing.T) {
|
|
|
709
736
|
var stdout, stderr bytes.Buffer
|
|
710
737
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
711
738
|
root.SetArgs([]string{
|
|
712
|
-
"
|
|
739
|
+
"get-thread",
|
|
713
740
|
"--thread-id", "thread_123",
|
|
714
741
|
"--run-id", "run_456",
|
|
715
742
|
})
|
|
@@ -723,12 +750,12 @@ func TestShortDramaGetThread(t *testing.T) {
|
|
|
723
750
|
}
|
|
724
751
|
}
|
|
725
752
|
|
|
726
|
-
func
|
|
753
|
+
func TestGetThreadRequiresThreadID(t *testing.T) {
|
|
727
754
|
clearDailyErrorLog(t)
|
|
728
755
|
|
|
729
756
|
var stdout, stderr bytes.Buffer
|
|
730
757
|
root := NewRootCommand(&stdout, &stderr)
|
|
731
|
-
root.SetArgs([]string{"
|
|
758
|
+
root.SetArgs([]string{"get-thread"})
|
|
732
759
|
|
|
733
760
|
err := root.Execute()
|
|
734
761
|
if err == nil {
|
|
@@ -742,7 +769,7 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
|
742
769
|
if len(entries) != 1 {
|
|
743
770
|
t.Fatalf("log entries = %d, want 1: %#v", len(entries), entries)
|
|
744
771
|
}
|
|
745
|
-
if entries[0]["command"] != "
|
|
772
|
+
if entries[0]["command"] != "get-thread" {
|
|
746
773
|
t.Fatalf("command = %v, want get-thread", entries[0]["command"])
|
|
747
774
|
}
|
|
748
775
|
if entries[0]["error"] != "--thread-id is required" {
|
|
@@ -750,7 +777,7 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
|
750
777
|
}
|
|
751
778
|
}
|
|
752
779
|
|
|
753
|
-
func
|
|
780
|
+
func TestGetThreadRequiresAccessKey(t *testing.T) {
|
|
754
781
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
755
782
|
t.Fatal("server should not receive request without access key")
|
|
756
783
|
}))
|
|
@@ -758,7 +785,7 @@ func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
|
|
|
758
785
|
|
|
759
786
|
var stdout, stderr bytes.Buffer
|
|
760
787
|
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
761
|
-
root.SetArgs([]string{"
|
|
788
|
+
root.SetArgs([]string{"get-thread", "--thread-id", "thread_123"})
|
|
762
789
|
|
|
763
790
|
err := root.Execute()
|
|
764
791
|
if err == nil {
|
|
@@ -767,7 +794,7 @@ func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
|
|
|
767
794
|
assertAccessKeyGuidance(t, err)
|
|
768
795
|
}
|
|
769
796
|
|
|
770
|
-
func
|
|
797
|
+
func TestListThreadFile(t *testing.T) {
|
|
771
798
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
772
799
|
if r.Method != http.MethodPost {
|
|
773
800
|
t.Fatalf("method = %s, want POST", r.Method)
|
|
@@ -802,7 +829,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
|
|
|
802
829
|
var stdout, stderr bytes.Buffer
|
|
803
830
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
804
831
|
root.SetArgs([]string{
|
|
805
|
-
"
|
|
832
|
+
"list-thread-file",
|
|
806
833
|
"--thread-id", "thread_123",
|
|
807
834
|
"--page-num", "2",
|
|
808
835
|
"--page-size", "10",
|
|
@@ -816,7 +843,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
|
|
|
816
843
|
if got["total"] != float64(1) {
|
|
817
844
|
t.Fatalf("total = %v, want 1", got["total"])
|
|
818
845
|
}
|
|
819
|
-
wantMessage := "<system-remind>\n-
|
|
846
|
+
wantMessage := "<system-remind>\n- 文件总数小于 200;继续使用当前 --page-num 2 查询\n</system-remind>"
|
|
820
847
|
if got["message"] != wantMessage {
|
|
821
848
|
t.Fatalf("message = %v, want current page hint", got["message"])
|
|
822
849
|
}
|
|
@@ -843,7 +870,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
|
|
|
843
870
|
}
|
|
844
871
|
}
|
|
845
872
|
|
|
846
|
-
func
|
|
873
|
+
func TestListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
847
874
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
848
875
|
if !strings.Contains(r.URL.Path, "list_thread_file") {
|
|
849
876
|
t.Fatalf("path = %s, want list_thread_file path", r.URL.Path)
|
|
@@ -855,7 +882,7 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
|
855
882
|
var stdout, stderr bytes.Buffer
|
|
856
883
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
857
884
|
root.SetArgs([]string{
|
|
858
|
-
"
|
|
885
|
+
"list-thread-file",
|
|
859
886
|
"--thread-id", "thread_123",
|
|
860
887
|
"--page-num", "2",
|
|
861
888
|
"--page-size", "200",
|
|
@@ -866,16 +893,16 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
|
866
893
|
}
|
|
867
894
|
|
|
868
895
|
got := decodeJSON(t, stdout.Bytes())
|
|
869
|
-
wantMessage := "<system-remind>\n-
|
|
896
|
+
wantMessage := "<system-remind>\n- 文件总数已达到 200;请使用 --page-num 3 查询下一页\n</system-remind>"
|
|
870
897
|
if got["message"] != wantMessage {
|
|
871
898
|
t.Fatalf("message = %v, want next page hint", got["message"])
|
|
872
899
|
}
|
|
873
900
|
}
|
|
874
901
|
|
|
875
|
-
func
|
|
902
|
+
func TestListThreadFileRequiresThreadID(t *testing.T) {
|
|
876
903
|
var stdout, stderr bytes.Buffer
|
|
877
904
|
root := NewRootCommand(&stdout, &stderr)
|
|
878
|
-
root.SetArgs([]string{"
|
|
905
|
+
root.SetArgs([]string{"list-thread-file"})
|
|
879
906
|
|
|
880
907
|
err := root.Execute()
|
|
881
908
|
if err == nil {
|
|
@@ -886,7 +913,7 @@ func TestShortDramaListThreadFileRequiresThreadID(t *testing.T) {
|
|
|
886
913
|
}
|
|
887
914
|
}
|
|
888
915
|
|
|
889
|
-
func
|
|
916
|
+
func TestListThreadFileRequiresAccessKey(t *testing.T) {
|
|
890
917
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
891
918
|
t.Fatal("server should not receive request without access key")
|
|
892
919
|
}))
|
|
@@ -894,7 +921,7 @@ func TestShortDramaListThreadFileRequiresAccessKey(t *testing.T) {
|
|
|
894
921
|
|
|
895
922
|
var stdout, stderr bytes.Buffer
|
|
896
923
|
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
897
|
-
root.SetArgs([]string{"
|
|
924
|
+
root.SetArgs([]string{"list-thread-file", "--thread-id", "thread_123"})
|
|
898
925
|
|
|
899
926
|
err := root.Execute()
|
|
900
927
|
if err == nil {
|
|
@@ -903,11 +930,11 @@ func TestShortDramaListThreadFileRequiresAccessKey(t *testing.T) {
|
|
|
903
930
|
assertAccessKeyGuidance(t, err)
|
|
904
931
|
}
|
|
905
932
|
|
|
906
|
-
func
|
|
933
|
+
func TestListThreadFileRejectsPageSizeAboveMax(t *testing.T) {
|
|
907
934
|
var stdout, stderr bytes.Buffer
|
|
908
935
|
root := NewRootCommand(&stdout, &stderr)
|
|
909
936
|
root.SetArgs([]string{
|
|
910
|
-
"
|
|
937
|
+
"list-thread-file",
|
|
911
938
|
"--thread-id", "thread_123",
|
|
912
939
|
"--page-size", "201",
|
|
913
940
|
})
|
|
@@ -942,13 +969,13 @@ func assertAccessKeyGuidance(t *testing.T, err error) {
|
|
|
942
969
|
t.Fatal("error = nil, want access key guidance")
|
|
943
970
|
}
|
|
944
971
|
msg := err.Error()
|
|
945
|
-
if !strings.Contains(msg, "XYQ_ACCESS_KEY
|
|
972
|
+
if !strings.Contains(msg, "XYQ_ACCESS_KEY 缺失") {
|
|
946
973
|
t.Fatalf("error = %q, want access key guidance", err)
|
|
947
974
|
}
|
|
948
975
|
if !strings.Contains(msg, "https://xyq.jianying.com/home?tab_name=home") {
|
|
949
976
|
t.Fatalf("error = %q, want access key settings URL", err)
|
|
950
977
|
}
|
|
951
|
-
if !strings.Contains(msg, `export XYQ_ACCESS_KEY="<access-key>"`) {
|
|
978
|
+
if !strings.Contains(msg, `export XYQ_ACCESS_KEY="<your-access-key>"`) {
|
|
952
979
|
t.Fatalf("error = %q, want setup command guidance", err)
|
|
953
980
|
}
|
|
954
981
|
}
|
package/cmd/update/update.go
CHANGED
|
@@ -60,21 +60,21 @@ func runUpdate(stdout, stderr io.Writer) error {
|
|
|
60
60
|
fmt.Fprintf(stderr, "Updating pippit-tool-cli via npm: %s\n", pkg)
|
|
61
61
|
restore, err := prepareSelfReplace()
|
|
62
62
|
if err != nil {
|
|
63
|
-
return fmt.Errorf("
|
|
63
|
+
return fmt.Errorf("准备替换当前可执行文件失败: %w", err)
|
|
64
64
|
}
|
|
65
65
|
if err := runInheritEnv(stderr, []string{"PIPPIT_CLI_SKIP_SKILLS=1"}, "npm", "install", "-g", pkg); err != nil {
|
|
66
66
|
restore()
|
|
67
|
-
return fmt.Errorf("
|
|
67
|
+
return fmt.Errorf("更新 pippit-tool-cli 失败: %w", err)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
root, err := globalPackageRoot(defaultPackage)
|
|
71
71
|
if err != nil {
|
|
72
|
-
return fmt.Errorf("
|
|
72
|
+
return fmt.Errorf("定位全局 npm 包失败: %w", err)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
fmt.Fprintln(stderr, "Updating pippit-tool-cli skills...")
|
|
76
76
|
if err := installSkills(root, stderr); err != nil {
|
|
77
|
-
return fmt.Errorf("
|
|
77
|
+
return fmt.Errorf("更新 pippit-tool-cli skills 失败: %w", err)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
reportBundledSkillTelemetry("update", "cli_update", stderr)
|
|
@@ -89,7 +89,7 @@ func globalPackageRoot(pkg string) (string, error) {
|
|
|
89
89
|
}
|
|
90
90
|
npmRoot := strings.TrimSpace(string(out))
|
|
91
91
|
if npmRoot == "" {
|
|
92
|
-
return "", fmt.Errorf("npm root -g
|
|
92
|
+
return "", fmt.Errorf("npm root -g 输出为空")
|
|
93
93
|
}
|
|
94
94
|
return filepath.Join(append([]string{npmRoot}, strings.Split(pkg, "/")...)...), nil
|
|
95
95
|
}
|
|
@@ -98,7 +98,7 @@ func installSkills(root string, stderr io.Writer) error {
|
|
|
98
98
|
if info, err := os.Stat(filepath.Join(root, "skills")); err != nil {
|
|
99
99
|
return err
|
|
100
100
|
} else if !info.IsDir() {
|
|
101
|
-
return fmt.Errorf("%s
|
|
101
|
+
return fmt.Errorf("%s 不是目录", filepath.Join(root, "skills"))
|
|
102
102
|
}
|
|
103
103
|
if err := runInherit(stderr, "npx", "-y", "skills", "add", root, "-g", "-y", "--skill", "*"); err != nil {
|
|
104
104
|
return err
|
|
@@ -153,7 +153,7 @@ func reportBundledSkillTelemetry(event string, source string, stderr io.Writer)
|
|
|
153
153
|
go func(payload telemetryPayload) {
|
|
154
154
|
defer wg.Done()
|
|
155
155
|
if err := reportSkillTelemetry(payload); err != nil && os.Getenv("PIPPIT_CLI_DEBUG_TELEMETRY") == "1" {
|
|
156
|
-
fmt.Fprintf(stderr, "[pippit-tool-cli]
|
|
156
|
+
fmt.Fprintf(stderr, "[pippit-tool-cli] 埋点上报失败: %v\n", err)
|
|
157
157
|
}
|
|
158
158
|
}(payload)
|
|
159
159
|
}
|
|
@@ -188,7 +188,7 @@ func reportSkillTelemetry(payload telemetryPayload) error {
|
|
|
188
188
|
defer resp.Body.Close()
|
|
189
189
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
190
190
|
if resp.StatusCode >= 400 {
|
|
191
|
-
return fmt.Errorf("
|
|
191
|
+
return fmt.Errorf("埋点请求返回 HTTP %d", resp.StatusCode)
|
|
192
192
|
}
|
|
193
193
|
return nil
|
|
194
194
|
}
|