commandmate 0.2.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +21 -2
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +32 -24
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +7 -7
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- 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/0.pack +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-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +2 -2
- 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 -1
- package/.next/server/app/api/auth/login/route.js +1 -0
- package/.next/server/app/api/auth/login/route.js.nft.json +1 -0
- package/.next/server/app/api/auth/logout/route.js +1 -0
- package/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
- package/.next/server/app/api/auth/status/route.js +1 -0
- package/.next/server/app/api/auth/status/route.js.nft.json +1 -0
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route.js +2 -2
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/slash-commands/route.js +1 -1
- package/.next/server/app/api/slash-commands/route.js.nft.json +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/login/page.js +1 -0
- package/.next/server/app/login/page.js.nft.json +1 -0
- package/.next/server/app/login/page_client-reference-manifest.js +1 -0
- package/.next/server/app/page.js +2 -2
- 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/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 +2 -2
- 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 +1 -1
- 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 +12 -8
- package/.next/server/chunks/3013.js +1 -0
- package/.next/server/chunks/3074.js +1 -0
- package/.next/server/chunks/{1287.js → 3294.js} +2 -2
- package/.next/server/chunks/3860.js +1 -1
- package/.next/server/chunks/4893.js +2 -2
- package/.next/server/chunks/539.js +35 -0
- package/.next/server/chunks/5795.js +1 -0
- package/.next/server/chunks/7536.js +1 -1
- package/.next/server/chunks/7566.js +19 -0
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/edge-runtime-webpack.js +2 -0
- package/.next/server/edge-runtime-webpack.js.map +1 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +28 -2
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/src/middleware.js +14 -0
- package/.next/server/src/middleware.js.map +1 -0
- package/.next/static/chunks/{2626.2125083a1ff3b80a.js → 6163.f672451d4575decf.js} +1 -1
- package/.next/static/chunks/{656.d72f25ce819bd77e.js → 656.5e2de0173f5a06bd.js} +1 -1
- package/.next/static/chunks/8091-925542bdfc843dce.js +1 -0
- package/.next/static/chunks/8528-4d554d3b94d4cf9b.js +1 -0
- package/.next/static/chunks/app/{layout-07755491d5d57242.js → layout-9110f9a5e41c6bf4.js} +1 -1
- package/.next/static/chunks/app/login/page-2d42204ba87cd136.js +1 -0
- package/.next/static/chunks/app/page-238b5a70d8c101e9.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-9418e49bdc1de02c.js +1 -0
- package/.next/static/chunks/main-db79434ee4a6c931.js +1 -0
- package/.next/static/chunks/webpack-3c0ee3ce5b546818.js +1 -0
- package/.next/static/css/b9ea6a4fad17dc32.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/auth/login/route.ts +343 -0
- package/.next/types/app/api/auth/logout/route.ts +343 -0
- package/.next/types/app/api/auth/status/route.ts +343 -0
- package/.next/types/app/login/page.ts +79 -0
- package/README.md +6 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +159 -14
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +4 -0
- package/dist/cli/config/security-messages.d.ts +3 -1
- package/dist/cli/config/security-messages.d.ts.map +1 -1
- package/dist/cli/config/security-messages.js +6 -2
- package/dist/cli/index.js +17 -0
- package/dist/cli/types/index.d.ts +17 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +16 -3
- package/dist/config/auth-config.d.ts +43 -0
- package/dist/config/auth-config.d.ts.map +1 -0
- package/dist/config/auth-config.js +112 -0
- package/dist/lib/auth.d.ts +104 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +250 -0
- package/dist/server/server.js +123 -12
- package/dist/server/src/config/auth-config.js +112 -0
- package/dist/server/src/lib/auth.js +250 -0
- package/dist/server/src/lib/auto-yes-manager.js +180 -96
- package/dist/server/src/lib/ip-restriction.js +241 -0
- package/dist/server/src/lib/ws-server.js +63 -33
- package/dist/server/src/types/slash-commands.js +1 -0
- package/package.json +2 -2
- package/.next/server/chunks/9238.js +0 -35
- package/.next/server/chunks/9367.js +0 -19
- package/.next/static/chunks/5970-2e18108d0cabd8af.js +0 -1
- package/.next/static/chunks/816-af44cb865b0c980e.js +0 -1
- package/.next/static/chunks/app/page-a6593b9640df66a6.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-d9a7913679eccfd9.js +0 -1
- package/.next/static/chunks/main-f00f82f1cf18dd99.js +0 -1
- package/.next/static/chunks/webpack-e6531fcf859d9451.js +0 -1
- package/.next/static/css/897ffb669f47c97b.css +0 -3
- /package/.next/static/{oUEq-Bd47xtkJcFDOI6rr → clTo9tuAoPMLcGRuVENfO}/_buildManifest.js +0 -0
- /package/.next/static/{oUEq-Bd47xtkJcFDOI6rr → clTo9tuAoPMLcGRuVENfO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* IP Restriction Module (Edge Runtime Compatible)
|
|
4
|
+
* Issue #332: Access control by IP address/CIDR range
|
|
5
|
+
*
|
|
6
|
+
* CONSTRAINT: This module must be Edge Runtime compatible.
|
|
7
|
+
* Do NOT import Node.js-specific modules (net, dns, os, fs, etc.).
|
|
8
|
+
*
|
|
9
|
+
* [S3-006] CLI build compatibility constraint:
|
|
10
|
+
* ip-restriction.ts is NOT directly imported from src/cli/.
|
|
11
|
+
* CLI sets CM_ALLOWED_IPS via process.env only; IP restriction logic
|
|
12
|
+
* runs server-side (middleware.ts / ws-server.ts).
|
|
13
|
+
*
|
|
14
|
+
* [S2-004] Difference from auth.ts:
|
|
15
|
+
* auth.ts silently disables on invalid hash (storedTokenHash = undefined).
|
|
16
|
+
* ip-restriction.ts uses fail-fast (throw) on invalid CIDR because
|
|
17
|
+
* silently ignoring security config errors would create a security hole.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.normalizeIp = normalizeIp;
|
|
21
|
+
exports.parseAllowedIps = parseAllowedIps;
|
|
22
|
+
exports.isIpAllowed = isIpAllowed;
|
|
23
|
+
exports.getClientIp = getClientIp;
|
|
24
|
+
exports.getAllowedRanges = getAllowedRanges;
|
|
25
|
+
exports.isIpRestrictionEnabled = isIpRestrictionEnabled;
|
|
26
|
+
// --- Internal constants (unexported) [S1-002] ---
|
|
27
|
+
// Integrated into this module; no external references needed (YAGNI).
|
|
28
|
+
const IPV4_MAPPED_IPV6_PREFIX = '::ffff:';
|
|
29
|
+
const IPV4_PATTERN = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
30
|
+
const IPV4_CIDR_PATTERN = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/;
|
|
31
|
+
const MAX_IPV4_PREFIX_LENGTH = 32;
|
|
32
|
+
/** [S4-002] DoS prevention: upper limit on CIDR entry count */
|
|
33
|
+
const MAX_ALLOWED_IP_ENTRIES = 256;
|
|
34
|
+
/** [S4-005] Input validation: max length per entry ('255.255.255.255/32' = 18 chars) */
|
|
35
|
+
const MAX_CIDR_ENTRY_LENGTH = 18;
|
|
36
|
+
// --- Module-scope initialization [S1-003] ---
|
|
37
|
+
// Following auth.ts storedTokenHash pattern: read env once at module load.
|
|
38
|
+
// Placed before functions that depend on these values for clarity.
|
|
39
|
+
const allowedIpsEnv = process.env.CM_ALLOWED_IPS?.trim() || '';
|
|
40
|
+
// [S4-006] CM_TRUST_PROXY value validation:
|
|
41
|
+
// 'true' is the only value that enables proxy trust. Other non-empty values
|
|
42
|
+
// (e.g., 'TRUE', '1', 'yes') fall back to safe default (no proxy trust),
|
|
43
|
+
// and a warning is emitted to help operators detect configuration mistakes.
|
|
44
|
+
const trustProxyEnv = process.env.CM_TRUST_PROXY?.trim() || '';
|
|
45
|
+
if (trustProxyEnv !== '' && trustProxyEnv !== 'true' && trustProxyEnv !== 'false') {
|
|
46
|
+
console.warn(`[IP-RESTRICTION] CM_TRUST_PROXY has unexpected value: "${trustProxyEnv}". ` +
|
|
47
|
+
'Only "true" (lowercase) enables proxy trust.');
|
|
48
|
+
}
|
|
49
|
+
/** Whether CM_TRUST_PROXY is strictly 'true' */
|
|
50
|
+
const trustProxy = trustProxyEnv === 'true';
|
|
51
|
+
// --- Pure functions ---
|
|
52
|
+
/**
|
|
53
|
+
* Parse an IPv4 address string into a 32-bit unsigned integer.
|
|
54
|
+
* Returns null if the format is invalid or any octet is out of range.
|
|
55
|
+
*/
|
|
56
|
+
function ipToInt(ip) {
|
|
57
|
+
const match = ip.match(IPV4_PATTERN);
|
|
58
|
+
if (!match)
|
|
59
|
+
return null;
|
|
60
|
+
const octets = [
|
|
61
|
+
parseInt(match[1], 10),
|
|
62
|
+
parseInt(match[2], 10),
|
|
63
|
+
parseInt(match[3], 10),
|
|
64
|
+
parseInt(match[4], 10),
|
|
65
|
+
];
|
|
66
|
+
for (const octet of octets) {
|
|
67
|
+
if (octet < 0 || octet > 255)
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Use unsigned right shift (>>> 0) to ensure unsigned 32-bit integer
|
|
71
|
+
return ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* IPv4-mapped IPv6 address (::ffff:x.x.x.x) normalization.
|
|
75
|
+
* Returns the IPv4 portion if mapped, otherwise returns as-is.
|
|
76
|
+
*/
|
|
77
|
+
function normalizeIp(ip) {
|
|
78
|
+
if (!ip)
|
|
79
|
+
return '';
|
|
80
|
+
const lower = ip.toLowerCase();
|
|
81
|
+
if (lower.startsWith(IPV4_MAPPED_IPV6_PREFIX)) {
|
|
82
|
+
return ip.substring(IPV4_MAPPED_IPV6_PREFIX.length);
|
|
83
|
+
}
|
|
84
|
+
return ip;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parse CM_ALLOWED_IPS environment variable string into CidrRange array.
|
|
88
|
+
* Throws Error on invalid CIDR format (fail-fast).
|
|
89
|
+
*
|
|
90
|
+
* [S4-002] Throws when entry count exceeds MAX_ALLOWED_IP_ENTRIES (256).
|
|
91
|
+
* Large CIDR entry counts cause parse delay and per-request OR-loop
|
|
92
|
+
* performance degradation.
|
|
93
|
+
*
|
|
94
|
+
* [S4-005] Throws when any entry exceeds MAX_CIDR_ENTRY_LENGTH (18 chars).
|
|
95
|
+
* IPv4 CIDR maximum is '255.255.255.255/32' (18 chars); longer input is
|
|
96
|
+
* rejected before regex matching.
|
|
97
|
+
*
|
|
98
|
+
* @throws {Error} Invalid IP address or CIDR format
|
|
99
|
+
* @throws {Error} Entry count exceeds MAX_ALLOWED_IP_ENTRIES (256)
|
|
100
|
+
* @throws {Error} Individual entry exceeds MAX_CIDR_ENTRY_LENGTH (18 chars)
|
|
101
|
+
*/
|
|
102
|
+
function parseAllowedIps(envValue) {
|
|
103
|
+
const trimmed = envValue.trim();
|
|
104
|
+
if (trimmed.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const entries = trimmed.split(',').map(e => e.trim()).filter(e => e.length > 0);
|
|
107
|
+
// [S4-002] DoS prevention: entry count upper limit
|
|
108
|
+
if (entries.length > MAX_ALLOWED_IP_ENTRIES) {
|
|
109
|
+
throw new Error(`CM_ALLOWED_IPS: too many entries (${entries.length}). Maximum is ${MAX_ALLOWED_IP_ENTRIES}.`);
|
|
110
|
+
}
|
|
111
|
+
const ranges = [];
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
// [S4-005] Entry length validation (before regex)
|
|
114
|
+
if (entry.length > MAX_CIDR_ENTRY_LENGTH) {
|
|
115
|
+
throw new Error(`CM_ALLOWED_IPS: entry "${entry}" exceeds maximum length of ${MAX_CIDR_ENTRY_LENGTH} characters.`);
|
|
116
|
+
}
|
|
117
|
+
// Try CIDR format first (x.x.x.x/N)
|
|
118
|
+
const cidrMatch = entry.match(IPV4_CIDR_PATTERN);
|
|
119
|
+
if (cidrMatch) {
|
|
120
|
+
const octets = [
|
|
121
|
+
parseInt(cidrMatch[1], 10),
|
|
122
|
+
parseInt(cidrMatch[2], 10),
|
|
123
|
+
parseInt(cidrMatch[3], 10),
|
|
124
|
+
parseInt(cidrMatch[4], 10),
|
|
125
|
+
];
|
|
126
|
+
const prefix = parseInt(cidrMatch[5], 10);
|
|
127
|
+
for (const octet of octets) {
|
|
128
|
+
if (octet < 0 || octet > 255) {
|
|
129
|
+
throw new Error(`CM_ALLOWED_IPS: invalid octet in "${entry}". Octets must be 0-255.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (prefix < 0 || prefix > MAX_IPV4_PREFIX_LENGTH) {
|
|
133
|
+
throw new Error(`CM_ALLOWED_IPS: invalid prefix length in "${entry}". Must be 0-${MAX_IPV4_PREFIX_LENGTH}.`);
|
|
134
|
+
}
|
|
135
|
+
const network = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
136
|
+
// Create mask: for prefix=24, mask = 0xFFFFFF00
|
|
137
|
+
// For prefix=0, mask = 0x00000000
|
|
138
|
+
const mask = prefix === 0 ? 0 : ((0xFFFFFFFF << (32 - prefix)) >>> 0);
|
|
139
|
+
ranges.push({ network: (network & mask) >>> 0, mask });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Try plain IP format (x.x.x.x -> treat as /32)
|
|
143
|
+
const ipMatch = entry.match(IPV4_PATTERN);
|
|
144
|
+
if (ipMatch) {
|
|
145
|
+
const octets = [
|
|
146
|
+
parseInt(ipMatch[1], 10),
|
|
147
|
+
parseInt(ipMatch[2], 10),
|
|
148
|
+
parseInt(ipMatch[3], 10),
|
|
149
|
+
parseInt(ipMatch[4], 10),
|
|
150
|
+
];
|
|
151
|
+
for (const octet of octets) {
|
|
152
|
+
if (octet < 0 || octet > 255) {
|
|
153
|
+
throw new Error(`CM_ALLOWED_IPS: invalid octet in "${entry}". Octets must be 0-255.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const network = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
157
|
+
const mask = 0xFFFFFFFF >>> 0;
|
|
158
|
+
ranges.push({ network, mask });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Neither valid IP nor valid CIDR
|
|
162
|
+
throw new Error(`CM_ALLOWED_IPS: invalid entry "${entry}". Expected IPv4 address or CIDR notation.`);
|
|
163
|
+
}
|
|
164
|
+
return ranges;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if an IP address is allowed by any of the given CIDR ranges.
|
|
168
|
+
* Multiple ranges are evaluated with OR logic (match any = allowed).
|
|
169
|
+
*/
|
|
170
|
+
function isIpAllowed(ip, ranges) {
|
|
171
|
+
if (ranges.length === 0)
|
|
172
|
+
return false;
|
|
173
|
+
const normalized = normalizeIp(ip);
|
|
174
|
+
const ipInt = ipToInt(normalized);
|
|
175
|
+
if (ipInt === null)
|
|
176
|
+
return false;
|
|
177
|
+
for (const range of ranges) {
|
|
178
|
+
if ((ipInt & range.mask) >>> 0 === range.network) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get client IP from request headers.
|
|
186
|
+
*
|
|
187
|
+
* [S1-004] Request parsing responsibility - separate from CIDR matching
|
|
188
|
+
* (isIpAllowed). These are different responsibilities: request parsing vs
|
|
189
|
+
* IP range evaluation. If proxy-related settings grow (e.g., trusted proxies
|
|
190
|
+
* list via CM_TRUSTED_PROXIES), consider splitting to a separate module
|
|
191
|
+
* (e.g., request-ip.ts).
|
|
192
|
+
*
|
|
193
|
+
* [S4-001] WARNING: When CM_TRUST_PROXY=true, the leftmost IP from
|
|
194
|
+
* X-Forwarded-For is used. An attacker can inject arbitrary IPs at the
|
|
195
|
+
* front of the header. The reverse proxy MUST overwrite X-Forwarded-For
|
|
196
|
+
* with the client IP it received (trusted proxy sets the client IP it
|
|
197
|
+
* received as the first entry). If the proxy does not do this correctly,
|
|
198
|
+
* IP restriction bypass is possible.
|
|
199
|
+
* Future extension: introduce CM_TRUSTED_PROXIES for a trusted proxy IP
|
|
200
|
+
* list and switch to rightmost-non-trusted-IP extraction.
|
|
201
|
+
*
|
|
202
|
+
* @param headers - Request headers with get() method
|
|
203
|
+
* @returns Client IP string or null
|
|
204
|
+
*/
|
|
205
|
+
function getClientIp(headers) {
|
|
206
|
+
if (trustProxy) {
|
|
207
|
+
// Trust X-Forwarded-For when CM_TRUST_PROXY=true
|
|
208
|
+
const xff = headers.get('x-forwarded-for');
|
|
209
|
+
if (xff) {
|
|
210
|
+
const firstIp = xff.split(',')[0].trim();
|
|
211
|
+
if (firstIp)
|
|
212
|
+
return firstIp;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Default: use X-Real-IP (set by server.ts from socket.remoteAddress)
|
|
216
|
+
return headers.get('x-real-ip') || null;
|
|
217
|
+
}
|
|
218
|
+
// [S1-001] Module-level cache of parsed ranges
|
|
219
|
+
// Shared by middleware.ts and ws-server.ts via getAllowedRanges()
|
|
220
|
+
const cachedRanges = allowedIpsEnv.length > 0
|
|
221
|
+
? parseAllowedIps(allowedIpsEnv)
|
|
222
|
+
: [];
|
|
223
|
+
/**
|
|
224
|
+
* Return the cached allowed CIDR ranges.
|
|
225
|
+
* Parsed once at module initialization from CM_ALLOWED_IPS.
|
|
226
|
+
*
|
|
227
|
+
* [S1-001] Use this instead of calling parseAllowedIps() each time.
|
|
228
|
+
* DRY cache strategy unified for HTTP and WebSocket layers.
|
|
229
|
+
*/
|
|
230
|
+
function getAllowedRanges() {
|
|
231
|
+
return cachedRanges;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check if IP restriction is enabled.
|
|
235
|
+
*
|
|
236
|
+
* [S1-003] Uses module-scope captured allowedIpsEnv (not process.env).
|
|
237
|
+
* Ensures cache consistency with getAllowedRanges().
|
|
238
|
+
*/
|
|
239
|
+
function isIpRestrictionEnabled() {
|
|
240
|
+
return allowedIpsEnv.length > 0;
|
|
241
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* WebSocket Server for Real-time Communication
|
|
4
4
|
* Manages WebSocket connections and room-based message broadcasting
|
|
5
|
+
* Issue #331: WebSocket authentication via Cookie header
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.setupWebSocket = setupWebSocket;
|
|
@@ -10,14 +11,31 @@ exports.broadcastMessage = broadcastMessage;
|
|
|
10
11
|
exports.cleanupRooms = cleanupRooms;
|
|
11
12
|
exports.closeWebSocket = closeWebSocket;
|
|
12
13
|
const ws_1 = require("ws");
|
|
14
|
+
const auth_1 = require("./auth");
|
|
15
|
+
const ip_restriction_1 = require("./ip-restriction");
|
|
13
16
|
// Global state
|
|
14
17
|
let wss = null;
|
|
15
18
|
const clients = new Map();
|
|
16
19
|
const rooms = new Map();
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
21
|
+
* Check if a WebSocket error is an expected non-fatal error.
|
|
22
|
+
* Common causes include mobile browser disconnects sending malformed close frames.
|
|
19
23
|
*
|
|
20
|
-
* @param
|
|
24
|
+
* @param error - Error with optional code property
|
|
25
|
+
* @returns true if the error is expected and can be silently handled
|
|
26
|
+
*/
|
|
27
|
+
function isExpectedWebSocketError(error) {
|
|
28
|
+
return (error.code === 'WS_ERR_INVALID_CLOSE_CODE' ||
|
|
29
|
+
error.message?.includes('Invalid WebSocket frame') ||
|
|
30
|
+
error.message?.includes('write after end') ||
|
|
31
|
+
error.message?.includes('ECONNRESET') ||
|
|
32
|
+
error.message?.includes('EPIPE'));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Setup WebSocket server on HTTP or HTTPS server
|
|
36
|
+
* Issue #331: Added auth check on WebSocket upgrade
|
|
37
|
+
*
|
|
38
|
+
* @param server - HTTP or HTTPS server instance
|
|
21
39
|
*
|
|
22
40
|
* @example
|
|
23
41
|
* ```typescript
|
|
@@ -31,10 +49,44 @@ function setupWebSocket(server) {
|
|
|
31
49
|
// Handle upgrade requests - only accept app WebSocket connections, not Next.js HMR
|
|
32
50
|
server.on('upgrade', (request, socket, head) => {
|
|
33
51
|
const pathname = request.url || '/';
|
|
34
|
-
// Let Next.js handle its own HMR WebSocket connections
|
|
52
|
+
// Let Next.js handle its own HMR WebSocket connections in development.
|
|
53
|
+
// In production there are no /_next/ WebSocket connections (no HMR).
|
|
54
|
+
// Leaving the socket unhandled in production can trigger the Node.js 'request'
|
|
55
|
+
// event as a fallback on Node.js 19+, causing TypeError in handleRequestImpl
|
|
56
|
+
// because the response has no setHeader (Issue #331).
|
|
35
57
|
if (pathname.startsWith('/_next/')) {
|
|
58
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
59
|
+
socket.write('HTTP/1.1 426 Upgrade Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
|
|
60
|
+
socket.destroy();
|
|
61
|
+
}
|
|
36
62
|
return;
|
|
37
63
|
}
|
|
64
|
+
// Issue #332: WebSocket IP restriction
|
|
65
|
+
// [S2-008] Uses request.socket.remoteAddress directly (not getClientIp()).
|
|
66
|
+
// getClientIp() is for HTTP headers (X-Real-IP/X-Forwarded-For);
|
|
67
|
+
// WebSocket upgrade gets IP from the socket connection directly.
|
|
68
|
+
if ((0, ip_restriction_1.isIpRestrictionEnabled)()) {
|
|
69
|
+
const wsClientIp = (0, ip_restriction_1.normalizeIp)(request.socket.remoteAddress || '');
|
|
70
|
+
if (!(0, ip_restriction_1.isIpAllowed)(wsClientIp, (0, ip_restriction_1.getAllowedRanges)())) {
|
|
71
|
+
// [S4-004] Log injection prevention: normalizeIp() + substring(0, 45)
|
|
72
|
+
const safeIp = wsClientIp.substring(0, 45);
|
|
73
|
+
console.warn(`[IP-RESTRICTION] WebSocket denied: ${safeIp}`);
|
|
74
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
75
|
+
socket.destroy();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Issue #331: WebSocket authentication via Cookie header
|
|
80
|
+
if ((0, auth_1.isAuthEnabled)()) {
|
|
81
|
+
const cookieHeader = request.headers.cookie || '';
|
|
82
|
+
const cookies = (0, auth_1.parseCookies)(cookieHeader);
|
|
83
|
+
const token = cookies[auth_1.AUTH_COOKIE_NAME];
|
|
84
|
+
if (!token || !(0, auth_1.verifyToken)(token)) {
|
|
85
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
|
|
86
|
+
socket.destroy();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
38
90
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
39
91
|
wss.emit('connection', ws, request);
|
|
40
92
|
});
|
|
@@ -57,13 +109,7 @@ function setupWebSocket(server) {
|
|
|
57
109
|
const socket = ws._socket;
|
|
58
110
|
if (socket) {
|
|
59
111
|
socket.on('error', (err) => {
|
|
60
|
-
|
|
61
|
-
const isExpectedError = err.code === 'WS_ERR_INVALID_CLOSE_CODE' ||
|
|
62
|
-
err.message?.includes('Invalid WebSocket frame') ||
|
|
63
|
-
err.message?.includes('write after end') ||
|
|
64
|
-
err.message?.includes('ECONNRESET') ||
|
|
65
|
-
err.message?.includes('EPIPE');
|
|
66
|
-
if (!isExpectedError) {
|
|
112
|
+
if (!isExpectedWebSocketError(err)) {
|
|
67
113
|
console.error('[WS Socket] Error:', err.message);
|
|
68
114
|
}
|
|
69
115
|
// Immediately destroy the socket to prevent further errors
|
|
@@ -94,11 +140,7 @@ function setupWebSocket(server) {
|
|
|
94
140
|
});
|
|
95
141
|
// Handle errors (including invalid close codes from mobile browsers)
|
|
96
142
|
ws.on('error', (error) => {
|
|
97
|
-
|
|
98
|
-
const isExpectedError = error.code === 'WS_ERR_INVALID_CLOSE_CODE' ||
|
|
99
|
-
error.message?.includes('Invalid WebSocket frame') ||
|
|
100
|
-
error.message?.includes('write after end');
|
|
101
|
-
if (!isExpectedError) {
|
|
143
|
+
if (!isExpectedWebSocketError(error)) {
|
|
102
144
|
console.error('[WS] WebSocket error:', error.message);
|
|
103
145
|
}
|
|
104
146
|
// Immediately terminate to prevent further errors
|
|
@@ -111,7 +153,7 @@ function setupWebSocket(server) {
|
|
|
111
153
|
handleDisconnect(ws);
|
|
112
154
|
});
|
|
113
155
|
});
|
|
114
|
-
|
|
156
|
+
// WebSocket server initialization complete (no log in production per CLAUDE.md)
|
|
115
157
|
}
|
|
116
158
|
/**
|
|
117
159
|
* Handle incoming WebSocket message
|
|
@@ -137,7 +179,6 @@ function handleMessage(ws, message) {
|
|
|
137
179
|
function handleSubscribe(ws, worktreeId) {
|
|
138
180
|
const clientInfo = clients.get(ws);
|
|
139
181
|
if (!clientInfo) {
|
|
140
|
-
console.log(`[WS] handleSubscribe: clientInfo not found for worktreeId: ${worktreeId}`);
|
|
141
182
|
return;
|
|
142
183
|
}
|
|
143
184
|
// Add worktreeId to client's subscriptions
|
|
@@ -148,7 +189,7 @@ function handleSubscribe(ws, worktreeId) {
|
|
|
148
189
|
}
|
|
149
190
|
const room = rooms.get(worktreeId);
|
|
150
191
|
room.add(ws);
|
|
151
|
-
|
|
192
|
+
// Client subscribed (no log in production per CLAUDE.md)
|
|
152
193
|
}
|
|
153
194
|
/**
|
|
154
195
|
* Unsubscribe client from a worktree room
|
|
@@ -168,20 +209,14 @@ function handleUnsubscribe(ws, worktreeId) {
|
|
|
168
209
|
rooms.delete(worktreeId);
|
|
169
210
|
}
|
|
170
211
|
}
|
|
171
|
-
|
|
212
|
+
// Client unsubscribed (no log in production per CLAUDE.md)
|
|
172
213
|
}
|
|
173
214
|
/**
|
|
174
215
|
* Broadcast message to all clients in a worktree room
|
|
175
216
|
*/
|
|
176
217
|
function handleBroadcast(worktreeId, data) {
|
|
177
218
|
const room = rooms.get(worktreeId);
|
|
178
|
-
|
|
179
|
-
if (!room) {
|
|
180
|
-
console.log(`[WS] No room found for ${worktreeId}`);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (room.size === 0) {
|
|
184
|
-
console.log(`[WS] Room for ${worktreeId} is empty`);
|
|
219
|
+
if (!room || room.size === 0) {
|
|
185
220
|
return;
|
|
186
221
|
}
|
|
187
222
|
try {
|
|
@@ -190,21 +225,16 @@ function handleBroadcast(worktreeId, data) {
|
|
|
190
225
|
worktreeId,
|
|
191
226
|
data,
|
|
192
227
|
});
|
|
193
|
-
let successCount = 0;
|
|
194
|
-
let errorCount = 0;
|
|
195
228
|
room.forEach((client) => {
|
|
196
229
|
if (client.readyState === ws_1.WebSocket.OPEN) {
|
|
197
230
|
try {
|
|
198
231
|
client.send(message);
|
|
199
|
-
successCount++;
|
|
200
232
|
}
|
|
201
233
|
catch (sendError) {
|
|
202
|
-
errorCount++;
|
|
203
234
|
console.error(`Error sending WebSocket message to client:`, sendError);
|
|
204
235
|
}
|
|
205
236
|
}
|
|
206
237
|
});
|
|
207
|
-
console.log(`Broadcast to worktree ${worktreeId}: ${successCount}/${room.size} clients (${errorCount} errors)`);
|
|
208
238
|
}
|
|
209
239
|
catch (broadcastError) {
|
|
210
240
|
console.error(`Error broadcasting to worktree ${worktreeId}:`, broadcastError);
|
|
@@ -309,7 +339,7 @@ function cleanupRooms(worktreeIds) {
|
|
|
309
339
|
});
|
|
310
340
|
// Delete the room
|
|
311
341
|
rooms.delete(worktreeId);
|
|
312
|
-
|
|
342
|
+
// Room cleaned up (no log in production per CLAUDE.md)
|
|
313
343
|
}
|
|
314
344
|
}
|
|
315
345
|
}
|
|
@@ -329,6 +359,6 @@ function closeWebSocket() {
|
|
|
329
359
|
// Close server
|
|
330
360
|
wss.close();
|
|
331
361
|
wss = null;
|
|
332
|
-
|
|
362
|
+
// WebSocket server closed (no log in production per CLAUDE.md)
|
|
333
363
|
}
|
|
334
364
|
}
|
|
@@ -17,6 +17,7 @@ exports.CATEGORY_LABELS = {
|
|
|
17
17
|
review: 'Review',
|
|
18
18
|
documentation: 'Documentation',
|
|
19
19
|
workflow: 'Workflow',
|
|
20
|
+
skill: 'Skills', // Issue #343: Skills category label
|
|
20
21
|
// Standard command category labels (Issue #56)
|
|
21
22
|
'standard-session': 'Standard (Session)',
|
|
22
23
|
'standard-config': 'Standard (Config)',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commandmate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Git worktree management with Claude CLI and tmux sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -95,6 +95,6 @@
|
|
|
95
95
|
"tsc-alias": "~1.8.16",
|
|
96
96
|
"tsx": "^4.20.6",
|
|
97
97
|
"typescript": "^5.5.0",
|
|
98
|
-
"vitest": "^4.0.
|
|
98
|
+
"vitest": "^4.0.16"
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";exports.id=9238,exports.ids=[9238],exports.modules={11865:(e,t,s)=>{s.d(t,{Ix:()=>m,pm:()=>h});var a=s(10326),r=s(17577),l=s(28567),n=s(69669),i=s(18019),o=s(94019),c=s(22202);let d=3e3;function u({type:e,iconColor:t}){let s=`h-5 w-5 ${t}`;switch(e){case"success":return a.jsx(l.Z,{className:s,"data-testid":"toast-icon-success"});case"error":return a.jsx(n.Z,{className:s,"data-testid":"toast-icon-error"});default:return a.jsx(i.Z,{className:s,"data-testid":"toast-icon-info"})}}function x({id:e,message:t,type:s,onClose:l,duration:n=d}){let i=(0,r.useRef)(null),c=function(e){switch(e){case"success":return{bgColor:"bg-green-50",borderColor:"border-green-200",textColor:"text-green-800",iconColor:"text-green-500"};case"error":return{bgColor:"bg-red-50",borderColor:"border-red-200",textColor:"text-red-800",iconColor:"text-red-500"};default:return{bgColor:"bg-blue-50",borderColor:"border-blue-200",textColor:"text-blue-800",iconColor:"text-blue-500"}}}(s),x=(0,r.useCallback)(()=>{i.current&&clearTimeout(i.current),l(e)},[e,l]);return(0,a.jsxs)("div",{"data-testid":`toast-${e}`,role:"alert",className:`
|
|
2
|
-
${c.bgColor}
|
|
3
|
-
${c.borderColor}
|
|
4
|
-
${c.textColor}
|
|
5
|
-
border rounded-lg shadow-lg p-4 min-w-[300px] max-w-[400px]
|
|
6
|
-
flex items-start gap-3
|
|
7
|
-
animate-slide-in
|
|
8
|
-
`,children:[a.jsx(u,{type:s,iconColor:c.iconColor}),a.jsx("p",{className:"flex-1 text-sm font-medium",children:t}),a.jsx("button",{"data-testid":"toast-close-button",onClick:x,"aria-label":"Close notification",className:`
|
|
9
|
-
${c.textColor}
|
|
10
|
-
hover:opacity-70
|
|
11
|
-
focus:outline-none focus:ring-2 focus:ring-offset-2
|
|
12
|
-
transition-opacity
|
|
13
|
-
`,children:a.jsx(o.Z,{className:"h-4 w-4"})})]})}function m({toasts:e,onClose:t}){return a.jsx("div",{"data-testid":"toast-container","aria-live":"polite",className:"fixed bottom-4 right-4 flex flex-col gap-2",style:{zIndex:c.k.TOAST},children:e.map(e=>a.jsx(x,{id:e.id,message:e.message,type:e.type,onClose:t,duration:e.duration},e.id))})}function h(){let[e,t]=(0,r.useState)([]),s=(0,r.useRef)(0);return{toasts:e,showToast:(0,r.useCallback)((e,a="info",r=d)=>{let l=`toast-${++s.current}-${Date.now()}`,n={id:l,message:e,type:a,duration:r};return t(e=>[...e,n]),l},[]),removeToast:(0,r.useCallback)(e=>{t(t=>t.filter(t=>t.id!==e))},[]),clearToasts:(0,r.useCallback)(()=>{t([])},[])}}},2583:(e,t,s)=>{s.d(t,{Vw:()=>S});var a=s(10326),r=s(17577);s(90434);var l=s(87403),n=s(2982),i=s(35047),o=s(3885),c=s(61421);function d({status:e,label:t}){let s=c.F4[e],r=`${t}: ${s.label}`;return"spinner"===s.type?a.jsx("span",{className:`w-2 h-2 rounded-full flex-shrink-0 border-2 border-t-transparent animate-spin ${s.className}`,title:r,"aria-label":r}):a.jsx("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${s.className}`,title:r,"aria-label":r})}let u=(0,r.memo)(function({branch:e,isSelected:t,onClick:s}){return(0,a.jsxs)("button",{"data-testid":"branch-list-item",onClick:s,"aria-current":t?"true":void 0,className:`
|
|
14
|
-
w-full px-4 py-3 flex flex-col gap-1
|
|
15
|
-
hover:bg-gray-800 transition-colors
|
|
16
|
-
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500
|
|
17
|
-
${t?"bg-gray-700 border-l-2 border-blue-500":"border-l-2 border-transparent"}
|
|
18
|
-
`,children:[(0,a.jsxs)("div",{className:"flex items-center gap-3 w-full",children:[e.cliStatus&&(0,a.jsxs)("div",{className:"flex items-center gap-1 flex-shrink-0","aria-label":"CLI tool status",children:[a.jsx(d,{status:e.cliStatus.claude,label:"Claude"}),a.jsx(d,{status:e.cliStatus.codex,label:"Codex"})]}),(0,a.jsxs)("div",{className:"flex-1 min-w-0 text-left",children:[a.jsx("p",{className:"text-sm font-medium text-white truncate",children:e.name}),a.jsx("p",{className:"text-xs text-gray-400 truncate",children:e.repositoryName})]}),e.hasUnread&&a.jsx("span",{"data-testid":"unread-indicator",className:"w-2 h-2 rounded-full bg-blue-500 flex-shrink-0","aria-label":"Has unread messages"})]}),e.description&&a.jsx("div",{"data-testid":"branch-description",className:"pl-6 pr-2 mt-1 text-left",children:a.jsx("p",{className:"text-xs text-gray-400 line-clamp-2",children:e.description})})]})}),x=[{key:"updatedAt",label:"Updated"},{key:"repositoryName",label:"Repository"},{key:"branchName",label:"Branch"},{key:"status",label:"Status"}],m=(0,r.memo)(function(){let{sortKey:e,sortDirection:t,setSortKey:s,setSortDirection:n}=(0,l.Sz)(),[i,o]=(0,r.useState)(!1),c=(0,r.useRef)(null);(0,r.useEffect)(()=>{function e(e){c.current&&!c.current.contains(e.target)&&o(!1)}if(i)return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[i]),(0,r.useEffect)(()=>{function e(e){"Escape"===e.key&&o(!1)}if(i)return document.addEventListener("keydown",e),()=>document.removeEventListener("keydown",e)},[i]);let d=(0,r.useCallback)(()=>{o(e=>!e)},[]),u=(0,r.useCallback)(a=>{a===e?n("asc"===t?"desc":"asc"):(s(a),n("updatedAt"===a?"desc":"asc")),o(!1)},[e,t,s,n]),m=(0,r.useCallback)(()=>{n("asc"===t?"desc":"asc")},[t,n]),b=x.find(t=>t.key===e)?.label||"Sort";return(0,a.jsxs)("div",{ref:c,className:"relative","data-testid":"sort-selector",children:[(0,a.jsxs)("div",{className:"flex items-center gap-1",children:[(0,a.jsxs)("button",{type:"button",onClick:d,"aria-expanded":i,"aria-haspopup":"listbox","aria-label":`Sort by ${b}`,className:" flex items-center gap-1 px-2 py-1 rounded text-xs text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors ",children:[a.jsx(h,{className:"w-3 h-3"}),a.jsx("span",{className:"hidden sm:inline",children:b})]}),a.jsx("button",{type:"button",onClick:m,"aria-label":"asc"===t?"Sort ascending":"Sort descending",className:" p-1 rounded text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors ",children:"asc"===t?a.jsx(p,{className:"w-3 h-3"}):a.jsx(g,{className:"w-3 h-3"})})]}),i&&a.jsx("div",{role:"listbox","aria-label":"Sort options",className:" absolute right-0 top-full mt-1 z-50 min-w-[140px] py-1 rounded-md shadow-lg bg-gray-800 border border-gray-600 ",children:x.map(s=>(0,a.jsxs)("button",{type:"button",role:"option","aria-selected":e===s.key,onClick:()=>u(s.key),className:`
|
|
19
|
-
w-full px-3 py-2 text-left text-sm
|
|
20
|
-
flex items-center justify-between
|
|
21
|
-
hover:bg-gray-700 transition-colors
|
|
22
|
-
${e===s.key?"text-blue-400":"text-gray-300"}
|
|
23
|
-
`,children:[a.jsx("span",{children:s.label}),e===s.key&&a.jsx("span",{className:"text-xs",children:"asc"===t?"ASC":"DESC"})]},s.key))})]})});function h({className:e}){return a.jsx("svg",{className:e,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"})})}function p({className:e}){return a.jsx("svg",{className:e,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"})})}function g({className:e}){return a.jsx("svg",{className:e,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"})})}var b=s(34643);let f=["en","ja"],y={en:"English",ja:"日本語"};function v(){let{currentLocale:e,switchLocale:t}={currentLocale:(0,b.bU)(),switchLocale:e=>{f.includes(e)&&(function(e){let t="https:"===window.location.protocol;document.cookie=`locale=${e};path=/;max-age=31536000;SameSite=Lax${t?";Secure":""}`}(e),localStorage.setItem("locale",e),window.location.reload())}};return a.jsx("select",{value:e,onChange:e=>t(e.target.value),"aria-label":"Language",className:" w-full px-3 py-2 rounded-md bg-gray-800 text-white border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm ",children:f.map(e=>a.jsx("option",{value:e,children:y[e]},e))})}var j=s(18421);let w={waiting:0,ready:1,running:2,generating:3,idle:4},N=(0,r.memo)(function(){let e=(0,i.useRouter)(),{worktrees:t,selectedWorktreeId:s,selectWorktree:n}=(0,o.Mu)(),{closeMobileDrawer:c,sortKey:d,sortDirection:x}=(0,l.Sz)(),[h,p]=(0,r.useState)(""),g=(0,r.useMemo)(()=>{let e=t.map(j.I_),s=e;if(h.trim()){let t=h.toLowerCase();s=e.filter(e=>e.name.toLowerCase().includes(t)||e.repositoryName.toLowerCase().includes(t))}return function(e,t,s){let a=[...e];return a.sort((e,a)=>{let r=0;switch(t){case"updatedAt":{let t=e=>e?e instanceof Date?e.getTime():new Date(e).getTime():0,s=t(e.lastActivity);r=t(a.lastActivity)-s;break}case"repositoryName":{let t=e.repositoryName.toLowerCase(),s=a.repositoryName.toLowerCase();r=t.localeCompare(s);break}case"branchName":{let t=e.name.toLowerCase(),s=a.name.toLowerCase();r=t.localeCompare(s);break}case"status":r=w[e.status]-w[a.status]}return("updatedAt"===t?"desc"===s:"asc"===s)?r:-r}),a}(s,d,x)},[t,h,d,x]),b=t=>{n(t),e.push(`/worktrees/${t}`),c()};return(0,a.jsxs)("nav",{"data-testid":"sidebar","aria-label":"Branch navigation",className:"h-full flex flex-col bg-gray-900 text-white",role:"navigation",children:[a.jsx("div",{"data-testid":"sidebar-header",className:"flex-shrink-0 px-4 py-4 border-b border-gray-700",children:(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[a.jsx("h2",{className:"text-lg font-semibold text-white",children:"Branches"}),a.jsx(m,{})]})}),a.jsx("div",{className:"flex-shrink-0 px-4 py-3 border-b border-gray-700",children:a.jsx("input",{type:"text",placeholder:"Search branches...",value:h,onChange:e=>p(e.target.value),className:" w-full px-3 py-2 rounded-md bg-gray-800 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent "})}),a.jsx("div",{"data-testid":"branch-list",className:"flex-1 overflow-y-auto",children:0===g.length?a.jsx("div",{className:"px-4 py-8 text-center text-gray-400",children:h?"No branches found":"No branches available"}):g.map(e=>a.jsx(u,{branch:e,isSelected:e.id===s,onClick:()=>b(e.id)},e.id))}),a.jsx("div",{className:"flex-shrink-0 px-4 py-3 border-t border-gray-700",children:a.jsx(v,{})})]})});var k=s(22202);let C="transform transition-transform duration-300 ease-out",S=(0,r.memo)(function({children:e}){let{isOpen:t,isMobileDrawerOpen:s,closeMobileDrawer:r}=(0,l.Sz)();return(0,n.d)()?(0,a.jsxs)("div",{"data-testid":"app-shell",className:"h-screen flex flex-col",children:[s&&a.jsx("div",{"data-testid":"drawer-overlay",className:"fixed inset-0 bg-black/50 z-40",onClick:r,"aria-hidden":"true"}),a.jsx("aside",{"data-testid":"sidebar-container",className:`
|
|
24
|
-
fixed left-0 top-0 h-full w-72 z-50
|
|
25
|
-
${C}
|
|
26
|
-
${s?"translate-x-0":"-translate-x-full"}
|
|
27
|
-
`,role:"complementary",children:a.jsx(N,{})}),a.jsx("main",{className:"flex-1 min-h-0 overflow-hidden",role:"main",children:e})]}):(0,a.jsxs)("div",{"data-testid":"app-shell",className:"h-screen flex",children:[a.jsx("aside",{"data-testid":"sidebar-container",className:`
|
|
28
|
-
fixed left-0 top-0 h-full w-72
|
|
29
|
-
${C}
|
|
30
|
-
${t?"translate-x-0":"-translate-x-full"}
|
|
31
|
-
`,style:{zIndex:k.k.SIDEBAR},role:"complementary","aria-hidden":!t,children:a.jsx(N,{})}),a.jsx("main",{className:`
|
|
32
|
-
flex-1 min-w-0 h-full overflow-hidden
|
|
33
|
-
transition-[padding] duration-300 ease-out
|
|
34
|
-
${t?"md:pl-72":"md:pl-0"}
|
|
35
|
-
`,role:"main",children:e})]})})},28676:(e,t,s)=>{s.d(t,{u:()=>i});var a=s(10326),r=s(17577),l=s(60962),n=s(22202);function i({isOpen:e,onClose:t,title:s,children:i,size:o="lg",showCloseButton:c=!0,disableClose:d=!1}){let u=(0,r.useRef)(null);return e?(0,l.createPortal)((0,a.jsxs)("div",{className:"fixed inset-0 overflow-y-auto",style:{zIndex:n.k.MODAL},children:[a.jsx("div",{className:"fixed inset-0 bg-black bg-opacity-50 transition-opacity",onClick:d?void 0:t}),a.jsx("div",{className:"relative flex min-h-full items-center justify-center p-2 sm:p-4",children:(0,a.jsxs)("div",{ref:u,className:`relative w-full ${{sm:"max-w-[calc(100vw-2rem)] sm:max-w-md",md:"max-w-[calc(100vw-2rem)] sm:max-w-2xl",lg:"max-w-[calc(100vw-2rem)] sm:max-w-4xl",xl:"max-w-[calc(100vw-2rem)] sm:max-w-6xl",full:"max-w-[calc(100vw-2rem)] sm:max-w-[95vw]"}[o]} max-h-[calc(100vh-1rem)] sm:max-h-[calc(100vh-2rem)] flex flex-col bg-white rounded-lg shadow-xl transform transition-all`,children:[(s||c)&&(0,a.jsxs)("div",{className:"flex items-center justify-between px-4 sm:px-6 py-3 sm:py-4 border-b border-gray-200 flex-shrink-0",children:[a.jsx("h3",{className:"text-base sm:text-lg font-semibold text-gray-900 truncate pr-2",children:s}),c&&a.jsx("button",{onClick:t,className:"text-gray-400 hover:text-gray-600 transition-colors flex-shrink-0",children:a.jsx("svg",{className:"w-5 h-5 sm:w-6 sm:h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),a.jsx("div",{className:"px-4 sm:px-6 py-3 sm:py-4 overflow-y-auto flex-1 min-h-0",children:i})]})})]}),document.body):null}},77758:(e,t,s)=>{s.d(t,{Ct:()=>c,zx:()=>r,Zb:()=>l,aY:()=>o,Ol:()=>n,ll:()=>i,u_:()=>d.u});var a=s(10326);function r({variant:e="primary",size:t="md",fullWidth:s=!1,loading:r=!1,disabled:l,className:n="",children:i,...o}){let c=["btn",{primary:"btn-primary",secondary:"btn-secondary",danger:"btn-danger",ghost:"bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-500"}[e],{sm:"btn-sm",md:"",lg:"btn-lg"}[t],s?"w-full":"",l||r?"opacity-50 cursor-not-allowed":"",n].filter(Boolean).join(" ");return(0,a.jsxs)("button",{className:c,disabled:l||r,...o,children:[r&&(0,a.jsxs)("svg",{className:"animate-spin -ml-1 mr-2 h-4 w-4",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[a.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),a.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),i]})}function l({hover:e=!1,padding:t="md",className:s="",children:r,...l}){let n=["card",e?"card-hover":"",{none:"",sm:"p-3",md:"p-4",lg:"p-6"}[t],s].filter(Boolean).join(" ");return a.jsx("div",{className:n,...l,children:r})}function n({className:e="",children:t,...s}){return a.jsx("div",{className:`mb-3 ${e}`,...s,children:t})}function i({className:e="",children:t,...s}){return a.jsx("h3",{className:`text-lg font-semibold text-gray-900 ${e}`,...s,children:t})}function o({className:e="",children:t,...s}){return a.jsx("div",{className:e,...s,children:t})}function c({variant:e="gray",dot:t=!1,className:s="",children:r,...l}){let n=["badge",{success:"badge-success",warning:"badge-warning",error:"badge-error",info:"badge-info",gray:"badge-gray"}[e],s].filter(Boolean).join(" ");return(0,a.jsxs)("span",{className:n,...l,children:[t&&a.jsx("span",{className:`mr-1.5 inline-block h-2 w-2 rounded-full ${{success:"bg-green-600",warning:"bg-yellow-600",error:"bg-red-600",info:"bg-blue-600",gray:"bg-gray-600"}[e]}`,"aria-hidden":"true"}),r]})}s(17577);var d=s(28676)},11867:(e,t,s)=>{s.d(t,{n:()=>d});var a=s(10326),r=s(17577),l=s(77758),n=s(11865),i=s(73002),o=s(91515),c=s(51223);function d({worktreeId:e}){let[t,s]=(0,r.useState)([]),[d,u]=(0,r.useState)(null),[x,m]=(0,r.useState)(null),[h,p]=(0,r.useState)(!0),[g,b]=(0,r.useState)(null),[f,y]=(0,r.useState)(""),[v,j]=(0,r.useState)(0),[w,N]=(0,r.useState)("all"),[k,C]=(0,r.useState)(!1),{toasts:S,showToast:L,removeToast:z}=(0,n.pm)(),$=(0,r.useMemo)(()=>"all"===w?t:t.filter(e=>e.toLowerCase().includes(w)),[t,w]),M=async t=>{try{p(!0),b(null);let s=await i.Iv.getLogFile(e,t);m(s.content),u(t),y(""),j(0)}catch(e){b((0,i.zG)(e))}finally{p(!1)}},E=(0,r.useCallback)(async()=>{if(d)try{C(!0);let t=await i.Iv.getLogFile(e,d,{sanitize:!0});await (0,o.v)(t.content),L("Log copied to clipboard (sanitized)","success")}catch(e){L(`Failed to export log: ${(0,i.zG)(e)}`,"error")}finally{C(!1)}},[e,d,L]),T=(0,r.useMemo)(()=>{let e;if(!f||!x)return[];let t=RegExp((0,c.hr)(f),"gi"),s=[];for(;null!==(e=t.exec(x));)s.push({index:e.index,length:e[0].length});return s},[f,x]),A=()=>{T.length>0&&j(e=>(e+1)%T.length)},I=()=>{T.length>0&&j(e=>(e-1+T.length)%T.length)},D=(0,r.useMemo)(()=>{if(!x||!f||0===T.length)return x;let e="",t=0;return T.forEach((s,a)=>{e+=(0,c.Xv)(x.substring(t,s.index));let r=x.substring(s.index,s.index+s.length),l=a===v;e+=`<mark class="${l?"bg-yellow-400 text-black":"bg-yellow-200 text-black"}" data-match-index="${a}">${(0,c.Xv)(r)}</mark>`,t=s.index+s.length}),e+=(0,c.Xv)(x.substring(t))},[x,f,T,v]);return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)(l.Zb,{padding:"md",children:[a.jsx(l.Ol,{children:(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[a.jsx(l.ll,{children:"Log Files"}),a.jsx(l.Ct,{variant:"gray",children:$.length})]}),(0,a.jsxs)("div",{className:"flex gap-2 flex-wrap",children:[(0,a.jsxs)(l.zx,{variant:"all"===w?"primary":"ghost",size:"sm",onClick:()=>N("all"),children:["All (",t.length,")"]}),(0,a.jsxs)(l.zx,{variant:"claude"===w?"primary":"ghost",size:"sm",onClick:()=>N("claude"),children:["Claude (",t.filter(e=>e.toLowerCase().includes("claude")).length,")"]}),(0,a.jsxs)(l.zx,{variant:"codex"===w?"primary":"ghost",size:"sm",onClick:()=>N("codex"),children:["Codex (",t.filter(e=>e.toLowerCase().includes("codex")).length,")"]}),(0,a.jsxs)(l.zx,{variant:"gemini"===w?"primary":"ghost",size:"sm",onClick:()=>N("gemini"),children:["Gemini (",t.filter(e=>e.toLowerCase().includes("gemini")).length,")"]})]})]})}),(0,a.jsxs)(l.aY,{children:[h&&0===t.length&&a.jsx("div",{className:"text-center py-4",children:a.jsx("div",{className:"inline-block animate-spin rounded-full h-6 w-6 border-4 border-gray-300 border-t-blue-600"})}),g&&a.jsx("div",{className:"p-3 bg-red-50 border border-red-200 rounded text-sm text-red-800",children:g}),!h&&0===$.length&&!g&&a.jsx("p",{className:"text-sm text-gray-600 text-center py-4",children:"all"===w?"No log files found":`No ${w} log files found`}),$.length>0&&a.jsx("div",{className:"space-y-2",children:$.map(e=>a.jsx("button",{onClick:()=>M(e),className:`w-full text-left px-3 py-2 rounded text-sm font-mono transition-colors ${d===e?"bg-blue-50 text-blue-700 border border-blue-200":"hover:bg-gray-50 border border-transparent"}`,children:e},e))})]})]}),d&&(0,a.jsxs)(l.Zb,{padding:"md",children:[a.jsx(l.Ol,{children:(0,a.jsxs)("div",{className:"flex flex-col gap-3",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[a.jsx(l.ll,{className:"font-mono text-base",children:d}),(0,a.jsxs)("div",{className:"flex gap-2",children:[a.jsx(l.zx,{variant:"ghost",size:"sm",onClick:E,disabled:!d||k,title:"Copy sanitized log to clipboard",children:k?"Exporting...":"Export"}),a.jsx(l.zx,{variant:"ghost",size:"sm",onClick:()=>{u(null),m(null),y(""),j(0)},children:"Close"})]})]}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsxs)("div",{className:"relative flex-1",children:[a.jsx("input",{type:"text",value:f,onChange:e=>y(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),e.shiftKey?I():A())},placeholder:"Search in log file...",className:"input w-full pr-20"}),T.length>0&&(0,a.jsxs)("div",{className:"absolute right-2 top-1/2 -translate-y-1/2 text-xs text-gray-500",children:[v+1," / ",T.length]})]}),T.length>0&&(0,a.jsxs)("div",{className:"flex gap-1",children:[a.jsx(l.zx,{variant:"ghost",size:"sm",onClick:I,disabled:0===T.length,title:"Previous match (Shift+Enter)",children:"↑"}),a.jsx(l.zx,{variant:"ghost",size:"sm",onClick:A,disabled:0===T.length,title:"Next match (Enter)",children:"↓"})]})]})]})}),(0,a.jsxs)(l.aY,{children:[h&&a.jsx("div",{className:"text-center py-8",children:a.jsx("div",{className:"inline-block animate-spin rounded-full h-8 w-8 border-4 border-gray-300 border-t-blue-600"})}),!h&&x&&a.jsx("div",{className:"bg-gray-900 text-gray-100 rounded p-4 overflow-x-auto max-h-[500px] scrollbar-thin",children:f&&T.length>0?a.jsx("pre",{className:"text-xs font-mono whitespace-pre-wrap",dangerouslySetInnerHTML:{__html:D||""}}):a.jsx("pre",{className:"text-xs font-mono whitespace-pre-wrap",children:x})}),!h&&f&&0===T.length&&x&&(0,a.jsxs)("div",{className:"text-center py-4 text-sm text-gray-500",children:['No matches found for "',f,'"']})]})]}),a.jsx(n.Ix,{toasts:S,onClose:z})]})}},15470:(e,t,s)=>{s.d(t,{R:()=>x});var a=s(10326),r=s(17577),l=s(73002);function n(e,t){if(!t.trim())return e;let s=t.toLowerCase();return e.map(e=>({...e,commands:e.commands.filter(e=>{let t=e.name.toLowerCase().includes(s),a=e.description.toLowerCase().includes(s);return t||a})})).filter(e=>e.commands.length>0)}function i({groups:e,onSelect:t,highlightedIndex:s=-1,className:r=""}){let l=0;return 0===e.length?a.jsx("div",{className:`text-sm text-gray-500 p-4 text-center ${r}`,children:"No commands available"}):a.jsx("div",{className:`overflow-y-auto ${r}`,children:e.map(e=>(0,a.jsxs)("div",{className:"mb-2",children:[a.jsx("div",{className:"px-3 py-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider bg-gray-50",children:e.label}),a.jsx("div",{children:e.commands.map(e=>{let r=l;l++;let n=r===s;return(0,a.jsxs)("button",{type:"button","data-command-item":!0,"data-highlighted":n,onClick:()=>t(e),className:`w-full px-3 py-2 text-left flex items-start gap-2 hover:bg-blue-50 transition-colors ${n?"bg-blue-100":""}`,children:[(0,a.jsxs)("span",{className:"text-blue-600 font-mono text-sm flex-shrink-0",children:["/",e.name]}),a.jsx("span",{className:"text-gray-600 text-sm truncate",children:e.description})]},e.name)})})]},e.category))})}function o({isOpen:e,groups:t,onSelect:s,onClose:l,isMobile:o=!1,position:c,onFreeInput:d}){let[u,x]=(0,r.useState)(""),[m,h]=(0,r.useState)(0),p=(0,r.useRef)(null),g=(0,r.useMemo)(()=>n(t,u),[t,u]),b=(0,r.useMemo)(()=>g.flatMap(e=>e.commands),[g]),f=(0,r.useCallback)(e=>{s(e),l()},[s,l]);return((0,r.useCallback)(t=>{if(e)switch(t.key){case"Escape":t.preventDefault(),l();break;case"ArrowDown":t.preventDefault(),h(e=>Math.min(e+1,b.length-1));break;case"ArrowUp":t.preventDefault(),h(e=>Math.max(e-1,0));break;case"Enter":t.preventDefault(),b[m]&&f(b[m])}},[e,b,m,l,f]),e)?o?(0,a.jsxs)(a.Fragment,{children:[a.jsx("div",{className:"fixed inset-0 bg-black/50 z-40",onClick:l,"aria-hidden":"true"}),(0,a.jsxs)("div",{"data-testid":"slash-command-bottom-sheet",className:"fixed bottom-0 left-0 right-0 bg-white rounded-t-xl z-50 max-h-[70vh] flex flex-col shadow-xl",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between px-4 py-3 border-b border-gray-200",children:[a.jsx("h2",{className:"text-lg font-semibold",children:"Commands"}),a.jsx("button",{type:"button",onClick:l,"aria-label":"Close",className:"p-2 rounded-full hover:bg-gray-100",children:a.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),a.jsx("div",{className:"px-4 py-2 border-b border-gray-100",children:a.jsx("input",{ref:p,type:"text",value:u,onChange:e=>x(e.target.value),placeholder:"Search commands...",className:"w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"})}),d&&(0,a.jsxs)("button",{type:"button","data-testid":"free-input-button",onClick:()=>d(u),className:"w-full px-4 py-3 text-left border-b border-gray-100 flex items-center gap-2 hover:bg-blue-50 transition-colors",children:[a.jsx("span",{className:"text-blue-600",children:a.jsx("svg",{className:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})})}),a.jsx("span",{className:"text-gray-600",children:"Enter custom command..."})]}),a.jsx(i,{groups:g,onSelect:f,highlightedIndex:m,className:"flex-1 overflow-y-auto pb-20"})]})]}):(0,a.jsxs)("div",{role:"listbox",className:"absolute bg-white border border-gray-200 rounded-lg shadow-lg z-50 w-80 max-h-96 flex flex-col",style:c?{top:c.top,left:c.left}:{bottom:"100%",left:0,marginBottom:"4px"},children:[a.jsx("div",{className:"px-3 py-2 border-b border-gray-100",children:a.jsx("input",{ref:p,type:"text",value:u,onChange:e=>x(e.target.value),placeholder:"Search commands...",className:"w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"})}),d&&(0,a.jsxs)("button",{type:"button","data-testid":"free-input-button",onClick:()=>d(u),className:"w-full px-3 py-2 text-left border-b border-gray-100 flex items-center gap-2 hover:bg-blue-50 transition-colors text-sm",children:[a.jsx("span",{className:"text-blue-600",children:a.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})})}),a.jsx("span",{className:"text-gray-600",children:"Enter custom command..."})]}),a.jsx(i,{groups:g,onSelect:f,highlightedIndex:m,className:"flex-1 overflow-y-auto"}),(0,a.jsxs)("div",{className:"px-3 py-1.5 border-t border-gray-100 text-xs text-gray-400 flex gap-3",children:[(0,a.jsxs)("span",{children:[a.jsx("kbd",{className:"px-1 py-0.5 bg-gray-100 rounded",children:"Enter"})," select"]}),(0,a.jsxs)("span",{children:[a.jsx("kbd",{className:"px-1 py-0.5 bg-gray-100 rounded",children:"Esc"})," close"]})]})]}):null}function c({worktreeId:e,cliToolId:t,disabled:s=!1,onInterrupt:l}){let[n,i]=(0,r.useState)(!1),o=(0,r.useRef)(0),c=(0,r.useCallback)(async()=>{let s=Date.now();if(!(s-o.current<1e3)){o.current=s,i(!0);try{let s=await fetch(`/api/worktrees/${e}/interrupt`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cliToolId:t})});if(s.ok)l?.();else{let e=await s.json().catch(()=>({}));console.error("[InterruptButton] Failed to send interrupt:",e.error||s.statusText)}}catch(e){console.error("[InterruptButton] Error sending interrupt:",e)}finally{i(!1)}}},[e,t,l]);return a.jsx("button",{type:"button",onClick:c,disabled:s||n,className:"flex-shrink-0 p-2 text-orange-600 hover:bg-orange-50 rounded-full transition-colors disabled:text-gray-300 disabled:hover:bg-transparent","aria-label":"Stop processing","data-testid":"interrupt-button",children:n?(0,a.jsxs)("svg",{className:"animate-spin h-5 w-5",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[a.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),a.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}):a.jsx(d,{})})}function d(){return a.jsx("svg",{className:"h-5 w-5",fill:"currentColor",viewBox:"0 0 24 24",children:a.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}var u=s(2982);function x({worktreeId:e,onMessageSent:t,cliToolId:s,isSessionRunning:i=!1}){let[d,x]=(0,r.useState)(""),[m,h]=(0,r.useState)(!1),[p,g]=(0,r.useState)(null),[b,f]=(0,r.useState)(!1),[y,v]=(0,r.useState)(!1),[j,w]=(0,r.useState)(!1),N=(0,r.useRef)(null),k=(0,r.useRef)(null),C=(0,r.useRef)(!1),S=(0,r.useRef)(null),L=(0,u.d)(),{groups:z}=function(e,t){let[s,a]=(0,r.useState)([]),[i,o]=(0,r.useState)(!0),[c,d]=(0,r.useState)(null),[u,x]=(0,r.useState)(""),[m,h]=(0,r.useState)(t||"claude"),p=(0,r.useCallback)(async()=>{try{o(!0),d(null);let s=e?`/api/worktrees/${e}/slash-commands`:"/api/slash-commands";t&&(s+=`?cliTool=${t}`);let r=await fetch(s);if(!r.ok)throw Error(`HTTP error ${r.status}`);let l=await r.json();a(l.groups),h(l.cliTool||t||"claude")}catch(e){d((0,l.zG)(e)),a([])}finally{o(!1)}},[e,t]),g=(0,r.useMemo)(()=>s.flatMap(e=>e.commands),[s]),b=(0,r.useMemo)(()=>n(s,u),[s,u]);return{groups:s,filteredGroups:b,allCommands:g,loading:i,error:c,filter:u,setFilter:x,refresh:(0,r.useCallback)(()=>{p()},[p]),cliTool:m}}(e,s),$=async()=>{if(!b&&d.trim()&&!m)try{h(!0),g(null);let a=s||"claude";await l.Iv.sendMessage(e,d.trim(),a),x(""),w(!1),t?.(a)}catch(e){g((0,l.zG)(e))}finally{h(!1)}},M=async e=>{e.preventDefault(),await $()},E=()=>{v(!1),w(!1),N.current?.focus()};return(0,a.jsxs)("div",{ref:S,className:"space-y-2 relative",children:[p&&a.jsx("div",{className:"p-2 bg-red-50 border border-red-200 rounded text-sm text-red-800",children:p}),(0,a.jsxs)("form",{onSubmit:M,className:"flex items-end gap-2 bg-white border border-gray-300 rounded-lg px-4 py-2 focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500",children:[L&&a.jsx("button",{type:"button",onClick:()=>{j&&w(!1),v(!0)},className:"flex-shrink-0 p-2 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded-full transition-colors","aria-label":"Show slash commands","data-testid":"mobile-command-button",children:a.jsx("svg",{className:"h-5 w-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M7 20l4-16m2 16l4-16M6 9h14M4 15h14"})})}),a.jsx("textarea",{ref:N,value:d,onChange:e=>{let t=e.target.value;if(x(t),""===t){w(!1),v(!1);return}j||("/"===t||t.startsWith("/")&&!t.includes(" ")?v(!0):v(!1))},onKeyDown:e=>{let{keyCode:t}=e.nativeEvent;if(229!==t){if("Escape"===e.key&&y){e.preventDefault(),E();return}if(C.current&&"Enter"===e.key){C.current=!1;return}if("Enter"===e.key&&!b&&(!y||j)){if(L)return;e.shiftKey||(e.preventDefault(),$())}}},onCompositionStart:()=>{f(!0),C.current=!1,k.current&&clearTimeout(k.current)},onCompositionEnd:()=>{f(!1),C.current=!0,k.current&&clearTimeout(k.current),k.current=setTimeout(()=>{C.current=!1},300)},placeholder:L?"Type your message...":"Type your message... (/ for commands, Shift+Enter for line break)",disabled:m,rows:1,className:"flex-1 outline-none bg-transparent resize-none py-1 overflow-y-auto scrollbar-thin",style:{minHeight:"24px",maxHeight:"160px"}}),a.jsx(c,{worktreeId:e,cliToolId:s||"claude",disabled:!i}),a.jsx("button",{type:"submit",disabled:!d.trim()||m,className:"flex-shrink-0 p-2 text-blue-600 hover:bg-blue-50 rounded-full transition-colors disabled:text-gray-300 disabled:hover:bg-transparent","aria-label":"Send message",children:m?(0,a.jsxs)("svg",{className:"animate-spin h-5 w-5",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[a.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),a.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}):a.jsx("svg",{className:"h-5 w-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:a.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 19l9 2-9-18-9 18 9-2zm0 0v-8"})})})]}),a.jsx(o,{isOpen:y,groups:z,onSelect:e=>{x(`/${e.name} `),v(!1),N.current?.focus()},onClose:E,isMobile:L,onFreeInput:e=>{v(!1),w(!0),x(e?`/${e}`:"/"),setTimeout(()=>{N.current?.focus()},50)}})]})}},61421:(e,t,s)=>{s.d(t,{F4:()=>r,Ie:()=>n,xh:()=>l});let a={idle:"bg-gray-500",ready:"bg-green-500",spinner:"border-blue-500",waiting:"bg-yellow-500",error:"bg-red-500"},r={idle:{className:a.idle,label:"Idle",type:"dot"},ready:{className:a.ready,label:"Ready",type:"dot"},running:{className:a.spinner,label:"Running",type:"spinner"},waiting:{className:a.waiting,label:"Waiting for response",type:"dot"},generating:{className:a.spinner,label:"Generating",type:"spinner"}},l={idle:{className:a.idle,label:"Idle",type:"dot"},ready:{className:a.ready,label:"Ready",type:"dot"},running:{className:a.spinner,label:"Running",type:"spinner"},waiting:{className:a.waiting,label:"Waiting for response",type:"dot"},error:{className:a.error,label:"Error",type:"dot"}},n={idle:{className:a.idle,label:"Idle - No active session",type:"dot"},ready:{className:a.ready,label:"Ready - Waiting for input",type:"dot"},running:{className:a.spinner,label:"Running - Processing",type:"spinner"},waiting:{className:a.waiting,label:"Waiting - User input required",type:"dot"},error:{className:a.error,label:"Error",type:"dot"}}},22202:(e,t,s)=>{s.d(t,{k:()=>a});let a={DROPDOWN:10,SIDEBAR:30,MODAL:50,MAXIMIZED_EDITOR:55,TOAST:60,CONTEXT_MENU:70}},2982:(e,t,s)=>{s.d(t,{G:()=>r,d:()=>l});var a=s(17577);let r=768;function l(e={}){let{breakpoint:t=r}=e,[s,l]=(0,a.useState)(!1);return s}},91515:(e,t,s)=>{s.d(t,{v:()=>r});let a=/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;async function r(e){if(!e||0===e.trim().length)return;let t=e.replace(a,"");await navigator.clipboard.writeText(t)}},41593:(e,t,s)=>{s.d(t,{G:()=>n});var a=s(27808),r=s(18811);let l={en:r._,ja:a.ja};function n(e){return l[e]??r._}},51223:(e,t,s)=>{function a(e,t){let s=null;return(...a)=>{s&&clearTimeout(s),s=setTimeout(()=>{e(...a),s=null},t)}}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function l(e){let t=new Set;for(let s of e){t.add(s);let e=s.split("/"),a="";for(let s=0;s<e.length-1;s++)a=a?`${a}/${e[s]}`:e[s],t.add(a)}return t}function n(e,t=30){return e.length<=t?e:`${e.substring(0,t-3)}...`}function i(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}s.d(t,{Ds:()=>a,Ps:()=>l,Xv:()=>i,aS:()=>n,hr:()=>r})},18421:(e,t,s)=>{function a(e){return e?e.isWaitingForResponse?"waiting":e.isProcessing?"running":e.isRunning?"ready":"idle":"idle"}function r(e){let t=!!e.lastAssistantMessageAt&&(!e.lastViewedAt||new Date(e.lastAssistantMessageAt)>new Date(e.lastViewedAt));return{id:e.id,name:e.name,repositoryName:e.repositoryName,status:"idle",hasUnread:t,lastActivity:e.updatedAt,description:e.description,cliStatus:{claude:a(e.sessionStatusByCli?.claude),codex:a(e.sessionStatusByCli?.codex)}}}s.d(t,{He:()=>a,I_:()=>r})},63601:(e,t,s)=>{s.r(t),s.d(t,{default:()=>r});var a=s(66621);let r=e=>[{type:"image/png",sizes:"180x180",url:(0,a.fillMetadataSegment)(".",e.params,"apple-icon.png")+"?31a89e65aa4ac65b"}]},71150:(e,t,s)=>{s.r(t),s.d(t,{default:()=>r});var a=s(66621);let r=e=>[{type:"image/png",sizes:"32x32",url:(0,a.fillMetadataSegment)(".",e.params,"icon.png")+"?32d9127ba30ee286"}]}};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
exports.id=9367,exports.ids=[9367],exports.modules={58359:()=>{},93739:()=>{},98241:(e,t,r)=>{"use strict";r.d(t,{s:()=>s});var n=r(55315),a=r.n(n),o=r(98838);function s(){return(0,o.Hb)("CM_LOG_DIR")||a().join(process.cwd(),"data","logs")}},89287:(e,t,r)=>{"use strict";r.d(t,{Z:()=>s});let n=/📄 Session log: (.+?\/([^\/\s]+\.jsonl))/,a=/Request ID: ([^\s\n]+)/,o=/Summary: (.+?)(?:\n─|$)/s;function s(e){let t={content:e},r=n.exec(e);r&&(t.logFileName=r[2]);let s=a.exec(e);s&&(t.requestId=s[1]);let i=o.exec(e);return i&&(t.summary=i[1].trim()),t}},62648:(e,t,r)=>{"use strict";r.d(t,{Lm:()=>S,Uv:()=>$,YI:()=>x,_f:()=>D,xd:()=>k,ym:()=>C});var n=r(10927),a=r(19377),o=r(92900),s=r(61282),i=r(21764),c=r(20629);let l=(0,i.promisify)(s.exec);function u(e){return e instanceof Error?e.message:String(e)}let d=["$","%","#"],f=null;async function w(){if(f)return f;let e=process.env.CLAUDE_PATH;if(e&&(/^[/a-zA-Z0-9._-]+$/.test(e)?!e.includes("..")||(console.log("[claude-session] CLAUDE_PATH contains path traversal sequence, ignoring"),!1):(console.log(`[claude-session] CLAUDE_PATH contains invalid characters, ignoring: ${e.substring(0,50)}`),!1)))try{return await (0,c.access)(e,c.constants.X_OK),f=e}catch{console.log(`[claude-session] CLAUDE_PATH is not executable: ${e}`)}try{let{stdout:e}=await l("which claude",{timeout:5e3});return f=e.trim()}catch{for(let e of["/opt/homebrew/bin/claude","/usr/local/bin/claude","/usr/bin/claude"])try{return await l(`test -x "${e}"`,{timeout:1e3}),f=e}catch{}throw Error("Claude CLI not found. Set CLAUDE_PATH environment variable or install Claude CLI.")}}async function m(e,t=50){let r=await (0,n.xq)(e,{startLine:-t});return(0,a.vp)(r)}async function h(e){try{let t=(await m(e)).trim();if(""===t)return{healthy:!1,reason:"empty output"};for(let e of a.KJ)if(t.includes(e))return{healthy:!1,reason:`error pattern: ${e}`};for(let e of a.dR)if(e.test(t))return{healthy:!1,reason:`error pattern: ${e.source}`};let r=t.split("\n").filter(e=>""!==e.trim()),n=r[r.length-1]?.trim()??"";if(n.length>=40)return{healthy:!0};if(d.some(e=>!(!n.endsWith(e)||"%"===e&&/\d+%$/.test(n))))return{healthy:!1,reason:`shell prompt ending detected: ${n}`};return{healthy:!0}}catch{return{healthy:!1,reason:"capture error"}}}async function y(e){let t=await h(e);return!!t.healthy||(console.warn(`[health-check] Session ${e} unhealthy: ${t.reason}`),await (0,n.AJ)(e),!1)}async function g(e){await l("tmux set-environment -g -u CLAUDECODE 2>/dev/null || true"),await (0,n.Is)(e,"unset CLAUDECODE",!0),await new Promise(e=>setTimeout(e,100))}function p(e){return`mcbd-claude-${e}`}async function $(){try{return await l("which claude",{timeout:5e3}),!0}catch{return!1}}async function x(e){let t=p(e);return!!await (0,n.Hk)(t)&&(await h(t)).healthy}async function E(e,t=5e3){let r=Date.now();for(;Date.now()-r<t;){let t=await m(e);if(a.V7.test(t))return;await new Promise(e=>setTimeout(e,200))}throw Error(`Prompt detection timeout (${t}ms)`)}async function C(e){let{worktreeId:t,worktreePath:r}=e;if(!await $())throw Error("Claude CLI is not installed or not in PATH");let o=p(t);if(await (0,n.Hk)(o)&&await y(o)){console.log(`Claude session ${o} already exists and is healthy`);return}try{await (0,n.ed)({sessionName:o,workingDirectory:r,historyLimit:5e4}),await g(o);let e=await w();await (0,n.Is)(o,e,!0);let t=Date.now(),s=!1,i=!1;for(;Date.now()-t<15e3;){await new Promise(e=>setTimeout(e,300));try{let e=await m(o);if(a.V7.test(e)){await new Promise(e=>setTimeout(e,500)),console.log(`Claude initialized in ${Date.now()-t}ms`),s=!0;break}!i&&a.H3.test(e)&&(await (0,n.Is)(o,"",!0),i=!0,console.log("Trust dialog detected, sending Enter to confirm"))}catch{}}if(!s)throw Error("Claude initialization timeout (15000ms)");console.log(`Started Claude session: ${o}`)}catch(e){throw f=null,console.log(`[claude-session] Session start failed: ${u(e)}`),Error("Failed to start Claude session")}}async function k(e,t){let r=p(e);if(!await (0,n.Hk)(r))throw Error(`Claude session ${r} does not exist. Start the session first.`);let s=await m(r);a.V7.test(s)||await E(r,1e4),await new Promise(e=>setTimeout(e,500)),await (0,n.Is)(r,t,!1),await (0,n.Is)(r,"",!0),t.includes("\n")&&await (0,o.N)(r),console.log(`Sent message to Claude session: ${r}`)}async function S(e,t=1e3){let r=p(e);if(!await (0,n.Hk)(r))throw Error(`Claude session ${r} does not exist`);try{return await (0,n.xq)(r,{startLine:-t})}catch(e){throw Error(`Failed to capture Claude output: ${u(e)}`)}}async function D(e){let t=p(e);try{await (0,n.Hk)(t)&&(await (0,n.Is)(t,"",!1),await l(`tmux send-keys -t "${t}" C-d`),await new Promise(e=>setTimeout(e,500)));let e=await (0,n.AJ)(t);return e&&console.log(`Stopped Claude session: ${t}`),e}catch(e){return console.error(`Error stopping Claude session: ${u(e)}`),!1}}},19377:(e,t,r)=>{"use strict";r.d(t,{H3:()=>c,KJ:()=>p,Sg:()=>x,V7:()=>s,Wg:()=>h,_r:()=>f,bs:()=>function e(t){switch(t){case"claude":return{promptPattern:s,separatorPattern:i,thinkingPattern:a,skipPatterns:[/^─{10,}$/,/^[>❯]\s*$/,a,/^\s*[⎿⏋]\s+Tip:/,/^\s*Tip:/,/^\s*\?\s*for shortcuts/,/to interrupt\)/,d]};case"codex":return{promptPattern:l,separatorPattern:u,thinkingPattern:o,skipPatterns:[/^─.*─+$/,/^›\s*$/,/^›\s+(Implement|Find and fix|Type)/,o,/^\s*\d+%\s+context left/,/^\s*for shortcuts$/,/╭─+╮/,/╰─+╯/,/•\s*Ran\s+/,/^\s*└/,/^\s*│/,/\(.*esc to interrupt\)/,d]};case"gemini":return{promptPattern:m,separatorPattern:/^gemini\s+--\s+/m,thinkingPattern:/(?!)/m,skipPatterns:[/^gemini\s+--\s+/,m,/^\s*$/]};default:return e("claude")}},d8:()=>d,dR:()=>$,vp:()=>g,ww:()=>w});let n=(0,r(43895).h)("cli-patterns"),a=RegExp(`[✻✽⏺\xb7∴✢✳✶⦿◉●○◌◎⊙⊚⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\\s+.+…|esc to interrupt`,"m"),o=/•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing|Ran|Deciding)/m,s=/^[>❯](\s*$|\s+\S)/m,i=/^─{10,}$/m,c=/Yes, I trust this folder/m,l=/^›\s*/m,u=/^─.*Worked for.*─+$/m,d=/\[Pasted text #\d+/,f=500,w=3,m=/^(%|\$|.*@.*[%$#])\s*$/m;function h(e,t){let r;let s=n.withContext({cliToolId:e});switch(s.debug("detectThinking:check",{contentLength:t.length}),e){case"claude":default:r=a.test(t);break;case"codex":r=o.test(t);break;case"gemini":r=!1}return s.debug("detectThinking:result",{isThinking:r}),r}let y=/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;function g(e){return e.replace(y,"")}let p=["Claude Code cannot be launched inside another Claude Code session"],$=[/Error:.*Claude/];function x(e){if("claude"===e)return{requireDefaultIndicator:!1}}},76966:(e,t,r)=>{"use strict";r.d(t,{o:()=>o});var n=r(75748),a=r(98636);async function o(e,t,r,o="claude"){let s=(0,n.vX)(e,t);if(s)try{await (0,a.xN)(t,s.content,r,o)}catch(e){console.error("[recordClaudeConversation] Failed to create log file:",e)}}},98636:(e,t,r)=>{"use strict";r.d(t,{e7:()=>f,xN:()=>d});var n=r(20629),a=r.n(n),o=r(55315),s=r.n(o),i=r(73853),c=r(98241);function l(e="claude"){return s().join((0,c.s)(),e)}async function u(e="claude"){let t=l(e);try{await a().access(t)}catch{await a().mkdir(t,{recursive:!0})}}async function d(e,t,r,n="claude"){await u(n);let o=function(e,t="claude"){let r=(0,i.WU)(new Date,"yyyy-MM-dd"),n=`${e}-${r}.md`,a=l(t);return s().join(a,n)}(e,n),c=(0,i.WU)(new Date,"yyyy-MM-dd HH:mm:ss"),d="";try{d=await a().readFile(o,"utf-8")}catch{let t="claude"===n?"Claude Code":"codex"===n?"Codex CLI":"Gemini CLI";d=`# ${t} Conversation Log: ${e}
|
|
2
|
-
|
|
3
|
-
Created: ${c}
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
`}return d+=`## Conversation at ${c}
|
|
8
|
-
|
|
9
|
-
### User
|
|
10
|
-
|
|
11
|
-
${t}
|
|
12
|
-
|
|
13
|
-
### ${"claude"===n?"Claude":"codex"===n?"Codex":"Gemini"}
|
|
14
|
-
|
|
15
|
-
${r}
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
`,await a().writeFile(o,d,"utf-8"),o}async function f(e,t="all"){let r=[];for(let n of"all"===t?["claude","codex","gemini"]:[t]){await u(n);let t=l(n);try{let n=(await a().readdir(t)).filter(t=>t.startsWith(`${e}-`)&&t.endsWith(".md")).map(e=>s().join(t,e));r.push(...n)}catch{}}return r.sort().reverse()}},43895:(e,t,r)=>{"use strict";r.d(t,{Y:()=>c,h:()=>l});var n=r(98838);let a=[{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:/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]"}],o=/password|secret|token|key|auth/i,s={debug:0,info:1,warn:2,error:3};function i(e,t,r,i,c){let l=(0,n.LI)().level;if(s[e]<s[l])return;let u=i?function e(t){if("string"==typeof t){let e=t;for(let{pattern:t,replacement:r}of a)e=e.replace(t,r);return e}if("object"==typeof t&&null!==t){if(Array.isArray(t))return t.map(e);let r={};for(let[n,a]of Object.entries(t))o.test(n)?r[n]="[REDACTED]":r[n]=e(a);return r}return t}(i):void 0,d=function(e){if("json"===(0,n.LI)().format)return JSON.stringify(e);let{timestamp:t,level:r,module:a,action:o,data:s,worktreeId:i,cliToolId:c,requestId:l}=e,u=[i,c].filter(Boolean),d=u.length>0?` [${u.join(":")}]`:"",f=l?` (${l.slice(0,8)})`:"",w=s?` ${JSON.stringify(s)}`:"";return`[${t}] [${r.toUpperCase()}] [${a}]${d}${f} ${o}${w}`}({level:e,module:t,action:r,timestamp:new Date().toISOString(),...c,...u&&{data:u}});switch(e){case"error":console.error(d);break;case"warn":console.warn(d);break;default:console.log(d)}}function c(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}function l(e){let t=r=>({debug:(t,n)=>i("debug",e,t,n,r),info:(t,n)=>i("info",e,t,n,r),warn:(t,n)=>i("warn",e,t,n,r),error:(t,n)=>i("error",e,t,n,r),withContext:e=>t({...r,...e})});return t()}},92900:(e,t,r)=>{"use strict";r.d(t,{N:()=>s});var n=r(10927),a=r(19377);let o=(0,r(43895).h)("pasted-text");async function s(e){for(let t=0;t<a.ww;t++){await new Promise(e=>setTimeout(e,a._r));let r=await (0,n.xq)(e,{startLine:-10});if(!a.d8.test((0,a.vp)(r)))return;await (0,n.Is)(e,"",!0),t===a.ww-1&&o.warn("Pasted text detection: max retries reached",{sessionName:e,maxRetries:a.ww,finalAttempt:t})}}},10927:(e,t,r)=>{"use strict";r.d(t,{AJ:()=>d,Hk:()=>o,Is:()=>i,ZV:()=>f,ed:()=>s,hL:()=>l,xq:()=>u});var n=r(61282);let a=(0,r(21764).promisify)(n.exec);async function o(e){try{return await a(`tmux has-session -t "${e}"`,{timeout:5e3}),!0}catch{return!1}}async function s(e,t){let r,n,o;"string"==typeof e?(r=e,n=t,o=5e4):(r=e.sessionName,n=e.workingDirectory,o=e.historyLimit||5e4);try{await a(`tmux new-session -d -s "${r}" -c "${n}"`,{timeout:5e3}),await a(`tmux set-option -t "${r}" history-limit ${o}`,{timeout:5e3})}catch(t){let e=t instanceof Error?t.message:String(t);throw Error(`Failed to create tmux session: ${e}`)}}async function i(e,t,r=!0){let n=t.replace(/'/g,"'\\''"),o=r?`tmux send-keys -t "${e}" '${n}' C-m`:`tmux send-keys -t "${e}" '${n}'`;try{await a(o,{timeout:5e3})}catch(t){let e=t instanceof Error?t.message:String(t);throw Error(`Failed to send keys to tmux session: ${e}`)}}let c=new Set(["Up","Down","Left","Right","Enter","Space","Tab","Escape","BSpace","DC"]);async function l(e,t){if(0!==t.length){for(let e of t)if(!c.has(e))throw Error(`Invalid special key: ${e}`);try{for(let r=0;r<t.length;r++){let n=`tmux send-keys -t "${e}" ${t[r]}`;await a(n,{timeout:5e3}),r<t.length-1&&await new Promise(e=>setTimeout(e,100))}}catch(t){let e=t instanceof Error?t.message:String(t);throw Error(`Failed to send special keys to tmux session: ${e}`)}}}async function u(e,t){let r,n;"number"==typeof t?(r=-t,n="-"):t?(r=t.startLine??-1e4,n=t.endLine??"-"):(r=-1e3,n="-");try{let{stdout:t}=await a(`tmux capture-pane -t "${e}" -p -e -S ${r} -E ${n}`,{timeout:5e3,maxBuffer:10485760});return t}catch(t){let e=t instanceof Error?t.message:String(t);throw Error(`Failed to capture pane: ${e}`)}}async function d(e){try{return await a(`tmux kill-session -t "${e}"`,{timeout:5e3}),!0}catch(t){let e=t instanceof Error?t.message:String(t);if(e?.includes("no server running")||e?.includes("can't find session"))return!1;throw Error(`Failed to kill tmux session: ${e}`)}}async function f(e,t){try{await a(`tmux send-keys -t "${e}" ${t}`,{timeout:5e3})}catch(t){let e=t instanceof Error?t.message:String(t);throw Error(`Failed to send special key: ${e}`)}}},25079:(e,t,r)=>{"use strict";r.d(t,{ZV:()=>l,fM:()=>i,ps:()=>c});var n=r(34893);let a=new Map,o=new Map;function s(e,t){let r=o.get(e);if(console.log(`[WS] handleBroadcast called for ${e}, room size: ${r?.size||0}`),!r){console.log(`[WS] No room found for ${e}`);return}if(0===r.size){console.log(`[WS] Room for ${e} is empty`);return}try{let a=JSON.stringify({type:"broadcast",worktreeId:e,data:t}),o=0,s=0;r.forEach(e=>{if(e.readyState===n.XY.OPEN)try{e.send(a),o++}catch(e){s++,console.error("Error sending WebSocket message to client:",e)}}),console.log(`Broadcast to worktree ${e}: ${o}/${r.size} clients (${s} errors)`)}catch(t){console.error(`Error broadcasting to worktree ${e}:`,t);try{let t=JSON.stringify({type:"broadcast",worktreeId:e,data:{error:"Message encoding error"}});r.forEach(e=>{if(e.readyState===n.XY.OPEN)try{e.send(t)}catch{}})}catch(e){console.error("Failed to send fallback message:",e)}}}function i(e,t){s(e,t)}function c(e,t){t.worktreeId?s(t.worktreeId,{type:e,...t}):console.warn("broadcastMessage called without worktreeId")}function l(e){for(let t of e){let e=o.get(t);e&&(e.forEach(e=>{let r=a.get(e);r&&r.worktreeIds.delete(t)}),o.delete(t),console.log(`[WS] Cleaned up room for worktree: ${t}`))}}}};
|