claude-code-hub 0.41.0 → 0.42.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/lib/cookies.js +13 -0
- package/lib/themes.json +548 -0
- package/package.json +6 -5
- package/public/app.js +5 -14
- package/server.js +50 -17
package/lib/cookies.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Returns the Cookie header value without the named cookie, or '' when nothing is left.
|
|
4
|
+
function stripCookie(header, name) {
|
|
5
|
+
if (typeof header !== 'string') return '';
|
|
6
|
+
return header
|
|
7
|
+
.split(';')
|
|
8
|
+
.map((part) => part.trim())
|
|
9
|
+
.filter((part) => part && part.split('=', 1)[0].trim() !== name)
|
|
10
|
+
.join('; ');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { stripCookie };
|
package/lib/themes.json
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "ember",
|
|
4
|
+
"label": "Ember",
|
|
5
|
+
"dark": {
|
|
6
|
+
"ember": "#e86f33",
|
|
7
|
+
"emberGlow": "#f0a070",
|
|
8
|
+
"emberDim": "rgba(232,111,51,0.25)",
|
|
9
|
+
"field": "#101114",
|
|
10
|
+
"surface": "#16181c",
|
|
11
|
+
"elevated": "#1e2025",
|
|
12
|
+
"hover": "#282a30",
|
|
13
|
+
"border": "#363840",
|
|
14
|
+
"ink1": "#f0f1f3",
|
|
15
|
+
"ink2": "#c2c4c9",
|
|
16
|
+
"ink3": "#9a9da5",
|
|
17
|
+
"inkMuted": "#7d808a"
|
|
18
|
+
},
|
|
19
|
+
"light": {
|
|
20
|
+
"ember": "#e86f33",
|
|
21
|
+
"emberGlow": "#a0501c",
|
|
22
|
+
"emberDim": "rgba(184,90,32,0.18)",
|
|
23
|
+
"field": "#e8e6e3",
|
|
24
|
+
"surface": "#efede9",
|
|
25
|
+
"elevated": "#fbfaf9",
|
|
26
|
+
"hover": "#d2d0cc",
|
|
27
|
+
"border": "#cfcbc4",
|
|
28
|
+
"ink1": "#0a0a0a",
|
|
29
|
+
"ink2": "#2c2c2c",
|
|
30
|
+
"ink3": "#565656",
|
|
31
|
+
"inkMuted": "#767676"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "gruvbox",
|
|
36
|
+
"label": "Gruvbox",
|
|
37
|
+
"dark": {
|
|
38
|
+
"ember": "#fe8019",
|
|
39
|
+
"emberGlow": "#f9b27c",
|
|
40
|
+
"emberDim": "rgba(254,128,25,0.25)",
|
|
41
|
+
"field": "#1d2021",
|
|
42
|
+
"surface": "#282828",
|
|
43
|
+
"elevated": "#32302f",
|
|
44
|
+
"hover": "#3c3836",
|
|
45
|
+
"border": "#504945",
|
|
46
|
+
"ink1": "#ebdbb2",
|
|
47
|
+
"ink2": "#d5c4a1",
|
|
48
|
+
"ink3": "#bdae93",
|
|
49
|
+
"inkMuted": "#928374"
|
|
50
|
+
},
|
|
51
|
+
"light": {
|
|
52
|
+
"ember": "#d65d0e",
|
|
53
|
+
"emberGlow": "#af3a03",
|
|
54
|
+
"emberDim": "rgba(214,93,14,0.18)",
|
|
55
|
+
"field": "#ede7d5",
|
|
56
|
+
"surface": "#f3eee0",
|
|
57
|
+
"elevated": "#faf7ec",
|
|
58
|
+
"hover": "#e4dcc6",
|
|
59
|
+
"border": "#c9bc9d",
|
|
60
|
+
"ink1": "#3c3836",
|
|
61
|
+
"ink2": "#504945",
|
|
62
|
+
"ink3": "#665c54",
|
|
63
|
+
"inkMuted": "#7c6f64"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "catppuccin",
|
|
68
|
+
"label": "Catppuccin",
|
|
69
|
+
"dark": {
|
|
70
|
+
"ember": "#cba6f7",
|
|
71
|
+
"emberGlow": "#d8c2fa",
|
|
72
|
+
"emberDim": "rgba(203,166,247,0.25)",
|
|
73
|
+
"field": "#11111b",
|
|
74
|
+
"surface": "#181825",
|
|
75
|
+
"elevated": "#1e1e2e",
|
|
76
|
+
"hover": "#313244",
|
|
77
|
+
"border": "#45475a",
|
|
78
|
+
"ink1": "#cdd6f4",
|
|
79
|
+
"ink2": "#bac2de",
|
|
80
|
+
"ink3": "#a6adc8",
|
|
81
|
+
"inkMuted": "#7f849c"
|
|
82
|
+
},
|
|
83
|
+
"light": {
|
|
84
|
+
"ember": "#8839ef",
|
|
85
|
+
"emberGlow": "#6f2dbd",
|
|
86
|
+
"emberDim": "rgba(136,57,239,0.18)",
|
|
87
|
+
"field": "#e6e9ef",
|
|
88
|
+
"surface": "#eff1f5",
|
|
89
|
+
"elevated": "#ffffff",
|
|
90
|
+
"hover": "#ccd0da",
|
|
91
|
+
"border": "#bcc0cc",
|
|
92
|
+
"ink1": "#4c4f69",
|
|
93
|
+
"ink2": "#5c5f77",
|
|
94
|
+
"ink3": "#6c6f85",
|
|
95
|
+
"inkMuted": "#8c8fa1"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "tokyo-night",
|
|
100
|
+
"label": "Tokyo Night",
|
|
101
|
+
"dark": {
|
|
102
|
+
"ember": "#7aa2f7",
|
|
103
|
+
"emberGlow": "#9ab8ff",
|
|
104
|
+
"emberDim": "rgba(122,162,247,0.25)",
|
|
105
|
+
"field": "#16161e",
|
|
106
|
+
"surface": "#1a1b26",
|
|
107
|
+
"elevated": "#1f2335",
|
|
108
|
+
"hover": "#292e42",
|
|
109
|
+
"border": "#3b4261",
|
|
110
|
+
"ink1": "#c0caf5",
|
|
111
|
+
"ink2": "#a9b1d6",
|
|
112
|
+
"ink3": "#787c99",
|
|
113
|
+
"inkMuted": "#565f89"
|
|
114
|
+
},
|
|
115
|
+
"light": {
|
|
116
|
+
"ember": "#2e7de9",
|
|
117
|
+
"emberGlow": "#1659c7",
|
|
118
|
+
"emberDim": "rgba(46,125,233,0.18)",
|
|
119
|
+
"field": "#e1e2e7",
|
|
120
|
+
"surface": "#e9eaf0",
|
|
121
|
+
"elevated": "#f7f8fc",
|
|
122
|
+
"hover": "#d0d5e3",
|
|
123
|
+
"border": "#a8aecb",
|
|
124
|
+
"ink1": "#343b58",
|
|
125
|
+
"ink2": "#565a6e",
|
|
126
|
+
"ink3": "#6c6e75",
|
|
127
|
+
"inkMuted": "#848cb5"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "solarized",
|
|
132
|
+
"label": "Solarized",
|
|
133
|
+
"dark": {
|
|
134
|
+
"ember": "#c96f47",
|
|
135
|
+
"emberGlow": "#d48a6a",
|
|
136
|
+
"emberDim": "rgba(201,111,71,0.25)",
|
|
137
|
+
"field": "#002b36",
|
|
138
|
+
"surface": "#073642",
|
|
139
|
+
"elevated": "#0a4250",
|
|
140
|
+
"hover": "#11505f",
|
|
141
|
+
"border": "#586e75",
|
|
142
|
+
"ink1": "#e2dfd0",
|
|
143
|
+
"ink2": "#b7c0bf",
|
|
144
|
+
"ink3": "#93a1a1",
|
|
145
|
+
"inkMuted": "#839496"
|
|
146
|
+
},
|
|
147
|
+
"light": {
|
|
148
|
+
"ember": "#2280c2",
|
|
149
|
+
"emberGlow": "#1b6aa3",
|
|
150
|
+
"emberDim": "rgba(34,128,194,0.18)",
|
|
151
|
+
"field": "#eee8d5",
|
|
152
|
+
"sidebar": "#e6dfc7",
|
|
153
|
+
"sidebarItem": "#f3ecd6",
|
|
154
|
+
"surface": "#fdf6e3",
|
|
155
|
+
"elevated": "#fffbef",
|
|
156
|
+
"hover": "#efe8d3",
|
|
157
|
+
"border": "#cdc4ab",
|
|
158
|
+
"ink1": "#073642",
|
|
159
|
+
"ink2": "#4f6369",
|
|
160
|
+
"ink3": "#586e75",
|
|
161
|
+
"inkMuted": "#728589"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "dracula",
|
|
166
|
+
"label": "Dracula",
|
|
167
|
+
"dark": {
|
|
168
|
+
"ember": "#bd93f9",
|
|
169
|
+
"emberGlow": "#d6b5ff",
|
|
170
|
+
"emberDim": "rgba(189,147,249,0.25)",
|
|
171
|
+
"field": "#21222c",
|
|
172
|
+
"surface": "#282a36",
|
|
173
|
+
"elevated": "#343746",
|
|
174
|
+
"hover": "#44475a",
|
|
175
|
+
"border": "#565a75",
|
|
176
|
+
"ink1": "#f8f8f2",
|
|
177
|
+
"ink2": "#d8d8d2",
|
|
178
|
+
"ink3": "#b0b3c5",
|
|
179
|
+
"inkMuted": "#6272a4"
|
|
180
|
+
},
|
|
181
|
+
"light": {
|
|
182
|
+
"ember": "#7c3aed",
|
|
183
|
+
"emberGlow": "#5b21b6",
|
|
184
|
+
"emberDim": "rgba(124,58,237,0.18)",
|
|
185
|
+
"field": "#e8e8ee",
|
|
186
|
+
"surface": "#f0f0f5",
|
|
187
|
+
"elevated": "#fbfbfd",
|
|
188
|
+
"hover": "#dfdfe8",
|
|
189
|
+
"border": "#c9c9d6",
|
|
190
|
+
"ink1": "#16161e",
|
|
191
|
+
"ink2": "#34343f",
|
|
192
|
+
"ink3": "#5a5a68",
|
|
193
|
+
"inkMuted": "#787885"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "nord",
|
|
198
|
+
"label": "Nord",
|
|
199
|
+
"dark": {
|
|
200
|
+
"ember": "#88c0d0",
|
|
201
|
+
"emberGlow": "#a8d8e8",
|
|
202
|
+
"emberDim": "rgba(136,192,208,0.25)",
|
|
203
|
+
"field": "#272c36",
|
|
204
|
+
"surface": "#2e3440",
|
|
205
|
+
"elevated": "#3b4252",
|
|
206
|
+
"hover": "#434c5e",
|
|
207
|
+
"border": "#4c566a",
|
|
208
|
+
"ink1": "#eceff4",
|
|
209
|
+
"ink2": "#d8dee9",
|
|
210
|
+
"ink3": "#aeb6c5",
|
|
211
|
+
"inkMuted": "#7b88a1"
|
|
212
|
+
},
|
|
213
|
+
"light": {
|
|
214
|
+
"ember": "#5e81ac",
|
|
215
|
+
"emberGlow": "#44688f",
|
|
216
|
+
"emberDim": "rgba(94,129,172,0.18)",
|
|
217
|
+
"field": "#e5e9f0",
|
|
218
|
+
"surface": "#eceff4",
|
|
219
|
+
"elevated": "#f8f9fb",
|
|
220
|
+
"hover": "#d8dee9",
|
|
221
|
+
"border": "#c2c9d6",
|
|
222
|
+
"ink1": "#2e3440",
|
|
223
|
+
"ink2": "#3b4252",
|
|
224
|
+
"ink3": "#4c566a",
|
|
225
|
+
"inkMuted": "#6b7589"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "rose-pine",
|
|
230
|
+
"label": "Rosé Pine",
|
|
231
|
+
"dark": {
|
|
232
|
+
"ember": "#c4a7e7",
|
|
233
|
+
"emberGlow": "#d9c4f2",
|
|
234
|
+
"emberDim": "rgba(196,167,231,0.25)",
|
|
235
|
+
"field": "#191724",
|
|
236
|
+
"surface": "#1f1d2e",
|
|
237
|
+
"elevated": "#26233a",
|
|
238
|
+
"hover": "#34304e",
|
|
239
|
+
"border": "#403d52",
|
|
240
|
+
"ink1": "#e0def4",
|
|
241
|
+
"ink2": "#c5c2dd",
|
|
242
|
+
"ink3": "#908caa",
|
|
243
|
+
"inkMuted": "#6e6a86"
|
|
244
|
+
},
|
|
245
|
+
"light": {
|
|
246
|
+
"ember": "#907aa9",
|
|
247
|
+
"emberGlow": "#6f598c",
|
|
248
|
+
"emberDim": "rgba(144,122,169,0.18)",
|
|
249
|
+
"field": "#f2e9e1",
|
|
250
|
+
"surface": "#faf4ed",
|
|
251
|
+
"elevated": "#fffaf3",
|
|
252
|
+
"hover": "#ebdfd4",
|
|
253
|
+
"border": "#cecacd",
|
|
254
|
+
"ink1": "#575279",
|
|
255
|
+
"ink2": "#635e87",
|
|
256
|
+
"ink3": "#6e6a88",
|
|
257
|
+
"inkMuted": "#857f96"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "everforest",
|
|
262
|
+
"label": "Everforest",
|
|
263
|
+
"dark": {
|
|
264
|
+
"ember": "#a7c080",
|
|
265
|
+
"emberGlow": "#c3d6a2",
|
|
266
|
+
"emberDim": "rgba(167,192,128,0.25)",
|
|
267
|
+
"field": "#232a2e",
|
|
268
|
+
"surface": "#2d353b",
|
|
269
|
+
"elevated": "#343f44",
|
|
270
|
+
"hover": "#3d484d",
|
|
271
|
+
"border": "#475258",
|
|
272
|
+
"ink1": "#d3c6aa",
|
|
273
|
+
"ink2": "#bfb6a3",
|
|
274
|
+
"ink3": "#9da9a0",
|
|
275
|
+
"inkMuted": "#859289"
|
|
276
|
+
},
|
|
277
|
+
"light": {
|
|
278
|
+
"ember": "#6f8352",
|
|
279
|
+
"emberGlow": "#56683f",
|
|
280
|
+
"emberDim": "rgba(111,131,82,0.18)",
|
|
281
|
+
"field": "#ebeadb",
|
|
282
|
+
"surface": "#f5f4e8",
|
|
283
|
+
"elevated": "#fbfbf4",
|
|
284
|
+
"hover": "#e5e4d3",
|
|
285
|
+
"border": "#d3d2bf",
|
|
286
|
+
"ink1": "#3d484e",
|
|
287
|
+
"ink2": "#55636b",
|
|
288
|
+
"ink3": "#66705f",
|
|
289
|
+
"inkMuted": "#7a8577"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"id": "kanagawa",
|
|
294
|
+
"label": "Kanagawa",
|
|
295
|
+
"dark": {
|
|
296
|
+
"ember": "#7e9cd8",
|
|
297
|
+
"emberGlow": "#9fb9ec",
|
|
298
|
+
"emberDim": "rgba(126,156,216,0.25)",
|
|
299
|
+
"field": "#16161d",
|
|
300
|
+
"surface": "#1f1f28",
|
|
301
|
+
"elevated": "#2a2a37",
|
|
302
|
+
"hover": "#363646",
|
|
303
|
+
"border": "#54546d",
|
|
304
|
+
"ink1": "#dcd7ba",
|
|
305
|
+
"ink2": "#c8c093",
|
|
306
|
+
"ink3": "#a6a69c",
|
|
307
|
+
"inkMuted": "#727169"
|
|
308
|
+
},
|
|
309
|
+
"light": {
|
|
310
|
+
"ember": "#4d699b",
|
|
311
|
+
"emberGlow": "#38537f",
|
|
312
|
+
"emberDim": "rgba(77,105,155,0.18)",
|
|
313
|
+
"field": "#e9e5d6",
|
|
314
|
+
"surface": "#f3f0e4",
|
|
315
|
+
"elevated": "#faf8f1",
|
|
316
|
+
"hover": "#e2ddcb",
|
|
317
|
+
"border": "#d2ccb6",
|
|
318
|
+
"ink1": "#545464",
|
|
319
|
+
"ink2": "#66667a",
|
|
320
|
+
"ink3": "#716e61",
|
|
321
|
+
"inkMuted": "#8a8775"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"id": "one-dark",
|
|
326
|
+
"label": "One Dark",
|
|
327
|
+
"dark": {
|
|
328
|
+
"ember": "#61afef",
|
|
329
|
+
"emberGlow": "#8cc7ff",
|
|
330
|
+
"emberDim": "rgba(97,175,239,0.25)",
|
|
331
|
+
"field": "#21252b",
|
|
332
|
+
"surface": "#282c34",
|
|
333
|
+
"elevated": "#2f343e",
|
|
334
|
+
"hover": "#3e4452",
|
|
335
|
+
"border": "#4b5263",
|
|
336
|
+
"ink1": "#d7dae0",
|
|
337
|
+
"ink2": "#abb2bf",
|
|
338
|
+
"ink3": "#8b919e",
|
|
339
|
+
"inkMuted": "#5c6370"
|
|
340
|
+
},
|
|
341
|
+
"light": {
|
|
342
|
+
"ember": "#4078f2",
|
|
343
|
+
"emberGlow": "#2c5dd4",
|
|
344
|
+
"emberDim": "rgba(64,120,242,0.18)",
|
|
345
|
+
"field": "#eaeaeb",
|
|
346
|
+
"surface": "#fafafa",
|
|
347
|
+
"elevated": "#ffffff",
|
|
348
|
+
"hover": "#e0e0e2",
|
|
349
|
+
"border": "#d4d4d6",
|
|
350
|
+
"ink1": "#383a42",
|
|
351
|
+
"ink2": "#50525a",
|
|
352
|
+
"ink3": "#696c77",
|
|
353
|
+
"inkMuted": "#a0a1a7"
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "night-owl",
|
|
358
|
+
"label": "Night Owl",
|
|
359
|
+
"dark": {
|
|
360
|
+
"ember": "#82aaff",
|
|
361
|
+
"emberGlow": "#a8c4ff",
|
|
362
|
+
"emberDim": "rgba(130,170,255,0.25)",
|
|
363
|
+
"field": "#011627",
|
|
364
|
+
"surface": "#0b2942",
|
|
365
|
+
"elevated": "#13344f",
|
|
366
|
+
"hover": "#1d3b53",
|
|
367
|
+
"border": "#5f7e97",
|
|
368
|
+
"ink1": "#d6deeb",
|
|
369
|
+
"ink2": "#b8c5d6",
|
|
370
|
+
"ink3": "#8ba3b8",
|
|
371
|
+
"inkMuted": "#637777"
|
|
372
|
+
},
|
|
373
|
+
"light": {
|
|
374
|
+
"ember": "#0c969b",
|
|
375
|
+
"emberGlow": "#0a7479",
|
|
376
|
+
"emberDim": "rgba(12,150,155,0.18)",
|
|
377
|
+
"field": "#ededed",
|
|
378
|
+
"surface": "#f6f6f6",
|
|
379
|
+
"elevated": "#fbfbfb",
|
|
380
|
+
"hover": "#e2e2e2",
|
|
381
|
+
"border": "#d0d0d0",
|
|
382
|
+
"ink1": "#403f53",
|
|
383
|
+
"ink2": "#545167",
|
|
384
|
+
"ink3": "#6f6e85",
|
|
385
|
+
"inkMuted": "#989fb1"
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"id": "monokai",
|
|
390
|
+
"label": "Monokai Pro",
|
|
391
|
+
"dark": {
|
|
392
|
+
"ember": "#ffd866",
|
|
393
|
+
"emberGlow": "#ffe6a0",
|
|
394
|
+
"emberDim": "rgba(255,216,102,0.25)",
|
|
395
|
+
"field": "#221f22",
|
|
396
|
+
"surface": "#2d2a2e",
|
|
397
|
+
"elevated": "#403e41",
|
|
398
|
+
"hover": "#4a484b",
|
|
399
|
+
"border": "#5b595c",
|
|
400
|
+
"ink1": "#fcfcfa",
|
|
401
|
+
"ink2": "#d9d8d6",
|
|
402
|
+
"ink3": "#939293",
|
|
403
|
+
"inkMuted": "#727072"
|
|
404
|
+
},
|
|
405
|
+
"light": {
|
|
406
|
+
"ember": "#c08a00",
|
|
407
|
+
"emberGlow": "#856000",
|
|
408
|
+
"emberDim": "rgba(192,138,0,0.18)",
|
|
409
|
+
"field": "#e9e6e4",
|
|
410
|
+
"surface": "#f1efed",
|
|
411
|
+
"elevated": "#fcfbfa",
|
|
412
|
+
"hover": "#e0dcda",
|
|
413
|
+
"border": "#cdc8c5",
|
|
414
|
+
"ink1": "#2c292d",
|
|
415
|
+
"ink2": "#46434a",
|
|
416
|
+
"ink3": "#6b686d",
|
|
417
|
+
"inkMuted": "#8d8a8d"
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"id": "github",
|
|
422
|
+
"label": "GitHub",
|
|
423
|
+
"dark": {
|
|
424
|
+
"ember": "#58a6ff",
|
|
425
|
+
"emberGlow": "#85bdff",
|
|
426
|
+
"emberDim": "rgba(88,166,255,0.25)",
|
|
427
|
+
"field": "#1f2428",
|
|
428
|
+
"surface": "#24292e",
|
|
429
|
+
"elevated": "#2b3138",
|
|
430
|
+
"hover": "#2f363d",
|
|
431
|
+
"border": "#444d56",
|
|
432
|
+
"ink1": "#e1e4e8",
|
|
433
|
+
"ink2": "#d1d5da",
|
|
434
|
+
"ink3": "#959da5",
|
|
435
|
+
"inkMuted": "#6a737d"
|
|
436
|
+
},
|
|
437
|
+
"light": {
|
|
438
|
+
"ember": "#0366d6",
|
|
439
|
+
"emberGlow": "#044289",
|
|
440
|
+
"emberDim": "rgba(3,102,214,0.18)",
|
|
441
|
+
"field": "#f0f2f4",
|
|
442
|
+
"surface": "#f6f8fa",
|
|
443
|
+
"elevated": "#ffffff",
|
|
444
|
+
"hover": "#e8eaed",
|
|
445
|
+
"border": "#d1d5da",
|
|
446
|
+
"ink1": "#24292e",
|
|
447
|
+
"ink2": "#444d56",
|
|
448
|
+
"ink3": "#586069",
|
|
449
|
+
"inkMuted": "#6a737d"
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"id": "ayu",
|
|
454
|
+
"label": "Ayu",
|
|
455
|
+
"dark": {
|
|
456
|
+
"ember": "#e6b450",
|
|
457
|
+
"emberGlow": "#f0cd85",
|
|
458
|
+
"emberDim": "rgba(230,180,80,0.25)",
|
|
459
|
+
"field": "#0d1017",
|
|
460
|
+
"surface": "#10141c",
|
|
461
|
+
"elevated": "#141821",
|
|
462
|
+
"hover": "#1d2330",
|
|
463
|
+
"border": "#2d3343",
|
|
464
|
+
"ink1": "#bfbdb6",
|
|
465
|
+
"ink2": "#a8a6a0",
|
|
466
|
+
"ink3": "#8a9199",
|
|
467
|
+
"inkMuted": "#6c7380"
|
|
468
|
+
},
|
|
469
|
+
"light": {
|
|
470
|
+
"ember": "#c97612",
|
|
471
|
+
"emberGlow": "#a65e0c",
|
|
472
|
+
"emberDim": "rgba(201,118,18,0.18)",
|
|
473
|
+
"field": "#eff1f3",
|
|
474
|
+
"surface": "#f8f9fa",
|
|
475
|
+
"elevated": "#fcfcfc",
|
|
476
|
+
"hover": "#e7eaed",
|
|
477
|
+
"border": "#d8dde2",
|
|
478
|
+
"ink1": "#3d4149",
|
|
479
|
+
"ink2": "#5c6166",
|
|
480
|
+
"ink3": "#787b80",
|
|
481
|
+
"inkMuted": "#8a9199"
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"id": "vitesse",
|
|
486
|
+
"label": "Vitesse",
|
|
487
|
+
"dark": {
|
|
488
|
+
"ember": "#4d9375",
|
|
489
|
+
"emberGlow": "#6fb392",
|
|
490
|
+
"emberDim": "rgba(77,147,117,0.25)",
|
|
491
|
+
"field": "#121212",
|
|
492
|
+
"surface": "#181818",
|
|
493
|
+
"elevated": "#1e1e1e",
|
|
494
|
+
"hover": "#262626",
|
|
495
|
+
"border": "#333333",
|
|
496
|
+
"ink1": "#dbd7ca",
|
|
497
|
+
"ink2": "#bfbaaa",
|
|
498
|
+
"ink3": "#8f8f85",
|
|
499
|
+
"inkMuted": "#758575"
|
|
500
|
+
},
|
|
501
|
+
"light": {
|
|
502
|
+
"ember": "#1c6b48",
|
|
503
|
+
"emberGlow": "#145236",
|
|
504
|
+
"emberDim": "rgba(28,107,72,0.18)",
|
|
505
|
+
"field": "#f0f0f0",
|
|
506
|
+
"surface": "#f7f7f7",
|
|
507
|
+
"elevated": "#ffffff",
|
|
508
|
+
"hover": "#e7e7e7",
|
|
509
|
+
"border": "#d6d6d6",
|
|
510
|
+
"ink1": "#393a34",
|
|
511
|
+
"ink2": "#4e4f47",
|
|
512
|
+
"ink3": "#6b6d63",
|
|
513
|
+
"inkMuted": "#a0ada0"
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"id": "synthwave",
|
|
518
|
+
"label": "Synthwave '84",
|
|
519
|
+
"dark": {
|
|
520
|
+
"ember": "#ff7edb",
|
|
521
|
+
"emberGlow": "#ffa9e7",
|
|
522
|
+
"emberDim": "rgba(255,126,219,0.25)",
|
|
523
|
+
"field": "#241b2f",
|
|
524
|
+
"surface": "#262335",
|
|
525
|
+
"elevated": "#322a47",
|
|
526
|
+
"hover": "#3d3460",
|
|
527
|
+
"border": "#495495",
|
|
528
|
+
"ink1": "#ffffff",
|
|
529
|
+
"ink2": "#d8d6e8",
|
|
530
|
+
"ink3": "#b6b1d8",
|
|
531
|
+
"inkMuted": "#848bbd"
|
|
532
|
+
},
|
|
533
|
+
"light": {
|
|
534
|
+
"ember": "#c936a6",
|
|
535
|
+
"emberGlow": "#9c2381",
|
|
536
|
+
"emberDim": "rgba(201,54,166,0.18)",
|
|
537
|
+
"field": "#e6e2f0",
|
|
538
|
+
"surface": "#efecf6",
|
|
539
|
+
"elevated": "#fbfafd",
|
|
540
|
+
"hover": "#ddd7ec",
|
|
541
|
+
"border": "#c5bcdd",
|
|
542
|
+
"ink1": "#241b2f",
|
|
543
|
+
"ink2": "#3d3455",
|
|
544
|
+
"ink3": "#5c5380",
|
|
545
|
+
"inkMuted": "#7d76a3"
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Unified hub for Claude Code tools — Marketplace + Kanban in one PWA",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node server.js",
|
|
11
11
|
"dev": "node server.js --open",
|
|
12
|
+
"test": "node --test test/*.test.js",
|
|
12
13
|
"update": "bash scripts/update-submodules.sh",
|
|
13
14
|
"update:check": "bash scripts/update-submodules.sh --check",
|
|
14
15
|
"lint": "npx @biomejs/biome check public/app.js server.js && npm run check:escaping && npm run check:security-lib",
|
|
@@ -43,10 +44,10 @@
|
|
|
43
44
|
},
|
|
44
45
|
"homepage": "https://github.com/NikiforovAll/claude-code-hub#readme",
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"claude-code-cost": "^0.
|
|
47
|
-
"claude-code-kanban": "^4.
|
|
48
|
-
"claude-code-marketplace": "^0.
|
|
49
|
-
"claude-code-memory-explorer": "^0.
|
|
47
|
+
"claude-code-cost": "^0.26.0",
|
|
48
|
+
"claude-code-kanban": "^4.31.1",
|
|
49
|
+
"claude-code-marketplace": "^0.22.0",
|
|
50
|
+
"claude-code-memory-explorer": "^0.17.0",
|
|
50
51
|
"express": "^4.21.0",
|
|
51
52
|
"open": "^10.1.0"
|
|
52
53
|
},
|
package/public/app.js
CHANGED
|
@@ -3,7 +3,6 @@ let activeApp = null;
|
|
|
3
3
|
const iframes = {};
|
|
4
4
|
const loadedApps = new Set();
|
|
5
5
|
const guardedApps = new Set();
|
|
6
|
-
let allowedOrigins = new Set();
|
|
7
6
|
// {theme: 'dark'|'light', colorTheme: '<id>', colorThemes: {<configDir>: '<id>'}} — survives hub
|
|
8
7
|
// reloads so late-loading iframes and fresh sessions get the last chosen theme. Light/dark is
|
|
9
8
|
// global; the color theme is per config dir, and colorTheme is the last one picked anywhere, which
|
|
@@ -55,7 +54,7 @@ function themeMessage() {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
// {<themeId>: {dark, light}}, each a map of CSS variables, from /api/config, which derives it from
|
|
58
|
-
//
|
|
57
|
+
// lib/themes.json — the same registry that generates each sub-app's themes.css. Empty until
|
|
59
58
|
// config arrives, and empty if the registry is unreadable; then the hub keeps index.html's colors.
|
|
60
59
|
let themePalettes = {};
|
|
61
60
|
// The last applied variables, so the loading screen paints in the theme before /api/config lands.
|
|
@@ -151,7 +150,7 @@ async function init() {
|
|
|
151
150
|
showLoading();
|
|
152
151
|
const res = await fetch('/api/config');
|
|
153
152
|
const config = await res.json();
|
|
154
|
-
|
|
153
|
+
apps = config.apps;
|
|
155
154
|
defaultConfigDir = config.defaultConfigDir ?? null;
|
|
156
155
|
activeConfigDir = config.activeConfigDir ?? null;
|
|
157
156
|
applyTitle(config.activeConfigDir);
|
|
@@ -172,11 +171,6 @@ function applyTitle(dir) {
|
|
|
172
171
|
document.title = dir && dir !== defaultConfigDir ? basename(dir) : 'Claude Code Hub';
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
function setApps(next) {
|
|
176
|
-
apps = next;
|
|
177
|
-
allowedOrigins = new Set(Object.values(apps).map((a) => new URL(a.url).origin));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
174
|
// After a config-dir switch every child is a new process, possibly on a new port. Reloading each
|
|
181
175
|
// iframe from scratch also drops the sub-apps' in-memory project state, which belonged to the old
|
|
182
176
|
// dir; the hub's own projectState is cleared for the same reason.
|
|
@@ -312,7 +306,8 @@ function listenMessages() {
|
|
|
312
306
|
if (guardedApps.size) e.preventDefault();
|
|
313
307
|
});
|
|
314
308
|
window.addEventListener('message', (e) => {
|
|
315
|
-
|
|
309
|
+
const appId = appIdOf(e.source);
|
|
310
|
+
if (!appId || e.origin !== originOf(appId)) return;
|
|
316
311
|
const data = e.data ?? {};
|
|
317
312
|
if (data.type === 'hub:navigate') {
|
|
318
313
|
if (!apps[data.app]) return;
|
|
@@ -324,14 +319,10 @@ function listenMessages() {
|
|
|
324
319
|
} else if (data.type === 'hub:keydown') {
|
|
325
320
|
handleForwardedKey(data);
|
|
326
321
|
} else if (data.type === 'hub:closeGuard') {
|
|
327
|
-
const appId = appIdOf(e.source);
|
|
328
|
-
if (!appId) return;
|
|
329
322
|
if (data.on === true) guardedApps.add(appId);
|
|
330
323
|
else guardedApps.delete(appId);
|
|
331
324
|
} else if (data.type === 'hub:terminalToken') {
|
|
332
325
|
// This hub page may have outlived the restart too, so the cached token is not trusted.
|
|
333
|
-
const appId = appIdOf(e.source);
|
|
334
|
-
if (!appId) return;
|
|
335
326
|
sendJson('GET', '/api/config')
|
|
336
327
|
.then((cfg) => {
|
|
337
328
|
const token = cfg.apps?.[appId]?.terminalToken;
|
|
@@ -785,7 +776,7 @@ async function commitConfigDir(row) {
|
|
|
785
776
|
if (target === palette.configDirs.active) return;
|
|
786
777
|
showLoading();
|
|
787
778
|
try {
|
|
788
|
-
|
|
779
|
+
apps = (await sendJson('POST', '/api/config-dirs/activate', { path: target })).apps;
|
|
789
780
|
// The palette renders from this cache before its refetch lands, and initialSel keys off it.
|
|
790
781
|
palette.configDirs.active = target;
|
|
791
782
|
activeConfigDir = target;
|
package/server.js
CHANGED
|
@@ -9,6 +9,7 @@ const tcp = require('net');
|
|
|
9
9
|
const os = require('os');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const { selectApps } = require('./lib/apps');
|
|
12
|
+
const { stripCookie } = require('./lib/cookies');
|
|
12
13
|
const { createNetGuard } = require('./lib/net-guard');
|
|
13
14
|
|
|
14
15
|
function getArg(name) {
|
|
@@ -20,6 +21,7 @@ function getArg(name) {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const HUB_PORT = parseInt(getArg('port') || process.env.PORT || '3540', 10);
|
|
24
|
+
let hubPort = HUB_PORT;
|
|
23
25
|
|
|
24
26
|
const POOL_SIZE = Math.max(1, parseInt(getArg('pool-size') || '3', 10));
|
|
25
27
|
const RESTART_DELAY_MS = 500;
|
|
@@ -191,7 +193,7 @@ function childEnv(pool, name) {
|
|
|
191
193
|
PORT: '0',
|
|
192
194
|
CLAUDE_CONFIG_DIR: pool.dir,
|
|
193
195
|
CLAUDE_HUB: '1',
|
|
194
|
-
HUB_URL: `http://localhost:${
|
|
196
|
+
HUB_URL: `http://localhost:${hubPort}`,
|
|
195
197
|
HOST: net.BIND_HOST,
|
|
196
198
|
ALLOWED_HOSTS: net.ALLOWED_HOSTS,
|
|
197
199
|
};
|
|
@@ -530,10 +532,14 @@ function forward(name, req, res, childPort, headers, replayable, onUpstream) {
|
|
|
530
532
|
// Host is forwarded untouched so the child's hostGuard still sees what the browser sent. Origin is
|
|
531
533
|
// the one header the child cannot judge on its own: its guard compares the port to its own
|
|
532
534
|
// ephemeral one, so the public-port spelling is mapped to the child's; anything else passes
|
|
533
|
-
// through as-is and the child rejects it.
|
|
535
|
+
// through as-is and the child rejects it. Cookies on localhost are shared across ports, so the
|
|
536
|
+
// browser sends the hub's own token cookie to every app; it never reaches a child.
|
|
534
537
|
function proxyHandler(name) {
|
|
535
538
|
return async (req, res) => {
|
|
536
539
|
const headers = { ...req.headers };
|
|
540
|
+
const cookie = stripCookie(headers.cookie, TOKEN_COOKIE);
|
|
541
|
+
if (cookie) headers.cookie = cookie;
|
|
542
|
+
else delete headers.cookie;
|
|
537
543
|
// req is consumed by the first attempt, so only a request with no body can be re-sent. Waiting
|
|
538
544
|
// for a port is not a replay — nothing has been sent yet — so every method gets the same wait.
|
|
539
545
|
const replayable = req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS';
|
|
@@ -562,6 +568,9 @@ function proxyHandler(name) {
|
|
|
562
568
|
sendUnavailable(req, res, name, 502, err.message);
|
|
563
569
|
return;
|
|
564
570
|
}
|
|
571
|
+
console.warn(
|
|
572
|
+
`[${name}] ${req.method} ${req.url.split('?')[0]} to 127.0.0.1:${childPort} failed (${err.code}), retrying`,
|
|
573
|
+
);
|
|
565
574
|
await delay(ATTEMPT_DELAY_MS);
|
|
566
575
|
}
|
|
567
576
|
};
|
|
@@ -580,24 +589,46 @@ function proxyUpgrade(name) {
|
|
|
580
589
|
}
|
|
581
590
|
const lines = [`${req.method} ${req.url} HTTP/${req.httpVersion}`];
|
|
582
591
|
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
592
|
+
const header = req.rawHeaders[i].toLowerCase();
|
|
583
593
|
let value = req.rawHeaders[i + 1];
|
|
584
|
-
if (
|
|
594
|
+
if (header === 'origin') value = rewriteOrigin(value, publicPorts[name], childPort);
|
|
595
|
+
if (header === 'cookie' && !(value = stripCookie(value, TOKEN_COOKIE))) continue;
|
|
585
596
|
lines.push(`${req.rawHeaders[i]}: ${value}`);
|
|
586
597
|
}
|
|
587
|
-
|
|
588
|
-
clearTimeout(connectTimer);
|
|
589
|
-
upstream.write(`${lines.join('\r\n')}\r\n\r\n`);
|
|
590
|
-
if (head?.length) upstream.write(head);
|
|
591
|
-
upstream.pipe(socket);
|
|
592
|
-
socket.pipe(upstream);
|
|
593
|
-
});
|
|
594
|
-
const connectTimer = setTimeout(() => upstream.destroy(), CONNECT_TIMEOUT_MS);
|
|
595
|
-
upstream.on('error', () => socket.destroy());
|
|
596
|
-
socket.on('close', () => upstream.destroy());
|
|
597
|
-
upstream.on('close', () => socket.destroy());
|
|
598
|
+
tunnel(name, childPort, `${lines.join('\r\n')}\r\n\r\n`, head, socket, 1);
|
|
598
599
|
};
|
|
599
600
|
}
|
|
600
601
|
|
|
602
|
+
// A fresh loopback connect to a child sometimes fails at once with ETIMEDOUT on Windows (cause
|
|
603
|
+
// unknown, seen only on a just-started hub), so a connect that never opened is tried once more.
|
|
604
|
+
function tunnel(name, childPort, request, head, socket, retries) {
|
|
605
|
+
let connected = false;
|
|
606
|
+
const upstream = tcp.connect(childPort, '127.0.0.1', () => {
|
|
607
|
+
connected = true;
|
|
608
|
+
clearTimeout(connectTimer);
|
|
609
|
+
upstream.write(request);
|
|
610
|
+
if (head?.length) upstream.write(head);
|
|
611
|
+
upstream.pipe(socket);
|
|
612
|
+
socket.pipe(upstream);
|
|
613
|
+
});
|
|
614
|
+
const connectTimer = setTimeout(() => upstream.destroy(), CONNECT_TIMEOUT_MS);
|
|
615
|
+
const onSocketClose = () => upstream.destroy();
|
|
616
|
+
socket.on('close', onSocketClose);
|
|
617
|
+
upstream.on('error', (err) => {
|
|
618
|
+
if (connected || socket.destroyed || retries < 1) {
|
|
619
|
+
if (!connected) console.warn(`[${name}] upgrade connect to 127.0.0.1:${childPort} failed (${err.code})`);
|
|
620
|
+
socket.destroy();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
console.warn(`[${name}] upgrade connect to 127.0.0.1:${childPort} failed (${err.code}), retrying`);
|
|
624
|
+
clearTimeout(connectTimer);
|
|
625
|
+
socket.off('close', onSocketClose);
|
|
626
|
+
upstream.removeAllListeners('close');
|
|
627
|
+
tunnel(name, childPort, request, head, socket, retries - 1);
|
|
628
|
+
});
|
|
629
|
+
upstream.on('close', () => socket.destroy());
|
|
630
|
+
}
|
|
631
|
+
|
|
601
632
|
function listenWithFallback(handler, port, onReady, label, onUpgrade) {
|
|
602
633
|
const opts = { maxHeaderSize: HDR_BYTES, onUpgrade };
|
|
603
634
|
const server = net.listenLoopback(handler, port, onReady, opts);
|
|
@@ -621,8 +652,6 @@ for (const [name, port] of Object.entries(publicPorts)) {
|
|
|
621
652
|
);
|
|
622
653
|
}
|
|
623
654
|
|
|
624
|
-
ensurePool(hubConfig.activeConfigDir);
|
|
625
|
-
|
|
626
655
|
const app = express();
|
|
627
656
|
|
|
628
657
|
// Mounted before the routes below, which are registered ahead of the first
|
|
@@ -649,7 +678,7 @@ const hubVars = (p) => ({
|
|
|
649
678
|
});
|
|
650
679
|
const themePalettes = (() => {
|
|
651
680
|
try {
|
|
652
|
-
const themes = JSON.parse(fs.readFileSync(path.join(__dirname, '
|
|
681
|
+
const themes = JSON.parse(fs.readFileSync(path.join(__dirname, 'lib/themes.json'), 'utf8'));
|
|
653
682
|
return Object.fromEntries(themes.map((t) => [t.id, { dark: hubVars(t.dark), light: hubVars(t.light) }]));
|
|
654
683
|
} catch {
|
|
655
684
|
// Palette falls back to the --accent in index.html; not worth failing startup over.
|
|
@@ -770,7 +799,11 @@ app.get('/api/resolve-path', (req, res) => {
|
|
|
770
799
|
|
|
771
800
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
772
801
|
|
|
802
|
+
// Children get HUB_URL at spawn and trust only that origin, so the first pool waits for the port
|
|
803
|
+
// the hub actually bound: HUB_PORT may be busy and fall back to a random one.
|
|
773
804
|
const onReady = (actual) => {
|
|
805
|
+
hubPort = actual;
|
|
806
|
+
ensurePool(hubConfig.activeConfigDir);
|
|
774
807
|
printBanner(actual);
|
|
775
808
|
if (process.argv.includes('--open')) {
|
|
776
809
|
import('open').then((m) => m.default(accessUrl(actual)));
|