@pippit-dev/cli 0.0.16 → 0.0.18

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
@@ -37,10 +37,15 @@ go run . short-drama +download-result --output-path ./thread_123/results/result.
37
37
  `/api/biz/v1/skill/get_thread` with `version=v2` and prints `readable_text`.
38
38
  `+upload-file` calls `/api/biz/v1/skill/upload_file` with
39
39
  `multipart/form-data` and prints the returned `asset_id`.
40
- Only `.doc` and `.txt` file extensions are supported.
40
+ Only `.doc`, `.docx`, and `.txt` file extensions are supported.
41
41
  `+download-result` downloads the result URL to
42
42
  the `--output-path` file path.
43
43
 
44
+ Short drama command errors are appended to a daily local log file under
45
+ `~/.pippit_tool_cli/logs/yyyy-mm-dd.log`. The path is built with the current
46
+ user home directory and the platform path separator, so it works on macOS,
47
+ Linux, and Windows.
48
+
44
49
  ## HTTP Client
45
50
 
46
51
  Command modules should receive `common.Runner` for service calls. Runtime
package/checksums.txt CHANGED
@@ -1,6 +1,6 @@
1
- a00ec8d732b4be5994aecb2863d9841cd1afd9008e83122667aad17df84dc0b3 pippit-tool-cli-0.0.16-darwin-amd64.tar.gz
2
- 58ddfc2b146ae34aa5f003664c4f123708d100dd90f61b262201e9c80b011fc6 pippit-tool-cli-0.0.16-darwin-arm64.tar.gz
3
- ad35f1d3fe7e811e9775d8cd6b2e7a8de649f19c196958c77b54926ae502a288 pippit-tool-cli-0.0.16-linux-amd64.tar.gz
4
- fda684084fb7b1c2e74ac7c9ffa2a8dec2e35b3efd8326c59ab2599363caaa17 pippit-tool-cli-0.0.16-linux-arm64.tar.gz
5
- d38681622dcfdd8d318efef6dcb1becebdef355b962c417824b8ce0699565a8d pippit-tool-cli-0.0.16-windows-amd64.zip
6
- 6a62da1a7a6482040c929f1a4f886188a966ae44d645bad18d89eb49f77aa800 pippit-tool-cli-0.0.16-windows-arm64.zip
1
+ 2cd3bb5e214d85004196806643c3b7d0a8ceb141ef3df58d1bbfc7aa9642f6ab pippit-tool-cli-0.0.18-darwin-amd64.tar.gz
2
+ 8fb57bef9d7e81b8bcc3421f2179879b656134c23387104e01a1090022f6e7c9 pippit-tool-cli-0.0.18-darwin-arm64.tar.gz
3
+ bdde278cbee36f2b90602b1b58f7be05790a186b3adbc90b8dd8fd970b48b20c pippit-tool-cli-0.0.18-linux-amd64.tar.gz
4
+ 878c6b0cd41a2cafdd62f99be98968eee05f0d16de0c6b173f84bb0328c1bef6 pippit-tool-cli-0.0.18-linux-arm64.tar.gz
5
+ e4423154ba1a52625f255e11060459844a4c5bcf3aab21af682f3e1adcce82cd pippit-tool-cli-0.0.18-windows-amd64.zip
6
+ cca47eab19bdc7200aabcea2d59a535c63267fe293f993b1013aa049a4be16b4 pippit-tool-cli-0.0.18-windows-arm64.zip
@@ -3,6 +3,8 @@ package short_drama
3
3
  import (
4
4
  "fmt"
5
5
  "io"
6
+ "path/filepath"
7
+ "strconv"
6
8
  "strings"
7
9
 
8
10
  "github.com/Pippit-dev/pippit-cli/internal/common"
@@ -35,7 +37,12 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
35
37
  Use: "+submit-run",
36
38
  Short: "Submit a Run task for the short drama scene",
37
39
  Args: cobra.NoArgs,
38
- RunE: func(cmd *cobra.Command, _ []string) error {
40
+ RunE: withErrorLog("short-drama +submit-run", func() map[string]string {
41
+ return map[string]string{
42
+ "thread_id": opts.ThreadID,
43
+ "asset_count": strconv.Itoa(len(opts.AssetIDs)),
44
+ }
45
+ }, func(cmd *cobra.Command, _ []string) error {
39
46
  opts.Message = strings.TrimSpace(opts.Message)
40
47
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
41
48
 
@@ -48,7 +55,7 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
48
55
  return err
49
56
  }
50
57
  return writeJSON(stdout, result)
51
- },
58
+ }),
52
59
  }
53
60
  cmd.SetOut(stdout)
54
61
  cmd.SetErr(stderr)
@@ -65,7 +72,11 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
65
72
  Use: "+upload-file",
66
73
  Short: "Upload a file for the short drama scene",
67
74
  Args: cobra.NoArgs,
68
- RunE: func(cmd *cobra.Command, _ []string) error {
75
+ RunE: withErrorLog("short-drama +upload-file", func() map[string]string {
76
+ return map[string]string{
77
+ "file_name": fileNameForLog(opts.Path),
78
+ }
79
+ }, func(cmd *cobra.Command, _ []string) error {
69
80
  opts.Path = strings.TrimSpace(opts.Path)
70
81
 
71
82
  if opts.Path == "" {
@@ -77,7 +88,7 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
77
88
  return err
78
89
  }
79
90
  return writeJSON(stdout, result)
80
- },
91
+ }),
81
92
  }
82
93
  cmd.SetOut(stdout)
83
94
  cmd.SetErr(stderr)
@@ -92,7 +103,13 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
92
103
  Use: "+download-result",
93
104
  Short: "Download a generated result URL",
94
105
  Args: cobra.NoArgs,
95
- RunE: func(cmd *cobra.Command, _ []string) error {
106
+ RunE: withErrorLog("short-drama +download-result", func() map[string]string {
107
+ return 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
+ }, func(cmd *cobra.Command, _ []string) error {
96
113
  opts.OutputPath = strings.TrimSpace(opts.OutputPath)
97
114
  if opts.OutputPath == "" {
98
115
  return fmt.Errorf("--output-path is required")
@@ -110,7 +127,7 @@ func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common
110
127
  return err
111
128
  }
112
129
  return writeJSON(stdout, result)
113
- },
130
+ }),
114
131
  }
115
132
  cmd.SetOut(stdout)
116
133
  cmd.SetErr(stderr)
@@ -127,7 +144,12 @@ func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runn
127
144
  Use: "+get-thread",
128
145
  Short: "Get a short drama thread detail",
129
146
  Args: cobra.NoArgs,
130
- RunE: func(cmd *cobra.Command, _ []string) error {
147
+ RunE: withErrorLog("short-drama +get-thread", func() map[string]string {
148
+ return map[string]string{
149
+ "thread_id": opts.ThreadID,
150
+ "run_id": opts.RunID,
151
+ }
152
+ }, func(cmd *cobra.Command, _ []string) error {
131
153
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
132
154
  if opts.ThreadID == "" {
133
155
  return fmt.Errorf("--thread-id is required")
@@ -138,8 +160,9 @@ func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runn
138
160
  if err != nil {
139
161
  return err
140
162
  }
141
- return writeJSON(stdout, result)
142
- },
163
+ _, err = fmt.Fprintln(stdout, result.ReadableText)
164
+ return err
165
+ }),
143
166
  }
144
167
  cmd.SetOut(stdout)
145
168
  cmd.SetErr(stderr)
@@ -155,7 +178,13 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
155
178
  Use: "+list-thread-file",
156
179
  Short: "List files in a short drama thread",
157
180
  Args: cobra.NoArgs,
158
- RunE: func(cmd *cobra.Command, _ []string) error {
181
+ RunE: withErrorLog("short-drama +list-thread-file", func() map[string]string {
182
+ return map[string]string{
183
+ "thread_id": opts.ThreadID,
184
+ "page_num": strconv.Itoa(opts.PageNum),
185
+ "page_size": strconv.Itoa(opts.PageSize),
186
+ }
187
+ }, func(cmd *cobra.Command, _ []string) error {
159
188
  opts.ThreadID = strings.TrimSpace(opts.ThreadID)
160
189
  if opts.ThreadID == "" {
161
190
  return fmt.Errorf("--thread-id is required")
@@ -172,7 +201,7 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
172
201
  return err
173
202
  }
174
203
  return writeJSON(stdout, result)
175
- },
204
+ }),
176
205
  }
177
206
  cmd.SetOut(stdout)
178
207
  cmd.SetErr(stderr)
@@ -182,6 +211,28 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
182
211
  return cmd
183
212
  }
184
213
 
214
+ func withErrorLog(command string, fields func() map[string]string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
215
+ return func(cmd *cobra.Command, args []string) error {
216
+ err := run(cmd, args)
217
+ if err != nil {
218
+ logFields := map[string]string(nil)
219
+ if fields != nil {
220
+ logFields = fields()
221
+ }
222
+ _ = common.AppendDailyErrorLog(command, err, logFields)
223
+ }
224
+ return err
225
+ }
226
+ }
227
+
228
+ func fileNameForLog(path string) string {
229
+ path = strings.TrimSpace(path)
230
+ if path == "" {
231
+ return ""
232
+ }
233
+ return filepath.Base(path)
234
+ }
235
+
185
236
  func writeJSON(w io.Writer, v any) error {
186
237
  data, err := sonic.Marshal(v)
187
238
  if err != nil {
@@ -9,6 +9,7 @@ import (
9
9
  "path/filepath"
10
10
  "strings"
11
11
  "testing"
12
+ "time"
12
13
 
13
14
  "github.com/Pippit-dev/pippit-cli/internal/common"
14
15
  "github.com/Pippit-dev/pippit-cli/internal/config"
@@ -17,6 +18,19 @@ import (
17
18
  "github.com/spf13/cobra"
18
19
  )
19
20
 
21
+ func TestMain(m *testing.M) {
22
+ home, err := os.MkdirTemp("", "pippit-cli-test-home")
23
+ if err != nil {
24
+ panic(err)
25
+ }
26
+ _ = os.Setenv("HOME", home)
27
+ _ = os.Setenv("USERPROFILE", home)
28
+
29
+ code := m.Run()
30
+ _ = os.RemoveAll(home)
31
+ os.Exit(code)
32
+ }
33
+
20
34
  func TestShortDramaSubmitRun(t *testing.T) {
21
35
  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
36
  if r.Method != http.MethodPost {
@@ -196,11 +210,11 @@ func TestShortDramaUploadFile(t *testing.T) {
196
210
  if len(files) != 1 {
197
211
  t.Fatalf("file parts = %d, want 1", len(files))
198
212
  }
199
- if files[0].Filename != "story.txt" {
200
- t.Fatalf("filename = %q, want story.txt", files[0].Filename)
213
+ if files[0].Filename != "story.docx" {
214
+ t.Fatalf("filename = %q, want story.docx", files[0].Filename)
201
215
  }
202
- if got := files[0].Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
203
- t.Fatalf("Content-Type = %q, want text/plain; charset=utf-8", got)
216
+ if got := files[0].Header.Get("Content-Type"); got != "application/vnd.openxmlformats-officedocument.wordprocessingml.document" {
217
+ t.Fatalf("Content-Type = %q, want docx content type", got)
204
218
  }
205
219
  file, err := files[0].Open()
206
220
  if err != nil {
@@ -211,16 +225,16 @@ func TestShortDramaUploadFile(t *testing.T) {
211
225
  if err != nil {
212
226
  t.Fatalf("ReadAll multipart file: %v", err)
213
227
  }
214
- if string(data) != "txt-data" {
215
- t.Fatalf("file content = %q, want txt-data", string(data))
228
+ if string(data) != "docx-data" {
229
+ t.Fatalf("file content = %q, want docx-data", string(data))
216
230
  }
217
231
  _, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"pippit_asset_id":"asset_123"}}`))
218
232
  }))
219
233
  defer server.Close()
220
234
 
221
235
  cwd := chdirTemp(t)
222
- path := filepath.Join(cwd, "story.txt")
223
- if err := os.WriteFile(path, []byte("txt-data"), 0o644); err != nil {
236
+ path := filepath.Join(cwd, "story.docx")
237
+ if err := os.WriteFile(path, []byte("docx-data"), 0o644); err != nil {
224
238
  t.Fatalf("WriteFile(): %v", err)
225
239
  }
226
240
 
@@ -292,7 +306,7 @@ func TestShortDramaUploadFileRejectsUnsupportedFileType(t *testing.T) {
292
306
  if err == nil {
293
307
  t.Fatal("Execute() error = nil, want file type validation error")
294
308
  }
295
- if !strings.Contains(err.Error(), "only .doc and .txt uploads are supported") {
309
+ if !strings.Contains(err.Error(), "only .doc, .docx, and .txt uploads are supported") {
296
310
  t.Fatalf("error = %q, want unsupported type validation", err)
297
311
  }
298
312
  }
@@ -591,13 +605,14 @@ func TestShortDramaGetThread(t *testing.T) {
591
605
  t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
592
606
  }
593
607
 
594
- got := decodeJSON(t, stdout.Bytes())
595
- if got["readable_text"] != "Thread: thread_123\n [assistant] hello" {
596
- t.Fatalf("readable_text = %#v, want API readable text", got["readable_text"])
608
+ if got := stdout.String(); got != "Thread: thread_123\n [assistant] hello\n" {
609
+ t.Fatalf("stdout = %#v, want API readable text", got)
597
610
  }
598
611
  }
599
612
 
600
613
  func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
614
+ clearDailyErrorLog(t)
615
+
601
616
  var stdout, stderr bytes.Buffer
602
617
  root := NewRootCommand(&stdout, &stderr)
603
618
  root.SetArgs([]string{"short-drama", "+get-thread"})
@@ -609,6 +624,17 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
609
624
  if !strings.Contains(err.Error(), "--thread-id is required") {
610
625
  t.Fatalf("error = %q, want thread-id validation", err)
611
626
  }
627
+
628
+ entries := readDailyErrorLog(t)
629
+ if len(entries) != 1 {
630
+ t.Fatalf("log entries = %d, want 1: %#v", len(entries), entries)
631
+ }
632
+ if entries[0]["command"] != "short-drama +get-thread" {
633
+ t.Fatalf("command = %v, want get-thread", entries[0]["command"])
634
+ }
635
+ if entries[0]["error"] != "--thread-id is required" {
636
+ t.Fatalf("error = %v, want thread-id validation", entries[0]["error"])
637
+ }
612
638
  }
613
639
 
614
640
  func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
@@ -788,6 +814,41 @@ func decodeJSON(t *testing.T, data []byte) map[string]any {
788
814
  return got
789
815
  }
790
816
 
817
+ func clearDailyErrorLog(t *testing.T) {
818
+ t.Helper()
819
+ if err := os.RemoveAll(filepath.Join(testHomeDir(t), ".pippit_tool_cli", "logs")); err != nil {
820
+ t.Fatalf("RemoveAll logs: %v", err)
821
+ }
822
+ }
823
+
824
+ func readDailyErrorLog(t *testing.T) []map[string]any {
825
+ t.Helper()
826
+ path := filepath.Join(testHomeDir(t), ".pippit_tool_cli", "logs", time.Now().Format("2006-01-02")+".log")
827
+ data, err := os.ReadFile(path)
828
+ if err != nil {
829
+ t.Fatalf("ReadFile(%s): %v", path, err)
830
+ }
831
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
832
+ entries := make([]map[string]any, 0, len(lines))
833
+ for _, line := range lines {
834
+ var entry map[string]any
835
+ if err := sonic.Unmarshal([]byte(line), &entry); err != nil {
836
+ t.Fatalf("decode log line: %v\n%s", err, line)
837
+ }
838
+ entries = append(entries, entry)
839
+ }
840
+ return entries
841
+ }
842
+
843
+ func testHomeDir(t *testing.T) string {
844
+ t.Helper()
845
+ home, err := os.UserHomeDir()
846
+ if err != nil {
847
+ t.Fatalf("UserHomeDir(): %v", err)
848
+ }
849
+ return home
850
+ }
851
+
791
852
  func chdirTemp(t *testing.T) string {
792
853
  t.Helper()
793
854
  cwd := t.TempDir()
@@ -0,0 +1,91 @@
1
+ package common
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "time"
8
+
9
+ "github.com/bytedance/sonic"
10
+ )
11
+
12
+ const (
13
+ errorLogDirName = ".pippit_tool_cli"
14
+ errorLogSubdir = "logs"
15
+ )
16
+
17
+ type errorLogEntry struct {
18
+ Time string `json:"time"`
19
+ Command string `json:"command"`
20
+ Fields map[string]string `json:"fields,omitempty"`
21
+ Error string `json:"error"`
22
+ }
23
+
24
+ // AppendDailyErrorLog appends one CLI error record to ~/.pippit_tool_cli/logs/yyyy-mm-dd.log.
25
+ func AppendDailyErrorLog(command string, err error, fields map[string]string) error {
26
+ if err == nil {
27
+ return nil
28
+ }
29
+ now := time.Now()
30
+ path, pathErr := dailyErrorLogPath(now)
31
+ if pathErr != nil {
32
+ return pathErr
33
+ }
34
+ if mkErr := os.MkdirAll(filepath.Dir(path), 0o700); mkErr != nil {
35
+ return mkErr
36
+ }
37
+
38
+ entry := errorLogEntry{
39
+ Time: now.Format(time.RFC3339),
40
+ Command: strings.TrimSpace(command),
41
+ Fields: cleanErrorLogFields(fields),
42
+ Error: err.Error(),
43
+ }
44
+ data, marshalErr := sonic.Marshal(entry)
45
+ if marshalErr != nil {
46
+ return marshalErr
47
+ }
48
+ data = append(data, '\n')
49
+ file, openErr := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
50
+ if openErr != nil {
51
+ return openErr
52
+ }
53
+ defer file.Close()
54
+ _, writeErr := file.Write(data)
55
+ return writeErr
56
+ }
57
+
58
+ func dailyErrorLogPath(now time.Time) (string, error) {
59
+ home, err := os.UserHomeDir()
60
+ if err != nil {
61
+ return "", err
62
+ }
63
+ return filepath.Join(home, errorLogDirName, errorLogSubdir, now.Format("2006-01-02")+".log"), nil
64
+ }
65
+
66
+ func cleanErrorLogFields(fields map[string]string) map[string]string {
67
+ if len(fields) == 0 {
68
+ return nil
69
+ }
70
+ cleaned := make(map[string]string, len(fields))
71
+ for k, v := range fields {
72
+ key := strings.TrimSpace(k)
73
+ value := strings.TrimSpace(v)
74
+ if key == "" || value == "" || isSensitiveLogField(key) {
75
+ continue
76
+ }
77
+ cleaned[key] = value
78
+ }
79
+ if len(cleaned) == 0 {
80
+ return nil
81
+ }
82
+ return cleaned
83
+ }
84
+
85
+ func isSensitiveLogField(key string) bool {
86
+ key = strings.ToLower(key)
87
+ return strings.Contains(key, "access") ||
88
+ strings.Contains(key, "authorization") ||
89
+ strings.Contains(key, "secret") ||
90
+ strings.Contains(key, "token")
91
+ }
@@ -0,0 +1,61 @@
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/bytedance/sonic"
12
+ )
13
+
14
+ func TestAppendDailyErrorLog(t *testing.T) {
15
+ home := t.TempDir()
16
+ t.Setenv("HOME", home)
17
+ t.Setenv("USERPROFILE", home)
18
+
19
+ err := AppendDailyErrorLog("short-drama +get-thread", fmt.Errorf("request failed"), map[string]string{
20
+ "thread_id": "thread_123",
21
+ "access_key": "secret",
22
+ "empty": "",
23
+ })
24
+ if err != nil {
25
+ t.Fatalf("AppendDailyErrorLog(): %v", err)
26
+ }
27
+ if err := AppendDailyErrorLog("short-drama +get-thread", fmt.Errorf("second failure"), nil); err != nil {
28
+ t.Fatalf("AppendDailyErrorLog() second call: %v", err)
29
+ }
30
+
31
+ path := filepath.Join(home, ".pippit_tool_cli", "logs", time.Now().Format("2006-01-02")+".log")
32
+ data, err := os.ReadFile(path)
33
+ if err != nil {
34
+ t.Fatalf("ReadFile(%s): %v", path, err)
35
+ }
36
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
37
+ if len(lines) != 2 {
38
+ t.Fatalf("log lines = %d, want 2\n%s", len(lines), string(data))
39
+ }
40
+
41
+ var first map[string]any
42
+ if err := sonic.Unmarshal([]byte(lines[0]), &first); err != nil {
43
+ t.Fatalf("decode first log line: %v\n%s", err, lines[0])
44
+ }
45
+ if first["command"] != "short-drama +get-thread" {
46
+ t.Fatalf("command = %v, want get-thread command", first["command"])
47
+ }
48
+ if first["error"] != "request failed" {
49
+ t.Fatalf("error = %v, want request failed", first["error"])
50
+ }
51
+ fields, ok := first["fields"].(map[string]any)
52
+ if !ok {
53
+ t.Fatalf("fields = %#v, want object", first["fields"])
54
+ }
55
+ if fields["thread_id"] != "thread_123" {
56
+ t.Fatalf("thread_id = %v, want thread_123", fields["thread_id"])
57
+ }
58
+ if _, ok := fields["access_key"]; ok {
59
+ t.Fatalf("access_key should be omitted from log fields: %#v", fields)
60
+ }
61
+ }
@@ -36,8 +36,9 @@ type uploadFileResponse struct {
36
36
  const uploadFileFieldName = "file"
37
37
 
38
38
  var allowedUploadExtensions = map[string]bool{
39
- ".doc": true,
40
- ".txt": true,
39
+ ".doc": true,
40
+ ".docx": true,
41
+ ".txt": true,
41
42
  }
42
43
 
43
44
  func UploadFile(ctx context.Context, opts UploadFileOptions, runner *Runner) (*UploadFileResult, error) {
@@ -62,7 +63,7 @@ func UploadFile(ctx context.Context, opts UploadFileOptions, runner *Runner) (*U
62
63
 
63
64
  ext := strings.ToLower(filepath.Ext(path))
64
65
  if !allowedUploadExtensions[ext] {
65
- return nil, fmt.Errorf("unsupported file extension %q; only .doc and .txt uploads are supported", ext)
66
+ return nil, fmt.Errorf("unsupported file extension %q; only .doc, .docx, and .txt uploads are supported", ext)
66
67
  }
67
68
  fileName := filepath.Base(path)
68
69
  contentType := mime.TypeByExtension(ext)
@@ -16,7 +16,7 @@ type GetThreadOptions struct {
16
16
  RunID string `json:"run_id,omitempty"`
17
17
  }
18
18
 
19
- // GetThreadResult is the JSON envelope printed by `pippit-tool-cli short-drama +get-thread`.
19
+ // GetThreadResult is the parsed get_thread response used by `pippit-tool-cli short-drama +get-thread`.
20
20
  type GetThreadResult struct {
21
21
  ReadableText string `json:"readable_text"`
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-tool-cli": "scripts/run.js"
@@ -25,7 +25,7 @@ metadata:
25
25
 
26
26
  1. **提交短剧 Run 任务** - 创建新会话或向已有会话发送短剧创作需求。
27
27
  2. **查询会话进展** - 根据 `thread_id` 和可选 `run_id` 拉取服务端 v2 `readable_text`,用于展示短剧任务进展、问题和结果。
28
- 3. **上传文件** - 上传本地 `.doc` / `.txt` 参考文件,得到 `asset_id`,供后续任务引用。
28
+ 3. **上传文件** - 上传本地 `.doc` / `.docx` / `.txt` 参考文件,得到 `asset_id`,供后续任务引用。
29
29
  4. **获取会话文件** - 根据 `thread_id` 拉取会话文件列表,得到 `file_path`、`download_url`。这和查询会话进展同等重要。
30
30
  5. **下载重要资产** - 使用文件列表中的 `download_url` 下载资源,并按 `file_path` 写入用户本地目标文件路径。
31
31
 
@@ -65,7 +65,7 @@ pippit-tool-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID
65
65
 
66
66
  ### 3. 上传文件
67
67
 
68
- 当用户提供短剧大纲、人物设定、世界观设定、已有分集或剧本等本地参考文件时,可先上传文件。`+upload-file` 当前只接收本地文件路径,并且只支持 `.doc` 和 `.txt` 后缀;不要把 `.md`、`.pdf`、图片、视频或 URL 传给该命令。
68
+ 当用户提供短剧大纲、人物设定、世界观设定、已有分集或剧本等本地参考文件时,可先上传文件。`+upload-file` 当前只接收本地文件路径,并且只支持 `.doc`、`.docx` 和 `.txt` 后缀;不要把 `.md`、`.pdf`、图片、视频或 URL 传给该命令。
69
69
 
70
70
  ```bash
71
71
  pippit-tool-cli short-drama +upload-file --path /path/to/outline.txt
@@ -140,7 +140,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
140
140
  ### 场景 2:用户提供参考文件要求创作
141
141
 
142
142
  ```
143
- 1. 检查用户提供的是一个本地 `.doc` 或 `.txt` 剧本文件路径;如果不是,告知当前上传命令只支持这两类文件,不要擅自转换或改写文件。
143
+ 1. 检查用户提供的是一个本地 `.doc`、`.docx` 或 `.txt` 剧本文件路径;如果不是,告知当前上传命令只支持这三类文件,不要擅自转换或改写文件。
144
144
  2. pippit-tool-cli short-drama +upload-file --path /path/to/file.txt
145
145
  → 拿到 asset_id
146
146
  3. pippit-tool-cli short-drama +submit-run --message "用户的原始短剧需求" --asset-ids asset_id
@@ -194,10 +194,13 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
194
194
 
195
195
  **+get-thread** 返回:
196
196
 
197
- ```json
198
- {
199
- "readable_text": "Thread: thread_...\n 标题: ...\n 状态: ...\n\n -- Run #1 --\n [assistant] ..."
200
- }
197
+ ```text
198
+ Thread: thread_...
199
+ 标题: ...
200
+ 状态: ...
201
+
202
+ -- Run #1 --
203
+ [assistant] ...
201
204
  ```
202
205
 
203
206
  **+upload-file** 返回:
@@ -208,7 +211,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
208
211
  }
209
212
  ```
210
213
 
211
- `+upload-file` 通过 `multipart/form-data` 上传文件,表单文件字段名为 `file`。本地文件必须存在、不能是目录,后缀必须是 `.doc` 或 `.txt`;不支持的后缀会直接报错。返回的 `asset_id` 来自服务端 `pippit_asset_id`,如果没有该字段才回退到 `asset_id`。
214
+ `+upload-file` 通过 `multipart/form-data` 上传文件,表单文件字段名为 `file`。本地文件必须存在、不能是目录,后缀必须是 `.doc`、`.docx` 或 `.txt`;不支持的后缀会直接报错。返回的 `asset_id` 来自服务端 `pippit_asset_id`,如果没有该字段才回退到 `asset_id`。
212
215
 
213
216
  **+list-thread-file** 返回:
214
217
 
@@ -280,7 +283,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
280
283
 
281
284
  你要做的只有三件事:
282
285
 
283
- 1. **上传**:如果用户给了本地 `.doc` / `.txt` 参考文件,先调用 `+upload-file`。
286
+ 1. **上传**:如果用户给了本地 `.doc` / `.docx` / `.txt` 参考文件,先调用 `+upload-file`。
284
287
  2. **提交任务**:首次创作时把用户原始短剧需求和唯一剧本 `asset_id` 通过 `+submit-run --asset-ids` 发给后端;同一 `thread_id` 后续续写或修改不再追加新的剧本文件。
285
288
  3. **传话、取文件、下载资源**:根据 `+get-thread` 返回的 `readable_text` 展示进展、问题和结果;根据 `+list-thread-file` 获取文件列表;再根据 `download_url` 调用 `+download-result` 把缺失资源下载到用户本地。
286
289
 
@@ -298,7 +301,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
298
301
  - `--message` 是用户的原始短剧需求,不能为空。
299
302
  - 查询进展时优先使用 `+submit-run` 返回的 `thread_id` 和 `run_id`;如果需要查看整个会话,可以省略 `--run-id`。
300
303
  - `+get-thread` 当前固定走服务端 v2 响应,输出字段是 `readable_text`;不要解析旧版 `messages` 数组。
301
- - `+upload-file` 当前用于短剧场景文件上传链路,只支持本地 `.doc` / `.txt` 文件;`--path` 不能为空,路径必须指向真实文件,不能是目录。
304
+ - `+upload-file` 当前用于短剧场景文件上传链路,只支持本地 `.doc` / `.docx` / `.txt` 文件;`--path` 不能为空,路径必须指向真实文件,不能是目录。
302
305
  - `+upload-file` 上传成功后只返回 `asset_id`;把该值原样作为 `+submit-run --asset-ids` 的参数。
303
306
  - 单次创作会话中(相同 `thread_id`),`+submit-run` 只支持绑定一个剧本文件。不要在同一 `thread_id` 下重复上传并追加第二个剧本 `asset_id`;用户给多个剧本时,先让用户选择一个,或分别开启新的创作会话。
304
307
  - `+list-thread-file` 只需要 `thread_id`;分页参数使用 `--page-num 1 --page-size 200` 起步,`total` 达到 200 时下一轮递增 `page-num`。