@ranger1/dx 0.1.14 → 0.1.16

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/dx-cli.js CHANGED
@@ -33,7 +33,6 @@ 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'
37
36
 
38
37
  class DxCli {
39
38
  constructor(options = {}) {
@@ -68,7 +67,6 @@ class DxCli {
68
67
  export: args => handleExport(this, args),
69
68
  contracts: args => handleContracts(this, args),
70
69
  release: args => handleRelease(this, args),
71
- ai: args => handleAi(this, args),
72
70
  }
73
71
 
74
72
  this.flagDefinitions = FLAG_DEFINITIONS
@@ -299,8 +297,7 @@ class DxCli {
299
297
  // - help: printing help should never require env vars.
300
298
  // - status: should be available even when env is incomplete.
301
299
  // - release: only edits package.json versions.
302
- // - ai: wraps external opencode CLI, should not require project deps/prisma/env.
303
- const skipStartupChecks = new Set(['help', 'status', 'release', 'ai'])
300
+ const skipStartupChecks = new Set(['help', 'status', 'release'])
304
301
  if (skipStartupChecks.has(this.command)) {
305
302
  await this.routeCommand()
306
303
  return
package/lib/cli/help.js CHANGED
@@ -68,9 +68,6 @@ export function showHelp() {
68
68
  '',
69
69
  ' status 查看系统状态',
70
70
  '',
71
- ' ai <name> 运行一个预配置的 opencode 任务(从 commands.json 读取)',
72
- ' 透传: -- 后的参数将原样传给 opencode run',
73
- '',
74
71
  '选项:',
75
72
  ' --dev, --development 使用开发环境',
76
73
  ' --prod, --production 使用生产环境',
@@ -104,9 +101,6 @@ export function showHelp() {
104
101
  ' dx contracts # 导出 OpenAPI 并生成 Zod 合约',
105
102
  ' dx release version 1.2.3 # 同步 backend/front/admin-front 版本号',
106
103
  '',
107
- ' dx ai review',
108
- ' dx ai review -- --share --title "my run"',
109
- '',
110
104
  ' # Stagewise 桥接(固定端口,自动清理占用)',
111
105
  ' dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)',
112
106
  ' dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front)',
@@ -257,38 +251,6 @@ release 命令用法:
257
251
  `)
258
252
  return
259
253
 
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
-
292
254
  default:
293
255
  showHelp()
294
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,214 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs'
2
- import { resolve, join, isAbsolute } 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
- function isBareFilename(input) {
45
- const raw = String(input ?? '')
46
- if (!raw) return false
47
- if (raw.startsWith('.')) return false
48
- return !raw.includes('/') && !raw.includes('\\')
49
- }
50
-
51
- function resolvePromptPath(promptFile) {
52
- const projectRoot = process.env.DX_PROJECT_ROOT || process.cwd()
53
- const expanded = expandHomePath(promptFile)
54
-
55
- if (isAbsolute(expanded)) return [expanded]
56
-
57
- const dxDir = join(projectRoot, 'dx')
58
- const candidates = []
59
-
60
- // 仅文件名:优先在 dx/prompts 下找(不破坏历史:仍保留 project root fallback)
61
- if (isBareFilename(expanded)) {
62
- candidates.push(join(dxDir, 'prompts', expanded))
63
- candidates.push(resolve(projectRoot, expanded))
64
- candidates.push(resolve(dxDir, expanded))
65
- return candidates
66
- }
67
-
68
- // 相对路径:优先保持与历史一致(相对 project root),找不到再 fallback 到 dx/ 下
69
- candidates.push(resolve(projectRoot, expanded))
70
- candidates.push(resolve(dxDir, expanded))
71
- return candidates
72
- }
73
-
74
- export async function handleAi(cli, args = []) {
75
- const name = args[0]
76
- if (!name) {
77
- const names = Object.keys(cli.commands?.ai || {})
78
- logger.error('请指定 ai 任务名称')
79
- logger.info(`用法: ${cli.invocation} ai <name> [-- <opencode flags>...]`)
80
- if (names.length > 0) {
81
- logger.info(`可用任务: ${names.join(', ')}`)
82
- }
83
- process.exitCode = 1
84
- return
85
- }
86
-
87
- const config = resolveAiConfig(cli, name)
88
- if (!config) {
89
- const names = Object.keys(cli.commands?.ai || {})
90
- logger.error(`未找到 ai 任务配置: ${name}`)
91
- if (names.length > 0) {
92
- logger.info(`可用任务: ${names.join(', ')}`)
93
- }
94
- process.exitCode = 1
95
- return
96
- }
97
-
98
- const availability = ensureOpencodeAvailable()
99
- if (!availability.ok) {
100
- logger.error('未找到 opencode 可执行文件(PATH 中不存在 opencode)')
101
- logger.info('请先安装并确保 `opencode --version` 可用')
102
- process.exitCode = 1
103
- return
104
- }
105
-
106
- const promptFile = config?.promptFile
107
- if (!promptFile) {
108
- logger.error(`ai.${name} 缺少 promptFile 配置(指向一个 .md 文件)`)
109
- process.exitCode = 1
110
- return
111
- }
112
-
113
- const promptCandidates = resolvePromptPath(promptFile)
114
- const promptPath = promptCandidates.find(p => existsSync(p)) || promptCandidates[0]
115
- if (!promptPath || !existsSync(promptPath)) {
116
- logger.error(`未找到提示词文件: ${promptFile}`)
117
- if (promptCandidates.length <= 1) {
118
- logger.info(`解析后的路径: ${promptPath}`)
119
- } else {
120
- logger.info('解析后的候选路径:')
121
- for (const p of promptCandidates) logger.info(`- ${p}`)
122
- }
123
- process.exitCode = 1
124
- return
125
- }
126
-
127
- let promptText = ''
128
- try {
129
- promptText = readFileSync(promptPath, 'utf8')
130
- } catch (error) {
131
- logger.error(`读取提示词文件失败: ${promptFile}`)
132
- logger.error(error?.message || String(error))
133
- process.exitCode = 1
134
- return
135
- }
136
-
137
- // 固定全权限:这会让 opencode 在当前目录拥有 bash/edit 等工具的自动执行权
138
- if (!cli.flags.Y) {
139
- const confirmed = await confirmManager.confirmDangerous(
140
- `ai.${name}(将以 OPENCODE_PERMISSION="allow" 运行,全权限)`,
141
- '当前目录',
142
- false,
143
- )
144
- if (!confirmed) {
145
- logger.info('操作已取消')
146
- return
147
- }
148
- }
149
-
150
- const model = config?.model ? String(config.model) : null
151
- const agent = config?.agent ? String(config.agent) : null
152
- const format = config?.format ? String(config.format) : null
153
- const attach = config?.attach ? String(config.attach) : null
154
-
155
- const passthrough = getPassthroughArgs(cli.args)
156
- const configPassthrough = Array.isArray(config?.passthrough)
157
- ? config.passthrough.map(v => String(v))
158
- : []
159
-
160
- const opencodeArgs = ['run']
161
- if (model) opencodeArgs.push('--model', model)
162
- if (agent) opencodeArgs.push('--agent', agent)
163
- if (format) opencodeArgs.push('--format', format)
164
- if (attach) opencodeArgs.push('--attach', attach)
165
- if (configPassthrough.length > 0) opencodeArgs.push(...configPassthrough)
166
- if (Array.isArray(passthrough) && passthrough.length > 0) opencodeArgs.push(...passthrough)
167
- opencodeArgs.push(promptText)
168
-
169
- logger.step(`ai ${name}`)
170
- logger.command(`opencode ${opencodeArgs.filter(a => a !== promptText).join(' ')} <prompt-from-file>`)
171
-
172
- await new Promise(resolvePromise => {
173
- const child = spawn('opencode', opencodeArgs, {
174
- cwd: process.cwd(),
175
- stdio: 'inherit',
176
- env: {
177
- ...process.env,
178
- // OpenCode expects OPENCODE_PERMISSION to be JSON (it JSON.parse's the value).
179
- OPENCODE_PERMISSION: '"allow"',
180
- },
181
- })
182
-
183
- const forwardSignal = signal => {
184
- try {
185
- child.kill(signal)
186
- } catch {}
187
- }
188
-
189
- const onSigint = () => forwardSignal('SIGINT')
190
- const onSigterm = () => forwardSignal('SIGTERM')
191
- process.on('SIGINT', onSigint)
192
- process.on('SIGTERM', onSigterm)
193
-
194
- const cleanup = () => {
195
- process.off('SIGINT', onSigint)
196
- process.off('SIGTERM', onSigterm)
197
- }
198
-
199
- child.on('error', error => {
200
- cleanup()
201
- logger.error(error?.message || String(error))
202
- process.exitCode = 1
203
- resolvePromise()
204
- })
205
-
206
- child.on('exit', code => {
207
- cleanup()
208
- if (typeof code === 'number' && code !== 0) {
209
- process.exitCode = code
210
- }
211
- resolvePromise()
212
- })
213
- })
214
- }