gaoding-cli 1.0.0-alpha.8 → 1.0.0-alpha.9
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/CHANGELOG.md +18 -0
- package/README.md +47 -64
- package/dist/bin/gd-cli.js +2 -2
- package/dist/src/api/app-runner.d.ts +1 -4
- package/dist/src/api/app-runner.js +10 -42
- package/dist/src/api/dam-client.d.ts +0 -7
- package/dist/src/api/dam-client.js +0 -17
- package/dist/src/commands/auth.js +5 -41
- package/dist/src/commands/creative.js +37 -45
- package/dist/src/commands/dam.js +248 -110
- package/dist/src/commands/org.js +17 -39
- package/dist/src/commands/skills.js +24 -114
- package/dist/src/commands/update.js +10 -85
- package/dist/src/commands/usage.js +4 -17
- package/dist/src/commands/workflows.d.ts +1 -1
- package/dist/src/commands/workflows.js +283 -199
- package/dist/src/core/output/envelope.d.ts +1 -0
- package/dist/src/core/output/envelope.js +76 -6
- package/dist/src/creative/contract.d.ts +28 -80
- package/dist/src/creative/contract.js +23 -44
- package/dist/src/creative/protocol.js +6 -6
- package/dist/src/io.d.ts +0 -2
- package/dist/src/io.js +0 -8
- package/dist/src/middleware/error-handler.d.ts +7 -1
- package/dist/src/middleware/error-handler.js +59 -54
- package/dist/src/program.d.ts +1 -0
- package/dist/src/program.js +138 -41
- package/dist/src/registry/agent-contracts.d.ts +14 -26
- package/dist/src/registry/agent-contracts.js +214 -64
- package/dist/src/utils/capability-presenter.js +6 -29
- package/dist/src/utils/config.d.ts +2 -2
- package/dist/src/utils/config.js +1 -1
- package/dist/src/utils/sensitive-path.js +1 -4
- package/package.json +2 -1
- package/skills/gd-cli/SKILL.md +6 -6
- package/skills/gd-cli/references/auth.md +3 -10
- package/skills/gd-cli/references/creative.md +11 -11
- package/skills/gd-cli/references/entitlement.md +2 -2
- package/skills/gd-cli/references/errors.md +4 -4
- package/skills/gd-cli/references/org.md +3 -3
- package/skills/gd-cli/references/sop.md +5 -5
- package/skills/gd-cli/references/update.md +4 -3
- package/skills/gd-cli/references/workflows.md +6 -5
- package/dist/src/utils/list-output-format.d.ts +0 -10
- package/dist/src/utils/list-output-format.js +0 -34
|
@@ -2,69 +2,69 @@ import chalk from "chalk";
|
|
|
2
2
|
import { CreativeClient } from "../api/creative-client.js";
|
|
3
3
|
import { CliUsageError } from "../core/output/cli-error.js";
|
|
4
4
|
import { formatOutput } from "../middleware/output.js";
|
|
5
|
-
import {
|
|
5
|
+
import { jsonFailureEnvelopeFor, setJsonFailureProjector, } from "../middleware/error-handler.js";
|
|
6
6
|
import { getCreativeExamples, getCreativeSchema, validateCreativeInput, } from "../creative/contract.js";
|
|
7
|
-
import { resolveInvokeOutputFormat, resolveListOutputFormat } from "../utils/list-output-format.js";
|
|
8
7
|
import { readJsonInput } from "../utils/input-json.js";
|
|
9
|
-
import { setStaticHelp } from "../utils/static-help.js";
|
|
10
8
|
import { setAuthRequirement } from "../middleware/auth.js";
|
|
9
|
+
const CREATIVE_FAILURE_META = {
|
|
10
|
+
command: "creative run",
|
|
11
|
+
schema_version: "creative/v1",
|
|
12
|
+
};
|
|
11
13
|
export function registerCreativeCommand(program, deps = {}) {
|
|
12
14
|
const creative = program
|
|
13
15
|
.command("creative")
|
|
14
16
|
.usage("<command> [options]")
|
|
15
|
-
.description("通过稿定 Creative 完成图片、视频和文本创作")
|
|
16
|
-
.configureHelp({ showGlobalOptions: true });
|
|
17
|
-
setStaticHelp(creative, CREATIVE_HELP);
|
|
17
|
+
.description("通过稿定 Creative 完成图片、视频和文本创作");
|
|
18
18
|
const run = creative
|
|
19
19
|
.command("run")
|
|
20
20
|
.usage("[options]")
|
|
21
21
|
.description("发起创作、继续已有创作或回答追问")
|
|
22
22
|
.option("--prompt <text>", "详细描述期望的最终结果")
|
|
23
23
|
.option("--input <path>", "从 JSON 文件读取完整输入;传 - 表示读取 stdin")
|
|
24
|
+
.option("--json", "输出机器可读 JSON")
|
|
24
25
|
.action(async (options, command) => {
|
|
25
26
|
await runCreative(options, command, deps);
|
|
26
27
|
});
|
|
28
|
+
setJsonFailureProjector(run, creativeFailureEnvelope);
|
|
27
29
|
setAuthRequirement(run, "current-org");
|
|
28
30
|
const schema = creative
|
|
29
31
|
.command("schema")
|
|
30
32
|
.description("查看 Creative 输入与输出 JSON Schema")
|
|
33
|
+
.option("--json", "输出完整 JSON Schema")
|
|
31
34
|
.action((_options, command) => {
|
|
32
35
|
const payload = getCreativeSchema();
|
|
33
|
-
|
|
34
|
-
if (format === "json") {
|
|
36
|
+
if (command.opts().json === true) {
|
|
35
37
|
formatOutput(payload, "json");
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
|
-
console.log("Creative
|
|
39
|
-
console.log(
|
|
40
|
-
console.log("
|
|
41
|
-
console.log(JSON.stringify(payload.output_schema, null, 2));
|
|
40
|
+
console.log("Creative 输入: prompt、media、parameters、conversation_id、question_message_id、answers");
|
|
41
|
+
console.log("Creative 输出: message、outputs,以及连续创作或追问所需的会话信息");
|
|
42
|
+
console.log("完整 JSON Schema: gd-cli creative schema --json");
|
|
42
43
|
});
|
|
43
44
|
setAuthRequirement(schema, "none");
|
|
44
45
|
const examples = creative
|
|
45
46
|
.command("examples")
|
|
46
47
|
.description("查看 Creative 调用示例")
|
|
47
|
-
.action((
|
|
48
|
+
.action(() => {
|
|
48
49
|
const payload = getCreativeExamples();
|
|
49
|
-
const format = resolveListOutputFormat(command);
|
|
50
|
-
if (format === "json") {
|
|
51
|
-
formatOutput(payload, "json");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
50
|
for (const [index, example] of payload.examples.entries()) {
|
|
55
51
|
console.log(`${index + 1}. ${example.description} (${example.name})`);
|
|
56
|
-
console.log(
|
|
52
|
+
console.log(` ${example.command}`);
|
|
53
|
+
console.log(" 请求 JSON:");
|
|
54
|
+
console.log(indentJson(JSON.stringify(example.input, null, 2), " "));
|
|
57
55
|
console.log("");
|
|
58
56
|
}
|
|
59
57
|
});
|
|
60
58
|
setAuthRequirement(examples, "none");
|
|
61
59
|
}
|
|
60
|
+
function indentJson(value, prefix) {
|
|
61
|
+
return value
|
|
62
|
+
.split("\n")
|
|
63
|
+
.map((line) => `${prefix}${line}`)
|
|
64
|
+
.join("\n");
|
|
65
|
+
}
|
|
62
66
|
export async function runCreative(options, command, deps = {}) {
|
|
63
|
-
const format =
|
|
64
|
-
const meta = {
|
|
65
|
-
command: "creative run",
|
|
66
|
-
schema_version: "creative/v1",
|
|
67
|
-
};
|
|
67
|
+
const format = command.opts().json === true ? "json" : "human";
|
|
68
68
|
try {
|
|
69
69
|
const input = await resolveCreativeInput(options, deps.readInput ?? readJsonInput);
|
|
70
70
|
const client = deps.createClient?.() ?? new CreativeClient();
|
|
@@ -73,17 +73,12 @@ export async function runCreative(options, command, deps = {}) {
|
|
|
73
73
|
status: "completed",
|
|
74
74
|
data: result,
|
|
75
75
|
error: null,
|
|
76
|
-
meta,
|
|
76
|
+
meta: CREATIVE_FAILURE_META,
|
|
77
77
|
};
|
|
78
78
|
renderCreativeEnvelope(envelope, format);
|
|
79
79
|
}
|
|
80
80
|
catch (error) {
|
|
81
|
-
const envelope =
|
|
82
|
-
status: "failed",
|
|
83
|
-
data: null,
|
|
84
|
-
error: toCreativeError(error),
|
|
85
|
-
meta,
|
|
86
|
-
};
|
|
81
|
+
const envelope = jsonFailureEnvelopeFor(command, error);
|
|
87
82
|
if (format === "json") {
|
|
88
83
|
formatOutput(envelope, "json");
|
|
89
84
|
}
|
|
@@ -140,8 +135,8 @@ function printCreativeResult(result, format) {
|
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
console.log(`\n conversation_id: ${result.conversation_id}`);
|
|
143
|
-
if (result.
|
|
144
|
-
console.log(`
|
|
138
|
+
if (result.question_message_id) {
|
|
139
|
+
console.log(` question_message_id: ${result.question_message_id}`);
|
|
145
140
|
}
|
|
146
141
|
}
|
|
147
142
|
}
|
|
@@ -164,8 +159,15 @@ function printCreativeError(error) {
|
|
|
164
159
|
if (error.hint)
|
|
165
160
|
console.error(` 说明: ${error.hint}`);
|
|
166
161
|
}
|
|
167
|
-
function
|
|
168
|
-
|
|
162
|
+
function creativeFailureEnvelope(error) {
|
|
163
|
+
return {
|
|
164
|
+
status: "failed",
|
|
165
|
+
data: null,
|
|
166
|
+
error: toCreativeError(error),
|
|
167
|
+
meta: CREATIVE_FAILURE_META,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function toCreativeError(body) {
|
|
169
171
|
return {
|
|
170
172
|
code: body.code,
|
|
171
173
|
type: body.type,
|
|
@@ -174,13 +176,3 @@ function toCreativeError(error) {
|
|
|
174
176
|
...(body.retryable !== undefined ? { retryable: body.retryable } : {}),
|
|
175
177
|
};
|
|
176
178
|
}
|
|
177
|
-
const CREATIVE_HELP = `
|
|
178
|
-
Usage: gd-cli creative <command> [options]
|
|
179
|
-
|
|
180
|
-
通过稿定 Creative 完成图片、视频和文本创作
|
|
181
|
-
|
|
182
|
-
常用命令:
|
|
183
|
-
run 发起创作、继续已有创作或回答追问
|
|
184
|
-
schema 查看输入与输出 JSON Schema
|
|
185
|
-
examples 查看调用示例
|
|
186
|
-
`;
|
package/dist/src/commands/dam.js
CHANGED
|
@@ -4,31 +4,27 @@ import { setAuthRequirement } from "../middleware/auth.js";
|
|
|
4
4
|
import { withErrorHandler } from "../middleware/error-handler.js";
|
|
5
5
|
import { DamClient } from "../api/dam-client.js";
|
|
6
6
|
import { formatOutput } from "../middleware/output.js";
|
|
7
|
-
import { resolveInvokeOutputFormat, resolveListOutputFormat, } from "../utils/list-output-format.js";
|
|
8
7
|
import { createSpinner } from "../utils/spinner.js";
|
|
9
|
-
import { emitJson, wrapEnvelope
|
|
10
|
-
import {
|
|
8
|
+
import { emitJson, wrapEnvelope } from "../core/output/envelope.js";
|
|
9
|
+
import { CliUsageError } from "../core/output/cli-error.js";
|
|
10
|
+
import { isSensitiveKey } from "../utils/redact.js";
|
|
11
|
+
import { assertSafeHttpUrl, isBlockedHost } from "../utils/url-safety.js";
|
|
12
|
+
const DAM_ASSET_TYPES = new Set(["image", "video", "audio", "font", "template", "file"]);
|
|
11
13
|
export function registerDamCommand(program) {
|
|
12
14
|
const dam = program
|
|
13
15
|
.command("dam")
|
|
14
16
|
.usage("<command> [options]")
|
|
15
|
-
.description("DAM 素材管理")
|
|
16
|
-
.configureHelp({ showGlobalOptions: true });
|
|
17
|
-
setStaticHelp(dam, DAM_HELP);
|
|
17
|
+
.description("DAM 素材管理");
|
|
18
18
|
const upload = dam
|
|
19
19
|
.command("upload <file>")
|
|
20
20
|
.description("上传本地文件并提交 DAM 入库,返回 DAM asset_id 与 CDN URL")
|
|
21
21
|
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
22
22
|
.option("--name <name>", "指定素材名称")
|
|
23
23
|
.option("--folder-id <id>", "DAM 文件夹 ID,MVP 默认 0", "0")
|
|
24
|
-
.option("--folder <name>", "文件夹名称")
|
|
25
|
-
.option("--tags <list>", "标签,逗号分隔")
|
|
26
24
|
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
27
|
-
.option("--storage-only", "只上传对象存储,不提交 DAM 入库", false)
|
|
28
25
|
.option("--allow-sensitive-path", "确认上传默认受保护的本地路径", false)
|
|
29
26
|
.option("--wait-analysis", "等待 DAM 后端分析完成并返回封面", false)
|
|
30
|
-
.option("--
|
|
31
|
-
.option("--analysis-interval <ms>", "分析轮询间隔(毫秒)", parsePositiveInteger, 3000)
|
|
27
|
+
.option("--json", "输出机器可读 JSON")
|
|
32
28
|
.action(withErrorHandler(async (file, options, command) => {
|
|
33
29
|
const opts = options;
|
|
34
30
|
const cmd = command;
|
|
@@ -41,56 +37,37 @@ export function registerDamCommand(program) {
|
|
|
41
37
|
spinner.start();
|
|
42
38
|
const damClient = new DamClient();
|
|
43
39
|
const uploaded = await damClient.uploadLocalAsset(p, {
|
|
44
|
-
register:
|
|
40
|
+
register: true,
|
|
45
41
|
allowSensitivePath: opts.allowSensitivePath === true,
|
|
46
42
|
repositoryId: optionalString(opts.repositoryId),
|
|
47
43
|
folderId: optionalString(opts.folderId) ?? "0",
|
|
48
44
|
title: optionalString(opts.name),
|
|
49
45
|
tagIds: splitList(opts.tagIds),
|
|
50
|
-
waitAnalysis: Boolean(opts.waitAnalysis)
|
|
51
|
-
analysisTimeoutMs: numberOption(opts.analysisTimeout),
|
|
52
|
-
analysisIntervalMs: numberOption(opts.analysisInterval),
|
|
46
|
+
waitAnalysis: Boolean(opts.waitAnalysis),
|
|
53
47
|
});
|
|
54
48
|
if (process.stderr.isTTY)
|
|
55
49
|
spinner.succeed("上传完成");
|
|
56
|
-
const asset = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
type: uploaded.mime_type,
|
|
50
|
+
const asset = projectPublicDamAsset({
|
|
51
|
+
...uploaded,
|
|
52
|
+
asset_id: uploaded.id,
|
|
60
53
|
filename: basename(p),
|
|
61
|
-
|
|
62
|
-
cover_url: uploaded.cover_url,
|
|
63
|
-
};
|
|
54
|
+
});
|
|
64
55
|
const payload = {
|
|
65
56
|
status: "completed",
|
|
66
57
|
asset,
|
|
67
58
|
outputs: {
|
|
68
|
-
asset_id:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
url: uploaded.url,
|
|
74
|
-
cover_url: uploaded.cover_url,
|
|
75
|
-
cover: uploaded.cover,
|
|
76
|
-
asset_info: uploaded.asset_info,
|
|
77
|
-
analysis_status: uploaded.analysis_status,
|
|
78
|
-
analysis_fail_msg: uploaded.analysis_fail_msg,
|
|
79
|
-
analysis_timed_out: uploaded.analysis_timed_out,
|
|
80
|
-
filename: uploaded.filename,
|
|
81
|
-
format: uploaded.format,
|
|
82
|
-
mime_type: uploaded.mime_type,
|
|
83
|
-
object_name: uploaded.object_name,
|
|
84
|
-
auth_key: uploaded.auth_key,
|
|
85
|
-
registration: uploaded.registration,
|
|
59
|
+
asset_id: asset.asset_id,
|
|
60
|
+
url: asset.url,
|
|
61
|
+
cover_url: asset.cover_url,
|
|
62
|
+
filename: asset.filename,
|
|
63
|
+
type: asset.mime_type ?? asset.type,
|
|
86
64
|
},
|
|
87
65
|
};
|
|
88
|
-
|
|
89
|
-
if (shouldEmitEnvelope(format)) {
|
|
66
|
+
if (isJson(cmd)) {
|
|
90
67
|
emitJson(wrapEnvelope(payload, { command: "dam upload" }));
|
|
91
68
|
}
|
|
92
69
|
else {
|
|
93
|
-
formatOutput(payload,
|
|
70
|
+
formatOutput(payload, "human");
|
|
94
71
|
}
|
|
95
72
|
}));
|
|
96
73
|
setAuthRequirement(upload, "current-org");
|
|
@@ -100,15 +77,13 @@ export function registerDamCommand(program) {
|
|
|
100
77
|
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
101
78
|
.option("--all-repositories", "搜索当前组织下所有资源库", false)
|
|
102
79
|
.option("--folder-id <id>", "限定文件夹")
|
|
103
|
-
.option("--include-child-folder", "包含子文件夹", true)
|
|
104
80
|
.option("--no-include-child-folder", "不包含子文件夹")
|
|
105
81
|
.option("--type <type>", "image/video/audio/font/template/file")
|
|
106
82
|
.option("--asset-format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
|
|
107
83
|
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
108
84
|
.option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
|
|
109
|
-
.option("--page <n>", "页码", parsePositiveInteger, 1)
|
|
110
85
|
.option("--cursor <id>", "分页 cursor/query_id")
|
|
111
|
-
.option("--
|
|
86
|
+
.option("--json", "输出机器可读 JSON")
|
|
112
87
|
.action(withErrorHandler(async (options, command) => {
|
|
113
88
|
const opts = options;
|
|
114
89
|
const cmd = command;
|
|
@@ -117,7 +92,7 @@ export function registerDamCommand(program) {
|
|
|
117
92
|
...buildDamSearchOptions(opts),
|
|
118
93
|
allowEmptyKeyword: true,
|
|
119
94
|
});
|
|
120
|
-
outputDamSearch(data,
|
|
95
|
+
outputDamSearch(data, isJson(cmd), "list");
|
|
121
96
|
}));
|
|
122
97
|
setAuthRequirement(list, "current-org");
|
|
123
98
|
const search = dam
|
|
@@ -126,37 +101,39 @@ export function registerDamCommand(program) {
|
|
|
126
101
|
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
127
102
|
.option("--all-repositories", "搜索当前组织下所有资源库", false)
|
|
128
103
|
.option("--folder-id <id>", "限定文件夹")
|
|
129
|
-
.option("--include-child-folder", "包含子文件夹", true)
|
|
130
104
|
.option("--no-include-child-folder", "不包含子文件夹")
|
|
131
105
|
.option("--type <type>", "image/video/audio/font/template/file")
|
|
132
106
|
.option("--asset-format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
|
|
133
107
|
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
134
108
|
.option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
|
|
135
|
-
.option("--page <n>", "页码", parsePositiveInteger, 1)
|
|
136
109
|
.option("--cursor <id>", "分页 cursor/query_id")
|
|
137
|
-
.option("--
|
|
110
|
+
.option("--json", "输出机器可读 JSON")
|
|
138
111
|
.action(withErrorHandler(async (keyword, options, command) => {
|
|
139
112
|
const opts = options;
|
|
140
113
|
const cmd = command;
|
|
141
114
|
const damClient = new DamClient();
|
|
142
115
|
const data = await damClient.searchAssets(String(keyword), buildDamSearchOptions(opts));
|
|
143
|
-
outputDamSearch(data,
|
|
116
|
+
outputDamSearch(data, isJson(cmd), "search");
|
|
144
117
|
}));
|
|
145
118
|
setAuthRequirement(search, "current-org");
|
|
146
119
|
const get = dam
|
|
147
|
-
.command("get <id
|
|
148
|
-
.description("按 asset_id
|
|
149
|
-
.
|
|
120
|
+
.command("get <asset-id>")
|
|
121
|
+
.description("按 asset_id 查询素材")
|
|
122
|
+
.option("--json", "输出机器可读 JSON")
|
|
123
|
+
.action(withErrorHandler(async (assetId, _opts, command) => {
|
|
150
124
|
const cmd = command;
|
|
151
|
-
const key =
|
|
125
|
+
const key = requireAssetId(assetId);
|
|
152
126
|
const damClient = new DamClient();
|
|
153
127
|
const data = await damClient.getAsset(key);
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
128
|
+
const payload = {
|
|
129
|
+
status: "completed",
|
|
130
|
+
asset: projectPublicDamAsset(data, key),
|
|
131
|
+
};
|
|
132
|
+
if (isJson(cmd)) {
|
|
133
|
+
emitJson(wrapEnvelope(payload, { command: "dam get" }));
|
|
157
134
|
}
|
|
158
135
|
else {
|
|
159
|
-
formatOutput(
|
|
136
|
+
formatOutput(payload, "human");
|
|
160
137
|
}
|
|
161
138
|
}));
|
|
162
139
|
setAuthRequirement(get, "current-org");
|
|
@@ -165,8 +142,7 @@ export function registerDamCommand(program) {
|
|
|
165
142
|
.description("删除 DAM 素材")
|
|
166
143
|
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
167
144
|
.option("--permanent", "进入回收站后继续永久删除", false)
|
|
168
|
-
.action(withErrorHandler(async (assetId, options
|
|
169
|
-
const cmd = command;
|
|
145
|
+
.action(withErrorHandler(async (assetId, options) => {
|
|
170
146
|
const opts = options;
|
|
171
147
|
const id = String(assetId);
|
|
172
148
|
const damClient = new DamClient();
|
|
@@ -174,53 +150,55 @@ export function registerDamCommand(program) {
|
|
|
174
150
|
repositoryId: optionalString(opts.repositoryId),
|
|
175
151
|
permanent: Boolean(opts.permanent),
|
|
176
152
|
});
|
|
177
|
-
|
|
178
|
-
if (shouldEmitEnvelope(format)) {
|
|
179
|
-
emitJson(wrapEnvelope(data, { command: "dam delete" }));
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
formatOutput(data, format);
|
|
183
|
-
}
|
|
153
|
+
formatOutput(data, "human");
|
|
184
154
|
}));
|
|
185
155
|
setAuthRequirement(deleteCommand, "current-org");
|
|
186
156
|
}
|
|
187
157
|
function buildDamSearchOptions(opts) {
|
|
158
|
+
const repositoryId = optionalString(opts.repositoryId);
|
|
159
|
+
const allRepositories = Boolean(opts.allRepositories);
|
|
160
|
+
if (repositoryId && allRepositories) {
|
|
161
|
+
invalidDamOption("--repository-id 与 --all-repositories 不能同时使用");
|
|
162
|
+
}
|
|
163
|
+
const type = optionalString(opts.type)?.toLowerCase();
|
|
164
|
+
if (type && !DAM_ASSET_TYPES.has(type)) {
|
|
165
|
+
invalidDamOption("--type 必须是 image、video、audio、font、template 或 file");
|
|
166
|
+
}
|
|
188
167
|
return {
|
|
189
|
-
repositoryId
|
|
190
|
-
allRepositories
|
|
168
|
+
repositoryId,
|
|
169
|
+
allRepositories,
|
|
191
170
|
folderId: optionalString(opts.folderId),
|
|
192
171
|
includeChildFolder: opts.includeChildFolder !== false,
|
|
193
|
-
type
|
|
172
|
+
type,
|
|
194
173
|
format: splitList(opts.assetFormat),
|
|
195
174
|
tagIds: splitList(opts.tagIds),
|
|
196
175
|
pageSize: numberOption(opts.pageSize),
|
|
197
|
-
page: numberOption(opts.page),
|
|
198
176
|
cursor: optionalString(opts.cursor),
|
|
199
|
-
raw: Boolean(opts.raw),
|
|
200
177
|
};
|
|
201
178
|
}
|
|
202
|
-
function outputDamSearch(data,
|
|
203
|
-
|
|
204
|
-
|
|
179
|
+
function outputDamSearch(data, json, mode) {
|
|
180
|
+
const payload = projectPublicDamSearchResult(data);
|
|
181
|
+
if (json) {
|
|
182
|
+
emitJson(wrapEnvelope(payload, { command: `dam ${mode}` }));
|
|
205
183
|
return;
|
|
206
184
|
}
|
|
207
|
-
const repo =
|
|
185
|
+
const repo = payload.repository_ids.join(", ") || "default";
|
|
208
186
|
if (mode === "list") {
|
|
209
|
-
console.log(`Found ${
|
|
187
|
+
console.log(`Found ${payload.total} recent assets in repo ${repo}`);
|
|
210
188
|
}
|
|
211
189
|
else {
|
|
212
|
-
console.log(`Found ${
|
|
190
|
+
console.log(`Found ${payload.total} assets for "${payload.query}" in repo ${repo}`);
|
|
213
191
|
}
|
|
214
192
|
console.log("");
|
|
215
|
-
formatOutput(rowsForDamSearch(
|
|
216
|
-
if (
|
|
193
|
+
formatOutput(rowsForDamSearch(payload), "table");
|
|
194
|
+
if (payload.next_cursor) {
|
|
217
195
|
const base = mode === "list"
|
|
218
196
|
? "gd-cli dam list"
|
|
219
|
-
: `gd-cli dam search ${quoteShell(
|
|
220
|
-
console.log(`\nNext:\n ${base} --cursor ${
|
|
197
|
+
: `gd-cli dam search ${quoteShell(payload.query)}`;
|
|
198
|
+
console.log(`\nNext:\n ${base} --cursor ${payload.next_cursor}`);
|
|
221
199
|
}
|
|
222
|
-
if (
|
|
223
|
-
console.log(`\nUse:\n gd-cli dam get ${
|
|
200
|
+
if (payload.assets.length === 1) {
|
|
201
|
+
console.log(`\nUse:\n gd-cli dam get ${payload.assets[0].asset_id}`);
|
|
224
202
|
}
|
|
225
203
|
}
|
|
226
204
|
function rowsForDamSearch(data) {
|
|
@@ -257,6 +235,191 @@ function parsePositiveInteger(value) {
|
|
|
257
235
|
function numberOption(value) {
|
|
258
236
|
return typeof value === "number" ? value : undefined;
|
|
259
237
|
}
|
|
238
|
+
function requireAssetId(value) {
|
|
239
|
+
const assetId = optionalString(value);
|
|
240
|
+
if (!assetId)
|
|
241
|
+
invalidDamOption("asset_id 不能为空");
|
|
242
|
+
if (/^https?:\/\//i.test(assetId))
|
|
243
|
+
invalidDamOption("asset_id 不能是 URL");
|
|
244
|
+
return assetId;
|
|
245
|
+
}
|
|
246
|
+
function invalidDamOption(message) {
|
|
247
|
+
throw new CliUsageError({
|
|
248
|
+
code: "PARAM_INVALID",
|
|
249
|
+
type: "validation",
|
|
250
|
+
message,
|
|
251
|
+
retryable: false,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function isJson(command) {
|
|
255
|
+
return command.opts().json === true;
|
|
256
|
+
}
|
|
257
|
+
function projectPublicDamSearchResult(data) {
|
|
258
|
+
return {
|
|
259
|
+
status: "completed",
|
|
260
|
+
query: data.query,
|
|
261
|
+
repository_ids: [...data.repository_ids],
|
|
262
|
+
page: data.page,
|
|
263
|
+
page_size: data.page_size,
|
|
264
|
+
total: data.total,
|
|
265
|
+
finish: data.finish,
|
|
266
|
+
...(data.next_cursor ? { next_cursor: data.next_cursor } : {}),
|
|
267
|
+
assets: data.assets.map((asset) => projectPublicDamAsset(asset)),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function projectPublicDamAsset(value, fallbackAssetId = "") {
|
|
271
|
+
const item = publicDamAssetRecord(value);
|
|
272
|
+
const origin = recordValue(item.origin_info) ?? recordValue(item.originInfo);
|
|
273
|
+
const preview = recordValue(item.preview) ??
|
|
274
|
+
recordValue(item.preview_info) ??
|
|
275
|
+
recordValue(item.previewInfo) ??
|
|
276
|
+
recordValue(item.cover);
|
|
277
|
+
const folder = recordValue(item.folder_info) ?? recordValue(item.folderInfo);
|
|
278
|
+
const format = stringValue(origin?.format) ?? stringValue(item.format);
|
|
279
|
+
const mimeType = stringValue(item.mime_type) ?? stringValue(item.mimeType);
|
|
280
|
+
const url = stringValue(origin?.url) ?? stringValue(item.origin_url) ?? stringValue(item.url);
|
|
281
|
+
const coverUrl = stringValue(preview?.url) ??
|
|
282
|
+
stringValue(item.cover_url) ??
|
|
283
|
+
stringValue(item.preview_url);
|
|
284
|
+
const assetId = stringValue(item.asset_id) ??
|
|
285
|
+
stringValue(item.id) ??
|
|
286
|
+
stringValue(item.content_id) ??
|
|
287
|
+
fallbackAssetId;
|
|
288
|
+
const tags = publicTags(item.tags);
|
|
289
|
+
const tagIds = publicTagIds(item.tag_ids, item.tags);
|
|
290
|
+
return compactRecord({
|
|
291
|
+
asset_id: assetId,
|
|
292
|
+
title: stringValue(item.title) ?? stringValue(item.name),
|
|
293
|
+
filename: stringValue(item.filename),
|
|
294
|
+
format,
|
|
295
|
+
mime_type: mimeType,
|
|
296
|
+
type: publicDamAssetType(item, format, mimeType),
|
|
297
|
+
size: numberValue(origin?.size) ?? numberValue(item.size),
|
|
298
|
+
url: url ? publicAssetUrl(url) : undefined,
|
|
299
|
+
cover_url: coverUrl ? publicAssetUrl(coverUrl) : undefined,
|
|
300
|
+
width: numberValue(preview?.width) ?? numberValue(item.width),
|
|
301
|
+
height: numberValue(preview?.height) ?? numberValue(item.height),
|
|
302
|
+
repository_id: stringValue(item.repository_id),
|
|
303
|
+
folder_id: stringValue(item.folder_id) ?? stringValue(folder?.id),
|
|
304
|
+
folder_name: stringValue(folder?.title) ?? stringValue(folder?.name),
|
|
305
|
+
tags,
|
|
306
|
+
tag_ids: tagIds,
|
|
307
|
+
analysis_status: numberValue(item.analysis_result) ??
|
|
308
|
+
numberValue(item.analysis_status) ??
|
|
309
|
+
stringValue(item.analysis_result) ??
|
|
310
|
+
stringValue(item.analysis_status),
|
|
311
|
+
created_at: stringValue(item.created_at),
|
|
312
|
+
updated_at: stringValue(item.updated_at),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function publicDamAssetRecord(value) {
|
|
316
|
+
if (!recordValue(value))
|
|
317
|
+
return {};
|
|
318
|
+
const queue = [value];
|
|
319
|
+
const seen = new Set();
|
|
320
|
+
while (queue.length > 0) {
|
|
321
|
+
const candidate = queue.shift();
|
|
322
|
+
const record = recordValue(candidate);
|
|
323
|
+
if (!record || seen.has(record))
|
|
324
|
+
continue;
|
|
325
|
+
seen.add(record);
|
|
326
|
+
if (isDamAssetRecord(record))
|
|
327
|
+
return record;
|
|
328
|
+
for (const key of ["data", "asset", "asset_info"]) {
|
|
329
|
+
if (record[key] !== undefined)
|
|
330
|
+
queue.push(record[key]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
function isDamAssetRecord(value) {
|
|
336
|
+
return [
|
|
337
|
+
"asset_id",
|
|
338
|
+
"id",
|
|
339
|
+
"content_id",
|
|
340
|
+
"origin_info",
|
|
341
|
+
"originInfo",
|
|
342
|
+
"origin_url",
|
|
343
|
+
"url",
|
|
344
|
+
].some((key) => value[key] !== undefined);
|
|
345
|
+
}
|
|
346
|
+
function publicDamAssetType(item, format, mimeType) {
|
|
347
|
+
const explicit = stringValue(item.type)?.toLowerCase();
|
|
348
|
+
if (explicit && DAM_ASSET_TYPES.has(explicit))
|
|
349
|
+
return explicit;
|
|
350
|
+
const contentFormat = stringValue(item.content_format)?.toLowerCase() ??
|
|
351
|
+
stringValue(item.storage_format)?.toLowerCase();
|
|
352
|
+
if (contentFormat === "gdpic" || contentFormat === "gdimage")
|
|
353
|
+
return "image";
|
|
354
|
+
if (contentFormat === "gdvideo")
|
|
355
|
+
return "video";
|
|
356
|
+
if (contentFormat === "gdaudio")
|
|
357
|
+
return "audio";
|
|
358
|
+
if (contentFormat === "gdfont")
|
|
359
|
+
return "font";
|
|
360
|
+
if (contentFormat === "gdtemplate")
|
|
361
|
+
return "template";
|
|
362
|
+
const mimeCategory = mimeType?.split("/", 1)[0]?.toLowerCase();
|
|
363
|
+
if (mimeCategory && DAM_ASSET_TYPES.has(mimeCategory))
|
|
364
|
+
return mimeCategory;
|
|
365
|
+
const normalizedFormat = format?.toLowerCase();
|
|
366
|
+
if (normalizedFormat && ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(normalizedFormat)) {
|
|
367
|
+
return "image";
|
|
368
|
+
}
|
|
369
|
+
if (normalizedFormat && ["mp4", "mov", "webm", "mkv"].includes(normalizedFormat)) {
|
|
370
|
+
return "video";
|
|
371
|
+
}
|
|
372
|
+
if (normalizedFormat && ["mp3", "wav", "m4a", "aac"].includes(normalizedFormat)) {
|
|
373
|
+
return "audio";
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
function recordValue(value) {
|
|
378
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
379
|
+
? value
|
|
380
|
+
: undefined;
|
|
381
|
+
}
|
|
382
|
+
function stringValue(value) {
|
|
383
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
384
|
+
}
|
|
385
|
+
function numberValue(value) {
|
|
386
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
387
|
+
}
|
|
388
|
+
function publicTags(value) {
|
|
389
|
+
if (!Array.isArray(value))
|
|
390
|
+
return undefined;
|
|
391
|
+
const tags = value
|
|
392
|
+
.map((item) => typeof item === "string" ? item : stringValue(recordValue(item)?.name))
|
|
393
|
+
.filter((item) => Boolean(item));
|
|
394
|
+
return tags.length > 0 ? tags : undefined;
|
|
395
|
+
}
|
|
396
|
+
function publicTagIds(tagIds, tags) {
|
|
397
|
+
const values = Array.isArray(tagIds)
|
|
398
|
+
? tagIds
|
|
399
|
+
: Array.isArray(tags)
|
|
400
|
+
? tags.map((item) => recordValue(item)?.id)
|
|
401
|
+
: [];
|
|
402
|
+
const ids = values.filter((item) => typeof item === "string" || typeof item === "number");
|
|
403
|
+
return ids.length > 0 ? ids : undefined;
|
|
404
|
+
}
|
|
405
|
+
function compactRecord(value) {
|
|
406
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
407
|
+
}
|
|
408
|
+
function publicAssetUrl(value) {
|
|
409
|
+
try {
|
|
410
|
+
const url = new URL(assertSafeHttpUrl(value, { context: "DAM 素材 URL" }));
|
|
411
|
+
if (isBlockedHost(url.hostname))
|
|
412
|
+
return undefined;
|
|
413
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
414
|
+
if (isSensitiveKey(key))
|
|
415
|
+
url.searchParams.delete(key);
|
|
416
|
+
}
|
|
417
|
+
return url.toString();
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
260
423
|
function formatBytes(value) {
|
|
261
424
|
if (!Number.isFinite(value))
|
|
262
425
|
return "";
|
|
@@ -271,28 +434,3 @@ function formatBytes(value) {
|
|
|
271
434
|
function quoteShell(value) {
|
|
272
435
|
return /^[\w./:=,-]+$/.test(value) ? value : `"${value.replace(/"/g, '\\"')}"`;
|
|
273
436
|
}
|
|
274
|
-
const DAM_HELP = `Usage: gd-cli dam <command> [options]
|
|
275
|
-
|
|
276
|
-
用于把本地文件上传到稿定素材库,获得可复用的 asset_id 和 CDN URL。
|
|
277
|
-
适合在 Creative、Workflows 和其他自动化流程中反复引用同一批商品图、Logo、参考图和交付文件。
|
|
278
|
-
|
|
279
|
-
常用命令:
|
|
280
|
-
upload <file> 上传文件并入库,返回 asset_id 与 CDN URL
|
|
281
|
-
get <id-or-url> 按 asset_id、CDN URL 或文件名查询素材
|
|
282
|
-
list 查看最近上传素材
|
|
283
|
-
search <keyword> 搜索素材
|
|
284
|
-
delete <asset-id> 删除素材
|
|
285
|
-
|
|
286
|
-
常用选项:
|
|
287
|
-
--name <name> 指定素材名称
|
|
288
|
-
--folder <folder> 指定 DAM 文件夹
|
|
289
|
-
--tags <tags> 添加标签,逗号分隔
|
|
290
|
-
--asset-format <list> 在 dam search/list 中筛选素材格式,例如 png,jpg
|
|
291
|
-
--json 输出机器可读 JSON
|
|
292
|
-
|
|
293
|
-
示例:
|
|
294
|
-
gd-cli dam upload ./sku-001.png --tags product,shoe
|
|
295
|
-
gd-cli dam get ast_123
|
|
296
|
-
gd-cli dam search "white sneaker" --type image --asset-format png,jpg
|
|
297
|
-
gd-cli dam delete ast_123 --yes
|
|
298
|
-
`;
|