@gaoding/cli 0.0.0 → 1.0.0-alpha.25

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,469 @@
1
+ import { bindAction, bindPreparedAction } from "./action-binding.js";
2
+ import { CliUsageError } from "./errors.js";
3
+ import { repositoryListView } from "./presenter.js";
4
+ export function registerDamCommands(program, runtime) {
5
+ const dam = program.command("dam")
6
+ .description("DAM 素材管理")
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
+ }
201
+ const list = browseCommand(leaf(dam.command("list")))
202
+ .description("查看最近素材");
203
+ bindPreparedAction(list, "organization", async (signal) => {
204
+ signal.throwIfAborted();
205
+ const input = browseInput(list);
206
+ runtime.validators.damListInput(input);
207
+ runtime.telemetry.annotate({ parameters: { ...input } });
208
+ return { handled: false, input };
209
+ }, async (context, input, signal) => {
210
+ const result = await runtime.dam.list({
211
+ input,
212
+ access: organizationAccess(context.state),
213
+ signal
214
+ });
215
+ await runtime.telemetry.stage("output", async () => {
216
+ runtime.presenter.result(result, {
217
+ json: list.opts().json === true,
218
+ view: assetListView
219
+ });
220
+ });
221
+ }, { operation: "dam.list" });
222
+ const search = browseCommand(leaf(dam.command("search")))
223
+ .description("按关键词搜索素材")
224
+ .argument("<query>", "搜索关键词");
225
+ bindPreparedAction(search, "organization", async (signal) => {
226
+ signal.throwIfAborted();
227
+ const input = {
228
+ query: position(search, 0),
229
+ ...browseInput(search)
230
+ };
231
+ runtime.validators.damSearchInput(input);
232
+ runtime.telemetry.annotate({ parameters: { ...input } });
233
+ return { handled: false, input };
234
+ }, async (context, input, signal) => {
235
+ const result = await runtime.dam.search({
236
+ input,
237
+ access: organizationAccess(context.state),
238
+ signal
239
+ });
240
+ await runtime.telemetry.stage("output", async () => {
241
+ runtime.presenter.result(result, {
242
+ json: search.opts().json === true,
243
+ view: assetListView
244
+ });
245
+ });
246
+ }, { operation: "dam.search" });
247
+ const get = leaf(dam.command("get"))
248
+ .description("查看素材完整信息")
249
+ .argument("<asset>", "素材 ID")
250
+ .option("--json", "输出 JSON");
251
+ bindPreparedAction(get, "organization", async (signal) => {
252
+ signal.throwIfAborted();
253
+ const input = { asset_id: position(get, 0) };
254
+ runtime.validators.damGetInput(input);
255
+ runtime.telemetry.annotate({ parameters: { ...input } });
256
+ return { handled: false, input };
257
+ }, async (context, input, signal) => {
258
+ const result = await runtime.dam.get({
259
+ input,
260
+ access: organizationAccess(context.state),
261
+ signal
262
+ });
263
+ await runtime.telemetry.stage("output", async () => {
264
+ runtime.presenter.result(result, {
265
+ json: get.opts().json === true,
266
+ view: assetDetailView
267
+ });
268
+ });
269
+ }, { operation: "dam.get" });
270
+ const upload = leaf(dam.command("upload"))
271
+ .description("上传本地文件并创建素材")
272
+ .argument("<file>", "本地文件路径")
273
+ .option("--repository-id <id>", "目标资源库 ID")
274
+ .option("--title <title>", "素材标题")
275
+ .option("--folder-id <id>", "目标文件夹 ID")
276
+ .option("--tag-ids <csv>", "标签 ID,逗号分隔")
277
+ .option("--allow-sensitive-path", "明确允许受保护路径")
278
+ .option("--wait-analysis", "等待素材分析结束")
279
+ .option("--json", "输出 JSON");
280
+ bindPreparedAction(upload, "organization", async (signal) => {
281
+ signal.throwIfAborted();
282
+ const options = upload.opts();
283
+ const input = {
284
+ file: position(upload, 0),
285
+ ...defined("repositoryId", options.repositoryId),
286
+ ...defined("title", options.title),
287
+ ...defined("folderId", options.folderId),
288
+ ...defined("tagIds", csv(options.tagIds)),
289
+ ...(options.allowSensitivePath === true ? { allowSensitivePath: true } : {}),
290
+ ...(options.waitAnalysis === true ? { waitAnalysis: true } : {})
291
+ };
292
+ runtime.validators.damUploadInput(input);
293
+ runtime.telemetry.annotate({ parameters: { ...input } });
294
+ return { handled: false, input };
295
+ }, async (context, input, signal) => {
296
+ const result = await runtime.dam.upload({
297
+ input,
298
+ access: organizationAccess(context.state),
299
+ signal
300
+ });
301
+ await runtime.telemetry.stage("output", async () => {
302
+ runtime.presenter.result(result, {
303
+ json: upload.opts().json === true,
304
+ view: assetDetailView
305
+ });
306
+ });
307
+ }, { operation: "dam.upload" });
308
+ const deleteCommand = leaf(dam.command("delete"))
309
+ .description("删除素材")
310
+ .argument("<asset>", "素材 ID")
311
+ .option("--repository-id <id>", "所属资源库 ID")
312
+ .option("--permanent", "移入回收站后永久删除");
313
+ bindPreparedAction(deleteCommand, "organization", async (signal) => {
314
+ signal.throwIfAborted();
315
+ const options = deleteCommand.opts();
316
+ const input = {
317
+ asset_id: position(deleteCommand, 0),
318
+ ...defined("repositoryId", options.repositoryId),
319
+ ...(options.permanent === true ? { permanent: true } : {})
320
+ };
321
+ runtime.validators.damDeleteInput(input);
322
+ runtime.telemetry.annotate({ parameters: { ...input } });
323
+ return { handled: false, input };
324
+ }, async (context, input, signal) => {
325
+ await runtime.dam.delete({
326
+ input,
327
+ access: organizationAccess(context.state),
328
+ signal
329
+ });
330
+ await runtime.telemetry.stage("output", async () => {
331
+ runtime.presenter.success(input.permanent === true
332
+ ? "素材已永久删除。"
333
+ : "素材已移入回收站。");
334
+ });
335
+ }, { operation: "dam.delete" });
336
+ }
337
+ function leaf(command) {
338
+ return command.allowExcessArguments(false);
339
+ }
340
+ function browseCommand(command) {
341
+ return command
342
+ .option("--repository-id <id>", "指定资源库 ID")
343
+ .option("--all-repositories", "查询全部可访问资源库")
344
+ .option("--folder-id <id>", "指定文件夹 ID")
345
+ .option("--no-include-child-folder", "不包含子文件夹")
346
+ .option("--type <type>", "素材类型")
347
+ .option("--asset-format <csv>", "文件格式,逗号分隔")
348
+ .option("--tag-ids <csv>", "标签 ID,逗号分隔")
349
+ .option("--page-size <1-100>", "请求分页大小", pageSize)
350
+ .option("--cursor <id>", "分页游标")
351
+ .option("--json", "输出 JSON");
352
+ }
353
+ function browseInput(command) {
354
+ const options = command.opts();
355
+ return {
356
+ ...defined("repositoryId", options.repositoryId),
357
+ ...(options.allRepositories === true ? { allRepositories: true } : {}),
358
+ ...defined("folderId", options.folderId),
359
+ ...(command.getOptionValueSource("includeChildFolder") === "cli"
360
+ ? { includeChildFolder: false }
361
+ : {}),
362
+ ...defined("type", options.type),
363
+ ...defined("assetFormats", csv(options.assetFormat)),
364
+ ...defined("tagIds", csv(options.tagIds)),
365
+ ...defined("pageSize", options.pageSize),
366
+ ...defined("cursor", options.cursor)
367
+ };
368
+ }
369
+ function defined(key, value) {
370
+ return value === undefined ? {} : { [key]: value };
371
+ }
372
+ function csv(value) {
373
+ return value?.split(",").map((item) => item.trim()).filter((item) => item !== "");
374
+ }
375
+ function pageSize(value) {
376
+ if (!/^\d+$/u.test(value))
377
+ throw new CliUsageError();
378
+ const parsed = Number(value);
379
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100)
380
+ throw new CliUsageError();
381
+ return parsed;
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
+ }
391
+ function position(command, index) {
392
+ const value = command.processedArgs[index];
393
+ if (typeof value !== "string")
394
+ throw new CliUsageError();
395
+ return value;
396
+ }
397
+ function organizationAccess(state) {
398
+ return {
399
+ credential: state.credential,
400
+ organizationId: state.organization.id
401
+ };
402
+ }
403
+ function assetListView(value) {
404
+ const assets = value.assets;
405
+ if (assets.length === 0)
406
+ return "暂无素材。";
407
+ return [
408
+ "ID\t标题\t类型\t格式",
409
+ ...assets.map(assetRow)
410
+ ].join("\n");
411
+ }
412
+ function assetRow(asset) {
413
+ return [asset.asset_id, asset.title ?? "", asset.type ?? "", asset.format ?? ""].join("\t");
414
+ }
415
+ function assetDetailView(value) {
416
+ const asset = value;
417
+ return [
418
+ `素材:${asset.title ?? asset.asset_id}`,
419
+ `ID:${asset.asset_id}`,
420
+ ...(asset.type === undefined ? [] : [`类型:${asset.type}`]),
421
+ ...(asset.format === undefined ? [] : [`格式:${asset.format}`]),
422
+ ...(asset.url === undefined ? [] : [`URL:${asset.url}`])
423
+ ].join("\n");
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
+ }
@@ -0,0 +1,73 @@
1
+ import { bindAction, bindPreparedAction } from "./action-binding.js";
2
+ export function registerEditorCommands(program, runtime) {
3
+ const editor = program.command("editor")
4
+ .description("连接并操作 AI+ Editor")
5
+ .helpCommand(false);
6
+ const connect = leaf(editor.command("connect"))
7
+ .description("打开并连接一个 AI+ Editor 作品")
8
+ .argument("[target]", "作品 ID 或完整 URL");
9
+ bindPreparedAction(connect, "none", async (signal) => {
10
+ signal.throwIfAborted();
11
+ const target = connect.processedArgs[0];
12
+ const input = typeof target === "string" ? { target } : {};
13
+ runtime.validators.editorConnectInput(input);
14
+ return { handled: false, input };
15
+ }, async (_context, input, signal) => {
16
+ const result = await runtime.editor.connect({ input, signal });
17
+ writeJson(runtime, result);
18
+ });
19
+ const disconnect = leaf(editor.command("disconnect"))
20
+ .description("断开当前 Editor 连接");
21
+ bindAction(disconnect, "none", async (_context, signal) => {
22
+ writeJson(runtime, await runtime.editor.disconnect({ signal }));
23
+ });
24
+ const snapshot = leaf(editor.command("snapshot"))
25
+ .description("读取当前作品快照");
26
+ bindAction(snapshot, "none", async (_context, signal) => {
27
+ writeJson(runtime, await runtime.editor.snapshot({ signal }));
28
+ });
29
+ const apply = leaf(editor.command("apply"))
30
+ .description("按顺序应用元素操作")
31
+ .requiredOption("--input <file|->", "从文件或标准输入读取 JSON");
32
+ bindPreparedAction(apply, "none", async (signal) => {
33
+ signal.throwIfAborted();
34
+ const source = apply.opts().input;
35
+ const input = await runtime.jsonInput.read(source, signal);
36
+ runtime.validators.editorApplyInput(input);
37
+ return { handled: false, input };
38
+ }, async (_context, input, signal) => {
39
+ writeJson(runtime, await runtime.editor.apply({ input, signal }));
40
+ });
41
+ const screenshot = leaf(editor.command("screenshot"))
42
+ .description("导出当前作品 PNG 截图")
43
+ .option("--json", "输出 JSON");
44
+ bindAction(screenshot, "none", async (_context, signal) => {
45
+ const result = await runtime.editor.screenshot({ signal });
46
+ runtime.presenter.result(result, {
47
+ json: screenshot.opts().json === true,
48
+ view: screenshotView
49
+ });
50
+ });
51
+ const save = leaf(editor.command("save"))
52
+ .description("保存当前 AI+ Editor 作品")
53
+ .option("--json", "输出 JSON");
54
+ bindAction(save, "none", async (_context, signal) => {
55
+ const result = await runtime.editor.save({ signal });
56
+ runtime.presenter.result(result, {
57
+ json: save.opts().json === true,
58
+ view: saveView
59
+ });
60
+ });
61
+ }
62
+ function leaf(command) {
63
+ return command.allowExcessArguments(false);
64
+ }
65
+ function writeJson(runtime, value) {
66
+ runtime.io.writeOut(`${JSON.stringify(value)}\n`);
67
+ }
68
+ function screenshotView(value) {
69
+ return `截图已保存至 ${value.path}`;
70
+ }
71
+ function saveView(value) {
72
+ return `作品已保存:${value.url}`;
73
+ }