gaoding-cli 1.0.0-alpha.7 → 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 (77) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +47 -60
  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 +11 -44
  6. package/dist/src/api/creative-client.d.ts +34 -0
  7. package/dist/src/api/creative-client.js +184 -0
  8. package/dist/src/api/dam-client.d.ts +0 -7
  9. package/dist/src/api/dam-client.js +0 -17
  10. package/dist/src/api/direct-client.d.ts +9 -2
  11. package/dist/src/api/direct-client.js +45 -18
  12. package/dist/src/api/hub-client.d.ts +11 -8
  13. package/dist/src/api/hub-client.js +17 -18
  14. package/dist/src/api/usage-client.d.ts +4 -4
  15. package/dist/src/api/usage-client.js +9 -49
  16. package/dist/src/auth/credential-store-v2.d.ts +0 -1
  17. package/dist/src/auth/credential-store-v2.js +0 -6
  18. package/dist/src/commands/auth.js +17 -53
  19. package/dist/src/commands/creative.d.ts +8 -0
  20. package/dist/src/commands/creative.js +178 -0
  21. package/dist/src/commands/dam.js +270 -127
  22. package/dist/src/commands/org.js +31 -47
  23. package/dist/src/commands/skills.js +33 -118
  24. package/dist/src/commands/update.js +11 -82
  25. package/dist/src/commands/usage.d.ts +1 -1
  26. package/dist/src/commands/usage.js +10 -21
  27. package/dist/src/commands/workflows.d.ts +1 -1
  28. package/dist/src/commands/workflows.js +299 -197
  29. package/dist/src/core/auth/auth-guard.d.ts +21 -0
  30. package/dist/src/core/auth/auth-guard.js +121 -0
  31. package/dist/src/core/auth/org-manager.d.ts +2 -1
  32. package/dist/src/core/auth/org-manager.js +61 -6
  33. package/dist/src/core/output/envelope.d.ts +1 -0
  34. package/dist/src/core/output/envelope.js +76 -6
  35. package/dist/src/creative/contract.d.ts +1060 -0
  36. package/dist/src/creative/contract.js +722 -0
  37. package/dist/src/creative/media.d.ts +19 -0
  38. package/dist/src/creative/media.js +83 -0
  39. package/dist/src/creative/protocol.d.ts +60 -0
  40. package/dist/src/creative/protocol.js +337 -0
  41. package/dist/src/io.d.ts +0 -2
  42. package/dist/src/io.js +0 -8
  43. package/dist/src/middleware/auth.d.ts +6 -2
  44. package/dist/src/middleware/auth.js +25 -63
  45. package/dist/src/middleware/error-handler.d.ts +8 -0
  46. package/dist/src/middleware/error-handler.js +61 -57
  47. package/dist/src/program.d.ts +1 -0
  48. package/dist/src/program.js +137 -122
  49. package/dist/src/registry/agent-contracts.d.ts +14 -34
  50. package/dist/src/registry/agent-contracts.js +209 -297
  51. package/dist/src/registry/direct-registry.d.ts +0 -5
  52. package/dist/src/registry/direct-registry.js +0 -19
  53. package/dist/src/registry/registry-client.d.ts +0 -21
  54. package/dist/src/registry/registry-client.js +1 -40
  55. package/dist/src/utils/capability-presenter.js +6 -29
  56. package/dist/src/utils/config.d.ts +2 -2
  57. package/dist/src/utils/config.js +1 -1
  58. package/dist/src/utils/help-verify.d.ts +1 -3
  59. package/dist/src/utils/help-verify.js +1 -25
  60. package/dist/src/utils/sensitive-path.js +1 -4
  61. package/package.json +4 -3
  62. package/skills/gd-cli/SKILL.md +13 -12
  63. package/skills/gd-cli/references/auth.md +9 -10
  64. package/skills/gd-cli/references/creative.md +47 -0
  65. package/skills/gd-cli/references/entitlement.md +3 -3
  66. package/skills/gd-cli/references/errors.md +10 -3
  67. package/skills/gd-cli/references/org.md +3 -3
  68. package/skills/gd-cli/references/sop.md +8 -8
  69. package/skills/gd-cli/references/update.md +4 -3
  70. package/skills/gd-cli/references/workflows.md +7 -6
  71. package/dist/src/commands/models.d.ts +0 -2
  72. package/dist/src/commands/models.js +0 -78
  73. package/dist/src/commands/tools.d.ts +0 -4
  74. package/dist/src/commands/tools.js +0 -383
  75. package/dist/src/utils/list-output-format.d.ts +0 -10
  76. package/dist/src/utils/list-output-format.js +0 -34
  77. package/skills/gd-cli/references/tools.md +0 -29
@@ -1,35 +1,31 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { basename, resolve } from "node:path";
3
- import { withAuth } from "../middleware/auth.js";
3
+ 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);
18
- dam
17
+ .description("DAM 素材管理");
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)
32
- .action(withErrorHandler(withAuth(async (file, options, command) => {
27
+ .option("--json", "输出机器可读 JSON")
28
+ .action(withErrorHandler(async (file, options, command) => {
33
29
  const opts = options;
34
30
  const cmd = command;
35
31
  const p = resolve(process.cwd(), String(file));
@@ -41,74 +37,54 @@ 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
- })));
96
- dam
72
+ }));
73
+ setAuthRequirement(upload, "current-org");
74
+ const list = dam
97
75
  .command("list")
98
76
  .description("查看最近上传素材")
99
77
  .option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
100
78
  .option("--all-repositories", "搜索当前组织下所有资源库", false)
101
79
  .option("--folder-id <id>", "限定文件夹")
102
- .option("--include-child-folder", "包含子文件夹", true)
103
80
  .option("--no-include-child-folder", "不包含子文件夹")
104
81
  .option("--type <type>", "image/video/audio/font/template/file")
105
- .option("--format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
82
+ .option("--asset-format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
106
83
  .option("--tag-ids <list>", "标签 ID,逗号分隔")
107
84
  .option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
108
- .option("--page <n>", "页码", parsePositiveInteger, 1)
109
85
  .option("--cursor <id>", "分页 cursor/query_id")
110
- .option("--raw", "JSON 输出包含后端原始数据", false)
111
- .action(withErrorHandler(withAuth(async (options, command) => {
86
+ .option("--json", "输出机器可读 JSON")
87
+ .action(withErrorHandler(async (options, command) => {
112
88
  const opts = options;
113
89
  const cmd = command;
114
90
  const damClient = new DamClient();
@@ -116,53 +92,57 @@ export function registerDamCommand(program) {
116
92
  ...buildDamSearchOptions(opts),
117
93
  allowEmptyKeyword: true,
118
94
  });
119
- outputDamSearch(data, resolveListOutputFormat(cmd), "list");
120
- })));
121
- dam
95
+ outputDamSearch(data, isJson(cmd), "list");
96
+ }));
97
+ setAuthRequirement(list, "current-org");
98
+ const search = dam
122
99
  .command("search <keyword>")
123
100
  .description("搜索素材")
124
101
  .option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
125
102
  .option("--all-repositories", "搜索当前组织下所有资源库", false)
126
103
  .option("--folder-id <id>", "限定文件夹")
127
- .option("--include-child-folder", "包含子文件夹", true)
128
104
  .option("--no-include-child-folder", "不包含子文件夹")
129
105
  .option("--type <type>", "image/video/audio/font/template/file")
130
- .option("--format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
106
+ .option("--asset-format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
131
107
  .option("--tag-ids <list>", "标签 ID,逗号分隔")
132
108
  .option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
133
- .option("--page <n>", "页码", parsePositiveInteger, 1)
134
109
  .option("--cursor <id>", "分页 cursor/query_id")
135
- .option("--raw", "JSON 输出包含后端原始数据", false)
136
- .action(withErrorHandler(withAuth(async (keyword, options, command) => {
110
+ .option("--json", "输出机器可读 JSON")
111
+ .action(withErrorHandler(async (keyword, options, command) => {
137
112
  const opts = options;
138
113
  const cmd = command;
139
114
  const damClient = new DamClient();
140
115
  const data = await damClient.searchAssets(String(keyword), buildDamSearchOptions(opts));
141
- outputDamSearch(data, resolveListOutputFormat(cmd), "search");
142
- })));
143
- dam
144
- .command("get <id-or-url>")
145
- .description(" asset_id、CDN URL 或文件名查询素材(依赖 Hub DAM API)")
146
- .action(withErrorHandler(withAuth(async (idOrUrl, _opts, command) => {
116
+ outputDamSearch(data, isJson(cmd), "search");
117
+ }));
118
+ setAuthRequirement(search, "current-org");
119
+ const get = dam
120
+ .command("get <asset-id>")
121
+ .description("按 asset_id 查询素材")
122
+ .option("--json", "输出机器可读 JSON")
123
+ .action(withErrorHandler(async (assetId, _opts, command) => {
147
124
  const cmd = command;
148
- const key = String(idOrUrl);
125
+ const key = requireAssetId(assetId);
149
126
  const damClient = new DamClient();
150
127
  const data = await damClient.getAsset(key);
151
- const format = resolveInvokeOutputFormat(cmd);
152
- if (shouldEmitEnvelope(format)) {
153
- 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" }));
154
134
  }
155
135
  else {
156
- formatOutput(data, format);
136
+ formatOutput(payload, "human");
157
137
  }
158
- })));
159
- dam
138
+ }));
139
+ setAuthRequirement(get, "current-org");
140
+ const deleteCommand = dam
160
141
  .command("delete <asset-id>")
161
142
  .description("删除 DAM 素材")
162
143
  .option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
163
144
  .option("--permanent", "进入回收站后继续永久删除", false)
164
- .action(withErrorHandler(withAuth(async (assetId, options, command) => {
165
- const cmd = command;
145
+ .action(withErrorHandler(async (assetId, options) => {
166
146
  const opts = options;
167
147
  const id = String(assetId);
168
148
  const damClient = new DamClient();
@@ -170,52 +150,55 @@ export function registerDamCommand(program) {
170
150
  repositoryId: optionalString(opts.repositoryId),
171
151
  permanent: Boolean(opts.permanent),
172
152
  });
173
- const format = resolveInvokeOutputFormat(cmd);
174
- if (shouldEmitEnvelope(format)) {
175
- emitJson(wrapEnvelope(data, { command: "dam delete" }));
176
- }
177
- else {
178
- formatOutput(data, format);
179
- }
180
- })));
153
+ formatOutput(data, "human");
154
+ }));
155
+ setAuthRequirement(deleteCommand, "current-org");
181
156
  }
182
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
+ }
183
167
  return {
184
- repositoryId: optionalString(opts.repositoryId),
185
- allRepositories: Boolean(opts.allRepositories),
168
+ repositoryId,
169
+ allRepositories,
186
170
  folderId: optionalString(opts.folderId),
187
171
  includeChildFolder: opts.includeChildFolder !== false,
188
- type: optionalString(opts.type),
189
- format: splitList(opts.format),
172
+ type,
173
+ format: splitList(opts.assetFormat),
190
174
  tagIds: splitList(opts.tagIds),
191
175
  pageSize: numberOption(opts.pageSize),
192
- page: numberOption(opts.page),
193
176
  cursor: optionalString(opts.cursor),
194
- raw: Boolean(opts.raw),
195
177
  };
196
178
  }
197
- function outputDamSearch(data, format, mode) {
198
- if (format === "json") {
199
- formatOutput(data, "json");
179
+ function outputDamSearch(data, json, mode) {
180
+ const payload = projectPublicDamSearchResult(data);
181
+ if (json) {
182
+ emitJson(wrapEnvelope(payload, { command: `dam ${mode}` }));
200
183
  return;
201
184
  }
202
- const repo = data.repository_ids.join(", ") || "default";
185
+ const repo = payload.repository_ids.join(", ") || "default";
203
186
  if (mode === "list") {
204
- console.log(`Found ${data.total} recent assets in repo ${repo}`);
187
+ console.log(`Found ${payload.total} recent assets in repo ${repo}`);
205
188
  }
206
189
  else {
207
- 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}`);
208
191
  }
209
192
  console.log("");
210
- formatOutput(rowsForDamSearch(data), format === "human" ? "table" : format);
211
- if (data.next_cursor) {
193
+ formatOutput(rowsForDamSearch(payload), "table");
194
+ if (payload.next_cursor) {
212
195
  const base = mode === "list"
213
196
  ? "gd-cli dam list"
214
- : `gd-cli dam search ${quoteShell(data.query)}`;
215
- 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}`);
216
199
  }
217
- if (data.assets.length === 1) {
218
- 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}`);
219
202
  }
220
203
  }
221
204
  function rowsForDamSearch(data) {
@@ -252,6 +235,191 @@ function parsePositiveInteger(value) {
252
235
  function numberOption(value) {
253
236
  return typeof value === "number" ? value : undefined;
254
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
+ }
255
423
  function formatBytes(value) {
256
424
  if (!Number.isFinite(value))
257
425
  return "";
@@ -266,28 +434,3 @@ function formatBytes(value) {
266
434
  function quoteShell(value) {
267
435
  return /^[\w./:=,-]+$/.test(value) ? value : `"${value.replace(/"/g, '\\"')}"`;
268
436
  }
269
- const DAM_HELP = `Usage: gd-cli dam <command> [options]
270
-
271
- 用于把本地文件上传到稿定素材库,获得可复用的 asset_id 和 CDN URL。
272
- 适合在 Workflows / Tools / Agent 工作流中反复引用同一批商品图、Logo、参考图和交付文件。
273
-
274
- 常用命令:
275
- upload <file> 上传文件并入库,返回 asset_id 与 CDN URL
276
- get <id-or-url> 按 asset_id、CDN URL 或文件名查询素材
277
- list 查看最近上传素材
278
- search <keyword> 搜索素材
279
- delete <asset-id> 删除素材
280
-
281
- 常用选项:
282
- --name <name> 指定素材名称
283
- --folder <folder> 指定 DAM 文件夹
284
- --tags <tags> 添加标签,逗号分隔
285
- --format <list> 在 dam search/list 中表示素材格式过滤,例如 png,jpg
286
- --json 输出机器可读 JSON
287
-
288
- 示例:
289
- gd-cli dam upload ./sku-001.png --tags product,shoe
290
- gd-cli dam get ast_123
291
- gd-cli dam search "white sneaker" --type image --format png,jpg
292
- gd-cli dam delete ast_123 --yes
293
- `;
@@ -1,29 +1,27 @@
1
1
  import chalk from "chalk";
2
2
  import { withErrorHandler } from "../middleware/error-handler.js";
3
- import { withAuth } from "../middleware/auth.js";
3
+ import { setAuthRequirement } from "../middleware/auth.js";
4
4
  import { OrgManager } from "../core/auth/org-manager.js";
5
5
  import { formatOutput } from "../middleware/output.js";
6
- import { resolveInvokeOutputFormat, resolveListOutputFormat, } from "../utils/list-output-format.js";
7
6
  import { getIO } from "../io.js";
8
7
  import { emitJson, wrapEnvelope } from "../core/output/envelope.js";
9
- import { setStaticHelp } from "../utils/static-help.js";
8
+ import { CliUsageError } from "../core/output/cli-error.js";
9
+ import { registerUsageCommand } from "./usage.js";
10
10
  export function registerOrgCommand(program) {
11
11
  const org = program
12
12
  .command("org")
13
13
  .usage("<command> [options]")
14
- .description("查看与切换当前 CLI 团队")
15
- .configureHelp({ showGlobalOptions: true });
16
- setStaticHelp(org, ORG_HELP);
17
- org
14
+ .description("查看与切换当前组织");
15
+ const list = org
18
16
  .command("list")
19
- .alias("ls")
20
- .description("列出可绑定的组织")
21
- .action(withErrorHandler(withAuth(async (_opts, command) => {
17
+ .description("列出可用组织")
18
+ .option("--json", "输出机器可读 JSON")
19
+ .action(withErrorHandler(async (_opts, command) => {
22
20
  const cmd = command;
23
21
  const manager = new OrgManager();
24
22
  const orgs = await manager.listOrgs();
25
23
  const current = manager.getCurrentOrgId();
26
- const format = resolveListOutputFormat(cmd);
24
+ const json = cmd.opts().json === true;
27
25
  const rows = orgs.map((o) => ({
28
26
  current: o.id === current ? "yes" : "",
29
27
  name: o.name,
@@ -33,20 +31,17 @@ export function registerOrgCommand(program) {
33
31
  status: o.status ?? "",
34
32
  expires: formatExpireDate(o.expire_date),
35
33
  }));
36
- if (getIO().prefersJsonOutput(format)) {
34
+ if (json) {
37
35
  emitJson(wrapEnvelope({ orgs, current_org_id: current }, { command: "org list" }));
38
36
  return;
39
37
  }
40
- if (format === "json") {
41
- formatOutput({ orgs, current_org_id: current }, format);
42
- }
43
- else {
44
- formatOutput(rows, format === "table" ? "table" : "text");
45
- }
46
- })));
47
- org
38
+ formatOutput(rows, "table");
39
+ }));
40
+ setAuthRequirement(list, "credential");
41
+ const current = org
48
42
  .command("current")
49
43
  .description("快速查询当前组织")
44
+ .option("--json", "输出机器可读 JSON")
50
45
  .action(withErrorHandler(async (_opts, command) => {
51
46
  const cmd = command;
52
47
  const manager = new OrgManager();
@@ -57,25 +52,23 @@ export function registerOrgCommand(program) {
57
52
  current_org: current ?? null,
58
53
  next_command: current ? undefined : "gd-cli org list",
59
54
  };
60
- const format = resolveInvokeOutputFormat(cmd);
61
- if (getIO().prefersJsonOutput(format) || format === "json") {
62
- formatOutput(payload, "json");
55
+ if (cmd.opts().json === true) {
56
+ emitJson(wrapEnvelope(payload, { command: "org current" }));
63
57
  return;
64
58
  }
65
59
  if (!current) {
66
- console.log(chalk.yellow("当前未绑定团队"));
60
+ console.log(chalk.yellow("当前未选择组织"));
67
61
  console.log(" gd-cli org list");
68
62
  return;
69
63
  }
70
64
  console.log(`当前组织: ${current.name} (${current.id})`);
71
65
  }));
72
- org
66
+ setAuthRequirement(current, "none");
67
+ const switchCommand = org
73
68
  .command("switch")
74
69
  .description("切换当前 CLI 组织(TTY 下可交互选择)")
75
70
  .option("--org <org-id>", "组织 ID")
76
- .action(withErrorHandler(withAuth(async (options, command) => {
77
- const cmd = command;
78
- const format = resolveListOutputFormat(cmd);
71
+ .action(withErrorHandler(async (options) => {
79
72
  const manager = new OrgManager();
80
73
  let orgId = options.org;
81
74
  if (!orgId) {
@@ -97,31 +90,22 @@ export function registerOrgCommand(program) {
97
90
  });
98
91
  }
99
92
  else {
100
- const orgs = await manager.listOrgs();
101
- emitJson(wrapEnvelope({ error: "org_selection_required", orgs }, { command: "org switch" }));
102
- process.exitCode = 1;
103
- return;
93
+ throw new CliUsageError({
94
+ code: "ORG_SELECTION_REQUIRED",
95
+ type: "validation",
96
+ message: "非交互环境请通过 --org <org-id> 指定要切换的组织。",
97
+ hint: "先执行 gd-cli org list 查看可用组织。",
98
+ retryable: false,
99
+ });
104
100
  }
105
101
  }
106
102
  const result = await manager.switchOrg(orgId);
107
- if (getIO().prefersJsonOutput(format)) {
108
- emitJson(wrapEnvelope({ org: result }, { command: "org switch" }));
109
- return;
110
- }
111
103
  console.log(chalk.green(`\n✓ 已切换到组织: ${result.name} (${result.id})`));
112
104
  console.log(chalk.yellow("\n请 60s 后再调用能力,即可按新组织的权益执行~\n"));
113
- })));
105
+ }));
106
+ setAuthRequirement(switchCommand, "credential");
107
+ registerUsageCommand(org);
114
108
  }
115
- const ORG_HELP = `
116
- Usage: gd-cli org <command> [options]
117
-
118
- 用于查看和切换当前组织
119
-
120
- 常用命令:
121
- list/ls 列出可绑定的组织
122
- current 查询当前绑定的组织
123
- switch 切换绑定的组织
124
- `;
125
109
  function formatOrgChoice(org, currentOrgId) {
126
110
  const product = org.product_name ? ` · ${org.product_name}` : "";
127
111
  const current = org.id === currentOrgId ? " ← 当前" : "";