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,207 @@
1
+ import { bindPreparedAction } from "./action-binding.js";
2
+ import { CliUsageError } from "./errors.js";
3
+ export function registerDamCommands(program, runtime) {
4
+ const dam = program.command("dam")
5
+ .description("DAM 素材管理")
6
+ .helpCommand(false);
7
+ const list = browseCommand(leaf(dam.command("list")))
8
+ .description("查看最近素材");
9
+ bindPreparedAction(list, "organization", async (signal) => {
10
+ signal.throwIfAborted();
11
+ const input = browseInput(list);
12
+ runtime.validators.damListInput(input);
13
+ return { handled: false, input };
14
+ }, async (context, input, signal) => {
15
+ const result = await runtime.dam.list({
16
+ input,
17
+ access: organizationAccess(context.state),
18
+ signal
19
+ });
20
+ runtime.presenter.result(result, {
21
+ json: list.opts().json === true,
22
+ view: assetListView
23
+ });
24
+ });
25
+ const search = browseCommand(leaf(dam.command("search")))
26
+ .description("按关键词搜索素材")
27
+ .argument("<query>", "搜索关键词");
28
+ bindPreparedAction(search, "organization", async (signal) => {
29
+ signal.throwIfAborted();
30
+ const input = {
31
+ query: position(search, 0),
32
+ ...browseInput(search)
33
+ };
34
+ runtime.validators.damSearchInput(input);
35
+ return { handled: false, input };
36
+ }, async (context, input, signal) => {
37
+ const result = await runtime.dam.search({
38
+ input,
39
+ access: organizationAccess(context.state),
40
+ signal
41
+ });
42
+ runtime.presenter.result(result, {
43
+ json: search.opts().json === true,
44
+ view: assetListView
45
+ });
46
+ });
47
+ const get = leaf(dam.command("get"))
48
+ .description("查看素材完整信息")
49
+ .argument("<asset>", "素材 ID")
50
+ .option("--json", "输出 JSON");
51
+ bindPreparedAction(get, "organization", async (signal) => {
52
+ signal.throwIfAborted();
53
+ const input = { asset_id: position(get, 0) };
54
+ runtime.validators.damGetInput(input);
55
+ return { handled: false, input };
56
+ }, async (context, input, signal) => {
57
+ const result = await runtime.dam.get({
58
+ input,
59
+ access: organizationAccess(context.state),
60
+ signal
61
+ });
62
+ runtime.presenter.result(result, {
63
+ json: get.opts().json === true,
64
+ view: assetDetailView
65
+ });
66
+ });
67
+ const upload = leaf(dam.command("upload"))
68
+ .description("上传本地文件并创建素材")
69
+ .argument("<file>", "本地文件路径")
70
+ .option("--repository-id <id>", "目标资源库 ID")
71
+ .option("--title <title>", "素材标题")
72
+ .option("--folder-id <id>", "目标文件夹 ID")
73
+ .option("--tag-ids <csv>", "标签 ID,逗号分隔")
74
+ .option("--allow-sensitive-path", "明确允许受保护路径")
75
+ .option("--wait-analysis", "等待素材分析结束")
76
+ .option("--json", "输出 JSON");
77
+ bindPreparedAction(upload, "organization", async (signal) => {
78
+ signal.throwIfAborted();
79
+ const options = upload.opts();
80
+ const input = {
81
+ file: position(upload, 0),
82
+ ...defined("repositoryId", options.repositoryId),
83
+ ...defined("title", options.title),
84
+ ...defined("folderId", options.folderId),
85
+ ...defined("tagIds", csv(options.tagIds)),
86
+ ...(options.allowSensitivePath === true ? { allowSensitivePath: true } : {}),
87
+ ...(options.waitAnalysis === true ? { waitAnalysis: true } : {})
88
+ };
89
+ runtime.validators.damUploadInput(input);
90
+ return { handled: false, input };
91
+ }, async (context, input, signal) => {
92
+ const result = await runtime.dam.upload({
93
+ input,
94
+ access: organizationAccess(context.state),
95
+ signal
96
+ });
97
+ runtime.presenter.result(result, {
98
+ json: upload.opts().json === true,
99
+ view: assetDetailView
100
+ });
101
+ });
102
+ const deleteCommand = leaf(dam.command("delete"))
103
+ .description("删除素材")
104
+ .argument("<asset>", "素材 ID")
105
+ .option("--repository-id <id>", "所属资源库 ID")
106
+ .option("--permanent", "移入回收站后永久删除");
107
+ bindPreparedAction(deleteCommand, "organization", async (signal) => {
108
+ signal.throwIfAborted();
109
+ const options = deleteCommand.opts();
110
+ const input = {
111
+ asset_id: position(deleteCommand, 0),
112
+ ...defined("repositoryId", options.repositoryId),
113
+ ...(options.permanent === true ? { permanent: true } : {})
114
+ };
115
+ runtime.validators.damDeleteInput(input);
116
+ return { handled: false, input };
117
+ }, async (context, input, signal) => {
118
+ await runtime.dam.delete({
119
+ input,
120
+ access: organizationAccess(context.state),
121
+ signal
122
+ });
123
+ runtime.presenter.success(input.permanent === true
124
+ ? "素材已永久删除。"
125
+ : "素材已移入回收站。");
126
+ });
127
+ }
128
+ function leaf(command) {
129
+ return command.allowExcessArguments(false);
130
+ }
131
+ function browseCommand(command) {
132
+ return command
133
+ .option("--repository-id <id>", "指定资源库 ID")
134
+ .option("--all-repositories", "查询全部可访问资源库")
135
+ .option("--folder-id <id>", "指定文件夹 ID")
136
+ .option("--no-include-child-folder", "不包含子文件夹")
137
+ .option("--type <type>", "素材类型")
138
+ .option("--asset-format <csv>", "文件格式,逗号分隔")
139
+ .option("--tag-ids <csv>", "标签 ID,逗号分隔")
140
+ .option("--page-size <1-100>", "请求分页大小", pageSize)
141
+ .option("--cursor <id>", "分页游标")
142
+ .option("--json", "输出 JSON");
143
+ }
144
+ function browseInput(command) {
145
+ const options = command.opts();
146
+ return {
147
+ ...defined("repositoryId", options.repositoryId),
148
+ ...(options.allRepositories === true ? { allRepositories: true } : {}),
149
+ ...defined("folderId", options.folderId),
150
+ ...(command.getOptionValueSource("includeChildFolder") === "cli"
151
+ ? { includeChildFolder: false }
152
+ : {}),
153
+ ...defined("type", options.type),
154
+ ...defined("assetFormats", csv(options.assetFormat)),
155
+ ...defined("tagIds", csv(options.tagIds)),
156
+ ...defined("pageSize", options.pageSize),
157
+ ...defined("cursor", options.cursor)
158
+ };
159
+ }
160
+ function defined(key, value) {
161
+ return value === undefined ? {} : { [key]: value };
162
+ }
163
+ function csv(value) {
164
+ return value?.split(",").map((item) => item.trim()).filter((item) => item !== "");
165
+ }
166
+ function pageSize(value) {
167
+ if (!/^\d+$/u.test(value))
168
+ throw new CliUsageError();
169
+ const parsed = Number(value);
170
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100)
171
+ throw new CliUsageError();
172
+ return parsed;
173
+ }
174
+ function position(command, index) {
175
+ const value = command.processedArgs[index];
176
+ if (typeof value !== "string")
177
+ throw new CliUsageError();
178
+ return value;
179
+ }
180
+ function organizationAccess(state) {
181
+ return {
182
+ credential: state.credential,
183
+ organizationId: state.organization.id
184
+ };
185
+ }
186
+ function assetListView(value) {
187
+ const assets = value.assets;
188
+ if (assets.length === 0)
189
+ return "暂无素材。";
190
+ return [
191
+ "ID\t标题\t类型\t格式",
192
+ ...assets.map(assetRow)
193
+ ].join("\n");
194
+ }
195
+ function assetRow(asset) {
196
+ return [asset.asset_id, asset.title ?? "", asset.type ?? "", asset.format ?? ""].join("\t");
197
+ }
198
+ function assetDetailView(value) {
199
+ const asset = value;
200
+ return [
201
+ `素材:${asset.title ?? asset.asset_id}`,
202
+ `ID:${asset.asset_id}`,
203
+ ...(asset.type === undefined ? [] : [`类型:${asset.type}`]),
204
+ ...(asset.format === undefined ? [] : [`格式:${asset.format}`]),
205
+ ...(asset.url === undefined ? [] : [`URL:${asset.url}`])
206
+ ].join("\n");
207
+ }
@@ -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
+ }
@@ -0,0 +1,90 @@
1
+ import { CommanderError } from "commander";
2
+ import { AuthExpiredError, AuthRequiredError } from "../features/auth/access-policy.js";
3
+ import { AuthStateInvalidError } from "../features/auth/state.js";
4
+ import { SsoRequestError } from "../features/auth/sso-service.js";
5
+ import { DeviceAuthorizationExpiredError } from "../features/auth/use-cases.js";
6
+ import { AgentInputError } from "../features/agent/use-cases.js";
7
+ import { ToolInputError } from "../features/tool/catalog.js";
8
+ import { ObjectStorageUploadError } from "../features/dam/object-storage.js";
9
+ import { DamInputError } from "../features/dam/use-cases.js";
10
+ import { OrganizationResponseError } from "../features/org/org-service.js";
11
+ import { OrganizationBindFailedError, OrganizationInvalidError, OrgUsageError } from "../features/org/use-cases.js";
12
+ import { RemoteRequestError } from "../platform/signed-http-transport.js";
13
+ import { InputContractError, OutputContractError } from "../bootstrap/validators.js";
14
+ import { JsonInputError } from "../platform/json-input.js";
15
+ import { LocalFileInputError } from "../platform/safe-upload-file.js";
16
+ import { EditorBridgeError } from "../features/editor/bridge-client.js";
17
+ import { EditorNotReadyError, EditorSessionNotFoundError, EditorTargetError } from "../features/editor/session.js";
18
+ import { UpdateError } from "../features/update/update-service.js";
19
+ export class CliUsageError extends Error {
20
+ constructor() {
21
+ super("命令或参数无效。");
22
+ this.name = "CliUsageError";
23
+ }
24
+ }
25
+ export function isHelpOutcome(error) {
26
+ return error instanceof CommanderError
27
+ && error.exitCode === 0
28
+ && (error.code === "commander.helpDisplayed" || error.code === "commander.version");
29
+ }
30
+ export function mapCliError(error, options) {
31
+ if (options.aborted)
32
+ return { kind: "interrupted", exitCode: 130 };
33
+ if (error instanceof AuthRequiredError) {
34
+ return failure("AUTH_REQUIRED", "请先执行 gd-cli auth login", 1, ["gd-cli auth login"]);
35
+ }
36
+ if (error instanceof AuthExpiredError) {
37
+ return failure("AUTH_EXPIRED", "登录已过期,请退出后重新登录。", 1, ["gd-cli auth logout", "gd-cli auth login"]);
38
+ }
39
+ if (error instanceof AuthStateInvalidError) {
40
+ return failure("AUTH_STATE_INVALID", "本地登录状态已失效,请退出后重新登录。", 1, ["gd-cli auth logout", "gd-cli auth login"]);
41
+ }
42
+ if (error instanceof OrganizationInvalidError) {
43
+ return failure("ORG_INVALID", "所选组织不可用,请重新选择组织。", 1, ["gd-cli org switch"]);
44
+ }
45
+ if (error instanceof UpdateError) {
46
+ return failure(error.code, error.message, 1, error.nextSteps);
47
+ }
48
+ if (error instanceof EditorTargetError) {
49
+ return failure(error.code, error.message, 2);
50
+ }
51
+ if (error instanceof OrgUsageError
52
+ || error instanceof CliUsageError
53
+ || error instanceof JsonInputError
54
+ || error instanceof InputContractError
55
+ || error instanceof AgentInputError
56
+ || error instanceof ToolInputError
57
+ || error instanceof DamInputError
58
+ || error instanceof LocalFileInputError
59
+ || error instanceof CommanderError) {
60
+ return failure("USAGE_ERROR", "命令或参数无效,请查看 --help。", 2);
61
+ }
62
+ if (error instanceof RemoteRequestError
63
+ || error instanceof SsoRequestError
64
+ || error instanceof DeviceAuthorizationExpiredError
65
+ || error instanceof OrganizationResponseError
66
+ || error instanceof OrganizationBindFailedError
67
+ || error instanceof ObjectStorageUploadError) {
68
+ return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败,请稍后重试。", 1);
69
+ }
70
+ if (error instanceof OutputContractError) {
71
+ return failure("INTERNAL_ERROR", "CLI 内部结果校验失败。", 1);
72
+ }
73
+ if (error instanceof EditorBridgeError
74
+ || error instanceof EditorSessionNotFoundError
75
+ || error instanceof EditorNotReadyError) {
76
+ return failure(error.code, error.message, 1);
77
+ }
78
+ return failure("INTERNAL_ERROR", "CLI 发生内部错误。", 1);
79
+ }
80
+ function failure(code, message, exitCode, nextSteps) {
81
+ return {
82
+ kind: "failure",
83
+ failure: {
84
+ code,
85
+ message,
86
+ ...(nextSteps ? { details: { next_steps: nextSteps } } : {})
87
+ },
88
+ exitCode
89
+ };
90
+ }
@@ -0,0 +1,47 @@
1
+ import { bindAction } from "./action-binding.js";
2
+ import { CliUsageError } from "./errors.js";
3
+ export function registerModelCommands(program, runtime) {
4
+ const model = program.command("model")
5
+ .description("创作模型")
6
+ .helpCommand(false);
7
+ const list = model.command("list")
8
+ .description("列出可用模型")
9
+ .option("--tool <tool>", "按工具筛选")
10
+ .allowExcessArguments(false);
11
+ bindAction(list, "organization", async (context, signal) => {
12
+ const selectedTool = list.opts().tool;
13
+ const input = selectedTool === undefined ? {} : { tool: selectedTool };
14
+ runtime.validators.modelListInput(input);
15
+ const result = await runtime.tool.listModels({
16
+ input,
17
+ access: {
18
+ credential: context.state.credential,
19
+ organizationId: context.state.organization.id
20
+ },
21
+ signal
22
+ });
23
+ runtime.validators.modelListOutput(result);
24
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
25
+ });
26
+ const get = model.command("get")
27
+ .description("查看模型完整信息")
28
+ .argument("<model>", "模型机器标识")
29
+ .allowExcessArguments(false);
30
+ bindAction(get, "organization", async (context, signal) => {
31
+ const modelName = get.processedArgs[0];
32
+ if (typeof modelName !== "string" || modelName.trim() === "")
33
+ throw new CliUsageError();
34
+ const input = { model: modelName };
35
+ runtime.validators.modelGetInput(input);
36
+ const result = await runtime.tool.getModel({
37
+ input,
38
+ access: {
39
+ credential: context.state.credential,
40
+ organizationId: context.state.organization.id
41
+ },
42
+ signal
43
+ });
44
+ runtime.validators.modelGetOutput(result);
45
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
46
+ });
47
+ }
@@ -0,0 +1,52 @@
1
+ import { bindAction } from "./action-binding.js";
2
+ import { OrgUsageError } from "../features/org/use-cases.js";
3
+ import { orgCurrentView, orgListView } from "./presenter.js";
4
+ export function registerOrgCommands(program, runtime) {
5
+ const org = program.command("org")
6
+ .description("组织选择")
7
+ .helpCommand(false);
8
+ const list = leaf(org.command("list"))
9
+ .description("列出可用组织")
10
+ .option("--json", "输出 JSON");
11
+ bindAction(list, "credential", async (context, signal) => {
12
+ const result = await runtime.org.list({ state: context.state, signal });
13
+ runtime.validators.orgList(result);
14
+ runtime.presenter.result(result, {
15
+ json: list.opts().json === true,
16
+ view: orgListView
17
+ });
18
+ });
19
+ const current = leaf(org.command("current"))
20
+ .description("查看当前组织")
21
+ .option("--json", "输出 JSON");
22
+ bindAction(current, "none", async () => {
23
+ const result = await runtime.org.current();
24
+ runtime.validators.orgCurrent(result);
25
+ runtime.presenter.result(result, {
26
+ json: current.opts().json === true,
27
+ view: orgCurrentView
28
+ });
29
+ });
30
+ const switchCommand = leaf(org.command("switch"))
31
+ .description("切换当前组织")
32
+ .option("--org <org-id>", "指定组织 ID");
33
+ bindAction(switchCommand, "credential", async (context, signal) => {
34
+ const organizationId = switchCommand.opts().org;
35
+ if (organizationId === undefined && !isInteractive(runtime))
36
+ throw new OrgUsageError();
37
+ const record = await runtime.org.switchOrganization({
38
+ state: context.state,
39
+ ...(organizationId === undefined
40
+ ? { select: (records) => runtime.prompt.choose(records, signal) }
41
+ : { organizationId }),
42
+ signal
43
+ });
44
+ runtime.presenter.success(`已切换到${record.publicName} (${record.id})。`);
45
+ });
46
+ }
47
+ function leaf(command) {
48
+ return command.allowExcessArguments(false);
49
+ }
50
+ function isInteractive(runtime) {
51
+ return runtime.io.inputTerminal && runtime.io.stderrTerminal;
52
+ }
@@ -0,0 +1,80 @@
1
+ import * as QRCode from "qrcode";
2
+ export function createPresenter(options) {
3
+ const qrCode = options.qrCode ?? ((text) => QRCode.toString(text, {
4
+ type: "terminal",
5
+ small: true
6
+ }));
7
+ return {
8
+ result(value, presentation) {
9
+ options.writeOut(presentation.json
10
+ ? `${JSON.stringify(value)}\n`
11
+ : `${presentation.view(value)}\n`);
12
+ },
13
+ success(message) {
14
+ options.writeOut(`${message}\n`);
15
+ },
16
+ async authorization(input) {
17
+ options.writeError(`授权地址: ${input.url.href}\n`);
18
+ if (input.terminal)
19
+ options.writeError(await qrCode(input.url.href));
20
+ },
21
+ warning(message) {
22
+ options.writeError(`警告: ${message}\n`);
23
+ },
24
+ error(outcome, presentation) {
25
+ if (outcome.kind === "interrupted") {
26
+ options.writeError("已取消。\n");
27
+ return;
28
+ }
29
+ if (presentation.json) {
30
+ options.writeError(`${JSON.stringify({ error: outcome.failure })}\n`);
31
+ return;
32
+ }
33
+ options.writeError(`${outcome.failure.code}: ${outcome.failure.message}\n`);
34
+ const nextSteps = nextStepsFrom(outcome.failure.details);
35
+ if (nextSteps.length > 0) {
36
+ options.writeError(`下一步: ${nextSteps.join(",")}\n`);
37
+ }
38
+ }
39
+ };
40
+ }
41
+ export const authStatusView = (value) => {
42
+ const status = value;
43
+ if (!status.logged_in)
44
+ return lines("未登录", nextStepsLine(status.next_steps));
45
+ return lines("已登录", `账号: ${status.account.name ?? "未记录"}${status.account.user_id ? ` (${status.account.user_id})` : ""}`, status.organization
46
+ ? `组织: ${status.organization.name} (${status.organization.id})`
47
+ : "组织: 未选择", `凭证: ${status.credential.valid ? "有效" : "已过期"},到期 ${status.credential.expires_at}`, status.credential.scope.length > 0
48
+ ? `权限: ${status.credential.scope.join(", ")}`
49
+ : "权限: 未声明", nextStepsLine(status.next_steps));
50
+ };
51
+ export const orgListView = (value) => {
52
+ const result = value;
53
+ if (result.orgs.length === 0)
54
+ return "没有可用组织。";
55
+ return result.orgs.map((organization) => {
56
+ const product = organization.product_name ? ` · ${organization.product_name}` : "";
57
+ const current = organization.id === result.current_org_id ? " [当前]" : "";
58
+ return `${organization.name} (${organization.id})${product}${current}`;
59
+ }).join("\n");
60
+ };
61
+ export const orgCurrentView = (value) => {
62
+ const result = value;
63
+ return result.organization
64
+ ? `当前组织: ${result.organization.name} (${result.organization.id})`
65
+ : lines("尚未选择组织。", nextStepsLine(result.next_steps));
66
+ };
67
+ function lines(...values) {
68
+ return values.filter((value) => Boolean(value)).join("\n");
69
+ }
70
+ function nextStepsLine(nextSteps) {
71
+ return nextSteps.length > 0 ? `下一步: ${nextSteps.join(",")}` : undefined;
72
+ }
73
+ function nextStepsFrom(details) {
74
+ if (typeof details !== "object" || details === null || Array.isArray(details))
75
+ return [];
76
+ const nextSteps = details.next_steps;
77
+ return Array.isArray(nextSteps) && nextSteps.every((item) => typeof item === "string")
78
+ ? nextSteps
79
+ : [];
80
+ }
@@ -0,0 +1,30 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { OrganizationInvalidError } from "../features/org/use-cases.js";
3
+ export function createOrganizationPrompt(options) {
4
+ return {
5
+ async choose(records, signal) {
6
+ signal.throwIfAborted();
7
+ if (records.length === 0)
8
+ throw new OrganizationInvalidError();
9
+ const readline = createInterface({ input: options.input, output: options.output });
10
+ try {
11
+ options.output.write("请选择组织:\n");
12
+ records.forEach((record, index) => {
13
+ const product = record.productName ? ` · ${record.productName}` : "";
14
+ options.output.write(`${index + 1}. ${record.publicName} (${record.id})${product}\n`);
15
+ });
16
+ while (true) {
17
+ const answer = (await readline.question("输入序号: ", { signal })).trim();
18
+ const index = Number(answer) - 1;
19
+ if (Number.isInteger(index) && index >= 0 && index < records.length) {
20
+ return records[index].id;
21
+ }
22
+ options.output.write("请输入列表中的有效序号。\n");
23
+ }
24
+ }
25
+ finally {
26
+ readline.close();
27
+ }
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,65 @@
1
+ import { bindAction, bindPreparedAction } from "./action-binding.js";
2
+ import { CliUsageError } from "./errors.js";
3
+ export function registerToolCommands(program, runtime) {
4
+ const tool = program.command("tool")
5
+ .description("创作工具")
6
+ .helpCommand(false);
7
+ const list = tool.command("list")
8
+ .description("列出可用工具")
9
+ .allowExcessArguments(false);
10
+ bindAction(list, "organization", async (context, signal) => {
11
+ const result = await runtime.tool.listTools({
12
+ access: {
13
+ credential: context.state.credential,
14
+ organizationId: context.state.organization.id
15
+ },
16
+ signal
17
+ });
18
+ runtime.validators.toolListOutput(result);
19
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
20
+ });
21
+ const call = tool.command("call")
22
+ .description("调用创作工具")
23
+ .argument("<tool>", "工具机器标识")
24
+ .option("--schema", "输出当前工具的输入 Schema")
25
+ .option("--input <file|->", "从文件或标准输入读取参数 JSON")
26
+ .allowExcessArguments(false);
27
+ bindPreparedAction(call, "organization", async (signal) => {
28
+ signal.throwIfAborted();
29
+ const name = call.processedArgs[0];
30
+ if (typeof name !== "string" || name.trim() === "")
31
+ throw new CliUsageError();
32
+ const options = call.opts();
33
+ const modes = Number(options.schema === true) + Number(options.input !== undefined);
34
+ if (modes !== 1)
35
+ throw new CliUsageError();
36
+ if (options.schema === true) {
37
+ return { handled: false, input: { mode: "schema", name } };
38
+ }
39
+ const argumentsInput = await runtime.jsonInput.read(options.input, signal);
40
+ const input = { name, arguments: argumentsInput };
41
+ runtime.validators.toolCallInput(input);
42
+ return { handled: false, input: { mode: "call", input } };
43
+ }, async (context, prepared, signal) => {
44
+ const access = {
45
+ credential: context.state.credential,
46
+ organizationId: context.state.organization.id
47
+ };
48
+ if (prepared.mode === "schema") {
49
+ const schema = await runtime.tool.getArgumentsSchema({
50
+ name: prepared.name,
51
+ access,
52
+ signal
53
+ });
54
+ runtime.io.writeOut(`${JSON.stringify(schema)}\n`);
55
+ return;
56
+ }
57
+ const result = await runtime.tool.call({
58
+ input: prepared.input,
59
+ access,
60
+ signal
61
+ });
62
+ runtime.validators.toolCallOutput(result);
63
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
64
+ });
65
+ }
@@ -0,0 +1,12 @@
1
+ import { bindAction } from "./action-binding.js";
2
+ export function registerUpdateCommand(program, runtime) {
3
+ const update = program.command("update")
4
+ .description("更新 GD CLI 与内置 Agent Skill")
5
+ .allowExcessArguments(false);
6
+ bindAction(update, "none", async (_context, signal) => {
7
+ const result = await runtime.update.run(signal);
8
+ runtime.presenter.success(result.updated
9
+ ? `gd-cli 已更新到 ${result.version},Agent Skill 已同步。`
10
+ : `gd-cli ${result.version} 已是最新版本,Agent Skill 已同步。`);
11
+ });
12
+ }
@@ -0,0 +1,4 @@
1
+ import { readFileSync } from "node:fs";
2
+ export function contractSchema(operation, direction) {
3
+ return JSON.parse(readFileSync(new URL(`../../../contracts/operations/${operation}/${direction}.schema.json`, import.meta.url), "utf8"));
4
+ }