gaoding-cli 1.0.0-alpha.10

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/contracts/operations/agent.send/input.schema.json +354 -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.delete/input.schema.json +23 -0
  8. package/contracts/operations/dam.delete/output.schema.json +6 -0
  9. package/contracts/operations/dam.get/input.schema.json +16 -0
  10. package/contracts/operations/dam.get/output.schema.json +46 -0
  11. package/contracts/operations/dam.list/input.schema.json +44 -0
  12. package/contracts/operations/dam.list/output.schema.json +71 -0
  13. package/contracts/operations/dam.search/input.schema.json +46 -0
  14. package/contracts/operations/dam.search/output.schema.json +71 -0
  15. package/contracts/operations/dam.upload/input.schema.json +31 -0
  16. package/contracts/operations/dam.upload/output.schema.json +46 -0
  17. package/contracts/operations/editor.apply/input.schema.json +134 -0
  18. package/contracts/operations/editor.apply/output.schema.json +15 -0
  19. package/contracts/operations/editor.connect/input.schema.json +15 -0
  20. package/contracts/operations/editor.connect/output.schema.json +17 -0
  21. package/contracts/operations/editor.disconnect/input.schema.json +8 -0
  22. package/contracts/operations/editor.disconnect/output.schema.json +14 -0
  23. package/contracts/operations/editor.save/input.schema.json +8 -0
  24. package/contracts/operations/editor.save/output.schema.json +23 -0
  25. package/contracts/operations/editor.screenshot/input.schema.json +8 -0
  26. package/contracts/operations/editor.screenshot/output.schema.json +16 -0
  27. package/contracts/operations/editor.snapshot/input.schema.json +8 -0
  28. package/contracts/operations/editor.snapshot/output.schema.json +113 -0
  29. package/contracts/operations/model.get/input.schema.json +18 -0
  30. package/contracts/operations/model.get/output.schema.json +190 -0
  31. package/contracts/operations/model.list/input.schema.json +15 -0
  32. package/contracts/operations/model.list/output.schema.json +111 -0
  33. package/contracts/operations/org.current/input.schema.json +7 -0
  34. package/contracts/operations/org.current/output.schema.json +49 -0
  35. package/contracts/operations/org.list/input.schema.json +7 -0
  36. package/contracts/operations/org.list/output.schema.json +71 -0
  37. package/contracts/operations/tool.call/input.schema.json +35 -0
  38. package/contracts/operations/tool.call/output.schema.json +129 -0
  39. package/contracts/operations/tool.list/input.schema.json +7 -0
  40. package/contracts/operations/tool.list/output.schema.json +45 -0
  41. package/dist/bin/gd-cli.js +38 -0
  42. package/dist/bin/postinstall.js +42 -0
  43. package/dist/src/bootstrap/create-cli.js +55 -0
  44. package/dist/src/bootstrap/create-runtime.js +222 -0
  45. package/dist/src/bootstrap/validators.js +118 -0
  46. package/dist/src/cli/action-binding.js +63 -0
  47. package/dist/src/cli/agent-commands.js +39 -0
  48. package/dist/src/cli/auth-commands.js +47 -0
  49. package/dist/src/cli/dam-commands.js +207 -0
  50. package/dist/src/cli/editor-commands.js +73 -0
  51. package/dist/src/cli/errors.js +90 -0
  52. package/dist/src/cli/model-commands.js +47 -0
  53. package/dist/src/cli/org-commands.js +52 -0
  54. package/dist/src/cli/presenter.js +80 -0
  55. package/dist/src/cli/prompt.js +30 -0
  56. package/dist/src/cli/tool-commands.js +65 -0
  57. package/dist/src/cli/update-command.js +12 -0
  58. package/dist/src/contracts/schema.js +4 -0
  59. package/dist/src/features/agent/creative-agent-adapter.js +31 -0
  60. package/dist/src/features/agent/creative-protocol.js +324 -0
  61. package/dist/src/features/agent/creative-stream.js +127 -0
  62. package/dist/src/features/agent/use-cases.js +113 -0
  63. package/dist/src/features/auth/access-policy.js +101 -0
  64. package/dist/src/features/auth/credential-store.js +62 -0
  65. package/dist/src/features/auth/sso-service.js +179 -0
  66. package/dist/src/features/auth/state.js +129 -0
  67. package/dist/src/features/auth/use-cases.js +106 -0
  68. package/dist/src/features/dam/asset-projection.js +270 -0
  69. package/dist/src/features/dam/dam-api-adapter.js +206 -0
  70. package/dist/src/features/dam/object-storage.js +191 -0
  71. package/dist/src/features/dam/registered-uploader.js +224 -0
  72. package/dist/src/features/dam/storage-upload.js +141 -0
  73. package/dist/src/features/dam/transient-uploader.js +17 -0
  74. package/dist/src/features/dam/use-cases.js +151 -0
  75. package/dist/src/features/editor/bridge-client.js +96 -0
  76. package/dist/src/features/editor/bridge-process.js +222 -0
  77. package/dist/src/features/editor/bridge-server.js +311 -0
  78. package/dist/src/features/editor/protocol.js +1 -0
  79. package/dist/src/features/editor/session-state.js +78 -0
  80. package/dist/src/features/editor/session.js +193 -0
  81. package/dist/src/features/editor/use-cases.js +61 -0
  82. package/dist/src/features/org/org-service.js +76 -0
  83. package/dist/src/features/org/use-cases.js +140 -0
  84. package/dist/src/features/skill/bundled-skills.js +55 -0
  85. package/dist/src/features/skill/installer.js +265 -0
  86. package/dist/src/features/tool/catalog.js +79 -0
  87. package/dist/src/features/tool/dynamic-schema.js +96 -0
  88. package/dist/src/features/tool/mns-catalog-adapter.js +262 -0
  89. package/dist/src/features/tool/tool-api-adapter.js +200 -0
  90. package/dist/src/features/tool/use-cases.js +126 -0
  91. package/dist/src/features/update/update-service.js +194 -0
  92. package/dist/src/platform/json-input.js +64 -0
  93. package/dist/src/platform/local-json-file.js +59 -0
  94. package/dist/src/platform/open-browser.js +8 -0
  95. package/dist/src/platform/redact.js +80 -0
  96. package/dist/src/platform/safe-upload-file.js +146 -0
  97. package/dist/src/platform/signature.js +19 -0
  98. package/dist/src/platform/signed-http-transport.js +109 -0
  99. package/dist/src/platform/url-safety.js +100 -0
  100. package/package.json +56 -0
  101. package/skills/gd-cli/SKILL.md +15 -0
  102. package/skills/gd-cli/references/auth-org.md +9 -0
  103. package/skills/gd-cli/references/creation.md +38 -0
  104. package/skills/gd-cli/references/dam.md +20 -0
  105. package/skills/gd-cli/references/editor.md +12 -0
  106. package/skills/gd-cli/references/errors.md +10 -0
  107. package/skills/gd-cli/references/update.md +11 -0
@@ -0,0 +1,224 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import sharp from "sharp";
5
+ import { RemoteRequestError } from "../../platform/signed-http-transport.js";
6
+ import { projectDamAssetDetail } from "./asset-projection.js";
7
+ const imageFormats = new Set([
8
+ "png", "jpg", "jpeg", "gif", "svg", "webp", "jfif", "bmp", "tiff", "tif", "heic", "avif"
9
+ ]);
10
+ const videoFormats = new Set([
11
+ "mp4", "avi", "flv", "mov", "3gp", "m3u8", "mpg", "asf", "wmv", "mkv", "ts", "webm", "mxf"
12
+ ]);
13
+ const audioFormats = new Set(["mp3", "wav", "ogg", "flac"]);
14
+ const fontFormats = new Set(["ttf", "otf"]);
15
+ const designFormats = new Set(["psd", "ai", "sketch", "pdf"]);
16
+ const slideFormats = new Set(["ppt", "pptx"]);
17
+ function storageFormat(format) {
18
+ const normalized = format.toLowerCase();
19
+ if (imageFormats.has(normalized))
20
+ return "gdpic";
21
+ if (videoFormats.has(normalized))
22
+ return "gdvideo";
23
+ if (audioFormats.has(normalized))
24
+ return "gdaudio";
25
+ if (fontFormats.has(normalized))
26
+ return "gdfont";
27
+ if (designFormats.has(normalized))
28
+ return "gdimage";
29
+ if (slideFormats.has(normalized))
30
+ return "gdslide";
31
+ return normalized;
32
+ }
33
+ function publicType(format) {
34
+ if (format === "gdpic" || format === "gdimage")
35
+ return "image";
36
+ if (format === "gdvideo")
37
+ return "video";
38
+ if (format === "gdaudio")
39
+ return "audio";
40
+ if (format === "gdfont")
41
+ return "font";
42
+ if (format === "gdtemplate")
43
+ return "template";
44
+ return "file";
45
+ }
46
+ async function hashes(filePath, signal) {
47
+ signal.throwIfAborted();
48
+ const md5 = createHash("md5");
49
+ const sha1 = createHash("sha1");
50
+ const stream = createReadStream(filePath, { signal });
51
+ try {
52
+ for await (const chunk of stream) {
53
+ signal.throwIfAborted();
54
+ md5.update(chunk);
55
+ sha1.update(chunk);
56
+ }
57
+ signal.throwIfAborted();
58
+ return { md5: md5.digest("hex"), sha1: sha1.digest("hex") };
59
+ }
60
+ catch (error) {
61
+ if (signal.aborted)
62
+ throw signal.reason;
63
+ throw error;
64
+ }
65
+ }
66
+ function filenameTitle(filename) {
67
+ const withoutExtension = filename.replace(/\.[^.]+$/u, "");
68
+ return withoutExtension === "" ? filename : withoutExtension;
69
+ }
70
+ function terminalAnalysisStatus(value) {
71
+ const normalized = typeof value === "string" ? Number(value) : value;
72
+ return normalized === 2 || normalized === 3 || normalized === 4;
73
+ }
74
+ async function defaultSleep(milliseconds, signal) {
75
+ signal.throwIfAborted();
76
+ try {
77
+ await delay(milliseconds, undefined, { signal });
78
+ }
79
+ catch (error) {
80
+ if (signal.aborted)
81
+ throw signal.reason;
82
+ throw error;
83
+ }
84
+ }
85
+ export function createDamRegisteredUploader(dependencies) {
86
+ const now = dependencies.now ?? (() => new Date());
87
+ const sleep = dependencies.sleep ?? defaultSleep;
88
+ const imageMetadata = dependencies.imageMetadata
89
+ ?? (async (filePath) => {
90
+ const metadata = await sharp(filePath).metadata();
91
+ return { width: metadata.width, height: metadata.height };
92
+ });
93
+ async function localMetadata(file, signal) {
94
+ if (!file.mediaType.startsWith("image/"))
95
+ return {};
96
+ try {
97
+ const metadata = await imageMetadata(file.filePath);
98
+ signal.throwIfAborted();
99
+ return {
100
+ ...(typeof metadata.width === "number" && Number.isFinite(metadata.width)
101
+ ? { width: metadata.width }
102
+ : {}),
103
+ ...(typeof metadata.height === "number" && Number.isFinite(metadata.height)
104
+ ? { height: metadata.height }
105
+ : {})
106
+ };
107
+ }
108
+ catch {
109
+ if (signal.aborted)
110
+ throw signal.reason;
111
+ return {};
112
+ }
113
+ }
114
+ return {
115
+ async upload(input) {
116
+ try {
117
+ input.signal.throwIfAborted();
118
+ const [digest, metadata] = await Promise.all([
119
+ hashes(input.file.filePath, input.signal),
120
+ localMetadata(input.file, input.signal)
121
+ ]);
122
+ input.signal.throwIfAborted();
123
+ const contentId = await dependencies.api.createAssetId({
124
+ access: input.access,
125
+ signal: input.signal
126
+ });
127
+ const stored = await dependencies.storage.upload({
128
+ file: input.file,
129
+ contentId
130
+ }, input.access, input.signal);
131
+ const format = input.file.format.toLowerCase();
132
+ const damFormat = storageFormat(format);
133
+ const title = input.input.title ?? filenameTitle(input.file.filename);
134
+ const folderId = input.input.folderId ?? "0";
135
+ const preview = input.file.mediaType.startsWith("image/")
136
+ ? {
137
+ url: stored.url,
138
+ ...metadata,
139
+ format
140
+ }
141
+ : {};
142
+ const body = {
143
+ folder_id: folderId,
144
+ repository_id: input.repository.id,
145
+ second_transmission: false,
146
+ storage_format: damFormat,
147
+ category_ids: [],
148
+ asset_info: {
149
+ id: contentId,
150
+ origin_url: stored.url,
151
+ size: input.file.size,
152
+ title,
153
+ md5: digest.md5,
154
+ sha1: digest.sha1,
155
+ mime_type: input.file.mediaType,
156
+ format,
157
+ preview
158
+ },
159
+ ...(input.input.tagIds === undefined
160
+ ? {}
161
+ : { tag_ids: [...input.input.tagIds] })
162
+ };
163
+ const fallback = projectDamAssetDetail({
164
+ id: contentId,
165
+ title,
166
+ format,
167
+ type: publicType(damFormat),
168
+ origin_info: {
169
+ url: stored.url,
170
+ size: input.file.size,
171
+ format
172
+ },
173
+ preview_info: preview,
174
+ repository_info: {
175
+ id: input.repository.id,
176
+ ...(input.repository.name === undefined ? {} : { name: input.repository.name })
177
+ },
178
+ folder_info: { id: folderId },
179
+ ...(input.input.tagIds === undefined
180
+ ? {}
181
+ : { tag_ids: [...input.input.tagIds] })
182
+ });
183
+ if (fallback === undefined)
184
+ throw new RemoteRequestError();
185
+ let latest = await dependencies.api.createMaterial({
186
+ body,
187
+ fallback,
188
+ access: input.access,
189
+ signal: input.signal
190
+ });
191
+ if (input.input.waitAnalysis !== true || terminalAnalysisStatus(latest.analysis_status)) {
192
+ return latest;
193
+ }
194
+ const deadline = now().getTime() + 180_000;
195
+ while (true) {
196
+ input.signal.throwIfAborted();
197
+ const current = await dependencies.api.getBatchStatus({
198
+ assetId: latest.asset_id,
199
+ fallback: latest,
200
+ access: input.access,
201
+ signal: input.signal
202
+ });
203
+ if (current !== null)
204
+ latest = current;
205
+ if (terminalAnalysisStatus(latest.analysis_status))
206
+ return latest;
207
+ const remaining = deadline - now().getTime();
208
+ if (remaining <= 0) {
209
+ dependencies.warn("素材分析仍在处理中,已返回当前结果。");
210
+ return latest;
211
+ }
212
+ await sleep(Math.min(3000, remaining), input.signal);
213
+ }
214
+ }
215
+ catch (error) {
216
+ if (input.signal.aborted)
217
+ throw input.signal.reason;
218
+ if (error instanceof RemoteRequestError)
219
+ throw error;
220
+ throw new RemoteRequestError();
221
+ }
222
+ }
223
+ };
224
+ }
@@ -0,0 +1,141 @@
1
+ import { RemoteRequestError } from "../../platform/signed-http-transport.js";
2
+ import { inspectUploadFile } from "../../platform/safe-upload-file.js";
3
+ function isRecord(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function requiredString(record, key) {
7
+ const value = record[key];
8
+ if (typeof value !== "string" || value.trim() === "")
9
+ throw new RemoteRequestError();
10
+ return value;
11
+ }
12
+ function optionalString(record, key) {
13
+ const value = record[key];
14
+ if (value === undefined)
15
+ return undefined;
16
+ if (typeof value !== "string" || value.trim() === "")
17
+ throw new RemoteRequestError();
18
+ return value;
19
+ }
20
+ function parseToken(response) {
21
+ if (!isRecord(response))
22
+ throw new RemoteRequestError();
23
+ const record = "data" in response ? response.data : response;
24
+ if (!isRecord(record))
25
+ throw new RemoteRequestError();
26
+ const standbyEndpoint = optionalString(record, "standby_endpoint");
27
+ const accelerateEndpoint = optionalString(record, "accelerate_endpoint");
28
+ const authKey = optionalString(record, "auth_key");
29
+ return {
30
+ securityToken: requiredString(record, "security_token"),
31
+ accessKeyId: requiredString(record, "access_key_id"),
32
+ accessKeySecret: requiredString(record, "access_key_secret"),
33
+ endpoint: requiredString(record, "endpoint"),
34
+ ...(standbyEndpoint === undefined ? {} : { standbyEndpoint }),
35
+ ...(accelerateEndpoint === undefined ? {} : { accelerateEndpoint }),
36
+ region: requiredString(record, "region"),
37
+ bucketName: requiredString(record, "bucket_name"),
38
+ objectName: requiredString(record, "object_name"),
39
+ host: requiredString(record, "host"),
40
+ ...(authKey === undefined ? {} : { authKey })
41
+ };
42
+ }
43
+ function publicAssetUrl(token) {
44
+ try {
45
+ const normalizedHost = token.host.includes("://") ? token.host : `https://${token.host}`;
46
+ const url = new URL(normalizedHost);
47
+ if ((url.protocol !== "http:" && url.protocol !== "https:")
48
+ || url.username !== ""
49
+ || url.password !== "") {
50
+ throw new RemoteRequestError();
51
+ }
52
+ url.search = "";
53
+ url.hash = "";
54
+ if (!url.pathname.endsWith("/"))
55
+ url.pathname += "/";
56
+ url.pathname += token.objectName
57
+ .replace(/^\/+/, "")
58
+ .split("/")
59
+ .map(encodeURIComponent)
60
+ .join("/");
61
+ if (token.authKey !== undefined)
62
+ url.searchParams.set("auth_key", token.authKey);
63
+ return url.href;
64
+ }
65
+ catch (error) {
66
+ if (error instanceof RemoteRequestError)
67
+ throw error;
68
+ throw new RemoteRequestError();
69
+ }
70
+ }
71
+ function storageToken(token) {
72
+ return {
73
+ securityToken: token.securityToken,
74
+ accessKeyId: token.accessKeyId,
75
+ accessKeySecret: token.accessKeySecret,
76
+ endpoint: token.endpoint,
77
+ ...(token.standbyEndpoint === undefined
78
+ ? {}
79
+ : { standbyEndpoint: token.standbyEndpoint }),
80
+ ...(token.accelerateEndpoint === undefined
81
+ ? {}
82
+ : { accelerateEndpoint: token.accelerateEndpoint }),
83
+ region: token.region,
84
+ bucketName: token.bucketName,
85
+ objectName: token.objectName
86
+ };
87
+ }
88
+ export function createDamStorageUploader(dependencies) {
89
+ return {
90
+ inspect(input, signal) {
91
+ return inspectUploadFile({ ...input, signal });
92
+ },
93
+ async upload(input, access, signal) {
94
+ signal.throwIfAborted();
95
+ let token;
96
+ let url;
97
+ try {
98
+ const response = await dependencies.transport.postJson({
99
+ path: "/tb-dam/asset/upload/tokens",
100
+ body: {
101
+ ...(input.contentId === undefined ? {} : { content_id: input.contentId }),
102
+ format: input.file.format
103
+ },
104
+ credential: access.credential,
105
+ organizationId: access.organizationId,
106
+ signal
107
+ });
108
+ signal.throwIfAborted();
109
+ token = parseToken(response);
110
+ url = publicAssetUrl(token);
111
+ }
112
+ catch (error) {
113
+ if (signal.aborted)
114
+ throw signal.reason;
115
+ if (error instanceof RemoteRequestError)
116
+ throw error;
117
+ throw new RemoteRequestError();
118
+ }
119
+ try {
120
+ await dependencies.storage.upload({
121
+ token: storageToken(token),
122
+ filePath: input.file.filePath,
123
+ mediaType: input.file.mediaType,
124
+ signal
125
+ });
126
+ signal.throwIfAborted();
127
+ return {
128
+ ...input.file,
129
+ url
130
+ };
131
+ }
132
+ catch (error) {
133
+ if (signal.aborted)
134
+ throw signal.reason;
135
+ if (error instanceof RemoteRequestError)
136
+ throw error;
137
+ throw new RemoteRequestError();
138
+ }
139
+ }
140
+ };
141
+ }
@@ -0,0 +1,17 @@
1
+ export function createDamTransientUploader(dependencies) {
2
+ return {
3
+ async upload(input, access, signal) {
4
+ const file = await dependencies.storageUpload.inspect({
5
+ url: input.url,
6
+ ...(input.mediaType === undefined ? {} : { claimedMediaType: input.mediaType })
7
+ }, signal);
8
+ const stored = await dependencies.storageUpload.upload({ file }, access, signal);
9
+ return {
10
+ url: stored.url,
11
+ mediaType: stored.mediaType,
12
+ filename: input.filename ?? stored.filename,
13
+ fromUserUpload: true
14
+ };
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,151 @@
1
+ import { resolve } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { RemoteRequestError } from "../../platform/signed-http-transport.js";
4
+ export class DamInputError extends Error {
5
+ constructor() {
6
+ super("DAM 输入不可用。");
7
+ this.name = "DamInputError";
8
+ }
9
+ }
10
+ function invalid() {
11
+ throw new DamInputError();
12
+ }
13
+ function nonblank(value) {
14
+ return value !== undefined && value.trim() !== "";
15
+ }
16
+ function assertPageSize(value) {
17
+ if (value !== undefined && (!Number.isInteger(value) || value < 1 || value > 100))
18
+ invalid();
19
+ }
20
+ function assertAssetId(value) {
21
+ if (value.trim() === "" || /^https?:\/\//iu.test(value))
22
+ invalid();
23
+ }
24
+ function assertRepositorySelection(input) {
25
+ if (input.repositoryId !== undefined && !nonblank(input.repositoryId))
26
+ invalid();
27
+ if (input.repositoryId !== undefined && input.allRepositories === true)
28
+ invalid();
29
+ }
30
+ function searchRequest(input, keyword, repositoryIds) {
31
+ return {
32
+ keyword,
33
+ repositoryIds,
34
+ includeChildFolder: input.includeChildFolder ?? true,
35
+ pageSize: input.pageSize ?? 20,
36
+ ...(input.folderId === undefined ? {} : { folderId: input.folderId }),
37
+ ...(input.type === undefined ? {} : { type: input.type }),
38
+ ...(input.assetFormats === undefined ? {} : { assetFormats: [...input.assetFormats] }),
39
+ ...(input.tagIds === undefined ? {} : { tagIds: [...input.tagIds] }),
40
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor })
41
+ };
42
+ }
43
+ export function createDamUseCases(dependencies) {
44
+ const validate = dependencies.validate;
45
+ async function resolveRepositories(input, access, signal) {
46
+ assertRepositorySelection(input);
47
+ if (input.repositoryId !== undefined)
48
+ return [{ id: input.repositoryId }];
49
+ signal.throwIfAborted();
50
+ const available = await dependencies.api.listRepositories({ access, signal });
51
+ signal.throwIfAborted();
52
+ 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];
60
+ }
61
+ return {
62
+ async list({ input, access, signal }) {
63
+ signal.throwIfAborted();
64
+ assertPageSize(input.pageSize);
65
+ const repositories = await resolveRepositories(input, access, signal);
66
+ const result = await dependencies.api.search({
67
+ ...searchRequest(input, "", repositories.map((repository) => repository.id)),
68
+ access,
69
+ signal
70
+ });
71
+ signal.throwIfAborted();
72
+ validate.list(result);
73
+ return result;
74
+ },
75
+ async search({ input, access, signal }) {
76
+ signal.throwIfAborted();
77
+ if (!nonblank(input.query))
78
+ invalid();
79
+ assertPageSize(input.pageSize);
80
+ const repositories = await resolveRepositories(input, access, signal);
81
+ const result = await dependencies.api.search({
82
+ ...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
83
+ access,
84
+ signal
85
+ });
86
+ signal.throwIfAborted();
87
+ validate.search(result);
88
+ return result;
89
+ },
90
+ async get({ input, access, signal }) {
91
+ signal.throwIfAborted();
92
+ assertAssetId(input.asset_id);
93
+ const result = await dependencies.api.getAsset({
94
+ assetId: input.asset_id,
95
+ access,
96
+ signal
97
+ });
98
+ signal.throwIfAborted();
99
+ validate.get(result);
100
+ return result;
101
+ },
102
+ async upload({ input, access, signal }) {
103
+ signal.throwIfAborted();
104
+ if (!nonblank(input.file))
105
+ invalid();
106
+ const file = await dependencies.storage.inspect({
107
+ url: pathToFileURL(resolve(process.cwd(), input.file)).href,
108
+ ...(input.allowSensitivePath === undefined
109
+ ? {}
110
+ : { allowSensitivePath: input.allowSensitivePath })
111
+ }, signal);
112
+ signal.throwIfAborted();
113
+ const [repository] = await resolveRepositories({
114
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
115
+ }, access, signal);
116
+ if (repository === undefined)
117
+ throw new RemoteRequestError();
118
+ const result = await dependencies.uploader.upload({
119
+ input,
120
+ file,
121
+ repository,
122
+ access,
123
+ signal
124
+ });
125
+ signal.throwIfAborted();
126
+ validate.upload(result);
127
+ return result;
128
+ },
129
+ async delete({ input, access, signal }) {
130
+ signal.throwIfAborted();
131
+ assertAssetId(input.asset_id);
132
+ const [repository] = await resolveRepositories({
133
+ ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
134
+ }, access, signal);
135
+ if (repository === undefined)
136
+ throw new RemoteRequestError();
137
+ const request = {
138
+ repositoryId: repository.id,
139
+ assetId: input.asset_id,
140
+ access,
141
+ signal
142
+ };
143
+ await dependencies.api.recycle(request);
144
+ signal.throwIfAborted();
145
+ if (input.permanent === true)
146
+ await dependencies.api.deleteRecycled(request);
147
+ signal.throwIfAborted();
148
+ validate.delete(null);
149
+ }
150
+ };
151
+ }
@@ -0,0 +1,96 @@
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 function createEditorBridgeClient(options) {
10
+ const fetch = options.fetch ?? globalThis.fetch;
11
+ const baseUrl = `http://127.0.0.1:${options.state.port}`;
12
+ return {
13
+ async health(signal) {
14
+ const value = await request("/health", "GET", undefined, signal);
15
+ if (!isRecord(value) ||
16
+ !hasExactKeys(value, ["status", "pageConnected", "protocolVersion"]) ||
17
+ value.status !== "ok" ||
18
+ typeof value.pageConnected !== "boolean" ||
19
+ typeof value.protocolVersion !== "number") {
20
+ throw invalidResponse();
21
+ }
22
+ return {
23
+ status: "ok",
24
+ pageConnected: value.pageConnected,
25
+ protocolVersion: value.protocolVersion
26
+ };
27
+ },
28
+ async rpc(method, payload, signal) {
29
+ const value = await request("/rpc", "POST", { method, ...(payload === undefined ? {} : { payload }) }, signal);
30
+ if (!isRecord(value) ||
31
+ !hasExactKeys(value, ["result"]) ||
32
+ !("result" in value)) {
33
+ throw invalidResponse();
34
+ }
35
+ return value.result;
36
+ },
37
+ async disconnect(signal) {
38
+ const value = await request("/disconnect", "POST", {}, signal);
39
+ if (!isRecord(value) ||
40
+ !hasExactKeys(value, ["status"]) ||
41
+ value.status !== "disconnected") {
42
+ throw invalidResponse();
43
+ }
44
+ }
45
+ };
46
+ async function request(pathname, method, body, signal) {
47
+ const response = await fetch(`${baseUrl}${pathname}`, {
48
+ method,
49
+ headers: {
50
+ Authorization: `Bearer ${options.state.token}`,
51
+ ...(body === undefined ? {} : { "Content-Type": "application/json" })
52
+ },
53
+ redirect: "error",
54
+ signal,
55
+ ...(body === undefined ? {} : { body: JSON.stringify(body) })
56
+ });
57
+ let value;
58
+ try {
59
+ value = await response.json();
60
+ }
61
+ catch {
62
+ throw invalidResponse();
63
+ }
64
+ if (!response.ok) {
65
+ const error = readStructuredError(value);
66
+ if (!error)
67
+ throw invalidResponse();
68
+ throw new EditorBridgeError(error.code, error.message);
69
+ }
70
+ return value;
71
+ }
72
+ }
73
+ function readStructuredError(value) {
74
+ if (!isRecord(value) || !isRecord(value.error))
75
+ return null;
76
+ const { code, message } = value.error;
77
+ if (typeof code !== "string" ||
78
+ !/^[A-Z][A-Z0-9_]*$/.test(code) ||
79
+ typeof message !== "string" ||
80
+ message.trim().length === 0) {
81
+ return null;
82
+ }
83
+ return { code, message };
84
+ }
85
+ function invalidResponse() {
86
+ return new EditorBridgeError("EDITOR_BRIDGE_INVALID_RESPONSE", "Editor Bridge 返回了无效响应。");
87
+ }
88
+ function isRecord(value) {
89
+ return typeof value === "object" && value !== null && !Array.isArray(value);
90
+ }
91
+ function hasExactKeys(value, keys) {
92
+ const actual = Object.keys(value).sort();
93
+ const expected = [...keys].sort();
94
+ return actual.length === expected.length &&
95
+ actual.every((key, index) => key === expected[index]);
96
+ }