@gaoding/cli 1.1.0 → 1.2.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 (38) hide show
  1. package/dist/bin/gd-cli.js +2 -1
  2. package/dist/bin/insmind-cli.js +60 -0
  3. package/dist/bin/postinstall.js +1 -1
  4. package/dist/src/bootstrap/create-cli.js +14 -4
  5. package/dist/src/bootstrap/create-runtime.js +88 -30
  6. package/dist/src/bootstrap/validators.js +9 -5
  7. package/dist/src/cli/agent-commands.js +5 -4
  8. package/dist/src/cli/auth-commands.js +13 -10
  9. package/dist/src/cli/credits-command.js +23 -0
  10. package/dist/src/cli/dam-commands.js +70 -69
  11. package/dist/src/cli/editor-commands.js +12 -11
  12. package/dist/src/cli/errors.js +49 -9
  13. package/dist/src/cli/model-commands.js +6 -5
  14. package/dist/src/cli/presenter.js +24 -6
  15. package/dist/src/cli/tool-commands.js +7 -6
  16. package/dist/src/cli/update-command.js +4 -3
  17. package/dist/src/features/auth/credential-store.js +1 -1
  18. package/dist/src/features/auth/sso-service.js +3 -1
  19. package/dist/src/features/auth/use-cases.js +38 -12
  20. package/dist/src/features/credits/use-cases.js +52 -0
  21. package/dist/src/features/dam/storage-upload.js +1 -1
  22. package/dist/src/features/editor/bridge-process.js +11 -4
  23. package/dist/src/features/editor/session-state.js +1 -1
  24. package/dist/src/features/editor/session.js +14 -8
  25. package/dist/src/features/org/use-cases.js +7 -0
  26. package/dist/src/features/skill/bundled-skills.js +13 -5
  27. package/dist/src/features/tool/catalog.js +13 -13
  28. package/dist/src/features/tool/use-cases.js +3 -3
  29. package/dist/src/features/update/latest-version.js +2 -2
  30. package/dist/src/features/update/update-notification.js +10 -3
  31. package/dist/src/features/update/update-service.js +57 -23
  32. package/dist/src/platform/signed-http-transport.js +1 -0
  33. package/dist/src/product/billing.js +45 -0
  34. package/dist/src/product/config.js +83 -0
  35. package/dist/src/product/locale.js +38 -0
  36. package/dist/src/product/text.js +3 -0
  37. package/dist/src/telemetry/sls-sink.js +1 -2
  38. package/package.json +5 -5
@@ -1,16 +1,17 @@
1
1
  import { bindAction, bindPreparedAction } from "./action-binding.js";
2
2
  import { CliUsageError } from "./errors.js";
3
3
  import { repositoryListView } from "./presenter.js";
4
+ import { productText as text } from "../product/text.js";
4
5
  export function registerDamCommands(program, runtime) {
5
6
  const dam = program.command("dam")
6
- .description("DAM 素材管理")
7
+ .description(text(runtime.product, "DAM 素材管理", "Asset management"))
7
8
  .helpCommand(false);
8
9
  const repository = dam.command("repository")
9
- .description("DAM 资源库")
10
+ .description(text(runtime.product, "DAM 资源库", "Asset repositories"))
10
11
  .helpCommand(false);
11
12
  const repositoryList = leaf(repository.command("list"))
12
- .description("列出可用资源库")
13
- .option("--json", "输出 JSON");
13
+ .description(text(runtime.product, "列出可用资源库", "List available repositories"))
14
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
14
15
  bindAction(repositoryList, "organization", async (context, signal) => {
15
16
  const result = await runtime.telemetry.stage("repository", () => (runtime.dam.repositoryList({
16
17
  access: organizationAccess(context.state),
@@ -24,16 +25,16 @@ export function registerDamCommands(program, runtime) {
24
25
  });
25
26
  }, { operation: "dam.repository.list" });
26
27
  const folder = dam.command("folder")
27
- .description("DAM 文件夹")
28
+ .description(text(runtime.product, "DAM 文件夹", "Asset folders"))
28
29
  .helpCommand(false);
29
30
  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");
31
+ .description(text(runtime.product, "列出文件夹", "List folders"))
32
+ .option("--repository-id <id>", text(runtime.product, "指定资源库 ID", "repository ID"))
33
+ .option("--parent-id <id>", text(runtime.product, "父文件夹 ID", "parent folder ID"))
34
+ .option("--query <text>", text(runtime.product, "按名称筛选", "filter by name"))
35
+ .option("--page <n>", text(runtime.product, "页码", "page number"), positiveInteger)
36
+ .option("--page-size <1-100>", text(runtime.product, "分页大小", "page size"), pageSize)
37
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
37
38
  bindPreparedAction(folderList, "organization", async (signal) => {
38
39
  signal.throwIfAborted();
39
40
  const options = folderList.opts();
@@ -61,13 +62,13 @@ export function registerDamCommands(program, runtime) {
61
62
  });
62
63
  }, { operation: "dam.folder.list" });
63
64
  const tag = dam.command("tag")
64
- .description("DAM 标签")
65
+ .description(text(runtime.product, "DAM 标签", "Asset tags"))
65
66
  .helpCommand(false);
66
67
  const tagList = leaf(tag.command("list"))
67
- .description("列出标签")
68
- .option("--repository-id <id>", "指定资源库 ID")
69
- .option("--query <text>", "按名称筛选")
70
- .option("--json", "输出 JSON");
68
+ .description(text(runtime.product, "列出标签", "List tags"))
69
+ .option("--repository-id <id>", text(runtime.product, "指定资源库 ID", "repository ID"))
70
+ .option("--query <text>", text(runtime.product, "按名称筛选", "filter by name"))
71
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
71
72
  bindPreparedAction(tagList, "organization", async (signal) => {
72
73
  signal.throwIfAborted();
73
74
  const options = tagList.opts();
@@ -92,11 +93,11 @@ export function registerDamCommands(program, runtime) {
92
93
  });
93
94
  }, { operation: "dam.tag.list" });
94
95
  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");
96
+ .description(text(runtime.product, "下载一个素材", "Download an asset"))
97
+ .argument("<asset>", text(runtime.product, "素材 ID", "asset ID"))
98
+ .option("--repository-id <id>", text(runtime.product, "所属资源库 ID", "repository ID"))
99
+ .option("--output-dir <directory>", text(runtime.product, "输出目录", "output directory"))
100
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
100
101
  bindPreparedAction(download, "organization", async (signal) => {
101
102
  signal.throwIfAborted();
102
103
  const options = download.opts();
@@ -122,12 +123,12 @@ export function registerDamCommands(program, runtime) {
122
123
  });
123
124
  }, { operation: "dam.download" });
124
125
  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");
126
+ .description(text(runtime.product, "重命名一个素材或文件夹", "Rename an asset or folder"))
127
+ .argument("<entry>", text(runtime.product, "素材或文件夹 ID", "asset or folder ID"))
128
+ .requiredOption("--kind <asset|folder>", text(runtime.product, "条目类型", "entry kind"))
129
+ .requiredOption("--title <title>", text(runtime.product, "新标题", "new title"))
130
+ .option("--repository-id <id>", text(runtime.product, "所属资源库 ID", "repository ID"))
131
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
131
132
  bindPreparedAction(rename, "organization", async (signal) => {
132
133
  signal.throwIfAborted();
133
134
  const options = rename.opts();
@@ -155,13 +156,13 @@ export function registerDamCommands(program, runtime) {
155
156
  }, { operation: "dam.rename" });
156
157
  for (const operation of ["move", "copy"]) {
157
158
  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");
159
+ .description(text(runtime.product, operation === "move" ? "移动一个素材或文件夹" : "复制一个素材或文件夹", operation === "move" ? "Move an asset or folder" : "Copy an asset or folder"))
160
+ .argument("<entry>", text(runtime.product, "素材或文件夹 ID", "asset or folder ID"))
161
+ .requiredOption("--kind <asset|folder>", text(runtime.product, "条目类型", "entry kind"))
162
+ .requiredOption("--target-folder-id <id>", text(runtime.product, "目标文件夹 ID;根目录使用 0", "target folder ID; use 0 for root"))
163
+ .option("--repository-id <source-id>", text(runtime.product, "源资源库 ID", "source repository ID"))
164
+ .option("--target-repository-id <target-id>", text(runtime.product, "目标资源库 ID", "target repository ID"))
165
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
165
166
  bindPreparedAction(command, "organization", async (signal) => {
166
167
  signal.throwIfAborted();
167
168
  const options = command.opts();
@@ -198,8 +199,8 @@ export function registerDamCommands(program, runtime) {
198
199
  });
199
200
  }, { operation: `dam.${operation}` });
200
201
  }
201
- const list = browseCommand(leaf(dam.command("list")))
202
- .description("查看最近素材");
202
+ const list = browseCommand(leaf(dam.command("list")), runtime)
203
+ .description(text(runtime.product, "查看最近素材", "List recent assets"));
203
204
  bindPreparedAction(list, "organization", async (signal) => {
204
205
  signal.throwIfAborted();
205
206
  const input = browseInput(list);
@@ -219,9 +220,9 @@ export function registerDamCommands(program, runtime) {
219
220
  });
220
221
  });
221
222
  }, { operation: "dam.list" });
222
- const search = browseCommand(leaf(dam.command("search")))
223
- .description("按关键词搜索素材")
224
- .argument("<query>", "搜索关键词");
223
+ const search = browseCommand(leaf(dam.command("search")), runtime)
224
+ .description(text(runtime.product, "按关键词搜索素材", "Search assets by keyword"))
225
+ .argument("<query>", text(runtime.product, "搜索关键词", "search query"));
225
226
  bindPreparedAction(search, "organization", async (signal) => {
226
227
  signal.throwIfAborted();
227
228
  const input = {
@@ -245,9 +246,9 @@ export function registerDamCommands(program, runtime) {
245
246
  });
246
247
  }, { operation: "dam.search" });
247
248
  const get = leaf(dam.command("get"))
248
- .description("查看素材完整信息")
249
- .argument("<asset>", "素材 ID")
250
- .option("--json", "输出 JSON");
249
+ .description(text(runtime.product, "查看素材完整信息", "Show complete asset information"))
250
+ .argument("<asset>", text(runtime.product, "素材 ID", "asset ID"))
251
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
251
252
  bindPreparedAction(get, "organization", async (signal) => {
252
253
  signal.throwIfAborted();
253
254
  const input = { asset_id: position(get, 0) };
@@ -268,15 +269,15 @@ export function registerDamCommands(program, runtime) {
268
269
  });
269
270
  }, { operation: "dam.get" });
270
271
  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");
272
+ .description(text(runtime.product, "上传本地文件并创建素材", "Upload a local file and create an asset"))
273
+ .argument("<file>", text(runtime.product, "本地文件路径", "local file path"))
274
+ .option("--repository-id <id>", text(runtime.product, "目标资源库 ID", "target repository ID"))
275
+ .option("--title <title>", text(runtime.product, "素材标题", "asset title"))
276
+ .option("--folder-id <id>", text(runtime.product, "目标文件夹 ID", "target folder ID"))
277
+ .option("--tag-ids <csv>", text(runtime.product, "标签 ID,逗号分隔", "comma-separated tag IDs"))
278
+ .option("--allow-sensitive-path", text(runtime.product, "明确允许受保护路径", "allow a protected local path"))
279
+ .option("--wait-analysis", text(runtime.product, "等待素材分析结束", "wait for asset analysis"))
280
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
280
281
  bindPreparedAction(upload, "organization", async (signal) => {
281
282
  signal.throwIfAborted();
282
283
  const options = upload.opts();
@@ -306,10 +307,10 @@ export function registerDamCommands(program, runtime) {
306
307
  });
307
308
  }, { operation: "dam.upload" });
308
309
  const deleteCommand = leaf(dam.command("delete"))
309
- .description("删除素材")
310
- .argument("<asset>", "素材 ID")
311
- .option("--repository-id <id>", "所属资源库 ID")
312
- .option("--permanent", "移入回收站后永久删除");
310
+ .description(text(runtime.product, "删除素材", "Delete an asset"))
311
+ .argument("<asset>", text(runtime.product, "素材 ID", "asset ID"))
312
+ .option("--repository-id <id>", text(runtime.product, "所属资源库 ID", "repository ID"))
313
+ .option("--permanent", text(runtime.product, "移入回收站后永久删除", "permanently delete after moving to trash"));
313
314
  bindPreparedAction(deleteCommand, "organization", async (signal) => {
314
315
  signal.throwIfAborted();
315
316
  const options = deleteCommand.opts();
@@ -329,26 +330,26 @@ export function registerDamCommands(program, runtime) {
329
330
  });
330
331
  await runtime.telemetry.stage("output", async () => {
331
332
  runtime.presenter.success(input.permanent === true
332
- ? "素材已永久删除。"
333
- : "素材已移入回收站。");
333
+ ? text(runtime.product, "素材已永久删除。", "Asset permanently deleted.")
334
+ : text(runtime.product, "素材已移入回收站。", "Asset moved to trash."));
334
335
  });
335
336
  }, { operation: "dam.delete" });
336
337
  }
337
338
  function leaf(command) {
338
339
  return command.allowExcessArguments(false);
339
340
  }
340
- function browseCommand(command) {
341
+ function browseCommand(command, runtime) {
341
342
  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");
343
+ .option("--repository-id <id>", text(runtime.product, "指定资源库 ID", "repository ID"))
344
+ .option("--all-repositories", text(runtime.product, "查询全部可访问资源库", "query all accessible repositories"))
345
+ .option("--folder-id <id>", text(runtime.product, "指定文件夹 ID", "folder ID"))
346
+ .option("--no-include-child-folder", text(runtime.product, "不包含子文件夹", "exclude child folders"))
347
+ .option("--type <type>", text(runtime.product, "素材类型", "asset type"))
348
+ .option("--asset-format <csv>", text(runtime.product, "文件格式,逗号分隔", "comma-separated file formats"))
349
+ .option("--tag-ids <csv>", text(runtime.product, "标签 ID,逗号分隔", "comma-separated tag IDs"))
350
+ .option("--page-size <1-100>", text(runtime.product, "请求分页大小", "request page size"), pageSize)
351
+ .option("--cursor <id>", text(runtime.product, "分页游标", "pagination cursor"))
352
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
352
353
  }
353
354
  function browseInput(command) {
354
355
  const options = command.opts();
@@ -1,11 +1,12 @@
1
1
  import { bindAction, bindPreparedAction } from "./action-binding.js";
2
+ import { productText as text } from "../product/text.js";
2
3
  export function registerEditorCommands(program, runtime) {
3
4
  const editor = program.command("editor")
4
- .description("连接并操作 AI+ Editor")
5
+ .description(text(runtime.product, "连接并操作 AI+ Editor", "Connect to and operate insMind Editor"))
5
6
  .helpCommand(false);
6
7
  const connect = leaf(editor.command("connect"))
7
- .description("打开并连接一个 AI+ Editor 作品")
8
- .argument("[target]", "作品 ID 或完整 URL");
8
+ .description(text(runtime.product, "打开并连接一个 AI+ Editor 作品", "Open and connect to an Editor work"))
9
+ .argument("[target]", text(runtime.product, "作品 ID 或完整 URL", "work ID or complete URL"));
9
10
  bindPreparedAction(connect, "none", async (signal) => {
10
11
  signal.throwIfAborted();
11
12
  const target = connect.processedArgs[0];
@@ -17,18 +18,18 @@ export function registerEditorCommands(program, runtime) {
17
18
  writeJson(runtime, result);
18
19
  });
19
20
  const disconnect = leaf(editor.command("disconnect"))
20
- .description("断开当前 Editor 连接");
21
+ .description(text(runtime.product, "断开当前 Editor 连接", "Disconnect the current Editor"));
21
22
  bindAction(disconnect, "none", async (_context, signal) => {
22
23
  writeJson(runtime, await runtime.editor.disconnect({ signal }));
23
24
  });
24
25
  const snapshot = leaf(editor.command("snapshot"))
25
- .description("读取当前作品快照");
26
+ .description(text(runtime.product, "读取当前作品快照", "Read the current work snapshot"));
26
27
  bindAction(snapshot, "none", async (_context, signal) => {
27
28
  writeJson(runtime, await runtime.editor.snapshot({ signal }));
28
29
  });
29
30
  const apply = leaf(editor.command("apply"))
30
- .description("按顺序应用元素操作")
31
- .requiredOption("--input <file|->", "从文件或标准输入读取 JSON");
31
+ .description(text(runtime.product, "按顺序应用元素操作", "Apply element operations in order"))
32
+ .requiredOption("--input <file|->", text(runtime.product, "从文件或标准输入读取 JSON", "read JSON from a file or stdin"));
32
33
  bindPreparedAction(apply, "none", async (signal) => {
33
34
  signal.throwIfAborted();
34
35
  const source = apply.opts().input;
@@ -39,8 +40,8 @@ export function registerEditorCommands(program, runtime) {
39
40
  writeJson(runtime, await runtime.editor.apply({ input, signal }));
40
41
  });
41
42
  const screenshot = leaf(editor.command("screenshot"))
42
- .description("导出当前作品 PNG 截图")
43
- .option("--json", "输出 JSON");
43
+ .description(text(runtime.product, "导出当前作品 PNG 截图", "Export a PNG screenshot of the current work"))
44
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
44
45
  bindAction(screenshot, "none", async (_context, signal) => {
45
46
  const result = await runtime.editor.screenshot({ signal });
46
47
  runtime.presenter.result(result, {
@@ -49,8 +50,8 @@ export function registerEditorCommands(program, runtime) {
49
50
  });
50
51
  });
51
52
  const save = leaf(editor.command("save"))
52
- .description("保存当前 AI+ Editor 作品")
53
- .option("--json", "输出 JSON");
53
+ .description(text(runtime.product, "保存当前 AI+ Editor 作品", "Save the current Editor work"))
54
+ .option("--json", text(runtime.product, "输出 JSON", "output JSON"));
54
55
  bindAction(save, "none", async (_context, signal) => {
55
56
  const result = await runtime.editor.save({ signal });
56
57
  runtime.presenter.result(result, {
@@ -2,7 +2,7 @@ import { CommanderError } from "commander";
2
2
  import { AuthExpiredError, AuthRequiredError } from "../features/auth/access-policy.js";
3
3
  import { AuthStateInvalidError } from "../features/auth/state.js";
4
4
  import { SsoRequestError } from "../features/auth/sso-service.js";
5
- import { DeviceAuthorizationExpiredError } from "../features/auth/use-cases.js";
5
+ import { AuthorizedAccountContextMissingError, DeviceAuthorizationExpiredError } from "../features/auth/use-cases.js";
6
6
  import { AgentInputError } from "../features/agent/use-cases.js";
7
7
  import { ToolInputError } from "../features/tool/catalog.js";
8
8
  import { ToolTaskFailedError } from "../features/tool/tool-api-adapter.js";
@@ -39,24 +39,36 @@ export function isHelpOutcome(error) {
39
39
  && (error.code === "commander.helpDisplayed" || error.code === "commander.version");
40
40
  }
41
41
  export function mapCliError(error, options) {
42
+ const insmind = options.product?.id === "insmind";
43
+ const command = options.product?.commandName ?? "gd-cli";
42
44
  if (options.aborted)
43
45
  return { kind: "interrupted", exitCode: 130 };
44
46
  if (error instanceof AuthRequiredError) {
47
+ if (insmind)
48
+ return failure("AUTH_REQUIRED", `Run ${command} auth login first.`, 1, [`${command} auth login`]);
45
49
  return failure("AUTH_REQUIRED", "请先执行 gd-cli auth login", 1, ["gd-cli auth login"]);
46
50
  }
47
51
  if (error instanceof AuthExpiredError) {
52
+ if (insmind)
53
+ return failure("AUTH_EXPIRED", "Authentication expired. Sign out and sign in again.", 1, [`${command} auth logout`, `${command} auth login`]);
48
54
  return failure("AUTH_EXPIRED", "登录已过期,请退出后重新登录。", 1, ["gd-cli auth logout", "gd-cli auth login"]);
49
55
  }
50
56
  if (error instanceof AuthStateInvalidError) {
57
+ if (insmind)
58
+ return failure("AUTH_STATE_INVALID", "Local authentication state is invalid. Sign out and sign in again.", 1, [`${command} auth logout`, `${command} auth login`]);
51
59
  return failure("AUTH_STATE_INVALID", "本地登录状态已失效,请退出后重新登录。", 1, ["gd-cli auth logout", "gd-cli auth login"]);
52
60
  }
53
61
  if (error instanceof OrganizationInvalidError) {
62
+ if (insmind)
63
+ return failure("AUTH_STATE_INVALID", "The account context is unavailable. Sign out and sign in again.", 1, [`${command} auth logout`, `${command} auth login`]);
54
64
  return failure("ORG_INVALID", "所选组织不可用,请重新选择组织。", 1, ["gd-cli org switch"]);
55
65
  }
56
66
  if (error instanceof UpdateError) {
57
67
  return failure(error.code, error.message, 1, error.nextSteps);
58
68
  }
59
69
  if (error instanceof EditorTargetError) {
70
+ if (insmind)
71
+ return failure(error.code, "Editor target must be a work ID or a complete trusted insMind Editor URL.", 2);
60
72
  return failure(error.code, error.message, 2);
61
73
  }
62
74
  if (error instanceof OrgUsageError
@@ -68,36 +80,52 @@ export function mapCliError(error, options) {
68
80
  || error instanceof DamInputError
69
81
  || error instanceof LocalFileInputError
70
82
  || error instanceof CommanderError) {
83
+ if (insmind)
84
+ return failure("USAGE_ERROR", "Invalid command or arguments. See --help.", 2);
71
85
  return failure("USAGE_ERROR", "命令或参数无效,请查看 --help。", 2);
72
86
  }
73
87
  if (isInsufficientCreditsError(error)) {
88
+ if (insmind)
89
+ return failure("INSUFFICIENT_CREDITS", "There are not enough insMind credits to complete this creation.", 1, [`Buy credits at ${options.product.pricingUrl.href}`, "Retry after adding credits."]);
74
90
  return failure("INSUFFICIENT_CREDITS", "当前组织稿豆不足,无法完成本次创作。", 1, INSUFFICIENT_CREDITS_NEXT_STEPS);
75
91
  }
76
92
  if (error instanceof RemoteProtocolError) {
93
+ if (insmind)
94
+ return failure("REMOTE_PROTOCOL_INCOMPATIBLE", "The insMind service returned a result this CLI cannot safely process; processing stopped to avoid duplicate creation.", 1);
77
95
  return failure("REMOTE_PROTOCOL_INCOMPATIBLE", "稿定服务返回了当前 CLI 无法安全处理的结果;为避免重复创作,已停止处理。", 1);
78
96
  }
79
97
  if (error instanceof ToolTaskFailedError) {
80
- return failure("TOOL_TASK_FAILED", "稿定工具任务执行失败。", 1);
98
+ return failure("TOOL_TASK_FAILED", insmind ? "The Tool task failed." : "稿定工具任务执行失败。", 1);
81
99
  }
82
100
  if (error instanceof DamAssetNotFoundError) {
83
- return failure("DAM_ASSET_NOT_FOUND", "未找到指定的 DAM 素材。", 1);
101
+ return failure("DAM_ASSET_NOT_FOUND", insmind ? "The requested asset was not found." : "未找到指定的 DAM 素材。", 1);
84
102
  }
85
103
  if (error instanceof DamFolderNotFoundError) {
86
- return failure("DAM_FOLDER_NOT_FOUND", "未找到指定的 DAM 文件夹。", 1);
104
+ return failure("DAM_FOLDER_NOT_FOUND", insmind ? "The requested folder was not found." : "未找到指定的 DAM 文件夹。", 1);
87
105
  }
88
106
  if (error instanceof DamAssetNotDownloadableError) {
107
+ if (insmind)
108
+ return failure("DAM_ASSET_NOT_DOWNLOADABLE", "The asset has no downloadable source file. Check its current status first.", 1, [`${command} dam get <asset-id>`]);
89
109
  return failure("DAM_ASSET_NOT_DOWNLOADABLE", "该素材当前没有可下载的源文件;请先查看素材状态。", 1, ["gd-cli dam get <asset-id>"]);
90
110
  }
91
111
  if (error instanceof DamDownloadDestinationExistsError) {
92
- return failure("DAM_DOWNLOAD_DESTINATION_EXISTS", "目标文件已存在,未覆盖任何文件;请更换输出目录或移走同名文件。", 1);
112
+ return failure("DAM_DOWNLOAD_DESTINATION_EXISTS", insmind
113
+ ? "The destination file already exists and was not overwritten. Choose another output directory."
114
+ : "目标文件已存在,未覆盖任何文件;请更换输出目录或移走同名文件。", 1);
93
115
  }
94
116
  if (error instanceof DamOperationUnconfirmedError) {
95
- return failure("DAM_OPERATION_UNCONFIRMED", "操作已提交,但未能确认最终结果;请先查询目标位置,不要立即重复提交。", 1);
117
+ return failure("DAM_OPERATION_UNCONFIRMED", insmind
118
+ ? "The operation was submitted, but its final result could not be confirmed. Check the destination before retrying."
119
+ : "操作已提交,但未能确认最终结果;请先查询目标位置,不要立即重复提交。", 1);
96
120
  }
97
121
  if (error instanceof DamOperationFailedError) {
98
- return failure("DAM_OPERATION_FAILED", "DAM 操作执行失败;请先按诊断 ID 查询原因,不要立即重复提交。", 1);
122
+ return failure("DAM_OPERATION_FAILED", insmind
123
+ ? "The asset operation failed. Use the diagnostic ID to investigate before retrying."
124
+ : "DAM 操作执行失败;请先按诊断 ID 查询原因,不要立即重复提交。", 1);
99
125
  }
100
126
  if (error instanceof DamRepositoryNotFoundError) {
127
+ if (insmind)
128
+ return failure("DAM_REPOSITORY_NOT_FOUND", "No accessible asset repository is available for this account.", 1, [`${command} auth logout`, `${command} auth login`]);
101
129
  return failure("DAM_REPOSITORY_NOT_FOUND", "当前组织没有可用的 DAM 资源库。", 1, ["gd-cli org list", "gd-cli org switch --org <org-id>"]);
102
130
  }
103
131
  if (error instanceof RemoteRequestError
@@ -105,18 +133,30 @@ export function mapCliError(error, options) {
105
133
  || error instanceof DeviceAuthorizationExpiredError
106
134
  || error instanceof OrganizationResponseError
107
135
  || error instanceof OrganizationBindFailedError
108
- || error instanceof ObjectStorageUploadError) {
136
+ || error instanceof ObjectStorageUploadError
137
+ || error instanceof AuthorizedAccountContextMissingError) {
138
+ if (insmind)
139
+ return failure("REMOTE_REQUEST_FAILED", "The insMind service request failed.", 1);
109
140
  return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败。", 1);
110
141
  }
111
142
  if (error instanceof OutputContractError) {
143
+ if (insmind)
144
+ return failure("INTERNAL_ERROR", "CLI result validation failed.", 1);
112
145
  return failure("INTERNAL_ERROR", "CLI 内部结果校验失败。", 1);
113
146
  }
114
147
  if (error instanceof EditorBridgeError
115
148
  || error instanceof EditorSessionNotFoundError
116
149
  || error instanceof EditorNotReadyError) {
150
+ if (insmind) {
151
+ if (error instanceof EditorSessionNotFoundError)
152
+ return failure(error.code, `No Editor session is available. Run ${command} editor connect [target] again.`, 1);
153
+ if (error instanceof EditorNotReadyError)
154
+ return failure(error.code, "The Editor page did not become ready in time.", 1);
155
+ return failure(error.code, "The Editor operation failed.", 1);
156
+ }
117
157
  return failure(error.code, error.message, 1);
118
158
  }
119
- return failure("INTERNAL_ERROR", "CLI 发生内部错误。", 1);
159
+ return failure("INTERNAL_ERROR", insmind ? "The CLI encountered an internal error." : "CLI 发生内部错误。", 1);
120
160
  }
121
161
  function isInsufficientCreditsError(error) {
122
162
  return (error instanceof RemoteRequestError
@@ -1,12 +1,13 @@
1
1
  import { bindAction } from "./action-binding.js";
2
2
  import { CliUsageError } from "./errors.js";
3
+ import { productText as text } from "../product/text.js";
3
4
  export function registerModelCommands(program, runtime) {
4
5
  const model = program.command("model")
5
- .description("创作模型")
6
+ .description(text(runtime.product, "创作模型", "Creation models"))
6
7
  .helpCommand(false);
7
8
  const list = model.command("list")
8
- .description("列出可用模型")
9
- .option("--tool <tool>", "按工具筛选")
9
+ .description(text(runtime.product, "列出可用模型", "List available models"))
10
+ .option("--tool <tool>", text(runtime.product, "按工具筛选", "filter by tool"))
10
11
  .allowExcessArguments(false);
11
12
  bindAction(list, "organization", async (context, signal) => {
12
13
  const selectedTool = list.opts().tool;
@@ -30,8 +31,8 @@ export function registerModelCommands(program, runtime) {
30
31
  });
31
32
  }, { operation: "model.list" });
32
33
  const get = model.command("get")
33
- .description("查看模型完整信息")
34
- .argument("<model>", "模型机器标识")
34
+ .description(text(runtime.product, "查看模型完整信息", "Show complete model information"))
35
+ .argument("<model>", text(runtime.product, "模型机器标识", "model machine identifier"))
35
36
  .allowExcessArguments(false);
36
37
  bindAction(get, "organization", async (context, signal) => {
37
38
  const modelName = get.processedArgs[0];
@@ -1,5 +1,6 @@
1
1
  import * as QRCode from "qrcode";
2
2
  export function createPresenter(options) {
3
+ const insmind = options.product?.id === "insmind";
3
4
  const qrCode = options.qrCode ?? ((text) => QRCode.toString(text, {
4
5
  type: "terminal",
5
6
  small: true
@@ -14,19 +15,19 @@ export function createPresenter(options) {
14
15
  options.writeOut(`${message}\n`);
15
16
  },
16
17
  async authorization(input) {
17
- options.writeError(`授权地址: ${input.url.href}\n`);
18
+ options.writeError(`${insmind ? "Authorization URL" : "授权地址"}: ${input.url.href}\n`);
18
19
  if (input.terminal)
19
20
  options.writeError(await qrCode(input.url.href));
20
21
  },
21
22
  notice(message) {
22
- options.writeError(`提示: ${message}\n`);
23
+ options.writeError(`${insmind ? "Notice" : "提示"}: ${message}\n`);
23
24
  },
24
25
  warning(message) {
25
- options.writeError(`警告: ${message}\n`);
26
+ options.writeError(`${insmind ? "Warning" : "警告"}: ${message}\n`);
26
27
  },
27
28
  error(outcome, presentation) {
28
29
  if (outcome.kind === "interrupted") {
29
- options.writeError("已取消。\n");
30
+ options.writeError(insmind ? "Cancelled.\n" : "已取消。\n");
30
31
  return;
31
32
  }
32
33
  if (presentation.json) {
@@ -43,10 +44,10 @@ export function createPresenter(options) {
43
44
  options.writeError(`${outcome.failure.code}: ${outcome.failure.message}\n`);
44
45
  const nextSteps = nextStepsFrom(outcome.failure.details);
45
46
  if (nextSteps.length > 0) {
46
- options.writeError(`下一步: ${nextSteps.join(",")}\n`);
47
+ options.writeError(`${insmind ? "Next steps" : "下一步"}: ${nextSteps.join(insmind ? ", " : ",")}\n`);
47
48
  }
48
49
  if (presentation.traceId) {
49
- options.writeError(`诊断 ID: ${presentation.traceId}\n`);
50
+ options.writeError(`${insmind ? "Diagnostic ID" : "诊断 ID"}: ${presentation.traceId}\n`);
50
51
  }
51
52
  }
52
53
  };
@@ -61,6 +62,18 @@ export const authStatusView = (value) => {
61
62
  ? `权限: ${status.credential.scope.join(", ")}`
62
63
  : "权限: 未声明", nextStepsLine(status.next_steps));
63
64
  };
65
+ export function authStatusViewFor(product) {
66
+ if (product.id !== "insmind")
67
+ return authStatusView;
68
+ return (value) => {
69
+ const status = value;
70
+ if (!status.logged_in)
71
+ return lines("Not signed in", nextStepsLineFor(status.next_steps, true));
72
+ return lines("Signed in", `Account: ${status.account.name ?? "Not recorded"}${status.account.user_id ? ` (${status.account.user_id})` : ""}`, `Credential: ${status.credential.valid ? "valid" : "expired"}; expires ${status.credential.expires_at}`, status.credential.scope.length > 0
73
+ ? `Scopes: ${status.credential.scope.join(", ")}`
74
+ : "Scopes: not declared", nextStepsLineFor(status.next_steps, true));
75
+ };
76
+ }
64
77
  export const orgListView = (value) => {
65
78
  const result = value;
66
79
  if (result.orgs.length === 0)
@@ -92,6 +105,11 @@ export const repositoryListView = (value) => {
92
105
  function lines(...values) {
93
106
  return values.filter((value) => Boolean(value)).join("\n");
94
107
  }
108
+ function nextStepsLineFor(nextSteps, english) {
109
+ return nextSteps.length > 0
110
+ ? `${english ? "Next steps" : "下一步"}: ${nextSteps.join(english ? ", " : ",")}`
111
+ : undefined;
112
+ }
95
113
  function nextStepsLine(nextSteps) {
96
114
  return nextSteps.length > 0 ? `下一步: ${nextSteps.join(",")}` : undefined;
97
115
  }
@@ -1,11 +1,12 @@
1
1
  import { bindAction, bindPreparedAction } from "./action-binding.js";
2
2
  import { CliUsageError } from "./errors.js";
3
+ import { productText as text } from "../product/text.js";
3
4
  export function registerToolCommands(program, runtime) {
4
5
  const tool = program.command("tool")
5
- .description("创作工具")
6
+ .description(text(runtime.product, "创作工具", "Creation tools"))
6
7
  .helpCommand(false);
7
8
  const list = tool.command("list")
8
- .description("列出可用工具")
9
+ .description(text(runtime.product, "列出可用工具", "List available tools"))
9
10
  .allowExcessArguments(false);
10
11
  bindAction(list, "organization", async (context, signal) => {
11
12
  const result = await runtime.tool.listTools({
@@ -21,10 +22,10 @@ export function registerToolCommands(program, runtime) {
21
22
  });
22
23
  }, { operation: "tool.list" });
23
24
  const call = tool.command("call")
24
- .description("调用创作工具")
25
- .argument("<tool>", "工具机器标识")
26
- .option("--schema", "输出当前工具所有模型参数并集的输入 Schema")
27
- .option("--input <file|->", "从文件或标准输入读取参数 JSON")
25
+ .description(text(runtime.product, "调用创作工具", "Call a creation tool"))
26
+ .argument("<tool>", text(runtime.product, "工具机器标识", "tool machine identifier"))
27
+ .option("--schema", text(runtime.product, "输出当前工具所有模型参数并集的输入 Schema", "output the tool-wide input Schema"))
28
+ .option("--input <file|->", text(runtime.product, "从文件或标准输入读取参数 JSON", "read argument JSON from a file or stdin"))
28
29
  .allowExcessArguments(false);
29
30
  bindPreparedAction(call, "organization", async (signal) => {
30
31
  signal.throwIfAborted();
@@ -1,14 +1,15 @@
1
1
  import { bindAction } from "./action-binding.js";
2
+ import { productText as text } from "../product/text.js";
2
3
  export function registerUpdateCommand(program, runtime) {
3
4
  const update = program.command("update")
4
- .description("更新 GD CLI 与内置 Agent Skill")
5
+ .description(text(runtime.product, "更新 GD CLI 与内置 Agent Skill", "Update insMind CLI and its bundled Agent Skill"))
5
6
  .allowExcessArguments(false);
6
7
  bindAction(update, "none", async (_context, signal) => {
7
8
  const result = await runtime.update.run(signal);
8
9
  await runtime.telemetry.stage("output", async () => {
9
10
  runtime.presenter.success(result.updated
10
- ? `gd-cli 已更新到 ${result.version},Agent Skill 已同步。`
11
- : `gd-cli ${result.version} 已是最新版本,Agent Skill 已同步。`);
11
+ ? text(runtime.product, `gd-cli 已更新到 ${result.version},Agent Skill 已同步。`, `insmind-cli updated to ${result.version}; Agent Skill synchronized.`)
12
+ : text(runtime.product, `gd-cli ${result.version} 已是最新版本,Agent Skill 已同步。`, `insmind-cli ${result.version} is current; Agent Skill synchronized.`));
12
13
  });
13
14
  }, { operation: "update" });
14
15
  }
@@ -4,7 +4,7 @@ import { createLocalJsonFile, LocalFileError } from "../../platform/local-json-f
4
4
  import { AuthStateInvalidError, parseStoredAuthState } from "./state.js";
5
5
  export function createLocalCredentialStore(options = {}) {
6
6
  const file = options.file ?? createLocalJsonFile({
7
- directory: join(options.homeDirectory ?? homedir(), ".gd"),
7
+ directory: join(options.homeDirectory ?? homedir(), options.stateDirectory ?? ".gd"),
8
8
  filename: "credentials.json"
9
9
  });
10
10
  return {
@@ -44,7 +44,9 @@ export function createSsoService(options) {
44
44
  }
45
45
  return {
46
46
  async startDeviceAuthorization(signal) {
47
- const response = await request("oauth/device-authorization?clientId=gdhub-cli", signal);
47
+ const response = await request(`oauth/device-authorization?${new URLSearchParams({
48
+ clientId: options.clientId ?? "gdhub-cli"
49
+ })}`, signal);
48
50
  if (!response.ok)
49
51
  throw new SsoRequestError(response.status);
50
52
  const body = await responseRecord(response);