gaoding-cli 1.0.0-alpha.21 → 1.0.0-alpha.22

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 (37) hide show
  1. package/contracts/operations/dam.copy/input.schema.json +19 -0
  2. package/contracts/operations/dam.copy/output.schema.json +19 -0
  3. package/contracts/operations/dam.download/input.schema.json +14 -0
  4. package/contracts/operations/dam.download/output.schema.json +25 -0
  5. package/contracts/operations/dam.folder.list/input.schema.json +15 -0
  6. package/contracts/operations/dam.folder.list/output.schema.json +37 -0
  7. package/contracts/operations/dam.list/output.schema.json +3 -3
  8. package/contracts/operations/dam.move/input.schema.json +19 -0
  9. package/contracts/operations/dam.move/output.schema.json +18 -0
  10. package/contracts/operations/dam.recycle.list/input.schema.json +13 -0
  11. package/contracts/operations/dam.recycle.list/output.schema.json +34 -0
  12. package/contracts/operations/dam.recycle.restore/input.schema.json +14 -0
  13. package/contracts/operations/dam.recycle.restore/output.schema.json +6 -0
  14. package/contracts/operations/dam.rename/input.schema.json +18 -0
  15. package/contracts/operations/dam.rename/output.schema.json +18 -0
  16. package/contracts/operations/dam.repository.list/input.schema.json +8 -0
  17. package/contracts/operations/dam.repository.list/output.schema.json +52 -0
  18. package/contracts/operations/dam.search/output.schema.json +3 -3
  19. package/contracts/operations/dam.tag.list/input.schema.json +12 -0
  20. package/contracts/operations/dam.tag.list/output.schema.json +26 -0
  21. package/dist/src/bootstrap/create-runtime.js +24 -0
  22. package/dist/src/bootstrap/validators.js +39 -0
  23. package/dist/src/cli/dam-commands.js +248 -1
  24. package/dist/src/cli/errors.js +22 -2
  25. package/dist/src/cli/presenter.js +12 -0
  26. package/dist/src/features/dam/dam-api-adapter.js +369 -28
  27. package/dist/src/features/dam/file-downloader.js +242 -0
  28. package/dist/src/features/dam/operation-executor.js +72 -0
  29. package/dist/src/features/dam/repository-catalog.js +127 -0
  30. package/dist/src/features/dam/use-cases.js +323 -11
  31. package/dist/src/features/editor/bridge-server.js +6 -1
  32. package/dist/src/platform/signature.js +2 -1
  33. package/dist/src/platform/signed-http-transport.js +9 -1
  34. package/package.json +1 -1
  35. package/skills/gd-cli/SKILL.md +1 -1
  36. package/skills/gd-cli/references/dam.md +45 -13
  37. package/skills/gd-cli/references/editor.md +3 -1
@@ -1,9 +1,203 @@
1
- import { bindPreparedAction } from "./action-binding.js";
1
+ import { bindAction, bindPreparedAction } from "./action-binding.js";
2
2
  import { CliUsageError } from "./errors.js";
3
+ import { repositoryListView } from "./presenter.js";
3
4
  export function registerDamCommands(program, runtime) {
4
5
  const dam = program.command("dam")
5
6
  .description("DAM 素材管理")
6
7
  .helpCommand(false);
8
+ const repository = dam.command("repository")
9
+ .description("DAM 资源库")
10
+ .helpCommand(false);
11
+ const repositoryList = leaf(repository.command("list"))
12
+ .description("列出可用资源库")
13
+ .option("--json", "输出 JSON");
14
+ bindAction(repositoryList, "organization", async (context, signal) => {
15
+ const result = await runtime.telemetry.stage("repository", () => (runtime.dam.repositoryList({
16
+ access: organizationAccess(context.state),
17
+ signal
18
+ })));
19
+ await runtime.telemetry.stage("output", async () => {
20
+ runtime.presenter.result(result, {
21
+ json: repositoryList.opts().json === true,
22
+ view: repositoryListView
23
+ });
24
+ });
25
+ }, { operation: "dam.repository.list" });
26
+ const folder = dam.command("folder")
27
+ .description("DAM 文件夹")
28
+ .helpCommand(false);
29
+ const folderList = leaf(folder.command("list"))
30
+ .description("列出文件夹")
31
+ .option("--repository-id <id>", "指定资源库 ID")
32
+ .option("--parent-id <id>", "父文件夹 ID")
33
+ .option("--query <text>", "按名称筛选")
34
+ .option("--page <n>", "页码", positiveInteger)
35
+ .option("--page-size <1-100>", "分页大小", pageSize)
36
+ .option("--json", "输出 JSON");
37
+ bindPreparedAction(folderList, "organization", async (signal) => {
38
+ signal.throwIfAborted();
39
+ const options = folderList.opts();
40
+ const input = {
41
+ ...defined("repositoryId", options.repositoryId),
42
+ ...defined("parentId", options.parentId),
43
+ ...defined("query", options.query),
44
+ ...defined("page", options.page),
45
+ ...defined("pageSize", options.pageSize)
46
+ };
47
+ runtime.validators.damFolderListInput(input);
48
+ runtime.telemetry.annotate({ parameters: { ...input } });
49
+ return { handled: false, input };
50
+ }, async (context, input, signal) => {
51
+ const result = await runtime.dam.folderList({
52
+ input,
53
+ access: organizationAccess(context.state),
54
+ signal
55
+ });
56
+ await runtime.telemetry.stage("output", async () => {
57
+ runtime.presenter.result(result, {
58
+ json: folderList.opts().json === true,
59
+ view: folderListView
60
+ });
61
+ });
62
+ }, { operation: "dam.folder.list" });
63
+ const tag = dam.command("tag")
64
+ .description("DAM 标签")
65
+ .helpCommand(false);
66
+ const tagList = leaf(tag.command("list"))
67
+ .description("列出标签")
68
+ .option("--repository-id <id>", "指定资源库 ID")
69
+ .option("--query <text>", "按名称筛选")
70
+ .option("--json", "输出 JSON");
71
+ bindPreparedAction(tagList, "organization", async (signal) => {
72
+ signal.throwIfAborted();
73
+ const options = tagList.opts();
74
+ const input = {
75
+ ...defined("repositoryId", options.repositoryId),
76
+ ...defined("query", options.query)
77
+ };
78
+ runtime.validators.damTagListInput(input);
79
+ runtime.telemetry.annotate({ parameters: { ...input } });
80
+ return { handled: false, input };
81
+ }, async (context, input, signal) => {
82
+ const result = await runtime.dam.tagList({
83
+ input,
84
+ access: organizationAccess(context.state),
85
+ signal
86
+ });
87
+ await runtime.telemetry.stage("output", async () => {
88
+ runtime.presenter.result(result, {
89
+ json: tagList.opts().json === true,
90
+ view: tagListView
91
+ });
92
+ });
93
+ }, { operation: "dam.tag.list" });
94
+ const download = leaf(dam.command("download"))
95
+ .description("下载一个素材")
96
+ .argument("<asset>", "素材 ID")
97
+ .option("--repository-id <id>", "所属资源库 ID")
98
+ .option("--output-dir <directory>", "输出目录")
99
+ .option("--json", "输出 JSON");
100
+ bindPreparedAction(download, "organization", async (signal) => {
101
+ signal.throwIfAborted();
102
+ const options = download.opts();
103
+ const input = {
104
+ asset_id: position(download, 0),
105
+ ...defined("repositoryId", options.repositoryId),
106
+ ...defined("outputDir", options.outputDir)
107
+ };
108
+ runtime.validators.damDownloadInput(input);
109
+ runtime.telemetry.annotate({ content_id: input.asset_id, parameters: { ...input } });
110
+ return { handled: false, input };
111
+ }, async (context, input, signal) => {
112
+ const result = await runtime.dam.download({
113
+ input,
114
+ access: organizationAccess(context.state),
115
+ signal
116
+ });
117
+ await runtime.telemetry.stage("output", async () => {
118
+ runtime.presenter.result(result, {
119
+ json: download.opts().json === true,
120
+ view: downloadView
121
+ });
122
+ });
123
+ }, { operation: "dam.download" });
124
+ const rename = leaf(dam.command("rename"))
125
+ .description("重命名一个素材或文件夹")
126
+ .argument("<entry>", "素材或文件夹 ID")
127
+ .requiredOption("--kind <asset|folder>", "条目类型")
128
+ .requiredOption("--title <title>", "新标题")
129
+ .option("--repository-id <id>", "所属资源库 ID")
130
+ .option("--json", "输出 JSON");
131
+ bindPreparedAction(rename, "organization", async (signal) => {
132
+ signal.throwIfAborted();
133
+ const options = rename.opts();
134
+ const input = {
135
+ entry_id: position(rename, 0),
136
+ kind: options.kind,
137
+ title: options.title,
138
+ ...defined("repositoryId", options.repositoryId)
139
+ };
140
+ runtime.validators.damRenameInput(input);
141
+ runtime.telemetry.annotate({ content_id: input.entry_id, parameters: { ...input } });
142
+ return { handled: false, input };
143
+ }, async (context, input, signal) => {
144
+ const result = await runtime.dam.rename({
145
+ input,
146
+ access: organizationAccess(context.state),
147
+ signal
148
+ });
149
+ await runtime.telemetry.stage("output", async () => {
150
+ runtime.presenter.result(result, {
151
+ json: rename.opts().json === true,
152
+ view: renameView
153
+ });
154
+ });
155
+ }, { operation: "dam.rename" });
156
+ for (const operation of ["move", "copy"]) {
157
+ const command = leaf(dam.command(operation))
158
+ .description(operation === "move" ? "移动一个素材或文件夹" : "复制一个素材或文件夹")
159
+ .argument("<entry>", "素材或文件夹 ID")
160
+ .requiredOption("--kind <asset|folder>", "条目类型")
161
+ .requiredOption("--target-folder-id <id>", "目标文件夹 ID;根目录使用 0")
162
+ .option("--repository-id <source-id>", "源资源库 ID")
163
+ .option("--target-repository-id <target-id>", "目标资源库 ID")
164
+ .option("--json", "输出 JSON");
165
+ bindPreparedAction(command, "organization", async (signal) => {
166
+ signal.throwIfAborted();
167
+ const options = command.opts();
168
+ const input = {
169
+ entry_id: position(command, 0),
170
+ kind: options.kind,
171
+ targetFolderId: options.targetFolderId,
172
+ ...defined("repositoryId", options.repositoryId),
173
+ ...defined("targetRepositoryId", options.targetRepositoryId)
174
+ };
175
+ if (operation === "move")
176
+ runtime.validators.damMoveInput(input);
177
+ else
178
+ runtime.validators.damCopyInput(input);
179
+ runtime.telemetry.annotate({ content_id: input.entry_id, parameters: { ...input } });
180
+ return { handled: false, input: input };
181
+ }, async (context, input, signal) => {
182
+ const result = operation === "move"
183
+ ? await runtime.dam.move({
184
+ input: input,
185
+ access: organizationAccess(context.state),
186
+ signal
187
+ })
188
+ : await runtime.dam.copy({
189
+ input: input,
190
+ access: organizationAccess(context.state),
191
+ signal
192
+ });
193
+ await runtime.telemetry.stage("output", async () => {
194
+ runtime.presenter.result(result, {
195
+ json: command.opts().json === true,
196
+ view: operation === "move" ? moveView : copyView
197
+ });
198
+ });
199
+ }, { operation: `dam.${operation}` });
200
+ }
7
201
  const list = browseCommand(leaf(dam.command("list")))
8
202
  .description("查看最近素材");
9
203
  bindPreparedAction(list, "organization", async (signal) => {
@@ -186,6 +380,14 @@ function pageSize(value) {
186
380
  throw new CliUsageError();
187
381
  return parsed;
188
382
  }
383
+ function positiveInteger(value) {
384
+ if (!/^\d+$/u.test(value))
385
+ throw new CliUsageError();
386
+ const parsed = Number(value);
387
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
388
+ throw new CliUsageError();
389
+ return parsed;
390
+ }
189
391
  function position(command, index) {
190
392
  const value = command.processedArgs[index];
191
393
  if (typeof value !== "string")
@@ -220,3 +422,48 @@ function assetDetailView(value) {
220
422
  ...(asset.url === undefined ? [] : [`URL:${asset.url}`])
221
423
  ].join("\n");
222
424
  }
425
+ function folderListView(value) {
426
+ const folders = value.folders;
427
+ if (folders.length === 0)
428
+ return "暂无文件夹。";
429
+ return [
430
+ "ID\t名称\t子文件夹数\t素材数",
431
+ ...folders.map((folder) => [
432
+ folder.folder_id,
433
+ folder.title,
434
+ folder.child_folder_count,
435
+ folder.asset_count
436
+ ].join("\t"))
437
+ ].join("\n");
438
+ }
439
+ function tagListView(value) {
440
+ const tags = value.tags;
441
+ if (tags.length === 0)
442
+ return "暂无标签。";
443
+ return [
444
+ "ID\t名称\t素材数",
445
+ ...tags.map((tag) => [tag.tag_id, tag.name, tag.asset_count].join("\t"))
446
+ ].join("\n");
447
+ }
448
+ function downloadView(value) {
449
+ return value.files
450
+ .map((file) => `已下载:${file.path}`)
451
+ .join("\n");
452
+ }
453
+ function renameView(value) {
454
+ const result = value;
455
+ return `已重命名:${result.title} (${result.entry_id})`;
456
+ }
457
+ function moveView(value) {
458
+ const result = value;
459
+ return `已移动至:${result.repository_id} / ${result.folder_id}`;
460
+ }
461
+ function copyView(value) {
462
+ const result = value;
463
+ return [
464
+ `已复制至:${result.repository_id} / ${result.folder_id}`,
465
+ ...(result.copied_entry_id === undefined
466
+ ? []
467
+ : [`复制条目 ID:${result.copied_entry_id}`])
468
+ ].join("\n");
469
+ }
@@ -7,8 +7,10 @@ import { AgentInputError } from "../features/agent/use-cases.js";
7
7
  import { ToolInputError } from "../features/tool/catalog.js";
8
8
  import { ToolTaskFailedError } from "../features/tool/tool-api-adapter.js";
9
9
  import { ObjectStorageUploadError } from "../features/dam/object-storage.js";
10
- import { DamAssetNotFoundError } from "../features/dam/dam-api-adapter.js";
11
- import { DamInputError } from "../features/dam/use-cases.js";
10
+ import { DamAssetNotDownloadableError, DamAssetNotFoundError, DamFolderNotFoundError } from "../features/dam/dam-api-adapter.js";
11
+ import { DamDownloadDestinationExistsError } from "../features/dam/file-downloader.js";
12
+ import { DamOperationFailedError } from "../features/dam/operation-executor.js";
13
+ import { DamInputError, DamOperationUnconfirmedError, DamRepositoryNotFoundError } from "../features/dam/use-cases.js";
12
14
  import { OrganizationResponseError } from "../features/org/org-service.js";
13
15
  import { OrganizationBindFailedError, OrganizationInvalidError, OrgUsageError } from "../features/org/use-cases.js";
14
16
  import { RemoteRequestError } from "../platform/signed-http-transport.js";
@@ -80,6 +82,24 @@ export function mapCliError(error, options) {
80
82
  if (error instanceof DamAssetNotFoundError) {
81
83
  return failure("DAM_ASSET_NOT_FOUND", "未找到指定的 DAM 素材。", 1);
82
84
  }
85
+ if (error instanceof DamFolderNotFoundError) {
86
+ return failure("DAM_FOLDER_NOT_FOUND", "未找到指定的 DAM 文件夹。", 1);
87
+ }
88
+ if (error instanceof DamAssetNotDownloadableError) {
89
+ return failure("DAM_ASSET_NOT_DOWNLOADABLE", "该素材当前没有可下载的源文件;请先查看素材状态。", 1, ["gd-cli dam get <asset-id>"]);
90
+ }
91
+ if (error instanceof DamDownloadDestinationExistsError) {
92
+ return failure("DAM_DOWNLOAD_DESTINATION_EXISTS", "目标文件已存在,未覆盖任何文件;请更换输出目录或移走同名文件。", 1);
93
+ }
94
+ if (error instanceof DamOperationUnconfirmedError) {
95
+ return failure("DAM_OPERATION_UNCONFIRMED", "操作已提交,但未能确认最终结果;请先查询目标位置,不要立即重复提交。", 1);
96
+ }
97
+ if (error instanceof DamOperationFailedError) {
98
+ return failure("DAM_OPERATION_FAILED", "DAM 操作执行失败;请先按诊断 ID 查询原因,不要立即重复提交。", 1);
99
+ }
100
+ if (error instanceof DamRepositoryNotFoundError) {
101
+ return failure("DAM_REPOSITORY_NOT_FOUND", "当前组织没有可用的 DAM 资源库。", 1, ["gd-cli org list", "gd-cli org switch --org <org-id>"]);
102
+ }
83
103
  if (error instanceof RemoteRequestError
84
104
  || error instanceof SsoRequestError
85
105
  || error instanceof DeviceAuthorizationExpiredError
@@ -74,6 +74,18 @@ export const orgCurrentView = (value) => {
74
74
  ? `当前组织: ${result.organization.name} (${result.organization.id})`
75
75
  : lines("尚未选择组织。", nextStepsLine(result.next_steps));
76
76
  };
77
+ export const repositoryListView = (value) => {
78
+ const result = value;
79
+ if (result.repositories.length === 0)
80
+ return "没有可用资源库。";
81
+ return result.repositories.map((repository) => {
82
+ const permission = repository.permission_code === undefined
83
+ ? ""
84
+ : ` · ${repository.permission_code}`;
85
+ const defaultMarker = repository.is_default ? " [默认]" : "";
86
+ return `${repository.name} (${repository.repository_id}) · ${repository.type}${permission}${defaultMarker}`;
87
+ }).join("\n");
88
+ };
77
89
  function lines(...values) {
78
90
  return values.filter((value) => Boolean(value)).join("\n");
79
91
  }