commandmate 0.1.11 → 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.
Files changed (158) hide show
  1. package/.env.example +4 -9
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/app-build-manifest.json +25 -25
  4. package/.next/app-path-routes-manifest.json +1 -1
  5. package/.next/build-manifest.json +7 -7
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/2.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  14. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  15. package/.next/cache/webpack/server-production/0.pack +0 -0
  16. package/.next/cache/webpack/server-production/index.pack +0 -0
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/react-loadable-manifest.json +7 -7
  20. package/.next/required-server-files.json +1 -1
  21. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/_not-found.html +1 -1
  23. package/.next/server/app/_not-found.rsc +2 -2
  24. package/.next/server/app/api/hooks/claude-done/route.js +1 -19
  25. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  26. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  27. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/repositories/clone/route.js +1 -1
  29. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  30. package/.next/server/app/api/repositories/excluded/route.js +36 -0
  31. package/.next/server/app/api/repositories/excluded/route.js.nft.json +1 -0
  32. package/.next/server/app/api/repositories/excluded.body +1 -0
  33. package/.next/server/app/api/repositories/excluded.meta +1 -0
  34. package/.next/server/app/api/repositories/restore/route.js +36 -0
  35. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -0
  36. package/.next/server/app/api/repositories/route.js +36 -1
  37. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  38. package/.next/server/app/api/repositories/scan/route.js +1 -1
  39. package/.next/server/app/api/repositories/sync/route.js +36 -1
  40. package/.next/server/app/api/slash-commands/route.js +1 -1
  41. package/.next/server/app/api/slash-commands.body +1 -1
  42. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  43. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  44. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  45. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  46. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  47. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  48. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  49. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  50. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  51. package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
  52. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  53. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  54. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  55. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  56. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  57. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  58. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
  59. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  60. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  61. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  62. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  63. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
  64. package/.next/server/app/api/worktrees/route.js +1 -1
  65. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  66. package/.next/server/app/index.html +2 -2
  67. package/.next/server/app/index.rsc +3 -3
  68. package/.next/server/app/page.js +7 -7
  69. package/.next/server/app/page.js.nft.json +1 -1
  70. package/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/.next/server/app/proxy/[...path]/route.js +2 -2
  72. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/worktrees/[id]/page.js +4 -4
  74. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  75. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  76. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  78. package/.next/server/app-paths-manifest.json +12 -10
  79. package/.next/server/chunks/5488.js +36 -0
  80. package/.next/server/chunks/6550.js +1 -1
  81. package/.next/server/chunks/7425.js +53 -50
  82. package/.next/server/chunks/7536.js +1 -0
  83. package/.next/server/chunks/8174.js +23 -0
  84. package/.next/server/chunks/9367.js +19 -0
  85. package/.next/server/functions-config-manifest.json +1 -1
  86. package/.next/server/middleware-build-manifest.js +1 -1
  87. package/.next/server/middleware-manifest.json +2 -28
  88. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  89. package/.next/server/pages/404.html +1 -1
  90. package/.next/server/pages/500.html +1 -1
  91. package/.next/server/server-reference-manifest.json +1 -1
  92. package/.next/static/chunks/4327.740cc7fe2d0b5049.js +60 -0
  93. package/.next/static/chunks/4343-ebe884a2a80eb033.js +1 -0
  94. package/.next/static/chunks/6568-38a33aa67d82e12b.js +1 -0
  95. package/.next/static/chunks/816-c254f4e2406e696a.js +1 -0
  96. package/.next/static/chunks/app/layout-4804cfba519283cf.js +1 -0
  97. package/.next/static/chunks/app/page-3926224c4cdf315b.js +1 -0
  98. package/.next/static/chunks/app/worktrees/[id]/page-d64624eb67af57c0.js +1 -0
  99. package/.next/static/chunks/main-b6d727aa9248d4f2.js +1 -0
  100. package/.next/static/chunks/{webpack-3fc79fab9bb738d7.js → webpack-4f85dcef6279c6ee.js} +1 -1
  101. package/.next/static/css/28be35e4727ae7ef.css +3 -0
  102. package/.next/trace +5 -5
  103. package/.next/types/app/api/repositories/excluded/route.ts +343 -0
  104. package/.next/types/app/api/repositories/restore/route.ts +343 -0
  105. package/README.md +2 -2
  106. package/dist/cli/commands/init.d.ts.map +1 -1
  107. package/dist/cli/commands/init.js +2 -13
  108. package/dist/cli/commands/start.d.ts.map +1 -1
  109. package/dist/cli/commands/start.js +3 -7
  110. package/dist/cli/config/security-messages.d.ts +11 -0
  111. package/dist/cli/config/security-messages.d.ts.map +1 -0
  112. package/dist/cli/config/security-messages.js +29 -0
  113. package/dist/cli/types/index.d.ts +0 -1
  114. package/dist/cli/types/index.d.ts.map +1 -1
  115. package/dist/cli/utils/daemon.d.ts.map +1 -1
  116. package/dist/cli/utils/daemon.js +3 -7
  117. package/dist/cli/utils/env-setup.d.ts +0 -4
  118. package/dist/cli/utils/env-setup.d.ts.map +1 -1
  119. package/dist/cli/utils/env-setup.js +0 -14
  120. package/dist/cli/utils/security-logger.d.ts.map +1 -1
  121. package/dist/cli/utils/security-logger.js +1 -2
  122. package/dist/server/src/lib/auto-yes-manager.js +19 -12
  123. package/dist/server/src/lib/claude-poller.js +337 -0
  124. package/dist/server/src/lib/claude-session.js +134 -28
  125. package/dist/server/src/lib/cli-patterns.js +9 -2
  126. package/dist/server/src/lib/cli-tools/base.js +7 -1
  127. package/dist/server/src/lib/cli-tools/codex.js +14 -2
  128. package/dist/server/src/lib/cli-tools/manager.js +27 -0
  129. package/dist/server/src/lib/cli-tools/types.js +7 -0
  130. package/dist/server/src/lib/cli-tools/validation.js +41 -0
  131. package/dist/server/src/lib/db.js +23 -0
  132. package/dist/server/src/lib/env.js +0 -17
  133. package/dist/server/src/lib/logger.js +0 -4
  134. package/dist/server/src/lib/prompt-detector.js +129 -31
  135. package/dist/server/src/lib/ws-server.js +12 -1
  136. package/dist/server/src/types/sidebar.js +16 -31
  137. package/dist/server/src/types/slash-commands.js +2 -0
  138. package/package.json +1 -1
  139. package/.next/server/chunks/1318.js +0 -29
  140. package/.next/server/chunks/1528.js +0 -1
  141. package/.next/server/chunks/7213.js +0 -1
  142. package/.next/server/chunks/9703.js +0 -31
  143. package/.next/server/chunks/9723.js +0 -19
  144. package/.next/server/edge-runtime-webpack.js +0 -2
  145. package/.next/server/edge-runtime-webpack.js.map +0 -1
  146. package/.next/server/src/middleware.js +0 -14
  147. package/.next/server/src/middleware.js.map +0 -1
  148. package/.next/static/chunks/2853-d11a80b03c9a1640.js +0 -1
  149. package/.next/static/chunks/4327.3b84aa049900fdeb.js +0 -60
  150. package/.next/static/chunks/816-7e340dad784be28c.js +0 -1
  151. package/.next/static/chunks/9365-733d8c05712d2888.js +0 -1
  152. package/.next/static/chunks/app/layout-37e55f11dcc8b1bf.js +0 -1
  153. package/.next/static/chunks/app/page-fe35d61f14b90a51.js +0 -1
  154. package/.next/static/chunks/app/worktrees/[id]/page-720605c2fb074444.js +0 -1
  155. package/.next/static/chunks/main-a960f4a5e1a2f598.js +0 -1
  156. package/.next/static/css/376b339640084689.css +0 -3
  157. /package/.next/static/{gRNW5YXY43KqCKbCdaJoJ → bdUePCj-b9Gv5okYGp49O}/_buildManifest.js +0 -0
  158. /package/.next/static/{gRNW5YXY43KqCKbCdaJoJ → bdUePCj-b9Gv5okYGp49O}/_ssgManifest.js +0 -0
@@ -8,6 +8,8 @@ exports.CLIToolManager = void 0;
8
8
  const claude_1 = require("./claude");
9
9
  const codex_1 = require("./codex");
10
10
  const gemini_1 = require("./gemini");
11
+ const response_poller_1 = require("../response-poller");
12
+ const claude_poller_1 = require("../claude-poller");
11
13
  /**
12
14
  * CLI Tool Manager (Singleton)
13
15
  * Provides centralized access to all CLI tools
@@ -139,5 +141,30 @@ class CLIToolManager {
139
141
  const allInfo = await this.getAllToolsInfo();
140
142
  return allInfo.filter(info => info.installed);
141
143
  }
144
+ /**
145
+ * Stop pollers for a specific worktree and CLI tool
146
+ * T2.4: Abstraction for poller stopping (MF1-001 DIP compliance)
147
+ *
148
+ * This method abstracts the poller stopping logic so API layer
149
+ * doesn't need to know about specific poller implementations.
150
+ *
151
+ * @param worktreeId - Worktree ID
152
+ * @param cliToolId - CLI tool ID
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const manager = CLIToolManager.getInstance();
157
+ * manager.stopPollers('my-worktree', 'claude');
158
+ * ```
159
+ */
160
+ stopPollers(worktreeId, cliToolId) {
161
+ // Stop response-poller for all tools
162
+ (0, response_poller_1.stopPolling)(worktreeId, cliToolId);
163
+ // claude-poller is Claude-specific
164
+ if (cliToolId === 'claude') {
165
+ (0, claude_poller_1.stopPolling)(worktreeId);
166
+ }
167
+ // Future: Add other tool-specific pollers here if needed
168
+ }
142
169
  }
143
170
  exports.CLIToolManager = CLIToolManager;
@@ -3,3 +3,10 @@
3
3
  * Type definitions and interfaces for CLI tools
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CLI_TOOL_IDS = void 0;
7
+ /**
8
+ * CLI Tool IDs constant array
9
+ * T2.1: Single source of truth for CLI tool IDs
10
+ * CLIToolType is derived from this constant (DRY principle)
11
+ */
12
+ exports.CLI_TOOL_IDS = ['claude', 'codex', 'gemini'];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * Session name validation module
4
+ * Issue #4: T2.2 - Security validation for session names (MF4-001)
5
+ *
6
+ * This module provides validation functions to prevent command injection
7
+ * attacks through session names used in tmux commands.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SESSION_NAME_PATTERN = void 0;
11
+ exports.validateSessionName = validateSessionName;
12
+ /**
13
+ * Session name pattern
14
+ * Only allows alphanumeric characters, underscores, and hyphens
15
+ * This prevents command injection through shell special characters
16
+ *
17
+ * Pattern breakdown:
18
+ * - ^ : Start of string
19
+ * - [a-zA-Z0-9_-] : Allowed characters (alphanumeric, underscore, hyphen)
20
+ * - + : One or more characters (empty strings not allowed)
21
+ * - $ : End of string
22
+ */
23
+ exports.SESSION_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
24
+ /**
25
+ * Validate session name format
26
+ * Throws an error if the session name contains invalid characters
27
+ *
28
+ * @param sessionName - Session name to validate
29
+ * @throws Error if session name is invalid
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * validateSessionName('mcbd-claude-test'); // OK
34
+ * validateSessionName('test;rm -rf'); // Throws Error
35
+ * ```
36
+ */
37
+ function validateSessionName(sessionName) {
38
+ if (!exports.SESSION_NAME_PATTERN.test(sessionName)) {
39
+ throw new Error(`Invalid session name format: ${sessionName}`);
40
+ }
41
+ }
@@ -19,6 +19,7 @@ exports.getMessages = getMessages;
19
19
  exports.getLastUserMessage = getLastUserMessage;
20
20
  exports.getLastMessage = getLastMessage;
21
21
  exports.deleteAllMessages = deleteAllMessages;
22
+ exports.deleteMessagesByCliTool = deleteMessagesByCliTool;
22
23
  exports.getSessionState = getSessionState;
23
24
  exports.updateSessionState = updateSessionState;
24
25
  exports.setInProgressMessageId = setInProgressMessageId;
@@ -458,6 +459,28 @@ function deleteAllMessages(db, worktreeId) {
458
459
  stmt.run(worktreeId);
459
460
  console.log(`[deleteAllMessages] Deleted all messages for worktree: ${worktreeId}`);
460
461
  }
462
+ /**
463
+ * Delete messages for a specific CLI tool in a worktree
464
+ * Issue #4: T4.2 - Individual CLI tool session termination (MF3-001)
465
+ *
466
+ * Used when killing only a specific CLI tool's session to clear its message history
467
+ * while preserving messages from other CLI tools.
468
+ * Note: Log files are preserved for historical reference
469
+ *
470
+ * @param db - Database instance
471
+ * @param worktreeId - Worktree ID
472
+ * @param cliTool - CLI tool ID to delete messages for
473
+ * @returns Number of deleted messages
474
+ */
475
+ function deleteMessagesByCliTool(db, worktreeId, cliTool) {
476
+ const stmt = db.prepare(`
477
+ DELETE FROM chat_messages
478
+ WHERE worktree_id = ? AND cli_tool_id = ?
479
+ `);
480
+ const result = stmt.run(worktreeId, cliTool);
481
+ console.log(`[deleteMessagesByCliTool] Deleted ${result.changes} messages for worktree: ${worktreeId}, cliTool: ${cliTool}`);
482
+ return result.changes;
483
+ }
461
484
  /**
462
485
  * Get session state for a worktree
463
486
  */
@@ -22,7 +22,6 @@ exports.getDatabasePathWithDeprecationWarning = getDatabasePathWithDeprecationWa
22
22
  exports.getLogConfig = getLogConfig;
23
23
  exports.getEnv = getEnv;
24
24
  exports.validateEnv = validateEnv;
25
- exports.isAuthRequired = isAuthRequired;
26
25
  const path_1 = __importDefault(require("path"));
27
26
  const db_path_resolver_1 = require("./db-path-resolver");
28
27
  // ============================================================
@@ -37,7 +36,6 @@ exports.ENV_MAPPING = {
37
36
  CM_ROOT_DIR: 'MCBD_ROOT_DIR',
38
37
  CM_PORT: 'MCBD_PORT',
39
38
  CM_BIND: 'MCBD_BIND',
40
- CM_AUTH_TOKEN: 'MCBD_AUTH_TOKEN',
41
39
  CM_LOG_LEVEL: 'MCBD_LOG_LEVEL',
42
40
  CM_LOG_FORMAT: 'MCBD_LOG_FORMAT',
43
41
  CM_LOG_DIR: 'MCBD_LOG_DIR',
@@ -160,7 +158,6 @@ function getEnv() {
160
158
  const rootDir = getEnvByKey('CM_ROOT_DIR') || process.cwd();
161
159
  const port = parseInt(getEnvByKey('CM_PORT') || '3000', 10);
162
160
  const bind = getEnvByKey('CM_BIND') || '127.0.0.1';
163
- const authToken = getEnvByKey('CM_AUTH_TOKEN');
164
161
  // Issue #135: DB path resolution with proper fallback chain
165
162
  // Priority: CM_DB_PATH > DATABASE_PATH (deprecated) > getDefaultDbPath()
166
163
  const databasePath = getEnvByKey('CM_DB_PATH')
@@ -176,10 +173,6 @@ function getEnv() {
176
173
  if (bind !== '127.0.0.1' && bind !== '0.0.0.0' && bind !== 'localhost') {
177
174
  throw new Error(`Invalid CM_BIND: ${bind}. Must be '127.0.0.1', '0.0.0.0', or 'localhost'.`);
178
175
  }
179
- // Require auth token for public binding
180
- if (bind === '0.0.0.0' && !authToken) {
181
- throw new Error('CM_AUTH_TOKEN (or MCBD_AUTH_TOKEN) is required when CM_BIND=0.0.0.0');
182
- }
183
176
  // Issue #135: Validate DB path for security (SEC-001)
184
177
  let validatedDbPath;
185
178
  try {
@@ -195,7 +188,6 @@ function getEnv() {
195
188
  CM_ROOT_DIR: path_1.default.resolve(rootDir),
196
189
  CM_PORT: port,
197
190
  CM_BIND: bind,
198
- CM_AUTH_TOKEN: authToken,
199
191
  CM_DB_PATH: validatedDbPath,
200
192
  };
201
193
  }
@@ -217,12 +209,3 @@ function validateEnv() {
217
209
  return { valid: false, errors };
218
210
  }
219
211
  }
220
- /**
221
- * Check if authentication is required based on bind address
222
- *
223
- * @returns True if authentication is required
224
- */
225
- function isAuthRequired() {
226
- const env = getEnv();
227
- return env.CM_BIND === '0.0.0.0';
228
- }
@@ -35,10 +35,6 @@ const SENSITIVE_PATTERNS = [
35
35
  { pattern: /(password|passwd|pwd)[=:]\s*\S+/gi, replacement: '$1=[REDACTED]' },
36
36
  // Token/secret related
37
37
  { pattern: /(token|secret|api_key|apikey|auth)[=:]\s*\S+/gi, replacement: '$1=[REDACTED]' },
38
- // CM_AUTH_TOKEN (new name - Issue #76)
39
- { pattern: /CM_AUTH_TOKEN=\S+/gi, replacement: 'CM_AUTH_TOKEN=[REDACTED]' },
40
- // MCBD_AUTH_TOKEN (legacy name)
41
- { pattern: /MCBD_AUTH_TOKEN=\S+/gi, replacement: 'MCBD_AUTH_TOKEN=[REDACTED]' },
42
38
  // Authorization header
43
39
  { pattern: /Authorization:\s*\S+/gi, replacement: 'Authorization: [REDACTED]' },
44
40
  // SSH key
@@ -147,14 +147,84 @@ const TEXT_INPUT_PATTERNS = [
147
147
  /custom/i,
148
148
  /differently/i,
149
149
  ];
150
+ /**
151
+ * Pattern for ❯ (U+276F) indicator lines used by Claude CLI to mark the default selection.
152
+ * Used in Pass 1 (existence check) and Pass 2 (option collection) of the 2-pass detection.
153
+ * Anchored at both ends -- ReDoS safe (S4-001).
154
+ */
155
+ const DEFAULT_OPTION_PATTERN = /^\s*\u276F\s*(\d+)\.\s*(.+)$/;
156
+ /**
157
+ * Pattern for normal option lines (no ❯ indicator, just leading whitespace + number).
158
+ * Only applied in Pass 2 when ❯ indicator existence is confirmed by Pass 1.
159
+ * Anchored at both ends -- ReDoS safe (S4-001).
160
+ */
161
+ const NORMAL_OPTION_PATTERN = /^\s*(\d+)\.\s*(.+)$/;
162
+ /**
163
+ * Defensive check: protection against future unknown false positive patterns.
164
+ * Note: The actual false positive pattern in Issue #161 ("1. Create file\n2. Run tests")
165
+ * IS consecutive from 1, so this validation alone does not prevent it.
166
+ * The primary defense layers are: Layer 1 (thinking check in caller) + Layer 2 (2-pass
167
+ * cursor detection). This function provides Layer 3 defense against future unknown
168
+ * patterns with scattered/non-consecutive numbering.
169
+ *
170
+ * [S3-010] This validation assumes Claude CLI always uses consecutive numbering
171
+ * starting from 1. If in the future Claude CLI is observed to filter choices and
172
+ * output non-consecutive numbers (e.g., 1, 2, 4), consider relaxing this validation
173
+ * (e.g., only check starts-from-1, remove consecutive requirement).
174
+ */
175
+ function isConsecutiveFromOne(numbers) {
176
+ if (numbers.length === 0)
177
+ return false;
178
+ if (numbers[0] !== 1)
179
+ return false;
180
+ for (let i = 1; i < numbers.length; i++) {
181
+ if (numbers[i] !== numbers[i - 1] + 1)
182
+ return false;
183
+ }
184
+ return true;
185
+ }
186
+ /**
187
+ * Continuation line detection for multiline option text wrapping.
188
+ * Detects lines that are part of a previous option's text, wrapped due to terminal width.
189
+ *
190
+ * Called within detectMultipleChoicePrompt() Pass 2 reverse scan, only when
191
+ * options.length > 0 (at least one option already detected):
192
+ * const rawLine = lines[i]; // Original line with indentation preserved
193
+ * const line = lines[i].trim(); // Trimmed line
194
+ * if (options.length > 0 && line && !line.match(/^[-─]+$/)) {
195
+ * if (isContinuationLine(rawLine, line)) { continue; }
196
+ * }
197
+ *
198
+ * Each condition's responsibility:
199
+ * - hasLeadingSpaces: Indented non-option line (label text wrapping with indentation)
200
+ * - isShortFragment: Short fragment (< 5 chars, e.g., filename tail)
201
+ * - isPathContinuation: Path string continuation (Issue #181)
202
+ *
203
+ * @param rawLine - Original line with indentation preserved (lines[i])
204
+ * @param line - Trimmed line (lines[i].trim())
205
+ * @returns true if the line should be treated as a continuation of a previous option
206
+ */
207
+ function isContinuationLine(rawLine, line) {
208
+ // Indented non-option line
209
+ const hasLeadingSpaces = rawLine.match(/^\s{2,}[^\d]/) && !rawLine.match(/^\s*\d+\./);
210
+ // Short fragment (< 5 chars, excluding question-ending lines)
211
+ const isShortFragment = line.length < 5 && !line.endsWith('?');
212
+ // Path string continuation: lines starting with / or ~, or alphanumeric-only fragments (2+ chars)
213
+ const isPathContinuation = /^[\/~]/.test(line) || (line.length >= 2 && /^[a-zA-Z0-9_-]+$/.test(line));
214
+ return !!(hasLeadingSpaces) || isShortFragment || isPathContinuation;
215
+ }
150
216
  /**
151
217
  * Detect multiple choice prompts (numbered list with ❯ indicator)
152
218
  *
153
- * This function scans the output from bottom to top looking for numbered options
154
- * with a selection indicator (❯). It requires at least 2 options and a default
155
- * indicator to be considered a valid prompt.
219
+ * Uses a 2-pass detection approach (Issue #161):
220
+ * - Pass 1: Scan 50-line window for indicator lines (defaultOptionPattern).
221
+ * If no lines found, immediately return isPrompt: false.
222
+ * - Pass 2: Only if ❯ was found, re-scan collecting options using both
223
+ * defaultOptionPattern (isDefault=true) and normalOptionPattern (isDefault=false).
224
+ *
225
+ * This prevents normal numbered lists from being accumulated in the options array.
156
226
  *
157
- * Example:
227
+ * Example of valid prompt:
158
228
  * Do you want to proceed?
159
229
  * ❯ 1. Yes
160
230
  * 2. No
@@ -165,36 +235,56 @@ const TEXT_INPUT_PATTERNS = [
165
235
  */
166
236
  function detectMultipleChoicePrompt(output) {
167
237
  const lines = output.split('\n');
168
- // Look for lines that match the pattern: [optional leading spaces] [❯ or spaces] [number]. [text]
169
- // Note: ANSI codes sometimes cause spaces to be lost after stripping, so we use \s* instead of \s+
170
- const optionPattern = /^\s*([❯ ]\s*)?(\d+)\.\s*(.+)$/;
238
+ // Calculate scan window: last 50 lines
239
+ const scanStart = Math.max(0, lines.length - 50);
240
+ // ==========================================================================
241
+ // Pass 1: Check for ❯ indicator existence in scan window
242
+ // If no ❯ lines found, there is no multiple_choice prompt.
243
+ // ==========================================================================
244
+ let hasDefaultLine = false;
245
+ for (let i = scanStart; i < lines.length; i++) {
246
+ const line = lines[i].trim();
247
+ if (DEFAULT_OPTION_PATTERN.test(line)) {
248
+ hasDefaultLine = true;
249
+ break;
250
+ }
251
+ }
252
+ if (!hasDefaultLine) {
253
+ return {
254
+ isPrompt: false,
255
+ cleanContent: output.trim(),
256
+ };
257
+ }
258
+ // ==========================================================================
259
+ // Pass 2: Collect options (only executed when ❯ was found in Pass 1)
260
+ // Scan from end to find options, using both patterns.
261
+ // ==========================================================================
171
262
  const options = [];
172
263
  let questionEndIndex = -1;
173
- let firstOptionIndex = -1;
174
- // Scan from the end to find options
175
- // Increased from 20 to 50 to handle multi-line wrapped options
176
- for (let i = lines.length - 1; i >= 0 && i >= lines.length - 50; i--) {
264
+ for (let i = lines.length - 1; i >= scanStart; i--) {
177
265
  const line = lines[i].trim();
178
- const rawLine = lines[i]; // Keep original indentation for checking
179
- const match = line.match(optionPattern);
180
- if (match) {
181
- const hasDefault = Boolean(match[1] && match[1].includes('❯'));
182
- const number = parseInt(match[2], 10);
183
- const label = match[3].trim();
184
- // Insert at beginning since we're scanning backwards
185
- options.unshift({ number, label, isDefault: hasDefault });
186
- if (firstOptionIndex === -1) {
187
- firstOptionIndex = i;
188
- }
266
+ // Try DEFAULT_OPTION_PATTERN first (❯ indicator)
267
+ const defaultMatch = line.match(DEFAULT_OPTION_PATTERN);
268
+ if (defaultMatch) {
269
+ const number = parseInt(defaultMatch[1], 10);
270
+ const label = defaultMatch[2].trim();
271
+ options.unshift({ number, label, isDefault: true });
272
+ continue;
189
273
  }
190
- else if (options.length > 0 && line && !line.match(/^[-─]+$/)) {
191
- // Check if this is a continuation line (indented line between options)
192
- // Continuation lines typically start with spaces (like " work/github...")
193
- // Also treat very short lines (< 5 chars) as potential word-wrap fragments
194
- const hasLeadingSpaces = rawLine.match(/^\s{2,}[^\d]/) && !rawLine.match(/^\s*\d+\./);
195
- const isShortFragment = line.length < 5 && !line.endsWith('?');
196
- const isContinuationLine = hasLeadingSpaces || isShortFragment;
197
- if (isContinuationLine) {
274
+ // Try NORMAL_OPTION_PATTERN (no indicator)
275
+ const normalMatch = line.match(NORMAL_OPTION_PATTERN);
276
+ if (normalMatch) {
277
+ const number = parseInt(normalMatch[1], 10);
278
+ const label = normalMatch[2].trim();
279
+ options.unshift({ number, label, isDefault: false });
280
+ continue;
281
+ }
282
+ // Non-option line handling
283
+ if (options.length > 0 && line && !line.match(/^[-─]+$/)) {
284
+ // Check if this is a continuation line (indented line between options,
285
+ // or path/filename fragments from terminal width wrapping - Issue #181)
286
+ const rawLine = lines[i]; // Original line with indentation preserved
287
+ if (isContinuationLine(rawLine, line)) {
198
288
  // Skip continuation lines and continue scanning for more options
199
289
  continue;
200
290
  }
@@ -203,7 +293,15 @@ function detectMultipleChoicePrompt(output) {
203
293
  break;
204
294
  }
205
295
  }
206
- // Must have at least 2 options AND at least one with ❯ indicator to be considered a prompt
296
+ // Layer 3: Consecutive number validation (defensive measure)
297
+ const optionNumbers = options.map(opt => opt.number);
298
+ if (!isConsecutiveFromOne(optionNumbers)) {
299
+ return {
300
+ isPrompt: false,
301
+ cleanContent: output.trim(),
302
+ };
303
+ }
304
+ // Layer 4: Must have at least 2 options AND at least one with ❯ indicator
207
305
  const hasDefaultIndicator = options.some(opt => opt.isDefault);
208
306
  if (options.length < 2 || !hasDefaultIndicator) {
209
307
  return {
@@ -27,7 +27,18 @@ const rooms = new Map();
27
27
  * ```
28
28
  */
29
29
  function setupWebSocket(server) {
30
- wss = new ws_1.WebSocketServer({ server });
30
+ wss = new ws_1.WebSocketServer({ noServer: true });
31
+ // Handle upgrade requests - only accept app WebSocket connections, not Next.js HMR
32
+ server.on('upgrade', (request, socket, head) => {
33
+ const pathname = request.url || '/';
34
+ // Let Next.js handle its own HMR WebSocket connections
35
+ if (pathname.startsWith('/_next/')) {
36
+ return;
37
+ }
38
+ wss.handleUpgrade(request, socket, head, (ws) => {
39
+ wss.emit('connection', ws, request);
40
+ });
41
+ });
31
42
  // Handle WebSocket server errors (e.g., invalid frames from clients)
32
43
  wss.on('error', (error) => {
33
44
  console.error('[WS Server] Error:', error.message);
@@ -5,43 +5,22 @@
5
5
  * Types for sidebar components and branch status display
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.deriveCliStatus = deriveCliStatus;
8
9
  exports.calculateHasUnread = calculateHasUnread;
9
10
  exports.toBranchItem = toBranchItem;
10
11
  /**
11
- * Determine branch status from Worktree data
12
- *
13
- * Status priority:
14
- * - waiting: Claude asked a yes/no prompt, waiting for user's answer (green dot)
15
- * - running: Claude is actively processing user's request (spinner)
16
- * - ready: Session running, waiting for user's new message (green dot)
17
- * - idle: Session not running (gray dot)
12
+ * Derive BranchStatus from per-CLI tool session status flags.
13
+ * Shared by sidebar (toBranchItem) and WorktreeDetailRefactored tab dots.
18
14
  */
19
- function determineBranchStatus(worktree) {
20
- // Check CLI-specific status first
21
- const claudeStatus = worktree.sessionStatusByCli?.claude;
22
- if (claudeStatus) {
23
- if (claudeStatus.isWaitingForResponse) {
24
- return 'waiting';
25
- }
26
- if (claudeStatus.isProcessing) {
27
- return 'running';
28
- }
29
- // Session running but not processing = ready (waiting for user to type new message)
30
- if (claudeStatus.isRunning) {
31
- return 'ready';
32
- }
33
- }
34
- // Fall back to legacy status fields
35
- if (worktree.isWaitingForResponse) {
15
+ function deriveCliStatus(toolStatus) {
16
+ if (!toolStatus)
17
+ return 'idle';
18
+ if (toolStatus.isWaitingForResponse)
36
19
  return 'waiting';
37
- }
38
- if (worktree.isProcessing) {
20
+ if (toolStatus.isProcessing)
39
21
  return 'running';
40
- }
41
- // Session running but not processing = ready
42
- if (worktree.isSessionRunning) {
22
+ if (toolStatus.isRunning)
43
23
  return 'ready';
44
- }
45
24
  return 'idle';
46
25
  }
47
26
  /**
@@ -74,7 +53,9 @@ function calculateHasUnread(worktree) {
74
53
  * @returns SidebarBranchItem for sidebar display
75
54
  */
76
55
  function toBranchItem(worktree) {
77
- const status = determineBranchStatus(worktree);
56
+ // Issue #4: Sidebar no longer shows per-CLI session status.
57
+ // Status is always 'idle' since detailed status is shown in WorktreeDetail.
58
+ const status = 'idle';
78
59
  // Use new hasUnread logic based on lastAssistantMessageAt and lastViewedAt
79
60
  const hasUnread = calculateHasUnread(worktree);
80
61
  return {
@@ -85,5 +66,9 @@ function toBranchItem(worktree) {
85
66
  hasUnread,
86
67
  lastActivity: worktree.updatedAt,
87
68
  description: worktree.description,
69
+ cliStatus: {
70
+ claude: deriveCliStatus(worktree.sessionStatusByCli?.claude),
71
+ codex: deriveCliStatus(worktree.sessionStatusByCli?.codex),
72
+ },
88
73
  };
89
74
  }
@@ -3,6 +3,8 @@
3
3
  * Slash Command Types
4
4
  *
5
5
  * Type definitions for slash commands loaded from .claude/commands/*.md
6
+ *
7
+ * Issue #4: Added cliTools field for CLI tool-specific command filtering
6
8
  */
7
9
  Object.defineProperty(exports, "__esModule", { value: true });
8
10
  exports.COMMAND_CATEGORIES = exports.CATEGORY_LABELS = void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commandmate",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "Git worktree management with Claude CLI and tmux sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,29 +0,0 @@
1
- "use strict";exports.id=1318,exports.ids=[1318],exports.modules={61318:(e,t,r)=>{r.d(t,{v:()=>_});var o=r(61282),s=r(92048),a=r(55315),i=r.n(a);class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}normalize(e){let t=e.match(/^ssh:\/\/git@([^:\/]+)(:\d+)?\/(.+?)(\.git)?$/);if(t)return`https://${t[1]}/${t[3]}`.toLowerCase().replace(/\/$/,"");let r=e.match(/^git@([^:]+):(.+?)(\.git)?$/);return r?`https://${r[1]}/${r[2]}`.toLowerCase().replace(/\/$/,""):e.replace(/\.git\/?$/,"").replace(/\/$/,"").toLowerCase()}isSameRepository(e,t){return this.normalize(e)===this.normalize(t)}extractRepoName(e){let t=e.match(/^ssh:\/\/git@[^\/]+\/(.+?)(\.git)?$/);if(t){let e=t[1].split("/");return e[e.length-1]}let r=e.match(/:(.+?)(\.git)?$/);if(r&&e.startsWith("git@")){let e=r[1].split("/");return e[e.length-1]}let o=e.match(/\/([^\/]+?)(\.git)?$/);return o?o[1]:""}getUrlType(e){return e.startsWith("https://")?"https":e.startsWith("git@")||e.startsWith("ssh://")?"ssh":null}validate(e){if(!e||"string"!=typeof e||""===e.trim())return{valid:!1,error:"EMPTY_URL"};let t=e.trim();return t.startsWith("https://")?/^https:\/\/[^\/]+\/[^\/]+\/[^\/]+(\.git)?$/.test(t)?{valid:!0}:{valid:!1,error:"INVALID_URL_FORMAT"}:t.startsWith("git@")?/^git@[^:]+:.+\/.+(\.git)?$/.test(t)?{valid:!0}:{valid:!1,error:"INVALID_URL_FORMAT"}:t.startsWith("ssh://")&&/^ssh:\/\/git@[^\/]+(:\d+)?\/[^\/]+\/.+(\.git)?$/.test(t)?{valid:!0}:{valid:!1,error:"INVALID_URL_FORMAT"}}}var l=r(57440),c=r(84770);function d(e){return{id:e.id,cloneUrl:e.clone_url,normalizedCloneUrl:e.normalized_clone_url,targetPath:e.target_path,repositoryId:e.repository_id||void 0,status:e.status,pid:e.pid||void 0,progress:e.progress,errorCategory:e.error_category||void 0,errorCode:e.error_code||void 0,errorMessage:e.error_message||void 0,startedAt:e.started_at?new Date(e.started_at):void 0,completedAt:e.completed_at?new Date(e.completed_at):void 0,createdAt:new Date(e.created_at)}}function u(e,t){let r=e.prepare(`
2
- SELECT * FROM clone_jobs
3
- WHERE id = ?
4
- `).get(t);return r?d(r):null}function g(e,t,r){let o=[],s=[];void 0!==r.status&&(o.push("status = ?"),s.push(r.status)),void 0!==r.pid&&(o.push("pid = ?"),s.push(r.pid)),void 0!==r.progress&&(o.push("progress = ?"),s.push(r.progress)),void 0!==r.repositoryId&&(o.push("repository_id = ?"),s.push(r.repositoryId)),void 0!==r.errorCategory&&(o.push("error_category = ?"),s.push(r.errorCategory)),void 0!==r.errorCode&&(o.push("error_code = ?"),s.push(r.errorCode)),void 0!==r.errorMessage&&(o.push("error_message = ?"),s.push(r.errorMessage)),void 0!==r.startedAt&&(o.push("started_at = ?"),s.push(r.startedAt.getTime())),void 0!==r.completedAt&&(o.push("completed_at = ?"),s.push(r.completedAt.getTime())),0!==o.length&&(s.push(t),e.prepare(`
5
- UPDATE clone_jobs
6
- SET ${o.join(", ")}
7
- WHERE id = ?
8
- `).run(...s))}var h=r(67722);class p extends Error{constructor(e){super(e.message),this.name="CloneManagerError",this.category=e.category,this.code=e.code,this.recoverable=e.recoverable,this.suggestedAction=e.suggestedAction}}let m={EMPTY_URL:{category:"validation",code:"EMPTY_URL",message:"Clone URL is required",recoverable:!0,suggestedAction:"Please enter a valid git clone URL"},INVALID_URL_FORMAT:{category:"validation",code:"INVALID_URL_FORMAT",message:"Invalid URL format. Please use HTTPS or SSH URL.",recoverable:!0,suggestedAction:"Enter a valid URL like https://github.com/owner/repo or git@github.com:owner/repo"},DUPLICATE_CLONE_URL:{category:"validation",code:"DUPLICATE_CLONE_URL",message:"This repository is already registered",recoverable:!1,suggestedAction:"Use the existing repository instead"},CLONE_IN_PROGRESS:{category:"validation",code:"CLONE_IN_PROGRESS",message:"A clone operation is already in progress for this URL",recoverable:!1,suggestedAction:"Wait for the current clone to complete"},DIRECTORY_EXISTS:{category:"filesystem",code:"DIRECTORY_EXISTS",message:"Target directory already exists",recoverable:!0,suggestedAction:"Choose a different directory or remove the existing one"},INVALID_TARGET_PATH:{category:"validation",code:"INVALID_TARGET_PATH",message:"Target path is invalid or outside allowed directory",recoverable:!0,suggestedAction:"Use a path within the configured base directory"},AUTH_FAILED:{category:"auth",code:"AUTH_FAILED",message:"Authentication failed",recoverable:!0,suggestedAction:"Check your credentials or SSH keys"},NETWORK_ERROR:{category:"network",code:"NETWORK_ERROR",message:"Network error occurred",recoverable:!0,suggestedAction:"Check your internet connection and try again"},GIT_ERROR:{category:"git",code:"GIT_ERROR",message:"Git command failed",recoverable:!1,suggestedAction:"Check the error message for details"},CLONE_TIMEOUT:{category:"network",code:"CLONE_TIMEOUT",message:"Clone operation timed out",recoverable:!0,suggestedAction:"Try again or clone a smaller repository"}};class _{constructor(e,t={}){this.db=e,this.urlNormalizer=n.getInstance(),this.config={basePath:t.basePath||process.env.WORKTREE_BASE_PATH||"/tmp/repos",timeout:t.timeout||6e5},this.activeProcesses=new Map}validateCloneRequest(e){let t=this.urlNormalizer.validate(e);return t.valid?{valid:!0,normalizedUrl:this.urlNormalizer.normalize(e),repoName:this.urlNormalizer.extractRepoName(e)}:{valid:!1,error:m[t.error||"INVALID_URL_FORMAT"]}}checkDuplicateRepository(e){return function(e,t){let r=e.prepare(`
9
- SELECT * FROM repositories
10
- WHERE normalized_clone_url = ?
11
- `).get(t);return r?{id:r.id,name:r.name,path:r.path,enabled:1===r.enabled,cloneUrl:r.clone_url||void 0,normalizedCloneUrl:r.normalized_clone_url||void 0,cloneSource:r.clone_source,isEnvManaged:1===r.is_env_managed,createdAt:new Date(r.created_at),updatedAt:new Date(r.updated_at)}:null}(this.db,e)}checkActiveCloneJob(e){return function(e,t){let r=e.prepare(`
12
- SELECT * FROM clone_jobs
13
- WHERE normalized_clone_url = ?
14
- AND status IN ('pending', 'running')
15
- ORDER BY created_at DESC
16
- LIMIT 1
17
- `).get(t);return r?d(r):null}(this.db,e)}createCloneJob(e){return function(e,t){let r=(0,c.randomUUID)(),o=Date.now();return e.prepare(`
18
- INSERT INTO clone_jobs (
19
- id, clone_url, normalized_clone_url, target_path,
20
- status, progress, created_at
21
- )
22
- VALUES (?, ?, ?, ?, 'pending', 0, ?)
23
- `).run(r,t.cloneUrl,t.normalizedCloneUrl,t.targetPath,o),{id:r,cloneUrl:t.cloneUrl,normalizedCloneUrl:t.normalizedCloneUrl,targetPath:t.targetPath,status:"pending",progress:0,createdAt:new Date(o)}}(this.db,e)}getTargetPath(e){return i().join(this.config.basePath,e)}async startCloneJob(e,t){let r=this.validateCloneRequest(e);if(!r.valid)return{success:!1,error:r.error};let o=r.normalizedUrl,a=r.repoName,i=this.checkDuplicateRepository(o);if(i)return{success:!1,error:{...m.DUPLICATE_CLONE_URL,message:`This repository is already registered as "${i.name}"`}};let n=this.checkActiveCloneJob(o);if(n)return{success:!1,jobId:n.id,error:m.CLONE_IN_PROGRESS};let c=t||this.getTargetPath(a);if(t&&!(0,l.j)(t,this.config.basePath))return{success:!1,error:{...m.INVALID_TARGET_PATH,message:`Target path must be within ${this.config.basePath}`}};if((0,s.existsSync)(c))return{success:!1,error:{...m.DIRECTORY_EXISTS,message:`Target directory already exists: ${c}`}};let d=this.createCloneJob({cloneUrl:e,normalizedCloneUrl:o,targetPath:c});return this.executeClone(d.id,e,c).catch(e=>{console.error(`[CloneManager] Clone failed for job ${d.id}:`,e)}),{success:!0,jobId:d.id}}async executeClone(e,t,r){g(this.db,e,{status:"running",startedAt:new Date});let a=i().dirname(r);return(0,s.existsSync)(a)||(0,s.mkdirSync)(a,{recursive:!0}),new Promise((s,a)=>{let i=(0,o.spawn)("git",["clone","--progress",t,r],{stdio:["ignore","pipe","pipe"]});this.activeProcesses.set(e,i),i.pid&&g(this.db,e,{pid:i.pid});let n="";i.stderr?.on("data",t=>{n+=t.toString();let r=this.parseGitProgress(t.toString());null!==r&&g(this.db,e,{progress:r})});let l=setTimeout(()=>{i.kill("SIGTERM"),g(this.db,e,{status:"failed",errorCategory:"network",errorCode:"CLONE_TIMEOUT",errorMessage:"Clone operation timed out",completedAt:new Date}),this.activeProcesses.delete(e),a(new p(m.CLONE_TIMEOUT))},this.config.timeout);i.on("close",async o=>{if(clearTimeout(l),this.activeProcesses.delete(e),0===o)await this.onCloneSuccess(e,t,r),s();else{let t=this.parseGitError(n,o);g(this.db,e,{status:"failed",errorCategory:t.category,errorCode:t.code,errorMessage:t.message,completedAt:new Date}),a(new p(t))}}),i.on("error",t=>{clearTimeout(l),this.activeProcesses.delete(e);let r={category:"system",code:"SPAWN_ERROR",message:`Failed to spawn git process: ${t.message}`,recoverable:!1,suggestedAction:"Ensure git is installed and available in PATH"};g(this.db,e,{status:"failed",errorCategory:r.category,errorCode:r.code,errorMessage:r.message,completedAt:new Date}),a(new p(r))})})}async onCloneSuccess(e,t,r){let o=u(this.db,e);if(!o)return;let s=this.urlNormalizer.getUrlType(t),a=function(e,t){let r=(0,c.randomUUID)(),o=Date.now();return e.prepare(`
24
- INSERT INTO repositories (
25
- id, name, path, enabled, clone_url, normalized_clone_url,
26
- clone_source, is_env_managed, created_at, updated_at
27
- )
28
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
- `).run(r,t.name,t.path,!1!==t.enabled?1:0,t.cloneUrl||null,t.normalizedCloneUrl||null,t.cloneSource,t.isEnvManaged?1:0,o,o),{id:r,name:t.name,path:t.path,enabled:!1!==t.enabled,cloneUrl:t.cloneUrl,normalizedCloneUrl:t.normalizedCloneUrl,cloneSource:t.cloneSource,isEnvManaged:t.isEnvManaged||!1,createdAt:new Date(o),updatedAt:new Date(o)}}(this.db,{name:i().basename(r),path:r,cloneUrl:t,normalizedCloneUrl:o.normalizedCloneUrl,cloneSource:s||"https"});try{let e=await (0,h.e9)(r);e.length>0&&((0,h.h2)(this.db,e),console.log(`[CloneManager] Registered ${e.length} worktree(s) for ${r}`))}catch(e){console.error(`[CloneManager] Failed to scan worktrees for ${r}:`,e)}g(this.db,e,{status:"completed",progress:100,repositoryId:a.id,completedAt:new Date})}parseGitProgress(e){let t=e.match(/(?:Receiving objects|Resolving deltas|Cloning into[^:]*?):\s*(\d+)%/);if(t){let e=parseInt(t[1],10);return isNaN(e)?null:e}return null}parseGitError(e,t){let r=e.toLowerCase(),o=e.substring(0,200);return["authentication failed","permission denied","could not read from remote repository"].some(e=>r.includes(e))?{...m.AUTH_FAILED,message:`Authentication failed: ${o}`}:["could not resolve host","connection refused","network is unreachable"].some(e=>r.includes(e))?{...m.NETWORK_ERROR,message:`Network error: ${o}`}:{...m.GIT_ERROR,message:`Git clone failed (exit code ${t}): ${o}`}}getCloneJobStatus(e){let t=u(this.db,e);if(!t)return null;let r={jobId:t.id,status:t.status,progress:t.progress,repositoryId:t.repositoryId};return"failed"===t.status&&t.errorCode&&(r.error={category:t.errorCategory||"system",code:t.errorCode,message:t.errorMessage||"Unknown error"}),r}cancelCloneJob(e){let t=this.activeProcesses.get(e);if(t)return t.kill("SIGTERM"),this.activeProcesses.delete(e),g(this.db,e,{status:"cancelled",completedAt:new Date}),!0;let r=u(this.db,e);return!!r&&"pending"===r.status&&(g(this.db,e,{status:"cancelled",completedAt:new Date}),!0)}}},57440:(e,t,r)=>{r.d(t,{j:()=>a});var o=r(55315),s=r.n(o);function a(e,t){if(!e||""===e.trim()||e.includes("\0"))return!1;let r=e;try{r=decodeURIComponent(e)}catch{r=e}if(r.includes("\0"))return!1;let o=s().resolve(t),a=s().resolve(t,r),i=s().relative(o,a);return!(i.startsWith("..")||s().isAbsolute(i))}},67722:(e,t,r)=>{r.d(t,{Lj:()=>u,a$:()=>c,e9:()=>d,h2:()=>g});var o=r(61282),s=r(21764),a=r(55315),i=r.n(a),n=r(75748),l=r(93346);function c(){let e=process.env.WORKTREE_REPOS;if(e&&e.trim())return e.split(",").map(e=>e.trim()).filter(e=>e.length>0);let t=(0,l.Hb)("CM_ROOT_DIR");return t&&t.trim()?[t.trim()]:[]}async function d(e){let t=(0,s.promisify)(o.exec);try{let{stdout:r}=await t("git worktree list",{cwd:e}),o=function(e){if(!e||""===e.trim())return[];let t=e.trim().split("\n"),r=[];for(let e of t){let t=e.trim();if(!t)continue;let o=t.match(/^(.+?)\s+([a-z0-9]+)\s+(?:\[(.+?)\]|\(detached HEAD\))/);if(o){let[,e,t,s]=o;r.push({path:e.trim(),branch:s||`detached-${t}`,commit:t})}}return r}(r),s=i().resolve(e),a=i().basename(s);return o.map(e=>({id:function(e,t){if(!e)return"";let r=e=>e.toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,""),o=r(e);if(t){let e=r(t);return`${e}-${o}`}return o}(e.branch,a),name:e.branch,path:i().resolve(e.path),repositoryPath:s,repositoryName:a})).filter(e=>["/etc","/root","/sys","/proc","/dev","/boot","/bin","/sbin","/usr/bin","/usr/sbin"].some(t=>e.path.startsWith(t))?(console.warn(`Skipping potentially unsafe worktree path: ${e.path}`),!1):!(e.path.includes("\0")||e.path.includes(".."))||(console.warn(`Skipping path with potentially malicious characters: ${e.path}`),!1))}catch(r){let e=r instanceof Error?r.message:String(r),t=r.code;if(e?.includes("not a git repository")||128===t)return[];throw r}}async function u(e){let t=[];for(let r of e)try{console.log(`Scanning repository: ${r}`);let e=await d(r);t.push(...e),console.log(` Found ${e.length} worktree(s)`)}catch(e){console.error(`Error scanning repository ${r}:`,e)}return t}function g(e,t){if(0===t.length)return;let r=new Map;for(let e of t){let t=e.repositoryPath||"";r.has(t)||r.set(t,[]),r.get(t).push(e)}for(let[t,o]of r){if(!t)continue;let r=(0,n.Pv)(e,t),s=new Set(o.map(e=>e.id)),a=r.filter(e=>!s.has(e));if(a.length>0){let r=(0,n.pM)(e,a);console.log(`Removed ${r.deletedCount} deleted worktree(s) from ${t}`)}for(let t of o)(0,n.ly)(e,t)}}}};
@@ -1 +0,0 @@
1
- "use strict";exports.id=1528,exports.ids=[1528],exports.modules={19377:(t,e,n)=>{n.d(e,{Wg:()=>c,bs:()=>function t(e){switch(e){case"claude":return{promptPattern:o,separatorPattern:a,thinkingPattern:s,skipPatterns:[/^─{10,}$/,/^[>❯]\s*$/,s,/^\s*[⎿⏋]\s+Tip:/,/^\s*Tip:/,/^\s*\?\s*for shortcuts/,/to interrupt\)/]};case"codex":return{promptPattern:p,separatorPattern:l,thinkingPattern:i,skipPatterns:[/^─.*─+$/,/^›\s*$/,/^›\s+(Implement|Find and fix|Type)/,i,/^\s*\d+%\s+context left/,/^\s*for shortcuts$/,/╭─+╮/,/╰─+╯/]};case"gemini":return{promptPattern:u,separatorPattern:/^gemini\s+--\s+/m,thinkingPattern:/(?!)/m,skipPatterns:[/^gemini\s+--\s+/,u,/^\s*$/]};default:return t("claude")}},vp:()=>g});let r=(0,n(43895).h)("cli-patterns"),s=RegExp(`[✻✽⏺\xb7∴✢✳✶⦿◉●○◌◎⊙⊚⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\\s+.+…|to interrupt\\)`,"m"),i=/•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing)/m,o=/^[>❯](\s*$|\s+\S)/m,a=/^─{10,}$/m,p=/^›\s+.+/m,l=/^─.*Worked for.*─+$/m,u=/^(%|\$|.*@.*[%$#])\s*$/m;function c(t,e){let n;let o=r.withContext({cliToolId:t});switch(o.debug("detectThinking:check",{contentLength:e.length}),t){case"claude":default:n=s.test(e);break;case"codex":n=i.test(e);break;case"gemini":n=!1}return o.debug("detectThinking:result",{isThinking:n}),n}let m=/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;function g(t){return t.replace(m,"")}},89194:(t,e,n)=>{n.d(e,{Lg:()=>o,NA:()=>a});var r=n(10927),s=n(97213);let i=(0,n(43895).h)("cli-session");async function o(t,e){let n=s.g.getInstance().getTool(e).getSessionName(t);return await (0,r.Hk)(n)}async function a(t,e,n=1e3){let o=i.withContext({worktreeId:t,cliToolId:e});o.debug("captureSessionOutput:start",{requestedLines:n});let a=s.g.getInstance().getTool(e),p=a.getSessionName(t);if(!await (0,r.Hk)(p))throw o.debug("captureSessionOutput:sessionNotFound",{sessionName:p}),Error(`${a.name} session ${p} does not exist`);try{let t=await (0,r.xq)(p,{startLine:-n}),e=t.split("\n").length;return o.debug("captureSessionOutput:success",{actualLines:e,lastFewLines:t.split("\n").slice(-3).join(" | ")}),t}catch(e){let t=e instanceof Error?e.message:String(e);throw o.error("captureSessionOutput:failed",{error:t}),Error(`Failed to capture ${a.name} output: ${t}`)}}},43895:(t,e,n)=>{n.d(e,{Y:()=>p,h:()=>l});var r=n(93346);let s=[{pattern:/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi,replacement:"Bearer [REDACTED]"},{pattern:/(password|passwd|pwd)[=:]\s*\S+/gi,replacement:"$1=[REDACTED]"},{pattern:/(token|secret|api_key|apikey|auth)[=:]\s*\S+/gi,replacement:"$1=[REDACTED]"},{pattern:/CM_AUTH_TOKEN=\S+/gi,replacement:"CM_AUTH_TOKEN=[REDACTED]"},{pattern:/MCBD_AUTH_TOKEN=\S+/gi,replacement:"MCBD_AUTH_TOKEN=[REDACTED]"},{pattern:/Authorization:\s*\S+/gi,replacement:"Authorization: [REDACTED]"},{pattern:/-----BEGIN\s+\w+\s+PRIVATE\s+KEY-----[\s\S]*?-----END\s+\w+\s+PRIVATE\s+KEY-----/g,replacement:"[SSH_KEY_REDACTED]"}],i=/password|secret|token|key|auth/i,o={debug:0,info:1,warn:2,error:3};function a(t,e,n,a,p){let l=(0,r.LI)().level;if(o[t]<o[l])return;let u=a?function t(e){if("string"==typeof e){let t=e;for(let{pattern:e,replacement:n}of s)t=t.replace(e,n);return t}if("object"==typeof e&&null!==e){if(Array.isArray(e))return e.map(t);let n={};for(let[r,s]of Object.entries(e))i.test(r)?n[r]="[REDACTED]":n[r]=t(s);return n}return e}(a):void 0,c=function(t){if("json"===(0,r.LI)().format)return JSON.stringify(t);let{timestamp:e,level:n,module:s,action:i,data:o,worktreeId:a,cliToolId:p,requestId:l}=t,u=[a,p].filter(Boolean),c=u.length>0?` [${u.join(":")}]`:"",m=l?` (${l.slice(0,8)})`:"",g=o?` ${JSON.stringify(o)}`:"";return`[${e}] [${n.toUpperCase()}] [${s}]${c}${m} ${i}${g}`}({level:t,module:e,action:n,timestamp:new Date().toISOString(),...p,...u&&{data:u}});switch(t){case"error":console.error(c);break;case"warn":console.warn(c);break;default:console.log(c)}}function p(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)})}function l(t){let e=n=>({debug:(e,r)=>a("debug",t,e,r,n),info:(e,r)=>a("info",t,e,r,n),warn:(e,r)=>a("warn",t,e,r,n),error:(e,r)=>a("error",t,e,r,n),withContext:t=>e({...n,...t})});return e()}},63661:(t,e,n)=>{n.d(e,{F:()=>s,J:()=>o});let r=(0,n(43895).h)("prompt-detector");function s(t){r.debug("detectPrompt:start",{outputLength:t.length});let e=t.split("\n").slice(-10).join("\n"),n=function(t){let e=t.split("\n"),n=/^\s*([❯ ]\s*)?(\d+)\.\s*(.+)$/,r=[],s=-1,o=-1;for(let t=e.length-1;t>=0&&t>=e.length-50;t--){let i=e[t].trim(),a=e[t],p=i.match(n);if(p){let e=!!(p[1]&&p[1].includes("❯")),n=parseInt(p[2],10),s=p[3].trim();r.unshift({number:n,label:s,isDefault:e}),-1===o&&(o=t)}else if(r.length>0&&i&&!i.match(/^[-─]+$/)){let e=a.match(/^\s{2,}[^\d]/)&&!a.match(/^\s*\d+\./),n=i.length<5&&!i.endsWith("?");if(e||n)continue;s=t;break}}let a=r.some(t=>t.isDefault);if(r.length<2||!a)return{isPrompt:!1,cleanContent:t.trim()};let p="";if(s>=0){let t=[];for(let n=Math.max(0,s-5);n<=s;n++){let r=e[n].trim();r&&!r.match(/^[-─]+$/)&&t.push(r)}p=t.join(" ")}else p="Please select an option:";return{isPrompt:!0,promptData:{type:"multiple_choice",question:p.trim(),options:r.map(t=>{let e=i.some(e=>e.test(t.label));return{number:t.number,label:t.label,isDefault:t.isDefault,requiresTextInput:e}}),status:"pending"},cleanContent:p.trim()}}(t);if(n.isPrompt)return r.info("detectPrompt:multipleChoice",{isPrompt:!0,question:n.promptData?.question,optionsCount:n.promptData?.options?.length}),n;let s=e.match(/^(.+)\s+\(y\/n\)\s*$/m);if(s)return{isPrompt:!0,promptData:{type:"yes_no",question:s[1].trim(),options:["yes","no"],status:"pending"},cleanContent:s[1].trim()};let o=e.match(/^(.+)\s+\[y\/N\]\s*$/m);if(o)return{isPrompt:!0,promptData:{type:"yes_no",question:o[1].trim(),options:["yes","no"],status:"pending",defaultOption:"no"},cleanContent:o[1].trim()};let a=e.match(/^(.+)\s+\[Y\/n\]\s*$/m);if(a)return{isPrompt:!0,promptData:{type:"yes_no",question:a[1].trim(),options:["yes","no"],status:"pending",defaultOption:"yes"},cleanContent:a[1].trim()};let p=e.match(/^(.+)\s+\(yes\/no\)\s*$/m);if(p)return{isPrompt:!0,promptData:{type:"yes_no",question:p[1].trim(),options:["yes","no"],status:"pending"},cleanContent:p[1].trim()};let l=e.match(/^(.*?)Approve\?\s*$/m);if(l){let t=l[1].trim();return{isPrompt:!0,promptData:{type:"yes_no",question:t?`${t} Approve?`:"Approve?",options:["yes","no"],status:"pending"},cleanContent:t||"Approve?"}}return r.debug("detectPrompt:complete",{isPrompt:!1}),{isPrompt:!1,cleanContent:t.trim()}}let i=[/type\s+here/i,/tell\s+(me|claude)/i,/enter\s+/i,/custom/i,/differently/i];function o(t,e="yes_no"){let n=t.toLowerCase().trim();if("multiple_choice"===e){if(/^\d+$/.test(n))return n;throw Error(`Invalid answer for multiple choice: ${t}. Expected a number.`)}if("yes"===n||"y"===n)return"y";if("no"===n||"n"===n)return"n";throw Error(`Invalid answer: ${t}. Expected 'yes', 'no', 'y', or 'n'.`)}}};
@@ -1 +0,0 @@
1
- "use strict";exports.id=7213,exports.ids=[7213],exports.modules={62648:(t,e,s)=>{s.d(e,{Lm:()=>w,Uv:()=>c,YI:()=>m,_f:()=>h,xd:()=>d,ym:()=>u});var i=s(10927),o=s(61282);let n=(0,s(21764).promisify)(o.exec),a=null;async function r(){if(a)return a;if(process.env.CLAUDE_PATH)return a=process.env.CLAUDE_PATH;try{let{stdout:t}=await n("which claude",{timeout:5e3});return a=t.trim()}catch{for(let t of["/opt/homebrew/bin/claude","/usr/local/bin/claude","/usr/bin/claude"])try{return await n(`test -x "${t}"`,{timeout:1e3}),a=t}catch{}throw Error("Claude CLI not found. Set CLAUDE_PATH environment variable or install Claude CLI.")}}function l(t){return`mcbd-claude-${t}`}async function c(){try{return await n("which claude",{timeout:5e3}),!0}catch{return!1}}async function m(t){let e=l(t);return await (0,i.Hk)(e)}async function u(t){let{worktreeId:e,worktreePath:s}=t;if(!await c())throw Error("Claude CLI is not installed or not in PATH");let o=l(e);if(await (0,i.Hk)(o)){console.log(`Claude session ${o} already exists`);return}try{await (0,i.ed)({sessionName:o,workingDirectory:s,historyLimit:5e4});let t=await r();await (0,i.Is)(o,t,!0);let e=Date.now();for(;Date.now()-e<1e4;){await new Promise(t=>setTimeout(t,500));try{let t=await (0,i.xq)(o,{startLine:-50});if(/^>\s*$/m.test(t)||/^─{10,}$/m.test(t)){console.log(`✓ Claude initialized in ${Date.now()-e}ms`);break}}catch{}}console.log(`✓ Started Claude session: ${o}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Claude session: ${t}`)}}async function d(t,e){let s=l(t);if(!await (0,i.Hk)(s))throw Error(`Claude session ${s} does not exist. Start the session first.`);try{await (0,i.Is)(s,e,!1),await new Promise(t=>setTimeout(t,100)),await n(`tmux send-keys -t "${s}" C-m`),await new Promise(t=>setTimeout(t,200)),console.log(`✓ Sent message to Claude session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Claude: ${t}`)}}async function w(t,e=1e3){let s=l(t);if(!await (0,i.Hk)(s))throw Error(`Claude session ${s} does not exist`);try{return await (0,i.xq)(s,{startLine:-e})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to capture Claude output: ${t}`)}}async function h(t){let e=l(t);try{await (0,i.Hk)(e)&&(await (0,i.Is)(e,"",!1),await n(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500)));let t=await (0,i.AJ)(e);return t&&console.log(`✓ Stopped Claude session: ${e}`),t}catch(e){let t=e instanceof Error?e.message:String(e);return console.error(`Error stopping Claude session: ${t}`),!1}}},97213:(t,e,s)=>{s.d(e,{g:()=>h});var i=s(61282),o=s(21764),n=s(10927);let a=(0,o.promisify)(i.exec);class r{async isInstalled(){try{return await a(`which ${this.command}`,{timeout:5e3}),!0}catch{return!1}}getSessionName(t){return`mcbd-${this.id}-${t}`}async interrupt(t){let e=this.getSessionName(t);await (0,n.ZV)(e,"Escape")}}var l=s(62648);class c extends r{async isInstalled(){return await (0,l.Uv)()}async isRunning(t){return await (0,l.YI)(t)}async startSession(t,e){await (0,l.ym)({worktreeId:t,worktreePath:e})}async sendMessage(t,e){await (0,l.xd)(t,e)}async killSession(t){await (0,l._f)(t)}constructor(...t){super(...t),this.id="claude",this.name="Claude Code",this.command="claude"}}let m=(0,o.promisify)(i.exec);class u extends r{async isRunning(t){let e=this.getSessionName(t);return await (0,n.Hk)(e)}async startSession(t,e){if(!await this.isInstalled())throw Error("Codex CLI is not installed or not in PATH");let s=this.getSessionName(t);if(await (0,n.Hk)(s)){console.log(`Codex session ${s} already exists`);return}try{await (0,n.ed)({sessionName:s,workingDirectory:e,historyLimit:5e4}),await new Promise(t=>setTimeout(t,100)),await (0,n.Is)(s,"codex",!0),await new Promise(t=>setTimeout(t,3e3)),await (0,n.Is)(s,"2",!0),await new Promise(t=>setTimeout(t,500)),console.log(`✓ Started Codex session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Codex session: ${t}`)}}async sendMessage(t,e){let s=this.getSessionName(t);if(!await (0,n.Hk)(s))throw Error(`Codex session ${s} does not exist. Start the session first.`);try{await (0,n.Is)(s,e,!1),await new Promise(t=>setTimeout(t,100)),await m(`tmux send-keys -t "${s}" C-m`),await new Promise(t=>setTimeout(t,200)),console.log(`✓ Sent message to Codex session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Codex: ${t}`)}}async killSession(t){let e=this.getSessionName(t);try{await (0,n.Hk)(e)&&(await m(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500))),await (0,n.AJ)(e)&&console.log(`✓ Stopped Codex session: ${e}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw console.error(`Error stopping Codex session: ${t}`),e}}constructor(...t){super(...t),this.id="codex",this.name="Codex CLI",this.command="codex"}}let d=(0,o.promisify)(i.exec);class w extends r{async isRunning(t){let e=this.getSessionName(t);return await (0,n.Hk)(e)}async startSession(t,e){if(!await this.isInstalled())throw Error("Gemini CLI is not installed or not in PATH");let s=this.getSessionName(t);if(await (0,n.Hk)(s)){console.log(`Gemini session ${s} already exists`);return}try{await (0,n.ed)({sessionName:s,workingDirectory:e,historyLimit:5e4}),console.log(`✓ Started Gemini session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Gemini session: ${t}`)}}async sendMessage(t,e){let s=this.getSessionName(t);if(!await (0,n.Hk)(s))throw Error(`Gemini session ${s} does not exist. Start the session first.`);try{let t=e.replace(/'/g,"'\\''");await (0,n.Is)(s,`echo '${t}' | gemini`,!0),console.log(`✓ Sent message to Gemini session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Gemini: ${t}`)}}async killSession(t){let e=this.getSessionName(t);try{await (0,n.Hk)(e)&&(await d(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500))),await (0,n.AJ)(e)&&console.log(`✓ Stopped Gemini session: ${e}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw console.error(`Error stopping Gemini session: ${t}`),e}}constructor(...t){super(...t),this.id="gemini",this.name="Gemini CLI",this.command="gemini"}}class h{constructor(){this.tools=new Map,this.tools.set("claude",new c),this.tools.set("codex",new u),this.tools.set("gemini",new w)}static getInstance(){return h.instance||(h.instance=new h),h.instance}getTool(t){let e=this.tools.get(t);if(!e)throw Error(`CLI tool '${t}' not found`);return e}getAllTools(){return Array.from(this.tools.values())}async getToolInfo(t){let e=this.getTool(t),s=await e.isInstalled();return{id:e.id,name:e.name,command:e.command,installed:s}}async getAllToolsInfo(){return Promise.all(this.getAllTools().map(async t=>{let e=await t.isInstalled();return{id:t.id,name:t.name,command:t.command,installed:e}}))}async getInstalledTools(){return(await this.getAllToolsInfo()).filter(t=>t.installed)}}},10927:(t,e,s)=>{s.d(e,{AJ:()=>c,Hk:()=>n,Is:()=>r,ZV:()=>m,ed:()=>a,xq:()=>l});var i=s(61282);let o=(0,s(21764).promisify)(i.exec);async function n(t){try{return await o(`tmux has-session -t "${t}"`,{timeout:5e3}),!0}catch{return!1}}async function a(t,e){let s,i,n;"string"==typeof t?(s=t,i=e,n=5e4):(s=t.sessionName,i=t.workingDirectory,n=t.historyLimit||5e4);try{await o(`tmux new-session -d -s "${s}" -c "${i}"`,{timeout:5e3}),await o(`tmux set-option -t "${s}" history-limit ${n}`,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to create tmux session: ${t}`)}}async function r(t,e,s=!0){let i=e.replace(/'/g,"'\\''"),n=s?`tmux send-keys -t "${t}" '${i}' C-m`:`tmux send-keys -t "${t}" '${i}'`;try{await o(n,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send keys to tmux session: ${t}`)}}async function l(t,e){let s,i;"number"==typeof e?(s=-e,i="-"):e?(s=e.startLine??-1e4,i=e.endLine??"-"):(s=-1e3,i="-");try{let{stdout:e}=await o(`tmux capture-pane -t "${t}" -p -e -S ${s} -E ${i}`,{timeout:5e3,maxBuffer:10485760});return e}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to capture pane: ${t}`)}}async function c(t){try{return await o(`tmux kill-session -t "${t}"`,{timeout:5e3}),!0}catch(e){let t=e instanceof Error?e.message:String(e);if(t?.includes("no server running")||t?.includes("can't find session"))return!1;throw Error(`Failed to kill tmux session: ${t}`)}}async function m(t,e){try{await o(`tmux send-keys -t "${t}" ${e}`,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send special key: ${t}`)}}}};