@fatdoge/wtree 0.1.9 → 0.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.
package/README.en.md CHANGED
@@ -11,8 +11,21 @@ English | [简体中文](https://github.com/FatDoge/wtree/blob/main/README.md)
11
11
  - Support for creating worktrees from new branches or existing branches/commits
12
12
  - Open worktrees instantly in your system file manager or preferred IDEs (Trae, Cursor, VS Code)
13
13
  - Support for Locking, Unlocking, and Pruning invalid worktrees
14
+ - Non-interactive mode: fully automated operations via CLI flags, ideal for scripts and AI Agents
15
+ - Provides Agent Skill for AI coding tools like Trae, Cursor, and Claude Code
14
16
  - Local API executes git commands securely on your machine, data never leaves your computer
15
17
 
18
+ ## Screenshots
19
+
20
+ <p align="center">
21
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/home.jpg" alt="Home" width="48%" />
22
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/new.jpg" alt="Create" width="48%" />
23
+ </p>
24
+ <p align="center">
25
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/settings.jpg" alt="Settings" width="48%" />
26
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/help.jpg" alt="Help" width="48%" />
27
+ </p>
28
+
16
29
  ## Installation
17
30
 
18
31
  Install globally via npm (specify the public registry if you are using a private one):
@@ -68,7 +81,7 @@ wtree --ui --port 0
68
81
  - `wtree`: Interactive main menu (Create/Delete/List/Open/Lock/Unlock/Prune)
69
82
  - `wtree list`: Print worktree list
70
83
  - `wtree create [branch]`: Create a worktree (interactive selection supported)
71
- - `wtree delete`: Delete a worktree (interactive selection, force deletion supported)
84
+ - `wtree delete [branch|path ...]`: Delete worktrees (interactive selection or specify targets directly, force deletion supported)
72
85
  - `wtree open [path|branch]`: Open a worktree
73
86
  - `wtree lock [path|branch]`: Lock a specific worktree to prevent it from being moved or deleted
74
87
  - `wtree unlock [path|branch]`: Unlock a specific worktree
@@ -78,6 +91,58 @@ wtree --ui --port 0
78
91
  - `wtree config set <key> <value>`: Set a configuration item
79
92
  - `wtree help`: View help information
80
93
 
94
+ ## CLI Flags
95
+
96
+ - `--ui`: Launch the local UI
97
+ - `--repo <path>`: Specify the repository path (defaults to the current directory)
98
+ - `--no-open`: Do not automatically open the browser
99
+ - `--port <number>`: Specify the UI port (`0` for auto-assign)
100
+ - `--json`: Output in JSON format (ideal for scripts and AI Agents)
101
+ - `--yes, -y`: Auto-confirm all prompts
102
+ - `--force, -f`: Force the operation (e.g., force-delete worktrees with uncommitted changes)
103
+ - `--dir <path>`: Specify worktree directory path (relative to the git root)
104
+ - `--base <ref>`: Base reference for new branch creation (e.g., `main`, `origin/main`)
105
+ - `--editor <name>`: Open in a specific editor after creation (`trae`, `cursor`, `code`, `none`)
106
+ - `--no-editor`: Do not open any editor after creation
107
+ - `--no-install`: Skip automatic dependency installation after creation
108
+
109
+ ## Non-Interactive Mode
110
+
111
+ All commands support fully non-interactive execution via CLI flags, suitable for scripts and AI Agents:
112
+
113
+ ```bash
114
+ # List worktrees (JSON output)
115
+ wtree list --json
116
+
117
+ # Create a worktree for an existing branch
118
+ wtree create feature/my-branch --yes --no-editor --no-install --json
119
+
120
+ # Create a worktree with a new branch based on main
121
+ wtree create feature/new-thing --base main --yes --dir worktrees/new-thing --no-editor --no-install --json
122
+
123
+ # Delete a specific worktree
124
+ wtree delete feature/old-branch --yes --json
125
+
126
+ # Force delete (even with uncommitted changes)
127
+ wtree delete feature/dirty --yes --force --json
128
+ ```
129
+
130
+ ## Agent Skill
131
+
132
+ `wtree` provides an Agent Skill that enables AI coding tools (Trae, Cursor, Claude Code, etc.) to manage git worktrees directly.
133
+
134
+ ### Install Skill
135
+
136
+ ```bash
137
+ # Install to current project
138
+ npx skills add FatDoge/wtree --skill wtree
139
+
140
+ # Install globally (available in all projects)
141
+ npx skills add FatDoge/wtree --skill wtree -g
142
+ ```
143
+
144
+ Once installed, the AI Agent will automatically recognize the `wtree` skill and invoke it when you need to manage worktrees.
145
+
81
146
  ## Configuration
82
147
 
83
148
  The UI settings page saves configuration to a local file:
package/README.md CHANGED
@@ -11,8 +11,21 @@
11
11
  - 支持创建新分支、从已有分支/提交创建 worktree
12
12
  - 支持在系统文件管理器或常用 IDE (Trae, Cursor, VS Code) 中一键打开
13
13
  - 支持锁定 (Lock) / 解锁 (Unlock) 以及清理 (Prune) 无效的 worktree
14
+ - 非交互模式:支持通过命令行参数完全自动化操作,适合脚本和 AI Agent 调用
15
+ - 提供 Agent Skill,可被 Trae / Cursor / Claude Code 等 AI 编码工具直接使用
14
16
  - 本地 API 执行 git 命令,数据不出机器
15
17
 
18
+ ## UI 截图
19
+
20
+ <p align="center">
21
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/home.jpg" alt="首页" width="48%" />
22
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/new.jpg" alt="创建页面" width="48%" />
23
+ </p>
24
+ <p align="center">
25
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/settings.jpg" alt="设置页面" width="48%" />
26
+ <img src="https://raw.githubusercontent.com/FatDoge/wtree/main/docs/screenshots/help.jpg" alt="帮助页面" width="48%" />
27
+ </p>
28
+
16
29
  ## 安装
17
30
 
18
31
  可以通过 npm 全局安装(如果使用了私有源,请指定官方源):
@@ -69,13 +82,21 @@ wtree --ui --port 0
69
82
  - `--repo <path>`:指定仓库路径(默认使用当前目录)
70
83
  - `--no-open`:不自动打开浏览器
71
84
  - `--port <number>`:指定 UI 端口(`0` 表示自动分配)
85
+ - `--json`:以 JSON 格式输出(适合脚本和 Agent 使用)
86
+ - `--yes, -y`:自动确认所有提示
87
+ - `--force, -f`:强制操作(如强制删除有未提交更改的 worktree)
88
+ - `--dir <path>`:指定 worktree 目录路径(相对于 git 根目录)
89
+ - `--base <ref>`:创建新分支时的基准引用(如 `main`、`origin/main`)
90
+ - `--editor <name>`:创建后使用指定编辑器打开(`trae`、`cursor`、`code`、`none`)
91
+ - `--no-editor`:创建后不打开编辑器
92
+ - `--no-install`:创建后不自动安装依赖
72
93
 
73
94
  ## CLI 命令
74
95
 
75
96
  - `wtree`:交互式主菜单 (创建/删除/列表/打开/锁定/解锁/清理)
76
97
  - `wtree list`:打印 worktree 列表
77
98
  - `wtree create [branch]`:创建 worktree(支持交互式选择)
78
- - `wtree delete`:删除 worktree(交互式选择,支持强制删除)
99
+ - `wtree delete [branch|path ...]`:删除 worktree(支持交互式选择和直接指定目标,支持强制删除)
79
100
  - `wtree open [path|branch]`:打开 worktree
80
101
  - `wtree lock [path|branch]`:锁定指定的 worktree,防止被移动或删除
81
102
  - `wtree unlock [path|branch]`:解锁指定的 worktree
@@ -85,6 +106,43 @@ wtree --ui --port 0
85
106
  - `wtree config set <key> <value>`:设置配置项
86
107
  - `wtree help`:查看帮助
87
108
 
109
+ ## 非交互模式
110
+
111
+ 所有命令都支持通过参数完全非交互地执行,适合在脚本或 AI Agent 中使用:
112
+
113
+ ```bash
114
+ # 查看 worktree 列表(JSON 输出)
115
+ wtree list --json
116
+
117
+ # 为已有分支创建 worktree
118
+ wtree create feature/my-branch --yes --no-editor --no-install --json
119
+
120
+ # 基于 main 创建新分支的 worktree
121
+ wtree create feature/new-thing --base main --yes --dir worktrees/new-thing --no-editor --no-install --json
122
+
123
+ # 删除指定 worktree
124
+ wtree delete feature/old-branch --yes --json
125
+
126
+ # 强制删除(即使有未提交更改)
127
+ wtree delete feature/dirty --yes --force --json
128
+ ```
129
+
130
+ ## Agent Skill
131
+
132
+ `wtree` 提供了 Agent Skill,可以让 AI 编码工具(Trae、Cursor、Claude Code 等)直接管理 git worktree。
133
+
134
+ ### 安装 Skill
135
+
136
+ ```bash
137
+ # 安装到当前项目
138
+ npx skills add FatDoge/wtree --skill wtree
139
+
140
+ # 全局安装(所有项目可用)
141
+ npx skills add FatDoge/wtree --skill wtree -g
142
+ ```
143
+
144
+ 安装后,AI Agent 会自动识别 `wtree` skill,在你需要管理 worktree 时自动调用。
145
+
88
146
  ## 配置
89
147
 
90
148
  UI 设置页会将配置写入本地文件:
@@ -137,3 +195,4 @@ pnpm run check
137
195
  - `api/`:CLI + 本地 API(git 执行与配置读写)
138
196
  - `src/`:UI(React + Vite + Tailwind)
139
197
  - `shared/`:前后端共享类型
198
+ - `skills/`:Agent Skill 定义
package/api/cli/wtree.ts CHANGED
@@ -13,8 +13,24 @@ import { openPath } from '../core/open.js'
13
13
  import { readConfig, writeConfig, getConfigPaths } from '../core/config.js'
14
14
  import { startUiDevServer } from '../ui/startUiDev.js'
15
15
 
16
+ type ParsedFlags = {
17
+ ui: boolean
18
+ noOpen: boolean
19
+ repo: string
20
+ port: number | undefined
21
+ json: boolean
22
+ yes: boolean
23
+ force: boolean
24
+ dir: string
25
+ base: string
26
+ editor: string | undefined
27
+ noEditor: boolean
28
+ noInstall: boolean
29
+ }
30
+
16
31
  type Ctx = {
17
32
  rootDir: string
33
+ flags: ParsedFlags
18
34
  }
19
35
 
20
36
  type SourceSelection =
@@ -33,11 +49,19 @@ function errMsg(e: unknown) {
33
49
 
34
50
  function parseArgs(argv: string[]) {
35
51
  const args = [...argv]
36
- const flags = {
52
+ const flags: ParsedFlags = {
37
53
  ui: false,
38
54
  noOpen: false,
39
55
  repo: '',
40
56
  port: undefined as number | undefined,
57
+ json: false,
58
+ yes: false,
59
+ force: false,
60
+ dir: '',
61
+ base: '',
62
+ editor: undefined,
63
+ noEditor: false,
64
+ noInstall: false,
41
65
  }
42
66
  const positional: string[] = []
43
67
 
@@ -60,6 +84,14 @@ function parseArgs(argv: string[]) {
60
84
  if (Number.isFinite(v)) flags.port = v
61
85
  continue
62
86
  }
87
+ if (a === '--json') { flags.json = true; continue }
88
+ if (a === '--yes' || a === '-y') { flags.yes = true; continue }
89
+ if (a === '--force' || a === '-f') { flags.force = true; continue }
90
+ if (a === '--dir') { flags.dir = String(args.shift() || ''); continue }
91
+ if (a === '--base') { flags.base = String(args.shift() || ''); continue }
92
+ if (a === '--editor') { flags.editor = String(args.shift() || ''); continue }
93
+ if (a === '--no-editor') { flags.noEditor = true; continue }
94
+ if (a === '--no-install') { flags.noInstall = true; continue }
63
95
  if (a.startsWith('--')) continue
64
96
  positional.push(a)
65
97
  }
@@ -99,8 +131,12 @@ function parseCommand(positional: string[]) {
99
131
  return { command: 'interactive' as CommandType, rest: positional }
100
132
  }
101
133
 
102
- function printWorktreeList(rootDir: string) {
134
+ function printWorktreeList(rootDir: string, json = false) {
103
135
  const items = listWorktrees(rootDir)
136
+ if (json) {
137
+ console.info(JSON.stringify(items, null, 2))
138
+ return
139
+ }
104
140
  if (items.length === 0) {
105
141
  console.info('未读取到 worktree。')
106
142
  return
@@ -118,7 +154,7 @@ function printHelp() {
118
154
  console.info(' wtree')
119
155
  console.info(' wtree list')
120
156
  console.info(' wtree create [branch]')
121
- console.info(' wtree delete')
157
+ console.info(' wtree delete [branch|path ...]')
122
158
  console.info(' wtree open [path|branch]')
123
159
  console.info(' wtree lock [path|branch]')
124
160
  console.info(' wtree unlock [path|branch]')
@@ -128,7 +164,23 @@ function printHelp() {
128
164
  console.info(' wtree config set <key> <value>')
129
165
  console.info(' wtree --ui [--repo <path>] [--no-open] [--port <number>]')
130
166
  console.info('')
167
+ console.info('选项:')
168
+ console.info(' --json 以 JSON 格式输出 (适合脚本/agent 使用)')
169
+ console.info(' --yes, -y 自动确认所有提示')
170
+ console.info(' --force, -f 强制操作 (如强制删除有未提交更改的 worktree)')
171
+ console.info(' --dir <path> 指定 worktree 目录路径 (相对于 git 根目录)')
172
+ console.info(' --base <ref> 创建新分支时的基准引用 (如 main, origin/main)')
173
+ console.info(' --editor <name> 创建后使用指定编辑器打开 (trae, cursor, code, none)')
174
+ console.info(' --no-editor 创建后不打开编辑器')
175
+ console.info(' --no-install 创建后不自动安装依赖')
176
+ console.info('')
131
177
  console.info('可用配置 key: baseDir, openCommand, editorCommand')
178
+ console.info('')
179
+ console.info('非交互示例:')
180
+ console.info(' wtree list --json')
181
+ console.info(' wtree create feat/x --yes --no-editor --no-install --json')
182
+ console.info(' wtree create feat/new --base main --yes --dir worktrees/feat-new --json')
183
+ console.info(' wtree delete feat/old --yes --force --json')
132
184
  }
133
185
 
134
186
  function resolveWorktree(rootDir: string, key: string) {
@@ -312,21 +364,23 @@ async function main() {
312
364
  return
313
365
  }
314
366
 
315
- console.info(chalk.blue(`检测到git repo根目录 ${rootDir},将在这里运行git命令`))
367
+ if (!flags.json) {
368
+ console.info(chalk.blue(`检测到git repo根目录 ${rootDir},将在这里运行git命令`))
369
+ }
316
370
 
317
371
  const { command, rest } = parseCommand(positional)
318
372
  if (command === 'list') {
319
- printWorktreeList(rootDir)
373
+ printWorktreeList(rootDir, flags.json)
320
374
  return
321
375
  }
322
376
 
323
377
  if (command === 'create') {
324
- await createWorktree({ rootDir }, rest[0])
378
+ await createWorktree({ rootDir, flags }, rest[0])
325
379
  return
326
380
  }
327
381
 
328
382
  if (command === 'delete') {
329
- await deleteWorktree({ rootDir })
383
+ await deleteWorktree({ rootDir, flags }, rest)
330
384
  return
331
385
  }
332
386
 
@@ -376,7 +430,7 @@ async function main() {
376
430
  const directBranch = rest[0]
377
431
 
378
432
  const action = await getUserAction(directBranch)
379
- const ctx: Ctx = { rootDir }
433
+ const ctx: Ctx = { rootDir, flags }
380
434
  if (action === 'create') {
381
435
  await createWorktree(ctx, directBranch)
382
436
  } else if (action === 'delete') {
@@ -416,7 +470,7 @@ async function getUserAction(directBranch?: string) {
416
470
  }
417
471
 
418
472
  async function createWorktree(ctx: Ctx, directBranch?: string) {
419
- const { rootDir } = ctx
473
+ const { rootDir, flags } = ctx
420
474
  const defaultBranch =
421
475
  git(rootDir, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']).stdout
422
476
  .replace(/^origin\//, '')
@@ -429,13 +483,16 @@ async function createWorktree(ctx: Ctx, directBranch?: string) {
429
483
  selection,
430
484
  directBranch,
431
485
  defaultBranch,
486
+ flags,
432
487
  )
433
- const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch)
488
+ const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch, flags)
434
489
 
435
- console.info(chalk.green(`\n准备创建 Worktree:`))
436
- console.info(` 分支: ${targetBranch}`)
437
- console.info(` 目录: ${targetDir}`)
438
- console.info(` 来源: ${baseRef || 'Existing Local'}`)
490
+ if (!flags.json) {
491
+ console.info(chalk.green(`\n准备创建 Worktree:`))
492
+ console.info(` 分支: ${targetBranch}`)
493
+ console.info(` 目录: ${targetDir}`)
494
+ console.info(` 来源: ${baseRef || 'Existing Local'}`)
495
+ }
439
496
 
440
497
  await createGitWorktree(
441
498
  rootDir,
@@ -448,8 +505,14 @@ async function createWorktree(ctx: Ctx, directBranch?: string) {
448
505
  )
449
506
 
450
507
  await setupWorktreeEnv(rootDir, targetDir, dirName)
451
- await installDependencies(targetDir)
452
- await openInIDE(targetDir)
508
+ await installDependencies(targetDir, flags.noInstall)
509
+ await openInIDE(targetDir, flags)
510
+
511
+ if (flags.json) {
512
+ const items = listWorktrees(rootDir)
513
+ const created = items.find(x => path.resolve(x.path) === path.resolve(targetDir))
514
+ console.info(JSON.stringify({ ok: true, data: created || null }))
515
+ }
453
516
  }
454
517
 
455
518
  async function selectSource(rootDir: string, directBranch: string | undefined, defaultBranch: string) {
@@ -503,6 +566,7 @@ async function resolveBranchInfo(
503
566
  selection: SourceSelection,
504
567
  directBranch: string | undefined,
505
568
  defaultBranch: string,
569
+ flags: ParsedFlags,
506
570
  ) {
507
571
  let targetBranch = ''
508
572
  let baseRef = ''
@@ -541,19 +605,24 @@ async function resolveBranchInfo(
541
605
  baseRef = `origin/${targetBranch}`
542
606
  isNewBranch = true
543
607
  } else {
544
- const { createNew } = await inquirer.prompt([
545
- {
546
- type: 'confirm',
547
- name: 'createNew',
548
- message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
549
- default: true,
550
- },
551
- ])
552
- if (createNew) {
553
- baseRef = defaultBranch
608
+ if (flags.yes) {
609
+ baseRef = flags.base || defaultBranch
554
610
  isNewBranch = true
555
611
  } else {
556
- process.exit(1)
612
+ const { createNew } = await inquirer.prompt([
613
+ {
614
+ type: 'confirm',
615
+ name: 'createNew',
616
+ message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
617
+ default: true,
618
+ },
619
+ ])
620
+ if (createNew) {
621
+ baseRef = defaultBranch
622
+ isNewBranch = true
623
+ } else {
624
+ process.exit(1)
625
+ }
557
626
  }
558
627
  }
559
628
  }
@@ -612,16 +681,25 @@ async function resolveBranchInfo(
612
681
  return { targetBranch, baseRef, isNewBranch }
613
682
  }
614
683
 
615
- async function selectTargetDir(rootDir: string, targetBranch: string) {
684
+ async function selectTargetDir(rootDir: string, targetBranch: string, flags: ParsedFlags) {
616
685
  const defaultDirName = `worktrees/${targetBranch.split('/').join('-')}`
617
- const { dirName } = await inquirer.prompt([
618
- {
619
- type: 'input',
620
- name: 'dirName',
621
- message: `请输入 Worktree 目录路径 (相对于 Git 根目录, 默认: ${defaultDirName}):`,
622
- default: defaultDirName,
623
- },
624
- ])
686
+
687
+ let dirName: string
688
+ if (flags.dir) {
689
+ dirName = flags.dir
690
+ } else if (flags.yes) {
691
+ dirName = defaultDirName
692
+ } else {
693
+ const result = await inquirer.prompt([
694
+ {
695
+ type: 'input',
696
+ name: 'dirName',
697
+ message: `请输入 Worktree 目录路径 (相对于 Git 根目录, 默认: ${defaultDirName}):`,
698
+ default: defaultDirName,
699
+ },
700
+ ])
701
+ dirName = result.dirName
702
+ }
625
703
 
626
704
  const targetDir = path.resolve(rootDir, dirName)
627
705
  if (fs.existsSync(targetDir)) {
@@ -682,7 +760,8 @@ async function setupWorktreeEnv(rootDir: string, targetDir: string, dirName: str
682
760
  }
683
761
  }
684
762
 
685
- async function installDependencies(targetDir: string) {
763
+ async function installDependencies(targetDir: string, skip = false) {
764
+ if (skip) return
686
765
  if (!fs.existsSync(path.join(targetDir, 'package.json'))) return
687
766
  try {
688
767
  execSync('pnpm --version', { stdio: 'ignore' })
@@ -702,7 +781,18 @@ function hasCommand(cmd: string) {
702
781
  }
703
782
  }
704
783
 
705
- async function openInIDE(targetDir: string) {
784
+ async function openInIDE(targetDir: string, flags: ParsedFlags) {
785
+ if (flags.noEditor) return
786
+ if (flags.editor !== undefined) {
787
+ if (flags.editor === 'none' || flags.editor === '') return
788
+ try {
789
+ execSync(`${flags.editor} "${targetDir}"`, { stdio: 'ignore' })
790
+ } catch (e: unknown) {
791
+ void e
792
+ }
793
+ return
794
+ }
795
+
706
796
  const editors: { name: string; value: string }[] = []
707
797
  if (hasCommand('trae')) editors.push({ name: `在 Trae 中打开 (trae ${targetDir})`, value: 'trae' })
708
798
  if (hasCommand('cursor')) editors.push({ name: `在 Cursor 中打开 (cursor ${targetDir})`, value: 'cursor' })
@@ -729,35 +819,64 @@ async function openInIDE(targetDir: string) {
729
819
  }
730
820
  }
731
821
 
732
- async function deleteWorktree(ctx: Ctx) {
733
- const worktrees = getWorktreeList(ctx.rootDir)
734
- const choices = getDeletableWorktrees(ctx.rootDir, worktrees)
822
+ async function deleteWorktree(ctx: Ctx, targets: string[] = []) {
823
+ const { rootDir, flags } = ctx
824
+ const worktrees = getWorktreeList(rootDir)
825
+ const choices = getDeletableWorktrees(rootDir, worktrees)
735
826
  if (choices.length === 0) {
736
- console.warn(chalk.yellow('没有可删除的 Worktree (除了主 Worktree)'))
827
+ if (flags.json) {
828
+ console.info(JSON.stringify({ ok: true, data: [], message: 'No deletable worktrees' }))
829
+ } else {
830
+ console.warn(chalk.yellow('没有可删除的 Worktree (除了主 Worktree)'))
831
+ }
737
832
  return
738
833
  }
739
834
 
740
- const { targetPaths } = await inquirer.prompt([
741
- {
742
- type: 'checkbox',
743
- name: 'targetPaths',
744
- message: '请选择要删除的 Worktree:',
745
- choices,
746
- validate: (answer: string[]) => (answer.length > 0 ? true : '请至少选择一个'),
747
- },
748
- ])
835
+ let targetPaths: string[]
836
+
837
+ if (targets.length > 0) {
838
+ // Non-interactive: resolve each target to a worktree path
839
+ targetPaths = []
840
+ for (const key of targets) {
841
+ const wt = resolveWorktree(rootDir, key)
842
+ if (!wt) {
843
+ console.error(chalk.red(`未找到 worktree: ${key}`))
844
+ process.exit(1)
845
+ }
846
+ if (path.resolve(wt.path) === path.resolve(rootDir)) {
847
+ console.error(chalk.red(`不能删除主 worktree: ${key}`))
848
+ process.exit(1)
849
+ }
850
+ targetPaths.push(wt.path)
851
+ }
852
+ } else {
853
+ // Interactive: checkbox prompt
854
+ const result = await inquirer.prompt([
855
+ {
856
+ type: 'checkbox',
857
+ name: 'targetPaths',
858
+ message: '请选择要删除的 Worktree:',
859
+ choices,
860
+ validate: (answer: string[]) => (answer.length > 0 ? true : '请至少选择一个'),
861
+ },
862
+ ])
863
+ targetPaths = result.targetPaths
864
+ }
865
+
866
+ if (!flags.yes) {
867
+ const { confirmDelete } = await inquirer.prompt([
868
+ {
869
+ type: 'confirm',
870
+ name: 'confirmDelete',
871
+ message: `确定要删除这 ${targetPaths.length} 个 Worktree 吗?`,
872
+ default: false,
873
+ },
874
+ ])
875
+ if (!confirmDelete) return
876
+ }
749
877
 
750
- const { confirmDelete } = await inquirer.prompt([
751
- {
752
- type: 'confirm',
753
- name: 'confirmDelete',
754
- message: `确定要删除这 ${targetPaths.length} 个 Worktree 吗?`,
755
- default: false,
756
- },
757
- ])
758
- if (!confirmDelete) return
759
878
  for (const targetPath of targetPaths) {
760
- await deleteSingleWorktree(ctx.rootDir, targetPath)
879
+ await deleteSingleWorktree(rootDir, targetPath, flags)
761
880
  }
762
881
  }
763
882
 
@@ -779,26 +898,47 @@ function getDeletableWorktrees(rootDir: string, worktrees: { path: string; branc
779
898
  })
780
899
  }
781
900
 
782
- async function deleteSingleWorktree(rootDir: string, targetPath: string) {
901
+ async function deleteSingleWorktree(rootDir: string, targetPath: string, flags: ParsedFlags) {
783
902
  try {
784
903
  gitOrThrow(rootDir, ['worktree', 'remove', targetPath], 'WORKTREE_REMOVE')
785
- console.info(chalk.green(`成功删除: ${targetPath}`))
904
+ if (flags.json) {
905
+ console.info(JSON.stringify({ ok: true, removed: targetPath }))
906
+ } else {
907
+ console.info(chalk.green(`成功删除: ${targetPath}`))
908
+ }
786
909
  } catch (e: unknown) {
787
- console.error(chalk.red(`删除失败: ${errMsg(e)}`))
788
- const { force } = await inquirer.prompt([
789
- {
790
- type: 'confirm',
791
- name: 'force',
792
- message: '删除失败 (可能有未提交的更改). 强制删除吗?',
793
- default: false,
794
- },
795
- ])
796
- if (!force) return
797
- try {
798
- gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE')
799
- console.info(chalk.green(`成功强制删除: ${targetPath}`))
800
- } catch (forceErr: unknown) {
801
- console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`))
910
+ if (flags.force) {
911
+ try {
912
+ gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE')
913
+ if (flags.json) {
914
+ console.info(JSON.stringify({ ok: true, removed: targetPath, forced: true }))
915
+ } else {
916
+ console.info(chalk.green(`成功强制删除: ${targetPath}`))
917
+ }
918
+ } catch (forceErr: unknown) {
919
+ if (flags.json) {
920
+ console.error(JSON.stringify({ ok: false, error: errMsg(forceErr), path: targetPath }))
921
+ } else {
922
+ console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`))
923
+ }
924
+ }
925
+ } else {
926
+ console.error(chalk.red(`删除失败: ${errMsg(e)}`))
927
+ const { force } = await inquirer.prompt([
928
+ {
929
+ type: 'confirm',
930
+ name: 'force',
931
+ message: '删除失败 (可能有未提交的更改). 强制删除吗?',
932
+ default: false,
933
+ },
934
+ ])
935
+ if (!force) return
936
+ try {
937
+ gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE')
938
+ console.info(chalk.green(`成功强制删除: ${targetPath}`))
939
+ } catch (forceErr: unknown) {
940
+ console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`))
941
+ }
802
942
  }
803
943
  }
804
944
  }