@pippit-dev/cli 0.0.17 → 0.0.19
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 +5 -0
- package/checksums.txt +6 -6
- package/cmd/short_drama/short_drama.go +60 -10
- package/cmd/short_drama_test.go +62 -0
- package/internal/common/error_log.go +91 -0
- package/internal/common/error_log_test.go +61 -0
- package/package.json +1 -1
- package/skills/short-drama/SKILL.md +33 -4
package/README.md
CHANGED
|
@@ -41,6 +41,11 @@ 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
|
+
35669c5443abd32d8318c30a48a4c8f2985c97b9719bdae2cb141cb0d08c8e73 pippit-tool-cli-0.0.19-darwin-amd64.tar.gz
|
|
2
|
+
168316fdf953f4a22b76281512368d04ab299577b2ee53cf4ce65f254474dd8e pippit-tool-cli-0.0.19-darwin-arm64.tar.gz
|
|
3
|
+
016af69efae30199ecc117a4e227e47c9c96c2a8ef24b86ac9e7d61108ef73cb pippit-tool-cli-0.0.19-linux-amd64.tar.gz
|
|
4
|
+
38e096dc71c3509c47943dd1f37076fbc525d2d8874f299fa000885ab34c574a pippit-tool-cli-0.0.19-linux-arm64.tar.gz
|
|
5
|
+
cc3ded92e540d36af090679a95b8fc5ddc81a9d47c6ff56456ed0c89b5518674 pippit-tool-cli-0.0.19-windows-amd64.zip
|
|
6
|
+
7db4bb48c1fcbd16ca1940dcaa06d7998ddf814a95e377bdbfa80c4075b07ed4 pippit-tool-cli-0.0.19-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")
|
|
@@ -140,7 +162,7 @@ func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runn
|
|
|
140
162
|
}
|
|
141
163
|
_, err = fmt.Fprintln(stdout, result.ReadableText)
|
|
142
164
|
return err
|
|
143
|
-
},
|
|
165
|
+
}),
|
|
144
166
|
}
|
|
145
167
|
cmd.SetOut(stdout)
|
|
146
168
|
cmd.SetErr(stderr)
|
|
@@ -156,7 +178,13 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
|
|
|
156
178
|
Use: "+list-thread-file",
|
|
157
179
|
Short: "List files in a short drama thread",
|
|
158
180
|
Args: cobra.NoArgs,
|
|
159
|
-
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 {
|
|
160
188
|
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
161
189
|
if opts.ThreadID == "" {
|
|
162
190
|
return fmt.Errorf("--thread-id is required")
|
|
@@ -173,7 +201,7 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
|
|
|
173
201
|
return err
|
|
174
202
|
}
|
|
175
203
|
return writeJSON(stdout, result)
|
|
176
|
-
},
|
|
204
|
+
}),
|
|
177
205
|
}
|
|
178
206
|
cmd.SetOut(stdout)
|
|
179
207
|
cmd.SetErr(stderr)
|
|
@@ -183,6 +211,28 @@ func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common
|
|
|
183
211
|
return cmd
|
|
184
212
|
}
|
|
185
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
|
+
|
|
186
236
|
func writeJSON(w io.Writer, v any) error {
|
|
187
237
|
data, err := sonic.Marshal(v)
|
|
188
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 {
|
|
@@ -597,6 +611,8 @@ func TestShortDramaGetThread(t *testing.T) {
|
|
|
597
611
|
}
|
|
598
612
|
|
|
599
613
|
func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
614
|
+
clearDailyErrorLog(t)
|
|
615
|
+
|
|
600
616
|
var stdout, stderr bytes.Buffer
|
|
601
617
|
root := NewRootCommand(&stdout, &stderr)
|
|
602
618
|
root.SetArgs([]string{"short-drama", "+get-thread"})
|
|
@@ -608,6 +624,17 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
|
608
624
|
if !strings.Contains(err.Error(), "--thread-id is required") {
|
|
609
625
|
t.Fatalf("error = %q, want thread-id validation", err)
|
|
610
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
|
+
}
|
|
611
638
|
}
|
|
612
639
|
|
|
613
640
|
func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
|
|
@@ -787,6 +814,41 @@ func decodeJSON(t *testing.T, data []byte) map[string]any {
|
|
|
787
814
|
return got
|
|
788
815
|
}
|
|
789
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
|
+
|
|
790
852
|
func chdirTemp(t *testing.T) string {
|
|
791
853
|
t.Helper()
|
|
792
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
|
+
}
|
package/package.json
CHANGED
|
@@ -31,6 +31,34 @@ metadata:
|
|
|
31
31
|
|
|
32
32
|
重要资产包括但不限于:剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物。只要 `+list-thread-file` 返回了这些资产的 `download_url`,就要及时调用下载工具落盘,不要只展示文件元信息。
|
|
33
33
|
|
|
34
|
+
## 短剧主流程顺序
|
|
35
|
+
|
|
36
|
+
短剧创作按以下主流程推进。用户侧 Agent 在展示后端 Agent 的表单、问卷、选项或确认问题时,必须先参考这个顺序判断当前阶段和合理下一步。
|
|
37
|
+
|
|
38
|
+
1. 剧本上传 / AI 剧本生成 / AI 剧本编辑
|
|
39
|
+
2. 剧本合并与完整剧本确认
|
|
40
|
+
3. 剧本分析
|
|
41
|
+
4. 短剧风格推荐确认
|
|
42
|
+
5. 剧本标准化(可选)
|
|
43
|
+
6. 场景分析
|
|
44
|
+
7. 所有必要场景图生成
|
|
45
|
+
8. 角色分析
|
|
46
|
+
9. 所有必要角色图生成
|
|
47
|
+
10. 分镜设计
|
|
48
|
+
11. 分镜视频生成
|
|
49
|
+
12. 完整视频合成
|
|
50
|
+
|
|
51
|
+
## 表单与问卷选项处理原则
|
|
52
|
+
|
|
53
|
+
后端 Agent 通过 `readable_text` 发出表单、问卷、选项、按钮或询问用户时,用户侧 Agent 不要机械原样转述所有选项。先结合短剧主流程顺序清洗选项,再把合理、必要、当前可执行的流程项呈现给用户。
|
|
54
|
+
|
|
55
|
+
- 保留当前阶段的确认项,以及不会跳过必要阶段的下一步流程项。
|
|
56
|
+
- 剔除跳过必要阶段的选项。例如未完成“剧本合并与完整剧本确认”前,不应让用户直接进入“剧本分析”;未完成“所有必要场景图生成”前,不应让用户直接进入“角色分析”。
|
|
57
|
+
- 剔除倒退到无关阶段的选项。只有用户明确要求返工、修改或重新生成时,才展示回退选项。
|
|
58
|
+
- `剧本标准化` 是可选阶段,只能出现在“短剧风格推荐确认”之后、“场景分析”之前。不要把它包装成任意阶段都可以跳过或补做的通用选项。
|
|
59
|
+
- 不替用户决定创意内容,例如风格、剧情方向、角色设定、镜头方案。只能清洗流程选项,不能代替用户选择创作偏好。
|
|
60
|
+
- 如果服务端问题混入跨度过大的多个流程选项,重新组织成当前阶段可回答的问题,并说明已按主流程剔除不合理或跳跃选项。
|
|
61
|
+
|
|
34
62
|
## 前置要求
|
|
35
63
|
|
|
36
64
|
需要已安装 `pippit-tool-cli`:
|
|
@@ -167,7 +195,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
167
195
|
- **重要资产识别**:每轮都检查 `+list-thread-file` 返回的文件。剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物都是重要资产。
|
|
168
196
|
- **文件下载**:解析 `+list-thread-file` 的结果后,对带 `download_url` 的重要资产立即调用 `+download-result` 下载资源;不要在 `list-thread-file` 阶段检查文件是否已存在,存在性检查由下载工具内部处理。
|
|
169
197
|
- **下载完成标准**:不要把文件元信息展示当成下载完成;必须拿到本地 `file_path`,或明确记录该文件在重试后仍下载失败。
|
|
170
|
-
-
|
|
198
|
+
- **用户确认**:如果消息中出现需要用户确认、补充设定或回答问题的内容,先判断是否包含表单、问卷、选项或按钮;包含时按“短剧主流程顺序”和“表单与问卷选项处理原则”清洗选项,再展示给用户并等待回复。
|
|
171
199
|
- **超时**:如果长时间无结果,告知用户任务仍在生成中,可稍后通过 `web_thread_link` 查看。
|
|
172
200
|
- **错误处理**:`+get-thread`、`+list-thread-file` 或 `+download-result` 任一调用失败时,记录失败原因和参数,在后续轮询中主动重试;重试期间继续处理其他成功返回的消息和文件。连续多轮失败后再向用户说明仍未完成的查询或下载项。
|
|
173
201
|
|
|
@@ -178,7 +206,8 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
178
206
|
1. 已处理 `+get-thread` 返回的最新 `readable_text`、用户确认问题和最终消息。
|
|
179
207
|
2. 已用 `--page-size 200` 调用 `+list-thread-file` 获取会话文件列表;如果本轮 `total` 达到 200,已在后续轮询中递增 `page-num` 查询新一页。
|
|
180
208
|
3. 对所有带 `download_url` 的重要资产,已调用 `+download-result` 下载到本地 `file_path`。
|
|
181
|
-
4.
|
|
209
|
+
4. 已按短剧主流程顺序检查服务端表单、问卷和选项,没有把跳过必要阶段的选项直接呈现给用户;如果跳过 `剧本标准化`,已明确这是可选阶段。
|
|
210
|
+
5. 对查询失败或下载失败的资产,已在后续轮询中主动重试,并在最终回复中列出仍失败的文件或命令。
|
|
182
211
|
|
|
183
212
|
## 输出格式
|
|
184
213
|
|
|
@@ -271,7 +300,7 @@ Thread: thread_...
|
|
|
271
300
|
|
|
272
301
|
- 任务提交后:立即展示 `web_thread_link`。
|
|
273
302
|
- 任务进行中:展示后端 Agent 返回的过程消息。
|
|
274
|
-
-
|
|
303
|
+
- 需要用户补充信息时:如果是普通问题,展示后端 Agent 的问题并等待用户回复;如果包含表单、问卷、选项或按钮,先按短剧主流程清洗不合理或跳跃的流程选项,再展示给用户。
|
|
275
304
|
- 任务完成后:展示短剧内容、分集草稿、设定说明或其他结果信息,同时检查是否有未下载的重要资产。
|
|
276
305
|
- 获取会话文件后:展示或记录文件元信息,不把它当成已下载结果。
|
|
277
306
|
- 文件资源下载后:展示已落盘的本地文件路径;已存在而跳过下载的文件也要标明。
|
|
@@ -285,7 +314,7 @@ Thread: thread_...
|
|
|
285
314
|
|
|
286
315
|
1. **上传**:如果用户给了本地 `.doc` / `.docx` / `.txt` 参考文件,先调用 `+upload-file`。
|
|
287
316
|
2. **提交任务**:首次创作时把用户原始短剧需求和唯一剧本 `asset_id` 通过 `+submit-run --asset-ids` 发给后端;同一 `thread_id` 后续续写或修改不再追加新的剧本文件。
|
|
288
|
-
3. **传话、取文件、下载资源**:根据 `+get-thread` 返回的 `readable_text`
|
|
317
|
+
3. **传话、取文件、下载资源**:根据 `+get-thread` 返回的 `readable_text` 展示进展、问题和结果;遇到表单、问卷、选项或按钮时,只做流程合理性清洗,不替用户决定创作内容;根据 `+list-thread-file` 获取文件列表;再根据 `download_url` 调用 `+download-result` 把缺失资源下载到用户本地。
|
|
289
318
|
|
|
290
319
|
**不要做的事:**
|
|
291
320
|
|