claude-code-kanban 7.18.0 → 7.19.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 +1 -1
- package/public/app.js +103 -50
- package/public/index.html +3 -3
- package/public/style.css +1 -1
- package/public/vendor/claude-hub-sdk.js +18 -3
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -3433,24 +3433,34 @@ function agentIdentHtml(a) {
|
|
|
3433
3433
|
|
|
3434
3434
|
// The count names every running agent; the rows stop at ZEN_AGENTS_MAX, and the Agents Log
|
|
3435
3435
|
// below the board lists the rest.
|
|
3436
|
-
function zenAgentsHtml(
|
|
3437
|
-
const active = agents.filter((a) => a.status === 'active');
|
|
3436
|
+
function zenAgentsHtml(active) {
|
|
3438
3437
|
if (!active.length) return '';
|
|
3439
|
-
const now = Date.now();
|
|
3440
3438
|
const rows = active.slice(0, ZEN_AGENTS_MAX).map((a) => {
|
|
3441
3439
|
const task = truncateAtWord(agentTaskText(a), 200);
|
|
3442
3440
|
return `<button type="button" class="zen-agent" title="${escapeHtml(task)}" onclick="showAgentModal('${escAttrJs(a.agentId)}')">
|
|
3443
3441
|
<span class="agent-dot active"></span>
|
|
3444
3442
|
<span class="zen-agent-text"><span class="zen-agent-task">${escapeHtml(task)}</span><span class="agent-ident-row">${agentIdentHtml(a)}</span></span>
|
|
3445
|
-
<span class="zen-agent-time"
|
|
3443
|
+
<span class="zen-agent-time"></span>
|
|
3446
3444
|
</button>`;
|
|
3447
3445
|
});
|
|
3448
3446
|
return `<div class="panel-section-header"><span>Agents</span><span class="panel-section-count">${active.length}</span></div>${rows.join('')}`;
|
|
3449
3447
|
}
|
|
3450
3448
|
|
|
3449
|
+
// The 1 s duration tick calls this. Rewriting the rows each time would drop the focus of a row,
|
|
3450
|
+
// so the rows are rewritten only when they change and the times are set in place.
|
|
3451
3451
|
function renderZenAgents(agents) {
|
|
3452
3452
|
const el = zenMode && sessionsList.querySelector('.zen-agents');
|
|
3453
|
-
if (el)
|
|
3453
|
+
if (!el) return;
|
|
3454
|
+
const active = agents.filter((a) => a.status === 'active');
|
|
3455
|
+
const html = zenAgentsHtml(active);
|
|
3456
|
+
if (html !== el.dataset.shape) {
|
|
3457
|
+
el.dataset.shape = html;
|
|
3458
|
+
el.innerHTML = html;
|
|
3459
|
+
}
|
|
3460
|
+
const now = Date.now();
|
|
3461
|
+
el.querySelectorAll('.zen-agent-time').forEach((t, i) => {
|
|
3462
|
+
t.textContent = formatDuration(agentElapsed(active[i], now));
|
|
3463
|
+
});
|
|
3454
3464
|
}
|
|
3455
3465
|
|
|
3456
3466
|
// Filter shutdown ghosts: for same-type agents, keep if they overlapped (parallel)
|
|
@@ -4111,11 +4121,13 @@ function renderSessions() {
|
|
|
4111
4121
|
${renderZenBookmarksHtml(zenSession.id)}
|
|
4112
4122
|
</div>
|
|
4113
4123
|
${zenSession.hasWorkflow ? renderWorkflowLiveHtml(zenSession.id) : ''}
|
|
4114
|
-
<div class="zen-agents"
|
|
4124
|
+
<div class="zen-agents"></div>`;
|
|
4125
|
+
renderZenAgents(visibleAgents());
|
|
4115
4126
|
bindLinkedDocsHandlers(sessionsList.querySelector('.linked-docs-section'), zenSession.id);
|
|
4116
4127
|
artifactsSection.load(zenSession.id);
|
|
4117
4128
|
scratchFilesSection.load(zenSession.id);
|
|
4118
4129
|
if (zenSession.hasWorkflow) pollWorkflowLive(zenSession.id);
|
|
4130
|
+
restoreSessionCursor();
|
|
4119
4131
|
return;
|
|
4120
4132
|
}
|
|
4121
4133
|
|
|
@@ -4364,7 +4376,10 @@ function renderSessions() {
|
|
|
4364
4376
|
const tail = sectioned ? sectionHtml(SECTION_SESSIONS, 'Sessions', true, restHtml, sgRest.length === 0) : restHtml;
|
|
4365
4377
|
sessionsList.innerHTML = html + sgSectionHtml(true) + tail + outsideHtml;
|
|
4366
4378
|
}
|
|
4379
|
+
restoreSessionCursor();
|
|
4380
|
+
}
|
|
4367
4381
|
|
|
4382
|
+
function restoreSessionCursor() {
|
|
4368
4383
|
const navItems = getNavigableItems();
|
|
4369
4384
|
const allSessions = getSessionItems();
|
|
4370
4385
|
const activeIdx = allSessions.findIndex((el) => el.classList.contains('active'));
|
|
@@ -7949,6 +7964,21 @@ document.addEventListener('keydown', (e) => {
|
|
|
7949
7964
|
toggleZenMode();
|
|
7950
7965
|
return;
|
|
7951
7966
|
}
|
|
7967
|
+
if (e.key === '$' && plainShift) {
|
|
7968
|
+
e.preventDefault();
|
|
7969
|
+
openCost(contextSessionId());
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7972
|
+
if (matchKey(e, 'KeyM')) {
|
|
7973
|
+
e.preventDefault();
|
|
7974
|
+
openMarketplace(contextSessionId());
|
|
7975
|
+
return;
|
|
7976
|
+
}
|
|
7977
|
+
if (ClaudeHub.comboOf(e) === 'ctrl+m') {
|
|
7978
|
+
e.preventDefault();
|
|
7979
|
+
openMemory(contextSessionId());
|
|
7980
|
+
return;
|
|
7981
|
+
}
|
|
7952
7982
|
if (e.key === '.' || e.key === '>') {
|
|
7953
7983
|
const sid = sessionsList.querySelector('.kb-selected')?.dataset.sessionId || currentSessionId;
|
|
7954
7984
|
if (sid) {
|
|
@@ -8089,23 +8119,6 @@ document.addEventListener('keydown', (e) => {
|
|
|
8089
8119
|
else toggleScratchpad();
|
|
8090
8120
|
return;
|
|
8091
8121
|
}
|
|
8092
|
-
if (e.key === '$' && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
|
8093
|
-
e.preventDefault();
|
|
8094
|
-
openCost(contextSid);
|
|
8095
|
-
return;
|
|
8096
|
-
}
|
|
8097
|
-
if (matchKey(e, 'KeyM')) {
|
|
8098
|
-
e.preventDefault();
|
|
8099
|
-
const mSession = contextSid ? sessions.find((s) => s.id === contextSid) : null;
|
|
8100
|
-
openMarketplace(mSession?.project);
|
|
8101
|
-
return;
|
|
8102
|
-
}
|
|
8103
|
-
if (ClaudeHub.comboOf(e) === 'ctrl+m') {
|
|
8104
|
-
e.preventDefault();
|
|
8105
|
-
const mSession = contextSid ? sessions.find((s) => s.id === contextSid) : null;
|
|
8106
|
-
openMemory(mSession?.project);
|
|
8107
|
-
return;
|
|
8108
|
-
}
|
|
8109
8122
|
if (ClaudeHub.comboOf(e) === 'ctrl+d') {
|
|
8110
8123
|
e.preventDefault();
|
|
8111
8124
|
dismissContextSession();
|
|
@@ -8709,7 +8722,9 @@ const ZOOM_CLAIMS = ['ctrlKey', 'metaKey'].flatMap((mod) =>
|
|
|
8709
8722
|
// first keeps it, and text fields keep every key.
|
|
8710
8723
|
const PANE_CLAIMS = [
|
|
8711
8724
|
...['\\', '[', ']', 'ContextMenu', 'Escape'].map((key) => ({ key, code: '' })),
|
|
8712
|
-
...['|', '{', '}', 'F10'].map((key) => ({ shiftKey: true, key, code: '' })),
|
|
8725
|
+
...['|', '{', '}', 'F10', '$'].map((key) => ({ shiftKey: true, key, code: '' })),
|
|
8726
|
+
{ key: 'Unidentified', code: 'KeyM' },
|
|
8727
|
+
{ ctrlKey: true, key: 'm', code: '' },
|
|
8713
8728
|
...['BracketLeft', 'BracketRight', 'KeyW'].map((code) => ({ altKey: true, key: 'Unidentified', code })),
|
|
8714
8729
|
...['KeyL', 'KeyM', 'KeyP', 'KeyS', 'KeyZ'].map((code) => ({ shiftKey: true, key: 'Unidentified', code })),
|
|
8715
8730
|
...'123456789'.split('').map((n) => ({ ctrlKey: true, altKey: true, key: 'Unidentified', code: `Digit${n}` })),
|
|
@@ -9197,7 +9212,7 @@ function createPreviewFrame(className, srcdoc, sandbox = 'allow-scripts allow-po
|
|
|
9197
9212
|
frame.setAttribute('referrerpolicy', 'no-referrer');
|
|
9198
9213
|
frame.srcdoc = srcdoc;
|
|
9199
9214
|
bridgedFrames.add(frame);
|
|
9200
|
-
frame.addEventListener('load', () => sendBridgeClaimsAll([frame], keyClaims()));
|
|
9215
|
+
frame.addEventListener('load', () => sendBridgeClaimsAll([frame], bridgeClaims(keyClaims())));
|
|
9201
9216
|
return frame;
|
|
9202
9217
|
}
|
|
9203
9218
|
|
|
@@ -10275,7 +10290,7 @@ function setupEventSource() {
|
|
|
10275
10290
|
// Alone, the page would open a tab without a user gesture, which the browser blocks; the
|
|
10276
10291
|
// CLI prints the link instead.
|
|
10277
10292
|
if (data.type === 'session:cost' && hub.inHub) {
|
|
10278
|
-
openCost(data.id, data.agent);
|
|
10293
|
+
openCost(data.id, { agent: data.agent, pane: false });
|
|
10279
10294
|
}
|
|
10280
10295
|
|
|
10281
10296
|
if (data.type === 'session:pin') {
|
|
@@ -13148,31 +13163,33 @@ function openFolderInEditor(folder, file) {
|
|
|
13148
13163
|
postAndToast('/api/open-folder', body, 'folder');
|
|
13149
13164
|
}
|
|
13150
13165
|
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
|
|
13154
|
-
hub.invoke(
|
|
13166
|
+
// Opens the app in a pane of `sid` and brings the pane into view. With no session, or `pane: false`
|
|
13167
|
+
// (a Shift-click), the hub switches to the app's tab instead.
|
|
13168
|
+
async function openAppAction(action, params, sid, { pane = true, label }) {
|
|
13169
|
+
if (!sid || !pane) return hub.invoke(action, params);
|
|
13170
|
+
const { url } = await hub.invoke(action, params, { open: 'pane' });
|
|
13171
|
+
if (!url) return;
|
|
13172
|
+
const onScreen = await postPane(sid, { target: url, title: label });
|
|
13173
|
+
if (onScreen !== null) revealPane(sid, onScreen);
|
|
13155
13174
|
}
|
|
13156
13175
|
|
|
13157
|
-
function
|
|
13158
|
-
|
|
13176
|
+
function openCost(sid, { agent, pane } = {}) {
|
|
13177
|
+
const opts = { pane, label: 'Cost' };
|
|
13178
|
+
if (sid && agent && hub.can('agent.cost')) return openAppAction('agent.cost', { session: sid, agent }, sid, opts);
|
|
13179
|
+
return openAppAction('session.cost', sid ? { session: sid } : {}, sid, opts);
|
|
13159
13180
|
}
|
|
13160
13181
|
|
|
13161
|
-
function
|
|
13162
|
-
|
|
13182
|
+
function openProjectApp(action, label, sid, { pane } = {}) {
|
|
13183
|
+
const project = sessions.find((s) => s.id === sid)?.project;
|
|
13184
|
+
return openAppAction(action, project ? { project } : {}, sid, { pane, label });
|
|
13163
13185
|
}
|
|
13186
|
+
const openMarketplace = (sid, opts) => openProjectApp('project.plugins', 'Marketplace', sid, opts);
|
|
13187
|
+
const openMemory = (sid, opts) => openProjectApp('project.memory', 'Memory', sid, opts);
|
|
13164
13188
|
|
|
13165
|
-
function openForInfoModalProject(open) {
|
|
13166
|
-
const s = sessions.find((x) => x.id === _infoModalSessionId);
|
|
13167
|
-
if (s?.project) open(s.project);
|
|
13168
|
-
}
|
|
13169
|
-
// biome-ignore lint/correctness/noUnusedVariables: used in HTML
|
|
13170
|
-
function openMarketplaceForInfoModal() {
|
|
13171
|
-
openForInfoModalProject(openMarketplace);
|
|
13172
|
-
}
|
|
13173
13189
|
// biome-ignore lint/correctness/noUnusedVariables: used in HTML
|
|
13174
|
-
function
|
|
13175
|
-
|
|
13190
|
+
function openAppForInfoModal(open, e) {
|
|
13191
|
+
closeTeamModal();
|
|
13192
|
+
open(_infoModalSessionId, { pane: !e.shiftKey });
|
|
13176
13193
|
}
|
|
13177
13194
|
|
|
13178
13195
|
//#endregion
|
|
@@ -13883,6 +13900,11 @@ async function loadPaneView(sid, pane, view, scroll) {
|
|
|
13883
13900
|
frame.setAttribute('referrerpolicy', 'no-referrer');
|
|
13884
13901
|
frame.title = pane.title;
|
|
13885
13902
|
frame.src = pane.target;
|
|
13903
|
+
frame.addEventListener('load', () => {
|
|
13904
|
+
view.classList.remove('keys-bridged');
|
|
13905
|
+
frame.hostKeysSig = null;
|
|
13906
|
+
pushTerminalClaims();
|
|
13907
|
+
});
|
|
13886
13908
|
view.appendChild(frame);
|
|
13887
13909
|
view.insertAdjacentHTML('beforeend', paneUrlBarHtml());
|
|
13888
13910
|
// Framed at once; a refusal found by the probe swaps the frame for the card.
|
|
@@ -15574,23 +15596,49 @@ function bridgeClaims(claims) {
|
|
|
15574
15596
|
return { ...claims, keys: [...claims.keys, ...ZOOM_CLAIMS, ...PANE_CLAIMS, ...review] };
|
|
15575
15597
|
}
|
|
15576
15598
|
|
|
15577
|
-
function sendBridgeClaimsAll(frames,
|
|
15578
|
-
const base = bridgeClaims(claims);
|
|
15599
|
+
function sendBridgeClaimsAll(frames, base) {
|
|
15579
15600
|
const rest = frames.filter((f) => f !== showState.frame);
|
|
15580
15601
|
sendBridgeClaims(rest, base);
|
|
15581
15602
|
if (rest.length < frames.length)
|
|
15582
15603
|
sendBridgeClaims([showState.frame], { ...base, keys: [...base.keys, ...SHOW_CLAIMS] });
|
|
15583
15604
|
}
|
|
15584
15605
|
|
|
15606
|
+
// A hub app in a URL pane runs the SDK, which hands back these keys (hub protocol §5.2). Only keys with
|
|
15607
|
+
// Ctrl, Alt or Meta: the page keeps Escape, brackets and its own letter keys.
|
|
15608
|
+
function sendHostKeys(frames, bridged) {
|
|
15609
|
+
const keys = bridged.keys.filter((k) => k.ctrlKey || k.altKey || k.metaKey);
|
|
15610
|
+
const sig = JSON.stringify(keys);
|
|
15611
|
+
for (const f of frames) {
|
|
15612
|
+
const origin = URL.parse(f.src)?.origin;
|
|
15613
|
+
if (!origin || origin === 'null' || f.hostKeysSig === sig) continue;
|
|
15614
|
+
f.hostKeysSig = sig;
|
|
15615
|
+
f.contentWindow?.postMessage({ type: 'hub:hostKeys', keys }, origin);
|
|
15616
|
+
}
|
|
15617
|
+
}
|
|
15618
|
+
|
|
15619
|
+
const urlPaneFrames = () => [...paneViews.querySelectorAll('.pane-url-frame')];
|
|
15620
|
+
|
|
15621
|
+
window.addEventListener('message', (e) => {
|
|
15622
|
+
const type = e.data?.type;
|
|
15623
|
+
if (type !== 'hub:hostKey' && type !== 'hub:hostReady') return;
|
|
15624
|
+
const frame = urlPaneFrames().find((f) => f.contentWindow === e.source);
|
|
15625
|
+
if (!frame || URL.parse(frame.src)?.origin !== e.origin) return;
|
|
15626
|
+
if (type === 'hub:hostReady') frame.parentElement.classList.add('keys-bridged');
|
|
15627
|
+
else replayKey(e.data, frame);
|
|
15628
|
+
});
|
|
15629
|
+
|
|
15585
15630
|
function pushTerminalClaims() {
|
|
15586
15631
|
if (terminalClaimsQueued) return;
|
|
15587
15632
|
terminalClaimsQueued = true;
|
|
15588
15633
|
queueMicrotask(() => {
|
|
15589
15634
|
terminalClaimsQueued = false;
|
|
15590
15635
|
const bridged = bridgedFrameEls();
|
|
15591
|
-
|
|
15636
|
+
const urlFrames = urlPaneFrames();
|
|
15637
|
+
if (!termFrame.inited && !kanbotState.inited && !bridged.length && !urlFrames.length) return;
|
|
15592
15638
|
const claims = keyClaims();
|
|
15593
|
-
|
|
15639
|
+
const base = bridgeClaims(claims);
|
|
15640
|
+
sendBridgeClaimsAll(bridged, base);
|
|
15641
|
+
sendHostKeys(urlFrames, base);
|
|
15594
15642
|
const sig = JSON.stringify(claims);
|
|
15595
15643
|
sendFrameClaims(kanbotState, claims, sig, postToKanbotFrame);
|
|
15596
15644
|
sendFrameClaims(termFrame, claims, sig, terminalFrameSend);
|
|
@@ -16999,10 +17047,15 @@ async function openShowInPane() {
|
|
|
16999
17047
|
}
|
|
17000
17048
|
|
|
17001
17049
|
// `onScreen` is postPane's result: null failed, true is already in view unless the terminal covers it.
|
|
17050
|
+
function revealPane(sid, onScreen) {
|
|
17051
|
+
if (onScreen && !wantsTerminal()) syncPanes();
|
|
17052
|
+
else if (sid === currentSessionId && wantsTerminal()) toggleTerminal();
|
|
17053
|
+
else openSession(sid);
|
|
17054
|
+
}
|
|
17055
|
+
|
|
17002
17056
|
function toastPaneAdded(sid, onScreen, label) {
|
|
17003
17057
|
if (onScreen === null || (onScreen && !wantsTerminal())) return;
|
|
17004
|
-
|
|
17005
|
-
showToast(`${label} added as a pane`, 'success', { label: 'Open', onClick: open });
|
|
17058
|
+
showToast(`${label} added as a pane`, 'success', { label: 'Open', onClick: () => revealPane(sid, false) });
|
|
17006
17059
|
}
|
|
17007
17060
|
|
|
17008
17061
|
function showCommand(cmd) {
|
package/public/index.html
CHANGED
|
@@ -557,13 +557,13 @@
|
|
|
557
557
|
<button id="session-info-sticky-btn" class="icon-btn" style="display:none" title="Sticky pin — always show in the list" onclick="toggleSessionSticky(_infoModalSessionId); updateStickyBtnState()">
|
|
558
558
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 L15 9 L22 9 L17 14 L19 22 L12 18 L5 22 L7 14 L2 9 L9 9 Z"/></svg>
|
|
559
559
|
</button>
|
|
560
|
-
<button id="session-info-cost-btn" class="icon-btn" style="display:none" title="Open in Cost" onclick="openCost
|
|
560
|
+
<button id="session-info-cost-btn" class="icon-btn" style="display:none" title="Open in Cost (Shift: Cost tab)" onclick="openAppForInfoModal(openCost, event)">
|
|
561
561
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>
|
|
562
562
|
</button>
|
|
563
|
-
<button id="session-info-marketplace-btn" class="icon-btn" style="display:none" title="Open in Marketplace" onclick="
|
|
563
|
+
<button id="session-info-marketplace-btn" class="icon-btn" style="display:none" title="Open in Marketplace (Shift: Marketplace tab)" onclick="openAppForInfoModal(openMarketplace, event)">
|
|
564
564
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
565
565
|
</button>
|
|
566
|
-
<button id="session-info-memory-btn" class="icon-btn" style="display:none" title="Open in Memory" onclick="
|
|
566
|
+
<button id="session-info-memory-btn" class="icon-btn" style="display:none" title="Open in Memory (Shift: Memory tab)" onclick="openAppForInfoModal(openMemory, event)">
|
|
567
567
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
|
|
568
568
|
</button>
|
|
569
569
|
<button class="modal-close" aria-label="Close dialog" onclick="closeTeamModal()">
|
package/public/style.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// claude-hub-sdk 1.
|
|
1
|
+
// claude-hub-sdk 1.8.0 (sha256 9a67dd2aa534). Copied by npm run sdk:sync in claude-code-hub. Do not edit.
|
|
2
2
|
// Claude Code Hub SDK: the one rule that names a key press, shared by the hub page, client.js and
|
|
3
3
|
// stub.js. The hub serves it to its page as /sdk/keys.js; mount() and sdk:sync put it in front of
|
|
4
4
|
// client.js and stub.js, so an app still loads one file.
|
|
@@ -33,7 +33,21 @@ var ClaudeHubKeys = (() => {
|
|
|
33
33
|
return combo.split('+').map((k) => mods[k] ?? named[k] ?? (k.length === 1 ? k.toUpperCase() : k));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// A host claim (protocol §5.2) names a press by its physical code, or by its character when the layout
|
|
37
|
+
// moves the key: {ctrlKey?, altKey?, shiftKey?, metaKey?, code, key: 'Unidentified'} or {…, key, code: ''}.
|
|
38
|
+
// A press matches a claim when one of its two signatures is the claim's first. AltGr types a
|
|
39
|
+
// character, so its press matches by character only. cck's frames use the same format.
|
|
40
|
+
function keySigs(e) {
|
|
41
|
+
const mods = [e.ctrlKey && 'ctrl', e.altKey && 'alt', e.shiftKey && 'shift', e.metaKey && 'meta']
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join('+');
|
|
44
|
+
return [
|
|
45
|
+
e.code && !e.getModifierState?.('AltGraph') && `${mods}|c:${e.code}`,
|
|
46
|
+
typeof e.key === 'string' && e.key !== 'Unidentified' && `${mods}|k:${e.key.toLowerCase()}`,
|
|
47
|
+
].filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { comboOf, keyParts, keySigs };
|
|
37
51
|
})();
|
|
38
52
|
|
|
39
53
|
if (typeof module === 'object' && module.exports) module.exports = ClaudeHubKeys;
|
|
@@ -69,9 +83,10 @@ if (typeof module === 'object' && module.exports) module.exports = ClaudeHubKeys
|
|
|
69
83
|
bindTheme: () => off,
|
|
70
84
|
handle() {},
|
|
71
85
|
publish() {},
|
|
72
|
-
invoke(action, params = {}) {
|
|
86
|
+
invoke(action, params = {}, opts = {}) {
|
|
73
87
|
const target = standaloneFn(action)?.(params);
|
|
74
88
|
if (!target) return Promise.resolve({ ok: false, reason: 'unhandled' });
|
|
89
|
+
if (opts.open === 'pane') return Promise.resolve({ ok: true, handledBy: 'standalone', url: target });
|
|
75
90
|
win.open(target, '_blank', 'noopener');
|
|
76
91
|
return Promise.resolve({ ok: true, handledBy: 'standalone' });
|
|
77
92
|
},
|