@ranger1/dx 0.1.10 → 0.1.12
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.
- package/lib/cli/commands/ai.js +178 -0
- package/lib/cli/dx-cli.js +4 -1
- package/lib/cli/help.js +150 -109
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
5
|
+
import { logger } from '../../logger.js'
|
|
6
|
+
import { confirmManager } from '../../confirm.js'
|
|
7
|
+
import { getPassthroughArgs } from '../args.js'
|
|
8
|
+
|
|
9
|
+
function ensureOpencodeAvailable() {
|
|
10
|
+
const result = spawnSync('opencode', ['--version'], { stdio: 'ignore' })
|
|
11
|
+
if (result?.error?.code === 'ENOENT') {
|
|
12
|
+
return { ok: false, reason: 'missing' }
|
|
13
|
+
}
|
|
14
|
+
return { ok: true }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveAiConfig(cli, name) {
|
|
18
|
+
const raw = cli.commands?.ai?.[name]
|
|
19
|
+
if (!raw) return null
|
|
20
|
+
|
|
21
|
+
const environment = cli.determineEnvironment()
|
|
22
|
+
const envKey = cli.normalizeEnvKey(environment)
|
|
23
|
+
let config = raw
|
|
24
|
+
|
|
25
|
+
// 允许按环境分支(保持与 build/start/export 一致)
|
|
26
|
+
if (typeof config === 'object' && config && !config.promptFile && !config.command) {
|
|
27
|
+
if (config[envKey]) config = config[envKey]
|
|
28
|
+
else if (envKey === 'staging' && config.prod) config = config.prod
|
|
29
|
+
else config = config.dev || config
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return config
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function expandHomePath(input) {
|
|
36
|
+
const raw = String(input ?? '')
|
|
37
|
+
if (raw === '~') return homedir()
|
|
38
|
+
if (raw.startsWith('~/') || raw.startsWith('~\\')) {
|
|
39
|
+
return resolve(homedir(), raw.slice(2))
|
|
40
|
+
}
|
|
41
|
+
return raw
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function handleAi(cli, args = []) {
|
|
45
|
+
const name = args[0]
|
|
46
|
+
if (!name) {
|
|
47
|
+
const names = Object.keys(cli.commands?.ai || {})
|
|
48
|
+
logger.error('请指定 ai 任务名称')
|
|
49
|
+
logger.info(`用法: ${cli.invocation} ai <name> [-- <opencode flags>...]`)
|
|
50
|
+
if (names.length > 0) {
|
|
51
|
+
logger.info(`可用任务: ${names.join(', ')}`)
|
|
52
|
+
}
|
|
53
|
+
process.exitCode = 1
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const config = resolveAiConfig(cli, name)
|
|
58
|
+
if (!config) {
|
|
59
|
+
const names = Object.keys(cli.commands?.ai || {})
|
|
60
|
+
logger.error(`未找到 ai 任务配置: ${name}`)
|
|
61
|
+
if (names.length > 0) {
|
|
62
|
+
logger.info(`可用任务: ${names.join(', ')}`)
|
|
63
|
+
}
|
|
64
|
+
process.exitCode = 1
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const availability = ensureOpencodeAvailable()
|
|
69
|
+
if (!availability.ok) {
|
|
70
|
+
logger.error('未找到 opencode 可执行文件(PATH 中不存在 opencode)')
|
|
71
|
+
logger.info('请先安装并确保 `opencode --version` 可用')
|
|
72
|
+
process.exitCode = 1
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const promptFile = config?.promptFile
|
|
77
|
+
if (!promptFile) {
|
|
78
|
+
logger.error(`ai.${name} 缺少 promptFile 配置(指向一个 .md 文件)`)
|
|
79
|
+
process.exitCode = 1
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const promptPath = resolve(process.cwd(), expandHomePath(promptFile))
|
|
84
|
+
if (!existsSync(promptPath)) {
|
|
85
|
+
logger.error(`未找到提示词文件: ${promptFile}`)
|
|
86
|
+
logger.info(`解析后的路径: ${promptPath}`)
|
|
87
|
+
process.exitCode = 1
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let promptText = ''
|
|
92
|
+
try {
|
|
93
|
+
promptText = readFileSync(promptPath, 'utf8')
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error(`读取提示词文件失败: ${promptFile}`)
|
|
96
|
+
logger.error(error?.message || String(error))
|
|
97
|
+
process.exitCode = 1
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 固定全权限:这会让 opencode 在当前目录拥有 bash/edit 等工具的自动执行权
|
|
102
|
+
if (!cli.flags.Y) {
|
|
103
|
+
const confirmed = await confirmManager.confirmDangerous(
|
|
104
|
+
`ai.${name}(将以 OPENCODE_PERMISSION="allow" 运行,全权限)`,
|
|
105
|
+
'当前目录',
|
|
106
|
+
false,
|
|
107
|
+
)
|
|
108
|
+
if (!confirmed) {
|
|
109
|
+
logger.info('操作已取消')
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const model = config?.model ? String(config.model) : null
|
|
115
|
+
const agent = config?.agent ? String(config.agent) : null
|
|
116
|
+
const format = config?.format ? String(config.format) : null
|
|
117
|
+
const attach = config?.attach ? String(config.attach) : null
|
|
118
|
+
|
|
119
|
+
const passthrough = getPassthroughArgs(cli.args)
|
|
120
|
+
const configPassthrough = Array.isArray(config?.passthrough)
|
|
121
|
+
? config.passthrough.map(v => String(v))
|
|
122
|
+
: []
|
|
123
|
+
|
|
124
|
+
const opencodeArgs = ['run']
|
|
125
|
+
if (model) opencodeArgs.push('--model', model)
|
|
126
|
+
if (agent) opencodeArgs.push('--agent', agent)
|
|
127
|
+
if (format) opencodeArgs.push('--format', format)
|
|
128
|
+
if (attach) opencodeArgs.push('--attach', attach)
|
|
129
|
+
if (configPassthrough.length > 0) opencodeArgs.push(...configPassthrough)
|
|
130
|
+
if (Array.isArray(passthrough) && passthrough.length > 0) opencodeArgs.push(...passthrough)
|
|
131
|
+
opencodeArgs.push(promptText)
|
|
132
|
+
|
|
133
|
+
logger.step(`ai ${name}`)
|
|
134
|
+
logger.command(`opencode ${opencodeArgs.filter(a => a !== promptText).join(' ')} <prompt-from-file>`)
|
|
135
|
+
|
|
136
|
+
await new Promise(resolvePromise => {
|
|
137
|
+
const child = spawn('opencode', opencodeArgs, {
|
|
138
|
+
cwd: process.cwd(),
|
|
139
|
+
stdio: 'inherit',
|
|
140
|
+
env: {
|
|
141
|
+
...process.env,
|
|
142
|
+
// OpenCode expects OPENCODE_PERMISSION to be JSON (it JSON.parse's the value).
|
|
143
|
+
OPENCODE_PERMISSION: '"allow"',
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const forwardSignal = signal => {
|
|
148
|
+
try {
|
|
149
|
+
child.kill(signal)
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const onSigint = () => forwardSignal('SIGINT')
|
|
154
|
+
const onSigterm = () => forwardSignal('SIGTERM')
|
|
155
|
+
process.on('SIGINT', onSigint)
|
|
156
|
+
process.on('SIGTERM', onSigterm)
|
|
157
|
+
|
|
158
|
+
const cleanup = () => {
|
|
159
|
+
process.off('SIGINT', onSigint)
|
|
160
|
+
process.off('SIGTERM', onSigterm)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
child.on('error', error => {
|
|
164
|
+
cleanup()
|
|
165
|
+
logger.error(error?.message || String(error))
|
|
166
|
+
process.exitCode = 1
|
|
167
|
+
resolvePromise()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
child.on('exit', code => {
|
|
171
|
+
cleanup()
|
|
172
|
+
if (typeof code === 'number' && code !== 0) {
|
|
173
|
+
process.exitCode = code
|
|
174
|
+
}
|
|
175
|
+
resolvePromise()
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
}
|
package/lib/cli/dx-cli.js
CHANGED
|
@@ -33,6 +33,7 @@ import { handlePackage } from './commands/package.js'
|
|
|
33
33
|
import { handleExport } from './commands/export.js'
|
|
34
34
|
import { handleContracts } from './commands/contracts.js'
|
|
35
35
|
import { handleRelease } from './commands/release.js'
|
|
36
|
+
import { handleAi } from './commands/ai.js'
|
|
36
37
|
|
|
37
38
|
class DxCli {
|
|
38
39
|
constructor(options = {}) {
|
|
@@ -67,6 +68,7 @@ class DxCli {
|
|
|
67
68
|
export: args => handleExport(this, args),
|
|
68
69
|
contracts: args => handleContracts(this, args),
|
|
69
70
|
release: args => handleRelease(this, args),
|
|
71
|
+
ai: args => handleAi(this, args),
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
this.flagDefinitions = FLAG_DEFINITIONS
|
|
@@ -297,7 +299,8 @@ class DxCli {
|
|
|
297
299
|
// - help: printing help should never require env vars.
|
|
298
300
|
// - status: should be available even when env is incomplete.
|
|
299
301
|
// - release: only edits package.json versions.
|
|
300
|
-
|
|
302
|
+
// - ai: wraps external opencode CLI, should not require project deps/prisma/env.
|
|
303
|
+
const skipStartupChecks = new Set(['help', 'status', 'release', 'ai'])
|
|
301
304
|
if (skipStartupChecks.has(this.command)) {
|
|
302
305
|
await this.routeCommand()
|
|
303
306
|
return
|
package/lib/cli/help.js
CHANGED
|
@@ -2,115 +2,124 @@ import { getPackageVersion } from '../version.js'
|
|
|
2
2
|
|
|
3
3
|
export function showHelp() {
|
|
4
4
|
const version = getPackageVersion()
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
service
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
target
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
target
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
action
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
dx db
|
|
38
|
-
dx db deploy --
|
|
39
|
-
dx db
|
|
40
|
-
dx db script fix-
|
|
41
|
-
dx db script
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
type
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
action
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
target
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
action
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
--
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
dx start
|
|
87
|
-
dx
|
|
88
|
-
dx
|
|
89
|
-
dx
|
|
90
|
-
dx
|
|
91
|
-
dx
|
|
92
|
-
dx
|
|
93
|
-
dx
|
|
94
|
-
dx
|
|
95
|
-
dx
|
|
96
|
-
dx
|
|
97
|
-
dx
|
|
98
|
-
dx
|
|
99
|
-
dx
|
|
100
|
-
dx
|
|
101
|
-
dx
|
|
102
|
-
|
|
103
|
-
#
|
|
104
|
-
dx
|
|
105
|
-
dx
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
dx
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
5
|
+
const lines = [
|
|
6
|
+
'',
|
|
7
|
+
`DX CLI v${version} - 统一开发环境管理工具`,
|
|
8
|
+
'',
|
|
9
|
+
'用法:',
|
|
10
|
+
' dx <命令> [选项] [参数...]',
|
|
11
|
+
'',
|
|
12
|
+
'命令:',
|
|
13
|
+
' start [service] [环境标志] 启动/桥接服务',
|
|
14
|
+
' service: backend, front, admin, all, dev, stack, stagewise-front, stagewise-admin (默认: dev)',
|
|
15
|
+
' stack: PM2 交互式服务栈管理(推荐)- 同时启动三个服务并提供交互式命令',
|
|
16
|
+
' 环境标志: --dev, --staging, --prod, --test, --e2e(支持别名 --development、--production 等)',
|
|
17
|
+
" 说明: 传入 --staging 时会加载 '.env.staging(.local)' 层,同时复用生产构建/启动流程",
|
|
18
|
+
'',
|
|
19
|
+
' build [target] [环境标志] 构建应用',
|
|
20
|
+
' target: backend, shared, front, admin, mobile, all, sdk, affected (默认: all)',
|
|
21
|
+
' 环境标志: --dev, --staging, --prod, --test, --e2e(未指定时默认 --dev)',
|
|
22
|
+
'',
|
|
23
|
+
' deploy <target> [环境标志] 部署前端到 Vercel',
|
|
24
|
+
' target: front, admin, telegram-bot, all',
|
|
25
|
+
' 环境标志: --dev, --staging, --prod(默认 --staging)',
|
|
26
|
+
'',
|
|
27
|
+
' install 安装依赖(使用 frozen-lockfile 确保版本一致)',
|
|
28
|
+
'',
|
|
29
|
+
' package backend [环境标志] 构建后端部署包(生成 backend-<version>-<sha>.tar.gz)',
|
|
30
|
+
' 环境标志: --dev, --staging, --prod, --test, --e2e(默认 --dev)',
|
|
31
|
+
' 产物位置: dist/backend/backend-*.tar.gz',
|
|
32
|
+
' 内含: dist/、node_modules(生产依赖)、prisma/、config/.env.runtime、bin/start.sh',
|
|
33
|
+
'',
|
|
34
|
+
' db [action] [环境标志] 数据库操作',
|
|
35
|
+
' action: generate, migrate, deploy, reset, seed, format, script',
|
|
36
|
+
' 用法示例:',
|
|
37
|
+
' dx db migrate --dev --name add_user_table # 创建新的迁移(开发环境需指定名称)',
|
|
38
|
+
' dx db deploy --dev # 应用开发环境已有迁移',
|
|
39
|
+
' dx db deploy --prod # 生产环境迁移(复用 deploy 流程,需确认)',
|
|
40
|
+
' dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)',
|
|
41
|
+
' dx db script fix-pending-transfer-status --prod # 运行数据库脚本(生产环境,需确认)',
|
|
42
|
+
' dx db script my-script --dev -- --arg1 --arg2 # 向脚本传递额外参数(-- 后面的部分)',
|
|
43
|
+
'',
|
|
44
|
+
' test [type] [target] [path] [-t pattern] 运行测试',
|
|
45
|
+
' type: e2e, unit (默认: e2e)',
|
|
46
|
+
' target: backend, all (默认: all)',
|
|
47
|
+
' path: 测试文件路径 (可选,仅支持e2e backend)',
|
|
48
|
+
' -t pattern: 指定测试用例名称模式 (可选,需要和path一起使用)',
|
|
49
|
+
'',
|
|
50
|
+
' worktree [action] [num...] Git Worktree管理',
|
|
51
|
+
' action: make, del, list, clean',
|
|
52
|
+
' num: issue编号 (make时需要1个,del时支持多个)',
|
|
53
|
+
' 支持批量删除: dx worktree del 123 456 789',
|
|
54
|
+
' 支持非交互式: dx worktree del 123 -Y',
|
|
55
|
+
' 注意:该封装与原生 git worktree 行为不同,勿混用',
|
|
56
|
+
'',
|
|
57
|
+
' lint 运行代码检查',
|
|
58
|
+
'',
|
|
59
|
+
' contracts [generate] 导出后端 OpenAPI 并生成 Zod 合约(packages/api-contracts)',
|
|
60
|
+
'',
|
|
61
|
+
' release version <semver> 统一同步 backend/front/admin-front 的版本号',
|
|
62
|
+
'',
|
|
63
|
+
' clean [target] 清理操作',
|
|
64
|
+
' target: all, deps (默认: all)',
|
|
65
|
+
'',
|
|
66
|
+
' cache [action] 缓存清理',
|
|
67
|
+
' action: clear (默认: clear)',
|
|
68
|
+
'',
|
|
69
|
+
' status 查看系统状态',
|
|
70
|
+
'',
|
|
71
|
+
' ai <name> 运行一个预配置的 opencode 任务(从 commands.json 读取)',
|
|
72
|
+
' 透传: -- 后的参数将原样传给 opencode run',
|
|
73
|
+
'',
|
|
74
|
+
'选项:',
|
|
75
|
+
' --dev, --development 使用开发环境',
|
|
76
|
+
' --prod, --production 使用生产环境',
|
|
77
|
+
' --staging, --stage 使用预发环境(加载 .env.staging*.,复用生产流程)',
|
|
78
|
+
' --test 使用测试环境',
|
|
79
|
+
' --e2e 使用E2E测试环境',
|
|
80
|
+
' -Y, --yes 跳过所有确认提示',
|
|
81
|
+
' -v, --verbose 详细输出',
|
|
82
|
+
' -h, --help 显示此帮助信息',
|
|
83
|
+
' -V, --version 显示版本号',
|
|
84
|
+
'',
|
|
85
|
+
'示例:',
|
|
86
|
+
' dx start stack # PM2 交互式服务栈(推荐)- 同时管理三个服务',
|
|
87
|
+
' dx start backend --dev # 启动后端开发服务',
|
|
88
|
+
' dx start front --dev # 启动用户前端开发服务',
|
|
89
|
+
' dx start admin --dev # 启动管理后台开发服务',
|
|
90
|
+
' dx start all # 同时启动所有开发服务(默认 --dev)',
|
|
91
|
+
' dx build all --prod # 构建所有应用(生产环境)',
|
|
92
|
+
' dx db deploy --dev # 应用开发环境数据库迁移',
|
|
93
|
+
' dx db reset --prod -Y # 重置生产数据库(跳过确认)',
|
|
94
|
+
' dx test e2e backend # 运行后端E2E测试',
|
|
95
|
+
' dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts # 运行单个E2E测试文件',
|
|
96
|
+
' dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts -t "should list all activity definitions" # 运行特定测试用例',
|
|
97
|
+
' dx deploy front --staging # 部署前端到 Vercel(staging)',
|
|
98
|
+
' dx worktree make 88 # 为issue #88创建worktree',
|
|
99
|
+
' dx worktree del 88 # 删除issue #88的worktree',
|
|
100
|
+
' dx worktree del 88 89 90 -Y # 批量删除多个worktree(非交互式)',
|
|
101
|
+
' dx worktree list # 列出所有worktree',
|
|
102
|
+
' dx clean deps # 清理并重新安装依赖',
|
|
103
|
+
' dx cache clear # 清除 Nx 与依赖缓存',
|
|
104
|
+
' dx contracts # 导出 OpenAPI 并生成 Zod 合约',
|
|
105
|
+
' dx release version 1.2.3 # 同步 backend/front/admin-front 版本号',
|
|
106
|
+
'',
|
|
107
|
+
' dx ai review',
|
|
108
|
+
' dx ai review -- --share --title "my run"',
|
|
109
|
+
'',
|
|
110
|
+
' # Stagewise 桥接(固定端口,自动清理占用)',
|
|
111
|
+
' dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)',
|
|
112
|
+
' dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front)',
|
|
113
|
+
'',
|
|
114
|
+
' # Start 用法示例',
|
|
115
|
+
' dx start backend --prod # 以生产环境变量启动后端',
|
|
116
|
+
' dx start backend --dev # 以开发环境变量启动后端',
|
|
117
|
+
' dx start backend --e2e # 以 E2E 环境变量启动后端',
|
|
118
|
+
'',
|
|
119
|
+
'',
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
console.log(lines.join('\n'))
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
export function showCommandHelp(command) {
|
|
@@ -248,6 +257,38 @@ release 命令用法:
|
|
|
248
257
|
`)
|
|
249
258
|
return
|
|
250
259
|
|
|
260
|
+
case 'ai':
|
|
261
|
+
console.log(
|
|
262
|
+
[
|
|
263
|
+
'',
|
|
264
|
+
'ai 命令用法:',
|
|
265
|
+
' dx ai <name> [-- <opencode flags>...]',
|
|
266
|
+
'',
|
|
267
|
+
'说明:',
|
|
268
|
+
' - 从 dx/config/commands.json 的 ai.<name> 读取一个固定配置并执行 opencode run',
|
|
269
|
+
' - 本次运行固定注入 OPENCODE_PERMISSION="allow"(全权限)',
|
|
270
|
+
'',
|
|
271
|
+
'commands.json 示例:',
|
|
272
|
+
' {',
|
|
273
|
+
' "ai": {',
|
|
274
|
+
' "review": {',
|
|
275
|
+
' "promptFile": "./prompts/review.md",',
|
|
276
|
+
' "model": "openai/gpt-4.1",',
|
|
277
|
+
' "agent": "general",',
|
|
278
|
+
' "format": "default",',
|
|
279
|
+
' "passthrough": ["--share"]',
|
|
280
|
+
' }',
|
|
281
|
+
' }',
|
|
282
|
+
' }',
|
|
283
|
+
'',
|
|
284
|
+
'示例:',
|
|
285
|
+
' dx ai review',
|
|
286
|
+
' dx ai review -- --title "my run"',
|
|
287
|
+
'',
|
|
288
|
+
].join('\n'),
|
|
289
|
+
)
|
|
290
|
+
return
|
|
291
|
+
|
|
251
292
|
default:
|
|
252
293
|
showHelp()
|
|
253
294
|
}
|