@pippit-dev/cli 1.0.46 → 1.0.48
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 +30 -4
- package/checksums.txt +6 -6
- package/cmd/canvas/canvas.go +8 -3
- package/cmd/canvas_upload_test.go +128 -0
- package/cmd/upload_file.go +10 -3
- package/cmd/upload_file_test.go +95 -9
- package/dist/checksums.txt +6 -0
- package/dist/xyq-canvas-command-runtime.cjs +13 -13
- package/dist/xyq-canvas-command-runtime.cjs.sha256 +1 -1
- package/internal/canvas/upload.go +32 -25
- package/internal/canvas/upload_url_test.go +72 -0
- package/internal/common/upload_file.go +53 -29
- package/package.json +2 -2
- package/scripts/canvas-command-guides.js +1 -1
- package/skills/xyq-nest-skill/SKILL.md +5 -4
- package/skills/xyq-nest-skill/agents/openai.yaml +1 -1
- package/skills/xyq-nest-skill/commands/canvas-assets.md +13 -2
- package/skills/xyq-nest-skill/commands/canvas.md +2 -1
- package/skills/xyq-nest-skill/commands/upload-file.md +40 -0
- package/skills/xyq-nest-skill/scripts/ensure-cli.js +1 -1
- package/skills/xyq-nest-skill/scripts/install.md +3 -1
- package/skills/xyq-nest-skill/workflows/canvas-edit.md +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
8e14b9a2c496491c2ee44da3ed950ac8301b67981dfaba3ab1e042cf8b937298 xyq-canvas-command-runtime.cjs
|
|
2
2
|
3b97217c41dfb3c298185c80b1e70c2b469f552450e53d00fc1dcf7098740ffb xyq-canvas-command-runtime.cjs.LEGAL.txt
|
|
@@ -29,6 +29,7 @@ var uploadContentTypeFallbacks = map[string]string{
|
|
|
29
29
|
|
|
30
30
|
type UploadOptions struct {
|
|
31
31
|
Path string
|
|
32
|
+
URL string
|
|
32
33
|
PollInterval time.Duration
|
|
33
34
|
WaitTimeout time.Duration
|
|
34
35
|
}
|
|
@@ -69,34 +70,40 @@ func Upload(ctx context.Context, opts UploadOptions, runner *common.Runner) (*Up
|
|
|
69
70
|
if opts.PollInterval < 0 || opts.WaitTimeout < 0 {
|
|
70
71
|
return nil, fmt.Errorf("canvas upload polling durations must not be negative")
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
return nil,
|
|
75
|
-
}
|
|
76
|
-
info, err := os.Stat(path)
|
|
77
|
-
if err != nil {
|
|
78
|
-
return nil, fmt.Errorf("inspect canvas upload file: %w", err)
|
|
79
|
-
}
|
|
80
|
-
if !info.Mode().IsRegular() {
|
|
81
|
-
return nil, fmt.Errorf("canvas upload path %q is not a regular file", path)
|
|
82
|
-
}
|
|
83
|
-
fileName := filepath.Base(path)
|
|
84
|
-
extension := strings.ToLower(filepath.Ext(fileName))
|
|
85
|
-
contentType := mime.TypeByExtension(extension)
|
|
86
|
-
if contentType == "" {
|
|
87
|
-
contentType = uploadContentTypeFallbacks[extension]
|
|
88
|
-
}
|
|
89
|
-
if contentType == "" {
|
|
90
|
-
contentType = "application/octet-stream"
|
|
73
|
+
source := common.UploadFileOptions{Path: opts.Path, URL: opts.URL}
|
|
74
|
+
if err := source.ValidateSource(); err != nil {
|
|
75
|
+
return nil, err
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
var envelope responseEnvelope
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
79
|
+
if sourceURL := strings.TrimSpace(opts.URL); sourceURL != "" {
|
|
80
|
+
err = client.SendRequest(ctx, UploadPath, map[string]string{"url": sourceURL}, &envelope)
|
|
81
|
+
} else {
|
|
82
|
+
path := strings.TrimSpace(opts.Path)
|
|
83
|
+
info, statErr := os.Stat(path)
|
|
84
|
+
if statErr != nil {
|
|
85
|
+
return nil, fmt.Errorf("inspect canvas upload file: %w", statErr)
|
|
86
|
+
}
|
|
87
|
+
if !info.Mode().IsRegular() {
|
|
88
|
+
return nil, fmt.Errorf("canvas upload path %q is not a regular file", path)
|
|
89
|
+
}
|
|
90
|
+
fileName := filepath.Base(path)
|
|
91
|
+
extension := strings.ToLower(filepath.Ext(fileName))
|
|
92
|
+
contentType := mime.TypeByExtension(extension)
|
|
93
|
+
if contentType == "" {
|
|
94
|
+
contentType = uploadContentTypeFallbacks[extension]
|
|
95
|
+
}
|
|
96
|
+
if contentType == "" {
|
|
97
|
+
contentType = "application/octet-stream"
|
|
98
|
+
}
|
|
99
|
+
err = client.SendMultipartRequest(ctx, UploadPath, nil, common.MultipartFile{
|
|
100
|
+
FieldName: "file",
|
|
101
|
+
Path: path,
|
|
102
|
+
FileName: fileName,
|
|
103
|
+
ContentType: contentType,
|
|
104
|
+
}, &envelope)
|
|
105
|
+
}
|
|
106
|
+
if err != nil {
|
|
100
107
|
return nil, fmt.Errorf("canvas upload request failed; outcome may be ambiguous, check assets before retrying: %w", err)
|
|
101
108
|
}
|
|
102
109
|
if err := envelope.validate("canvas upload"); err != nil {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package canvas
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
"time"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestUploadURLPreservesRecoveryAndErrors(t *testing.T) {
|
|
11
|
+
const uploaded = `{"ret":"0","log_id":"upload-log","data":{"asset_id":"source-id","pippit_asset_id":"asset-url"}}`
|
|
12
|
+
for _, tc := range []struct {
|
|
13
|
+
name string
|
|
14
|
+
upload string
|
|
15
|
+
query string
|
|
16
|
+
timeout bool
|
|
17
|
+
wantErr string
|
|
18
|
+
wantWarn string
|
|
19
|
+
wantQueries int
|
|
20
|
+
}{
|
|
21
|
+
{"query_timeout", uploaded, "", true, "", "not queryable", 1},
|
|
22
|
+
{"query_failure", uploaded, `{"ret":"1","errmsg":"denied","log_id":"query-log"}`, false, "", "query-log", 1},
|
|
23
|
+
{"media_not_ready", uploaded, `{"ret":"0","data":{"Assets":[{"PippitAssetID":"asset-url","AssetType":1,"Image":{}}]}}`, false, "", "cloud media ID is not ready", 1},
|
|
24
|
+
{"rejected", `{"ret":"1","errmsg":"rejected","log_id":"upload-log"}`, "", false, "upload-log", "", 0},
|
|
25
|
+
{"missing_id", `{"ret":"0","log_id":"upload-log","data":{"asset_id":"source-id"}}`, "", false, "upload-log", "", 0},
|
|
26
|
+
} {
|
|
27
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
28
|
+
uploads, queries := 0, 0
|
|
29
|
+
client := &fakeClient{send: func(ctx context.Context, path string, body, out any) error {
|
|
30
|
+
switch path {
|
|
31
|
+
case UploadPath:
|
|
32
|
+
uploads++
|
|
33
|
+
return decodeInto(out, tc.upload)
|
|
34
|
+
case QueryPath:
|
|
35
|
+
queries++
|
|
36
|
+
if tc.timeout {
|
|
37
|
+
<-ctx.Done()
|
|
38
|
+
return ctx.Err()
|
|
39
|
+
}
|
|
40
|
+
return decodeInto(out, tc.query)
|
|
41
|
+
default:
|
|
42
|
+
t.Fatalf("unexpected request: %s", path)
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
}}
|
|
46
|
+
waitTimeout := time.Second
|
|
47
|
+
if tc.timeout {
|
|
48
|
+
waitTimeout = time.Millisecond
|
|
49
|
+
}
|
|
50
|
+
result, err := Upload(context.Background(), UploadOptions{
|
|
51
|
+
URL: "https://assets.example.invalid/media", WaitTimeout: waitTimeout,
|
|
52
|
+
}, runnerWithClient(client))
|
|
53
|
+
if uploads != 1 || queries != tc.wantQueries {
|
|
54
|
+
t.Fatalf("uploads=%d queries=%d; import must not be replayed", uploads, queries)
|
|
55
|
+
}
|
|
56
|
+
if tc.wantErr != "" {
|
|
57
|
+
if err == nil || !strings.Contains(err.Error(), tc.wantErr) || result != nil {
|
|
58
|
+
t.Fatalf("result=%+v err=%v, want upload error with LogID", result, err)
|
|
59
|
+
}
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
if err != nil || result == nil {
|
|
63
|
+
t.Fatalf("result=%+v err=%v", result, err)
|
|
64
|
+
}
|
|
65
|
+
if result.State != StateProcessing || result.PippitAssetID != "asset-url" || result.AssetID != "source-id" ||
|
|
66
|
+
result.Locator.PippitAssetID != "asset-url" || result.MediaAssetID != "" ||
|
|
67
|
+
!strings.Contains(result.Warning, tc.wantWarn) {
|
|
68
|
+
t.Fatalf("result=%+v, want recoverable processing result", result)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"errors"
|
|
6
6
|
"fmt"
|
|
7
7
|
"mime"
|
|
8
|
+
"net/url"
|
|
8
9
|
"os"
|
|
9
10
|
"path/filepath"
|
|
10
11
|
"strings"
|
|
@@ -15,9 +16,26 @@ import (
|
|
|
15
16
|
// UploadFileOptions is the stable command-facing request shape for file upload.
|
|
16
17
|
type UploadFileOptions struct {
|
|
17
18
|
Path string `json:"path"`
|
|
19
|
+
URL string `json:"url,omitempty"`
|
|
18
20
|
FileName string `json:"file_name"`
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
// ValidateSource checks the source before either command-level file validation
|
|
24
|
+
// or direct callers can read a file or send a request.
|
|
25
|
+
func (opts UploadFileOptions) ValidateSource() error {
|
|
26
|
+
path, sourceURL := strings.TrimSpace(opts.Path), strings.TrimSpace(opts.URL)
|
|
27
|
+
if (path == "") == (sourceURL == "") {
|
|
28
|
+
return fmt.Errorf("--path 与 --url 必须且只能提供一个")
|
|
29
|
+
}
|
|
30
|
+
if sourceURL != "" {
|
|
31
|
+
u, err := url.Parse(sourceURL)
|
|
32
|
+
if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.Fragment != "" {
|
|
33
|
+
return fmt.Errorf("--url 必须是有效的 HTTPS 素材链接,不能包含用户凭据或 fragment")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
// UploadFileResult is the shared JSON output for media and short-drama uploads.
|
|
22
40
|
type UploadFileResult struct {
|
|
23
41
|
TeamID string `json:"team_id"`
|
|
@@ -56,39 +74,45 @@ func UploadFile(ctx context.Context, opts UploadFileOptions, runner *Runner) (*U
|
|
|
56
74
|
return nil, fmt.Errorf("上传文件运行器客户端缺失")
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return nil, fmt.Errorf("上传文件路径不能为空")
|
|
77
|
+
if err := opts.ValidateSource(); err != nil {
|
|
78
|
+
return nil, err
|
|
62
79
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
|
|
81
|
+
var resp uploadFileResponse
|
|
82
|
+
if sourceURL := strings.TrimSpace(opts.URL); sourceURL != "" {
|
|
83
|
+
if err := runner.Client.SendRequest(ctx, uploadFilePath(runner), map[string]string{"url": sourceURL}, &resp); err != nil {
|
|
84
|
+
return nil, fmt.Errorf("URL 导入请求失败,结果可能未确认,请检查资产后再重试: %w", err)
|
|
67
85
|
}
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
} else {
|
|
87
|
+
path := strings.TrimSpace(opts.Path)
|
|
88
|
+
info, err := os.Stat(path)
|
|
89
|
+
if err != nil {
|
|
90
|
+
if os.IsNotExist(err) {
|
|
91
|
+
return nil, fmt.Errorf("上传文件不存在: %s", path)
|
|
92
|
+
}
|
|
93
|
+
if os.IsPermission(err) {
|
|
94
|
+
return nil, fmt.Errorf("没有权限读取上传文件: %s", path)
|
|
95
|
+
}
|
|
96
|
+
return nil, fmt.Errorf("获取上传文件信息失败: %w", err)
|
|
97
|
+
}
|
|
98
|
+
if info.IsDir() {
|
|
99
|
+
return nil, fmt.Errorf("上传路径 %q 是目录,请指定文件", path)
|
|
70
100
|
}
|
|
71
|
-
return nil, fmt.Errorf("获取上传文件信息失败: %w", err)
|
|
72
|
-
}
|
|
73
|
-
if info.IsDir() {
|
|
74
|
-
return nil, fmt.Errorf("上传路径 %q 是目录,请指定文件", path)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
ext := strings.ToLower(filepath.Ext(path))
|
|
78
|
-
fileName := filepath.Base(path)
|
|
79
|
-
contentType := mime.TypeByExtension(ext)
|
|
80
|
-
if contentType == "" {
|
|
81
|
-
contentType = uploadFileContentTypeFallbacks[ext]
|
|
82
|
-
}
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
ext := strings.ToLower(filepath.Ext(path))
|
|
103
|
+
fileName := filepath.Base(path)
|
|
104
|
+
contentType := mime.TypeByExtension(ext)
|
|
105
|
+
if contentType == "" {
|
|
106
|
+
contentType = uploadFileContentTypeFallbacks[ext]
|
|
107
|
+
}
|
|
108
|
+
if err := runner.Client.SendMultipartRequest(ctx, uploadFilePath(runner), nil, MultipartFile{
|
|
109
|
+
FieldName: uploadFileFieldName,
|
|
110
|
+
Path: path,
|
|
111
|
+
FileName: fileName,
|
|
112
|
+
ContentType: contentType,
|
|
113
|
+
}, &resp); err != nil {
|
|
114
|
+
return nil, fmt.Errorf("上传文件请求失败: %w", err)
|
|
115
|
+
}
|
|
92
116
|
}
|
|
93
117
|
if resp.Ret != "0" {
|
|
94
118
|
if resp.Errmsg == "" {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pippit-dev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "Pippit CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pippit-tool-cli": "scripts/run.js"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"test:marketing-skill": "node scripts/marketing-skill.test.js",
|
|
11
11
|
"prepack": "node scripts/prepare-canvas-runtime.js",
|
|
12
12
|
"prepare:canvas-runtime": "node scripts/prepare-canvas-runtime.js",
|
|
13
|
-
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/marketing-skill.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && node scripts/canvas-media-identity.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
13
|
+
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/marketing-skill.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && node scripts/canvas-media-identity.test.js && node scripts/canvas-video-ratio.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
14
14
|
"verify:canvas-runtime": "node scripts/prepare-canvas-runtime.js --check-only",
|
|
15
15
|
"test:canvas-runtime": "node scripts/canvas-command-runtime.test.js",
|
|
16
16
|
"test:canvas-operations": "node scripts/canvas-command-canvas-operations.test.js && node scripts/canvas-command-scene3d-operations.test.js && node scripts/canvas-command-timeline-operations.test.js"
|
|
@@ -36,7 +36,7 @@ const GUIDES = [
|
|
|
36
36
|
"先用 list 确认 xyq.generation.update_prompt 可用,再 describe 获取参数。该命令接收 nodeId、prompt、可选 dryRun;nodeId 是当前 Canvas 的目标图片或视频节点 ID。get_snapshot 可查节点,get_asset 可读节点资产与 generation.references;用 describe 查看这两个查询命令的输入,避免混淆 Canvas ID、节点 ID 与媒体 Pippit ID。",
|
|
37
37
|
'<node-asset>NODE_ASSET_ID</node-asset> 引用已有节点。标签 ID 来自查询结果中的真实节点资产身份;不要用展示名称代替。CLI 复用 Web 标签匹配、节点连边和角色默认选材,保留标签属性。角色已有 data-reference-selection="IMAGE_ID,VOICE_ID" 属性使用逗号分隔选择 ID,需与角色已有资料匹配,不另传 selectedIds 或 reference 数组。',
|
|
38
38
|
"独立上传/素材库媒体可以没有节点。对已在目标 generation.references 中的素材,用 <pippit-asset-id>MEDIA_PIPPIT_ASSET_ID</pippit-asset-id>,ID 取自该草稿的 pippitAssetId。视频目标可使用已有图片、视频、音频候选;图片目标须符合自身支持的引用类型。无须新增 referenceSource 入参。",
|
|
39
|
-
"
|
|
39
|
+
"外链素材可用 canvas upload --url 导入并等待查询,复用其 pippit_asset_id;同一素材不重复导入。只有 upload-file 或 canvas upload 返回的素材 ID 还不够:本命令不会查询未知媒体并加入草稿。canvas get 只查询,不添加引用。找不到的普通标签返回 UNRESOLVED_PROMPT_REFERENCE,不写文档。需要画布参考节点时,先用 create_biz_node 的 initialData.pippitAssetId 关联已导入素材,再引用实际节点;upload-file.asset_id 是稳定 Pippit ID,canvas upload 则取 pippit_asset_id。相同媒体若匹配到已有 Canvas 源节点,沿用 Web 节点优先规则连边。新独立素材也可通过现有 Web 上传/素材库流程建立草稿。",
|
|
40
40
|
"dryRun:true 在隔离文档预演,不改变原文档。成功编辑保留其他 generation 参数及 caption/title/name;空 prompt 清空文本并保留现有引用与连边。本命令不触发生成。不要浅覆盖整个 generation。故事板有自己的脚本与引用流程,见 storyboard 指南。",
|
|
41
41
|
].join("\n\n"),
|
|
42
42
|
next_commands: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: xyq-skill
|
|
3
|
-
description: 使用小云雀 pippit-tool-cli
|
|
3
|
+
description: 使用小云雀 pippit-tool-cli 上传本地素材或通过 URL 导入素材,生成或编辑图片、生成视频、超分和擦字幕,查询结果并交付媒体;操作小云雀个人或团队 Canvas 画布、节点、布局、连线、角色/场景、生成提示词、3D 导演台与多轨草稿;查询积分及管理授权。用户提到小云雀、xyq 并需要这些操作时使用。
|
|
4
4
|
user-invocable: true
|
|
5
5
|
metadata:
|
|
6
6
|
{"openclaw": {"emoji": "💬", "requires": {"bins": ["node"]}}}
|
|
@@ -8,13 +8,13 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# 小云雀媒体创作与画布操作
|
|
10
10
|
|
|
11
|
-
通过 CLI
|
|
11
|
+
通过 CLI 完成素材上传或 URL 导入、生成、处理、结果下载与媒体交付。支持下表中的操作;不提供多轮会话续写或自动拆分剧本、分镜并编排成片的能力。复杂需求先确认能由所列命令完成的具体操作,不承诺未覆盖的流程。
|
|
12
12
|
|
|
13
13
|
## 开始执行
|
|
14
14
|
|
|
15
15
|
1. 画布任务运行 `node "{baseDir}/scripts/ensure-cli.js" --canvas`,其他任务运行 `node "{baseDir}/scripts/ensure-cli.js"`。保存返回的 `cli_path`;Canvas 还需保存 `canvas_entry`。文档中的 `pippit-tool-cli` 替换为带引号的 `cli_path`;画布语义命令按模块说明通过 Node 入口执行。同一任务复用,安装细节见 [安装说明](scripts/install.md)。
|
|
16
16
|
2. 按下表选择操作,只读取命中的命令文档。执行需要鉴权的操作前,按 [授权说明](commands/auth.md) 检查登录;有效登录可复用。
|
|
17
|
-
3. 生成、视频处理和查询已有媒体结果时,还必须读取 [异步结果与媒体交付](workflows/async-delivery.md)。画布任务使用 [画布查询、编辑与验证](workflows/canvas-edit.md)
|
|
17
|
+
3. 生成、视频处理和查询已有媒体结果时,还必须读取 [异步结果与媒体交付](workflows/async-delivery.md)。画布任务使用 [画布查询、编辑与验证](workflows/canvas-edit.md),不把画布编辑当作媒体生成。素材上传或导入保存返回的资产 ID 与空间;积分与授权操作直接返回结果,不套用生成任务轮询。
|
|
18
18
|
|
|
19
19
|
参数是否存在、命令语法以当前 `cli_path` 对应的 `--help` 为准;返回字段和成功判断按命令文档及真实响应核对。文档与实际不一致时说明差异,不猜参数、不绕过 CLI 自行调用 HTTP。
|
|
20
20
|
|
|
@@ -36,6 +36,7 @@ metadata:
|
|
|
36
36
|
| 用户意图 | CLI | 必读文档 |
|
|
37
37
|
| --- | --- | --- |
|
|
38
38
|
| 查看登录状态、登录、退出或切换账号 | `status` / `login` / `logout` | [授权](commands/auth.md) |
|
|
39
|
+
| 上传本地素材,或从 HTTPS URL 导入素材 | `upload-file`;画布流程可用 `canvas upload` | [素材上传与 URL 导入](commands/upload-file.md);[Canvas 上传与查询等待](commands/canvas-assets.md#url-import) |
|
|
39
40
|
| 创建或查询小云雀个人或团队画布,编辑节点、布局、连线、角色/场景、提示词、3D 或多轨草稿 | `canvas` | [Canvas 能力与命令发现](commands/canvas.md) |
|
|
40
41
|
| 生成图片,或基于参考图修改图片 | `generate-image` | [生图与图片编辑](commands/generate-image.md) |
|
|
41
42
|
| 查询当前可用的图片/视频模型、比例、分辨率及推理强度等参数配置 | `model list` / `model describe` | [模型发现](commands/model.md) |
|
|
@@ -57,7 +58,7 @@ metadata:
|
|
|
57
58
|
- 图片模型对用户只展示服务端返回的名称;列表、选择、详情与生成命令均使用完整名称,不解释或展示对应底层枚举。名称到提交标识由 CLI 解析,详见 [模型发现](commands/model.md)。
|
|
58
59
|
- 用户明确要求生成或处理,即可在该范围内执行;仅咨询用法、费用或方案时不提交。范围、必填信息或消耗 credits 的授权不明确时,先确认,不重复索要已给出的授权。
|
|
59
60
|
- 提问优先使用宿主实际提供且当前模式允许的工具:Codex 的 `request_user_input` 或 `request_user_input_async`,WorkBuddy 的 `ask_user_question`;不可用时用普通聊天。需要答案时等待答复。
|
|
60
|
-
-
|
|
61
|
+
- 生成和视频处理命令的素材参数接收本地文件路径,CLI 内部上传,单文件必须小于 500 MB(500000000 字节);不能把 URL 或资产 ID 填入这些路径参数。用户要求从外链导入素材时,使用 `upload-file --url`,或在画布流程中使用 `canvas upload --url` 并保留其查询等待与输出,由服务端读取源文件,无需先下载到本机;参数、限制及后续资产复用见 [上传说明](commands/upload-file.md)。
|
|
61
62
|
- `generate-image` / `generate-video` 上传素材失败时,向用户说明 CLI 返回的素材类型、类型内序号、文件名、原始原因和可用的 LogID,并说明尚未提交生成任务。序号按同类型参数的传入顺序从 1 开始,不是所有附件的混合序号;只有服务端明确返回审核拒绝时才说明审核未通过,不能把网络、权限等失败都归为审核问题。
|
|
62
63
|
- `generate-image` / `generate-video` 将所有参考素材按最多 10 个并发上传,等待本批上传结束后统一返回全部失败项;某个素材失败不会跳过其他素材。错误按图片、视频、音频分组及各自输入顺序列出,生成引用也保留输入顺序,不按上传完成先后排序。任一素材失败均不提交生成;向用户一次性列全失败素材,不只转述第一项。网络失败、取消或超时不代表审核通过。根据失败原因等待用户替换对应素材或解决问题,不自动删减素材、重传或重新提交生成。旧 CLI 如首错即停或未返回文件定位,须如实说明其结果范围,不凭图片内容猜测未检查素材的审核结论。
|
|
63
64
|
- 提交成功后立即展示真实 `web_thread_link`;未返回链接时如实说明,保留任务 ID。后续查询和下载失败不能触发重复生成。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| `canvas create` | `--title` 最多 50 字符;`--request-id` 可选;`--wait` 等初始化;可调 `--poll-interval`、`--timeout` | 保存 `project_id`、`canvas_asset_id`、`web_url`、`request_id`;检查 `state`、`warning` 和退出码 |
|
|
8
8
|
| `canvas get` | `--asset-id` 必填,可重复 | 返回 `requested_asset_ids`、`assets` 及可用的 `log_id`;核对实际返回资产 |
|
|
9
|
-
| `canvas upload` | `--path`
|
|
9
|
+
| `canvas upload` | `--path` 本地文件或 `--url` HTTPS 媒体直链,必须且只能提供一个;可调 `--poll-interval`、`--timeout` | 返回 `pippit_asset_id`、可用的 `media_asset_id`、`source_id`、`locator`、`state`、`warning`;素材可查询不代表已插入画布 |
|
|
10
10
|
| `canvas allocate` | `--count` 分配数量 | 返回 `asset_ids[]`;只预留 ID,不创建节点或资产 |
|
|
11
11
|
| `canvas apply` | `--project-id` 项目 ID;`--file` JSON 文件,默认 `-` 读 stdin | 一个 transaction,可含多条 patch;检查事务 ACK 和目标资产版本 |
|
|
12
12
|
|
|
@@ -14,15 +14,26 @@
|
|
|
14
14
|
pippit-tool-cli canvas create --title "产品方案" --wait
|
|
15
15
|
pippit-tool-cli canvas get --asset-id CANVAS_ASSET_ID
|
|
16
16
|
pippit-tool-cli canvas upload --path "/path/to/reference.png"
|
|
17
|
+
pippit-tool-cli canvas upload --url 'https://example.com/reference?signature=...' --team-id TEAM_ID
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
示例代表不同操作,不应因为读到示例就全部执行。创建、上传可能以退出码 0 返回 `creating` / `processing` 和 `warning`:保留已返回的 ID,稍后用 `canvas get` 回查资产可见性;不要重复创建或上传。画布初始化概览尚未完成时,回查根资产仅证明资产可见,不足以宣称所有初始化完成,应保留 `web_url` 和原始状态说明限制。`request_id` 用于追踪,不能假定跨故障重试严格幂等。
|
|
20
21
|
|
|
22
|
+
## URL Import
|
|
23
|
+
|
|
24
|
+
画布流程可使用 `canvas upload --url` 导入外链;只需要稳定资产 ID 时也可按 [上传说明](upload-file.md) 使用 `upload-file --url`,同一素材选择一个入口,不重复导入。导入和目标画布使用相同 `--team-id`。Canvas 上传保存 `pippit_asset_id` 和 `state`,通用上传保存 `team_id` 与 `asset_id`。
|
|
25
|
+
|
|
26
|
+
URL 导入前检查实际入口的 `--help` 含 `--url`;旧版缺少时按 [安装说明](../scripts/install.md) 更新并重新检查,仍不支持则说明缺口。服务端也须部署 URL 能力。仅支持服务端可访问且不重定向的 HTTPS 图片、视频、音频直链;当前服务端上限为 200 MiB,仍需格式校验和审核。不要求素材类型或文件名;保留原签名和查询参数,不把 URL 放进 `--path`。CLI 不读取、下载或缓存源素材。
|
|
27
|
+
|
|
28
|
+
`--poll-interval` 与 `--timeout` 只控制上传返回后的素材可查询等待,不是服务端导入的超时参数。等待失败或超时仍输出已有稳定 ID、`processing` 和 `warning`,后续用 `canvas get` 查询,不重放上传。`ready` 不表示已创建节点;批量需控制导入和查询并发。
|
|
29
|
+
|
|
30
|
+
这里的 `canvas upload.pippit_asset_id` 与 `upload-file.asset_id` 都是稳定的 Pippit 素材 ID。先用 `pippit-tool-cli canvas get --asset-id PIPPIT_ASSET_ID --team-id TEAM_ID` 查询真实素材,再在用户要求建节点时,将该 ID 填入语义 `create_biz_node.initialData.pippitAssetId`,其余输入按当前 schema 和目标画布确定。节点中的真实云媒体身份由现有 CLI 查询并填写,不把源 URL 写成节点 ID 或媒体 ID。写后按 [画布编辑流程](../workflows/canvas-edit.md) 回读;素材导入成功不代表节点已创建。
|
|
31
|
+
|
|
21
32
|
## 低层事务的使用边界
|
|
22
33
|
|
|
23
34
|
媒体节点优先通过语义 `create_biz_node` 创建,`initialData` 只传 `pippitAssetId`:CLI 查询素材并填写真实云媒体 `assetId`。用 `update_asset.contentPatch` 或 `update_node_data.patch` 替换素材时也可只传新的 `pippitAssetId`。显式填写的 `assetId` 必须与素材查询结果匹配,否则返回 `INVALID_MEDIA_IDENTITY` 并拒绝写入。
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
`canvas upload` 顶层旧字段 `asset_id` 是保留兼容的 workspace/source ID,不能当节点媒体 ID;它与 `upload-file.asset_id` 的含义不同。`media_asset_id` 来自查询结果对应的 `Image/Video/Audio.asset_id`;缺失时不能用 `source_id`、`pippit_asset_id` 或画布节点 ID 补位。`node_asset_refs[].pippit_asset_id` 使用画布节点资产 ID。
|
|
26
37
|
|
|
27
38
|
批量 `apply_mutations` 默认非原子执行:必须检查 `data.results` 中每一项的 `committed` / `aborted` 状态,保留已成功项,不重跑整批。需要任一失败时整批回滚,可显式传 `atomic: true`。
|
|
28
39
|
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
| 操作 | 入口或可定位的语义命令 | 要点 |
|
|
24
24
|
| --- | --- | --- |
|
|
25
|
-
|
|
|
25
|
+
| 创建画布、查资产、上传本地或 URL 素材、分配 ID、提交事务 | `canvas create/get/upload/allocate/apply` | 见 [原生资产命令](canvas-assets.md) |
|
|
26
|
+
| 导入外链素材并用于画布 | `canvas upload --url`(带查询等待)或 `upload-file --url`,再按需创建节点 | 见 [URL 导入与资产复用](canvas-assets.md#url-import) |
|
|
26
27
|
| 读取画布和资产 | `get_snapshot`、`get_asset`、`get_permissions` | 取得真实节点/资产 ID,不用名称猜 ID |
|
|
27
28
|
| 创建业务节点 | `create_biz_node` | 支持类型和初始字段用 `--node-kind` 查看;不要手拼业务节点结构 |
|
|
28
29
|
| 节点位置、大小、样式、布局、分组 | `move_nodes`、`resize_node`、`set_node_style`、`align_nodes`、`arrange_nodes`、`group_nodes` 等 | 先查询目标和归属;删除与重排仅限用户请求范围 |
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# upload-file:素材上传与 URL 导入
|
|
2
|
+
|
|
3
|
+
将本地文件或 HTTPS 媒体链接导入当前个人或团队空间,返回可复用的 Pippit 资产 ID。`--path` 与 `--url` 必须且只能提供一个。URL 模式只需用户提供链接,素材类型和内部文件名由服务端识别,不要求 `asset_type` 或 `file_name`。
|
|
4
|
+
|
|
5
|
+
先用 `pippit-tool-cli upload-file --help` 确认当前版本支持 `--url`;旧版缺少该参数时先更新到支持版本,再核对帮助。可用发布版本仍不支持时说明能力缺口,不反复升级。URL 导入还需要服务端部署对应能力,帮助检查通过不代表服务端已可用。
|
|
6
|
+
|
|
7
|
+
## Inputs
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pippit-tool-cli upload-file --path "/path/to/reference.png" --team-id TEAM_ID
|
|
11
|
+
pippit-tool-cli upload-file --url 'https://example.com/reference?signature=...' --team-id TEAM_ID
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
示例二选一执行。`TEAM_ID` 使用本次工作流已确定的空间,个人为 `0`,后续查资产或建节点仍使用相同空间。
|
|
15
|
+
|
|
16
|
+
- `--path`:本地普通文件,支持图片、视频及 `.mp3` / `.wav` 音频。CLI 要求小于 500 MB(500000000 字节);还须满足服务端的格式、大小和审核限制。
|
|
17
|
+
- `--url`:服务端可访问的 HTTPS 图片、视频或音频直链,可以是有效签名 URL,也可以没有文件后缀。服务端拒绝自动重定向,短链接或返回 3xx 的地址需换成最终素材直链。不能含 `user:password@host` 或 fragment;保留原始查询参数与签名,不擅自解码、删改或重签。shell 中为完整 URL 加单引号,避免 `&` 等字符被解释为命令。
|
|
18
|
+
- URL 模式由服务端探测内容并导入,CLI 不下载、读取或缓存源素材。当前服务端上限为 200 MiB,仍须通过格式校验和审核;不能仅凭 URL 后缀判断素材类型或可用性。
|
|
19
|
+
|
|
20
|
+
## Output
|
|
21
|
+
|
|
22
|
+
成功输出示例:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{"team_id":"0","asset_id":"PIPPIT_ASSET_ID"}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
保存这两个字符串。此处 `asset_id` 取自 API 的 `data.pippit_asset_id`,是稳定的 Pippit 资产 ID,不是云媒体 ID 或画布节点 ID。命令不返回 thread/run ID,也不提供 Canvas 的 `state`、`locator` 或就绪轮询;不要调用 `query-result` 等待导入。
|
|
29
|
+
|
|
30
|
+
用户只要求导入时,返回该资产 ID 与空间即可。用户还要求建立画布参考节点时,按 [Canvas 资产说明](canvas-assets.md#url-import) 和 [画布编辑流程](../workflows/canvas-edit.md) 查询素材,再用现有节点创建能力关联该 ID,写后回读。取得 ID 不代表节点已经创建或生成任务已经完成。
|
|
31
|
+
|
|
32
|
+
生成、超分与擦字幕命令的文件参数仍接收本地路径;不能将本命令返回的资产 ID 或源 URL 填入这些参数。
|
|
33
|
+
|
|
34
|
+
## Failures
|
|
35
|
+
|
|
36
|
+
双传或均未提供来源时,先修正参数。链接过期、源站拒绝访问、格式/大小不符或审核失败时,保留实际错误与可用的 LogID,按原因处理;不能把所有失败都归为审核拒绝。
|
|
37
|
+
|
|
38
|
+
收到资产 ID 后复用已有资产。超时或请求结果不确定时,保留已有 ID 和错误信息;有 ID 可在原空间查询,没有 ID 时不能猜测或声称导入成功,也不自动重放请求。批量调用控制并发,逐项保留成功 ID 与失败原因,不重跑已成功项。
|
|
39
|
+
|
|
40
|
+
URL 导入失败不自动改为本地下载再上传。画布流程也可以使用 [`canvas upload --url`](canvas-assets.md#url-import),它复用相同服务端导入接口,但另有素材查询等待和 `state` / `locator` 输出;稳定 ID 取 `pippit_asset_id`,不能套用本命令的 `asset_id` 含义。已导入素材直接复用,不为等待就绪再调用另一个上传入口。
|
|
@@ -7,7 +7,7 @@ const os = require("os");
|
|
|
7
7
|
const path = require("path");
|
|
8
8
|
|
|
9
9
|
const REQUIRED_COMMANDS = [
|
|
10
|
-
"status", "login", "logout", "query-result",
|
|
10
|
+
"status", "login", "logout", "query-result", "upload-file",
|
|
11
11
|
"space list", "space use",
|
|
12
12
|
"generate-image", "generate-video", "video-super-resolution",
|
|
13
13
|
"erase-video-subtitle", "get-credit-balance", "model list", "model describe",
|
|
@@ -12,7 +12,9 @@ node "{baseDir}/scripts/ensure-cli.js"
|
|
|
12
12
|
|
|
13
13
|
## 查找与复用
|
|
14
14
|
|
|
15
|
-
脚本依次检查 PATH 中的 CLI 和自身缓存,验证版本及本 Skill 使用命令的 `--help`;命令集合维护在脚本的 `REQUIRED_COMMANDS`,包含 `model list`、`model describe`、`space list` 和 `space use`。同时从 `generate-image --help` 检查各业务命令共用的全局 `--team-id` 参数;缺少该参数时沿用缺少必需命令的升级流程。帮助检查不调用生成服务,也不需要凭据,不证明账号权限或服务端运行状态。
|
|
15
|
+
脚本依次检查 PATH 中的 CLI 和自身缓存,验证版本及本 Skill 使用命令的 `--help`;命令集合维护在脚本的 `REQUIRED_COMMANDS`,包含 `upload-file`、`model list`、`model describe`、`space list` 和 `space use`。同时从 `generate-image --help` 检查各业务命令共用的全局 `--team-id` 参数;缺少该参数时沿用缺少必需命令的升级流程。帮助检查不调用生成服务,也不需要凭据,不证明账号权限或服务端运行状态。
|
|
16
|
+
|
|
17
|
+
可选参数按实际任务检查帮助:URL 导入前核对所选入口 `upload-file --help` 或 `canvas upload --help` 含 `--url`,缺少时先更新到支持版本。安装脚本只检查必需命令,不因未使用的 URL 参数自动升级,也不证明服务端已部署 URL 导入能力。
|
|
16
18
|
|
|
17
19
|
命令齐全则直接复用,不检查最新版本;不存在或缺少必需命令时,获取 `@pippit-dev/cli@latest`。PATH 旧版本缺少命令但缓存完整时复用缓存,避免每次升级。版本命令不能运行或检查超时则报告运行错误。
|
|
18
20
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
1. **准备入口**:执行 Canvas 安装检查,保存 `cli_path` 和 `canvas_entry`,按授权文档确认身份,并按 Space Selection 确定 TeamID。后续独立命令均显式使用同一 `--team-id TEAM_ID`,个人为 `0`;已有画布沿用原空间。只查命令目录和离线指南无需登录;查询真实画布、执行编辑需要授权。
|
|
6
6
|
2. **确定目标**:已有画布使用真实 `canvas_asset_id` 作为语义命令的 `--canvas-id`。用户提供的信息无法唯一定位时先补齐,不创建替代画布。新画布仅在用户要求创建时使用原生 `canvas create`,检查其返回状态。
|
|
7
7
|
3. **发现与查询**:`list → describe/schema` 定位操作。用 `get_snapshot` 查节点,用 `get_asset` 查具体资产;3D/多轨再查询其独立文档。`run` 是统一执行入口,其中 query/get 类操作是读取,不等于所有 run 都是写入。
|
|
8
|
-
4. **准备输入**:使用查询所得的节点、子对象和资产 ID
|
|
8
|
+
4. **准备输入**:使用查询所得的节点、子对象和资产 ID。外链素材通过 `canvas upload --url` 导入并保存 `pippit_asset_id`、`state` 与可能的 `warning`;也可复用 `upload-file --url` 返回的稳定 `asset_id`,再按 [URL 导入与资产复用](../commands/canvas-assets.md#url-import) 创建所需节点;已有资产直接复用,不重复导入。多轨 `expectedRevision` 来自最新查询。复杂输入写入 JSON 文件后传 `--file`;不要同时传 `--file` 和 `--input`。只操作用户指定范围。
|
|
9
9
|
5. **预演与写入**:当前 schema 支持 `dryRun` 时可先预演,再在已有授权范围内执行真实修改。不能给所有命令强加此字段:领域命令的 dryRun 验证具体编辑,通用 `apply_mutations.dryRun` 不能替代领域预演。删除、恢复等操作仅按用户明确要求执行。
|
|
10
10
|
6. **验证结果**:检查退出码与 JSON 的 `ok`、错误信息,`ok=false` 即失败。`dryRun=true` 成功不代表落盘;实际写入成功后,用对应查询回读目标字段及关联关系,确认没有误改其他对象。
|
|
11
11
|
7. **反馈交付**:说明已验证的画布变化,保留已有/返回的画布链接及目标 ID。只完成文档编辑时不要声称图片/视频已生成;确有媒体文件待交付时遵循 [媒体交付标准](async-delivery.md)。
|