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.
- package/contracts/operations/dam.copy/input.schema.json +19 -0
- package/contracts/operations/dam.copy/output.schema.json +19 -0
- package/contracts/operations/dam.download/input.schema.json +14 -0
- package/contracts/operations/dam.download/output.schema.json +25 -0
- package/contracts/operations/dam.folder.list/input.schema.json +15 -0
- package/contracts/operations/dam.folder.list/output.schema.json +37 -0
- package/contracts/operations/dam.list/output.schema.json +3 -3
- package/contracts/operations/dam.move/input.schema.json +19 -0
- package/contracts/operations/dam.move/output.schema.json +18 -0
- package/contracts/operations/dam.recycle.list/input.schema.json +13 -0
- package/contracts/operations/dam.recycle.list/output.schema.json +34 -0
- package/contracts/operations/dam.recycle.restore/input.schema.json +14 -0
- package/contracts/operations/dam.recycle.restore/output.schema.json +6 -0
- package/contracts/operations/dam.rename/input.schema.json +18 -0
- package/contracts/operations/dam.rename/output.schema.json +18 -0
- package/contracts/operations/dam.repository.list/input.schema.json +8 -0
- package/contracts/operations/dam.repository.list/output.schema.json +52 -0
- package/contracts/operations/dam.search/output.schema.json +3 -3
- package/contracts/operations/dam.tag.list/input.schema.json +12 -0
- package/contracts/operations/dam.tag.list/output.schema.json +26 -0
- package/dist/src/bootstrap/create-runtime.js +24 -0
- package/dist/src/bootstrap/validators.js +39 -0
- package/dist/src/cli/dam-commands.js +248 -1
- package/dist/src/cli/errors.js +22 -2
- package/dist/src/cli/presenter.js +12 -0
- package/dist/src/features/dam/dam-api-adapter.js +369 -28
- package/dist/src/features/dam/file-downloader.js +242 -0
- package/dist/src/features/dam/operation-executor.js +72 -0
- package/dist/src/features/dam/repository-catalog.js +127 -0
- package/dist/src/features/dam/use-cases.js +323 -11
- package/dist/src/features/editor/bridge-server.js +6 -1
- package/dist/src/platform/signature.js +2 -1
- package/dist/src/platform/signed-http-transport.js +9 -1
- package/package.json +1 -1
- package/skills/gd-cli/SKILL.md +1 -1
- package/skills/gd-cli/references/dam.md +45 -13
- 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
|
+
}
|
package/dist/src/cli/errors.js
CHANGED
|
@@ -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 {
|
|
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
|
}
|