colyn-cli 3.1.4

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 (263) hide show
  1. package/README-en.md +85 -0
  2. package/README.md +85 -0
  3. package/bin/colyn +25 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +116 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/add.d.ts +6 -0
  9. package/dist/commands/add.d.ts.map +1 -0
  10. package/dist/commands/add.helpers.d.ts +48 -0
  11. package/dist/commands/add.helpers.d.ts.map +1 -0
  12. package/dist/commands/add.helpers.js +251 -0
  13. package/dist/commands/add.helpers.js.map +1 -0
  14. package/dist/commands/add.js +286 -0
  15. package/dist/commands/add.js.map +1 -0
  16. package/dist/commands/checkout.d.ts +16 -0
  17. package/dist/commands/checkout.d.ts.map +1 -0
  18. package/dist/commands/checkout.js +428 -0
  19. package/dist/commands/checkout.js.map +1 -0
  20. package/dist/commands/completion.d.ts +10 -0
  21. package/dist/commands/completion.d.ts.map +1 -0
  22. package/dist/commands/completion.js +380 -0
  23. package/dist/commands/completion.js.map +1 -0
  24. package/dist/commands/config.d.ts +11 -0
  25. package/dist/commands/config.d.ts.map +1 -0
  26. package/dist/commands/config.js +338 -0
  27. package/dist/commands/config.js.map +1 -0
  28. package/dist/commands/index.d.ts +10 -0
  29. package/dist/commands/index.d.ts.map +1 -0
  30. package/dist/commands/index.js +41 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/info.d.ts +6 -0
  33. package/dist/commands/info.d.ts.map +1 -0
  34. package/dist/commands/info.js +374 -0
  35. package/dist/commands/info.js.map +1 -0
  36. package/dist/commands/init.d.ts +6 -0
  37. package/dist/commands/init.d.ts.map +1 -0
  38. package/dist/commands/init.handlers.d.ts +27 -0
  39. package/dist/commands/init.handlers.d.ts.map +1 -0
  40. package/dist/commands/init.handlers.js +314 -0
  41. package/dist/commands/init.handlers.js.map +1 -0
  42. package/dist/commands/init.helpers.d.ts +42 -0
  43. package/dist/commands/init.helpers.d.ts.map +1 -0
  44. package/dist/commands/init.helpers.js +275 -0
  45. package/dist/commands/init.helpers.js.map +1 -0
  46. package/dist/commands/init.js +61 -0
  47. package/dist/commands/init.js.map +1 -0
  48. package/dist/commands/install.d.ts +6 -0
  49. package/dist/commands/install.d.ts.map +1 -0
  50. package/dist/commands/install.helpers.d.ts +32 -0
  51. package/dist/commands/install.helpers.d.ts.map +1 -0
  52. package/dist/commands/install.helpers.js +124 -0
  53. package/dist/commands/install.helpers.js.map +1 -0
  54. package/dist/commands/install.js +104 -0
  55. package/dist/commands/install.js.map +1 -0
  56. package/dist/commands/list-project.d.ts +11 -0
  57. package/dist/commands/list-project.d.ts.map +1 -0
  58. package/dist/commands/list-project.js +260 -0
  59. package/dist/commands/list-project.js.map +1 -0
  60. package/dist/commands/list.d.ts +15 -0
  61. package/dist/commands/list.d.ts.map +1 -0
  62. package/dist/commands/list.helpers.d.ts +50 -0
  63. package/dist/commands/list.helpers.d.ts.map +1 -0
  64. package/dist/commands/list.helpers.js +143 -0
  65. package/dist/commands/list.helpers.js.map +1 -0
  66. package/dist/commands/list.js +530 -0
  67. package/dist/commands/list.js.map +1 -0
  68. package/dist/commands/merge.d.ts +6 -0
  69. package/dist/commands/merge.d.ts.map +1 -0
  70. package/dist/commands/merge.helpers.d.ts +74 -0
  71. package/dist/commands/merge.helpers.d.ts.map +1 -0
  72. package/dist/commands/merge.helpers.js +307 -0
  73. package/dist/commands/merge.helpers.js.map +1 -0
  74. package/dist/commands/merge.js +260 -0
  75. package/dist/commands/merge.js.map +1 -0
  76. package/dist/commands/release.d.ts +6 -0
  77. package/dist/commands/release.d.ts.map +1 -0
  78. package/dist/commands/release.helpers.d.ts +61 -0
  79. package/dist/commands/release.helpers.d.ts.map +1 -0
  80. package/dist/commands/release.helpers.js +277 -0
  81. package/dist/commands/release.helpers.js.map +1 -0
  82. package/dist/commands/release.js +127 -0
  83. package/dist/commands/release.js.map +1 -0
  84. package/dist/commands/remove.d.ts +6 -0
  85. package/dist/commands/remove.d.ts.map +1 -0
  86. package/dist/commands/remove.helpers.d.ts +59 -0
  87. package/dist/commands/remove.helpers.d.ts.map +1 -0
  88. package/dist/commands/remove.helpers.js +190 -0
  89. package/dist/commands/remove.helpers.js.map +1 -0
  90. package/dist/commands/remove.js +137 -0
  91. package/dist/commands/remove.js.map +1 -0
  92. package/dist/commands/repair.d.ts +6 -0
  93. package/dist/commands/repair.d.ts.map +1 -0
  94. package/dist/commands/repair.helpers.d.ts +5 -0
  95. package/dist/commands/repair.helpers.d.ts.map +1 -0
  96. package/dist/commands/repair.helpers.js +499 -0
  97. package/dist/commands/repair.helpers.js.map +1 -0
  98. package/dist/commands/repair.js +28 -0
  99. package/dist/commands/repair.js.map +1 -0
  100. package/dist/commands/status.d.ts +6 -0
  101. package/dist/commands/status.d.ts.map +1 -0
  102. package/dist/commands/status.js +116 -0
  103. package/dist/commands/status.js.map +1 -0
  104. package/dist/commands/system-integration.d.ts +6 -0
  105. package/dist/commands/system-integration.d.ts.map +1 -0
  106. package/dist/commands/system-integration.helpers.d.ts +70 -0
  107. package/dist/commands/system-integration.helpers.d.ts.map +1 -0
  108. package/dist/commands/system-integration.helpers.js +313 -0
  109. package/dist/commands/system-integration.helpers.js.map +1 -0
  110. package/dist/commands/system-integration.js +132 -0
  111. package/dist/commands/system-integration.js.map +1 -0
  112. package/dist/commands/tmux.d.ts +11 -0
  113. package/dist/commands/tmux.d.ts.map +1 -0
  114. package/dist/commands/tmux.js +500 -0
  115. package/dist/commands/tmux.js.map +1 -0
  116. package/dist/commands/todo.d.ts +6 -0
  117. package/dist/commands/todo.d.ts.map +1 -0
  118. package/dist/commands/todo.helpers.d.ts +50 -0
  119. package/dist/commands/todo.helpers.d.ts.map +1 -0
  120. package/dist/commands/todo.helpers.js +297 -0
  121. package/dist/commands/todo.helpers.js.map +1 -0
  122. package/dist/commands/todo.js +579 -0
  123. package/dist/commands/todo.js.map +1 -0
  124. package/dist/commands/update.d.ts +18 -0
  125. package/dist/commands/update.d.ts.map +1 -0
  126. package/dist/commands/update.helpers.d.ts +89 -0
  127. package/dist/commands/update.helpers.d.ts.map +1 -0
  128. package/dist/commands/update.helpers.js +335 -0
  129. package/dist/commands/update.helpers.js.map +1 -0
  130. package/dist/commands/update.js +187 -0
  131. package/dist/commands/update.js.map +1 -0
  132. package/dist/core/config-loader.d.ts +47 -0
  133. package/dist/core/config-loader.d.ts.map +1 -0
  134. package/dist/core/config-loader.js +188 -0
  135. package/dist/core/config-loader.js.map +1 -0
  136. package/dist/core/config-merger.d.ts +29 -0
  137. package/dist/core/config-merger.d.ts.map +1 -0
  138. package/dist/core/config-merger.js +100 -0
  139. package/dist/core/config-merger.js.map +1 -0
  140. package/dist/core/config-migration.d.ts +64 -0
  141. package/dist/core/config-migration.d.ts.map +1 -0
  142. package/dist/core/config-migration.js +340 -0
  143. package/dist/core/config-migration.js.map +1 -0
  144. package/dist/core/config-new.d.ts +19 -0
  145. package/dist/core/config-new.d.ts.map +1 -0
  146. package/dist/core/config-new.js +58 -0
  147. package/dist/core/config-new.js.map +1 -0
  148. package/dist/core/config-schema.d.ts +221 -0
  149. package/dist/core/config-schema.d.ts.map +1 -0
  150. package/dist/core/config-schema.js +168 -0
  151. package/dist/core/config-schema.js.map +1 -0
  152. package/dist/core/config.d.ts +55 -0
  153. package/dist/core/config.d.ts.map +1 -0
  154. package/dist/core/config.js +143 -0
  155. package/dist/core/config.js.map +1 -0
  156. package/dist/core/dev-server.d.ts +29 -0
  157. package/dist/core/dev-server.d.ts.map +1 -0
  158. package/dist/core/dev-server.js +54 -0
  159. package/dist/core/dev-server.js.map +1 -0
  160. package/dist/core/discovery.d.ts +51 -0
  161. package/dist/core/discovery.d.ts.map +1 -0
  162. package/dist/core/discovery.js +247 -0
  163. package/dist/core/discovery.js.map +1 -0
  164. package/dist/core/env.d.ts +13 -0
  165. package/dist/core/env.d.ts.map +1 -0
  166. package/dist/core/env.js +75 -0
  167. package/dist/core/env.js.map +1 -0
  168. package/dist/core/git.d.ts +31 -0
  169. package/dist/core/git.d.ts.map +1 -0
  170. package/dist/core/git.js +56 -0
  171. package/dist/core/git.js.map +1 -0
  172. package/dist/core/paths.d.ts +86 -0
  173. package/dist/core/paths.d.ts.map +1 -0
  174. package/dist/core/paths.js +256 -0
  175. package/dist/core/paths.js.map +1 -0
  176. package/dist/core/tmux-config.d.ts +174 -0
  177. package/dist/core/tmux-config.d.ts.map +1 -0
  178. package/dist/core/tmux-config.js +545 -0
  179. package/dist/core/tmux-config.js.map +1 -0
  180. package/dist/core/tmux-config.legacy.d.ts +243 -0
  181. package/dist/core/tmux-config.legacy.d.ts.map +1 -0
  182. package/dist/core/tmux-config.legacy.js +896 -0
  183. package/dist/core/tmux-config.legacy.js.map +1 -0
  184. package/dist/core/tmux.d.ts +214 -0
  185. package/dist/core/tmux.d.ts.map +1 -0
  186. package/dist/core/tmux.js +712 -0
  187. package/dist/core/tmux.js.map +1 -0
  188. package/dist/core/toolchain-resolver.d.ts +51 -0
  189. package/dist/core/toolchain-resolver.d.ts.map +1 -0
  190. package/dist/core/toolchain-resolver.js +364 -0
  191. package/dist/core/toolchain-resolver.js.map +1 -0
  192. package/dist/core/worktree-status.d.ts +20 -0
  193. package/dist/core/worktree-status.d.ts.map +1 -0
  194. package/dist/core/worktree-status.js +67 -0
  195. package/dist/core/worktree-status.js.map +1 -0
  196. package/dist/i18n/index.d.ts +36 -0
  197. package/dist/i18n/index.d.ts.map +1 -0
  198. package/dist/i18n/index.js +157 -0
  199. package/dist/i18n/index.js.map +1 -0
  200. package/dist/i18n/locales/en.d.ts +866 -0
  201. package/dist/i18n/locales/en.d.ts.map +1 -0
  202. package/dist/i18n/locales/en.js +985 -0
  203. package/dist/i18n/locales/en.js.map +1 -0
  204. package/dist/i18n/locales/zh-CN.d.ts +865 -0
  205. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  206. package/dist/i18n/locales/zh-CN.js +985 -0
  207. package/dist/i18n/locales/zh-CN.js.map +1 -0
  208. package/dist/index.d.ts +2 -0
  209. package/dist/index.d.ts.map +1 -0
  210. package/dist/index.js +4 -0
  211. package/dist/index.js.map +1 -0
  212. package/dist/plugins/builtin/gradle.d.ts +9 -0
  213. package/dist/plugins/builtin/gradle.d.ts.map +1 -0
  214. package/dist/plugins/builtin/gradle.js +164 -0
  215. package/dist/plugins/builtin/gradle.js.map +1 -0
  216. package/dist/plugins/builtin/maven.d.ts +9 -0
  217. package/dist/plugins/builtin/maven.d.ts.map +1 -0
  218. package/dist/plugins/builtin/maven.js +127 -0
  219. package/dist/plugins/builtin/maven.js.map +1 -0
  220. package/dist/plugins/builtin/npm.d.ts +9 -0
  221. package/dist/plugins/builtin/npm.d.ts.map +1 -0
  222. package/dist/plugins/builtin/npm.js +238 -0
  223. package/dist/plugins/builtin/npm.js.map +1 -0
  224. package/dist/plugins/builtin/pip.d.ts +9 -0
  225. package/dist/plugins/builtin/pip.d.ts.map +1 -0
  226. package/dist/plugins/builtin/pip.js +210 -0
  227. package/dist/plugins/builtin/pip.js.map +1 -0
  228. package/dist/plugins/builtin/xcode.d.ts +12 -0
  229. package/dist/plugins/builtin/xcode.d.ts.map +1 -0
  230. package/dist/plugins/builtin/xcode.js +438 -0
  231. package/dist/plugins/builtin/xcode.js.map +1 -0
  232. package/dist/plugins/index.d.ts +13 -0
  233. package/dist/plugins/index.d.ts.map +1 -0
  234. package/dist/plugins/index.js +24 -0
  235. package/dist/plugins/index.js.map +1 -0
  236. package/dist/plugins/manager.d.ts +93 -0
  237. package/dist/plugins/manager.d.ts.map +1 -0
  238. package/dist/plugins/manager.js +270 -0
  239. package/dist/plugins/manager.js.map +1 -0
  240. package/dist/plugins/utils.d.ts +42 -0
  241. package/dist/plugins/utils.d.ts.map +1 -0
  242. package/dist/plugins/utils.js +175 -0
  243. package/dist/plugins/utils.js.map +1 -0
  244. package/dist/types/index.d.ts +104 -0
  245. package/dist/types/index.d.ts.map +1 -0
  246. package/dist/types/index.js +21 -0
  247. package/dist/types/index.js.map +1 -0
  248. package/dist/types/plugin.d.ts +200 -0
  249. package/dist/types/plugin.d.ts.map +1 -0
  250. package/dist/types/plugin.js +19 -0
  251. package/dist/types/plugin.js.map +1 -0
  252. package/dist/utils/logger.d.ts +42 -0
  253. package/dist/utils/logger.d.ts.map +1 -0
  254. package/dist/utils/logger.js +80 -0
  255. package/dist/utils/logger.js.map +1 -0
  256. package/docs/en/manual/04-command-reference/README.md +58 -0
  257. package/docs/en/manual/README.md +108 -0
  258. package/docs/zh-CN/manual/04-command-reference/README.md +58 -0
  259. package/docs/zh-CN/manual/README.md +108 -0
  260. package/package.json +65 -0
  261. package/shell/colyn.sh +55 -0
  262. package/shell/completion.bash +270 -0
  263. package/shell/completion.zsh +167 -0
@@ -0,0 +1,712 @@
1
+ /**
2
+ * tmux 集成工具模块
3
+ *
4
+ * 提供 tmux 环境检测、session/window/pane 管理功能
5
+ */
6
+ import { execSync } from 'child_process';
7
+ /**
8
+ * 执行 tmux 命令
9
+ * @param command tmux 子命令和参数
10
+ * @param options 选项
11
+ * @returns 命令输出(如果成功)
12
+ */
13
+ function execTmux(command, options = {}) {
14
+ try {
15
+ // 创建一个不包含 COLYN_USER_CWD 的环境变量对象
16
+ // 防止这个内部环境变量泄漏到 tmux session 中
17
+ const cleanEnv = { ...process.env };
18
+ delete cleanEnv.COLYN_USER_CWD;
19
+ const output = execSync(`tmux ${command}`, {
20
+ encoding: 'utf-8',
21
+ stdio: options.silent ? 'pipe' : ['pipe', 'pipe', 'pipe'],
22
+ env: cleanEnv,
23
+ });
24
+ return output.trim();
25
+ }
26
+ catch (error) {
27
+ if (options.ignoreError) {
28
+ return '';
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ /**
34
+ * 检测 tmux 是否已安装
35
+ */
36
+ export function isTmuxAvailable() {
37
+ try {
38
+ execSync('which tmux', { stdio: 'ignore' });
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ /**
46
+ * 检测是否在 tmux 环境中
47
+ */
48
+ export function isInTmux() {
49
+ return !!process.env.TMUX;
50
+ }
51
+ /**
52
+ * 获取当前 tmux session 名称
53
+ * @returns session 名称,如果不在 tmux 中返回 null
54
+ */
55
+ export function getCurrentSession() {
56
+ if (!isInTmux()) {
57
+ return null;
58
+ }
59
+ try {
60
+ return execTmux('display-message -p "#{session_name}"', { silent: true });
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * 获取当前 window 索引
68
+ * @returns window 索引,如果不在 tmux 中返回 null
69
+ */
70
+ export function getCurrentWindowIndex() {
71
+ if (!isInTmux()) {
72
+ return null;
73
+ }
74
+ try {
75
+ const index = execTmux('display-message -p "#{window_index}"', {
76
+ silent: true,
77
+ });
78
+ return parseInt(index, 10);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ /**
85
+ * 获取 tmux 环境完整信息
86
+ */
87
+ export function getTmuxEnvironment() {
88
+ const available = isTmuxAvailable();
89
+ const inTmux = isInTmux();
90
+ return {
91
+ available,
92
+ inTmux,
93
+ currentSession: inTmux ? getCurrentSession() ?? undefined : undefined,
94
+ currentWindowIndex: inTmux ? getCurrentWindowIndex() ?? undefined : undefined,
95
+ };
96
+ }
97
+ /**
98
+ * 检查 session 是否存在
99
+ * @param sessionName session 名称
100
+ */
101
+ export function sessionExists(sessionName) {
102
+ try {
103
+ execTmux(`has-session -t "${sessionName}"`, { silent: true });
104
+ return true;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * 创建新的 tmux session(detached 模式)
112
+ * @param sessionName session 名称
113
+ * @param workingDir 工作目录
114
+ * @returns 是否成功
115
+ */
116
+ export function createSession(sessionName, workingDir) {
117
+ try {
118
+ // 如果 session 已存在,直接返回成功
119
+ if (sessionExists(sessionName)) {
120
+ return true;
121
+ }
122
+ // 创建 detached session
123
+ execTmux(`new-session -d -s "${sessionName}" -c "${workingDir}"`, {
124
+ silent: true,
125
+ });
126
+ return true;
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ }
132
+ /**
133
+ * 检查 window 是否存在
134
+ * @param sessionName session 名称
135
+ * @param windowIndex window 索引
136
+ */
137
+ export function windowExists(sessionName, windowIndex) {
138
+ try {
139
+ execTmux(`select-window -t "${sessionName}:${windowIndex}"`, {
140
+ silent: true,
141
+ });
142
+ return true;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ /**
149
+ * 从分支名提取 window 名称
150
+ * 使用分支名的最后一段
151
+ * @param branch 分支名
152
+ * @returns window 名称
153
+ */
154
+ export function getWindowName(branch) {
155
+ return branch.split('/').pop() || branch;
156
+ }
157
+ /**
158
+ * 创建新的 tmux window
159
+ * @param sessionName session 名称
160
+ * @param windowIndex window 索引
161
+ * @param windowName window 名称
162
+ * @param workingDir 工作目录
163
+ * @returns 是否成功
164
+ */
165
+ export function createWindow(sessionName, windowIndex, windowName, workingDir) {
166
+ try {
167
+ // 检查是否是创建 window 0(需要重命名而不是新建)
168
+ if (windowIndex === 0) {
169
+ // session 创建时会自动创建 window 0,只需要重命名
170
+ execTmux(`rename-window -t "${sessionName}:0" "${windowName}"`, { silent: true });
171
+ return true;
172
+ }
173
+ // 创建新 window,使用 -t 指定目标 session 和索引
174
+ execTmux(`new-window -t "${sessionName}:${windowIndex}" -n "${windowName}" -c "${workingDir}"`, { silent: true });
175
+ return true;
176
+ }
177
+ catch {
178
+ return false;
179
+ }
180
+ }
181
+ /**
182
+ * 设置 3-pane 布局
183
+ * 布局: 左侧 | 右上 | 右下
184
+ *
185
+ * @param sessionName session 名称
186
+ * @param windowIndex window 索引
187
+ * @param workingDir 工作目录
188
+ * @param layout 可选的布局配置
189
+ * @returns 是否成功
190
+ */
191
+ export function setupPaneLayout(sessionName, windowIndex, workingDir, layout) {
192
+ const target = `${sessionName}:${windowIndex}`;
193
+ // 如果没有布局配置,默认使用三窗格
194
+ const layoutType = layout?.layout ?? 'three-pane';
195
+ try {
196
+ switch (layoutType) {
197
+ case 'single-pane':
198
+ // 单窗格:不需要分割
199
+ return true;
200
+ case 'two-pane-horizontal':
201
+ return setupTwoPaneHorizontal(target, workingDir, layout);
202
+ case 'two-pane-vertical':
203
+ return setupTwoPaneVertical(target, workingDir, layout);
204
+ case 'three-pane':
205
+ return setupThreePane(target, workingDir, layout);
206
+ case 'four-pane':
207
+ return setupFourPane(target, workingDir, layout);
208
+ default:
209
+ // 未知布局类型,默认使用三窗格
210
+ return setupThreePane(target, workingDir, layout);
211
+ }
212
+ }
213
+ catch {
214
+ return false;
215
+ }
216
+ }
217
+ /**
218
+ * 设置两窗格水平布局
219
+ */
220
+ function setupTwoPaneHorizontal(target, workingDir, layout) {
221
+ const leftSize = layout?.leftSize ?? 50;
222
+ const rightSize = 100 - leftSize;
223
+ // 垂直分割:左右
224
+ execTmux(`split-window -t "${target}" -h -p ${rightSize} -c "${workingDir}"`, { silent: true });
225
+ // 选择左侧 pane
226
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
227
+ return true;
228
+ }
229
+ /**
230
+ * 设置两窗格垂直布局
231
+ */
232
+ function setupTwoPaneVertical(target, workingDir, layout) {
233
+ const topSize = layout?.topSize ?? 50;
234
+ const bottomSize = 100 - topSize;
235
+ // 水平分割:上下
236
+ execTmux(`split-window -t "${target}" -v -p ${bottomSize} -c "${workingDir}"`, { silent: true });
237
+ // 选择上方 pane
238
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
239
+ return true;
240
+ }
241
+ /**
242
+ * 设置三窗格布局
243
+ */
244
+ function setupThreePane(target, workingDir, layout) {
245
+ const leftSize = layout?.leftSize ?? 60;
246
+ const rightTopSize = layout?.rightTopSize ?? 30;
247
+ const rightSize = 100 - leftSize;
248
+ const rightBottomSize = 100 - rightTopSize;
249
+ // 1. 垂直分割:左侧 leftSize%,右侧 rightSize%
250
+ execTmux(`split-window -t "${target}" -h -p ${rightSize} -c "${workingDir}"`, { silent: true });
251
+ // 2. 分割右侧为上下:上 rightTopSize%,下 rightBottomSize%
252
+ execTmux(`split-window -t "${target}" -v -p ${rightBottomSize} -c "${workingDir}"`, { silent: true });
253
+ // 3. 选择左侧 pane (pane 0)
254
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
255
+ return true;
256
+ }
257
+ /**
258
+ * 设置四窗格布局
259
+ */
260
+ function setupFourPane(target, workingDir, layout) {
261
+ const hasHorizontalSplit = layout?.horizontalSplit !== undefined;
262
+ const hasVerticalSplit = layout?.verticalSplit !== undefined;
263
+ if (hasHorizontalSplit && hasVerticalSplit) {
264
+ // 同时配置两个 split:使用固定分割
265
+ return setupFourPaneBothSplits(target, workingDir, layout);
266
+ }
267
+ else if (hasHorizontalSplit) {
268
+ // 仅水平分割
269
+ return setupFourPaneHorizontalSplit(target, workingDir, layout);
270
+ }
271
+ else if (hasVerticalSplit) {
272
+ // 仅垂直分割
273
+ return setupFourPaneVerticalSplit(target, workingDir, layout);
274
+ }
275
+ else {
276
+ // 默认:50/50 分割
277
+ return setupFourPaneDefault(target, workingDir);
278
+ }
279
+ }
280
+ /**
281
+ * 四窗格:同时配置 horizontalSplit 和 verticalSplit
282
+ */
283
+ function setupFourPaneBothSplits(target, workingDir, layout) {
284
+ const verticalSplit = layout?.verticalSplit ?? 50;
285
+ const horizontalSplit = layout?.horizontalSplit ?? 50;
286
+ const rightWidth = 100 - verticalSplit;
287
+ const bottomHeight = 100 - horizontalSplit;
288
+ // 1. 垂直分割:左右
289
+ execTmux(`split-window -t "${target}" -h -p ${rightWidth} -c "${workingDir}"`, { silent: true });
290
+ // 2. 分割左侧:上下
291
+ execTmux(`split-window -t "${target}.0" -v -p ${bottomHeight} -c "${workingDir}"`, { silent: true });
292
+ // 3. 分割右侧:上下
293
+ execTmux(`split-window -t "${target}.1" -v -p ${bottomHeight} -c "${workingDir}"`, { silent: true });
294
+ // 4. 选择左上 pane
295
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
296
+ return true;
297
+ }
298
+ /**
299
+ * 四窗格:仅 horizontalSplit(先上下分割)
300
+ */
301
+ function setupFourPaneHorizontalSplit(target, workingDir, layout) {
302
+ const horizontalSplit = layout?.horizontalSplit ?? 50;
303
+ const topLeftSize = layout?.topLeftSize ?? 50;
304
+ const bottomLeftSize = layout?.bottomLeftSize ?? 50;
305
+ const bottomHeight = 100 - horizontalSplit;
306
+ const topRightWidth = 100 - topLeftSize;
307
+ const bottomRightWidth = 100 - bottomLeftSize;
308
+ // 1. 水平分割:上下
309
+ execTmux(`split-window -t "${target}" -v -p ${bottomHeight} -c "${workingDir}"`, { silent: true });
310
+ // 2. 分割上方:左右
311
+ execTmux(`split-window -t "${target}.0" -h -p ${topRightWidth} -c "${workingDir}"`, { silent: true });
312
+ // 3. 分割下方:左右
313
+ execTmux(`split-window -t "${target}.1" -h -p ${bottomRightWidth} -c "${workingDir}"`, { silent: true });
314
+ // 4. 选择左上 pane
315
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
316
+ return true;
317
+ }
318
+ /**
319
+ * 四窗格:仅 verticalSplit(先左右分割)
320
+ */
321
+ function setupFourPaneVerticalSplit(target, workingDir, layout) {
322
+ const verticalSplit = layout?.verticalSplit ?? 50;
323
+ const topLeftSize = layout?.topLeftSize ?? 50;
324
+ const topRightSize = layout?.topRightSize ?? 50;
325
+ const rightWidth = 100 - verticalSplit;
326
+ const bottomLeftHeight = 100 - topLeftSize;
327
+ const bottomRightHeight = 100 - topRightSize;
328
+ // 1. 垂直分割:左右
329
+ execTmux(`split-window -t "${target}" -h -p ${rightWidth} -c "${workingDir}"`, { silent: true });
330
+ // 2. 分割左侧:上下
331
+ execTmux(`split-window -t "${target}.0" -v -p ${bottomLeftHeight} -c "${workingDir}"`, { silent: true });
332
+ // 3. 分割右侧:上下
333
+ execTmux(`split-window -t "${target}.1" -v -p ${bottomRightHeight} -c "${workingDir}"`, { silent: true });
334
+ // 4. 选择左上 pane
335
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
336
+ return true;
337
+ }
338
+ /**
339
+ * 四窗格:默认 50/50 分割
340
+ */
341
+ function setupFourPaneDefault(target, workingDir) {
342
+ // 默认:50/50 分割
343
+ // 1. 垂直分割:左右
344
+ execTmux(`split-window -t "${target}" -h -p 50 -c "${workingDir}"`, { silent: true });
345
+ // 2. 分割左侧:上下
346
+ execTmux(`split-window -t "${target}.0" -v -p 50 -c "${workingDir}"`, { silent: true });
347
+ // 3. 分割右侧:上下
348
+ execTmux(`split-window -t "${target}.1" -v -p 50 -c "${workingDir}"`, { silent: true });
349
+ // 4. 选择左上 pane
350
+ execTmux(`select-pane -t "${target}.0"`, { silent: true });
351
+ return true;
352
+ }
353
+ /**
354
+ * 向指定 pane 发送命令
355
+ * @param sessionName session 名称
356
+ * @param windowIndex window 索引
357
+ * @param paneIndex pane 索引
358
+ * @param command 要执行的命令
359
+ */
360
+ export function sendKeys(sessionName, windowIndex, paneIndex, command) {
361
+ const target = `${sessionName}:${windowIndex}.${paneIndex}`;
362
+ try {
363
+ // 发送命令并按 Enter
364
+ execTmux(`send-keys -t "${target}" "${command}" Enter`, { silent: true });
365
+ return true;
366
+ }
367
+ catch {
368
+ return false;
369
+ }
370
+ }
371
+ /**
372
+ * 切换到指定 window
373
+ * @param sessionName session 名称
374
+ * @param windowIndex window 索引
375
+ * @param projectName 项目名(用于设置 iTerm2 title)
376
+ * @param branchName 分支名(用于设置 iTerm2 title)
377
+ */
378
+ export function switchWindow(sessionName, windowIndex, projectName, branchName) {
379
+ try {
380
+ execTmux(`select-window -t "${sessionName}:${windowIndex}"`, {
381
+ silent: true,
382
+ });
383
+ // 切换后更新 iTerm2 title
384
+ if (projectName && branchName) {
385
+ setIterm2Title(sessionName, windowIndex, projectName, branchName);
386
+ }
387
+ return true;
388
+ }
389
+ catch {
390
+ return false;
391
+ }
392
+ }
393
+ /**
394
+ * 重命名 window
395
+ * @param sessionName session 名称
396
+ * @param windowIndex window 索引
397
+ * @param newName 新名称
398
+ */
399
+ export function renameWindow(sessionName, windowIndex, newName) {
400
+ try {
401
+ execTmux(`rename-window -t "${sessionName}:${windowIndex}" "${newName}"`, {
402
+ silent: true,
403
+ });
404
+ return true;
405
+ }
406
+ catch {
407
+ return false;
408
+ }
409
+ }
410
+ /**
411
+ * 获取 session 的所有 window 列表
412
+ * @param sessionName session 名称
413
+ * @returns window 信息数组
414
+ */
415
+ export function listWindows(sessionName) {
416
+ try {
417
+ const output = execTmux(`list-windows -t "${sessionName}" -F "#{window_index}:#{window_name}"`, { silent: true });
418
+ if (!output) {
419
+ return [];
420
+ }
421
+ return output.split('\n').map((line) => {
422
+ const [indexStr, name] = line.split(':');
423
+ return {
424
+ index: parseInt(indexStr, 10),
425
+ name,
426
+ };
427
+ });
428
+ }
429
+ catch {
430
+ return [];
431
+ }
432
+ }
433
+ /**
434
+ * 获取指定 window 的当前名称
435
+ * @param sessionName session 名称
436
+ * @param windowIndex window 索引
437
+ * @returns window 名称,如果不存在返回 null
438
+ */
439
+ export function getWindowCurrentName(sessionName, windowIndex) {
440
+ try {
441
+ const output = execTmux(`display-message -t "${sessionName}:${windowIndex}" -p "#{window_name}"`, { silent: true });
442
+ return output || null;
443
+ }
444
+ catch {
445
+ return null;
446
+ }
447
+ }
448
+ /**
449
+ * 设置完整的 window 环境
450
+ * @param options 配置选项
451
+ * @returns 是否成功
452
+ */
453
+ export function setupWindow(options) {
454
+ const { sessionName, windowIndex, windowName, workingDir, devCommand, paneCommands, paneLayout, skipWindowCreation = false, projectName, branchName, } = options;
455
+ try {
456
+ // 1. 创建 window(如果需要)
457
+ if (!skipWindowCreation) {
458
+ if (!createWindow(sessionName, windowIndex, windowName, workingDir)) {
459
+ return false;
460
+ }
461
+ }
462
+ else {
463
+ // 只重命名 window 0
464
+ renameWindow(sessionName, windowIndex, windowName);
465
+ }
466
+ // 2. 设置 3-pane 布局
467
+ if (!setupPaneLayout(sessionName, windowIndex, workingDir, paneLayout)) {
468
+ return false;
469
+ }
470
+ // 3. 发送命令到各个 pane
471
+ if (paneCommands) {
472
+ // 使用新的 paneCommands 配置
473
+ if (paneCommands.pane0) {
474
+ sendKeys(sessionName, windowIndex, 0, paneCommands.pane0);
475
+ }
476
+ if (paneCommands.pane1) {
477
+ sendKeys(sessionName, windowIndex, 1, paneCommands.pane1);
478
+ }
479
+ if (paneCommands.pane2) {
480
+ sendKeys(sessionName, windowIndex, 2, paneCommands.pane2);
481
+ }
482
+ if (paneCommands.pane3) {
483
+ sendKeys(sessionName, windowIndex, 3, paneCommands.pane3);
484
+ }
485
+ }
486
+ else if (devCommand) {
487
+ // 向后兼容:如果只提供 devCommand,在 pane 1 执行
488
+ sendKeys(sessionName, windowIndex, 1, devCommand);
489
+ }
490
+ // 4. 设置 iTerm2 title
491
+ if (projectName && branchName) {
492
+ setIterm2Title(sessionName, windowIndex, projectName, branchName);
493
+ }
494
+ return true;
495
+ }
496
+ catch {
497
+ return false;
498
+ }
499
+ }
500
+ /**
501
+ * 删除 window
502
+ * @param sessionName session 名称
503
+ * @param windowIndex window 索引
504
+ */
505
+ export function killWindow(sessionName, windowIndex) {
506
+ try {
507
+ execTmux(`kill-window -t "${sessionName}:${windowIndex}"`, {
508
+ silent: true,
509
+ });
510
+ return true;
511
+ }
512
+ catch {
513
+ return false;
514
+ }
515
+ }
516
+ /**
517
+ * 删除 session
518
+ * @param sessionName session 名称
519
+ */
520
+ export function killSession(sessionName) {
521
+ try {
522
+ execTmux(`kill-session -t "${sessionName}"`, { silent: true });
523
+ return true;
524
+ }
525
+ catch {
526
+ return false;
527
+ }
528
+ }
529
+ /**
530
+ * 连接到 session(不在 tmux 中时使用)
531
+ * @param sessionName session 名称
532
+ * @returns 是否成功(此函数会接管当前进程,通常不会返回)
533
+ */
534
+ export function attachSession(sessionName) {
535
+ try {
536
+ execTmux(`attach-session -t "${sessionName}"`, { silent: false });
537
+ return true;
538
+ }
539
+ catch {
540
+ return false;
541
+ }
542
+ }
543
+ /**
544
+ * 切换客户端到指定 session(在 tmux 中时使用)
545
+ * @param sessionName 目标 session 名称
546
+ * @returns 是否成功
547
+ */
548
+ export function switchClient(sessionName) {
549
+ try {
550
+ execTmux(`switch-client -t "${sessionName}"`, { silent: true });
551
+ return true;
552
+ }
553
+ catch {
554
+ return false;
555
+ }
556
+ }
557
+ /**
558
+ * 断开当前客户端连接(在 tmux 中时使用)
559
+ * @returns 是否成功
560
+ */
561
+ export function detachClient() {
562
+ try {
563
+ execTmux('detach-client', { silent: true });
564
+ return true;
565
+ }
566
+ catch {
567
+ return false;
568
+ }
569
+ }
570
+ /**
571
+ * 获取所有 tmux session 列表
572
+ * @returns session 名称数组
573
+ */
574
+ export function listSessions() {
575
+ try {
576
+ const output = execTmux('list-sessions -F "#{session_name}"', {
577
+ silent: true,
578
+ });
579
+ if (!output) {
580
+ return [];
581
+ }
582
+ return output.split('\n').filter((name) => name.trim() !== '');
583
+ }
584
+ catch {
585
+ return [];
586
+ }
587
+ }
588
+ /**
589
+ * 获取指定 pane 的当前工作目录
590
+ * @param sessionName session 名称
591
+ * @param windowIndex window 索引
592
+ * @param paneIndex pane 索引
593
+ * @returns 当前工作目录路径,如果获取失败返回 null
594
+ */
595
+ export function getPaneCurrentPath(sessionName, windowIndex, paneIndex) {
596
+ try {
597
+ const target = `${sessionName}:${windowIndex}.${paneIndex}`;
598
+ const output = execTmux(`display-message -t "${target}" -p "#{pane_current_path}"`, { silent: true });
599
+ return output || null;
600
+ }
601
+ catch {
602
+ return null;
603
+ }
604
+ }
605
+ /**
606
+ * 检测是否在 iTerm2 中运行
607
+ */
608
+ function isInIterm2() {
609
+ return (process.env.TERM_PROGRAM === 'iTerm.app' ||
610
+ process.env.LC_TERMINAL === 'iTerm2');
611
+ }
612
+ /**
613
+ * 检查 tmux 是否已开启 allow-passthrough(需要 tmux 3.3+)
614
+ * @returns 'on' 已开启 | 'off' 已关闭 | 'unsupported' 版本不支持
615
+ */
616
+ export function checkAllowPassthrough() {
617
+ try {
618
+ const result = execTmux('show-option -gv allow-passthrough', {
619
+ silent: true,
620
+ ignoreError: true
621
+ });
622
+ const val = result.trim();
623
+ if (val === 'on' || val === 'all')
624
+ return 'on';
625
+ if (val === 'off')
626
+ return 'off';
627
+ // 空结果通常意味着版本不支持此选项
628
+ return 'unsupported';
629
+ }
630
+ catch {
631
+ return 'unsupported';
632
+ }
633
+ }
634
+ /** 常见 shell 进程名 */
635
+ const SHELL_COMMANDS = new Set(['bash', 'zsh', 'sh', 'fish', 'tcsh', 'csh', 'dash']);
636
+ /**
637
+ * 在指定 window 中查找正在运行 shell 的 pane 索引
638
+ * 跳过正在运行交互式程序(如 claude、vim)的 pane
639
+ */
640
+ function findShellPane(sessionName, windowIndex) {
641
+ try {
642
+ const output = execTmux(`list-panes -t "${sessionName}:${windowIndex}" -F "#{pane_index} #{pane_current_command}"`, { silent: true, ignoreError: true });
643
+ for (const line of output.split('\n').filter(l => l.trim())) {
644
+ const spaceIdx = line.indexOf(' ');
645
+ if (spaceIdx !== -1) {
646
+ const paneIndex = parseInt(line.substring(0, spaceIdx), 10);
647
+ const command = line.substring(spaceIdx + 1).trim();
648
+ if (SHELL_COMMANDS.has(command)) {
649
+ return paneIndex;
650
+ }
651
+ }
652
+ }
653
+ return null;
654
+ }
655
+ catch {
656
+ return null;
657
+ }
658
+ }
659
+ /**
660
+ * 设置 iTerm2 tab title
661
+ * @param sessionName session 名称
662
+ * @param windowIndex window 索引(即 worktree ID)
663
+ * @param projectName 项目名
664
+ * @param branchName 分支名
665
+ */
666
+ export function setIterm2Title(sessionName, windowIndex, projectName, branchName) {
667
+ // 检测是否在 iTerm2 中运行
668
+ if (!isInIterm2()) {
669
+ return false;
670
+ }
671
+ let tabTitle;
672
+ if (isInTmux()) {
673
+ // tmux 环境:tab title 统一显示项目名 + #tmux
674
+ tabTitle = `🐶 ${projectName} #tmux`;
675
+ }
676
+ else {
677
+ // 非 tmux 环境:显示完整信息
678
+ const windowName = getWindowName(branchName);
679
+ tabTitle = `🐶 ${projectName} #${windowIndex} - ${windowName}`;
680
+ }
681
+ try {
682
+ // 只设置 tab title (icon name)
683
+ // \033]1;title\007 设置 tab title
684
+ if (isInTmux()) {
685
+ const passthroughStatus = checkAllowPassthrough();
686
+ if (passthroughStatus === 'on') {
687
+ // allow-passthrough 已开启:使用 DCS 直通模式
688
+ // 格式:\033Ptmux;\033\033]1;title\007\033\
689
+ // DCS 内部的 ESC 需要双写(\033\033)
690
+ process.stderr.write(`\x1bPtmux;\x1b\x1b]1;${tabTitle}\x07\x1b\\`);
691
+ }
692
+ else {
693
+ // 回退方案:在 window 中找一个空闲的 shell pane 执行 printf
694
+ // 这样可以避免向正在运行 Claude 等交互式程序的 pane 发送键盘输入
695
+ const shellPane = findShellPane(sessionName, windowIndex);
696
+ if (shellPane !== null) {
697
+ const escapeSeq = `printf '\\033]1;${tabTitle}\\007'`;
698
+ execTmux(`send-keys -t "${sessionName}:${windowIndex}.${shellPane}" "${escapeSeq}" Enter`, { silent: true, ignoreError: true });
699
+ }
700
+ }
701
+ }
702
+ else {
703
+ // 非 tmux 环境,直接输出到 stderr
704
+ process.stderr.write(`\x1b]1;${tabTitle}\x07`);
705
+ }
706
+ return true;
707
+ }
708
+ catch {
709
+ return false;
710
+ }
711
+ }
712
+ //# sourceMappingURL=tmux.js.map