claude-code-hub 0.37.0 → 0.39.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -40
- package/lib/net-guard.js +20 -3
- package/package.json +7 -5
- package/public/app.js +206 -47
- package/public/index.html +131 -10
- package/public/locked.html +169 -0
- package/public/sw.js +12 -2
- package/server.js +367 -58
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link rel="manifest" href="/manifest.json">
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/icons/icon-192.svg">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
|
|
9
|
+
<title>Claude Code Hub</title>
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--bg: #1a1a2e;
|
|
13
|
+
--border: #0f3460;
|
|
14
|
+
--accent: #e86f33;
|
|
15
|
+
--surface: #16213e;
|
|
16
|
+
--surface-hover: #1f2a4a;
|
|
17
|
+
--text: #e8e8ef;
|
|
18
|
+
--text-dim: #8a8fa3;
|
|
19
|
+
--shadow: rgba(0, 0, 0, 0.55);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:root.light {
|
|
23
|
+
--bg: #f4f4f5;
|
|
24
|
+
--border: #e4e4e7;
|
|
25
|
+
--surface: #ffffff;
|
|
26
|
+
--surface-hover: #f1f1f4;
|
|
27
|
+
--text: #18181b;
|
|
28
|
+
--text-dim: #71717a;
|
|
29
|
+
--shadow: rgba(0, 0, 0, 0.18);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
33
|
+
|
|
34
|
+
body {
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
display: grid;
|
|
37
|
+
place-items: center;
|
|
38
|
+
padding: 16px;
|
|
39
|
+
background:
|
|
40
|
+
radial-gradient(circle at 50% 42%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 45%),
|
|
41
|
+
var(--bg);
|
|
42
|
+
color: var(--text);
|
|
43
|
+
font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main {
|
|
47
|
+
width: min(440px, 100%);
|
|
48
|
+
display: grid;
|
|
49
|
+
justify-items: center;
|
|
50
|
+
gap: 20px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.clawd {
|
|
54
|
+
width: 64px;
|
|
55
|
+
height: 40px;
|
|
56
|
+
fill: #d97757;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.card {
|
|
60
|
+
width: 100%;
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: 14px;
|
|
63
|
+
padding: 22px;
|
|
64
|
+
background: var(--surface);
|
|
65
|
+
border: 1px solid var(--border);
|
|
66
|
+
border-radius: 12px;
|
|
67
|
+
box-shadow: 0 16px 48px var(--shadow);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
h1 { font-size: 16px; font-weight: 600; }
|
|
71
|
+
|
|
72
|
+
p { color: var(--text-dim); font-size: 13px; }
|
|
73
|
+
|
|
74
|
+
code {
|
|
75
|
+
padding: 1px 5px;
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
background: var(--surface-hover);
|
|
78
|
+
color: var(--text);
|
|
79
|
+
font: inherit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
form { display: flex; gap: 8px; }
|
|
83
|
+
|
|
84
|
+
input {
|
|
85
|
+
flex: 1;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
padding: 9px 12px;
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
border-radius: 8px;
|
|
90
|
+
background: var(--bg);
|
|
91
|
+
color: var(--text);
|
|
92
|
+
font: inherit;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
button {
|
|
96
|
+
padding: 9px 16px;
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
background: var(--accent);
|
|
100
|
+
color: #fff;
|
|
101
|
+
font: inherit;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
button:hover { filter: brightness(1.08); }
|
|
107
|
+
|
|
108
|
+
button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
109
|
+
|
|
110
|
+
.error { color: #e5484d; }
|
|
111
|
+
|
|
112
|
+
.error:empty { display: none; }
|
|
113
|
+
</style>
|
|
114
|
+
<script>
|
|
115
|
+
(() => {
|
|
116
|
+
let mode;
|
|
117
|
+
let cached;
|
|
118
|
+
try {
|
|
119
|
+
mode = JSON.parse(localStorage.getItem('hub-theme'))?.theme;
|
|
120
|
+
cached = JSON.parse(localStorage.getItem('hub-palette'));
|
|
121
|
+
} catch {}
|
|
122
|
+
mode ??= matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
123
|
+
const root = document.documentElement;
|
|
124
|
+
root.classList.toggle('light', mode === 'light');
|
|
125
|
+
if (cached?.key?.endsWith(`/${mode}`)) {
|
|
126
|
+
for (const [name, value] of Object.entries(cached.vars ?? {})) root.style.setProperty(name, value);
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
</script>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
<main>
|
|
133
|
+
<svg class="clawd" viewBox="1 0 16 10" shape-rendering="crispEdges" aria-hidden="true">
|
|
134
|
+
<rect x="3" y="0" width="12" height="2"/>
|
|
135
|
+
<rect x="3" y="2" width="2" height="2"/>
|
|
136
|
+
<rect x="6" y="2" width="6" height="2"/>
|
|
137
|
+
<rect x="13" y="2" width="2" height="2"/>
|
|
138
|
+
<rect x="1" y="4" width="16" height="2"/>
|
|
139
|
+
<rect x="3" y="6" width="12" height="2"/>
|
|
140
|
+
<rect x="4" y="8" width="1" height="2"/>
|
|
141
|
+
<rect x="6" y="8" width="1" height="2"/>
|
|
142
|
+
<rect x="11" y="8" width="1" height="2"/>
|
|
143
|
+
<rect x="13" y="8" width="1" height="2"/>
|
|
144
|
+
</svg>
|
|
145
|
+
<div class="card">
|
|
146
|
+
<h1>Unlock Claude Code Hub</h1>
|
|
147
|
+
<p>Open the link the hub prints when it starts, or paste the token from <code>~/.claude-hub/token</code>. This browser remembers it.</p>
|
|
148
|
+
<form id="unlock">
|
|
149
|
+
<input id="token" type="password" autocomplete="off" spellcheck="false" placeholder="Access token" aria-label="Access token" required autofocus>
|
|
150
|
+
<button type="submit">Unlock</button>
|
|
151
|
+
</form>
|
|
152
|
+
<p class="error" id="error" role="alert"></p>
|
|
153
|
+
</div>
|
|
154
|
+
</main>
|
|
155
|
+
<script>
|
|
156
|
+
const params = new URLSearchParams(location.search);
|
|
157
|
+
if (params.has('token')) {
|
|
158
|
+
document.getElementById('error').textContent = 'That token does not match. Check it and try again.';
|
|
159
|
+
params.delete('token');
|
|
160
|
+
history.replaceState(null, '', location.pathname + (params.size ? `?${params}` : ''));
|
|
161
|
+
}
|
|
162
|
+
document.getElementById('unlock').addEventListener('submit', (e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
params.set('token', document.getElementById('token').value.trim());
|
|
165
|
+
location.replace(`${location.pathname}?${params}`);
|
|
166
|
+
});
|
|
167
|
+
</script>
|
|
168
|
+
</body>
|
|
169
|
+
</html>
|
package/public/sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CACHE_NAME = 'hub-shell-
|
|
1
|
+
const CACHE_NAME = 'hub-shell-v2';
|
|
2
2
|
const SHELL_ASSETS = ['/', '/index.html', '/app.js', '/manifest.json'];
|
|
3
3
|
|
|
4
4
|
self.addEventListener('install', (e) => {
|
|
@@ -23,6 +23,16 @@ self.addEventListener('fetch', (e) => {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
e.respondWith(
|
|
26
|
-
fetch(e.request)
|
|
26
|
+
fetch(e.request)
|
|
27
|
+
.then((res) => {
|
|
28
|
+
// Refresh the copy, or a load while the hub restarts serves the shell from install time.
|
|
29
|
+
if (res.ok && e.request.method === 'GET' && SHELL_ASSETS.includes(new URL(e.request.url).pathname)) {
|
|
30
|
+
const copy = res.clone();
|
|
31
|
+
e.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.put(e.request, copy)));
|
|
32
|
+
}
|
|
33
|
+
return res;
|
|
34
|
+
})
|
|
35
|
+
// respondWith(undefined) throws, so a miss while the server is down must still be a Response.
|
|
36
|
+
.catch(() => caches.match(e.request).then((cached) => cached || Response.error()))
|
|
27
37
|
);
|
|
28
38
|
});
|