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.
Files changed (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +47 -64
  3. package/dist/bin/gd-cli.js +2 -2
  4. package/dist/src/api/app-runner.d.ts +1 -4
  5. package/dist/src/api/app-runner.js +10 -42
  6. package/dist/src/api/dam-client.d.ts +0 -7
  7. package/dist/src/api/dam-client.js +0 -17
  8. package/dist/src/commands/auth.js +5 -41
  9. package/dist/src/commands/creative.js +37 -45
  10. package/dist/src/commands/dam.js +248 -110
  11. package/dist/src/commands/org.js +17 -39
  12. package/dist/src/commands/skills.js +24 -114
  13. package/dist/src/commands/update.js +10 -85
  14. package/dist/src/commands/usage.js +4 -17
  15. package/dist/src/commands/workflows.d.ts +1 -1
  16. package/dist/src/commands/workflows.js +283 -199
  17. package/dist/src/core/output/envelope.d.ts +1 -0
  18. package/dist/src/core/output/envelope.js +76 -6
  19. package/dist/src/creative/contract.d.ts +28 -80
  20. package/dist/src/creative/contract.js +23 -44
  21. package/dist/src/creative/protocol.js +6 -6
  22. package/dist/src/io.d.ts +0 -2
  23. package/dist/src/io.js +0 -8
  24. package/dist/src/middleware/error-handler.d.ts +7 -1
  25. package/dist/src/middleware/error-handler.js +59 -54
  26. package/dist/src/program.d.ts +1 -0
  27. package/dist/src/program.js +138 -41
  28. package/dist/src/registry/agent-contracts.d.ts +14 -26
  29. package/dist/src/registry/agent-contracts.js +214 -64
  30. package/dist/src/utils/capability-presenter.js +6 -29
  31. package/dist/src/utils/config.d.ts +2 -2
  32. package/dist/src/utils/config.js +1 -1
  33. package/dist/src/utils/sensitive-path.js +1 -4
  34. package/package.json +2 -1
  35. package/skills/gd-cli/SKILL.md +6 -6
  36. package/skills/gd-cli/references/auth.md +3 -10
  37. package/skills/gd-cli/references/creative.md +11 -11
  38. package/skills/gd-cli/references/entitlement.md +2 -2
  39. package/skills/gd-cli/references/errors.md +4 -4
  40. package/skills/gd-cli/references/org.md +3 -3
  41. package/skills/gd-cli/references/sop.md +5 -5
  42. package/skills/gd-cli/references/update.md +4 -3
  43. package/skills/gd-cli/references/workflows.md +6 -5
  44. package/dist/src/utils/list-output-format.d.ts +0 -10
  45. 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 { toCliErrorBody } from "../middleware/error-handler.js";
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
- const format = resolveListOutputFormat(command);
34
- if (format === "json") {
36
+ if (command.opts().json === true) {
35
37
  formatOutput(payload, "json");
36
38
  return;
37
39
  }
38
- console.log("Creative Input Schema\n");
39
- console.log(JSON.stringify(payload.input_schema, null, 2));
40
- console.log("\nCreative Output Schema\n");
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((_options, command) => {
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(JSON.stringify(example.input, null, 2));
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 = resolveInvokeOutputFormat(command);
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.last_tool_message_id) {
144
- console.log(` last_tool_message_id: ${result.last_tool_message_id}`);
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 toCreativeError(error) {
168
- const body = toCliErrorBody(error);
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
- `;
@@ -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, shouldEmitEnvelope } from "../core/output/envelope.js";
10
- import { setStaticHelp } from "../utils/static-help.js";
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("--analysis-timeout <ms>", "等待分析超时时间(毫秒)", parsePositiveInteger, 180000)
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: !opts.storageOnly,
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) && !opts.storageOnly,
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
- id: uploaded.id,
58
- url: uploaded.url,
59
- type: uploaded.mime_type,
50
+ const asset = projectPublicDamAsset({
51
+ ...uploaded,
52
+ asset_id: uploaded.id,
60
53
  filename: basename(p),
61
- source: "cli",
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: uploaded.id,
69
- id: uploaded.id,
70
- storage_id: uploaded.storage_id,
71
- registered_asset_id: uploaded.registered_asset_id,
72
- registered: uploaded.registered ?? false,
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
- const format = resolveInvokeOutputFormat(cmd);
89
- if (shouldEmitEnvelope(format)) {
66
+ if (isJson(cmd)) {
90
67
  emitJson(wrapEnvelope(payload, { command: "dam upload" }));
91
68
  }
92
69
  else {
93
- formatOutput(payload, format);
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("--raw", "JSON 输出包含后端原始数据", false)
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, resolveListOutputFormat(cmd), "list");
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("--raw", "JSON 输出包含后端原始数据", false)
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, resolveListOutputFormat(cmd), "search");
116
+ outputDamSearch(data, isJson(cmd), "search");
144
117
  }));
145
118
  setAuthRequirement(search, "current-org");
146
119
  const get = dam
147
- .command("get <id-or-url>")
148
- .description("按 asset_id、CDN URL 或文件名查询素材(依赖 Hub DAM API)")
149
- .action(withErrorHandler(async (idOrUrl, _opts, command) => {
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 = String(idOrUrl);
125
+ const key = requireAssetId(assetId);
152
126
  const damClient = new DamClient();
153
127
  const data = await damClient.getAsset(key);
154
- const format = resolveInvokeOutputFormat(cmd);
155
- if (shouldEmitEnvelope(format)) {
156
- emitJson(wrapEnvelope(data, { command: "dam get" }));
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(data, format);
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, command) => {
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
- const format = resolveInvokeOutputFormat(cmd);
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: optionalString(opts.repositoryId),
190
- allRepositories: Boolean(opts.allRepositories),
168
+ repositoryId,
169
+ allRepositories,
191
170
  folderId: optionalString(opts.folderId),
192
171
  includeChildFolder: opts.includeChildFolder !== false,
193
- type: optionalString(opts.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, format, mode) {
203
- if (format === "json") {
204
- formatOutput(data, "json");
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 = data.repository_ids.join(", ") || "default";
185
+ const repo = payload.repository_ids.join(", ") || "default";
208
186
  if (mode === "list") {
209
- console.log(`Found ${data.total} recent assets in repo ${repo}`);
187
+ console.log(`Found ${payload.total} recent assets in repo ${repo}`);
210
188
  }
211
189
  else {
212
- console.log(`Found ${data.total} assets for "${data.query}" in repo ${repo}`);
190
+ console.log(`Found ${payload.total} assets for "${payload.query}" in repo ${repo}`);
213
191
  }
214
192
  console.log("");
215
- formatOutput(rowsForDamSearch(data), format === "human" ? "table" : format);
216
- if (data.next_cursor) {
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(data.query)}`;
220
- console.log(`\nNext:\n ${base} --cursor ${data.next_cursor}`);
197
+ : `gd-cli dam search ${quoteShell(payload.query)}`;
198
+ console.log(`\nNext:\n ${base} --cursor ${payload.next_cursor}`);
221
199
  }
222
- if (data.assets.length === 1) {
223
- console.log(`\nUse:\n gd-cli dam get ${data.assets[0].asset_id}`);
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
- `;