commandmate 0.2.2 → 0.2.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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +46 -50
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +9 -9
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/fetch-cache/799a63cbfa61e2ab38626c05fe43500464c7bbd38341bdde69f5ec4b25acff68 +1 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -0
- package/.next/server/app/{worktrees/[id]/simple-terminal/page.js.nft.json → api/app/update-check/route.js.nft.json} +1 -1
- package/.next/server/app/api/app/update-check.body +1 -0
- package/.next/server/app/api/app/update-check.meta +1 -0
- package/.next/server/app/api/repositories/restore/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +4 -4
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/page.js +2 -4
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +3 -15
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js +3 -3
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +13 -13
- package/.next/server/chunks/1287.js +4 -0
- package/.next/server/chunks/2683.js +1 -0
- package/.next/server/chunks/3348.js +1 -0
- package/.next/server/chunks/369.js +1 -0
- package/.next/server/chunks/3860.js +1 -0
- package/.next/server/chunks/4559.js +1 -0
- package/.next/server/chunks/4704.js +35 -0
- package/.next/server/chunks/5488.js +1 -1
- package/.next/server/chunks/5781.js +1 -0
- package/.next/server/chunks/5823.js +1 -0
- package/.next/server/chunks/5853.js +1 -0
- package/.next/server/chunks/6837.js +1 -0
- package/.next/server/chunks/7266.js +1 -0
- package/.next/server/chunks/7425.js +3 -3
- package/.next/server/chunks/7458.js +1 -0
- package/.next/server/chunks/7536.js +1 -1
- package/.next/server/chunks/8705.js +1 -0
- package/.next/server/chunks/8744.js +1 -0
- package/.next/server/chunks/9367.js +2 -2
- package/.next/server/chunks/9582.js +1 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/1038-3509435b68c0967e.js +1 -0
- package/.next/static/chunks/216-f18f4a9d8b04a91e.js +1 -0
- package/.next/static/chunks/2330-0299b9879f4977d2.js +1 -0
- package/.next/static/chunks/4733-50bdfc169adb4881.js +1 -0
- package/.next/static/chunks/6140-389f1951062dcf4f.js +1 -0
- package/.next/static/chunks/816-af44cb865b0c980e.js +1 -0
- package/.next/static/chunks/8216-00e20326f32abd12.js +1 -0
- package/.next/static/chunks/9234-b0304101384ca079.js +3 -0
- package/.next/static/chunks/app/layout-07755491d5d57242.js +1 -0
- package/.next/static/chunks/app/page-43b5de1a0a788b1f.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-ce9ac3658f2b7d91.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-9632761937a4d1ad.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/terminal/page-5d85a7e508ce36d3.js +1 -0
- package/.next/static/chunks/{main-b6d727aa9248d4f2.js → main-f00f82f1cf18dd99.js} +1 -1
- package/.next/static/chunks/{webpack-4f85dcef6279c6ee.js → webpack-af8567a485ade35a.js} +1 -1
- package/.next/static/css/6a92c8ad3c94d15a.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/app/update-check/route.ts +343 -0
- package/README.md +84 -82
- package/dist/server/src/config/auto-yes-config.js +4 -4
- package/dist/server/src/config/log-config.js +41 -0
- package/dist/server/src/lib/cli-tools/manager.js +0 -6
- package/dist/server/src/lib/log-manager.js +2 -6
- package/dist/server/src/lib/prompt-detector.js +272 -75
- package/dist/server/src/lib/response-poller.js +29 -43
- package/package.json +2 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -6
- package/.next/server/app/_not-found.rsc +0 -10
- package/.next/server/app/index.html +0 -9
- package/.next/server/app/index.meta +0 -5
- package/.next/server/app/index.rsc +0 -8
- package/.next/server/app/worktrees/[id]/simple-terminal/page.js +0 -4
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +0 -1
- package/.next/server/chunks/3053.js +0 -1
- package/.next/server/chunks/434.js +0 -1
- package/.next/server/chunks/4471.js +0 -2
- package/.next/server/chunks/6550.js +0 -1
- package/.next/server/chunks/8174.js +0 -23
- package/.next/server/chunks/8887.js +0 -1
- package/.next/server/pages/404.html +0 -1
- package/.next/static/chunks/4343-ebe884a2a80eb033.js +0 -1
- package/.next/static/chunks/4851-45df4d388db5623f.js +0 -1
- package/.next/static/chunks/6568-c65d7e4d7db7655b.js +0 -1
- package/.next/static/chunks/6725-f7607851b7d57eb1.js +0 -1
- package/.next/static/chunks/7648-325564a6e12a3257.js +0 -1
- package/.next/static/chunks/816-c254f4e2406e696a.js +0 -1
- package/.next/static/chunks/9325-9e98829c1e75f42f.js +0 -1
- package/.next/static/chunks/app/layout-4804cfba519283cf.js +0 -1
- package/.next/static/chunks/app/page-3926224c4cdf315b.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-7eb14f8043796805.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-912c3c4c66821d99.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/simple-terminal/page-16feb3e86e42f4d1.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/terminal/page-be802baffc84dbd2.js +0 -1
- package/.next/static/css/d4b58a1129eff6af.css +0 -3
- package/.next/types/app/worktrees/[id]/simple-terminal/page.ts +0 -79
- package/dist/server/src/lib/claude-poller.js +0 -341
- /package/.next/static/{HhG0EHeG9E4wTJ4sqnLdv → b3UR0y5mw3Ubf_vI5JjIN}/_buildManifest.js +0 -0
- /package/.next/static/{HhG0EHeG9E4wTJ4sqnLdv → b3UR0y5mw3Ubf_vI5JjIN}/_ssgManifest.js +0 -0
- /package/.next/static/chunks/{2117-d845c2cd62e344a6.js → 2117-e31fa477cb500950.js} +0 -0
- /package/.next/static/chunks/app/_not-found/{page-a9d04e58c81115ec.js → page-ac4e4463b39d0cf7.js} +0 -0
- /package/.next/static/chunks/{fd9d1056-bbe86e4ae099d5cd.js → fd9d1056-cfdf4f91f13d3485.js} +0 -0
|
@@ -16,11 +16,7 @@ exports.cleanupOldLogs = cleanupOldLogs;
|
|
|
16
16
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const date_fns_1 = require("date-fns");
|
|
19
|
-
const
|
|
20
|
-
/**
|
|
21
|
-
* Log directory configuration (with fallback support - Issue #76)
|
|
22
|
-
*/
|
|
23
|
-
const LOG_DIR = (0, env_1.getEnvByKey)('CM_LOG_DIR') || path_1.default.join(process.cwd(), 'data', 'logs');
|
|
19
|
+
const log_config_1 = require("../config/log-config");
|
|
24
20
|
/**
|
|
25
21
|
* Get log directory for a CLI tool
|
|
26
22
|
*
|
|
@@ -28,7 +24,7 @@ const LOG_DIR = (0, env_1.getEnvByKey)('CM_LOG_DIR') || path_1.default.join(proc
|
|
|
28
24
|
* @returns Log directory path
|
|
29
25
|
*/
|
|
30
26
|
function getCliToolLogDir(cliToolId = 'claude') {
|
|
31
|
-
return path_1.default.join(
|
|
27
|
+
return path_1.default.join((0, log_config_1.getLogDir)(), cliToolId);
|
|
32
28
|
}
|
|
33
29
|
/**
|
|
34
30
|
* Ensure log directory exists
|
|
@@ -8,6 +8,40 @@ exports.detectPrompt = detectPrompt;
|
|
|
8
8
|
exports.getAnswerInput = getAnswerInput;
|
|
9
9
|
const logger_1 = require("./logger");
|
|
10
10
|
const logger = (0, logger_1.createLogger)('prompt-detector');
|
|
11
|
+
/**
|
|
12
|
+
* Maximum number of lines to retain in rawContent.
|
|
13
|
+
* Tail lines are preserved (instruction text typically appears just before the prompt).
|
|
14
|
+
* @see truncateRawContent
|
|
15
|
+
*/
|
|
16
|
+
const RAW_CONTENT_MAX_LINES = 200;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of characters to retain in rawContent.
|
|
19
|
+
* Tail characters are preserved.
|
|
20
|
+
* @see truncateRawContent
|
|
21
|
+
*/
|
|
22
|
+
const RAW_CONTENT_MAX_CHARS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Truncate raw content to fit within size limits.
|
|
25
|
+
* Preserves the tail (end) of the content since instruction text
|
|
26
|
+
* typically appears just before the prompt at the end of output.
|
|
27
|
+
*
|
|
28
|
+
* Security: No regular expressions used -- no ReDoS risk. [SF-S4-002]
|
|
29
|
+
* String.split('\n') and String.slice() are literal string operations only.
|
|
30
|
+
*
|
|
31
|
+
* @param content - The content to truncate
|
|
32
|
+
* @returns Truncated content (last RAW_CONTENT_MAX_LINES lines, max RAW_CONTENT_MAX_CHARS characters)
|
|
33
|
+
*/
|
|
34
|
+
function truncateRawContent(content) {
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
const truncatedLines = lines.length > RAW_CONTENT_MAX_LINES
|
|
37
|
+
? lines.slice(-RAW_CONTENT_MAX_LINES)
|
|
38
|
+
: lines;
|
|
39
|
+
let result = truncatedLines.join('\n');
|
|
40
|
+
if (result.length > RAW_CONTENT_MAX_CHARS) {
|
|
41
|
+
result = result.slice(-RAW_CONTENT_MAX_CHARS);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
11
45
|
/**
|
|
12
46
|
* Yes/no pattern definitions for data-driven matching.
|
|
13
47
|
* Each entry defines a regex pattern and its associated default option.
|
|
@@ -27,6 +61,32 @@ const YES_NO_PATTERNS = [
|
|
|
27
61
|
// (yes/no) - no default
|
|
28
62
|
{ regex: /^(.+)\s+\(yes\/no\)\s*$/m },
|
|
29
63
|
];
|
|
64
|
+
/**
|
|
65
|
+
* Creates a yes/no prompt detection result.
|
|
66
|
+
* Centralizes the repeated construction of yes_no PromptDetectionResult objects
|
|
67
|
+
* used by both YES_NO_PATTERNS matching and Approve pattern matching.
|
|
68
|
+
*
|
|
69
|
+
* @param question - The question text
|
|
70
|
+
* @param cleanContent - The clean content string
|
|
71
|
+
* @param rawContent - The raw content string (last 20 lines, trimmed)
|
|
72
|
+
* @param defaultOption - Optional default option ('yes' or 'no')
|
|
73
|
+
* @returns PromptDetectionResult with isPrompt: true and yes_no prompt data
|
|
74
|
+
*/
|
|
75
|
+
function yesNoPromptResult(question, cleanContent, rawContent, defaultOption) {
|
|
76
|
+
return {
|
|
77
|
+
isPrompt: true,
|
|
78
|
+
promptData: {
|
|
79
|
+
type: 'yes_no',
|
|
80
|
+
question,
|
|
81
|
+
options: ['yes', 'no'],
|
|
82
|
+
status: 'pending',
|
|
83
|
+
...(defaultOption !== undefined && { defaultOption }),
|
|
84
|
+
instructionText: rawContent,
|
|
85
|
+
},
|
|
86
|
+
cleanContent,
|
|
87
|
+
rawContent,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
30
90
|
/**
|
|
31
91
|
* Detect if output contains an interactive prompt
|
|
32
92
|
*
|
|
@@ -51,7 +111,8 @@ const YES_NO_PATTERNS = [
|
|
|
51
111
|
function detectPrompt(output, options) {
|
|
52
112
|
logger.debug('detectPrompt:start', { outputLength: output.length });
|
|
53
113
|
const lines = output.split('\n');
|
|
54
|
-
|
|
114
|
+
// [SF-003] [MF-S2-001] Expanded from 10 to 20 lines for rawContent coverage
|
|
115
|
+
const lastLines = lines.slice(-20).join('\n');
|
|
55
116
|
// Pattern 0: Multiple choice (numbered options with ❯ indicator)
|
|
56
117
|
// Example:
|
|
57
118
|
// Do you want to proceed?
|
|
@@ -68,21 +129,12 @@ function detectPrompt(output, options) {
|
|
|
68
129
|
return multipleChoiceResult;
|
|
69
130
|
}
|
|
70
131
|
// Patterns 1-4: Yes/no patterns (data-driven matching)
|
|
132
|
+
const trimmedLastLines = lastLines.trim();
|
|
71
133
|
for (const pattern of YES_NO_PATTERNS) {
|
|
72
134
|
const match = lastLines.match(pattern.regex);
|
|
73
135
|
if (match) {
|
|
74
136
|
const question = match[1].trim();
|
|
75
|
-
return
|
|
76
|
-
isPrompt: true,
|
|
77
|
-
promptData: {
|
|
78
|
-
type: 'yes_no',
|
|
79
|
-
question,
|
|
80
|
-
options: ['yes', 'no'],
|
|
81
|
-
status: 'pending',
|
|
82
|
-
...(pattern.defaultOption !== undefined && { defaultOption: pattern.defaultOption }),
|
|
83
|
-
},
|
|
84
|
-
cleanContent: question,
|
|
85
|
-
};
|
|
137
|
+
return yesNoPromptResult(question, question, trimmedLastLines, pattern.defaultOption);
|
|
86
138
|
}
|
|
87
139
|
}
|
|
88
140
|
// Pattern 5: Approve?
|
|
@@ -93,16 +145,7 @@ function detectPrompt(output, options) {
|
|
|
93
145
|
const content = approveMatch[1].trim();
|
|
94
146
|
// If there's content before "Approve?", include it in the question
|
|
95
147
|
const question = content ? `${content} Approve?` : 'Approve?';
|
|
96
|
-
return
|
|
97
|
-
isPrompt: true,
|
|
98
|
-
promptData: {
|
|
99
|
-
type: 'yes_no',
|
|
100
|
-
question: question,
|
|
101
|
-
options: ['yes', 'no'],
|
|
102
|
-
status: 'pending',
|
|
103
|
-
},
|
|
104
|
-
cleanContent: content || 'Approve?',
|
|
105
|
-
};
|
|
148
|
+
return yesNoPromptResult(question, content || 'Approve?', trimmedLastLines);
|
|
106
149
|
}
|
|
107
150
|
// No prompt detected
|
|
108
151
|
logger.debug('detectPrompt:complete', { isPrompt: false });
|
|
@@ -141,6 +184,28 @@ const NORMAL_OPTION_PATTERN = /^\s*(\d+)\.\s*(.+)$/;
|
|
|
141
184
|
* Anchored at both ends -- ReDoS safe (S4-001).
|
|
142
185
|
*/
|
|
143
186
|
const SEPARATOR_LINE_PATTERN = /^[-─]+$/;
|
|
187
|
+
/**
|
|
188
|
+
* Maximum number of lines to scan upward from questionEndIndex
|
|
189
|
+
* when the questionEndIndex line itself is not a question-like line.
|
|
190
|
+
*
|
|
191
|
+
* Design rationale (IC-256-001):
|
|
192
|
+
* - model selection prompts have 1-2 lines between "Select model" and first option
|
|
193
|
+
* - multi-line question wrapping typically produces 2-3 continuation lines
|
|
194
|
+
* - value of 3 covers these cases while minimizing False Positive surface
|
|
195
|
+
*
|
|
196
|
+
* [SF-002] Change guidelines:
|
|
197
|
+
* - Increase this value ONLY if real-world prompts are discovered where
|
|
198
|
+
* the question line is more than 3 lines above questionEndIndex
|
|
199
|
+
* - Before increasing, verify that the new value does not cause
|
|
200
|
+
* T11h-T11m False Positive tests to fail
|
|
201
|
+
* - Consider that larger values increase the False Positive surface area
|
|
202
|
+
* - If increasing beyond 5, consider whether the detection approach
|
|
203
|
+
* itself needs to be redesigned (e.g., pattern-based instead of scan-based)
|
|
204
|
+
* - Document the specific prompt pattern that necessitated the change
|
|
205
|
+
*
|
|
206
|
+
* @see Issue #256: multiple_choice prompt detection improvement
|
|
207
|
+
*/
|
|
208
|
+
const QUESTION_SCAN_RANGE = 3;
|
|
144
209
|
/**
|
|
145
210
|
* Creates a "no prompt detected" result.
|
|
146
211
|
* Centralizes the repeated pattern of returning isPrompt: false with trimmed content.
|
|
@@ -201,17 +266,75 @@ function isQuestionLikeLine(line) {
|
|
|
201
266
|
// Empty lines are not questions
|
|
202
267
|
if (line.length === 0)
|
|
203
268
|
return false;
|
|
204
|
-
// Pattern 1: Lines
|
|
269
|
+
// Pattern 1: Lines containing question mark anywhere (English '?' or full-width U+FF1F).
|
|
270
|
+
// This covers both:
|
|
271
|
+
// - Lines ending with '?' (standard question format)
|
|
272
|
+
// - Lines with '?' mid-line (Issue #256: multi-line question wrapping where '?'
|
|
273
|
+
// appears mid-line due to terminal width causing the question text to wrap)
|
|
274
|
+
//
|
|
205
275
|
// Full-width question mark (U+FF1F) support is a defensive measure: Claude Code/CLI
|
|
206
276
|
// displays questions in English, but this covers future multi-language support
|
|
207
277
|
// and third-party tool integration.
|
|
208
|
-
|
|
278
|
+
//
|
|
279
|
+
// [SF-001] Scope constraints:
|
|
280
|
+
// - The mid-line '?' detection is effective without False Positive risk only within
|
|
281
|
+
// SEC-001b guard context (questionEndIndex vicinity and upward scan range).
|
|
282
|
+
// - isQuestionLikeLine() is currently module-private (no export).
|
|
283
|
+
// - If this function is exported for external use in the future, consider:
|
|
284
|
+
// (a) Providing a stricter variant (e.g., isStrictQuestionLikeLine()) without mid-line match
|
|
285
|
+
// (b) Separating mid-line match into a SEC-001b-specific helper function
|
|
286
|
+
// (c) Adding URL exclusion logic (/[?&]\w+=/.test(line) to exclude)
|
|
287
|
+
if (line.includes('?') || line.includes('\uff1f'))
|
|
209
288
|
return true;
|
|
210
|
-
// Pattern 2: Lines
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
289
|
+
// Pattern 2: Lines containing a selection/input keyword.
|
|
290
|
+
// Detects both colon-terminated (e.g., "Select an option:", "Choose a mode:") and
|
|
291
|
+
// non-colon forms (e.g., "Select model") used by CLI prompts (Issue #256).
|
|
292
|
+
//
|
|
293
|
+
// [SF-001] Scope constraints apply:
|
|
294
|
+
// - Effective without False Positive risk only within SEC-001b guard context.
|
|
295
|
+
// - T11h-T11m False Positive lines do not contain QUESTION_KEYWORD_PATTERN keywords.
|
|
296
|
+
// - If this function is exported, consider restricting this pattern to SEC-001b context.
|
|
297
|
+
if (QUESTION_KEYWORD_PATTERN.test(line))
|
|
298
|
+
return true;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Search upward from a given line index to find a question-like line.
|
|
303
|
+
* Skips empty lines and separator lines (horizontal rules).
|
|
304
|
+
*
|
|
305
|
+
* This function is used by SEC-001b guard to find a question line above
|
|
306
|
+
* questionEndIndex when the questionEndIndex line itself is not a question-like line.
|
|
307
|
+
* This handles cases where the question text wraps across multiple lines or
|
|
308
|
+
* where description lines appear between the question and the numbered options.
|
|
309
|
+
*
|
|
310
|
+
* @param lines - Array of output lines
|
|
311
|
+
* @param startIndex - Starting line index (exclusive, searches startIndex-1 and above)
|
|
312
|
+
* @param scanRange - Maximum number of lines to scan upward (must be >= 0, clamped to MAX_SCAN_RANGE=10)
|
|
313
|
+
* @param lowerBound - Minimum line index (inclusive, scan will not go below this)
|
|
314
|
+
* @returns true if a question-like line is found within the scan range
|
|
315
|
+
*
|
|
316
|
+
* @see IC-256-002: SEC-001b upward scan implementation
|
|
317
|
+
* @see SF-003: Function extraction for readability
|
|
318
|
+
* @see SF-S4-001: scanRange input validation (defensive clamping)
|
|
319
|
+
*
|
|
320
|
+
* ReDoS safe: Uses SEPARATOR_LINE_PATTERN (existing ReDoS safe pattern) and
|
|
321
|
+
* isQuestionLikeLine() (literal character checks + simple alternation pattern).
|
|
322
|
+
* No new regex patterns introduced. (C-S4-001)
|
|
323
|
+
*/
|
|
324
|
+
function findQuestionLineInRange(lines, startIndex, scanRange, lowerBound) {
|
|
325
|
+
// [SF-S4-001] Defensive input validation: clamp scanRange to safe bounds.
|
|
326
|
+
// Currently only called with QUESTION_SCAN_RANGE=3, but guards against
|
|
327
|
+
// future misuse if the function is refactored or exported.
|
|
328
|
+
const safeScanRange = Math.min(Math.max(scanRange, 0), 10);
|
|
329
|
+
const scanLimit = Math.max(lowerBound, startIndex - safeScanRange);
|
|
330
|
+
for (let i = startIndex - 1; i >= scanLimit; i--) {
|
|
331
|
+
const candidateLine = lines[i]?.trim() ?? '';
|
|
332
|
+
// Skip empty lines and separator lines (horizontal rules)
|
|
333
|
+
if (!candidateLine || SEPARATOR_LINE_PATTERN.test(candidateLine))
|
|
334
|
+
continue;
|
|
335
|
+
if (isQuestionLikeLine(candidateLine)) {
|
|
214
336
|
return true;
|
|
337
|
+
}
|
|
215
338
|
}
|
|
216
339
|
return false;
|
|
217
340
|
}
|
|
@@ -265,19 +388,103 @@ function isConsecutiveFromOne(numbers) {
|
|
|
265
388
|
* @returns true if the line should be treated as a continuation of a previous option
|
|
266
389
|
*/
|
|
267
390
|
function isContinuationLine(rawLine, line) {
|
|
268
|
-
//
|
|
269
|
-
// Excludes lines ending with '?' or '?' (U+FF1F) because those are typically question lines
|
|
391
|
+
// Lines ending with '?' or full-width '?' (U+FF1F) are typically question lines
|
|
270
392
|
// (e.g., " Do you want to proceed?", " コピーしたい対象はどれですか?") from CLI tool output
|
|
271
|
-
// where both the question and options are 2-space indented.
|
|
272
|
-
//
|
|
273
|
-
//
|
|
393
|
+
// where both the question and options are 2-space indented. These must NOT be
|
|
394
|
+
// treated as continuation lines, otherwise questionEndIndex remains -1 and
|
|
395
|
+
// Layer 5 SEC-001 blocks detection.
|
|
274
396
|
const endsWithQuestion = line.endsWith('?') || line.endsWith('\uff1f');
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
397
|
+
// Check 1: Indented non-option line (label text wrapping with indentation).
|
|
398
|
+
// Must have 2+ leading spaces, not start with a number (option line), and not end with '?'.
|
|
399
|
+
if (!endsWithQuestion && /^\s{2,}[^\d]/.test(rawLine) && !/^\s*\d+\./.test(rawLine)) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
// Check 2: Short fragment (< 5 chars, e.g., filename tail).
|
|
403
|
+
// Excludes question-ending lines to prevent misclassifying short questions.
|
|
404
|
+
if (line.length < 5 && !endsWithQuestion) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
// Check 3: Path string continuation (Issue #181).
|
|
408
|
+
// Lines starting with / or ~, or alphanumeric-only fragments (2+ chars).
|
|
409
|
+
if (/^[\/~]/.test(line) || (line.length >= 2 && /^[a-zA-Z0-9_-]+$/.test(line))) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Extract question text from the lines around questionEndIndex.
|
|
416
|
+
* Collects non-empty, non-separator lines from up to 5 lines before questionEndIndex
|
|
417
|
+
* through questionEndIndex itself, joining them with spaces.
|
|
418
|
+
*
|
|
419
|
+
* @param lines - Array of output lines
|
|
420
|
+
* @param questionEndIndex - Index of the last line before options, or -1 if not found
|
|
421
|
+
* @returns Extracted question text, or generic fallback if questionEndIndex is -1
|
|
422
|
+
*/
|
|
423
|
+
function extractQuestionText(lines, questionEndIndex) {
|
|
424
|
+
if (questionEndIndex < 0) {
|
|
425
|
+
return 'Please select an option:';
|
|
426
|
+
}
|
|
427
|
+
const questionLines = [];
|
|
428
|
+
for (let i = Math.max(0, questionEndIndex - 5); i <= questionEndIndex; i++) {
|
|
429
|
+
const line = lines[i].trim();
|
|
430
|
+
if (line && !SEPARATOR_LINE_PATTERN.test(line)) {
|
|
431
|
+
questionLines.push(line);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return questionLines.join(' ');
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Extract instruction text for the prompt block.
|
|
438
|
+
* Captures the complete AskUserQuestion block including context before the question,
|
|
439
|
+
* option descriptions, and navigation hints.
|
|
440
|
+
*
|
|
441
|
+
* @param lines - Array of output lines
|
|
442
|
+
* @param questionEndIndex - Index of the last line before options, or -1 if not found
|
|
443
|
+
* @param effectiveEnd - End index of non-trailing-empty lines
|
|
444
|
+
* @returns Instruction text string, or undefined if no question line found
|
|
445
|
+
*/
|
|
446
|
+
function extractInstructionText(lines, questionEndIndex, effectiveEnd) {
|
|
447
|
+
if (questionEndIndex < 0) {
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const contextStart = Math.max(0, questionEndIndex - 19);
|
|
451
|
+
const blockLines = lines.slice(contextStart, effectiveEnd)
|
|
452
|
+
.map(l => l.trimEnd());
|
|
453
|
+
const joined = blockLines.join('\n').trim();
|
|
454
|
+
return joined.length > 0 ? joined : undefined;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Build the final PromptDetectionResult for a multiple choice prompt.
|
|
458
|
+
* Maps collected options to the output format, checking each option for
|
|
459
|
+
* text input requirements using TEXT_INPUT_PATTERNS.
|
|
460
|
+
*
|
|
461
|
+
* @param question - Extracted question text
|
|
462
|
+
* @param collectedOptions - Options collected during Pass 2 scanning
|
|
463
|
+
* @param instructionText - Instruction text for the prompt block
|
|
464
|
+
* @param output - Original output text (used for rawContent truncation)
|
|
465
|
+
* @returns PromptDetectionResult with isPrompt: true and multiple_choice data
|
|
466
|
+
*/
|
|
467
|
+
function buildMultipleChoiceResult(question, collectedOptions, instructionText, output) {
|
|
468
|
+
return {
|
|
469
|
+
isPrompt: true,
|
|
470
|
+
promptData: {
|
|
471
|
+
type: 'multiple_choice',
|
|
472
|
+
question: question.trim(),
|
|
473
|
+
options: collectedOptions.map(opt => {
|
|
474
|
+
const requiresTextInput = TEXT_INPUT_PATTERNS.some(pattern => pattern.test(opt.label));
|
|
475
|
+
return {
|
|
476
|
+
number: opt.number,
|
|
477
|
+
label: opt.label,
|
|
478
|
+
isDefault: opt.isDefault,
|
|
479
|
+
requiresTextInput,
|
|
480
|
+
};
|
|
481
|
+
}),
|
|
482
|
+
status: 'pending',
|
|
483
|
+
instructionText,
|
|
484
|
+
},
|
|
485
|
+
cleanContent: question.trim(),
|
|
486
|
+
rawContent: truncateRawContent(output.trim()), // Issue #235: complete prompt output (truncated) [MF-001]
|
|
487
|
+
};
|
|
281
488
|
}
|
|
282
489
|
/**
|
|
283
490
|
* Detect multiple choice prompts (numbered list with ❯ indicator)
|
|
@@ -356,6 +563,20 @@ function detectMultipleChoicePrompt(output, options) {
|
|
|
356
563
|
}
|
|
357
564
|
// Non-option line handling
|
|
358
565
|
if (collectedOptions.length > 0 && line && !SEPARATOR_LINE_PATTERN.test(line)) {
|
|
566
|
+
// [MF-001 / Issue #256] Check if line is a question-like line BEFORE
|
|
567
|
+
// continuation check. This preserves isContinuationLine()'s SRP by not
|
|
568
|
+
// mixing question detection into it. Without this pre-check, indented
|
|
569
|
+
// question lines (e.g., " Select model") could be misclassified as
|
|
570
|
+
// continuation lines by isContinuationLine()'s hasLeadingSpaces check.
|
|
571
|
+
//
|
|
572
|
+
// [SF-S4-003] Both this pre-check and SEC-001b upward scan use the same
|
|
573
|
+
// isQuestionLikeLine() function intentionally (DRY). If a question line is
|
|
574
|
+
// caught here, SEC-001b upward scan is not needed (questionEndIndex line
|
|
575
|
+
// itself passes isQuestionLikeLine()).
|
|
576
|
+
if (isQuestionLikeLine(line)) {
|
|
577
|
+
questionEndIndex = i;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
359
580
|
// Check if this is a continuation line (indented line between options,
|
|
360
581
|
// or path/filename fragments from terminal width wrapping - Issue #181)
|
|
361
582
|
const rawLine = lines[i]; // Original line with indentation preserved
|
|
@@ -392,47 +613,23 @@ function detectMultipleChoicePrompt(output, options) {
|
|
|
392
613
|
// SEC-001b: Question line exists but is not actually a question/selection request.
|
|
393
614
|
// Validates that the question line contains a question mark or a selection keyword
|
|
394
615
|
// with colon, distinguishing "Select an option:" from "Recommendations:".
|
|
616
|
+
//
|
|
617
|
+
// [Issue #256] Enhanced with upward scan via findQuestionLineInRange() (SF-003).
|
|
618
|
+
// When questionEndIndex line itself is not a question-like line, scan upward
|
|
619
|
+
// within QUESTION_SCAN_RANGE to find a question line above it. This handles:
|
|
620
|
+
// - Multi-line question wrapping where ? is on a line above questionEndIndex
|
|
621
|
+
// - Model selection prompts where "Select model" is above description lines
|
|
395
622
|
const questionLine = lines[questionEndIndex]?.trim() ?? '';
|
|
396
623
|
if (!isQuestionLikeLine(questionLine)) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// Extract question text
|
|
401
|
-
let question = '';
|
|
402
|
-
if (questionEndIndex >= 0) {
|
|
403
|
-
// Get all non-empty lines from questionEndIndex up to (but not including) first option
|
|
404
|
-
const questionLines = [];
|
|
405
|
-
for (let i = Math.max(0, questionEndIndex - 5); i <= questionEndIndex; i++) {
|
|
406
|
-
const line = lines[i].trim();
|
|
407
|
-
if (line && !SEPARATOR_LINE_PATTERN.test(line)) {
|
|
408
|
-
questionLines.push(line);
|
|
624
|
+
// Upward scan: look for a question-like line above questionEndIndex
|
|
625
|
+
if (!findQuestionLineInRange(lines, questionEndIndex, QUESTION_SCAN_RANGE, scanStart)) {
|
|
626
|
+
return noPromptResult(output);
|
|
409
627
|
}
|
|
410
628
|
}
|
|
411
|
-
question = questionLines.join(' ');
|
|
412
629
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
return {
|
|
418
|
-
isPrompt: true,
|
|
419
|
-
promptData: {
|
|
420
|
-
type: 'multiple_choice',
|
|
421
|
-
question: question.trim(),
|
|
422
|
-
options: collectedOptions.map(opt => {
|
|
423
|
-
// Check if this option requires text input using module-level patterns
|
|
424
|
-
const requiresTextInput = TEXT_INPUT_PATTERNS.some(pattern => pattern.test(opt.label));
|
|
425
|
-
return {
|
|
426
|
-
number: opt.number,
|
|
427
|
-
label: opt.label,
|
|
428
|
-
isDefault: opt.isDefault,
|
|
429
|
-
requiresTextInput,
|
|
430
|
-
};
|
|
431
|
-
}),
|
|
432
|
-
status: 'pending',
|
|
433
|
-
},
|
|
434
|
-
cleanContent: question.trim(),
|
|
435
|
-
};
|
|
630
|
+
const question = extractQuestionText(lines, questionEndIndex);
|
|
631
|
+
const instructionText = extractInstructionText(lines, questionEndIndex, effectiveEnd);
|
|
632
|
+
return buildMultipleChoiceResult(question, collectedOptions, instructionText, output);
|
|
436
633
|
}
|
|
437
634
|
/**
|
|
438
635
|
* Get tmux input string for an answer
|
|
@@ -53,6 +53,25 @@ const MAX_POLLING_DURATION = 5 * 60 * 1000;
|
|
|
53
53
|
* @constant
|
|
54
54
|
*/
|
|
55
55
|
const RESPONSE_THINKING_TAIL_LINE_COUNT = 5;
|
|
56
|
+
/**
|
|
57
|
+
* Gemini auth/loading state indicators that should not be treated as complete responses.
|
|
58
|
+
* Braille spinner characters are shared with CLAUDE_SPINNER_CHARS in cli-patterns.ts.
|
|
59
|
+
* Extracted to module level for clarity and to avoid re-creation on each call.
|
|
60
|
+
*/
|
|
61
|
+
const GEMINI_LOADING_INDICATORS = [
|
|
62
|
+
'Waiting for auth',
|
|
63
|
+
'\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f',
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Creates an incomplete extraction result with empty response.
|
|
67
|
+
* Centralizes the repeated pattern of returning an in-progress/incomplete state.
|
|
68
|
+
*
|
|
69
|
+
* @param lineCount - Current line count for state tracking
|
|
70
|
+
* @returns ExtractionResult with empty response and isComplete: false
|
|
71
|
+
*/
|
|
72
|
+
function incompleteResult(lineCount) {
|
|
73
|
+
return { response: '', isComplete: false, lineCount };
|
|
74
|
+
}
|
|
56
75
|
/**
|
|
57
76
|
* Active pollers map: "worktreeId:cliToolId" -> NodeJS.Timeout
|
|
58
77
|
*/
|
|
@@ -335,11 +354,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
335
354
|
// Prevents false blocking when completed thinking summaries appear in the response body.
|
|
336
355
|
const responseTailLines = response.split('\n').slice(-RESPONSE_THINKING_TAIL_LINE_COUNT).join('\n');
|
|
337
356
|
if (thinkingPattern.test(responseTailLines)) {
|
|
338
|
-
return
|
|
339
|
-
response: '',
|
|
340
|
-
isComplete: false,
|
|
341
|
-
lineCount: totalLines,
|
|
342
|
-
};
|
|
357
|
+
return incompleteResult(totalLines);
|
|
343
358
|
}
|
|
344
359
|
// CRITICAL FIX: Detect and skip Claude Code startup banner/screen
|
|
345
360
|
// The startup screen contains: ASCII art logo, version info, prompt, separator
|
|
@@ -366,20 +381,12 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
366
381
|
!/^─+$/.test(trimmed);
|
|
367
382
|
});
|
|
368
383
|
if (contentLines.length === 0) {
|
|
369
|
-
return
|
|
370
|
-
response: '',
|
|
371
|
-
isComplete: false,
|
|
372
|
-
lineCount: totalLines,
|
|
373
|
-
};
|
|
384
|
+
return incompleteResult(totalLines);
|
|
374
385
|
}
|
|
375
386
|
}
|
|
376
387
|
else if ((hasBannerArt || hasVersionInfo || hasStartupTips || hasProjectInit) && response.length < 2000) {
|
|
377
388
|
// No user prompt found, but has banner characteristics - likely initial startup
|
|
378
|
-
return
|
|
379
|
-
response: '',
|
|
380
|
-
isComplete: false,
|
|
381
|
-
lineCount: totalLines,
|
|
382
|
-
};
|
|
389
|
+
return incompleteResult(totalLines);
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
// Gemini-specific check: ensure response contains actual content (✦ marker)
|
|
@@ -388,31 +395,13 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
388
395
|
const bannerCharCount = (response.match(/[░███]/g) || []).length;
|
|
389
396
|
const totalChars = response.length;
|
|
390
397
|
if (bannerCharCount > totalChars * 0.3) {
|
|
391
|
-
return
|
|
392
|
-
response: '',
|
|
393
|
-
isComplete: false,
|
|
394
|
-
lineCount: totalLines,
|
|
395
|
-
};
|
|
398
|
+
return incompleteResult(totalLines);
|
|
396
399
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const LOADING_INDICATORS = [
|
|
400
|
-
'Waiting for auth',
|
|
401
|
-
'⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏',
|
|
402
|
-
];
|
|
403
|
-
if (LOADING_INDICATORS.some(indicator => response.includes(indicator))) {
|
|
404
|
-
return {
|
|
405
|
-
response: '',
|
|
406
|
-
isComplete: false,
|
|
407
|
-
lineCount: totalLines,
|
|
408
|
-
};
|
|
400
|
+
if (GEMINI_LOADING_INDICATORS.some(indicator => response.includes(indicator))) {
|
|
401
|
+
return incompleteResult(totalLines);
|
|
409
402
|
}
|
|
410
403
|
if (!response.includes('✦') && response.length < 10) {
|
|
411
|
-
return
|
|
412
|
-
response: '',
|
|
413
|
-
isComplete: false,
|
|
414
|
-
lineCount: totalLines,
|
|
415
|
-
};
|
|
404
|
+
return incompleteResult(totalLines);
|
|
416
405
|
}
|
|
417
406
|
}
|
|
418
407
|
return {
|
|
@@ -460,11 +449,7 @@ function extractResponse(output, lastCapturedLine, cliToolId) {
|
|
|
460
449
|
};
|
|
461
450
|
}
|
|
462
451
|
// Response not yet complete (or is in thinking state)
|
|
463
|
-
return
|
|
464
|
-
response: '',
|
|
465
|
-
isComplete: false,
|
|
466
|
-
lineCount: totalLines,
|
|
467
|
-
};
|
|
452
|
+
return incompleteResult(totalLines);
|
|
468
453
|
}
|
|
469
454
|
/**
|
|
470
455
|
* Check for CLI tool response once
|
|
@@ -533,7 +518,8 @@ async function checkForResponse(worktreeId, cliToolId) {
|
|
|
533
518
|
const message = (0, db_1.createMessage)(db, {
|
|
534
519
|
worktreeId,
|
|
535
520
|
role: 'assistant',
|
|
536
|
-
|
|
521
|
+
// Issue #235: rawContent優先でDB保存 (rawContent contains complete prompt output)
|
|
522
|
+
content: promptDetection.rawContent || promptDetection.cleanContent,
|
|
537
523
|
messageType: 'prompt',
|
|
538
524
|
promptData: promptDetection.promptData,
|
|
539
525
|
timestamp: new Date(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commandmate",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Git worktree management with Claude CLI and tmux sessions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"lucide-react": "^0.554.0",
|
|
49
49
|
"mermaid": "^11.12.2",
|
|
50
50
|
"next": "^14.2.35",
|
|
51
|
+
"next-intl": "^4.8.2",
|
|
51
52
|
"postcss": "^8.5.6",
|
|
52
53
|
"react": "^18.3.0",
|
|
53
54
|
"react-dom": "^18.3.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/d4b58a1129eff6af.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-4f85dcef6279c6ee.js"/><script src="/_next/static/chunks/fd9d1056-bbe86e4ae099d5cd.js" async=""></script><script src="/_next/static/chunks/2117-d845c2cd62e344a6.js" async=""></script><script src="/_next/static/chunks/main-app-420d93e43682fee5.js" async=""></script><script src="/_next/static/chunks/816-c254f4e2406e696a.js" async=""></script><script src="/_next/static/chunks/app/layout-4804cfba519283cf.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>CommandMate</title><meta name="description" content="Git worktree management with Claude CLI and tmux sessions"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="min-h-screen bg-gray-50"><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-4f85dcef6279c6ee.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/d4b58a1129eff6af.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[12846,[],\"\"]\n4:I[4707,[],\"\"]\n5:I[36423,[],\"\"]\n6:I[91795,[\"816\",\"static/chunks/816-c254f4e2406e696a.js\",\"3185\",\"static/chunks/app/layout-4804cfba519283cf.js\"],\"AppProviders\"]\nc:I[61060,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"HhG0EHeG9E4wTJ4sqnLdv\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/d4b58a1129eff6af.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"ja\",\"children\":[\"$\",\"body\",null,{\"className\":\"min-h-screen bg-gray-50\",\"children\":[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$8\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$9\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$a\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"CommandMate\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Git worktree management with Claude CLI and tmux sessions\"}]]\n3:null\n"])</script></body></html>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
2:I[4707,[],""]
|
|
2
|
-
3:I[36423,[],""]
|
|
3
|
-
4:I[91795,["816","static/chunks/816-c254f4e2406e696a.js","3185","static/chunks/app/layout-4804cfba519283cf.js"],"AppProviders"]
|
|
4
|
-
5:{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"}
|
|
5
|
-
6:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"}
|
|
6
|
-
7:{"display":"inline-block"}
|
|
7
|
-
8:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0}
|
|
8
|
-
0:["HhG0EHeG9E4wTJ4sqnLdv",[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["/_not-found",{"children":["__PAGE__",{},[["$L1",[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null],null],null]},[null,["$","$L2",null,{"parallelRouterKey":"children","segmentPath":["children","/_not-found","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d4b58a1129eff6af.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"ja","children":["$","body",null,{"className":"min-h-screen bg-gray-50","children":["$","$L4",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$5","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$6","children":"404"}],["$","div",null,{"style":"$7","children":["$","h2",null,{"style":"$8","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]}]],null],null],["$L9",["$","meta",null,{"name":"robots","content":"noindex"}]]]]]
|
|
9
|
-
9:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"CommandMate"}],["$","meta","3",{"name":"description","content":"Git worktree management with Claude CLI and tmux sessions"}]]
|
|
10
|
-
1:null
|