@gaoding/cli 0.0.0 → 1.0.0

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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -3
  3. package/contracts/operations/agent.send/input.schema.json +353 -0
  4. package/contracts/operations/agent.send/output.schema.json +271 -0
  5. package/contracts/operations/auth.status/input.schema.json +7 -0
  6. package/contracts/operations/auth.status/output.schema.json +132 -0
  7. package/contracts/operations/dam.copy/input.schema.json +19 -0
  8. package/contracts/operations/dam.copy/output.schema.json +19 -0
  9. package/contracts/operations/dam.delete/input.schema.json +23 -0
  10. package/contracts/operations/dam.delete/output.schema.json +6 -0
  11. package/contracts/operations/dam.download/input.schema.json +14 -0
  12. package/contracts/operations/dam.download/output.schema.json +25 -0
  13. package/contracts/operations/dam.folder.list/input.schema.json +15 -0
  14. package/contracts/operations/dam.folder.list/output.schema.json +37 -0
  15. package/contracts/operations/dam.get/input.schema.json +16 -0
  16. package/contracts/operations/dam.get/output.schema.json +46 -0
  17. package/contracts/operations/dam.list/input.schema.json +44 -0
  18. package/contracts/operations/dam.list/output.schema.json +71 -0
  19. package/contracts/operations/dam.move/input.schema.json +19 -0
  20. package/contracts/operations/dam.move/output.schema.json +18 -0
  21. package/contracts/operations/dam.recycle.list/input.schema.json +13 -0
  22. package/contracts/operations/dam.recycle.list/output.schema.json +34 -0
  23. package/contracts/operations/dam.recycle.restore/input.schema.json +14 -0
  24. package/contracts/operations/dam.recycle.restore/output.schema.json +6 -0
  25. package/contracts/operations/dam.rename/input.schema.json +18 -0
  26. package/contracts/operations/dam.rename/output.schema.json +18 -0
  27. package/contracts/operations/dam.repository.list/input.schema.json +8 -0
  28. package/contracts/operations/dam.repository.list/output.schema.json +52 -0
  29. package/contracts/operations/dam.search/input.schema.json +46 -0
  30. package/contracts/operations/dam.search/output.schema.json +71 -0
  31. package/contracts/operations/dam.tag.list/input.schema.json +12 -0
  32. package/contracts/operations/dam.tag.list/output.schema.json +26 -0
  33. package/contracts/operations/dam.upload/input.schema.json +31 -0
  34. package/contracts/operations/dam.upload/output.schema.json +46 -0
  35. package/contracts/operations/editor.apply/input.schema.json +134 -0
  36. package/contracts/operations/editor.apply/output.schema.json +15 -0
  37. package/contracts/operations/editor.connect/input.schema.json +15 -0
  38. package/contracts/operations/editor.connect/output.schema.json +17 -0
  39. package/contracts/operations/editor.disconnect/input.schema.json +8 -0
  40. package/contracts/operations/editor.disconnect/output.schema.json +14 -0
  41. package/contracts/operations/editor.save/input.schema.json +8 -0
  42. package/contracts/operations/editor.save/output.schema.json +23 -0
  43. package/contracts/operations/editor.screenshot/input.schema.json +8 -0
  44. package/contracts/operations/editor.screenshot/output.schema.json +16 -0
  45. package/contracts/operations/editor.snapshot/input.schema.json +8 -0
  46. package/contracts/operations/editor.snapshot/output.schema.json +113 -0
  47. package/contracts/operations/model.get/input.schema.json +18 -0
  48. package/contracts/operations/model.get/output.schema.json +191 -0
  49. package/contracts/operations/model.list/input.schema.json +15 -0
  50. package/contracts/operations/model.list/output.schema.json +111 -0
  51. package/contracts/operations/org.current/input.schema.json +7 -0
  52. package/contracts/operations/org.current/output.schema.json +49 -0
  53. package/contracts/operations/org.list/input.schema.json +7 -0
  54. package/contracts/operations/org.list/output.schema.json +71 -0
  55. package/contracts/operations/tool.call/input.schema.json +35 -0
  56. package/contracts/operations/tool.call/output.schema.json +129 -0
  57. package/contracts/operations/tool.list/input.schema.json +7 -0
  58. package/contracts/operations/tool.list/output.schema.json +45 -0
  59. package/dist/bin/gd-cli.js +60 -0
  60. package/dist/bin/postinstall.js +42 -0
  61. package/dist/src/bootstrap/create-cli.js +112 -0
  62. package/dist/src/bootstrap/create-runtime.js +290 -0
  63. package/dist/src/bootstrap/validators.js +157 -0
  64. package/dist/src/cli/action-binding.js +109 -0
  65. package/dist/src/cli/agent-commands.js +49 -0
  66. package/dist/src/cli/auth-commands.js +55 -0
  67. package/dist/src/cli/dam-commands.js +469 -0
  68. package/dist/src/cli/editor-commands.js +73 -0
  69. package/dist/src/cli/errors.js +157 -0
  70. package/dist/src/cli/model-commands.js +61 -0
  71. package/dist/src/cli/org-commands.js +58 -0
  72. package/dist/src/cli/presenter.js +105 -0
  73. package/dist/src/cli/prompt.js +30 -0
  74. package/dist/src/cli/tool-commands.js +77 -0
  75. package/dist/src/cli/update-command.js +14 -0
  76. package/dist/src/contracts/schema.js +4 -0
  77. package/dist/src/features/agent/creative-agent-adapter.js +36 -0
  78. package/dist/src/features/agent/creative-protocol.js +432 -0
  79. package/dist/src/features/agent/creative-stream.js +127 -0
  80. package/dist/src/features/agent/use-cases.js +150 -0
  81. package/dist/src/features/auth/access-policy.js +101 -0
  82. package/dist/src/features/auth/credential-store.js +62 -0
  83. package/dist/src/features/auth/sso-service.js +194 -0
  84. package/dist/src/features/auth/state.js +129 -0
  85. package/dist/src/features/auth/use-cases.js +136 -0
  86. package/dist/src/features/dam/asset-projection.js +278 -0
  87. package/dist/src/features/dam/dam-api-adapter.js +562 -0
  88. package/dist/src/features/dam/file-downloader.js +242 -0
  89. package/dist/src/features/dam/object-storage.js +193 -0
  90. package/dist/src/features/dam/operation-executor.js +72 -0
  91. package/dist/src/features/dam/registered-uploader.js +247 -0
  92. package/dist/src/features/dam/repository-catalog.js +127 -0
  93. package/dist/src/features/dam/storage-upload.js +141 -0
  94. package/dist/src/features/dam/transient-uploader.js +17 -0
  95. package/dist/src/features/dam/use-cases.js +470 -0
  96. package/dist/src/features/editor/bridge-client.js +109 -0
  97. package/dist/src/features/editor/bridge-process.js +222 -0
  98. package/dist/src/features/editor/bridge-server.js +337 -0
  99. package/dist/src/features/editor/protocol.js +1 -0
  100. package/dist/src/features/editor/public-url.js +36 -0
  101. package/dist/src/features/editor/session-state.js +78 -0
  102. package/dist/src/features/editor/session.js +207 -0
  103. package/dist/src/features/editor/use-cases.js +88 -0
  104. package/dist/src/features/org/org-service.js +76 -0
  105. package/dist/src/features/org/use-cases.js +156 -0
  106. package/dist/src/features/skill/bundled-skills.js +55 -0
  107. package/dist/src/features/skill/installer.js +271 -0
  108. package/dist/src/features/tool/catalog.js +83 -0
  109. package/dist/src/features/tool/dynamic-schema.js +128 -0
  110. package/dist/src/features/tool/mns-catalog-adapter.js +265 -0
  111. package/dist/src/features/tool/tool-api-adapter.js +352 -0
  112. package/dist/src/features/tool/use-cases.js +177 -0
  113. package/dist/src/features/update/latest-version.js +37 -0
  114. package/dist/src/features/update/update-notification.js +262 -0
  115. package/dist/src/features/update/update-service.js +183 -0
  116. package/dist/src/platform/json-input.js +67 -0
  117. package/dist/src/platform/local-json-file.js +59 -0
  118. package/dist/src/platform/open-browser.js +8 -0
  119. package/dist/src/platform/redact.js +80 -0
  120. package/dist/src/platform/remote-error-evidence.js +80 -0
  121. package/dist/src/platform/remote-protocol.js +10 -0
  122. package/dist/src/platform/safe-upload-file.js +146 -0
  123. package/dist/src/platform/signature.js +20 -0
  124. package/dist/src/platform/signed-http-transport.js +143 -0
  125. package/dist/src/platform/url-safety.js +100 -0
  126. package/dist/src/telemetry/invocation.js +156 -0
  127. package/dist/src/telemetry/sls-sink.js +31 -0
  128. package/package.json +54 -8
  129. package/skills/gd-cli/SKILL.md +15 -0
  130. package/skills/gd-cli/references/auth-org.md +9 -0
  131. package/skills/gd-cli/references/creation.md +115 -0
  132. package/skills/gd-cli/references/dam.md +52 -0
  133. package/skills/gd-cli/references/editor.md +254 -0
  134. package/skills/gd-cli/references/errors.md +13 -0
  135. package/skills/gd-cli/references/update.md +13 -0
  136. package/index.js +0 -3
@@ -0,0 +1,470 @@
1
+ import { resolve } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { DamOperationUnconfirmedError } from "./operation-executor.js";
4
+ export class DamInputError extends Error {
5
+ constructor() {
6
+ super("DAM 输入不可用。");
7
+ this.name = "DamInputError";
8
+ }
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";
17
+ function invalid() {
18
+ throw new DamInputError();
19
+ }
20
+ function nonblank(value) {
21
+ return value !== undefined && value.trim() !== "";
22
+ }
23
+ function assertPageSize(value) {
24
+ if (value !== undefined && (!Number.isInteger(value) || value < 1 || value > 100))
25
+ invalid();
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
+ }
35
+ function assertAssetId(value) {
36
+ if (value.trim() === "" || /^https?:\/\//iu.test(value))
37
+ invalid();
38
+ }
39
+ function assertRepositorySelection(input) {
40
+ if (input.repositoryId !== undefined && !nonblank(input.repositoryId))
41
+ invalid();
42
+ if (input.repositoryId !== undefined && input.allRepositories === true)
43
+ invalid();
44
+ }
45
+ function searchRequest(input, keyword, repositoryIds) {
46
+ return {
47
+ keyword,
48
+ repositoryIds,
49
+ includeChildFolder: input.includeChildFolder ?? true,
50
+ pageSize: input.pageSize ?? 20,
51
+ ...(input.folderId === undefined ? {} : { folderId: input.folderId }),
52
+ ...(input.type === undefined ? {} : { type: input.type }),
53
+ ...(input.assetFormats === undefined ? {} : { assetFormats: [...input.assetFormats] }),
54
+ ...(input.tagIds === undefined ? {} : { tagIds: [...input.tagIds] }),
55
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor })
56
+ };
57
+ }
58
+ export function createDamUseCases(dependencies) {
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
+ }
165
+ async function resolveRepositories(input, access, signal) {
166
+ assertRepositorySelection(input);
167
+ if (input.repositoryId !== undefined)
168
+ return [{ id: input.repositoryId }];
169
+ signal.throwIfAborted();
170
+ const available = await dependencies.repositories.list({ access, signal });
171
+ signal.throwIfAborted();
172
+ if (available.length === 0)
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
+ }));
181
+ }
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
+ },
193
+ async list({ input, access, signal }) {
194
+ signal.throwIfAborted();
195
+ assertPageSize(input.pageSize);
196
+ const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
197
+ return dependencies.telemetry.stage("request", async () => {
198
+ const result = await dependencies.api.search({
199
+ ...searchRequest(input, "", repositories.map((repository) => repository.id)),
200
+ access,
201
+ signal
202
+ });
203
+ signal.throwIfAborted();
204
+ validate.list(result);
205
+ return result;
206
+ });
207
+ },
208
+ async search({ input, access, signal }) {
209
+ signal.throwIfAborted();
210
+ if (!nonblank(input.query))
211
+ invalid();
212
+ assertPageSize(input.pageSize);
213
+ const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
214
+ return dependencies.telemetry.stage("request", async () => {
215
+ const result = await dependencies.api.search({
216
+ ...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
217
+ access,
218
+ signal
219
+ });
220
+ signal.throwIfAborted();
221
+ validate.search(result);
222
+ return result;
223
+ });
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
+ },
406
+ async get({ input, access, signal }) {
407
+ signal.throwIfAborted();
408
+ assertAssetId(input.asset_id);
409
+ return dependencies.telemetry.stage("request", async () => {
410
+ const result = await dependencies.api.getAsset({
411
+ assetId: input.asset_id,
412
+ access,
413
+ signal
414
+ });
415
+ signal.throwIfAborted();
416
+ validate.get(result);
417
+ return result;
418
+ });
419
+ },
420
+ async upload({ input, access, signal }) {
421
+ signal.throwIfAborted();
422
+ if (!nonblank(input.file))
423
+ invalid();
424
+ const file = await dependencies.telemetry.stage("inspect", () => (dependencies.storage.inspect({
425
+ url: pathToFileURL(resolve(process.cwd(), input.file)).href,
426
+ ...(input.allowSensitivePath === undefined
427
+ ? {}
428
+ : { allowSensitivePath: input.allowSensitivePath })
429
+ }, signal)));
430
+ signal.throwIfAborted();
431
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
432
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
433
+ }, access, signal)));
434
+ if (repository === undefined)
435
+ throw new DamRepositoryNotFoundError();
436
+ const result = await dependencies.uploader.upload({
437
+ input,
438
+ file,
439
+ repository,
440
+ access,
441
+ signal
442
+ });
443
+ signal.throwIfAborted();
444
+ validate.upload(result);
445
+ return result;
446
+ },
447
+ async delete({ input, access, signal }) {
448
+ signal.throwIfAborted();
449
+ assertAssetId(input.asset_id);
450
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
451
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
452
+ }, access, signal)));
453
+ if (repository === undefined)
454
+ throw new DamRepositoryNotFoundError();
455
+ const request = {
456
+ repositoryId: repository.id,
457
+ assetId: input.asset_id,
458
+ access,
459
+ signal
460
+ };
461
+ await dependencies.telemetry.stage("recycle", () => dependencies.api.recycle(request));
462
+ signal.throwIfAborted();
463
+ if (input.permanent === true) {
464
+ await dependencies.telemetry.stage("delete", () => dependencies.api.deleteRecycled(request));
465
+ }
466
+ signal.throwIfAborted();
467
+ validate.delete(null);
468
+ }
469
+ };
470
+ }
@@ -0,0 +1,109 @@
1
+ export class EditorBridgeError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.name = "EditorBridgeError";
6
+ this.code = code;
7
+ }
8
+ }
9
+ export class EditorBridgeUnavailableError extends Error {
10
+ constructor() {
11
+ super("Editor Bridge is unavailable.");
12
+ this.name = "EditorBridgeUnavailableError";
13
+ }
14
+ }
15
+ export function createEditorBridgeClient(options) {
16
+ const fetch = options.fetch ?? globalThis.fetch;
17
+ const baseUrl = `http://127.0.0.1:${options.state.port}`;
18
+ return {
19
+ async health(signal) {
20
+ const value = await request("/health", "GET", undefined, signal);
21
+ if (!isRecord(value) ||
22
+ !hasExactKeys(value, ["status", "pageConnected", "protocolVersion"]) ||
23
+ value.status !== "ok" ||
24
+ typeof value.pageConnected !== "boolean" ||
25
+ typeof value.protocolVersion !== "number") {
26
+ throw invalidResponse();
27
+ }
28
+ return {
29
+ status: "ok",
30
+ pageConnected: value.pageConnected,
31
+ protocolVersion: value.protocolVersion
32
+ };
33
+ },
34
+ async rpc(method, payload, signal) {
35
+ const value = await request("/rpc", "POST", { method, ...(payload === undefined ? {} : { payload }) }, signal);
36
+ if (!isRecord(value) ||
37
+ (!hasExactKeys(value, []) && !hasExactKeys(value, ["result"]))) {
38
+ throw invalidResponse();
39
+ }
40
+ return value.result;
41
+ },
42
+ async disconnect(signal) {
43
+ const value = await request("/disconnect", "POST", {}, signal);
44
+ if (!isRecord(value) ||
45
+ !hasExactKeys(value, ["status"]) ||
46
+ value.status !== "disconnected") {
47
+ throw invalidResponse();
48
+ }
49
+ }
50
+ };
51
+ async function request(pathname, method, body, signal) {
52
+ signal.throwIfAborted();
53
+ let response;
54
+ try {
55
+ response = await fetch(`${baseUrl}${pathname}`, {
56
+ method,
57
+ headers: {
58
+ Authorization: `Bearer ${options.state.token}`,
59
+ ...(body === undefined ? {} : { "Content-Type": "application/json" })
60
+ },
61
+ redirect: "error",
62
+ signal,
63
+ ...(body === undefined ? {} : { body: JSON.stringify(body) })
64
+ });
65
+ }
66
+ catch {
67
+ signal.throwIfAborted();
68
+ throw new EditorBridgeUnavailableError();
69
+ }
70
+ let value;
71
+ try {
72
+ value = await response.json();
73
+ }
74
+ catch {
75
+ throw invalidResponse();
76
+ }
77
+ if (!response.ok) {
78
+ const error = readStructuredError(value);
79
+ if (!error)
80
+ throw invalidResponse();
81
+ throw new EditorBridgeError(error.code, error.message);
82
+ }
83
+ return value;
84
+ }
85
+ }
86
+ function readStructuredError(value) {
87
+ if (!isRecord(value) || !isRecord(value.error))
88
+ return null;
89
+ const { code, message } = value.error;
90
+ if (typeof code !== "string" ||
91
+ !/^[A-Z][A-Z0-9_]*$/.test(code) ||
92
+ typeof message !== "string" ||
93
+ message.trim().length === 0) {
94
+ return null;
95
+ }
96
+ return { code, message };
97
+ }
98
+ function invalidResponse() {
99
+ return new EditorBridgeError("EDITOR_BRIDGE_INVALID_RESPONSE", "Editor Bridge 返回了无效响应。");
100
+ }
101
+ function isRecord(value) {
102
+ return typeof value === "object" && value !== null && !Array.isArray(value);
103
+ }
104
+ function hasExactKeys(value, keys) {
105
+ const actual = Object.keys(value).sort();
106
+ const expected = [...keys].sort();
107
+ return actual.length === expected.length &&
108
+ actual.every((key, index) => key === expected[index]);
109
+ }