commandmate 0.2.0 → 0.2.1

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 (62) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +9 -9
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/config.json +3 -3
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/1.pack +0 -0
  9. package/.next/cache/webpack/client-production/2.pack +0 -0
  10. package/.next/cache/webpack/client-production/index.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  13. package/.next/cache/webpack/server-production/0.pack +0 -0
  14. package/.next/cache/webpack/server-production/index.pack +0 -0
  15. package/.next/next-server.js.nft.json +1 -1
  16. package/.next/prerender-manifest.json +1 -1
  17. package/.next/required-server-files.json +1 -1
  18. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/server/app/_not-found.html +1 -1
  20. package/.next/server/app/_not-found.rsc +1 -1
  21. package/.next/server/app/api/repositories/excluded/route.js +8 -8
  22. package/.next/server/app/api/repositories/restore/route.js +7 -7
  23. package/.next/server/app/api/repositories/route.js +5 -5
  24. package/.next/server/app/api/repositories/sync/route.js +5 -5
  25. package/.next/server/app/api/slash-commands.body +1 -1
  26. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  27. package/.next/server/app/api/worktrees/[id]/capture/route.js +2 -2
  28. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  29. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  30. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  31. package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
  32. package/.next/server/app/api/worktrees/route.js +1 -1
  33. package/.next/server/app/index.html +1 -1
  34. package/.next/server/app/index.rsc +1 -1
  35. package/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  37. package/.next/server/app/worktrees/[id]/page.js +1 -1
  38. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
  40. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  41. package/.next/server/app-paths-manifest.json +7 -7
  42. package/.next/server/chunks/5488.js +5 -5
  43. package/.next/server/chunks/7536.js +1 -1
  44. package/.next/server/chunks/9367.js +2 -2
  45. package/.next/server/functions-config-manifest.json +1 -1
  46. package/.next/server/pages/404.html +1 -1
  47. package/.next/server/pages/500.html +1 -1
  48. package/.next/server/server-reference-manifest.json +1 -1
  49. package/.next/static/chunks/app/worktrees/[id]/{page-d64624eb67af57c0.js → page-8bd88bdc29607413.js} +1 -1
  50. package/.next/trace +5 -5
  51. package/dist/server/server.js +25 -2
  52. package/dist/server/src/lib/auto-yes-manager.js +88 -7
  53. package/dist/server/src/lib/claude-poller.js +4 -0
  54. package/dist/server/src/lib/claude-session.js +48 -19
  55. package/dist/server/src/lib/cli-patterns.js +60 -4
  56. package/dist/server/src/lib/db-repository.js +482 -0
  57. package/dist/server/src/lib/prompt-detector.js +199 -109
  58. package/dist/server/src/lib/response-poller.js +73 -27
  59. package/dist/server/src/lib/tmux.js +48 -0
  60. package/package.json +1 -1
  61. /package/.next/static/{bdUePCj-b9Gv5okYGp49O → oUD-A998xeBoez6zsrTH3}/_buildManifest.js +0 -0
  62. /package/.next/static/{bdUePCj-b9Gv5okYGp49O → oUD-A998xeBoez6zsrTH3}/_ssgManifest.js +0 -0
@@ -37,6 +37,8 @@ const response_poller_1 = require("./src/lib/response-poller");
37
37
  const auto_yes_manager_1 = require("./src/lib/auto-yes-manager");
38
38
  const db_migrations_1 = require("./src/lib/db-migrations");
39
39
  const env_1 = require("./src/lib/env");
40
+ const db_repository_1 = require("./src/lib/db-repository");
41
+ const db_1 = require("./src/lib/db");
40
42
  const dev = process.env.NODE_ENV !== 'production';
41
43
  const hostname = (0, env_1.getEnvByKey)('CM_BIND') || '127.0.0.1';
42
44
  const port = parseInt((0, env_1.getEnvByKey)('CM_PORT') || '3000', 10);
@@ -76,8 +78,29 @@ app.prepare().then(() => {
76
78
  repositoryPaths.forEach((path, i) => {
77
79
  console.log(` ${i + 1}. ${path}`);
78
80
  });
79
- // Scan all repositories
80
- const worktrees = await (0, worktrees_1.scanMultipleRepositories)(repositoryPaths);
81
+ // Issue #202: Register environment variable repositories and filter out excluded ones
82
+ // registerAndFilterRepositories() encapsulates the ordering constraint:
83
+ // registration MUST happen before filtering (see design policy Section 4)
84
+ const { filteredPaths, excludedPaths, excludedCount } = (0, db_repository_1.registerAndFilterRepositories)(db, repositoryPaths);
85
+ if (excludedCount > 0) {
86
+ console.log(`Excluded repositories: ${excludedCount}, Active repositories: ${filteredPaths.length}`);
87
+ // SF-SEC-003: Log excluded repository paths for audit/troubleshooting
88
+ excludedPaths.forEach(p => {
89
+ console.log(` [excluded] ${p}`);
90
+ });
91
+ // Issue #202: Remove worktrees of excluded repositories from DB
92
+ // Without this, worktree records remain in DB and appear in the UI
93
+ for (const excludedPath of excludedPaths) {
94
+ const resolvedPath = (0, db_repository_1.resolveRepositoryPath)(excludedPath);
95
+ const worktreeIds = (0, db_1.getWorktreeIdsByRepository)(db, resolvedPath);
96
+ if (worktreeIds.length > 0) {
97
+ const result = (0, db_1.deleteWorktreesByIds)(db, worktreeIds);
98
+ console.log(` Removed ${result.deletedCount} worktree(s) from excluded repository: ${resolvedPath}`);
99
+ }
100
+ }
101
+ }
102
+ // Scan filtered repositories (excluded repos are skipped)
103
+ const worktrees = await (0, worktrees_1.scanMultipleRepositories)(filteredPaths);
81
104
  // Sync to database
82
105
  (0, worktrees_1.syncWorktreesToDB)(db, worktrees);
83
106
  console.log(`✓ Total: ${worktrees.length} worktree(s) synced to database`);
@@ -9,7 +9,7 @@
9
9
  * auto-yes responses when browser tabs are in background.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.POLLING_INTERVAL_MS = void 0;
12
+ exports.THINKING_CHECK_LINE_COUNT = exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.POLLING_INTERVAL_MS = void 0;
13
13
  exports.isValidWorktreeId = isValidWorktreeId;
14
14
  exports.calculateBackoffInterval = calculateBackoffInterval;
15
15
  exports.isAutoYesExpired = isAutoYesExpired;
@@ -41,6 +41,16 @@ exports.MAX_CONSECUTIVE_ERRORS = 5;
41
41
  exports.MAX_CONCURRENT_POLLERS = 50;
42
42
  /** Timeout duration: 1 hour in milliseconds */
43
43
  const AUTO_YES_TIMEOUT_MS = 3600000;
44
+ /**
45
+ * Number of lines from the end to check for thinking indicators (Issue #191)
46
+ * Matches detectPrompt()'s multiple_choice scan range (50 lines in prompt-detector.ts)
47
+ * to ensure Issue #161 Layer 1 defense covers the same scope as prompt detection.
48
+ *
49
+ * IMPORTANT: This value is semantically coupled to the hardcoded 50 in
50
+ * prompt-detector.ts detectMultipleChoicePrompt() (L268: Math.max(0, lines.length - 50)).
51
+ * See SF-001 in Stage 1 review. A cross-reference test validates this coupling.
52
+ */
53
+ exports.THINKING_CHECK_LINE_COUNT = 50;
44
54
  /** Worktree ID validation pattern (security: prevent command injection) */
45
55
  const WORKTREE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
46
56
  /** In-memory storage for auto-yes states (globalThis for hot reload persistence) */
@@ -210,12 +220,29 @@ async function pollAutoYes(worktreeId, cliToolId) {
210
220
  // 2.5. Skip prompt detection during thinking state (Issue #161, Layer 1)
211
221
  // This prevents false positive detection of numbered lists in CLI output
212
222
  // while Claude is actively processing (thinking/planning).
213
- if ((0, cli_patterns_1.detectThinking)(cliToolId, cleanOutput)) {
223
+ //
224
+ // Issue #191: Apply windowing to detectThinking() to prevent stale thinking
225
+ // summary lines (e.g., "· Simmering…") from blocking prompt detection.
226
+ // Window size matches detectPrompt()'s multiple_choice scan range (50 lines).
227
+ //
228
+ // Safety: Claude CLI does not emit prompts during thinking, so narrowing
229
+ // the window cannot cause false auto-responses (see IA-003 in design doc).
230
+ //
231
+ // Processing order: stripAnsi -> split -> slice -> join
232
+ // stripAnsi is applied BEFORE split to ensure ANSI escape sequences spanning
233
+ // line boundaries do not affect line counting (IA-002).
234
+ //
235
+ // Boundary case: if buffer has fewer than 50 lines, slice(-50) returns the
236
+ // entire array (Array.prototype.slice specification), which is safe degradation
237
+ // equivalent to pre-fix behavior (IA-001).
238
+ const recentLines = cleanOutput.split('\n').slice(-exports.THINKING_CHECK_LINE_COUNT).join('\n');
239
+ if ((0, cli_patterns_1.detectThinking)(cliToolId, recentLines)) {
214
240
  scheduleNextPoll(worktreeId, cliToolId);
215
241
  return;
216
242
  }
217
243
  // 3. Detect prompt
218
- const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput);
244
+ const promptOptions = (0, cli_patterns_1.buildDetectPromptOptions)(cliToolId);
245
+ const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput, promptOptions);
219
246
  if (!promptDetection.isPrompt || !promptDetection.promptData) {
220
247
  // No prompt detected, schedule next poll
221
248
  scheduleNextPoll(worktreeId, cliToolId);
@@ -232,10 +259,64 @@ async function pollAutoYes(worktreeId, cliToolId) {
232
259
  const manager = manager_1.CLIToolManager.getInstance();
233
260
  const cliTool = manager.getTool(cliToolId);
234
261
  const sessionName = cliTool.getSessionName(worktreeId);
235
- // Send answer followed by Enter
236
- await (0, tmux_1.sendKeys)(sessionName, answer, false);
237
- await new Promise(resolve => setTimeout(resolve, 100));
238
- await (0, tmux_1.sendKeys)(sessionName, '', true);
262
+ // Issue #193: Claude Code AskUserQuestion uses cursor-based navigation
263
+ // (Arrow/Space/Enter), not number input. Detect multi-choice and send
264
+ // appropriate key sequence instead of typing the number.
265
+ const isClaudeMultiChoice = cliToolId === 'claude'
266
+ && promptDetection.promptData?.type === 'multiple_choice'
267
+ && /^\d+$/.test(answer);
268
+ if (isClaudeMultiChoice && promptDetection.promptData?.type === 'multiple_choice') {
269
+ const targetNum = parseInt(answer, 10);
270
+ const mcOptions = promptDetection.promptData.options;
271
+ const defaultOption = mcOptions.find(o => o.isDefault);
272
+ const defaultNum = defaultOption?.number ?? 1;
273
+ const offset = targetNum - defaultNum;
274
+ // Detect multi-select (checkbox) prompts by checking for [ ] in option labels.
275
+ const isMultiSelect = mcOptions.some(o => /^\[[ x]\] /.test(o.label));
276
+ if (isMultiSelect) {
277
+ // Multi-select: toggle checkbox, then navigate to "Next" and submit
278
+ const checkboxCount = mcOptions.filter(o => /^\[[ x]\] /.test(o.label)).length;
279
+ const keys = [];
280
+ // 1. Navigate to target option
281
+ if (offset > 0) {
282
+ for (let i = 0; i < offset; i++)
283
+ keys.push('Down');
284
+ }
285
+ else if (offset < 0) {
286
+ for (let i = 0; i < Math.abs(offset); i++)
287
+ keys.push('Up');
288
+ }
289
+ // 2. Space to toggle checkbox
290
+ keys.push('Space');
291
+ // 3. Navigate to "Next" button (positioned right after all checkbox options)
292
+ const downToNext = checkboxCount - targetNum + 1;
293
+ for (let i = 0; i < downToNext; i++)
294
+ keys.push('Down');
295
+ // 4. Enter to submit
296
+ keys.push('Enter');
297
+ await (0, tmux_1.sendSpecialKeys)(sessionName, keys);
298
+ }
299
+ else {
300
+ // Single-select: navigate and Enter to select
301
+ const keys = [];
302
+ if (offset > 0) {
303
+ for (let i = 0; i < offset; i++)
304
+ keys.push('Down');
305
+ }
306
+ else if (offset < 0) {
307
+ for (let i = 0; i < Math.abs(offset); i++)
308
+ keys.push('Up');
309
+ }
310
+ keys.push('Enter');
311
+ await (0, tmux_1.sendSpecialKeys)(sessionName, keys);
312
+ }
313
+ }
314
+ else {
315
+ // Standard CLI prompt: send text + Enter (y/n, Approve?, etc.)
316
+ await (0, tmux_1.sendKeys)(sessionName, answer, false);
317
+ await new Promise(resolve => setTimeout(resolve, 100));
318
+ await (0, tmux_1.sendKeys)(sessionName, '', true);
319
+ }
239
320
  // 6. Update timestamp
240
321
  updateLastServerResponseTimestamp(worktreeId, Date.now());
241
322
  // 7. Reset error count on success
@@ -138,6 +138,8 @@ function extractClaudeResponse(output, lastCapturedLine) {
138
138
  }
139
139
  // Check if this is an interactive prompt (yes/no or multiple choice)
140
140
  // Interactive prompts don't have the ">" prompt and separator, so we need to detect them separately
141
+ // TODO [Issue #193]: This code path is unreachable (claude-poller.ts is superseded by response-poller.ts).
142
+ // When refactoring/removing claude-poller.ts, apply stripAnsi() + buildDetectPromptOptions() here.
141
143
  if (!isThinking) {
142
144
  const fullOutput = lines.join('\n');
143
145
  const promptDetection = (0, prompt_detector_1.detectPrompt)(fullOutput);
@@ -196,6 +198,8 @@ async function checkForResponse(worktreeId) {
196
198
  return false;
197
199
  }
198
200
  // Response is complete! Check if it's a prompt
201
+ // TODO [Issue #193]: This code path is unreachable (claude-poller.ts is superseded by response-poller.ts).
202
+ // When refactoring/removing claude-poller.ts, apply stripAnsi() + buildDetectPromptOptions() here.
199
203
  const promptDetection = (0, prompt_detector_1.detectPrompt)(result.response);
200
204
  if (promptDetection.isPrompt) {
201
205
  // This is a prompt - save as prompt message
@@ -4,7 +4,7 @@
4
4
  * Manages Claude CLI sessions within tmux for each worktree
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
7
+ exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
8
8
  exports.getSessionName = getSessionName;
9
9
  exports.isClaudeInstalled = isClaudeInstalled;
10
10
  exports.isClaudeRunning = isClaudeRunning;
@@ -42,6 +42,9 @@ function getErrorMessage(error) {
42
42
  * - Authenticate with Anthropic servers (if needed)
43
43
  * - Display the interactive prompt
44
44
  *
45
+ * This timeout also covers trust dialog auto-response time (typically <1s).
46
+ * When reducing this value, consider dialog response overhead.
47
+ *
45
48
  * 15 seconds provides headroom for slower networks or cold starts.
46
49
  */
47
50
  exports.CLAUDE_INIT_TIMEOUT = 15000;
@@ -75,6 +78,24 @@ exports.CLAUDE_POST_PROMPT_DELAY = 500;
75
78
  * if it's still processing a previous request.
76
79
  */
77
80
  exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
81
+ /**
82
+ * Prompt wait timeout before message send (milliseconds).
83
+ *
84
+ * Used exclusively by sendMessageToClaude() to limit how long we wait
85
+ * for Claude to return to a prompt state before sending a user message.
86
+ * This is separate from CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms, the default
87
+ * for waitForPrompt()) because sendMessageToClaude() may be called
88
+ * shortly after session initialization, where Claude CLI needs additional
89
+ * time to become ready.
90
+ *
91
+ * Relationship to other timeout constants:
92
+ * - CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms): Default for waitForPrompt()
93
+ * - CLAUDE_SEND_PROMPT_WAIT_TIMEOUT (10000ms): sendMessageToClaude() specific
94
+ * - CLAUDE_INIT_TIMEOUT (15000ms): Session initialization timeout
95
+ *
96
+ * @see Issue #187 - Constant unification for sendMessageToClaude timeout
97
+ */
98
+ exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = 10000;
78
99
  /**
79
100
  * Prompt wait polling interval (milliseconds)
80
101
  *
@@ -227,7 +248,6 @@ async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_T
227
248
  * await startClaudeSession({
228
249
  * worktreeId: 'feature-foo',
229
250
  * worktreePath: '/path/to/worktree',
230
- * baseUrl: 'http://localhost:3000',
231
251
  * });
232
252
  * ```
233
253
  */
@@ -262,21 +282,33 @@ async function startClaudeSession(options) {
262
282
  const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
263
283
  const startTime = Date.now();
264
284
  let initialized = false;
285
+ let trustDialogHandled = false;
265
286
  while (Date.now() - startTime < maxWaitTime) {
266
287
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
267
288
  try {
268
289
  const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
269
- // Claude is ready when we see the prompt or separator line (DRY-001, DRY-002)
270
- // Use patterns from cli-patterns.ts for consistency
290
+ // Claude is ready when we see the prompt (DRY-001)
291
+ // Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts for consistency
271
292
  // Strip ANSI escape sequences before pattern matching (Issue #152)
293
+ // Note: CLAUDE_SEPARATOR_PATTERN was removed from initialization check (Issue #187, P1-1)
294
+ // because separator early-detection caused premature returns before prompt was ready
272
295
  const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
273
- if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput) || cli_patterns_1.CLAUDE_SEPARATOR_PATTERN.test(cleanOutput)) {
296
+ if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput)) {
274
297
  // Wait for stability after prompt detection (CONS-007, DOC-001)
275
298
  await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
276
299
  console.log(`Claude initialized in ${Date.now() - startTime}ms`);
277
300
  initialized = true;
278
301
  break;
279
302
  }
303
+ // Issue #201: Detect trust dialog and auto-respond with Enter
304
+ // Condition order: CLAUDE_PROMPT_PATTERN (above) is checked first for shortest path
305
+ if (!trustDialogHandled && cli_patterns_1.CLAUDE_TRUST_DIALOG_PATTERN.test(cleanOutput)) {
306
+ await (0, tmux_1.sendKeys)(sessionName, '', true);
307
+ trustDialogHandled = true;
308
+ // TODO: Log output method unification (console.log vs createLogger) to be addressed in a separate Issue (SF-002)
309
+ console.log('Trust dialog detected, sending Enter to confirm');
310
+ // Continue polling to wait for prompt detection
311
+ }
280
312
  }
281
313
  catch {
282
314
  // Ignore capture errors during initialization
@@ -312,23 +344,21 @@ async function sendMessageToClaude(worktreeId, message) {
312
344
  throw new Error(`Claude session ${sessionName} does not exist. Start the session first.`);
313
345
  }
314
346
  // Verify prompt state before sending (CONS-006, DRY-001)
315
- // Use -50 lines to ensure we capture the prompt even with status bars
316
347
  const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
317
- // Strip ANSI escape sequences before pattern matching (Issue #152)
318
348
  if (!cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
319
- // Wait for prompt if not at prompt state
320
- // Use longer timeout (10s) to handle slow responses
321
- try {
322
- await waitForPrompt(sessionName, 10000);
323
- }
324
- catch {
325
- // Log warning but don't block - Claude might be in a special state
326
- console.warn(`[sendMessageToClaude] Prompt not detected, sending anyway`);
327
- }
349
+ // Path B: Prompt not detected - wait for it (P1: throw on timeout)
350
+ await waitForPrompt(sessionName, exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT);
328
351
  }
352
+ // P0: Stability delay after prompt detection (both Path A and Path B)
353
+ // Same delay as startClaudeSession() to ensure Claude CLI input handler is ready
354
+ // NOTE: This 500ms delay also applies to 2nd+ messages. Currently acceptable since
355
+ // Claude CLI response time (seconds to tens of seconds) dwarfs this overhead.
356
+ // If future batch-send use cases arise, this could be optimized to first-message-only,
357
+ // but that optimization is deferred per YAGNI principle. (ref: F-3)
358
+ await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
329
359
  // Send message using sendKeys consistently (CONS-001)
330
- await (0, tmux_1.sendKeys)(sessionName, message, false); // Message without Enter
331
- await (0, tmux_1.sendKeys)(sessionName, '', true); // Enter key
360
+ await (0, tmux_1.sendKeys)(sessionName, message, false);
361
+ await (0, tmux_1.sendKeys)(sessionName, '', true);
332
362
  console.log(`Sent message to Claude session: ${sessionName}`);
333
363
  }
334
364
  /**
@@ -403,7 +433,6 @@ async function stopClaudeSession(worktreeId) {
403
433
  * await restartClaudeSession({
404
434
  * worktreeId: 'feature-foo',
405
435
  * worktreePath: '/path/to/worktree',
406
- * baseUrl: 'http://localhost:3000',
407
436
  * });
408
437
  * ```
409
438
  */
@@ -4,10 +4,11 @@
4
4
  * Shared between response-poller.ts and API routes
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.GEMINI_PROMPT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
7
+ exports.GEMINI_PROMPT_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
8
8
  exports.detectThinking = detectThinking;
9
9
  exports.getCliToolPatterns = getCliToolPatterns;
10
10
  exports.stripAnsi = stripAnsi;
11
+ exports.buildDetectPromptOptions = buildDetectPromptOptions;
11
12
  const logger_1 = require("./logger");
12
13
  const logger = (0, logger_1.createLogger)('cli-patterns');
13
14
  /**
@@ -23,8 +24,14 @@ exports.CLAUDE_SPINNER_CHARS = [
23
24
  * Claude thinking pattern
24
25
  * Matches spinner character followed by activity text ending with …
25
26
  * The text can contain spaces (e.g., "Verifying implementation (dead code detection)…")
27
+ *
28
+ * Alternative 2: "esc to interrupt" status bar text (Issue #XXX)
29
+ * Claude Code shows "esc to interrupt" in the terminal status bar during active processing.
30
+ * Previous pattern required closing paren `to interrupt\)` matching `(esc to interrupt)`,
31
+ * but Claude Code v2.x status bar format uses `· esc to interrupt ·` without parens.
32
+ * Updated to match `esc to interrupt` which covers both formats.
26
33
  */
27
- exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.join('')}]\\s+.+…|to interrupt\\)`, 'm');
34
+ exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.join('')}]\\s+.+…|esc to interrupt`, 'm');
28
35
  /**
29
36
  * Codex thinking pattern
30
37
  * Matches activity indicators like "• Planning", "• Searching", etc.
@@ -45,6 +52,19 @@ exports.CLAUDE_PROMPT_PATTERN = /^[>❯](\s*$|\s+\S)/m;
45
52
  * Claude separator pattern
46
53
  */
47
54
  exports.CLAUDE_SEPARATOR_PATTERN = /^─{10,}$/m;
55
+ /**
56
+ * Claude trust dialog pattern (Issue #201)
57
+ *
58
+ * Matches the "Quick safety check" dialog displayed by Claude CLI v2.x
59
+ * when accessing a workspace for the first time.
60
+ *
61
+ * Intentionally uses partial matching (no line-start anchor ^):
62
+ * Other pattern constants (CLAUDE_PROMPT_PATTERN, CLAUDE_SEPARATOR_PATTERN, etc.)
63
+ * use line-start anchors (^), but this pattern needs to match at any position
64
+ * within the tmux output buffer because the dialog text may appear after
65
+ * tmux padding or other output. (SF-001)
66
+ */
67
+ exports.CLAUDE_TRUST_DIALOG_PATTERN = /Yes, I trust this folder/m;
48
68
  /**
49
69
  * Codex prompt pattern
50
70
  * T1.2: Improved to detect empty prompts as well
@@ -140,10 +160,46 @@ function getCliToolPatterns(cliToolId) {
140
160
  }
141
161
  }
142
162
  /**
143
- * Strip ANSI escape codes from a string
144
- * Optimized version at module level for performance
163
+ * Strip ANSI escape codes from a string.
164
+ * Optimized version at module level for performance.
165
+ *
166
+ * Covers:
167
+ * - SGR sequences: ESC[Nm (colors, bold, underline, etc.)
168
+ * - OSC sequences: ESC]...BEL (window title, hyperlinks, etc.)
169
+ * - CSI sequences: ESC[...letter (cursor movement, erase, etc.)
170
+ *
171
+ * Known limitations (SEC-002):
172
+ * - 8-bit CSI (0x9B): C1 control code form of CSI is not covered
173
+ * - DEC private modes: ESC[?25h and similar are not covered
174
+ * - Character set switching: ESC(0, ESC(B are not covered
175
+ * - Some RGB color forms: ESC[38;2;r;g;bm may not be fully matched
176
+ *
177
+ * In practice, tmux capture-pane output rarely contains these sequences,
178
+ * so the risk is low. Future consideration: adopt the `strip-ansi` npm package
179
+ * for more comprehensive coverage.
145
180
  */
146
181
  const ANSI_PATTERN = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;
147
182
  function stripAnsi(str) {
148
183
  return str.replace(ANSI_PATTERN, '');
149
184
  }
185
+ /**
186
+ * Build DetectPromptOptions for a given CLI tool.
187
+ * Centralizes cliToolId-to-options mapping logic (DRY - MF-001).
188
+ *
189
+ * prompt-detector.ts remains CLI tool independent (Issue #161 principle);
190
+ * this function lives in cli-patterns.ts which already depends on CLIToolType.
191
+ *
192
+ * [Future extension memo (C-002)]
193
+ * If CLI tool count grows significantly (currently 3), consider migrating
194
+ * to a CLIToolConfig registry pattern where tool-specific settings
195
+ * (including promptDetectionOptions) are managed in a Record<CLIToolType, CLIToolConfig>.
196
+ *
197
+ * @param cliToolId - CLI tool identifier
198
+ * @returns DetectPromptOptions for the tool, or undefined for default behavior
199
+ */
200
+ function buildDetectPromptOptions(cliToolId) {
201
+ if (cliToolId === 'claude') {
202
+ return { requireDefaultIndicator: false };
203
+ }
204
+ return undefined; // Default behavior (requireDefaultIndicator = true)
205
+ }