@epiphytic/claudecodeui 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -25,11 +25,11 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-CWwPqmRx.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-Dlv06cpK.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DcyRfQm3.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CJLzwpLB.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DfaPXD3y.js">
32
- <link rel="stylesheet" crossorigin href="/assets/index-BV_fwoPa.css">
32
+ <link rel="stylesheet" crossorigin href="/assets/index-CceDF8mT.css">
33
33
  </head>
34
34
  <body>
35
35
  <div id="root"></div>
package/dist/sw.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Service Worker for Claude Code UI PWA
2
2
  // Supports both direct access and orchestrator proxy access via proxyBase parameter
3
3
 
4
- const CACHE_NAME = "claude-ui-v2";
4
+ const CACHE_NAME = "claude-ui-v3";
5
5
 
6
6
  // Extract proxyBase from the service worker URL query string
7
7
  // e.g., sw.js?proxyBase=/clients/badal-laptop/proxy
@@ -9,7 +9,21 @@ const swUrl = new URL(self.location.href);
9
9
  const proxyBase = swUrl.searchParams.get("proxyBase") || "";
10
10
 
11
11
  // URLs to cache (root-relative, without proxyBase)
12
- const urlsToCache = ["/", "/index.html", "/manifest.json"];
12
+ const urlsToCache = [
13
+ "/",
14
+ "/index.html",
15
+ "/manifest.json",
16
+ "/favicon.svg",
17
+ "/favicon.png",
18
+ "/icons/claude-ai-icon.svg",
19
+ "/icons/cursor.svg",
20
+ "/icons/cursor-white.svg",
21
+ "/icons/codex.svg",
22
+ "/icons/codex-white.svg",
23
+ "/icons/icon-152x152.png",
24
+ "/icons/icon-192x192.png",
25
+ "/icons/icon-512x512.png",
26
+ ];
13
27
 
14
28
  // Normalize a URL by removing the proxyBase prefix if present
15
29
  // This allows us to use consistent cache keys regardless of access path
@@ -56,6 +70,11 @@ self.addEventListener("install", (event) => {
56
70
  self.skipWaiting();
57
71
  });
58
72
 
73
+ // Check if URL is a static asset that should use cache-first strategy
74
+ function isStaticAsset(url) {
75
+ return /\.(svg|png|jpg|jpeg|gif|ico|woff2?|ttf|eot|css|js)(\?.*)?$/.test(url);
76
+ }
77
+
59
78
  // Fetch event
60
79
  self.addEventListener("fetch", (event) => {
61
80
  event.respondWith(
@@ -69,7 +88,10 @@ self.addEventListener("fetch", (event) => {
69
88
  normalizedUrl === "manifest.json"
70
89
  ) {
71
90
  try {
72
- const networkResponse = await fetch(request);
91
+ // Use cache: 'no-cache' to get fresh content but still respect ETag
92
+ const networkResponse = await fetch(request.url, {
93
+ cache: "no-cache",
94
+ });
73
95
  // Only cache successful responses
74
96
  if (networkResponse.ok) {
75
97
  const cache = await caches.open(CACHE_NAME);
@@ -109,7 +131,25 @@ self.addEventListener("fetch", (event) => {
109
131
  return response;
110
132
  }
111
133
 
112
- // Otherwise fetch from network
134
+ // For static assets, fetch with cache mode 'default' to use browser caching
135
+ // and cache the response in the service worker for offline use
136
+ if (isStaticAsset(normalizedUrl)) {
137
+ try {
138
+ // Use cache: 'default' to leverage browser HTTP caching
139
+ const networkResponse = await fetch(request.url, {
140
+ cache: "default",
141
+ });
142
+ if (networkResponse.ok) {
143
+ cache.put(request, networkResponse.clone());
144
+ }
145
+ return networkResponse;
146
+ } catch {
147
+ // If fetch fails and we have no cached response, throw
148
+ throw new Error(`Failed to fetch static asset: ${normalizedUrl}`);
149
+ }
150
+ }
151
+
152
+ // For other requests, just fetch from network
113
153
  return fetch(request);
114
154
  })(),
115
155
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epiphytic/claudecodeui",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "A web-based UI for Claude Code CLI",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -69,7 +69,7 @@
69
69
  "@xterm/addon-webgl": "^0.18.0",
70
70
  "@xterm/xterm": "^5.5.0",
71
71
  "bcrypt": "^6.0.0",
72
- "better-sqlite3": "^12.2.0",
72
+ "better-sqlite3": "^12.6.2",
73
73
  "chokidar": "^4.0.3",
74
74
  "class-variance-authority": "^0.7.1",
75
75
  "clsx": "^2.1.1",
@@ -85,6 +85,8 @@
85
85
  "multer": "^2.0.1",
86
86
  "node-fetch": "^2.7.0",
87
87
  "node-pty": "^1.1.0-beta34",
88
+ "pino": "^10.3.0",
89
+ "pino-pretty": "^13.1.3",
88
90
  "react": "^18.2.0",
89
91
  "react-dom": "^18.2.0",
90
92
  "react-dropzone": "^14.2.3",
package/public/sw.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Service Worker for Claude Code UI PWA
2
2
  // Supports both direct access and orchestrator proxy access via proxyBase parameter
3
3
 
4
- const CACHE_NAME = "claude-ui-v2";
4
+ const CACHE_NAME = "claude-ui-v3";
5
5
 
6
6
  // Extract proxyBase from the service worker URL query string
7
7
  // e.g., sw.js?proxyBase=/clients/badal-laptop/proxy
@@ -9,7 +9,21 @@ const swUrl = new URL(self.location.href);
9
9
  const proxyBase = swUrl.searchParams.get("proxyBase") || "";
10
10
 
11
11
  // URLs to cache (root-relative, without proxyBase)
12
- const urlsToCache = ["/", "/index.html", "/manifest.json"];
12
+ const urlsToCache = [
13
+ "/",
14
+ "/index.html",
15
+ "/manifest.json",
16
+ "/favicon.svg",
17
+ "/favicon.png",
18
+ "/icons/claude-ai-icon.svg",
19
+ "/icons/cursor.svg",
20
+ "/icons/cursor-white.svg",
21
+ "/icons/codex.svg",
22
+ "/icons/codex-white.svg",
23
+ "/icons/icon-152x152.png",
24
+ "/icons/icon-192x192.png",
25
+ "/icons/icon-512x512.png",
26
+ ];
13
27
 
14
28
  // Normalize a URL by removing the proxyBase prefix if present
15
29
  // This allows us to use consistent cache keys regardless of access path
@@ -56,6 +70,11 @@ self.addEventListener("install", (event) => {
56
70
  self.skipWaiting();
57
71
  });
58
72
 
73
+ // Check if URL is a static asset that should use cache-first strategy
74
+ function isStaticAsset(url) {
75
+ return /\.(svg|png|jpg|jpeg|gif|ico|woff2?|ttf|eot|css|js)(\?.*)?$/.test(url);
76
+ }
77
+
59
78
  // Fetch event
60
79
  self.addEventListener("fetch", (event) => {
61
80
  event.respondWith(
@@ -69,7 +88,10 @@ self.addEventListener("fetch", (event) => {
69
88
  normalizedUrl === "manifest.json"
70
89
  ) {
71
90
  try {
72
- const networkResponse = await fetch(request);
91
+ // Use cache: 'no-cache' to get fresh content but still respect ETag
92
+ const networkResponse = await fetch(request.url, {
93
+ cache: "no-cache",
94
+ });
73
95
  // Only cache successful responses
74
96
  if (networkResponse.ok) {
75
97
  const cache = await caches.open(CACHE_NAME);
@@ -109,7 +131,25 @@ self.addEventListener("fetch", (event) => {
109
131
  return response;
110
132
  }
111
133
 
112
- // Otherwise fetch from network
134
+ // For static assets, fetch with cache mode 'default' to use browser caching
135
+ // and cache the response in the service worker for offline use
136
+ if (isStaticAsset(normalizedUrl)) {
137
+ try {
138
+ // Use cache: 'default' to leverage browser HTTP caching
139
+ const networkResponse = await fetch(request.url, {
140
+ cache: "default",
141
+ });
142
+ if (networkResponse.ok) {
143
+ cache.put(request, networkResponse.clone());
144
+ }
145
+ return networkResponse;
146
+ } catch {
147
+ // If fetch fails and we have no cached response, throw
148
+ throw new Error(`Failed to fetch static asset: ${normalizedUrl}`);
149
+ }
150
+ }
151
+
152
+ // For other requests, just fetch from network
113
153
  return fetch(request);
114
154
  })(),
115
155
  );
@@ -97,6 +97,11 @@ const runMigrations = () => {
97
97
  );
98
98
  }
99
99
 
100
+ if (!columnNames.includes("avatar_url")) {
101
+ console.log("Running migration: Adding avatar_url column");
102
+ db.exec("ALTER TABLE users ADD COLUMN avatar_url TEXT");
103
+ }
104
+
100
105
  // Check if tmux_sessions table exists
101
106
  const tables = db
102
107
  .prepare(
@@ -214,7 +219,7 @@ const userDb = {
214
219
  try {
215
220
  const row = db
216
221
  .prepare(
217
- "SELECT id, username, created_at, last_login FROM users WHERE id = ? AND is_active = 1",
222
+ "SELECT id, username, avatar_url, created_at, last_login FROM users WHERE id = ? AND is_active = 1",
218
223
  )
219
224
  .get(userId);
220
225
  return row;
@@ -227,7 +232,7 @@ const userDb = {
227
232
  try {
228
233
  const row = db
229
234
  .prepare(
230
- "SELECT id, username, created_at, last_login FROM users WHERE is_active = 1 LIMIT 1",
235
+ "SELECT id, username, avatar_url, created_at, last_login FROM users WHERE is_active = 1 LIMIT 1",
231
236
  )
232
237
  .get();
233
238
  return row;
@@ -298,25 +303,28 @@ const userDb = {
298
303
  throw new Error("githubId is required and must be a non-empty string");
299
304
  }
300
305
 
306
+ // Construct avatar URL using our proxy endpoint for cacheability
307
+ const avatarUrl = `/api/avatar/${githubId}?s=80`;
308
+
301
309
  // First, try to find user by github_id (most authoritative)
302
310
  let user = db
303
311
  .prepare(
304
- "SELECT id, username, github_id, created_at, last_login FROM users WHERE github_id = ? AND is_active = 1",
312
+ "SELECT id, username, github_id, avatar_url, created_at, last_login FROM users WHERE github_id = ? AND is_active = 1",
305
313
  )
306
314
  .get(githubId);
307
315
 
308
316
  if (user) {
309
- // Found user by github_id, update last_login
317
+ // Found user by github_id, update last_login and avatar_url
310
318
  db.prepare(
311
- "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?",
312
- ).run(user.id);
313
- return user;
319
+ "UPDATE users SET last_login = CURRENT_TIMESTAMP, avatar_url = ? WHERE id = ?",
320
+ ).run(avatarUrl, user.id);
321
+ return { ...user, avatar_url: avatarUrl };
314
322
  }
315
323
 
316
324
  // No user found by github_id, check if username exists
317
325
  const existingByUsername = db
318
326
  .prepare(
319
- "SELECT id, username, github_id, created_at, last_login FROM users WHERE username = ? AND is_active = 1",
327
+ "SELECT id, username, github_id, avatar_url, created_at, last_login FROM users WHERE username = ? AND is_active = 1",
320
328
  )
321
329
  .get(githubUsername);
322
330
 
@@ -333,13 +341,14 @@ const userDb = {
333
341
  const passwordHash = await bcrypt.hash(randomPassword, 12);
334
342
 
335
343
  const stmt = db.prepare(
336
- "INSERT INTO users (username, password_hash, github_id, has_completed_onboarding, last_login) VALUES (?, ?, ?, 1, CURRENT_TIMESTAMP)",
344
+ "INSERT INTO users (username, password_hash, github_id, avatar_url, has_completed_onboarding, last_login) VALUES (?, ?, ?, ?, 1, CURRENT_TIMESTAMP)",
337
345
  );
338
- const result = stmt.run(namespacedUsername, passwordHash, githubId);
346
+ const result = stmt.run(namespacedUsername, passwordHash, githubId, avatarUrl);
339
347
  user = {
340
348
  id: result.lastInsertRowid,
341
349
  username: namespacedUsername,
342
350
  github_id: githubId,
351
+ avatar_url: avatarUrl,
343
352
  };
344
353
  console.log(
345
354
  `[ORCHESTRATOR] Created namespaced user for orchestrator auth: ${namespacedUsername} (GitHub ID: ${githubId})`,
@@ -347,14 +356,14 @@ const userDb = {
347
356
  return user;
348
357
  }
349
358
 
350
- // Username exists with no github_id or matching github_id - bind the github_id
359
+ // Username exists with no github_id or matching github_id - bind the github_id and avatar
351
360
  db.prepare(
352
- "UPDATE users SET github_id = ?, last_login = CURRENT_TIMESTAMP WHERE id = ?",
353
- ).run(githubId, existingByUsername.id);
361
+ "UPDATE users SET github_id = ?, avatar_url = ?, last_login = CURRENT_TIMESTAMP WHERE id = ?",
362
+ ).run(githubId, avatarUrl, existingByUsername.id);
354
363
  console.log(
355
364
  `[ORCHESTRATOR] Bound github_id ${githubId} to existing user: ${githubUsername}`,
356
365
  );
357
- return { ...existingByUsername, github_id: githubId };
366
+ return { ...existingByUsername, github_id: githubId, avatar_url: avatarUrl };
358
367
  }
359
368
 
360
369
  // No existing user found, create new one
@@ -362,13 +371,14 @@ const userDb = {
362
371
  const passwordHash = await bcrypt.hash(randomPassword, 12);
363
372
 
364
373
  const stmt = db.prepare(
365
- "INSERT INTO users (username, password_hash, github_id, has_completed_onboarding, last_login) VALUES (?, ?, ?, 1, CURRENT_TIMESTAMP)",
374
+ "INSERT INTO users (username, password_hash, github_id, avatar_url, has_completed_onboarding, last_login) VALUES (?, ?, ?, ?, 1, CURRENT_TIMESTAMP)",
366
375
  );
367
- const result = stmt.run(githubUsername, passwordHash, githubId);
376
+ const result = stmt.run(githubUsername, passwordHash, githubId, avatarUrl);
368
377
  user = {
369
378
  id: result.lastInsertRowid,
370
379
  username: githubUsername,
371
380
  github_id: githubId,
381
+ avatar_url: avatarUrl,
372
382
  };
373
383
  console.log(
374
384
  `[ORCHESTRATOR] Created new user for orchestrator auth: ${githubUsername} (GitHub ID: ${githubId})`,
@@ -12,7 +12,8 @@ CREATE TABLE IF NOT EXISTS users (
12
12
  git_name TEXT,
13
13
  git_email TEXT,
14
14
  has_completed_onboarding BOOLEAN DEFAULT 0,
15
- github_id TEXT UNIQUE
15
+ github_id TEXT UNIQUE,
16
+ avatar_url TEXT
16
17
  );
17
18
 
18
19
  -- Indexes for performance