@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 +6 -1
- package/checksums.txt +6 -6
- package/cmd/short_drama/short_drama.go +62 -11
- package/cmd/short_drama_test.go +73 -12
- package/internal/common/error_log.go +91 -0
- package/internal/common/error_log_test.go +61 -0
- package/internal/common/upload_file.go +4 -3
- package/internal/short_drama/get_thread.go +1 -1
- package/package.json +1 -1
- package/skills/short-drama/SKILL.md +13 -10
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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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 {
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -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.
|
|
200
|
-
t.Fatalf("filename = %q, want story.
|
|
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 != "
|
|
203
|
-
t.Fatalf("Content-Type = %q, want
|
|
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) != "
|
|
215
|
-
t.Fatalf("file content = %q, want
|
|
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.
|
|
223
|
-
if err := os.WriteFile(path, []byte("
|
|
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 :=
|
|
595
|
-
|
|
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":
|
|
40
|
-
".
|
|
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
|
|
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
|
@@ -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
|
-
```
|
|
198
|
-
|
|
199
|
-
|
|
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`。
|