@pippit-dev/cli 0.0.11 → 0.0.13
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 +12 -10
- package/checksums.txt +6 -6
- package/cmd/root.go +2 -2
- package/cmd/short_drama/short_drama.go +0 -3
- package/cmd/short_drama_test.go +97 -10
- package/cmd/update/update.go +7 -7
- package/internal/common/client.go +95 -0
- package/internal/common/download_results.go +1 -1
- package/internal/common/list_thread_file.go +1 -1
- package/internal/common/upload_file.go +80 -25
- package/internal/short_drama/get_thread.go +1 -1
- package/internal/short_drama/submit_run.go +1 -1
- package/package.json +2 -2
- package/scripts/install-wizard.js +10 -10
- package/scripts/install.js +2 -2
- package/scripts/run.js +2 -2
- package/scripts/version-check.js +1 -1
- package/skills/short-drama/SKILL.md +18 -18
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pippit-cli
|
|
1
|
+
# pippit-tool-cli
|
|
2
2
|
|
|
3
3
|
Minimal demo CLI for Pippit workflows.
|
|
4
4
|
|
|
@@ -10,11 +10,11 @@ matching prebuilt binary for macOS, Linux, or Windows:
|
|
|
10
10
|
```bash
|
|
11
11
|
npx @pippit-dev/cli@latest install
|
|
12
12
|
export XYQ_ACCESS_KEY="<access-key>"
|
|
13
|
-
pippit-cli --version
|
|
14
|
-
pippit-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
|
|
15
|
-
pippit-cli short-drama +upload-file --path ./
|
|
16
|
-
pippit-cli short-drama +get-thread --thread-id thread_123 --run-id run_456 --after-seq 0
|
|
17
|
-
pippit-cli short-drama +download-result --output-path ./thread_123/results/result.mp4 --url URL
|
|
13
|
+
pippit-tool-cli --version
|
|
14
|
+
pippit-tool-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
|
|
15
|
+
pippit-tool-cli short-drama +upload-file --path ./reference.doc
|
|
16
|
+
pippit-tool-cli short-drama +get-thread --thread-id thread_123 --run-id run_456 --after-seq 0
|
|
17
|
+
pippit-tool-cli short-drama +download-result --output-path ./thread_123/results/result.mp4 --url URL
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
NPM package names must be lowercase, so the publishable package name is
|
|
@@ -26,7 +26,7 @@ Submit a Run task for the short drama scene:
|
|
|
26
26
|
export XYQ_ACCESS_KEY="<access-key>"
|
|
27
27
|
go run . --version
|
|
28
28
|
go run . short-drama +submit-run --message "写一个赛博朋克短剧开头"
|
|
29
|
-
go run . short-drama +upload-file --path ./
|
|
29
|
+
go run . short-drama +upload-file --path ./reference.doc
|
|
30
30
|
go run . short-drama +get-thread --thread-id thread_123 --run-id run_456 --after-seq 0
|
|
31
31
|
go run . short-drama +download-result --output-path ./thread_123/results/result.mp4 --url URL
|
|
32
32
|
```
|
|
@@ -35,9 +35,11 @@ go run . short-drama +download-result --output-path ./thread_123/results/result.
|
|
|
35
35
|
`run_id`, and `web_thread_link`; `--message` is required.
|
|
36
36
|
`+get-thread` calls
|
|
37
37
|
`/api/biz/v1/skill/get_thread` and prints extracted `messages`.
|
|
38
|
+
`+upload-file` calls `/api/biz/v1/skill/upload_file` with
|
|
39
|
+
`multipart/form-data` and prints the returned `asset_id`.
|
|
40
|
+
Only `.doc` and `.txt` file extensions are supported.
|
|
38
41
|
`+download-result` downloads the result URL to
|
|
39
|
-
the `--output-path` file path.
|
|
40
|
-
its real service contract is wired.
|
|
42
|
+
the `--output-path` file path.
|
|
41
43
|
|
|
42
44
|
## HTTP Client
|
|
43
45
|
|
|
@@ -47,6 +49,6 @@ settings such as base URL, HTTP timeout, and API paths are loaded by
|
|
|
47
49
|
|
|
48
50
|
## Auth
|
|
49
51
|
|
|
50
|
-
`short-drama +submit-run` and `short-drama +
|
|
52
|
+
`short-drama +submit-run`, `short-drama +get-thread`, and `short-drama +upload-file` authenticate with
|
|
51
53
|
`Authorization: Bearer <XYQ_ACCESS_KEY>`. OAuth command code remains available,
|
|
52
54
|
but runtime short drama requests do not use it.
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
c02c37229ea76195c04881880a9d5a913ab95846958ab4e4ab41bc2b82649e49 pippit-tool-cli-0.0.13-darwin-amd64.tar.gz
|
|
2
|
+
4937231063eeaad1e494be4f2b6218860313abbf529d868568d9d6e48bf62e3e pippit-tool-cli-0.0.13-darwin-arm64.tar.gz
|
|
3
|
+
da2bb0c5472bdd02a2cc861a17564254abb86fa5c63c44b58c7fbb64211550f1 pippit-tool-cli-0.0.13-linux-amd64.tar.gz
|
|
4
|
+
476e0907a6983f9360324732411edcd9c2c80f9d903a7974949ef2d09b1f35ba pippit-tool-cli-0.0.13-linux-arm64.tar.gz
|
|
5
|
+
cfe8e5b92b8b6abbf5ed3abc9ce31c1371efcc34698edfa5ee2d8d675d4f7886 pippit-tool-cli-0.0.13-windows-amd64.zip
|
|
6
|
+
edff7af4414b2b080338f96eab5ce9cca2a14d4bd8fc3099a1af7a2dc43109d2 pippit-tool-cli-0.0.13-windows-arm64.zip
|
package/cmd/root.go
CHANGED
|
@@ -13,7 +13,7 @@ import (
|
|
|
13
13
|
"github.com/spf13/cobra"
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
// Execute runs the pippit-cli command tree.
|
|
16
|
+
// Execute runs the pippit-tool-cli command tree.
|
|
17
17
|
func Execute() error {
|
|
18
18
|
return NewRootCommand(os.Stdout, os.Stderr).Execute()
|
|
19
19
|
}
|
|
@@ -27,7 +27,7 @@ func NewRootCommand(stdout, stderr io.Writer) *cobra.Command {
|
|
|
27
27
|
|
|
28
28
|
func newRootCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
29
29
|
root := &cobra.Command{
|
|
30
|
-
Use: "pippit-cli",
|
|
30
|
+
Use: "pippit-tool-cli",
|
|
31
31
|
Short: "Pippit CLI",
|
|
32
32
|
Long: "Pippit CLI submits short-drama workflows, downloads generated assets, and updates the installed CLI package.",
|
|
33
33
|
Version: version.Current(),
|
|
@@ -3,7 +3,6 @@ package short_drama
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
5
|
"io"
|
|
6
|
-
"path/filepath"
|
|
7
6
|
"strings"
|
|
8
7
|
|
|
9
8
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
@@ -72,8 +71,6 @@ func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Run
|
|
|
72
71
|
if opts.Path == "" {
|
|
73
72
|
return fmt.Errorf("--path is required")
|
|
74
73
|
}
|
|
75
|
-
opts.FileName = filepath.Base(opts.Path)
|
|
76
|
-
opts.Mock = true
|
|
77
74
|
|
|
78
75
|
result, err := common.UploadFile(cmd.Context(), opts, runner)
|
|
79
76
|
if err != nil {
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -176,23 +176,65 @@ func TestShortDramaSubmitRunRequiresAccessKey(t *testing.T) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
func TestShortDramaUploadFile(t *testing.T) {
|
|
179
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
180
|
+
if r.Method != http.MethodPost {
|
|
181
|
+
t.Fatalf("method = %s, want POST", r.Method)
|
|
182
|
+
}
|
|
183
|
+
if r.URL.Path != "/api/biz/v1/skill/upload_file" {
|
|
184
|
+
t.Fatalf("path = %s, want upload_file path", r.URL.Path)
|
|
185
|
+
}
|
|
186
|
+
if r.Header.Get("Authorization") != "Bearer test-token" {
|
|
187
|
+
t.Fatalf("Authorization = %q, want test bearer token", r.Header.Get("Authorization"))
|
|
188
|
+
}
|
|
189
|
+
if err := r.ParseMultipartForm(1 << 20); err != nil {
|
|
190
|
+
t.Fatalf("ParseMultipartForm(): %v", err)
|
|
191
|
+
}
|
|
192
|
+
if got := r.FormValue("accessKey"); got != "" {
|
|
193
|
+
t.Fatalf("accessKey = %q, want empty (auth via header only)", got)
|
|
194
|
+
}
|
|
195
|
+
files := r.MultipartForm.File["file"]
|
|
196
|
+
if len(files) != 1 {
|
|
197
|
+
t.Fatalf("file parts = %d, want 1", len(files))
|
|
198
|
+
}
|
|
199
|
+
if files[0].Filename != "story.txt" {
|
|
200
|
+
t.Fatalf("filename = %q, want story.txt", files[0].Filename)
|
|
201
|
+
}
|
|
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)
|
|
204
|
+
}
|
|
205
|
+
file, err := files[0].Open()
|
|
206
|
+
if err != nil {
|
|
207
|
+
t.Fatalf("Open multipart file: %v", err)
|
|
208
|
+
}
|
|
209
|
+
defer file.Close()
|
|
210
|
+
data, err := io.ReadAll(file)
|
|
211
|
+
if err != nil {
|
|
212
|
+
t.Fatalf("ReadAll multipart file: %v", err)
|
|
213
|
+
}
|
|
214
|
+
if string(data) != "txt-data" {
|
|
215
|
+
t.Fatalf("file content = %q, want txt-data", string(data))
|
|
216
|
+
}
|
|
217
|
+
_, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"pippit_asset_id":"asset_123"}}`))
|
|
218
|
+
}))
|
|
219
|
+
defer server.Close()
|
|
220
|
+
|
|
221
|
+
cwd := chdirTemp(t)
|
|
222
|
+
path := filepath.Join(cwd, "story.txt")
|
|
223
|
+
if err := os.WriteFile(path, []byte("txt-data"), 0o644); err != nil {
|
|
224
|
+
t.Fatalf("WriteFile(): %v", err)
|
|
225
|
+
}
|
|
226
|
+
|
|
179
227
|
var stdout, stderr bytes.Buffer
|
|
180
|
-
root :=
|
|
181
|
-
root.SetArgs([]string{"short-drama", "+upload-file", "--path",
|
|
228
|
+
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
229
|
+
root.SetArgs([]string{"short-drama", "+upload-file", "--path", path})
|
|
182
230
|
|
|
183
231
|
if err := root.Execute(); err != nil {
|
|
184
232
|
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
185
233
|
}
|
|
186
234
|
|
|
187
235
|
got := decodeJSON(t, stdout.Bytes())
|
|
188
|
-
if got["
|
|
189
|
-
t.Fatalf("
|
|
190
|
-
}
|
|
191
|
-
if got["status"] != "uploaded" {
|
|
192
|
-
t.Fatalf("status = %v, want uploaded", got["status"])
|
|
193
|
-
}
|
|
194
|
-
if !strings.HasPrefix(got["file_id"].(string), "file_mock_") {
|
|
195
|
-
t.Fatalf("file_id = %v, want mock file id", got["file_id"])
|
|
236
|
+
if got["asset_id"] != "asset_123" {
|
|
237
|
+
t.Fatalf("asset_id = %v, want asset_123", got["asset_id"])
|
|
196
238
|
}
|
|
197
239
|
}
|
|
198
240
|
|
|
@@ -210,6 +252,51 @@ func TestShortDramaUploadFileRequiresPath(t *testing.T) {
|
|
|
210
252
|
}
|
|
211
253
|
}
|
|
212
254
|
|
|
255
|
+
func TestShortDramaUploadFileRequiresAccessKey(t *testing.T) {
|
|
256
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
257
|
+
t.Fatal("server should not receive request without access key")
|
|
258
|
+
}))
|
|
259
|
+
defer server.Close()
|
|
260
|
+
|
|
261
|
+
cwd := chdirTemp(t)
|
|
262
|
+
path := filepath.Join(cwd, "story.txt")
|
|
263
|
+
if err := os.WriteFile(path, []byte("txt-data"), 0o644); err != nil {
|
|
264
|
+
t.Fatalf("WriteFile(): %v", err)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var stdout, stderr bytes.Buffer
|
|
268
|
+
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
269
|
+
root.SetArgs([]string{"short-drama", "+upload-file", "--path", path})
|
|
270
|
+
|
|
271
|
+
err := root.Execute()
|
|
272
|
+
if err == nil {
|
|
273
|
+
t.Fatal("Execute() error = nil, want access key error")
|
|
274
|
+
}
|
|
275
|
+
if !strings.Contains(err.Error(), "XYQ_ACCESS_KEY is required") {
|
|
276
|
+
t.Fatalf("error = %q, want access key guidance", err)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
func TestShortDramaUploadFileRejectsUnsupportedFileType(t *testing.T) {
|
|
281
|
+
cwd := chdirTemp(t)
|
|
282
|
+
path := filepath.Join(cwd, "story.png")
|
|
283
|
+
if err := os.WriteFile(path, []byte("png-data"), 0o644); err != nil {
|
|
284
|
+
t.Fatalf("WriteFile(): %v", err)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
var stdout, stderr bytes.Buffer
|
|
288
|
+
root := NewRootCommand(&stdout, &stderr)
|
|
289
|
+
root.SetArgs([]string{"short-drama", "+upload-file", "--path", path})
|
|
290
|
+
|
|
291
|
+
err := root.Execute()
|
|
292
|
+
if err == nil {
|
|
293
|
+
t.Fatal("Execute() error = nil, want file type validation error")
|
|
294
|
+
}
|
|
295
|
+
if !strings.Contains(err.Error(), "only .doc and .txt uploads are supported") {
|
|
296
|
+
t.Fatalf("error = %q, want unsupported type validation", err)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
213
300
|
func TestShortDramaDownloadResult(t *testing.T) {
|
|
214
301
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
215
302
|
if r.Header.Get("User-Agent") != "Pippit-CLI/1.0" {
|
package/cmd/update/update.go
CHANGED
|
@@ -18,7 +18,7 @@ const defaultPackage = "@pippit-dev/cli"
|
|
|
18
18
|
func NewCommand(stdout, stderr io.Writer) *cobra.Command {
|
|
19
19
|
cmd := &cobra.Command{
|
|
20
20
|
Use: "update",
|
|
21
|
-
Short: "Update pippit-cli and bundled skills",
|
|
21
|
+
Short: "Update pippit-tool-cli and bundled skills",
|
|
22
22
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
23
23
|
return runUpdate(stdout, stderr)
|
|
24
24
|
},
|
|
@@ -32,14 +32,14 @@ func runUpdate(stdout, stderr io.Writer) error {
|
|
|
32
32
|
pkg = defaultPackage + "@latest"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
fmt.Fprintf(stderr, "Updating pippit-cli via npm: %s\n", pkg)
|
|
35
|
+
fmt.Fprintf(stderr, "Updating pippit-tool-cli via npm: %s\n", pkg)
|
|
36
36
|
restore, err := prepareSelfReplace()
|
|
37
37
|
if err != nil {
|
|
38
38
|
return fmt.Errorf("prepare self replace: %w", err)
|
|
39
39
|
}
|
|
40
40
|
if err := runInheritEnv(stderr, []string{"PIPPIT_CLI_SKIP_SKILLS=1"}, "npm", "install", "-g", pkg); err != nil {
|
|
41
41
|
restore()
|
|
42
|
-
return fmt.Errorf("update pippit-cli: %w", err)
|
|
42
|
+
return fmt.Errorf("update pippit-tool-cli: %w", err)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
root, err := globalPackageRoot(defaultPackage)
|
|
@@ -47,12 +47,12 @@ func runUpdate(stdout, stderr io.Writer) error {
|
|
|
47
47
|
return fmt.Errorf("locate global package: %w", err)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
fmt.Fprintln(stderr, "Updating pippit-cli skills...")
|
|
50
|
+
fmt.Fprintln(stderr, "Updating pippit-tool-cli skills...")
|
|
51
51
|
if err := installSkills(root, stderr); err != nil {
|
|
52
|
-
return fmt.Errorf("update pippit-cli skills: %w", err)
|
|
52
|
+
return fmt.Errorf("update pippit-tool-cli skills: %w", err)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
fmt.Fprintln(stdout, "pippit-cli and skills updated")
|
|
55
|
+
fmt.Fprintln(stdout, "pippit-tool-cli and skills updated")
|
|
56
56
|
return nil
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -107,7 +107,7 @@ func prepareSelfReplace() (func(), error) {
|
|
|
107
107
|
if err != nil {
|
|
108
108
|
return nil, err
|
|
109
109
|
}
|
|
110
|
-
if filepath.Base(exe) != "pippit-cli.exe" || !strings.Contains(exe, "node_modules") {
|
|
110
|
+
if filepath.Base(exe) != "pippit-tool-cli.exe" || !strings.Contains(exe, "node_modules") {
|
|
111
111
|
return func() {}, nil
|
|
112
112
|
}
|
|
113
113
|
old := exe + ".old"
|
|
@@ -5,8 +5,12 @@ import (
|
|
|
5
5
|
"context"
|
|
6
6
|
"fmt"
|
|
7
7
|
"io"
|
|
8
|
+
"mime/multipart"
|
|
8
9
|
"net/http"
|
|
10
|
+
"net/textproto"
|
|
9
11
|
"net/url"
|
|
12
|
+
"os"
|
|
13
|
+
"path/filepath"
|
|
10
14
|
"reflect"
|
|
11
15
|
"strings"
|
|
12
16
|
"time"
|
|
@@ -16,12 +20,20 @@ import (
|
|
|
16
20
|
|
|
17
21
|
type Client interface {
|
|
18
22
|
SendRequest(ctx context.Context, path string, body any, out any) error
|
|
23
|
+
SendMultipartRequest(ctx context.Context, path string, fields map[string]string, file MultipartFile, out any) error
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
type RequestAuthorizer interface {
|
|
22
27
|
Inject(ctx context.Context, req *http.Request) error
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
type MultipartFile struct {
|
|
31
|
+
FieldName string
|
|
32
|
+
Path string
|
|
33
|
+
FileName string
|
|
34
|
+
ContentType string
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
type httpClient struct {
|
|
26
38
|
baseURL string
|
|
27
39
|
httpClient *http.Client
|
|
@@ -100,6 +112,89 @@ func (c *httpClient) SendRequest(ctx context.Context, path string, body any, out
|
|
|
100
112
|
return c.do(req, out)
|
|
101
113
|
}
|
|
102
114
|
|
|
115
|
+
func (c *httpClient) SendMultipartRequest(ctx context.Context, path string, fields map[string]string, file MultipartFile, out any) error {
|
|
116
|
+
reqURL, err := c.resolveURL(path, nil)
|
|
117
|
+
if err != nil {
|
|
118
|
+
return err
|
|
119
|
+
}
|
|
120
|
+
if file.FieldName == "" {
|
|
121
|
+
return fmt.Errorf("multipart file field name is required")
|
|
122
|
+
}
|
|
123
|
+
if file.FileName == "" {
|
|
124
|
+
file.FileName = filepath.Base(file.Path)
|
|
125
|
+
}
|
|
126
|
+
if file.ContentType == "" {
|
|
127
|
+
file.ContentType = "application/octet-stream"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pr, pw := io.Pipe()
|
|
131
|
+
writer := multipart.NewWriter(pw)
|
|
132
|
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, pr)
|
|
133
|
+
if err != nil {
|
|
134
|
+
_ = pr.Close()
|
|
135
|
+
_ = pw.Close()
|
|
136
|
+
return fmt.Errorf("build POST request: %w", err)
|
|
137
|
+
}
|
|
138
|
+
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
139
|
+
req.Header.Set("Accept", "application/json")
|
|
140
|
+
|
|
141
|
+
if c.authorizer == nil {
|
|
142
|
+
_ = pr.Close()
|
|
143
|
+
_ = pw.Close()
|
|
144
|
+
return fmt.Errorf("authorized request requires authorizer")
|
|
145
|
+
}
|
|
146
|
+
if err := c.authorizer.Inject(ctx, req); err != nil {
|
|
147
|
+
_ = pr.Close()
|
|
148
|
+
_ = pw.Close()
|
|
149
|
+
return fmt.Errorf("inject auth headers: %w", err)
|
|
150
|
+
}
|
|
151
|
+
c.injectHeaders(req)
|
|
152
|
+
|
|
153
|
+
go func() {
|
|
154
|
+
err := writeMultipartBody(writer, fields, file)
|
|
155
|
+
closeErr := writer.Close()
|
|
156
|
+
if err != nil {
|
|
157
|
+
_ = pw.CloseWithError(err)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
_ = pw.CloseWithError(closeErr)
|
|
161
|
+
}()
|
|
162
|
+
|
|
163
|
+
return c.do(req, out)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func writeMultipartBody(writer *multipart.Writer, fields map[string]string, file MultipartFile) error {
|
|
167
|
+
for key, value := range fields {
|
|
168
|
+
if err := writer.WriteField(key, value); err != nil {
|
|
169
|
+
return err
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
f, err := os.Open(file.Path)
|
|
174
|
+
if err != nil {
|
|
175
|
+
return fmt.Errorf("open upload file: %w", err)
|
|
176
|
+
}
|
|
177
|
+
defer f.Close()
|
|
178
|
+
|
|
179
|
+
header := make(textproto.MIMEHeader)
|
|
180
|
+
header.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(file.FieldName), escapeQuotes(file.FileName)))
|
|
181
|
+
header.Set("Content-Type", file.ContentType)
|
|
182
|
+
part, err := writer.CreatePart(header)
|
|
183
|
+
if err != nil {
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
if _, err := io.Copy(part, f); err != nil {
|
|
187
|
+
return fmt.Errorf("write upload file: %w", err)
|
|
188
|
+
}
|
|
189
|
+
return nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var quotesReplacer = strings.NewReplacer("\\", "\\\\", `"`, `\"`)
|
|
193
|
+
|
|
194
|
+
func escapeQuotes(s string) string {
|
|
195
|
+
return quotesReplacer.Replace(s)
|
|
196
|
+
}
|
|
197
|
+
|
|
103
198
|
func (c *httpClient) injectHeaders(req *http.Request) {
|
|
104
199
|
for k, values := range c.headers {
|
|
105
200
|
for _, v := range values {
|
|
@@ -31,7 +31,7 @@ type DownloadResultError struct {
|
|
|
31
31
|
Error string `json:"error"`
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// DownloadResultResponse is the JSON envelope printed by `pippit-cli short-drama +download-result`.
|
|
34
|
+
// DownloadResultResponse is the JSON envelope printed by `pippit-tool-cli short-drama +download-result`.
|
|
35
35
|
type DownloadResultResponse struct {
|
|
36
36
|
OutputPath string `json:"output_path"`
|
|
37
37
|
Downloaded []string `json:"downloaded"`
|
|
@@ -24,7 +24,7 @@ type ThreadFile struct {
|
|
|
24
24
|
DownloadURL string `json:"download_url"`
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// ListThreadFileResult is the JSON envelope printed by `pippit-cli short-drama +list-thread-file`.
|
|
27
|
+
// ListThreadFileResult is the JSON envelope printed by `pippit-tool-cli short-drama +list-thread-file`.
|
|
28
28
|
type ListThreadFileResult struct {
|
|
29
29
|
Files []*ThreadFile `json:"files"`
|
|
30
30
|
Total int64 `json:"total"`
|
|
@@ -2,48 +2,103 @@ package common
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"fmt"
|
|
6
|
+
"mime"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
8
12
|
)
|
|
9
13
|
|
|
10
14
|
// UploadFileOptions is the stable command-facing request shape for file upload.
|
|
11
15
|
type UploadFileOptions struct {
|
|
12
16
|
Path string `json:"path"`
|
|
13
17
|
FileName string `json:"file_name"`
|
|
14
|
-
Purpose string `json:"purpose"`
|
|
15
|
-
Mock bool `json:"mock"`
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
// UploadFileResult is the JSON envelope printed by `pippit-cli short-drama +upload-file`.
|
|
20
|
+
// UploadFileResult is the JSON envelope printed by `pippit-tool-cli short-drama +upload-file`.
|
|
19
21
|
type UploadFileResult struct {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
AssetID string `json:"asset_id"`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type uploadFileResponse struct {
|
|
26
|
+
Ret string `json:"ret"`
|
|
27
|
+
Errmsg string `json:"errmsg"`
|
|
28
|
+
SvrTime int64 `json:"svr_time"`
|
|
29
|
+
LogID string `json:"log_id"`
|
|
30
|
+
Data struct {
|
|
31
|
+
PippitAssetID string `json:"pippit_asset_id"`
|
|
32
|
+
AssetID string `json:"asset_id"`
|
|
33
|
+
} `json:"data"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const uploadFileFieldName = "file"
|
|
37
|
+
|
|
38
|
+
var allowedUploadExtensions = map[string]bool{
|
|
39
|
+
".doc": true,
|
|
40
|
+
".txt": true,
|
|
25
41
|
}
|
|
26
42
|
|
|
27
|
-
func UploadFile(ctx context.Context, opts UploadFileOptions,
|
|
43
|
+
func UploadFile(ctx context.Context, opts UploadFileOptions, runner *Runner) (*UploadFileResult, error) {
|
|
28
44
|
if err := ctx.Err(); err != nil {
|
|
29
45
|
return nil, err
|
|
30
46
|
}
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
if runner == nil || runner.Client == nil {
|
|
48
|
+
return nil, fmt.Errorf("upload_file runner client is required")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
path := strings.TrimSpace(opts.Path)
|
|
52
|
+
if path == "" {
|
|
53
|
+
return nil, fmt.Errorf("upload file path is required")
|
|
54
|
+
}
|
|
55
|
+
info, err := os.Stat(path)
|
|
56
|
+
if err != nil {
|
|
57
|
+
return nil, fmt.Errorf("stat upload file: %w", err)
|
|
58
|
+
}
|
|
59
|
+
if info.IsDir() {
|
|
60
|
+
return nil, fmt.Errorf("upload path %q is a directory", path)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ext := strings.ToLower(filepath.Ext(path))
|
|
64
|
+
if !allowedUploadExtensions[ext] {
|
|
65
|
+
return nil, fmt.Errorf("unsupported file extension %q; only .doc and .txt uploads are supported", ext)
|
|
66
|
+
}
|
|
67
|
+
fileName := filepath.Base(path)
|
|
68
|
+
contentType := mime.TypeByExtension(ext)
|
|
69
|
+
|
|
70
|
+
var resp uploadFileResponse
|
|
71
|
+
if err := runner.Client.SendMultipartRequest(ctx, uploadFilePath(runner), nil, MultipartFile{
|
|
72
|
+
FieldName: uploadFileFieldName,
|
|
73
|
+
Path: path,
|
|
74
|
+
FileName: fileName,
|
|
75
|
+
ContentType: contentType,
|
|
76
|
+
}, &resp); err != nil {
|
|
77
|
+
return nil, fmt.Errorf("upload_file request failed: %w", err)
|
|
78
|
+
}
|
|
79
|
+
if resp.Ret != "0" {
|
|
80
|
+
if resp.Errmsg == "" {
|
|
81
|
+
resp.Errmsg = "unknown error"
|
|
82
|
+
}
|
|
83
|
+
return nil, fmt.Errorf("upload_file failed: ret=%s errmsg=%s", resp.Ret, resp.Errmsg)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
assetID := strings.TrimSpace(resp.Data.PippitAssetID)
|
|
87
|
+
if assetID == "" {
|
|
88
|
+
assetID = strings.TrimSpace(resp.Data.AssetID)
|
|
89
|
+
}
|
|
90
|
+
if assetID == "" {
|
|
91
|
+
return nil, fmt.Errorf("upload_file response missing pippit_asset_id")
|
|
92
|
+
}
|
|
93
|
+
|
|
33
94
|
return &UploadFileResult{
|
|
34
|
-
|
|
35
|
-
FileID: "file_mock_" + id,
|
|
36
|
-
Status: "uploaded",
|
|
37
|
-
Uploaded: time.Now().UTC().Format(time.RFC3339),
|
|
38
|
-
Request: opts,
|
|
95
|
+
AssetID: assetID,
|
|
39
96
|
}, nil
|
|
40
97
|
}
|
|
41
98
|
|
|
42
|
-
func
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if n > len(id) {
|
|
46
|
-
return id
|
|
99
|
+
func uploadFilePath(runner *Runner) string {
|
|
100
|
+
if runner != nil && runner.Config != nil && runner.Config.Paths != nil && runner.Config.Paths.UploadFile != "" {
|
|
101
|
+
return runner.Config.Paths.UploadFile
|
|
47
102
|
}
|
|
48
|
-
return
|
|
103
|
+
return config.UploadFilePath
|
|
49
104
|
}
|
|
@@ -22,7 +22,7 @@ type ThreadEntry struct {
|
|
|
22
22
|
Content []any `json:"content"`
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// GetThreadResult is the JSON envelope printed by `pippit-cli short-drama +get-thread`.
|
|
25
|
+
// GetThreadResult is the JSON envelope printed by `pippit-tool-cli short-drama +get-thread`.
|
|
26
26
|
type GetThreadResult struct {
|
|
27
27
|
Messages []*ThreadEntry `json:"messages"`
|
|
28
28
|
}
|
|
@@ -15,7 +15,7 @@ type SubmitRunOptions struct {
|
|
|
15
15
|
AssetIDs []string `json:"asset_ids,omitempty"`
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// SubmitRunResult is the JSON envelope printed by `pippit-cli short-drama +submit-run`.
|
|
18
|
+
// SubmitRunResult is the JSON envelope printed by `pippit-tool-cli short-drama +submit-run`.
|
|
19
19
|
type SubmitRunResult struct {
|
|
20
20
|
ThreadID string `json:"thread_id"`
|
|
21
21
|
RunID string `json:"run_id"`
|
package/package.json
CHANGED
|
@@ -17,12 +17,12 @@ function getGloballyInstalledVersion() {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function
|
|
20
|
+
function whichPippitToolCli() {
|
|
21
21
|
try {
|
|
22
22
|
const prefix = runSilent("npm", ["prefix", "-g"], { timeout: 15000 }).toString().trim();
|
|
23
23
|
const bin = isWindows
|
|
24
|
-
? path.join(prefix, "pippit-cli.cmd")
|
|
25
|
-
: path.join(prefix, "bin", "pippit-cli");
|
|
24
|
+
? path.join(prefix, "pippit-tool-cli.cmd")
|
|
25
|
+
: path.join(prefix, "bin", "pippit-tool-cli");
|
|
26
26
|
if (fs.existsSync(bin)) return bin;
|
|
27
27
|
} catch (_) {
|
|
28
28
|
// Fall back to PATH lookup.
|
|
@@ -30,7 +30,7 @@ function whichPippitCli() {
|
|
|
30
30
|
|
|
31
31
|
try {
|
|
32
32
|
const cmd = isWindows ? "where" : "which";
|
|
33
|
-
return runSilent(cmd, ["pippit-cli"]).toString().split("\n")[0].trim();
|
|
33
|
+
return runSilent(cmd, ["pippit-tool-cli"]).toString().split("\n")[0].trim();
|
|
34
34
|
} catch (_) {
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
@@ -39,7 +39,7 @@ function whichPippitCli() {
|
|
|
39
39
|
function main() {
|
|
40
40
|
const installed = getGloballyInstalledVersion();
|
|
41
41
|
if (installed) {
|
|
42
|
-
console.log(`Updating global pippit-cli (${installed}) via ${PKG}...`);
|
|
42
|
+
console.log(`Updating global pippit-tool-cli (${installed}) via ${PKG}...`);
|
|
43
43
|
} else {
|
|
44
44
|
console.log(`Installing ${PKG} globally...`);
|
|
45
45
|
}
|
|
@@ -48,7 +48,7 @@ function main() {
|
|
|
48
48
|
env: { ...process.env, PIPPIT_CLI_SKIP_SKILLS: "1" },
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
console.log("Installing pippit-cli skills...");
|
|
51
|
+
console.log("Installing pippit-tool-cli skills...");
|
|
52
52
|
try {
|
|
53
53
|
installGlobalPackageSkills(DEFAULT_PKG);
|
|
54
54
|
} catch (err) {
|
|
@@ -60,15 +60,15 @@ function main() {
|
|
|
60
60
|
installGlobalPackageSkills(DEFAULT_PKG);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const bin =
|
|
63
|
+
const bin = whichPippitToolCli();
|
|
64
64
|
if (!bin) {
|
|
65
|
-
console.error("pippit-cli was installed, but no global command was found in npm prefix.");
|
|
65
|
+
console.error("pippit-tool-cli was installed, but no global command was found in npm prefix.");
|
|
66
66
|
console.error("Check that npm's global bin directory is in PATH.");
|
|
67
67
|
process.exit(1);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
console.log(`pippit-cli is ready: ${bin}`);
|
|
71
|
-
console.log("Try: pippit-cli short-drama +submit-run --message \"写一个短剧开头\"");
|
|
70
|
+
console.log(`pippit-tool-cli is ready: ${bin}`);
|
|
71
|
+
console.log("Try: pippit-tool-cli short-drama +submit-run --message \"写一个短剧开头\"");
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
main();
|
package/scripts/install.js
CHANGED
|
@@ -9,7 +9,7 @@ const { installSkillsFromRoot } = require("./skills");
|
|
|
9
9
|
|
|
10
10
|
const VERSION = require("../package.json").version.replace(/-.*$/, "");
|
|
11
11
|
const REPO = "Pippit-dev/cli";
|
|
12
|
-
const NAME = "pippit-cli";
|
|
12
|
+
const NAME = "pippit-tool-cli";
|
|
13
13
|
const ROOT = path.join(__dirname, "..");
|
|
14
14
|
const BIN_DIR = path.join(ROOT, "bin");
|
|
15
15
|
|
|
@@ -121,7 +121,7 @@ function install() {
|
|
|
121
121
|
|
|
122
122
|
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
123
123
|
|
|
124
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pippit-cli-"));
|
|
124
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pippit-tool-cli-"));
|
|
125
125
|
const archivePath = path.join(tmpDir, archiveName);
|
|
126
126
|
try {
|
|
127
127
|
download(releaseURL, archivePath);
|
package/scripts/run.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require("path");
|
|
|
6
6
|
const { maybeWarnNewVersion } = require("./version-check");
|
|
7
7
|
|
|
8
8
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
9
|
-
const bin = path.join(__dirname, "..", "bin", "pippit-cli" + ext);
|
|
9
|
+
const bin = path.join(__dirname, "..", "bin", "pippit-tool-cli" + ext);
|
|
10
10
|
const args = process.argv.slice(2);
|
|
11
11
|
|
|
12
12
|
const oldBin = bin + ".old";
|
|
@@ -50,7 +50,7 @@ if (args[0] === "install") {
|
|
|
50
50
|
});
|
|
51
51
|
} catch (_) {
|
|
52
52
|
console.error(
|
|
53
|
-
"\nFailed to prepare pippit-cli binary.\n" +
|
|
53
|
+
"\nFailed to prepare pippit-tool-cli binary.\n" +
|
|
54
54
|
"Make sure Go is installed and available in PATH, then retry.\n"
|
|
55
55
|
);
|
|
56
56
|
process.exit(1);
|
package/scripts/version-check.js
CHANGED
|
@@ -85,7 +85,7 @@ function maybeWarnNewVersion(args = [], opts = {}) {
|
|
|
85
85
|
if (compareSemver(latest, current) <= 0) return;
|
|
86
86
|
|
|
87
87
|
const warn = opts.warn || console.error;
|
|
88
|
-
warn(`[pippit-cli] New version available: ${current} -> ${latest}. Run: pippit-cli update`);
|
|
88
|
+
warn(`[pippit-tool-cli] New version available: ${current} -> ${latest}. Run: pippit-tool-cli update`);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
module.exports = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pippit-short-drama-skill
|
|
3
|
-
description: 使用 pippit-cli 的短剧场景能力提交和查询短剧创作任务。覆盖短剧生成、续写、改写、剧情扩展、人物设定、分集草稿、世界观设定、会话文件获取、文件资源下载等创作场景。当用户要求创作短剧、写短剧剧本、续写故事、修改剧情、补充角色设定、查询短剧任务进展、获取短剧会话文件或下载短剧文件资源,或提到 pippit-cli short-drama / 小云雀短剧时触发。
|
|
3
|
+
description: 使用 pippit-tool-cli 的短剧场景能力提交和查询短剧创作任务。覆盖短剧生成、续写、改写、剧情扩展、人物设定、分集草稿、世界观设定、会话文件获取、文件资源下载等创作场景。当用户要求创作短剧、写短剧剧本、续写故事、修改剧情、补充角色设定、查询短剧任务进展、获取短剧会话文件或下载短剧文件资源,或提到 pippit-tool-cli short-drama / 小云雀短剧时触发。
|
|
4
4
|
user-invocable: true
|
|
5
5
|
metadata:
|
|
6
6
|
{
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
"emoji": "📖",
|
|
10
10
|
"requires":
|
|
11
11
|
{
|
|
12
|
-
"bins": ["pippit-cli"]
|
|
12
|
+
"bins": ["pippit-tool-cli"]
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -17,7 +17,7 @@ metadata:
|
|
|
17
17
|
|
|
18
18
|
# 小云雀短剧创作
|
|
19
19
|
|
|
20
|
-
通过 `pippit-cli short-drama` 命令提交短剧创作任务、上传参考文件,并行查询任务进展和会话产物文件,及时把重要资产下载到用户本地。
|
|
20
|
+
通过 `pippit-tool-cli short-drama` 命令提交短剧创作任务、上传参考文件,并行查询任务进展和会话产物文件,及时把重要资产下载到用户本地。
|
|
21
21
|
|
|
22
22
|
短剧场景面向剧情、人物、分集与画面化叙事创作,用户的原始需求通过 `--message` 发送给后端 Agent。后端 Agent 负责理解任务、编排流程和生成内容;用户侧 Agent 负责提交任务、并行查询进展与产物、主动下载重要资产并展示结果。
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ metadata:
|
|
|
33
33
|
|
|
34
34
|
## 前置要求
|
|
35
35
|
|
|
36
|
-
需要已安装 `pippit-cli`:
|
|
36
|
+
需要已安装 `pippit-tool-cli`:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
npx @pippit-dev/cli@latest install
|
|
@@ -45,20 +45,20 @@ npx @pippit-dev/cli@latest install
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
# 创建新会话并提交短剧创作需求
|
|
48
|
-
pippit-cli short-drama +submit-run --message "创作一个赛博朋克短剧开头"
|
|
48
|
+
pippit-tool-cli short-drama +submit-run --message "创作一个赛博朋克短剧开头"
|
|
49
49
|
|
|
50
50
|
# 向已有会话追加新的短剧需求
|
|
51
|
-
pippit-cli short-drama +submit-run --message "继续写下一集,重点描写主角的逃亡" --thread-id THREAD_ID
|
|
51
|
+
pippit-tool-cli short-drama +submit-run --message "继续写下一集,重点描写主角的逃亡" --thread-id THREAD_ID
|
|
52
52
|
|
|
53
53
|
# 携带已上传文件 ID 提交任务
|
|
54
|
-
pippit-cli short-drama +submit-run --message "参考这个大纲写第一集" --asset-ids ASSET_ID
|
|
54
|
+
pippit-tool-cli short-drama +submit-run --message "参考这个大纲写第一集" --asset-ids ASSET_ID
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
### 2. 查询短剧任务进展
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
60
|
# 查询会话消息列表
|
|
61
|
-
pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq 0
|
|
61
|
+
pippit-tool-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq 0
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
> `thread_id` 和 `run_id` 由 `+submit-run` 返回。`after-seq` 用于增量拉取消息,首次查询可使用 `0`。
|
|
@@ -68,14 +68,14 @@ pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after
|
|
|
68
68
|
当用户提供短剧大纲、人物设定、世界观设定、已有分集或剧本等本地文件路径时,可先上传文件。
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
pippit-cli short-drama +upload-file --path /path/to/outline.md
|
|
71
|
+
pippit-tool-cli short-drama +upload-file --path /path/to/outline.md
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
### 4. 获取会话文件
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
# 获取会话文件列表
|
|
78
|
-
pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 200
|
|
78
|
+
pippit-tool-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 200
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
`+list-thread-file` 返回的每个文件对象包含:
|
|
@@ -93,7 +93,7 @@ pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1 --pa
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
# 下载文件资源到指定文件路径
|
|
96
|
-
pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH
|
|
96
|
+
pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
`FILE_PATH` 必须直接使用 `+list-thread-file` 返回的完整 `file_path`,包含文件名,不要取父目录。`+download-result` 负责把会话产生的文件通过 URL 下载到该目标文件路径;如果目标文件已存在,跳过下载。
|
|
@@ -103,12 +103,12 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
|
|
|
103
103
|
### 场景 1:用户要求生成短剧内容
|
|
104
104
|
|
|
105
105
|
```
|
|
106
|
-
1. pippit-cli short-drama +submit-run --message "用户的原始短剧需求"
|
|
106
|
+
1. pippit-tool-cli short-drama +submit-run --message "用户的原始短剧需求"
|
|
107
107
|
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
108
108
|
2. 立即将 web_thread_link 展示给用户
|
|
109
109
|
3. 并行发起,二者同等重要:
|
|
110
|
-
a. pippit-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq SEQUENCE
|
|
111
|
-
b. pippit-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num PAGE_NUM --page-size 200
|
|
110
|
+
a. pippit-tool-cli short-drama +get-thread --thread-id THREAD_ID --run-id RUN_ID --after-seq SEQUENCE
|
|
111
|
+
b. pippit-tool-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num PAGE_NUM --page-size 200
|
|
112
112
|
4. 检查 `get-thread` 返回的 messages:
|
|
113
113
|
- 如果任务仍在进行中:展示过程消息,继续查询
|
|
114
114
|
- 如果后端 Agent 提出问题:展示问题,等待用户回复
|
|
@@ -130,9 +130,9 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
|
|
|
130
130
|
### 场景 2:用户提供参考文件要求创作
|
|
131
131
|
|
|
132
132
|
```
|
|
133
|
-
1. pippit-cli short-drama +upload-file --path /path/to/file
|
|
133
|
+
1. pippit-tool-cli short-drama +upload-file --path /path/to/file
|
|
134
134
|
→ 拿到 file_id
|
|
135
|
-
2. pippit-cli short-drama +submit-run --message "用户的原始短剧需求" --asset-ids file_id
|
|
135
|
+
2. pippit-tool-cli short-drama +submit-run --message "用户的原始短剧需求" --asset-ids file_id
|
|
136
136
|
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
137
137
|
3. 后续同场景 1 的并行查询、重要资产发现和文件下载流程
|
|
138
138
|
```
|
|
@@ -140,7 +140,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
|
|
|
140
140
|
### 场景 3:在已有短剧会话中续写或修改
|
|
141
141
|
|
|
142
142
|
```
|
|
143
|
-
1. pippit-cli short-drama +submit-run --message "用户的新需求" --thread-id THREAD_ID
|
|
143
|
+
1. pippit-tool-cli short-drama +submit-run --message "用户的新需求" --thread-id THREAD_ID
|
|
144
144
|
→ 拿到新的 run_id 和 web_thread_link
|
|
145
145
|
2. 继续按场景 1 展示进展、处理用户补充问题、获取新增会话文件列表,并及时下载新增重要资产
|
|
146
146
|
```
|
|
@@ -264,7 +264,7 @@ pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PA
|
|
|
264
264
|
|
|
265
265
|
对带 `download_url` 的重要资产调用下载工具,可并行。重要资产必须主动下载,不要等用户再次要求,也不要在调用下载工具前先检查本地文件是否存在。
|
|
266
266
|
|
|
267
|
-
1. 调用 `pippit-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH`。
|
|
267
|
+
1. 调用 `pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FILE_PATH`。
|
|
268
268
|
2. 下载完成后,向用户展示本地文件路径;如果某个文件下载失败,记录失败项并在后续轮询中重试,不阻塞已成功落盘的文件展示。
|
|
269
269
|
|
|
270
270
|
## 向用户展示内容
|