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.
Files changed (154) hide show
  1. package/.env.example +21 -2
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/app-build-manifest.json +32 -24
  4. package/.next/app-path-routes-manifest.json +1 -1
  5. package/.next/build-manifest.json +7 -7
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/2.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  14. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  15. package/.next/cache/webpack/server-production/0.pack +0 -0
  16. package/.next/cache/webpack/server-production/index.pack +0 -0
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/react-loadable-manifest.json +2 -2
  20. package/.next/required-server-files.json +1 -1
  21. package/.next/routes-manifest.json +1 -1
  22. package/.next/server/app/_not-found/page.js +1 -1
  23. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  24. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/app/update-check/route.js +1 -1
  26. package/.next/server/app/api/auth/login/route.js +1 -0
  27. package/.next/server/app/api/auth/login/route.js.nft.json +1 -0
  28. package/.next/server/app/api/auth/logout/route.js +1 -0
  29. package/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
  30. package/.next/server/app/api/auth/status/route.js +1 -0
  31. package/.next/server/app/api/auth/status/route.js.nft.json +1 -0
  32. package/.next/server/app/api/hooks/claude-done/route.js +1 -1
  33. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  34. package/.next/server/app/api/repositories/route.js +2 -2
  35. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  36. package/.next/server/app/api/slash-commands/route.js +1 -1
  37. package/.next/server/app/api/slash-commands/route.js.nft.json +1 -1
  38. package/.next/server/app/api/slash-commands.body +1 -1
  39. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  40. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  41. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  42. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  43. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  44. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  45. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  46. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  47. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  48. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  49. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  50. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  51. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  52. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  53. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  54. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  55. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  56. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  57. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -1
  58. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  59. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
  60. package/.next/server/app/api/worktrees/route.js +1 -1
  61. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  62. package/.next/server/app/login/page.js +1 -0
  63. package/.next/server/app/login/page.js.nft.json +1 -0
  64. package/.next/server/app/login/page_client-reference-manifest.js +1 -0
  65. package/.next/server/app/page.js +2 -2
  66. package/.next/server/app/page.js.nft.json +1 -1
  67. package/.next/server/app/page_client-reference-manifest.js +1 -1
  68. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -1
  69. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
  70. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  71. package/.next/server/app/worktrees/[id]/page.js +2 -2
  72. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  73. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/worktrees/[id]/terminal/page.js +1 -1
  75. package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
  76. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app-paths-manifest.json +12 -8
  78. package/.next/server/chunks/3013.js +1 -0
  79. package/.next/server/chunks/3074.js +1 -0
  80. package/.next/server/chunks/{1287.js → 3294.js} +2 -2
  81. package/.next/server/chunks/3860.js +1 -1
  82. package/.next/server/chunks/4893.js +2 -2
  83. package/.next/server/chunks/539.js +35 -0
  84. package/.next/server/chunks/5795.js +1 -0
  85. package/.next/server/chunks/7536.js +1 -1
  86. package/.next/server/chunks/7566.js +19 -0
  87. package/.next/server/chunks/8693.js +1 -1
  88. package/.next/server/edge-runtime-webpack.js +2 -0
  89. package/.next/server/edge-runtime-webpack.js.map +1 -0
  90. package/.next/server/functions-config-manifest.json +1 -1
  91. package/.next/server/middleware-build-manifest.js +1 -1
  92. package/.next/server/middleware-manifest.json +28 -2
  93. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  94. package/.next/server/pages/500.html +1 -1
  95. package/.next/server/server-reference-manifest.json +1 -1
  96. package/.next/server/src/middleware.js +14 -0
  97. package/.next/server/src/middleware.js.map +1 -0
  98. package/.next/static/chunks/{2626.2125083a1ff3b80a.js → 6163.f672451d4575decf.js} +1 -1
  99. package/.next/static/chunks/{656.d72f25ce819bd77e.js → 656.5e2de0173f5a06bd.js} +1 -1
  100. package/.next/static/chunks/8091-925542bdfc843dce.js +1 -0
  101. package/.next/static/chunks/8528-4d554d3b94d4cf9b.js +1 -0
  102. package/.next/static/chunks/app/{layout-07755491d5d57242.js → layout-9110f9a5e41c6bf4.js} +1 -1
  103. package/.next/static/chunks/app/login/page-2d42204ba87cd136.js +1 -0
  104. package/.next/static/chunks/app/page-238b5a70d8c101e9.js +1 -0
  105. package/.next/static/chunks/app/worktrees/[id]/page-9418e49bdc1de02c.js +1 -0
  106. package/.next/static/chunks/main-db79434ee4a6c931.js +1 -0
  107. package/.next/static/chunks/webpack-3c0ee3ce5b546818.js +1 -0
  108. package/.next/static/css/b9ea6a4fad17dc32.css +3 -0
  109. package/.next/trace +5 -5
  110. package/.next/types/app/api/auth/login/route.ts +343 -0
  111. package/.next/types/app/api/auth/logout/route.ts +343 -0
  112. package/.next/types/app/api/auth/status/route.ts +343 -0
  113. package/.next/types/app/login/page.ts +79 -0
  114. package/README.md +6 -1
  115. package/dist/cli/commands/init.d.ts.map +1 -1
  116. package/dist/cli/commands/init.js +2 -0
  117. package/dist/cli/commands/start.d.ts +2 -0
  118. package/dist/cli/commands/start.d.ts.map +1 -1
  119. package/dist/cli/commands/start.js +159 -14
  120. package/dist/cli/commands/status.d.ts.map +1 -1
  121. package/dist/cli/commands/status.js +4 -0
  122. package/dist/cli/config/security-messages.d.ts +3 -1
  123. package/dist/cli/config/security-messages.d.ts.map +1 -1
  124. package/dist/cli/config/security-messages.js +6 -2
  125. package/dist/cli/index.js +17 -0
  126. package/dist/cli/types/index.d.ts +17 -0
  127. package/dist/cli/types/index.d.ts.map +1 -1
  128. package/dist/cli/utils/daemon.d.ts.map +1 -1
  129. package/dist/cli/utils/daemon.js +16 -3
  130. package/dist/config/auth-config.d.ts +43 -0
  131. package/dist/config/auth-config.d.ts.map +1 -0
  132. package/dist/config/auth-config.js +112 -0
  133. package/dist/lib/auth.d.ts +104 -0
  134. package/dist/lib/auth.d.ts.map +1 -0
  135. package/dist/lib/auth.js +250 -0
  136. package/dist/server/server.js +123 -12
  137. package/dist/server/src/config/auth-config.js +112 -0
  138. package/dist/server/src/lib/auth.js +250 -0
  139. package/dist/server/src/lib/auto-yes-manager.js +180 -96
  140. package/dist/server/src/lib/ip-restriction.js +241 -0
  141. package/dist/server/src/lib/ws-server.js +63 -33
  142. package/dist/server/src/types/slash-commands.js +1 -0
  143. package/package.json +2 -2
  144. package/.next/server/chunks/9238.js +0 -35
  145. package/.next/server/chunks/9367.js +0 -19
  146. package/.next/static/chunks/5970-2e18108d0cabd8af.js +0 -1
  147. package/.next/static/chunks/816-af44cb865b0c980e.js +0 -1
  148. package/.next/static/chunks/app/page-a6593b9640df66a6.js +0 -1
  149. package/.next/static/chunks/app/worktrees/[id]/page-d9a7913679eccfd9.js +0 -1
  150. package/.next/static/chunks/main-f00f82f1cf18dd99.js +0 -1
  151. package/.next/static/chunks/webpack-e6531fcf859d9451.js +0 -1
  152. package/.next/static/css/897ffb669f47c97b.css +0 -3
  153. /package/.next/static/{oUEq-Bd47xtkJcFDOI6rr → clTo9tuAoPMLcGRuVENfO}/_buildManifest.js +0 -0
  154. /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
- * Setup WebSocket server on HTTP server
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 server - HTTP server instance
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
- // Suppress common mobile browser disconnect errors
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
- // Suppress noisy errors from mobile browser disconnects
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
- console.log('WebSocket server initialized');
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
- console.log(`Client subscribed to worktree: ${worktreeId}, room size: ${room.size}, ws readyState: ${ws.readyState}`);
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
- console.log(`Client unsubscribed from worktree: ${worktreeId}`);
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
- console.log(`[WS] handleBroadcast called for ${worktreeId}, room size: ${room?.size || 0}`);
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
- console.log(`[WS] Cleaned up room for worktree: ${worktreeId}`);
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
- console.log('WebSocket server closed');
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.2.13",
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.9"
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}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}`))}}}};