synabun 2026.4.21 → 2026.4.25-beta.2
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/CHANGELOG.md +175 -881
- package/README.md +1 -1
- package/hooks/claude-code/prompt-submit.mjs +39 -18
- package/hooks/claude-code/session-start.mjs +10 -3
- package/hooks/claude-code/shared.mjs +39 -2
- package/hooks/claude-code/stop.mjs +33 -6
- package/mcp-server/src/index.ts +9 -1
- package/mcp-server/src/services/neural-interface.ts +42 -14
- package/mcp-server/src/services/profiles.ts +8 -7
- package/mcp-server/src/tools/browser-advanced.ts +12 -10
- package/mcp-server/src/tools/browser-interact.ts +39 -38
- package/mcp-server/src/tools/browser-navigate.ts +12 -4
- package/mcp-server/src/tools/loop.ts +7 -3
- package/mcp-server/src/tools/profile.ts +19 -1
- package/neural-interface/lib/loop-logger.js +103 -0
- package/neural-interface/lib/mcp-installer.js +109 -0
- package/neural-interface/lib/plugin-installer.js +264 -0
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/shared/api.js +66 -17
- package/neural-interface/public/shared/cdx/cdx-icons.js +5 -4
- package/neural-interface/public/shared/cdx/cdx-panel.js +117 -14
- package/neural-interface/public/shared/cdx/cdx-render.js +170 -43
- package/neural-interface/public/shared/cdx/cdx-styles.js +55 -10
- package/neural-interface/public/shared/cdx/cdx-tabs.js +394 -72
- package/neural-interface/public/shared/cli-status.js +157 -0
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/html-shell.js +32 -11
- package/neural-interface/public/shared/ocp/ocp-panel.js +127 -28
- package/neural-interface/public/shared/ocp/ocp-render.js +52 -44
- package/neural-interface/public/shared/ocp/ocp-styles.js +33 -0
- package/neural-interface/public/shared/ocp/ocp-tabs.js +693 -82
- package/neural-interface/public/shared/styles.css +635 -0
- package/neural-interface/public/shared/ui-automation-studio.js +38 -12
- package/neural-interface/public/shared/ui-claude-panel.js +322 -15
- package/neural-interface/public/shared/ui-file-explorer.js +38 -18
- package/neural-interface/public/shared/ui-menubar.js +34 -10
- package/neural-interface/public/shared/ui-navbar.js +8 -6
- package/neural-interface/public/shared/ui-schedules-studio.js +1464 -0
- package/neural-interface/public/shared/ui-settings.js +274 -50
- package/neural-interface/public/shared/ui-sidepanel-layout.js +18 -7
- package/neural-interface/public/shared/ui-sidepanel-tray.js +287 -0
- package/neural-interface/public/shared/ui-sync.js +9 -5
- package/neural-interface/public/shared/ui-terminal.js +76 -27
- package/neural-interface/public/shared/ui-tool-updates.js +49 -15
- package/neural-interface/public/shared/ui-tutorial-steps.js +4 -2
- package/neural-interface/public/shared/ui-tutorial.js +24 -18
- package/neural-interface/public/shared/ui-whiteboard.js +79 -31
- package/neural-interface/public/shared/vterm-buffer.js +9 -8
- package/neural-interface/public/variant/2d/main.js +9 -7
- package/neural-interface/public/variant/3d/main.js +10 -8
- package/neural-interface/server.js +1292 -255
- package/package.json +1 -1
- package/skills/leonardo/SKILL.md +5 -1
- package/skills/leonardo/modules/image-prompter.md +5 -1
- package/skills/leonardo/modules/video-prompter.md +5 -1
- package/skills/synabun/SKILL.md +44 -26
- package/skills/synabun/modules/audit.md +40 -36
- package/skills/synabun/modules/changelog.md +22 -18
- package/skills/synabun/modules/memorize.md +19 -15
- package/skills/synabun/modules/schedule.md +285 -0
- package/CLAUDE.md +0 -217
|
@@ -1318,26 +1318,42 @@ function buildTerminalTab(cliConfig) {
|
|
|
1318
1318
|
function buildMcpTab() {
|
|
1319
1319
|
return `
|
|
1320
1320
|
<div class="settings-tab-body" data-tab="mcp">
|
|
1321
|
-
<div class="stg-
|
|
1322
|
-
<
|
|
1323
|
-
<
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1321
|
+
<div class="stg-mcp-page-tabs" role="tablist">
|
|
1322
|
+
<button type="button" class="stg-mcp-page-tab active" data-pane="profiles">Profiles</button>
|
|
1323
|
+
<button type="button" class="stg-mcp-page-tab" data-pane="installed">Installed</button>
|
|
1324
|
+
</div>
|
|
1325
|
+
<div class="stg-mcp-pane" data-pane="profiles">
|
|
1326
|
+
<div class="stg-section">
|
|
1327
|
+
<div class="gfx-group-title">MCP Profiles</div>
|
|
1328
|
+
<div class="stg-mcp-intro">
|
|
1329
|
+
<div class="settings-hint">Toggle tool groups per profile. Click a profile name to activate it.</div>
|
|
1330
|
+
<div class="stg-mcp-note">
|
|
1331
|
+
<strong>Tool-enabled modes only.</strong>
|
|
1332
|
+
<span>Profiles reduce token usage and response times by loading only the tools each workflow needs.</span>
|
|
1333
|
+
</div>
|
|
1328
1334
|
</div>
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
<div class="stg-mcp-matrix-empty">Loading…</div>
|
|
1332
|
-
</div>
|
|
1333
|
-
<div id="stg-mcp-always-on" class="stg-mcp-subsection"></div>
|
|
1334
|
-
<div class="stg-mcp-subsection stg-mcp-servers-section">
|
|
1335
|
-
<div class="stg-mcp-section-head">
|
|
1336
|
-
<div class="stg-mcp-section-header">External Servers</div>
|
|
1337
|
-
<div class="settings-hint">Assign custom MCP servers per profile without expanding the tool-group matrix.</div>
|
|
1335
|
+
<div id="stg-mcp-matrix-wrap" class="stg-mcp-matrix-wrap stg-mcp-block">
|
|
1336
|
+
<div class="stg-mcp-matrix-empty">Loading…</div>
|
|
1338
1337
|
</div>
|
|
1339
|
-
<div id="stg-mcp-
|
|
1340
|
-
<
|
|
1338
|
+
<div id="stg-mcp-always-on" class="stg-mcp-subsection"></div>
|
|
1339
|
+
<div class="stg-mcp-subsection stg-mcp-servers-section">
|
|
1340
|
+
<div class="stg-mcp-section-head">
|
|
1341
|
+
<div class="stg-mcp-section-header">External Servers</div>
|
|
1342
|
+
<div class="settings-hint">Assign custom MCP servers per profile without expanding the tool-group matrix.</div>
|
|
1343
|
+
</div>
|
|
1344
|
+
<div id="stg-mcp-ext-servers" class="stg-mcp-servers-list"></div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
1348
|
+
<div class="stg-mcp-pane" data-pane="installed" style="display:none">
|
|
1349
|
+
<div class="stg-section">
|
|
1350
|
+
<div class="gfx-group-title">Installed MCPs</div>
|
|
1351
|
+
<div class="settings-hint">Every third-party MCP server registered with Claude Code, OpenCode, Codex, Gemini, or SynaBun — plus installed Claude Code plugins. SynaBun's built-in tool groups are not shown.</div>
|
|
1352
|
+
<div id="stg-mcp-installed-all" class="stg-mcp-servers-list"></div>
|
|
1353
|
+
<div class="stg-mcp-section-header" style="margin-top:16px">Claude Code Plugins</div>
|
|
1354
|
+
<div class="settings-hint">Plugins installed from GitHub repos that ship a .claude-plugin/ manifest. Symlinked into ~/.claude/plugins/.</div>
|
|
1355
|
+
<div id="stg-mcp-ext-plugins" class="stg-mcp-servers-list"></div>
|
|
1356
|
+
<button class="stg-action-btn compact stg-mcp-add-server-btn" id="stg-mcp-add-server">+ Install from GitHub</button>
|
|
1341
1357
|
<div class="stg-mcp-add-form" id="stg-mcp-add-form" style="display:none" data-transport="stdio" data-source="paste">
|
|
1342
1358
|
<div class="stg-mcp-source-tabs" id="stg-mcp-source-tabs" role="tablist">
|
|
1343
1359
|
<button type="button" class="stg-mcp-source-tab active" data-source="github">From GitHub</button>
|
|
@@ -2871,14 +2887,59 @@ async function refreshIconsTab(panel) {
|
|
|
2871
2887
|
} catch {}
|
|
2872
2888
|
}
|
|
2873
2889
|
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2890
|
+
function activateSettingsTab(overlay, tabId = 'server') {
|
|
2891
|
+
const nav = overlay.querySelector(`.settings-nav-item[data-tab="${tabId}"]`);
|
|
2892
|
+
const body = overlay.querySelector(`.settings-tab-body[data-tab="${tabId}"]`);
|
|
2893
|
+
if (!nav || !body) return false;
|
|
2894
|
+
|
|
2895
|
+
overlay.querySelectorAll('.settings-nav-item').forEach(n => n.classList.remove('active'));
|
|
2896
|
+
overlay.querySelectorAll('.settings-tab-body').forEach(b => b.classList.remove('active'));
|
|
2897
|
+
nav.classList.add('active');
|
|
2898
|
+
body.classList.add('active');
|
|
2899
|
+
return true;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
function applySettingsOpenOptions(overlay, options = {}) {
|
|
2903
|
+
if (!overlay || !options) return;
|
|
2904
|
+
|
|
2905
|
+
if (options.tab) activateSettingsTab(overlay, options.tab);
|
|
2906
|
+
|
|
2907
|
+
const expand = Array.isArray(options.expand) ? options.expand : (options.expand ? [options.expand] : []);
|
|
2908
|
+
for (const id of expand) {
|
|
2909
|
+
const section = overlay.querySelector(`#${id}`);
|
|
2910
|
+
if (section) section.classList.remove('collapsed');
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
overlay.querySelectorAll('.settings-focus-target').forEach(el => {
|
|
2914
|
+
el.classList.remove('settings-focus-target');
|
|
2915
|
+
});
|
|
2916
|
+
|
|
2917
|
+
const highlight = Array.isArray(options.highlight) ? options.highlight : (options.highlight ? [options.highlight] : []);
|
|
2918
|
+
for (const id of highlight) {
|
|
2919
|
+
const section = overlay.querySelector(`#${id}`);
|
|
2920
|
+
if (!section) continue;
|
|
2921
|
+
section.classList.add('settings-focus-target');
|
|
2922
|
+
setTimeout(() => section.classList.remove('settings-focus-target'), 2600);
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
const scrollTarget = options.scrollTo ? overlay.querySelector(`#${options.scrollTo}`) : null;
|
|
2926
|
+
if (scrollTarget) {
|
|
2927
|
+
setTimeout(() => scrollTarget.scrollIntoView({ block: 'center', behavior: 'smooth' }), 80);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// ═══════════════════════════════════════════
|
|
2932
|
+
// MAIN ENTRY — openSettingsModal
|
|
2933
|
+
// ═══════════════════════════════════════════
|
|
2934
|
+
|
|
2935
|
+
export async function openSettingsModal(options = {}) {
|
|
2936
|
+
// If already open, just bring it to front
|
|
2937
|
+
const existing = document.getElementById('settings-panel');
|
|
2938
|
+
if (existing) {
|
|
2939
|
+
existing.style.zIndex = '300001';
|
|
2940
|
+
applySettingsOpenOptions(existing, options);
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2882
2943
|
|
|
2883
2944
|
// ── Fetch all data in parallel ──
|
|
2884
2945
|
let settings = {};
|
|
@@ -3032,17 +3093,16 @@ export async function openSettingsModal() {
|
|
|
3032
3093
|
overlay.remove();
|
|
3033
3094
|
};
|
|
3034
3095
|
|
|
3035
|
-
// ── Nav switching ──
|
|
3036
|
-
overlay.querySelectorAll('.settings-nav-item').forEach(nav => {
|
|
3037
|
-
nav.addEventListener('click', () => {
|
|
3038
|
-
overlay
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
// ── Interface customization suite ──
|
|
3096
|
+
// ── Nav switching ──
|
|
3097
|
+
overlay.querySelectorAll('.settings-nav-item').forEach(nav => {
|
|
3098
|
+
nav.addEventListener('click', () => {
|
|
3099
|
+
activateSettingsTab(overlay, nav.dataset.tab);
|
|
3100
|
+
});
|
|
3101
|
+
});
|
|
3102
|
+
|
|
3103
|
+
applySettingsOpenOptions(overlay, options);
|
|
3104
|
+
|
|
3105
|
+
// ── Interface customization suite ──
|
|
3046
3106
|
{
|
|
3047
3107
|
let ifaceCfg = loadIfaceConfig();
|
|
3048
3108
|
|
|
@@ -5013,12 +5073,13 @@ export async function openSettingsModal() {
|
|
|
5013
5073
|
if (data.ok && data.toml) {
|
|
5014
5074
|
cachedConfig = data.toml;
|
|
5015
5075
|
preview.textContent = cachedConfig;
|
|
5016
|
-
} else if (data.ok) {
|
|
5017
|
-
const mp = setupStatus.paths?.mcpIndexPath || '<path-to>/mcp-server/run.mjs';
|
|
5018
|
-
const ep = setupStatus.paths?.envPath || '<path-to>/synabun/.env';
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5076
|
+
} else if (data.ok) {
|
|
5077
|
+
const mp = setupStatus.paths?.mcpIndexPath || '<path-to>/mcp-server/run.mjs';
|
|
5078
|
+
const ep = setupStatus.paths?.envPath || '<path-to>/synabun/.env';
|
|
5079
|
+
const dataHome = setupStatus.paths?.dataHome || '<path-to>/synabun';
|
|
5080
|
+
cachedConfig = `[mcp_servers.SynaBun]\ncommand = "node"\nargs = ["${mp}"]\nenv = { DOTENV_PATH = "${ep}", SYNABUN_DATA_HOME = "${dataHome}", MEMORY_DATA_DIR = "${dataHome}/mcp-data", SYNABUN_PROFILE = "codex-browser", SYNABUN_BROWSER_FAST = "1", SYNABUN_BROWSER_COMPACT = "1" }`;
|
|
5081
|
+
preview.textContent = cachedConfig;
|
|
5082
|
+
} else { preview.textContent = 'Could not load config.'; }
|
|
5022
5083
|
}).catch(() => { preview.textContent = 'Failed to load.'; });
|
|
5023
5084
|
}
|
|
5024
5085
|
wireCopyBtn('setup-codex-config-copy', () => cachedConfig, 'Copy TOML Config');
|
|
@@ -6008,11 +6069,13 @@ export async function openSettingsModal() {
|
|
|
6008
6069
|
{ id: 'claude-opus-4-6', label: 'Opus 4.6', desc: 'Most capable', tier: 'top' },
|
|
6009
6070
|
{ id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', desc: 'Balanced', tier: 'default' },
|
|
6010
6071
|
{ id: 'claude-haiku-4-5', label: 'Haiku 4.5', desc: 'Fastest', tier: 'light' },
|
|
6011
|
-
],
|
|
6012
|
-
'codex': [
|
|
6013
|
-
{ id: '
|
|
6014
|
-
{ id: '
|
|
6015
|
-
|
|
6072
|
+
],
|
|
6073
|
+
'codex': [
|
|
6074
|
+
{ id: 'gpt-5.5', label: 'GPT-5.5', desc: 'Most capable', tier: 'top' },
|
|
6075
|
+
{ id: 'gpt-5.4', label: 'GPT-5.4', desc: 'Strong general' },
|
|
6076
|
+
{ id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini', desc: 'Fast & cheap', tier: 'default' },
|
|
6077
|
+
{ id: 'gpt-5.3-codex', label: 'GPT-5.3 Codex', desc: 'Codex-tuned' },
|
|
6078
|
+
],
|
|
6016
6079
|
'gemini': [
|
|
6017
6080
|
{ id: 'gemini-2.5-pro', label: '2.5 Pro', desc: 'Most capable', tier: 'default' },
|
|
6018
6081
|
{ id: 'gemini-2.5-flash', label: '2.5 Flash', desc: 'Lightweight', tier: 'light' },
|
|
@@ -7377,11 +7440,139 @@ export async function openSettingsModal() {
|
|
|
7377
7440
|
});
|
|
7378
7441
|
}
|
|
7379
7442
|
|
|
7443
|
+
// ── Top-level page tabs (Profiles / Installed) ──
|
|
7444
|
+
const mcpPageTabs = overlay.querySelectorAll('.stg-mcp-page-tab');
|
|
7445
|
+
const mcpPanes = overlay.querySelectorAll('.stg-mcp-pane');
|
|
7446
|
+
mcpPageTabs.forEach(tabBtn => {
|
|
7447
|
+
tabBtn.addEventListener('click', () => {
|
|
7448
|
+
const targetPane = tabBtn.dataset.pane;
|
|
7449
|
+
mcpPageTabs.forEach(t => t.classList.toggle('active', t === tabBtn));
|
|
7450
|
+
mcpPanes.forEach(p => { p.style.display = (p.dataset.pane === targetPane) ? '' : 'none'; });
|
|
7451
|
+
if (targetPane === 'installed') renderInstalledAll();
|
|
7452
|
+
});
|
|
7453
|
+
});
|
|
7454
|
+
|
|
7455
|
+
const CLI_BADGES = {
|
|
7456
|
+
synabun: { label: 'SB', title: 'SynaBun registry' },
|
|
7457
|
+
claudeCode: { label: 'CC', title: 'Claude Code (~/.claude.json)' },
|
|
7458
|
+
opencode: { label: 'OC', title: 'OpenCode' },
|
|
7459
|
+
codex: { label: 'CX', title: 'Codex (~/.codex/config.toml)' },
|
|
7460
|
+
gemini: { label: 'GE', title: 'Gemini (~/.gemini/settings.json)' },
|
|
7461
|
+
};
|
|
7462
|
+
|
|
7463
|
+
async function renderInstalledAll() {
|
|
7464
|
+
const el = overlay.querySelector('#stg-mcp-installed-all');
|
|
7465
|
+
if (!el) return;
|
|
7466
|
+
el.innerHTML = '<div class="stg-mcp-empty-note">Loading…</div>';
|
|
7467
|
+
let servers = [];
|
|
7468
|
+
try {
|
|
7469
|
+
const r = await fetch('/api/mcp/installed-all').then(r => r.json());
|
|
7470
|
+
if (r?.ok) servers = r.servers || [];
|
|
7471
|
+
} catch {}
|
|
7472
|
+
if (!servers.length) {
|
|
7473
|
+
el.innerHTML = '<div class="stg-mcp-empty-note">No external MCP servers installed</div>';
|
|
7474
|
+
return;
|
|
7475
|
+
}
|
|
7476
|
+
el.innerHTML = servers.map(s => {
|
|
7477
|
+
const badges = (s.registeredWith || []).map(k => {
|
|
7478
|
+
const b = CLI_BADGES[k];
|
|
7479
|
+
if (!b) return '';
|
|
7480
|
+
return `<span class="stg-mx-ext-cli-badge" title="${esc(b.title)}">${esc(b.label)}</span>`;
|
|
7481
|
+
}).join('');
|
|
7482
|
+
const cmd = s.command ? `${esc(s.command)}${Array.isArray(s.args) && s.args.length ? ' ' + esc(s.args.join(' ')) : ''}` : (s.url ? esc(s.url) : '');
|
|
7483
|
+
const regAttr = esc((s.registeredWith || []).join(','));
|
|
7484
|
+
return `<div class="stg-mx-ext-row">
|
|
7485
|
+
<div class="stg-mx-ext-main">
|
|
7486
|
+
<span class="stg-mx-ext-dot"></span>
|
|
7487
|
+
<div class="stg-mx-ext-copy">
|
|
7488
|
+
<div class="stg-mx-ext-head">
|
|
7489
|
+
<span class="stg-mx-ext-name">${esc(s.name)}</span>
|
|
7490
|
+
<span class="stg-mx-ext-type">${esc(s.transport || '?')}</span>
|
|
7491
|
+
<span class="stg-mx-ext-clis">${badges}</span>
|
|
7492
|
+
</div>
|
|
7493
|
+
<div class="stg-mx-ext-sub">${cmd || '—'}</div>
|
|
7494
|
+
</div>
|
|
7495
|
+
<button class="stg-mx-srv-del stg-mx-installed-del" data-server="${esc(s.name)}" data-registered="${regAttr}" title="Uninstall from all CLIs">×</button>
|
|
7496
|
+
</div>
|
|
7497
|
+
</div>`;
|
|
7498
|
+
}).join('');
|
|
7499
|
+
|
|
7500
|
+
el.querySelectorAll('.stg-mx-installed-del').forEach(btn => {
|
|
7501
|
+
btn.addEventListener('click', async (e) => {
|
|
7502
|
+
e.stopPropagation();
|
|
7503
|
+
const name = btn.dataset.server;
|
|
7504
|
+
const registered = (btn.dataset.registered || '').split(',').filter(Boolean);
|
|
7505
|
+
if (!name) return;
|
|
7506
|
+
const cliList = registered.filter(k => k !== 'synabun');
|
|
7507
|
+
const summary = registered.map(k => CLI_BADGES[k]?.label || k).join(', ');
|
|
7508
|
+
if (!confirm(`Remove "${name}" from ${summary}? This unregisters it from each CLI's config.`)) return;
|
|
7509
|
+
btn.disabled = true;
|
|
7510
|
+
try {
|
|
7511
|
+
if (registered.includes('synabun')) {
|
|
7512
|
+
await fetch(`/api/mcp/registry/servers/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
|
7513
|
+
}
|
|
7514
|
+
if (cliList.length) {
|
|
7515
|
+
await fetch('/api/mcp/sync', {
|
|
7516
|
+
method: 'DELETE',
|
|
7517
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7518
|
+
body: JSON.stringify({ name, platforms: cliList }),
|
|
7519
|
+
});
|
|
7520
|
+
}
|
|
7521
|
+
} catch {}
|
|
7522
|
+
await refreshMcpTab();
|
|
7523
|
+
await renderInstalledAll();
|
|
7524
|
+
});
|
|
7525
|
+
});
|
|
7526
|
+
}
|
|
7527
|
+
|
|
7528
|
+
async function renderPluginsList() {
|
|
7529
|
+
const el = overlay.querySelector('#stg-mcp-ext-plugins');
|
|
7530
|
+
if (!el) return;
|
|
7531
|
+
let plugins = [];
|
|
7532
|
+
try {
|
|
7533
|
+
const r = await fetch('/api/plugins/list').then(r => r.json());
|
|
7534
|
+
if (r?.ok) plugins = r.plugins || [];
|
|
7535
|
+
} catch {}
|
|
7536
|
+
if (!plugins.length) {
|
|
7537
|
+
el.innerHTML = '<div class="stg-mcp-empty-note">No plugins installed</div>';
|
|
7538
|
+
return;
|
|
7539
|
+
}
|
|
7540
|
+
el.innerHTML = plugins.map(p => {
|
|
7541
|
+
const brokenBadge = p.broken ? '<span class="stg-mx-ext-type" style="color:var(--t-err,#f55)">broken</span>' : '';
|
|
7542
|
+
const sha = p.gitCommitSha && p.gitCommitSha !== 'unknown' ? p.gitCommitSha.slice(0, 7) : '';
|
|
7543
|
+
return `<div class="stg-mx-ext-row">
|
|
7544
|
+
<div class="stg-mx-ext-main">
|
|
7545
|
+
<span class="stg-mx-ext-dot"></span>
|
|
7546
|
+
<div class="stg-mx-ext-copy">
|
|
7547
|
+
<div class="stg-mx-ext-head">
|
|
7548
|
+
<span class="stg-mx-ext-name">${esc(p.pluginName)}</span>
|
|
7549
|
+
<span class="stg-mx-ext-type">claude-plugin</span>
|
|
7550
|
+
${brokenBadge}
|
|
7551
|
+
</div>
|
|
7552
|
+
<div class="stg-mx-ext-sub">${esc(p.marketplaceName)}${sha ? ' · ' + sha : ''} · ${esc(p.repoPath || '')}</div>
|
|
7553
|
+
</div>
|
|
7554
|
+
<button class="stg-mx-srv-del stg-mx-ext-plugin-del" data-marketplace="${esc(p.marketplaceName)}" data-plugin="${esc(p.pluginName)}" title="Uninstall plugin">×</button>
|
|
7555
|
+
</div>
|
|
7556
|
+
</div>`;
|
|
7557
|
+
}).join('');
|
|
7558
|
+
el.querySelectorAll('.stg-mx-ext-plugin-del').forEach(btn => {
|
|
7559
|
+
btn.addEventListener('click', async (e) => {
|
|
7560
|
+
e.stopPropagation();
|
|
7561
|
+
const mk = btn.dataset.marketplace;
|
|
7562
|
+
const pn = btn.dataset.plugin;
|
|
7563
|
+
if (!mk) return;
|
|
7564
|
+
await fetch(`/api/plugins/${encodeURIComponent(mk)}?pluginName=${encodeURIComponent(pn || '')}&purgeFiles=1`, { method: 'DELETE' });
|
|
7565
|
+
await refreshMcpTab();
|
|
7566
|
+
});
|
|
7567
|
+
});
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7380
7570
|
async function refreshMcpTab() {
|
|
7381
7571
|
await fetchMcpRegistry();
|
|
7382
7572
|
renderMcpMatrix();
|
|
7383
7573
|
renderAlwaysOn();
|
|
7384
7574
|
renderExtServerList();
|
|
7575
|
+
renderPluginsList();
|
|
7385
7576
|
}
|
|
7386
7577
|
|
|
7387
7578
|
// Initial load
|
|
@@ -7587,6 +7778,9 @@ export async function openSettingsModal() {
|
|
|
7587
7778
|
});
|
|
7588
7779
|
overlay.querySelector('#stg-mcp-srv-cancel')?.addEventListener('click', () => {
|
|
7589
7780
|
if (addForm) { addForm.style.display = 'none'; resetAddForm(); }
|
|
7781
|
+
setFormMode('mcp');
|
|
7782
|
+
if (ghProgress) ghProgress.style.display = 'none';
|
|
7783
|
+
if (ghStatus) ghStatus.textContent = '';
|
|
7590
7784
|
});
|
|
7591
7785
|
|
|
7592
7786
|
// ── Source-tab switcher (GitHub / JSON) ──
|
|
@@ -7643,9 +7837,24 @@ export async function openSettingsModal() {
|
|
|
7643
7837
|
if (addForm) addForm.dataset.transport = detected.type || 'stdio';
|
|
7644
7838
|
}
|
|
7645
7839
|
|
|
7840
|
+
// Toggle form between 'mcp' and 'plugin' modes.
|
|
7841
|
+
// In plugin mode we hide MCP-specific fields (Type/Command/Args/URL/env/platforms/Save)
|
|
7842
|
+
// and show a single Done button because the plugin is already activated server-side.
|
|
7843
|
+
function setFormMode(mode) {
|
|
7844
|
+
if (addForm) addForm.dataset.kind = mode;
|
|
7845
|
+
const isPlugin = mode === 'plugin';
|
|
7846
|
+
const mcpOnly = addForm?.querySelectorAll('.stg-mcp-form-grid, #stg-mcp-env-rows, #stg-mcp-env-add, #stg-mcp-platforms') || [];
|
|
7847
|
+
mcpOnly.forEach(el => { el.style.display = isPlugin ? 'none' : ''; });
|
|
7848
|
+
const envLabels = addForm?.querySelectorAll('.stg-mcp-form-label') || [];
|
|
7849
|
+
envLabels.forEach(el => { el.style.display = isPlugin ? 'none' : ''; });
|
|
7850
|
+
const saveBtn = overlay.querySelector('#stg-mcp-srv-save');
|
|
7851
|
+
if (saveBtn) saveBtn.style.display = isPlugin ? 'none' : '';
|
|
7852
|
+
}
|
|
7853
|
+
|
|
7646
7854
|
ghInstallBtn?.addEventListener('click', async () => {
|
|
7647
7855
|
const url = ghUrlEl?.value?.trim();
|
|
7648
7856
|
if (!url) return;
|
|
7857
|
+
setFormMode('mcp'); // reset so form is in a clean state before classification
|
|
7649
7858
|
if (ghProgress) ghProgress.style.display = '';
|
|
7650
7859
|
if (ghStatus) ghStatus.textContent = 'Cloning repository…';
|
|
7651
7860
|
if (ghLog) ghLog.textContent = '';
|
|
@@ -7657,10 +7866,25 @@ export async function openSettingsModal() {
|
|
|
7657
7866
|
}).then(r => r.json());
|
|
7658
7867
|
if (ghLog && Array.isArray(res.log)) ghLog.textContent = res.log.join('\n');
|
|
7659
7868
|
if (!res.ok) {
|
|
7660
|
-
|
|
7869
|
+
const hintStr = Array.isArray(res.hints) && res.hints.length ? ` (hints: ${res.hints.join(', ')})` : '';
|
|
7870
|
+
if (ghStatus) ghStatus.textContent = `${res.error || 'install failed'}${hintStr}`;
|
|
7871
|
+
return;
|
|
7872
|
+
}
|
|
7873
|
+
if (res.kind === 'claude-plugin') {
|
|
7874
|
+
setFormMode('plugin');
|
|
7875
|
+
const p = res.plugin || {};
|
|
7876
|
+
const hooks = (p.hooks || []).join(', ') || 'none';
|
|
7877
|
+
if (ghStatus) {
|
|
7878
|
+
ghStatus.innerHTML = `Installed Claude Code plugin <b>${esc(p.pluginName || res.name)}</b> @ <b>${esc(p.marketplaceName || '')}</b>. Hooks: ${esc(hooks)}. Symlinked to <code>${esc(p.installPath || '')}</code>.`;
|
|
7879
|
+
}
|
|
7880
|
+
await refreshMcpTab();
|
|
7661
7881
|
return;
|
|
7662
7882
|
}
|
|
7663
|
-
|
|
7883
|
+
// MCP (existing behavior)
|
|
7884
|
+
setFormMode('mcp');
|
|
7885
|
+
const sourceHint = res.alsoClaudePlugin ? ' (repo also ships a Claude Code plugin — install separately if desired)' : '';
|
|
7886
|
+
const warnLine = res.installWarning ? `\n⚠ ${res.installWarning}` : (res.install?.skipped && res.install?.reason ? `\n⚙ Skipped local deps — ${res.install.reason}` : '');
|
|
7887
|
+
if (ghStatus) ghStatus.textContent = `Detected via ${res.detected?.source || res.detectSource || 'heuristic'} — review below and click Save.${sourceHint}${warnLine}`;
|
|
7664
7888
|
applyDetected(res.name, res.detected);
|
|
7665
7889
|
} catch (err) {
|
|
7666
7890
|
if (ghStatus) ghStatus.textContent = `Error: ${err.message || err}`;
|
|
@@ -3,7 +3,7 @@ const RESERVED_GAP_VAR = '--right-panel-gap';
|
|
|
3
3
|
const LEGACY_WIDTH_VAR = '--claude-panel-width';
|
|
4
4
|
const LEGACY_GAP_VAR = '--claude-panel-gap';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const _reservations = new Map();
|
|
7
7
|
|
|
8
8
|
function px(value) {
|
|
9
9
|
return `${Math.max(0, Math.round(Number(value) || 0))}px`;
|
|
@@ -20,6 +20,14 @@ function applyReservation(reservedWidthPx, gapPx) {
|
|
|
20
20
|
setVarPair(RESERVED_GAP_VAR, LEGACY_GAP_VAR, px(gapPx));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function applyActiveReservation() {
|
|
24
|
+
let active = null;
|
|
25
|
+
for (const entry of _reservations.values()) {
|
|
26
|
+
if (!active || entry.reservedWidth > active.reservedWidth) active = entry;
|
|
27
|
+
}
|
|
28
|
+
applyReservation(active?.reservedWidth || 0, active?.gap || 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
export function measureRightPanelReservation(panel, outerGap = 20) {
|
|
24
32
|
if (!panel) return 0;
|
|
25
33
|
return Math.ceil(panel.getBoundingClientRect().width) + outerGap;
|
|
@@ -30,14 +38,17 @@ export function reserveRightPanelLayout(owner, panelOrReservedWidth, outerGap =
|
|
|
30
38
|
const reservedWidth = typeof panelOrReservedWidth === 'number'
|
|
31
39
|
? panelOrReservedWidth
|
|
32
40
|
: measureRightPanelReservation(panelOrReservedWidth, outerGap);
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
_reservations.set(owner, {
|
|
42
|
+
reservedWidth,
|
|
43
|
+
gap: Math.max(0, Number(outerGap) || 0),
|
|
44
|
+
});
|
|
45
|
+
applyActiveReservation();
|
|
35
46
|
return reservedWidth;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
export function clearRightPanelLayout(owner) {
|
|
39
|
-
if (owner
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
50
|
+
if (owner) _reservations.delete(owner);
|
|
51
|
+
else _reservations.clear();
|
|
52
|
+
applyActiveReservation();
|
|
53
|
+
return _reservations.size === 0;
|
|
43
54
|
}
|