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
@@ -0,0 +1,72 @@
1
+ export class DamOperationFailedError extends Error {
2
+ operation;
3
+ constructor(operation) {
4
+ super("DAM 操作执行失败。");
5
+ this.name = "DamOperationFailedError";
6
+ this.operation = operation;
7
+ }
8
+ }
9
+ export class DamOperationUnconfirmedError extends Error {
10
+ operation;
11
+ constructor(operation) {
12
+ super("DAM 操作已提交,但未能确认最终结果。");
13
+ this.name = "DamOperationUnconfirmedError";
14
+ this.operation = operation;
15
+ }
16
+ }
17
+ function copiedEntryId(resultIds) {
18
+ const unique = [...new Set((resultIds ?? []).filter((value) => value.trim() !== ""))];
19
+ return unique.length === 1 ? unique[0] : undefined;
20
+ }
21
+ export function createDamOperationExecutor(dependencies) {
22
+ return {
23
+ async execute(input) {
24
+ input.signal.throwIfAborted();
25
+ const startedAt = dependencies.now();
26
+ const submission = await input.submit();
27
+ input.signal.throwIfAborted();
28
+ const taskId = submission.task_id;
29
+ if (taskId === undefined) {
30
+ const copied = input.operation === "copy"
31
+ ? copiedEntryId(submission.result_ids)
32
+ : undefined;
33
+ return copied === undefined ? {} : { copied_entry_id: copied };
34
+ }
35
+ dependencies.annotate({ task_id: taskId });
36
+ let delayMilliseconds = 1_000;
37
+ const deadline = startedAt + 180_000;
38
+ while (true) {
39
+ input.signal.throwIfAborted();
40
+ if (dependencies.now() >= deadline) {
41
+ throw new DamOperationUnconfirmedError(input.operation);
42
+ }
43
+ const task = await input.getTask(taskId, input.signal);
44
+ input.signal.throwIfAborted();
45
+ if (task === null)
46
+ throw new DamOperationUnconfirmedError(input.operation);
47
+ if (task.status === 2) {
48
+ if (task.fail_count !== null && task.fail_count > 0) {
49
+ throw new DamOperationFailedError(input.operation);
50
+ }
51
+ if (input.operation !== "copy" || input.inspectResult === undefined)
52
+ return {};
53
+ const result = await input.inspectResult(taskId, input.signal);
54
+ input.signal.throwIfAborted();
55
+ const copied = copiedEntryId(result.result_ids);
56
+ return copied === undefined ? {} : { copied_entry_id: copied };
57
+ }
58
+ if (task.status === 3)
59
+ throw new DamOperationFailedError(input.operation);
60
+ if (task.status !== 0 && task.status !== 1) {
61
+ throw new DamOperationUnconfirmedError(input.operation);
62
+ }
63
+ const remaining = deadline - dependencies.now();
64
+ if (remaining <= 0)
65
+ throw new DamOperationUnconfirmedError(input.operation);
66
+ await dependencies.sleep(Math.min(delayMilliseconds, remaining), input.signal);
67
+ input.signal.throwIfAborted();
68
+ delayMilliseconds = Math.min(delayMilliseconds + 500, 3_000);
69
+ }
70
+ }
71
+ };
72
+ }
@@ -0,0 +1,127 @@
1
+ import { RemoteProtocolError } from "../../platform/remote-protocol.js";
2
+ const permissions = new Set([
3
+ "OWNER",
4
+ "MANAGE",
5
+ "EDIT",
6
+ "USE",
7
+ "READ"
8
+ ]);
9
+ function record(value) {
10
+ return value !== null && typeof value === "object" && !Array.isArray(value)
11
+ ? value
12
+ : undefined;
13
+ }
14
+ function rows(value) {
15
+ if (Array.isArray(value))
16
+ return value;
17
+ const nested = record(value)?.data;
18
+ if (Array.isArray(nested))
19
+ return nested;
20
+ throw new RemoteProtocolError();
21
+ }
22
+ function nonblank(value) {
23
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
24
+ }
25
+ function permission(value) {
26
+ return typeof value === "string"
27
+ && permissions.has(value)
28
+ ? value
29
+ : undefined;
30
+ }
31
+ function privateRepositories(value) {
32
+ return rows(value).flatMap((candidate) => {
33
+ const item = record(candidate);
34
+ const repositoryId = nonblank(item?.repository_id);
35
+ const name = nonblank(item?.name) ?? nonblank(item?.title);
36
+ const type = item?.type === 1
37
+ ? "personal"
38
+ : item?.type === 6
39
+ ? "draft"
40
+ : undefined;
41
+ if (repositoryId === undefined || name === undefined || type === undefined)
42
+ return [];
43
+ return [{ repository_id: repositoryId, name, type, is_default: false }];
44
+ });
45
+ }
46
+ function teamRepositories(value) {
47
+ return rows(value).flatMap((candidate) => {
48
+ const item = record(candidate);
49
+ const repositoryId = nonblank(item?.repository_id);
50
+ const name = nonblank(item?.name) ?? nonblank(item?.title);
51
+ if (repositoryId === undefined || name === undefined)
52
+ return [];
53
+ const permissionCode = permission(item?.permission_code);
54
+ return [{
55
+ repository_id: repositoryId,
56
+ name,
57
+ type: "team",
58
+ ...(permissionCode === undefined ? {} : { permission_code: permissionCode }),
59
+ is_default: false
60
+ }];
61
+ });
62
+ }
63
+ function enterpriseRepositories(value) {
64
+ return rows(value).flatMap((candidate) => {
65
+ const item = record(candidate);
66
+ const repositoryId = nonblank(item?.id);
67
+ const name = nonblank(item?.name) ?? nonblank(item?.title);
68
+ if (repositoryId === undefined || name === undefined)
69
+ return [];
70
+ const permissionCode = permission(item?.permission_code);
71
+ return [{
72
+ repository_id: repositoryId,
73
+ name,
74
+ type: "enterprise",
75
+ ...(permissionCode === undefined ? {} : { permission_code: permissionCode }),
76
+ is_default: false
77
+ }];
78
+ });
79
+ }
80
+ export function normalizeRepositoryCatalog(privateRows, teamRows, enterpriseRows) {
81
+ const merged = [
82
+ ...privateRepositories(privateRows),
83
+ ...teamRepositories(teamRows),
84
+ ...enterpriseRepositories(enterpriseRows)
85
+ ];
86
+ const seen = new Set();
87
+ const unique = merged.filter((repository) => {
88
+ if (seen.has(repository.repository_id))
89
+ return false;
90
+ seen.add(repository.repository_id);
91
+ return true;
92
+ });
93
+ const defaultIndex = unique.findIndex((repository) => repository.type === "personal");
94
+ const selectedIndex = defaultIndex === -1 && unique.length > 0 ? 0 : defaultIndex;
95
+ return unique.map((repository, index) => ({
96
+ ...repository,
97
+ is_default: index === selectedIndex
98
+ }));
99
+ }
100
+ function requestContext(input) {
101
+ return {
102
+ credential: input.access.credential,
103
+ organizationId: input.access.organizationId,
104
+ signal: input.signal
105
+ };
106
+ }
107
+ export function createDamRepositoryCatalog(dependencies) {
108
+ return {
109
+ async list(input) {
110
+ const [privateRows, teamRows, enterpriseRows] = await Promise.all([
111
+ dependencies.transport.getJson({
112
+ path: "/tb-dam/repositories",
113
+ ...requestContext(input)
114
+ }),
115
+ dependencies.transport.getJson({
116
+ path: "/tb-dam/teams",
117
+ ...requestContext(input)
118
+ }),
119
+ dependencies.transport.getJson({
120
+ path: "/dam/org-repositories",
121
+ ...requestContext(input)
122
+ })
123
+ ]);
124
+ return normalizeRepositoryCatalog(privateRows, teamRows, enterpriseRows);
125
+ }
126
+ };
127
+ }
@@ -1,12 +1,19 @@
1
1
  import { resolve } from "node:path";
2
2
  import { pathToFileURL } from "node:url";
3
- import { RemoteRequestError } from "../../platform/signed-http-transport.js";
3
+ import { DamOperationUnconfirmedError } from "./operation-executor.js";
4
4
  export class DamInputError extends Error {
5
5
  constructor() {
6
6
  super("DAM 输入不可用。");
7
7
  this.name = "DamInputError";
8
8
  }
9
9
  }
10
+ export class DamRepositoryNotFoundError extends Error {
11
+ constructor() {
12
+ super("当前组织没有可用的 DAM 资源库。");
13
+ this.name = "DamRepositoryNotFoundError";
14
+ }
15
+ }
16
+ export { DamOperationUnconfirmedError } from "./operation-executor.js";
10
17
  function invalid() {
11
18
  throw new DamInputError();
12
19
  }
@@ -17,6 +24,14 @@ function assertPageSize(value) {
17
24
  if (value !== undefined && (!Number.isInteger(value) || value < 1 || value > 100))
18
25
  invalid();
19
26
  }
27
+ function assertPage(value) {
28
+ if (value !== undefined && (!Number.isInteger(value) || value < 1))
29
+ invalid();
30
+ }
31
+ function assertOptionalNonblank(value) {
32
+ if (value !== undefined && !nonblank(value))
33
+ invalid();
34
+ }
20
35
  function assertAssetId(value) {
21
36
  if (value.trim() === "" || /^https?:\/\//iu.test(value))
22
37
  invalid();
@@ -42,23 +57,139 @@ function searchRequest(input, keyword, repositoryIds) {
42
57
  }
43
58
  export function createDamUseCases(dependencies) {
44
59
  const validate = dependencies.validate;
60
+ async function entryTitle(input) {
61
+ if (input.kind === "asset") {
62
+ return (await dependencies.api.getAsset({
63
+ assetId: input.entryId,
64
+ access: input.access,
65
+ signal: input.signal
66
+ })).title;
67
+ }
68
+ return (await dependencies.api.getFolder({
69
+ folderId: input.entryId,
70
+ repositoryId: input.repositoryId,
71
+ access: input.access,
72
+ signal: input.signal
73
+ })).title;
74
+ }
75
+ async function entryLocation(input) {
76
+ if (input.kind === "asset") {
77
+ const detail = await dependencies.api.getAsset({
78
+ assetId: input.entryId,
79
+ access: input.access,
80
+ signal: input.signal
81
+ });
82
+ return {
83
+ ...(detail.repository_id === undefined ? {} : { repositoryId: detail.repository_id }),
84
+ ...(detail.folder_id === undefined ? {} : { folderId: detail.folder_id })
85
+ };
86
+ }
87
+ const detail = await dependencies.api.getFolder({
88
+ folderId: input.entryId,
89
+ repositoryId: input.repositoryId,
90
+ access: input.access,
91
+ signal: input.signal
92
+ });
93
+ return { repositoryId: detail.repository_id, folderId: detail.folder_id };
94
+ }
95
+ function assertEntryOperationInput(input) {
96
+ assertAssetId(input.entry_id);
97
+ if (input.kind !== "asset" && input.kind !== "folder")
98
+ invalid();
99
+ if (!nonblank(input.targetFolderId))
100
+ invalid();
101
+ assertOptionalNonblank(input.repositoryId);
102
+ assertOptionalNonblank(input.targetRepositoryId);
103
+ if (input.kind === "folder" && input.entry_id === input.targetFolderId)
104
+ invalid();
105
+ }
106
+ async function entryOperation(input) {
107
+ assertEntryOperationInput(input.request);
108
+ const [sourceRepository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
109
+ ...(input.request.repositoryId === undefined
110
+ ? {}
111
+ : { repositoryId: input.request.repositoryId })
112
+ }, input.access, input.signal)));
113
+ if (sourceRepository === undefined)
114
+ throw new DamRepositoryNotFoundError();
115
+ const targetRepositoryId = input.request.targetRepositoryId ?? sourceRepository.id;
116
+ const locationRequest = {
117
+ entryId: input.request.entry_id,
118
+ kind: input.request.kind,
119
+ repositoryId: sourceRepository.id,
120
+ access: input.access,
121
+ signal: input.signal
122
+ };
123
+ const current = await dependencies.telemetry.stage("resolve", () => (entryLocation(locationRequest)));
124
+ if (input.operation === "move"
125
+ && current.repositoryId === targetRepositoryId
126
+ && current.folderId === input.request.targetFolderId)
127
+ invalid();
128
+ const execution = await dependencies.telemetry.stage("wait", () => (dependencies.operations.execute({
129
+ operation: input.operation,
130
+ submit: () => dependencies.telemetry.stage("submit", () => (dependencies.api.submitEntryOperation({
131
+ entryId: input.request.entry_id,
132
+ kind: input.request.kind,
133
+ operation: input.operation,
134
+ sourceRepositoryId: sourceRepository.id,
135
+ targetRepositoryId,
136
+ targetFolderId: input.request.targetFolderId,
137
+ access: input.access,
138
+ signal: input.signal
139
+ }))),
140
+ getTask: (taskId, signal) => dependencies.api.getOperationTask({
141
+ taskId,
142
+ access: input.access,
143
+ signal
144
+ }),
145
+ ...(input.operation === "copy"
146
+ ? {
147
+ inspectResult: (taskId, signal) => (dependencies.api.getOperationChildren({
148
+ taskId,
149
+ access: input.access,
150
+ signal
151
+ }))
152
+ }
153
+ : {}),
154
+ signal: input.signal
155
+ })));
156
+ return {
157
+ sourceRepositoryId: sourceRepository.id,
158
+ targetRepositoryId,
159
+ targetFolderId: input.request.targetFolderId,
160
+ ...(execution.copied_entry_id === undefined
161
+ ? {}
162
+ : { copiedEntryId: execution.copied_entry_id })
163
+ };
164
+ }
45
165
  async function resolveRepositories(input, access, signal) {
46
166
  assertRepositorySelection(input);
47
167
  if (input.repositoryId !== undefined)
48
168
  return [{ id: input.repositoryId }];
49
169
  signal.throwIfAborted();
50
- const available = await dependencies.api.listRepositories({ access, signal });
170
+ const available = await dependencies.repositories.list({ access, signal });
51
171
  signal.throwIfAborted();
52
172
  if (available.length === 0)
53
- throw new RemoteRequestError();
54
- if (input.allRepositories === true)
55
- return available;
56
- const selected = available.find((repository) => repository.type === 1) ?? available[0];
57
- if (selected === undefined)
58
- throw new RemoteRequestError();
59
- return [selected];
173
+ throw new DamRepositoryNotFoundError();
174
+ const selected = input.allRepositories === true
175
+ ? available
176
+ : [available.find((repository) => repository.is_default) ?? available[0]];
177
+ return selected.map((repository) => ({
178
+ id: repository.repository_id,
179
+ ...(repository.name === undefined ? {} : { name: repository.name })
180
+ }));
60
181
  }
61
182
  return {
183
+ async repositoryList({ access, signal }) {
184
+ signal.throwIfAborted();
185
+ const repositories = await dependencies.repositories.list({ access, signal });
186
+ signal.throwIfAborted();
187
+ if (repositories.length === 0)
188
+ throw new DamRepositoryNotFoundError();
189
+ const result = { repositories };
190
+ validate.repositoryList(result);
191
+ return result;
192
+ },
62
193
  async list({ input, access, signal }) {
63
194
  signal.throwIfAborted();
64
195
  assertPageSize(input.pageSize);
@@ -91,6 +222,187 @@ export function createDamUseCases(dependencies) {
91
222
  return result;
92
223
  });
93
224
  },
225
+ async folderList({ input, access, signal }) {
226
+ signal.throwIfAborted();
227
+ assertOptionalNonblank(input.parentId);
228
+ assertOptionalNonblank(input.query);
229
+ assertPage(input.page);
230
+ assertPageSize(input.pageSize);
231
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
232
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
233
+ }, access, signal)));
234
+ if (repository === undefined)
235
+ throw new DamRepositoryNotFoundError();
236
+ return dependencies.telemetry.stage("request", async () => {
237
+ const result = await dependencies.api.listFolders({
238
+ repositoryId: repository.id,
239
+ parentId: input.parentId ?? "0",
240
+ page: input.page ?? 1,
241
+ pageSize: input.pageSize ?? 20,
242
+ ...(input.query === undefined ? {} : { query: input.query }),
243
+ access,
244
+ signal
245
+ });
246
+ signal.throwIfAborted();
247
+ validate.folderList(result);
248
+ return result;
249
+ });
250
+ },
251
+ async tagList({ input, access, signal }) {
252
+ signal.throwIfAborted();
253
+ assertOptionalNonblank(input.query);
254
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
255
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
256
+ }, access, signal)));
257
+ if (repository === undefined)
258
+ throw new DamRepositoryNotFoundError();
259
+ return dependencies.telemetry.stage("request", async () => {
260
+ const result = await dependencies.api.listTags({
261
+ repositoryId: repository.id,
262
+ ...(input.query === undefined ? {} : { query: input.query }),
263
+ access,
264
+ signal
265
+ });
266
+ signal.throwIfAborted();
267
+ validate.tagList(result);
268
+ return result;
269
+ });
270
+ },
271
+ async download({ input, access, signal }) {
272
+ signal.throwIfAborted();
273
+ assertAssetId(input.asset_id);
274
+ assertOptionalNonblank(input.outputDir);
275
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
276
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
277
+ }, access, signal)));
278
+ if (repository === undefined)
279
+ throw new DamRepositoryNotFoundError();
280
+ const detail = await dependencies.telemetry.stage("resolve", () => (dependencies.api.getAsset({ assetId: input.asset_id, access, signal })));
281
+ signal.throwIfAborted();
282
+ const urls = await dependencies.telemetry.stage("download", () => (dependencies.api.getDownloadUrls({
283
+ assetId: input.asset_id,
284
+ repositoryId: repository.id,
285
+ access,
286
+ signal
287
+ })));
288
+ signal.throwIfAborted();
289
+ const files = await dependencies.telemetry.stage("persist", () => (dependencies.downloader.download({
290
+ urls,
291
+ ...(detail.title === undefined ? {} : { fallbackTitle: detail.title }),
292
+ ...(detail.format === undefined ? {} : { fallbackFormat: detail.format }),
293
+ outputDirectory: input.outputDir ?? ".",
294
+ signal
295
+ })));
296
+ signal.throwIfAborted();
297
+ const result = { asset_id: input.asset_id, files };
298
+ validate.download(result);
299
+ return result;
300
+ },
301
+ async rename({ input, access, signal }) {
302
+ signal.throwIfAborted();
303
+ assertAssetId(input.entry_id);
304
+ if (input.kind !== "asset" && input.kind !== "folder")
305
+ invalid();
306
+ if (!nonblank(input.title) || input.title.length > 100)
307
+ invalid();
308
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
309
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
310
+ }, access, signal)));
311
+ if (repository === undefined)
312
+ throw new DamRepositoryNotFoundError();
313
+ const request = {
314
+ entryId: input.entry_id,
315
+ kind: input.kind,
316
+ repositoryId: repository.id,
317
+ access,
318
+ signal
319
+ };
320
+ const currentTitle = await dependencies.telemetry.stage("rename", async () => {
321
+ const title = await entryTitle(request);
322
+ signal.throwIfAborted();
323
+ if (title?.trim() !== input.title.trim()) {
324
+ await dependencies.api.renameEntry({
325
+ ...request,
326
+ repositoryId: repository.id,
327
+ title: input.title
328
+ });
329
+ }
330
+ return title;
331
+ });
332
+ if (currentTitle?.trim() === input.title.trim()) {
333
+ const result = {
334
+ entry_id: input.entry_id,
335
+ kind: input.kind,
336
+ repository_id: repository.id,
337
+ title: currentTitle
338
+ };
339
+ validate.rename(result);
340
+ return result;
341
+ }
342
+ await dependencies.telemetry.stage("verify", async () => {
343
+ for (let attempt = 0; attempt <= 30; attempt += 1) {
344
+ signal.throwIfAborted();
345
+ if (await entryTitle(request) === input.title)
346
+ return;
347
+ if (attempt < 30)
348
+ await dependencies.sleep(2_000, signal);
349
+ }
350
+ throw new DamOperationUnconfirmedError("rename");
351
+ });
352
+ const result = {
353
+ entry_id: input.entry_id,
354
+ kind: input.kind,
355
+ repository_id: repository.id,
356
+ title: input.title
357
+ };
358
+ validate.rename(result);
359
+ return result;
360
+ },
361
+ async move({ input, access, signal }) {
362
+ signal.throwIfAborted();
363
+ const operation = await entryOperation({ operation: "move", request: input, access, signal });
364
+ await dependencies.telemetry.stage("verify", async () => {
365
+ for (let attempt = 0; attempt <= 30; attempt += 1) {
366
+ signal.throwIfAborted();
367
+ const location = await entryLocation({
368
+ entryId: input.entry_id,
369
+ kind: input.kind,
370
+ repositoryId: operation.targetRepositoryId,
371
+ access,
372
+ signal
373
+ });
374
+ if (location.repositoryId === operation.targetRepositoryId
375
+ && location.folderId === operation.targetFolderId)
376
+ return;
377
+ if (attempt < 30)
378
+ await dependencies.sleep(2_000, signal);
379
+ }
380
+ throw new DamOperationUnconfirmedError("move");
381
+ });
382
+ const result = {
383
+ entry_id: input.entry_id,
384
+ kind: input.kind,
385
+ repository_id: operation.targetRepositoryId,
386
+ folder_id: operation.targetFolderId
387
+ };
388
+ await dependencies.telemetry.stage("output", () => { validate.move(result); });
389
+ return result;
390
+ },
391
+ async copy({ input, access, signal }) {
392
+ signal.throwIfAborted();
393
+ const operation = await entryOperation({ operation: "copy", request: input, access, signal });
394
+ const result = {
395
+ source_entry_id: input.entry_id,
396
+ kind: input.kind,
397
+ repository_id: operation.targetRepositoryId,
398
+ folder_id: operation.targetFolderId,
399
+ ...(operation.copiedEntryId === undefined
400
+ ? {}
401
+ : { copied_entry_id: operation.copiedEntryId })
402
+ };
403
+ await dependencies.telemetry.stage("output", () => { validate.copy(result); });
404
+ return result;
405
+ },
94
406
  async get({ input, access, signal }) {
95
407
  signal.throwIfAborted();
96
408
  assertAssetId(input.asset_id);
@@ -120,7 +432,7 @@ export function createDamUseCases(dependencies) {
120
432
  ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
121
433
  }, access, signal)));
122
434
  if (repository === undefined)
123
- throw new RemoteRequestError();
435
+ throw new DamRepositoryNotFoundError();
124
436
  const result = await dependencies.uploader.upload({
125
437
  input,
126
438
  file,
@@ -139,7 +451,7 @@ export function createDamUseCases(dependencies) {
139
451
  ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
140
452
  }, access, signal)));
141
453
  if (repository === undefined)
142
- throw new RemoteRequestError();
454
+ throw new DamRepositoryNotFoundError();
143
455
  const request = {
144
456
  repositoryId: repository.id,
145
457
  assetId: input.asset_id,
@@ -3,11 +3,14 @@ import { createServer } from "node:http";
3
3
  import { WebSocket, WebSocketServer } from "ws";
4
4
  import { EDITOR_PROTOCOL_VERSION } from "./protocol.js";
5
5
  const DEFAULT_RPC_TIMEOUT_MS = 60_000;
6
+ const DEFAULT_SAVE_RPC_TIMEOUT_MS = 5 * 60_000 + 30_000;
6
7
  export async function startEditorBridge(options) {
7
8
  const token = options.token ?? randomBytes(32).toString("hex");
8
9
  const now = options.now ?? (() => new Date());
9
10
  const nextRequestId = options.requestId ?? randomUUID;
10
- const rpcTimeoutMs = Math.max(0, options.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS);
11
+ const rpcTimeoutOverrideMs = options.rpcTimeoutMs === undefined
12
+ ? undefined
13
+ : Math.max(0, options.rpcTimeoutMs);
11
14
  const webSockets = new WebSocketServer({ noServer: true });
12
15
  let pageSocket = null;
13
16
  let pending;
@@ -146,6 +149,8 @@ export async function startEditorBridge(options) {
146
149
  }
147
150
  const requestId = nextRequestId();
148
151
  const socket = pageSocket;
152
+ const rpcTimeoutMs = rpcTimeoutOverrideMs
153
+ ?? (body.method === "save" ? DEFAULT_SAVE_RPC_TIMEOUT_MS : DEFAULT_RPC_TIMEOUT_MS);
149
154
  const timer = setTimeout(() => {
150
155
  const current = takePending(requestId);
151
156
  if (!current)
@@ -3,7 +3,8 @@ export function canonicalizeQuery(entries) {
3
3
  const sorted = [...entries].sort(([left], [right]) => left.localeCompare(right));
4
4
  return new URLSearchParams(sorted.map(([key, value]) => [key, value]))
5
5
  .toString()
6
- .replaceAll("%2C", ",");
6
+ .replaceAll("%2C", ",")
7
+ .replaceAll("%3A", ":");
7
8
  }
8
9
  export function createSigningString(input) {
9
10
  return `${input.method.toUpperCase()}@${input.pathname}@${input.canonicalQuery}@${input.timestamp}`;
@@ -108,13 +108,21 @@ export function createSignedHttpTransport(options) {
108
108
  throw new RemoteProtocolError(evidence);
109
109
  }
110
110
  }
111
+ async function getJsonResponse(request) {
112
+ const response = await send("GET", request, "application/json", undefined, true);
113
+ return { body: await json(response), headers: response.headers };
114
+ }
111
115
  return {
112
116
  async getJson(request) {
113
- return json(await send("GET", request, "application/json", undefined, true));
117
+ return (await getJsonResponse(request)).body;
114
118
  },
119
+ getJsonResponse,
115
120
  async postJson(request) {
116
121
  return json(await send("POST", request, "application/json", JSON.stringify(request.body), true));
117
122
  },
123
+ async putJson(request) {
124
+ return json(await send("PUT", request, "application/json", JSON.stringify(request.body), true));
125
+ },
118
126
  async postStream(request) {
119
127
  const response = await send("POST", request, request.accept, JSON.stringify(request.body), false);
120
128
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaoding-cli",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.22",
4
4
  "description": "Gaoding command-line interface for agents and people.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,7 @@ description: Use when creating images, videos or text with GD CLI, operating the
9
9
 
10
10
  - 按业务意图选择 Agent / Tool 创作路由、发现 Model 并生成图片、视频或文本:[创作](references/creation.md)
11
11
  - 操作当前 AI+ Editor 作品:[编辑器](references/editor.md)
12
- - 查询、上传或删除 DAM 素材:[素材管理](references/dam.md)
12
+ - 发现资源库、文件夹与标签,查询、上传、下载或单条目管理 DAM 资源:[素材管理](references/dam.md)
13
13
  - 登录或选择组织:[登录与组织](references/auth-org.md)
14
14
  - 处理输出、警告、失败或中断:[错误处理](references/errors.md)
15
15
  - 更新 CLI 与随包 Agent Skill:[更新](references/update.md)