@icode-js/icode 3.0.4 → 3.1.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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -3
  3. package/bin/icode.js +4 -4
  4. package/package.json +1 -1
  5. package/src/cli.js +54 -23
  6. package/src/commands/ai.js +4 -4
  7. package/src/commands/checkout.js +2 -2
  8. package/src/commands/clean.js +1 -1
  9. package/src/commands/codereview.js +1 -1
  10. package/src/commands/completion.js +90 -0
  11. package/src/commands/config.js +117 -7
  12. package/src/commands/explain.js +2 -2
  13. package/src/commands/help.js +7 -0
  14. package/src/commands/info.js +1 -1
  15. package/src/commands/migrate.js +2 -2
  16. package/src/commands/push.js +29 -8
  17. package/src/commands/sync.js +2 -2
  18. package/src/commands/tag.js +2 -2
  19. package/src/commands/undo.js +60 -9
  20. package/src/core/{ai-client.js → ai/client.js} +3 -3
  21. package/src/core/{ai-config.js → ai/config.js} +2 -2
  22. package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
  23. package/src/core/cli/completion.js +687 -0
  24. package/src/core/cli/update-notifier.js +138 -0
  25. package/src/core/{shell.js → commands/shell.js} +13 -1
  26. package/src/core/{config-store.js → config/store.js} +55 -3
  27. package/src/core/{git-context.js → git/context.js} +25 -10
  28. package/src/core/{git-service.js → git/service.js} +44 -4
  29. package/src/core/remote-merge/client.js +42 -0
  30. package/src/core/remote-merge/providers/gitlab.js +537 -0
  31. package/src/core/remote-merge/shared.js +49 -0
  32. package/src/core/{prompts.js → tools/interactive.js} +46 -5
  33. package/src/workflows/ai-codereview-workflow.js +4 -4
  34. package/src/workflows/ai-commit-workflow.js +6 -6
  35. package/src/workflows/ai-conflict-workflow.js +4 -4
  36. package/src/workflows/ai-explain-workflow.js +4 -4
  37. package/src/workflows/ai-risk-review-workflow.js +1 -1
  38. package/src/workflows/checkout-workflow.js +3 -3
  39. package/src/workflows/clean-workflow.js +5 -5
  40. package/src/workflows/info-workflow.js +4 -4
  41. package/src/workflows/migrate-workflow.js +4 -4
  42. package/src/workflows/push-workflow.js +259 -24
  43. package/src/workflows/rollback-workflow.js +4 -4
  44. package/src/workflows/sync-workflow.js +4 -4
  45. package/src/workflows/tag-workflow.js +4 -4
  46. package/src/workflows/undo-workflow.js +12 -5
  47. /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
  48. /package/src/core/{args.js → cli/args.js} +0 -0
  49. /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
  50. /package/src/core/{hook-diagnostics.js → git/hook-diagnostics.js} +0 -0
  51. /package/src/core/{loading.js → tools/loading.js} +0 -0
  52. /package/src/core/{logger.js → tools/logger.js} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -23,6 +23,26 @@ icode info
23
23
  icode config ai list
24
24
  ```
25
25
 
26
+ ## Shell Tab 补全
27
+
28
+ `icode` 现在支持生成 `bash` / `zsh` 的 tab 补全脚本。
29
+
30
+ 临时启用:
31
+
32
+ ```bash
33
+ source <(icode completion bash)
34
+ source <(icode completion zsh)
35
+ ```
36
+
37
+ 持久启用可以把对应命令追加到 `~/.bashrc` 或 `~/.zshrc`。
38
+
39
+ 补全范围包括:
40
+
41
+ - 顶层命令和嵌套子命令
42
+ - 已知参数开关与枚举值
43
+ - 常见 Git 分支 / ref
44
+ - `icode config ai ...` 下已配置的 profile 名称
45
+
26
46
  ## 全局参数
27
47
 
28
48
  - `-h, --help`:查看帮助(也可 `icode <command> -h` 查看子命令参数)
@@ -105,7 +125,7 @@ icode checkout <branch> [base] [--push-origin] [--pull-main] [--repo-mode auto|s
105
125
  ### push
106
126
 
107
127
  ```bash
108
- icode push [targetBranch...] [-m "commit message"] [--ai-commit] [--pull-main] [--not-push-current]
128
+ icode push [targetBranch...] [-m "commit message"] [--ai-commit] [--pull-main] [--not-push-current] [--remote-merge]
109
129
  ```
110
130
 
111
131
  示例:
@@ -120,6 +140,7 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
120
140
  - 若仓库存在 husky/hooks/commitlint 规则,AI commit 会先扫描本地规范再生成提交信息
121
141
  - 支持把当前分支合并到多个目标分支
122
142
  - 默认使用本地 merge 模式(会生成 merge commit)
143
+ - 支持远程 PR/MR API 模式(需配置平台密钥)
123
144
  - 支持受保护分支策略(通过 `icode config protect ...` 管理)
124
145
 
125
146
  参数说明:
@@ -128,6 +149,7 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
128
149
  - `-m, --message <msg>`:提交信息(未填会提示输入)
129
150
  - `-y, --yes`:自动确认(跳过确认提示)
130
151
  - `--local-merge`:显式声明使用本地 merge 模式(当前为默认行为,会切换分支并生成 merge commit)
152
+ - `--remote-merge`:改为远程 PR/MR 模式;会先推送源分支,再通过 API 创建合并请求;若发生冲突会暂停,并输出明确失败原因
131
153
  - `--ai-commit`:push 前自动执行 AI commit
132
154
  - `--ai-profile <name>`:指定 AI profile(用于 `--ai-commit`)
133
155
  - `--pull-main`:提交前同步主分支到当前分支
@@ -140,6 +162,19 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
140
162
  说明:
141
163
  - `push` 的布尔开关(如 `--ai-commit`、`--pull-main`、`--no-verify`、`-y`)仅在命令行显式传入时生效。
142
164
 
165
+ 远程合并配置示例:
166
+
167
+ ```bash
168
+ icode config platform remote-merge set --provider gitlab --api-key rm_xxx
169
+ ```
170
+
171
+ 建议:
172
+ - 密钥按平台维度管理,例如 GitLab / GitHub / 自建平台。
173
+ - GitLab API 地址固定为仓库 `origin` 对应站点的 `/api/v4`,无需再单独配置服务地址。
174
+ - 项目/仓库默认启用远程合并;如个别仓库不希望启用,可显式设置 `repositories.<repo>.remoteMerge.enabled=false`。
175
+ - 远程合并会直接根据 `origin` 自动提取 GitLab 项目地址;若无法识别,会提示你修正仓库 remote 配置。
176
+ - 这样更符合密钥复用与安全边界,避免多个仓库重复维护同一平台 API 地址。
177
+
143
178
  ### ai
144
179
 
145
180
  ```bash
@@ -239,18 +274,20 @@ icode tag [--name <tag>] [--message <msg>] [--from <ref>]
239
274
  ### undo
240
275
 
241
276
  ```bash
242
- icode undo [--mode revert|soft|mixed|hard] [--ref <ref>] [--recover continue|abort|keep]
277
+ icode undo [ref] [--mode revert|soft|mixed|hard] [--ref <ref>] [--hash <hash>] [--recover continue|abort|keep]
243
278
  ```
244
279
 
245
280
  - 向导式撤销命令(交互选择回滚策略)
246
281
  - 适合新人或低频 Git 操作场景
247
- - 也支持非交互参数:`--mode` + `--ref`
282
+ - 也支持非交互参数:`--mode` + `--ref` / `--hash`
283
+ - 支持用位置参数指定 commit hash,例如:`icode undo a1b2c3d --mode revert -y`
248
284
  - 检测到 revert/cherry-pick 冲突时,会提示继续或中止
249
285
 
250
286
  参数说明:
251
287
 
252
288
  - `--mode <mode>`:回滚模式(`revert|soft|mixed|hard`)
253
289
  - `--ref <ref>`:回滚目标,默认按 mode 自动给出
290
+ - `--hash <hash>`:按 commit hash 指定回滚目标(等同 `--ref`)
254
291
  - `--recover <action>`:冲突恢复策略(`continue|abort|keep`)
255
292
  - `-y, --yes`:自动确认(跳过确认提示)
256
293
  - `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
package/bin/icode.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import('../src/cli.js').catch((error) => {
3
- const message = error?.message || String(error)
4
- console.error(`\x1b[31m[icode] ${message}\x1b[0m`)
5
- process.exit(error?.exitCode || 1)
2
+ import { handleCliError, main } from '../src/cli.js'
3
+
4
+ main().catch((error) => {
5
+ handleCliError(error)
6
6
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icode-js/icode",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "Modern git workflow CLI with robust parent-repo and hook handling",
5
5
  "type": "module",
6
6
  "author": "C",
package/src/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { runAiCommand } from './commands/ai.js'
2
2
  import { runCleanCommand } from './commands/clean.js'
3
3
  import { runCodeReviewCommand } from './commands/codereview.js'
4
+ import { runCompletionCommand, runHiddenCompletionCommand } from './commands/completion.js'
4
5
  import { runCheckoutCommand } from './commands/checkout.js'
5
6
  import { runConfigCommand } from './commands/config.js'
6
7
  import { runExplainCommand } from './commands/explain.js'
@@ -12,8 +13,9 @@ import { runSyncCommand } from './commands/sync.js'
12
13
  import { runTagCommand } from './commands/tag.js'
13
14
  import { runUndoCommand } from './commands/undo.js'
14
15
  import { asIcodeError } from './core/errors.js'
15
- import { logger } from './core/logger.js'
16
- import { normalizeLegacyArgs } from './core/args.js'
16
+ import { normalizeLegacyArgs } from './core/cli/args.js'
17
+ import { notifyIfCliUpdateAvailable } from './core/cli/update-notifier.js'
18
+ import { logger } from './core/tools/logger.js'
17
19
 
18
20
  function isTruthy(value) {
19
21
  const normalized = String(value || '').trim().toLowerCase()
@@ -31,6 +33,7 @@ function serializeErrorMeta(meta) {
31
33
  const COMMANDS = {
32
34
  ai: runAiCommand,
33
35
  codereview: runCodeReviewCommand,
36
+ completion: runCompletionCommand,
34
37
  checkout: runCheckoutCommand,
35
38
  push: runPushCommand,
36
39
  sync: runSyncCommand,
@@ -43,7 +46,44 @@ const COMMANDS = {
43
46
  info: runInfoCommand,
44
47
  help: async () => {
45
48
  printMainHelp()
49
+ },
50
+ __complete: runHiddenCompletionCommand
51
+ }
52
+
53
+ const NO_UPDATE_NOTIFICATION_COMMANDS = new Set(['help', 'completion', '__complete'])
54
+
55
+ /**
56
+ * 执行 CLI 主流程并在命令成功后触发升级提示检查。
57
+ * 输入为命令行参数与可注入依赖,输出为建议的退出码。
58
+ */
59
+ export async function runCli(argv = process.argv.slice(2), dependencies = {}) {
60
+ const commands = dependencies.commands || COMMANDS
61
+ const printHelp = dependencies.printMainHelp || printMainHelp
62
+ const notifyUpdate = dependencies.notifyIfCliUpdateAvailable || notifyIfCliUpdateAvailable
63
+ const { globalArgs, commandName, commandArgs } = parseEntryArgs(argv)
64
+ const globalResult = applyGlobalFlags(globalArgs)
65
+
66
+ if (globalResult.shouldExit) {
67
+ return globalResult.exitCode
68
+ }
69
+
70
+ if (!commandName) {
71
+ printHelp()
72
+ return 0
73
+ }
74
+
75
+ const command = commands[commandName]
76
+ if (!command) {
77
+ throw new Error(`未知命令: ${commandName}`)
78
+ }
79
+
80
+ await command(commandArgs)
81
+
82
+ if (!NO_UPDATE_NOTIFICATION_COMMANDS.has(commandName)) {
83
+ await notifyUpdate()
46
84
  }
85
+
86
+ return 0
47
87
  }
48
88
 
49
89
  function parseEntryArgs(argv) {
@@ -86,28 +126,19 @@ function applyGlobalFlags(globalArgs) {
86
126
  }
87
127
  }
88
128
 
89
- async function main() {
90
- const { globalArgs, commandName, commandArgs } = parseEntryArgs(process.argv.slice(2))
91
- const globalResult = applyGlobalFlags(globalArgs)
92
-
93
- if (globalResult.shouldExit) {
94
- process.exit(globalResult.exitCode)
95
- }
96
-
97
- if (!commandName) {
98
- printMainHelp()
99
- process.exit(0)
100
- }
101
-
102
- const command = COMMANDS[commandName]
103
- if (!command) {
104
- throw new Error(`未知命令: ${commandName}`)
105
- }
106
-
107
- await command(commandArgs)
129
+ /**
130
+ * 启动 CLI 入口,复用 runCli 处理参数与命令调度。
131
+ * 输入为可选 argv 与依赖注入,输出为退出码。
132
+ */
133
+ export async function main(argv = process.argv.slice(2), dependencies = {}) {
134
+ return runCli(argv, dependencies)
108
135
  }
109
136
 
110
- main().catch((error) => {
137
+ /**
138
+ * 统一处理 CLI 异常并输出对用户可读的错误信息。
139
+ * 输入为任意异常对象,输出为进程退出。
140
+ */
141
+ export function handleCliError(error) {
111
142
  const normalized = asIcodeError(error)
112
143
  logger.error(normalized.message)
113
144
 
@@ -128,4 +159,4 @@ main().catch((error) => {
128
159
  }
129
160
 
130
161
  process.exit(normalized.exitCode || 1)
131
- })
162
+ }
@@ -1,9 +1,9 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { normalizeLegacyArgs } from '../core/args.js'
3
- import { getAiCommandOptions } from '../core/ai-config.js'
4
- import { formatAiCommitSummary } from '../core/ai-commit-summary.js'
2
+ import { formatAiCommitSummary } from '../core/ai/commit-summary.js'
3
+ import { getAiCommandOptions } from '../core/ai/config.js'
4
+ import { normalizeLegacyArgs } from '../core/cli/args.js'
5
5
  import { IcodeError } from '../core/errors.js'
6
- import { logger } from '../core/logger.js'
6
+ import { logger } from '../core/tools/logger.js'
7
7
  import { runAiCodeReviewWorkflow } from '../workflows/ai-codereview-workflow.js'
8
8
  import { runAiCommitWorkflow } from '../workflows/ai-commit-workflow.js'
9
9
  import { runAiConflictWorkflow } from '../workflows/ai-conflict-workflow.js'
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { normalizeLegacyArgs } from '../core/args.js'
3
- import { logger } from '../core/logger.js'
2
+ import { normalizeLegacyArgs } from '../core/cli/args.js'
3
+ import { logger } from '../core/tools/logger.js'
4
4
  import { runCheckoutWorkflow } from '../workflows/checkout-workflow.js'
5
5
 
6
6
  function printHelp() {
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { logger } from '../core/logger.js'
2
+ import { logger } from '../core/tools/logger.js'
3
3
  import { runCleanWorkflow } from '../workflows/clean-workflow.js'
4
4
 
5
5
  function printHelp() {
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { logger } from '../core/logger.js'
2
+ import { logger } from '../core/tools/logger.js'
3
3
  import { runAiCodeReviewWorkflow } from '../workflows/ai-codereview-workflow.js'
4
4
 
5
5
  function printHelp() {
@@ -0,0 +1,90 @@
1
+ import { parseArgs } from 'node:util'
2
+ import { IcodeError } from '../core/errors.js'
3
+ import { getCompletionCandidates, renderCompletionScript } from '../core/cli/completion.js'
4
+
5
+ /**
6
+ * 输出 completion 命令帮助,说明支持的 shell 与安装方式。
7
+ * 输入为空,输出为标准输出文本。
8
+ */
9
+ function printHelp() {
10
+ process.stdout.write(`
11
+ Usage:
12
+ icode completion <bash|zsh>
13
+
14
+ Options:
15
+ -h, --help 查看帮助
16
+
17
+ Examples:
18
+ source <(icode completion bash)
19
+ source <(icode completion zsh)
20
+ `)
21
+ }
22
+
23
+ /**
24
+ * 解析隐藏补全命令参数,提取当前词与已确认参数列表。
25
+ * 输入为隐藏命令的原始参数数组,输出为 current 和 previousWords。
26
+ */
27
+ function parseHiddenCompletionArgs(rawArgs) {
28
+ let current = ''
29
+ let separatorIndex = rawArgs.indexOf('--')
30
+
31
+ if (separatorIndex === -1) {
32
+ separatorIndex = rawArgs.length
33
+ }
34
+
35
+ for (let index = 0; index < separatorIndex; index += 1) {
36
+ const token = rawArgs[index]
37
+ if (token === '--current') {
38
+ current = rawArgs[index + 1] || ''
39
+ index += 1
40
+ }
41
+ }
42
+
43
+ return {
44
+ current,
45
+ previousWords: rawArgs.slice(separatorIndex + 1)
46
+ }
47
+ }
48
+
49
+ /**
50
+ * 执行公开的 completion 命令,输出对应 shell 的补全脚本。
51
+ * 输入为命令行参数数组,输出为脚本文本到标准输出。
52
+ */
53
+ export async function runCompletionCommand(rawArgs) {
54
+ const parsed = parseArgs({
55
+ args: rawArgs,
56
+ allowPositionals: true,
57
+ options: {
58
+ help: { type: 'boolean', short: 'h', default: false }
59
+ }
60
+ })
61
+
62
+ if (parsed.values.help || parsed.positionals.length === 0) {
63
+ printHelp()
64
+ return
65
+ }
66
+
67
+ const [shellName] = parsed.positionals
68
+ const normalizedShell = shellName.trim().toLowerCase()
69
+ if (!['bash', 'zsh'].includes(normalizedShell)) {
70
+ throw new IcodeError(`completion 仅支持 bash|zsh,当前输入: ${shellName}`, {
71
+ code: 'COMPLETION_SHELL_INVALID',
72
+ exitCode: 2
73
+ })
74
+ }
75
+
76
+ process.stdout.write(renderCompletionScript(normalizedShell))
77
+ }
78
+
79
+ /**
80
+ * 执行隐藏补全命令,只输出候选项,避免污染 shell 的补全结果。
81
+ * 输入为隐藏命令参数数组,输出为逐行候选项到标准输出。
82
+ */
83
+ export async function runHiddenCompletionCommand(rawArgs) {
84
+ const { current, previousWords } = parseHiddenCompletionArgs(rawArgs)
85
+ const candidates = getCompletionCandidates(previousWords, current)
86
+
87
+ if (candidates.length > 0) {
88
+ process.stdout.write(`${candidates.join('\n')}\n`)
89
+ }
90
+ }
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import { parseArgs } from 'node:util'
3
- import { askAi } from '../core/ai-client.js'
3
+ import { askAi } from '../core/ai/client.js'
4
4
  import {
5
5
  getAiCommandOptions,
6
6
  getAiProfileForDisplay,
@@ -11,28 +11,31 @@ import {
11
11
  upsertAiCommandOptions,
12
12
  upsertAiProfile,
13
13
  useAiProfile
14
- } from '../core/ai-config.js'
14
+ } from '../core/ai/config.js'
15
15
  import {
16
16
  deleteValue,
17
17
  getConfigFilePath,
18
+ getPlatformConfig,
18
19
  getRepoPolicy,
19
20
  getValue,
20
21
  readConfig,
22
+ setPlatformConfig,
21
23
  setRepoPolicy,
22
24
  setValue
23
- } from '../core/config-store.js'
24
- import { parseConfigValue } from '../core/args.js'
25
+ } from '../core/config/store.js'
26
+ import { parseConfigValue } from '../core/cli/args.js'
25
27
  import { IcodeError } from '../core/errors.js'
26
- import { resolveGitContext } from '../core/git-context.js'
27
- import { logger } from '../core/logger.js'
28
+ import { resolveGitContext } from '../core/git/context.js'
29
+ import { logger } from '../core/tools/logger.js'
28
30
 
31
+ // 输出 config 命令帮助,并展示当前支持的远程合并配置方式。
29
32
  function printHelp() {
30
33
  process.stdout.write(`
31
34
  Usage:
32
35
  icode config <command> [...args]
33
36
 
34
37
  Arguments:
35
- <command> 子命令(list/get/set/delete/ai/protect)
38
+ <command> 子命令(list/get/set/delete/ai/protect/platform)
36
39
 
37
40
  Commands:
38
41
  list 查看全部配置
@@ -43,6 +46,7 @@ Commands:
43
46
  protect list 查看受保护分支
44
47
  protect add <branch...> 添加受保护分支
45
48
  protect remove <branch...> 移除受保护分支
49
+ platform <subcommand> 平台级配置(如远程合并密钥)
46
50
 
47
51
  Options:
48
52
  --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承),仅影响 protect
@@ -52,11 +56,112 @@ Examples:
52
56
  icode config set defaults.repoMode strict
53
57
  icode config get defaults.repoMode
54
58
  icode config protect add main release
59
+ icode config platform remote-merge set --provider gitlab --api-key rm_xxx
55
60
  icode config ai set openai --format openai --base-url https://api.openai.com/v1 --api-key sk-xxx --model gpt-4o-mini --activate
56
61
  icode config ai set ollama --format ollama --base-url http://127.0.0.1:11434 --model qwen2.5:7b --activate
57
62
  `)
58
63
  }
59
64
 
65
+ function maskSecret(secret) {
66
+ const raw = String(secret || '').trim()
67
+ if (!raw) {
68
+ return ''
69
+ }
70
+ if (raw.length <= 8) {
71
+ return `${raw.slice(0, 2)}****${raw.slice(-1)}`
72
+ }
73
+ return `${raw.slice(0, 4)}****${raw.slice(-4)}`
74
+ }
75
+
76
+ // 输出 platform 子命令帮助,明确 remote-merge 仅需平台名与密钥。
77
+ function printPlatformHelp() {
78
+ process.stdout.write(`
79
+ Usage:
80
+ icode config platform <subcommand> [options]
81
+
82
+ Subcommands:
83
+ remote-merge show
84
+ remote-merge set --provider gitlab --api-key <key>
85
+
86
+ Examples:
87
+ icode config platform remote-merge show
88
+ icode config platform remote-merge set --provider gitlab --api-key rm_xxx
89
+ `)
90
+ }
91
+
92
+ // 处理平台级远程合并配置,当前只接受 GitLab 平台密钥写入。
93
+ async function runPlatformConfigCommand(args) {
94
+ if (!args.length || args[0] === '-h' || args[0] === '--help') {
95
+ printPlatformHelp()
96
+ return
97
+ }
98
+
99
+ const [domain, action, ...rest] = args
100
+ if (domain !== 'remote-merge') {
101
+ throw new IcodeError(`未知 platform 子命令: ${domain}`, {
102
+ code: 'CONFIG_PLATFORM_UNKNOWN_SUBCOMMAND',
103
+ exitCode: 2
104
+ })
105
+ }
106
+
107
+ if (action === 'show') {
108
+ const config = getPlatformConfig('remoteMerge')
109
+ process.stdout.write(`${JSON.stringify({
110
+ provider: config.provider || '',
111
+ hasApiKey: Boolean(config.apiKey),
112
+ apiKeyMasked: maskSecret(config.apiKey)
113
+ }, null, 2)}\n`)
114
+ return
115
+ }
116
+
117
+ if (action === 'set') {
118
+ const parsed = parseArgs({
119
+ args: rest,
120
+ allowPositionals: true,
121
+ options: {
122
+ provider: { type: 'string' },
123
+ 'api-key': { type: 'string' }
124
+ }
125
+ })
126
+
127
+ const provider = String(parsed.values.provider || '').trim()
128
+ const apiKey = String(parsed.values['api-key'] || '').trim()
129
+
130
+ if (!provider) {
131
+ throw new IcodeError('缺少 --provider,示例: icode config platform remote-merge set --provider gitlab --api-key xxx', {
132
+ code: 'CONFIG_PLATFORM_PROVIDER_REQUIRED',
133
+ exitCode: 2
134
+ })
135
+ }
136
+
137
+ if (!apiKey) {
138
+ throw new IcodeError('缺少 --api-key,示例: icode config platform remote-merge set --provider gitlab --api-key xxx', {
139
+ code: 'CONFIG_PLATFORM_API_KEY_REQUIRED',
140
+ exitCode: 2
141
+ })
142
+ }
143
+
144
+ if (provider !== 'gitlab') {
145
+ throw new IcodeError(`platform remote-merge 当前仅支持 gitlab,收到 provider=${provider}`, {
146
+ code: 'CONFIG_PLATFORM_PROVIDER_UNSUPPORTED',
147
+ exitCode: 2
148
+ })
149
+ }
150
+
151
+ setPlatformConfig('remoteMerge', {
152
+ provider,
153
+ apiKey
154
+ })
155
+ logger.success(`平台远程合并密钥已写入: ${provider}`)
156
+ return
157
+ }
158
+
159
+ throw new IcodeError('platform remote-merge 仅支持: show | set', {
160
+ code: 'CONFIG_PLATFORM_INVALID_ACTION',
161
+ exitCode: 2
162
+ })
163
+ }
164
+
60
165
  function printAiHelp() {
61
166
  process.stdout.write(`
62
167
  Usage:
@@ -386,6 +491,11 @@ export async function runConfigCommand(rawArgs) {
386
491
  return
387
492
  }
388
493
 
494
+ if (rawArgs[0] === 'platform') {
495
+ await runPlatformConfigCommand(rawArgs.slice(1))
496
+ return
497
+ }
498
+
389
499
  const parsed = parseArgs({
390
500
  args: rawArgs,
391
501
  allowPositionals: true,
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { getAiCommandOptions } from '../core/ai-config.js'
3
- import { logger } from '../core/logger.js'
2
+ import { getAiCommandOptions } from '../core/ai/config.js'
3
+ import { logger } from '../core/tools/logger.js'
4
4
  import { runAiExplainWorkflow } from '../workflows/ai-explain-workflow.js'
5
5
 
6
6
  function printHelp() {
@@ -1,3 +1,7 @@
1
+ /**
2
+ * 输出主帮助文本,集中展示命令总览与常用示例。
3
+ * 输入为空,输出为标准输出文本。
4
+ */
1
5
  export function printMainHelp() {
2
6
  process.stdout.write(`
3
7
  icode v3 - Git workflow CLI
@@ -18,6 +22,7 @@ Commands:
18
22
  config 查看和修改本地配置(含 AI profile)
19
23
  explain AI 解释 Git diff(自然语言)
20
24
  info 查看当前 git 与配置环境
25
+ completion 生成 bash/zsh tab 补全脚本
21
26
  help 查看帮助(命令总览)
22
27
 
23
28
  Global options:
@@ -38,12 +43,14 @@ Examples:
38
43
  icode push release test -m "feat: keep merge commit" --local-merge -y
39
44
  icode sync --all-local --merge-main
40
45
  icode clean --remote --force -y
46
+ icode undo a1b2c3d --mode revert -y
41
47
  icode undo --mode revert --ref HEAD~1 -y
42
48
  icode undo --recover abort
43
49
  icode migrate feature/login release --push -y
44
50
  icode migrate --interactive
45
51
  icode push --no-verify -m "chore: bypass hooks"
46
52
  icode config protect add main release
53
+ source <(icode completion zsh)
47
54
  icode info
48
55
  `)
49
56
  }
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { logger } from '../core/logger.js'
2
+ import { logger } from '../core/tools/logger.js'
3
3
  import { runInfoWorkflow } from '../workflows/info-workflow.js'
4
4
 
5
5
  function printHelp() {
@@ -1,7 +1,7 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { normalizeLegacyArgs } from '../core/args.js'
2
+ import { normalizeLegacyArgs } from '../core/cli/args.js'
3
3
  import { IcodeError } from '../core/errors.js'
4
- import { logger } from '../core/logger.js'
4
+ import { logger } from '../core/tools/logger.js'
5
5
  import { runMigrateWorkflow } from '../workflows/migrate-workflow.js'
6
6
 
7
7
  function printHelp() {