claude-code-hub 1.35.0 → 1.36.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/features/voice/feature.json +1 -1
- package/package.json +2 -2
- package/packages/claude-hub-sdk/src/client.js +6 -2
- package/public/app.js +15 -1
- package/server.js +17 -2
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"listing": {
|
|
21
21
|
"tagline": "Hold a key and speak: start a session or send a prompt with no Enter press",
|
|
22
22
|
"description": "Hold Ctrl+Shift+Space, speak and let go. \"New session, fix the flaky test\" opens the New session dialog with that prompt, and anything else goes to the session on screen as a prompt and is submitted. Kanban's New session microphone uses the same speech to text. Hex does the speech to text on your machine; without it, the browser's speech recognition does it.",
|
|
23
|
-
"tags": ["voice"
|
|
23
|
+
"tags": ["voice"],
|
|
24
24
|
"screenshots": [
|
|
25
25
|
{
|
|
26
26
|
"url": "https://nikiforovall.blog/claude-code-hub/shots/extensions/voice-demo.webp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "Unified hub for Claude Code tools — Marketplace + Kanban in one PWA",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"homepage": "https://github.com/NikiforovAll/claude-code-hub#readme",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"claude-code-cost": "1.6.2",
|
|
50
|
-
"claude-code-kanban": "7.
|
|
50
|
+
"claude-code-kanban": "7.33.0",
|
|
51
51
|
"claude-code-marketplace": "1.7.3",
|
|
52
52
|
"claude-code-memory-explorer": "1.3.6",
|
|
53
53
|
"express": "^4.21.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
} catch (_) {}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function connect({ standalone = {} } = {}) {
|
|
104
|
+
function connect({ standalone = {}, plainHostKeys = [] } = {}) {
|
|
105
105
|
if (hub) return hub;
|
|
106
106
|
const topics = new Map();
|
|
107
107
|
const activeFns = new Set();
|
|
@@ -275,7 +275,11 @@
|
|
|
275
275
|
const m = e.data;
|
|
276
276
|
if (!framed || e.source !== win.parent || win.parent === win.top) return;
|
|
277
277
|
if (m?.type !== 'hub:hostKeys' || !Array.isArray(m.keys)) return;
|
|
278
|
-
if (!hostKeys)
|
|
278
|
+
if (!hostKeys)
|
|
279
|
+
win.parent.postMessage(
|
|
280
|
+
{ type: 'hub:hostReady', ...(plainHostKeys.length && { plainKeys: plainHostKeys }) },
|
|
281
|
+
e.origin,
|
|
282
|
+
);
|
|
279
283
|
hostOrigin = e.origin;
|
|
280
284
|
hostKeys = new Set(m.keys.map((k) => keySigs(k)[0]));
|
|
281
285
|
}
|
package/public/app.js
CHANGED
|
@@ -1111,8 +1111,9 @@ function addIframe(id) {
|
|
|
1111
1111
|
document.getElementById('iframe-container').appendChild(iframes[id]);
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
|
+
// A deferredStart app's first page load starts it, so its frame waits until the tab opens.
|
|
1114
1115
|
function buildIframes() {
|
|
1115
|
-
for (const id of tabIds()) addIframe(id);
|
|
1116
|
+
for (const id of tabIds()) if (!apps[id].deferredStart) addIframe(id);
|
|
1116
1117
|
}
|
|
1117
1118
|
|
|
1118
1119
|
const TAB_KEY = 'hub-tab';
|
|
@@ -1656,6 +1657,19 @@ const APP_ICONS = {
|
|
|
1656
1657
|
['path', { d: 'M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3' }],
|
|
1657
1658
|
['path', { d: 'M12 12V8' }],
|
|
1658
1659
|
],
|
|
1660
|
+
'notebook-pen': [
|
|
1661
|
+
['path', { d: 'M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4' }],
|
|
1662
|
+
['path', { d: 'M2 6h4' }],
|
|
1663
|
+
['path', { d: 'M2 10h4' }],
|
|
1664
|
+
['path', { d: 'M2 14h4' }],
|
|
1665
|
+
['path', { d: 'M2 18h4' }],
|
|
1666
|
+
[
|
|
1667
|
+
'path',
|
|
1668
|
+
{
|
|
1669
|
+
d: 'M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z',
|
|
1670
|
+
},
|
|
1671
|
+
],
|
|
1672
|
+
],
|
|
1659
1673
|
puzzle: [
|
|
1660
1674
|
[
|
|
1661
1675
|
'path',
|
package/server.js
CHANGED
|
@@ -397,6 +397,7 @@ async function restartApp(pool, app) {
|
|
|
397
397
|
const { id } = app;
|
|
398
398
|
pool.restarts[id] = [];
|
|
399
399
|
pool.stopped.delete(id);
|
|
400
|
+
pool.deferred.delete(id);
|
|
400
401
|
await killApp(pool, id);
|
|
401
402
|
if (pool.retired) return false;
|
|
402
403
|
const child = spawnOne(pool, app);
|
|
@@ -406,9 +407,21 @@ async function restartApp(pool, app) {
|
|
|
406
407
|
|
|
407
408
|
async function stopApp(pool, { id }) {
|
|
408
409
|
pool.stopped.add(id);
|
|
410
|
+
pool.deferred.delete(id);
|
|
409
411
|
await killApp(pool, id);
|
|
410
412
|
}
|
|
411
413
|
|
|
414
|
+
// A deferredStart app that nothing has started yet starts on its first page load: its tab, a pane,
|
|
415
|
+
// an action. One the user stopped stays stopped.
|
|
416
|
+
function startOnDemand(name) {
|
|
417
|
+
const pool = activePool();
|
|
418
|
+
if (!pool?.deferred.delete(name)) return;
|
|
419
|
+
console.log(`[${name}] starting on first open`);
|
|
420
|
+
pool.stopped.delete(name);
|
|
421
|
+
const target = ENABLED_APPS.find((a) => a.id === name);
|
|
422
|
+
spawnOne(pool, target);
|
|
423
|
+
}
|
|
424
|
+
|
|
412
425
|
// Out of pool.children before the kill, so the exit handler does not restart it.
|
|
413
426
|
async function killApp(pool, id) {
|
|
414
427
|
const old = pool.children.get(id);
|
|
@@ -448,7 +461,7 @@ async function ensurePool(dir) {
|
|
|
448
461
|
pool = pools.get(dir);
|
|
449
462
|
if (pool) return pool.ready;
|
|
450
463
|
const stopped = new Set(ENABLED_APPS.filter((a) => a.deferredStart).map((a) => a.id));
|
|
451
|
-
pool = { dir, children: new Map(), ports: {}, restarts: {}, stopped, retired: false };
|
|
464
|
+
pool = { dir, children: new Map(), ports: {}, restarts: {}, stopped, deferred: new Set(stopped), retired: false };
|
|
452
465
|
pools.set(dir, pool);
|
|
453
466
|
spawnChildren(pool);
|
|
454
467
|
const ready = [...pool.children.values()].map((c) => c.ready);
|
|
@@ -819,6 +832,7 @@ function proxyHandler(name) {
|
|
|
819
832
|
if (body === undefined) return;
|
|
820
833
|
// One deadline for the whole request, not one per attempt: a retry must not re-spend the wait.
|
|
821
834
|
const budget = { deadline: Date.now() + (isNavigation(req) ? NAV_PORT_WAIT_MS : API_PORT_WAIT_MS) };
|
|
835
|
+
if (isNavigation(req)) startOnDemand(name);
|
|
822
836
|
// One listener for the request, however many attempts it takes.
|
|
823
837
|
let current = null;
|
|
824
838
|
res.once('close', () => current?.destroy());
|
|
@@ -963,6 +977,7 @@ function appsConfig() {
|
|
|
963
977
|
publishes: a.publishes,
|
|
964
978
|
};
|
|
965
979
|
if (a.hidden) entry.hidden = true;
|
|
980
|
+
if (a.deferredStart) entry.deferredStart = true;
|
|
966
981
|
if (a.settings.length) entry.settings = appValues.get(a.id);
|
|
967
982
|
if (TERMINAL_TOKEN && a.id === TERMINAL_APP.id) entry.terminalToken = TERMINAL_TOKEN;
|
|
968
983
|
return [a.id, entry];
|
|
@@ -999,7 +1014,7 @@ app.get('/api/config', (_req, res) => {
|
|
|
999
1014
|
configDirSwitch: configDirSwitch(hubConfig),
|
|
1000
1015
|
voice: voice && { provider: voice.provider, auto: voice.auto, commands: voice.commands },
|
|
1001
1016
|
version: HUB_VERSION,
|
|
1002
|
-
onboarding: hubConfig.onboarding ?? null,
|
|
1017
|
+
onboarding: process.env.CLAUDE_HUB_NO_ONBOARDING ? 'skipped' : (hubConfig.onboarding ?? null),
|
|
1003
1018
|
});
|
|
1004
1019
|
});
|
|
1005
1020
|
|