claude-code-hub 0.36.0 → 0.37.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-hub",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Unified hub for Claude Code tools — Marketplace + Kanban in one PWA",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -37,9 +37,9 @@
37
37
  },
38
38
  "homepage": "https://github.com/NikiforovAll/claude-code-hub#readme",
39
39
  "dependencies": {
40
- "claude-code-cost": "^0.22.0",
41
- "claude-code-kanban": "^4.25.0",
42
- "claude-code-marketplace": "^0.17.0",
40
+ "claude-code-cost": "^0.23.0",
41
+ "claude-code-kanban": "^4.26.0",
42
+ "claude-code-marketplace": "^0.18.0",
43
43
  "claude-code-memory-explorer": "^0.14.0",
44
44
  "express": "^4.21.0",
45
45
  "open": "^10.1.0"
package/public/app.js CHANGED
@@ -10,6 +10,8 @@ const themeState = loadThemeState();
10
10
  // hub-theme: starting unset means there is nothing to race against each sub-app's own
11
11
  // project self-restore on boot. Once set it never returns to null.
12
12
  let projectState = null;
13
+ // The config dir the hub falls back to (~/.claude). Stays out of the window title.
14
+ let defaultConfigDir = null;
13
15
  // mode: 'project' (Ctrl+Alt+P) or 'configDir' (Ctrl+Alt+W). One widget, two row sources.
14
16
  const palette = {
15
17
  open: false,
@@ -120,6 +122,8 @@ async function init() {
120
122
  const res = await fetch('/api/config');
121
123
  const config = await res.json();
122
124
  setApps(config.apps);
125
+ defaultConfigDir = config.defaultConfigDir ?? null;
126
+ applyTitle(config.activeConfigDir);
123
127
  themeAccents = config.themeAccents ?? {};
124
128
  applyHubTheme();
125
129
  buildIframes();
@@ -130,6 +134,13 @@ async function init() {
130
134
  registerSW();
131
135
  }
132
136
 
137
+ // The hub has no visible chrome, so the window/tab title is the only place the active config dir
138
+ // shows. Only a non-default dir is named, and by basename alone — the PWA window prepends the
139
+ // manifest name, so a product name here would read as "Claude Code Hub - .claude-eom · Hub".
140
+ function applyTitle(dir) {
141
+ document.title = dir && dir !== defaultConfigDir ? basename(dir) : 'Claude Code Hub';
142
+ }
143
+
133
144
  function setApps(next) {
134
145
  apps = next;
135
146
  allowedOrigins = new Set(Object.values(apps).map((a) => new URL(a.url).origin));
@@ -210,6 +221,17 @@ function listenMessages() {
210
221
  if (data.url) iframes[data.app].src = apps[data.app].url + data.url;
211
222
  } else if (data.type === 'hub:keydown') {
212
223
  handleForwardedKey(data);
224
+ } else if (data.type === 'hub:openExternal') {
225
+ // In the installed PWA window a framed sub-app's own target=_blank opens
226
+ // nothing, so the shims hand external links up to the top frame instead.
227
+ let url;
228
+ try {
229
+ url = new URL(String(data.url));
230
+ } catch (_) {
231
+ return;
232
+ }
233
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return;
234
+ window.open(url.href, '_blank', 'noopener');
213
235
  } else if (data.type === 'hub:theme') {
214
236
  // Legacy senders pass only {theme}; colorTheme is optional and sticky.
215
237
  if (data.theme !== 'light' && data.theme !== 'dark') return;
@@ -622,6 +644,7 @@ async function commitConfigDir(row) {
622
644
  setApps((await sendJson('POST', '/api/config-dirs/activate', { path: target })).apps);
623
645
  // The palette renders from this cache before its refetch lands, and initialSel keys off it.
624
646
  palette.configDirs.active = target;
647
+ applyTitle(target);
625
648
  reloadIframes();
626
649
  } catch (err) {
627
650
  console.warn('config dir switch failed:', err.message);
package/server.js CHANGED
@@ -38,6 +38,10 @@ function activePool() {
38
38
  // be known before the first spawn, and the hub already has one localStorage key to reason about.
39
39
  const HUB_CONFIG_FILE = path.join(os.homedir(), '.claude-hub', 'config.json');
40
40
 
41
+ // The dir the hub would use with no saved config. Set by loadHubConfig; the client uses it to keep
42
+ // the default dir out of the window title.
43
+ let defaultConfigDir = null;
44
+
41
45
  function expandHome(p) {
42
46
  return p.startsWith('~') ? p.replace('~', os.homedir()) : p;
43
47
  }
@@ -59,6 +63,7 @@ function loadHubConfig() {
59
63
  const fallback = canonical(
60
64
  process.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_DIR || path.join(os.homedir(), '.claude'),
61
65
  );
66
+ defaultConfigDir = fallback;
62
67
  let raw = '';
63
68
  let saved = {};
64
69
  try {
@@ -346,7 +351,7 @@ function appsConfig() {
346
351
  }
347
352
 
348
353
  app.get('/api/config', (_req, res) => {
349
- res.json({ themeAccents, apps: appsConfig() });
354
+ res.json({ themeAccents, apps: appsConfig(), activeConfigDir: hubConfig.activeConfigDir, defaultConfigDir });
350
355
  });
351
356
 
352
357
  app.get('/api/config-dirs', (_req, res) => {