synabun 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/CHANGELOG.md +665 -0
- package/LICENSE +191 -0
- package/README.md +853 -0
- package/hooks/claude-code/post-plan.mjs +391 -0
- package/hooks/claude-code/post-remember.mjs +239 -0
- package/hooks/claude-code/pre-compact.mjs +241 -0
- package/hooks/claude-code/pre-websearch.mjs +90 -0
- package/hooks/claude-code/prompt-submit.mjs +861 -0
- package/hooks/claude-code/session-start.mjs +494 -0
- package/hooks/claude-code/shared.mjs +368 -0
- package/hooks/claude-code/stop.mjs +646 -0
- package/hooks/claude-code/test-user-learning.mjs +531 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/run.mjs +10 -0
- package/mcp-server/src/config.ts +70 -0
- package/mcp-server/src/http.ts +93 -0
- package/mcp-server/src/index.ts +203 -0
- package/mcp-server/src/preload-http.ts +25 -0
- package/mcp-server/src/preload.ts +29 -0
- package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
- package/mcp-server/src/services/categories.ts +429 -0
- package/mcp-server/src/services/discord.ts +702 -0
- package/mcp-server/src/services/file-checksums.ts +33 -0
- package/mcp-server/src/services/local-embeddings.ts +77 -0
- package/mcp-server/src/services/neural-interface.ts +344 -0
- package/mcp-server/src/services/sqlite.ts +823 -0
- package/mcp-server/src/tools/browser-advanced.ts +123 -0
- package/mcp-server/src/tools/browser-interact.ts +255 -0
- package/mcp-server/src/tools/browser-navigate.ts +77 -0
- package/mcp-server/src/tools/browser-observe.ts +1330 -0
- package/mcp-server/src/tools/browser.ts +107 -0
- package/mcp-server/src/tools/card-tools.ts +193 -0
- package/mcp-server/src/tools/card.ts +22 -0
- package/mcp-server/src/tools/category.ts +379 -0
- package/mcp-server/src/tools/discord-channel.ts +274 -0
- package/mcp-server/src/tools/discord-guild.ts +188 -0
- package/mcp-server/src/tools/discord-member.ts +158 -0
- package/mcp-server/src/tools/discord-message.ts +231 -0
- package/mcp-server/src/tools/discord-onboarding.ts +228 -0
- package/mcp-server/src/tools/discord-role.ts +209 -0
- package/mcp-server/src/tools/discord-thread.ts +110 -0
- package/mcp-server/src/tools/discord-webhook.ts +176 -0
- package/mcp-server/src/tools/discord.ts +40 -0
- package/mcp-server/src/tools/forget.ts +38 -0
- package/mcp-server/src/tools/git-tools.ts +141 -0
- package/mcp-server/src/tools/git.ts +13 -0
- package/mcp-server/src/tools/image-tools.ts +98 -0
- package/mcp-server/src/tools/image.ts +17 -0
- package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
- package/mcp-server/src/tools/leonardo.ts +28 -0
- package/mcp-server/src/tools/loop.ts +348 -0
- package/mcp-server/src/tools/memories.ts +118 -0
- package/mcp-server/src/tools/recall.ts +323 -0
- package/mcp-server/src/tools/reflect.ts +145 -0
- package/mcp-server/src/tools/remember.ts +115 -0
- package/mcp-server/src/tools/response.ts +26 -0
- package/mcp-server/src/tools/restore.ts +34 -0
- package/mcp-server/src/tools/sync.ts +114 -0
- package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
- package/mcp-server/src/tools/tictactoe.ts +13 -0
- package/mcp-server/src/tools/utils.ts +25 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
- package/mcp-server/src/tools/whiteboard.ts +22 -0
- package/mcp-server/src/tui.ts +940 -0
- package/mcp-server/src/types.ts +85 -0
- package/mcp-server/tsconfig.json +19 -0
- package/neural-interface/package.json +27 -0
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/claude.svg +1 -0
- package/neural-interface/public/category-logos/gemini-color.svg +1 -0
- package/neural-interface/public/category-logos/openai.svg +1 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude-chat.css +878 -0
- package/neural-interface/public/claude-chat.html +92 -0
- package/neural-interface/public/claude-chat.js +1168 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/gallery.svg +6 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index-legacy.html +14677 -0
- package/neural-interface/public/index.html +212 -0
- package/neural-interface/public/index2d-legacy.html +8193 -0
- package/neural-interface/public/index2d.html +127 -0
- package/neural-interface/public/invite.html +351 -0
- package/neural-interface/public/leonardoai.svg +1 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +14 -0
- package/neural-interface/public/offline.html +299 -0
- package/neural-interface/public/onboarding.html +2335 -0
- package/neural-interface/public/openclaw-logo-text.png +0 -0
- package/neural-interface/public/pin-svgrepo-com.svg +25 -0
- package/neural-interface/public/sessionicon.svg +8 -0
- package/neural-interface/public/shared/ansi-parser.js +305 -0
- package/neural-interface/public/shared/api.js +1043 -0
- package/neural-interface/public/shared/colors.js +153 -0
- package/neural-interface/public/shared/constants.js +141 -0
- package/neural-interface/public/shared/html-shell.js +777 -0
- package/neural-interface/public/shared/html-term-renderer.js +1022 -0
- package/neural-interface/public/shared/i18n.js +147 -0
- package/neural-interface/public/shared/registry.js +82 -0
- package/neural-interface/public/shared/state.js +86 -0
- package/neural-interface/public/shared/storage.js +150 -0
- package/neural-interface/public/shared/styles.css +21212 -0
- package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
- package/neural-interface/public/shared/ui-bookmarks.js +231 -0
- package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
- package/neural-interface/public/shared/ui-command-runner.js +534 -0
- package/neural-interface/public/shared/ui-cost-widget.js +602 -0
- package/neural-interface/public/shared/ui-detail.js +1454 -0
- package/neural-interface/public/shared/ui-explorer.js +1356 -0
- package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
- package/neural-interface/public/shared/ui-games.js +411 -0
- package/neural-interface/public/shared/ui-help.js +107 -0
- package/neural-interface/public/shared/ui-image-gallery.js +461 -0
- package/neural-interface/public/shared/ui-invite.js +519 -0
- package/neural-interface/public/shared/ui-keybinds.js +452 -0
- package/neural-interface/public/shared/ui-layouts.js +297 -0
- package/neural-interface/public/shared/ui-link.js +731 -0
- package/neural-interface/public/shared/ui-loading.js +255 -0
- package/neural-interface/public/shared/ui-menubar.js +499 -0
- package/neural-interface/public/shared/ui-multiselect.js +93 -0
- package/neural-interface/public/shared/ui-navbar.js +222 -0
- package/neural-interface/public/shared/ui-notifications.js +287 -0
- package/neural-interface/public/shared/ui-panels.js +309 -0
- package/neural-interface/public/shared/ui-resume.js +653 -0
- package/neural-interface/public/shared/ui-search.js +145 -0
- package/neural-interface/public/shared/ui-sessions.js +531 -0
- package/neural-interface/public/shared/ui-settings.js +5987 -0
- package/neural-interface/public/shared/ui-sidebar.js +1905 -0
- package/neural-interface/public/shared/ui-skills.js +2805 -0
- package/neural-interface/public/shared/ui-stats.js +38 -0
- package/neural-interface/public/shared/ui-sync.js +201 -0
- package/neural-interface/public/shared/ui-terminal.js +6385 -0
- package/neural-interface/public/shared/ui-tooltip.js +123 -0
- package/neural-interface/public/shared/ui-trash.js +439 -0
- package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
- package/neural-interface/public/shared/ui-tutorial.js +1117 -0
- package/neural-interface/public/shared/ui-update.js +244 -0
- package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
- package/neural-interface/public/shared/ui-workspaces.js +722 -0
- package/neural-interface/public/shared/utils.js +360 -0
- package/neural-interface/public/shared/vterm-buffer.js +1102 -0
- package/neural-interface/public/sw.js +52 -0
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/toolbar-album.svg +8 -0
- package/neural-interface/public/toolbar-question.svg +6 -0
- package/neural-interface/public/toolbar-sessions.svg +6 -0
- package/neural-interface/public/variant/2d/background.js +113 -0
- package/neural-interface/public/variant/2d/context-menu.js +133 -0
- package/neural-interface/public/variant/2d/gfx.js +45 -0
- package/neural-interface/public/variant/2d/graph.js +1635 -0
- package/neural-interface/public/variant/2d/hulls.js +207 -0
- package/neural-interface/public/variant/2d/lasso.js +135 -0
- package/neural-interface/public/variant/2d/layout.js +594 -0
- package/neural-interface/public/variant/2d/main.js +521 -0
- package/neural-interface/public/variant/2d/minimap.js +312 -0
- package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
- package/neural-interface/public/variant/3d/background.js +884 -0
- package/neural-interface/public/variant/3d/camera.js +661 -0
- package/neural-interface/public/variant/3d/gfx.js +116 -0
- package/neural-interface/public/variant/3d/graph.js +2399 -0
- package/neural-interface/public/variant/3d/main.js +500 -0
- package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
- package/neural-interface/scripts/rebuild-pty.js +100 -0
- package/neural-interface/server.js +15000 -0
- package/neural-interface/templates/CLAUDE-template.md +211 -0
- package/package.json +53 -0
- package/postinstall.js +135 -0
- package/setup.js +260 -0
- package/skills/leonardo/SKILL.md +159 -0
- package/skills/leonardo/icon.svg +1 -0
- package/skills/leonardo/modules/browser-guide.md +378 -0
- package/skills/leonardo/modules/image-prompter.md +277 -0
- package/skills/leonardo/modules/model-advisor.md +100 -0
- package/skills/leonardo/modules/prompt-library.md +110 -0
- package/skills/leonardo/modules/style-guide.md +186 -0
- package/skills/leonardo/modules/video-prompter.md +311 -0
- package/skills/synabun/SKILL.md +185 -0
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +374 -0
- package/skills/synabun/modules/changelog.md +194 -0
- package/skills/synabun/modules/idea.md +144 -0
- package/skills/synabun/modules/memorize.md +188 -0
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — OS-Style Menubar
|
|
3
|
+
// Handles open/close, mutual exclusivity, hover-to-switch,
|
|
4
|
+
// keyboard nav, and wiring for View / Graph menus.
|
|
5
|
+
// ═══════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import { state, emit, on } from './state.js';
|
|
8
|
+
import { storage } from './storage.js';
|
|
9
|
+
import { KEYS } from './constants.js';
|
|
10
|
+
import { getMenuItems } from './registry.js';
|
|
11
|
+
import { openHelp } from './ui-help.js';
|
|
12
|
+
import { registerAction } from './ui-keybinds.js';
|
|
13
|
+
import { isGuest, hasPermission } from './ui-sync.js';
|
|
14
|
+
import { sendToPanel } from './ui-claude-panel.js';
|
|
15
|
+
|
|
16
|
+
const $ = (id) => document.getElementById(id);
|
|
17
|
+
|
|
18
|
+
let openMenu = null; // currently open data-menu value, or null
|
|
19
|
+
let hoverMode = false; // after first click, hovering over labels opens them
|
|
20
|
+
|
|
21
|
+
// ── Public API ──
|
|
22
|
+
|
|
23
|
+
export function initMenubar() {
|
|
24
|
+
const menubar = document.querySelector('.menubar');
|
|
25
|
+
if (!menubar) return;
|
|
26
|
+
|
|
27
|
+
// ── Keep --navbar-height in sync with actual title bar height ──
|
|
28
|
+
const titleBar = document.getElementById('title-bar');
|
|
29
|
+
if (titleBar) {
|
|
30
|
+
const updateNavbarHeight = () => {
|
|
31
|
+
const h = titleBar.offsetHeight;
|
|
32
|
+
if (h > 0) document.documentElement.style.setProperty('--navbar-height', h + 'px');
|
|
33
|
+
};
|
|
34
|
+
updateNavbarHeight();
|
|
35
|
+
new ResizeObserver(updateNavbarHeight).observe(titleBar);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const items = menubar.querySelectorAll('.menubar-item');
|
|
39
|
+
|
|
40
|
+
// Click to open / close
|
|
41
|
+
items.forEach(item => {
|
|
42
|
+
const label = item.querySelector('.menubar-label');
|
|
43
|
+
const menuId = item.dataset.menu;
|
|
44
|
+
|
|
45
|
+
label.addEventListener('click', (e) => {
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
if (openMenu === menuId) {
|
|
48
|
+
closeAll();
|
|
49
|
+
} else {
|
|
50
|
+
openMenuById(menuId);
|
|
51
|
+
hoverMode = true;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Hover-to-switch when a menu is already open
|
|
56
|
+
label.addEventListener('mouseenter', () => {
|
|
57
|
+
if (hoverMode && openMenu && openMenu !== menuId) {
|
|
58
|
+
openMenuById(menuId);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Click outside closes menu
|
|
64
|
+
document.addEventListener('click', (e) => {
|
|
65
|
+
if (openMenu && !e.target.closest('.menubar-item')) {
|
|
66
|
+
closeAll();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Escape closes menu
|
|
71
|
+
document.addEventListener('keydown', (e) => {
|
|
72
|
+
if (e.key === 'Escape' && openMenu) {
|
|
73
|
+
closeAll();
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Keyboard navigation within open menu
|
|
79
|
+
document.addEventListener('keydown', (e) => {
|
|
80
|
+
if (!openMenu) return;
|
|
81
|
+
const dropdown = document.querySelector('.menubar-item.open .menubar-dropdown');
|
|
82
|
+
if (!dropdown) return;
|
|
83
|
+
|
|
84
|
+
const allItems = [...dropdown.querySelectorAll('.menu-item:not(.disabled)')];
|
|
85
|
+
if (allItems.length === 0) return;
|
|
86
|
+
|
|
87
|
+
const focused = dropdown.querySelector('.menu-item.focused');
|
|
88
|
+
let idx = allItems.indexOf(focused);
|
|
89
|
+
|
|
90
|
+
if (e.key === 'ArrowDown') {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
if (focused) focused.classList.remove('focused');
|
|
93
|
+
idx = (idx + 1) % allItems.length;
|
|
94
|
+
allItems[idx].classList.add('focused');
|
|
95
|
+
} else if (e.key === 'ArrowUp') {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
if (focused) focused.classList.remove('focused');
|
|
98
|
+
idx = (idx - 1 + allItems.length) % allItems.length;
|
|
99
|
+
allItems[idx].classList.add('focused');
|
|
100
|
+
} else if (e.key === 'Enter' && focused) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
focused.click();
|
|
103
|
+
} else if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
|
|
104
|
+
const menuItems = [...document.querySelectorAll('.menubar-item')];
|
|
105
|
+
const currentIdx = menuItems.findIndex(m => m.dataset.menu === openMenu);
|
|
106
|
+
const dir = e.key === 'ArrowRight' ? 1 : -1;
|
|
107
|
+
const nextIdx = (currentIdx + dir + menuItems.length) % menuItems.length;
|
|
108
|
+
openMenuById(menuItems[nextIdx].dataset.menu);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Integrate with existing panel close system
|
|
113
|
+
on('panel:close-all-dropdowns', () => closeAll());
|
|
114
|
+
|
|
115
|
+
// Wire up menu item handlers
|
|
116
|
+
wireViewMenu();
|
|
117
|
+
wireGraphMenu();
|
|
118
|
+
wireSkillsMenu();
|
|
119
|
+
wireAutomationsMenu();
|
|
120
|
+
wireTerminalMenu();
|
|
121
|
+
injectVariantMenuItems();
|
|
122
|
+
populateLinkTypes();
|
|
123
|
+
|
|
124
|
+
// ── Guest permission visual feedback on menu items ──
|
|
125
|
+
const MENU_PERM_MAP = {
|
|
126
|
+
'menu-open-skills-studio': 'skills',
|
|
127
|
+
'menu-skills-new': 'skills',
|
|
128
|
+
'menu-skills-import': 'skills',
|
|
129
|
+
'menu-open-automation-studio': 'automations',
|
|
130
|
+
'menu-automations-new': 'automations',
|
|
131
|
+
'menu-automations-import': 'automations',
|
|
132
|
+
'menu-terminal-claude': 'terminal',
|
|
133
|
+
'menu-terminal-codex': 'terminal',
|
|
134
|
+
'menu-terminal-gemini': 'terminal',
|
|
135
|
+
'menu-terminal-shell': 'terminal',
|
|
136
|
+
'menu-terminal-browser': 'browser',
|
|
137
|
+
'menu-terminal-youtube': 'browser',
|
|
138
|
+
'menu-game-tictactoe': 'whiteboard',
|
|
139
|
+
};
|
|
140
|
+
function updateMenuPermVisuals() {
|
|
141
|
+
const guest = isGuest();
|
|
142
|
+
Object.entries(MENU_PERM_MAP).forEach(([id, perm]) => {
|
|
143
|
+
const el = $(id);
|
|
144
|
+
if (!el) return;
|
|
145
|
+
const blocked = guest && !hasPermission(perm);
|
|
146
|
+
el.style.opacity = blocked ? '0.35' : '';
|
|
147
|
+
el.style.pointerEvents = blocked ? 'none' : '';
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
on('session:info', updateMenuPermVisuals);
|
|
151
|
+
on('permissions:changed', updateMenuPermVisuals);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function closeMenubar() {
|
|
155
|
+
closeAll();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Internal ──
|
|
159
|
+
|
|
160
|
+
function openMenuById(menuId) {
|
|
161
|
+
const menubar = document.querySelector('.menubar');
|
|
162
|
+
if (!menubar) return;
|
|
163
|
+
|
|
164
|
+
// Close all first
|
|
165
|
+
menubar.querySelectorAll('.menubar-item').forEach(item => {
|
|
166
|
+
item.classList.remove('open');
|
|
167
|
+
// Clear keyboard focus
|
|
168
|
+
const focused = item.querySelector('.menu-item.focused');
|
|
169
|
+
if (focused) focused.classList.remove('focused');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const target = menubar.querySelector(`.menubar-item[data-menu="${menuId}"]`);
|
|
173
|
+
if (target) {
|
|
174
|
+
target.classList.add('open');
|
|
175
|
+
openMenu = menuId;
|
|
176
|
+
|
|
177
|
+
// Trigger content rendering for data-driven panels
|
|
178
|
+
if (menuId === 'bookmarks') emit('bookmarks:render');
|
|
179
|
+
if (menuId === 'layouts') emit('layouts:render');
|
|
180
|
+
if (menuId === 'resume') emit('resume:render');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function closeAll() {
|
|
185
|
+
const menubar = document.querySelector('.menubar');
|
|
186
|
+
if (!menubar) return;
|
|
187
|
+
menubar.querySelectorAll('.menubar-item').forEach(item => {
|
|
188
|
+
item.classList.remove('open');
|
|
189
|
+
const focused = item.querySelector('.menu-item.focused');
|
|
190
|
+
if (focused) focused.classList.remove('focused');
|
|
191
|
+
});
|
|
192
|
+
openMenu = null;
|
|
193
|
+
hoverMode = false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── View Menu ──
|
|
197
|
+
|
|
198
|
+
function wireViewMenu() {
|
|
199
|
+
// Categories toggle
|
|
200
|
+
const catItem = $('menu-toggle-categories');
|
|
201
|
+
if (catItem) {
|
|
202
|
+
// Sync initial state — sidebar starts visible
|
|
203
|
+
catItem.classList.add('active');
|
|
204
|
+
|
|
205
|
+
catItem.addEventListener('click', () => {
|
|
206
|
+
const sidebar = $('category-sidebar');
|
|
207
|
+
if (!sidebar) return;
|
|
208
|
+
const visible = sidebar.style.display !== 'none';
|
|
209
|
+
sidebar.style.display = visible ? 'none' : '';
|
|
210
|
+
catItem.classList.toggle('active', !visible);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Sync when sidebar is closed via its own close button
|
|
215
|
+
on('sidebar:closed', () => {
|
|
216
|
+
const catItem = $('menu-toggle-categories');
|
|
217
|
+
if (catItem) catItem.classList.remove('active');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Keyboard shortcut: toggle categories (via central keybinds)
|
|
221
|
+
registerAction('toggle-categories', () => { if (catItem) catItem.click(); });
|
|
222
|
+
|
|
223
|
+
// Help
|
|
224
|
+
const helpItem = $('menu-help');
|
|
225
|
+
if (helpItem) {
|
|
226
|
+
helpItem.addEventListener('click', () => {
|
|
227
|
+
closeAll();
|
|
228
|
+
openHelp();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── Graph Menu ──
|
|
234
|
+
|
|
235
|
+
function wireGraphMenu() {
|
|
236
|
+
// Link mode radio group
|
|
237
|
+
const linkRadios = document.querySelectorAll('.menu-radio[data-group="link-mode"]');
|
|
238
|
+
linkRadios.forEach(item => {
|
|
239
|
+
item.addEventListener('click', () => {
|
|
240
|
+
const value = item.dataset.value;
|
|
241
|
+
state.linkMode = value;
|
|
242
|
+
// Update all radios in the group
|
|
243
|
+
linkRadios.forEach(r => r.classList.toggle('active', r.dataset.value === value));
|
|
244
|
+
emit('link-mode-changed', value);
|
|
245
|
+
emit('graph:refresh');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Node limit radio group
|
|
250
|
+
const limitRadios = document.querySelectorAll('.menu-radio[data-group="node-limit"]');
|
|
251
|
+
if (limitRadios.length > 0) {
|
|
252
|
+
// Restore saved selection on load
|
|
253
|
+
const saved = storage.getItem(KEYS.NODE_LIMIT) || '0';
|
|
254
|
+
limitRadios.forEach(r => r.classList.toggle('active', r.dataset.value === saved));
|
|
255
|
+
|
|
256
|
+
limitRadios.forEach(item => {
|
|
257
|
+
item.addEventListener('click', () => {
|
|
258
|
+
const value = parseInt(item.dataset.value, 10) || 0;
|
|
259
|
+
limitRadios.forEach(r => r.classList.toggle('active', r.dataset.value === item.dataset.value));
|
|
260
|
+
emit('node-limit-changed', value);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Reset Layout
|
|
266
|
+
const resetItem = $('menu-reset-layout');
|
|
267
|
+
if (resetItem) {
|
|
268
|
+
resetItem.addEventListener('click', () => {
|
|
269
|
+
closeAll();
|
|
270
|
+
emit('layout:reset');
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Skills Menu ──
|
|
276
|
+
|
|
277
|
+
function wireSkillsMenu() {
|
|
278
|
+
const openItem = $('menu-open-skills-studio');
|
|
279
|
+
if (openItem) {
|
|
280
|
+
openItem.addEventListener('click', () => {
|
|
281
|
+
closeAll();
|
|
282
|
+
emit('skills:open');
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const newItem = $('menu-skills-new');
|
|
286
|
+
if (newItem) {
|
|
287
|
+
newItem.addEventListener('click', () => {
|
|
288
|
+
closeAll();
|
|
289
|
+
emit('skills:open-wizard');
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
const importItem = $('menu-skills-import');
|
|
293
|
+
if (importItem) {
|
|
294
|
+
importItem.addEventListener('click', () => {
|
|
295
|
+
closeAll();
|
|
296
|
+
emit('skills:import');
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Keyboard shortcut: open Skills Studio (via central keybinds)
|
|
301
|
+
registerAction('open-skills', () => emit('skills:open'));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── Automations Menu ──
|
|
305
|
+
|
|
306
|
+
function wireAutomationsMenu() {
|
|
307
|
+
const openItem = $('menu-open-automation-studio');
|
|
308
|
+
if (openItem) {
|
|
309
|
+
openItem.addEventListener('click', () => {
|
|
310
|
+
closeAll();
|
|
311
|
+
emit('automations:open');
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const schedulesItem = $('menu-automations-schedules');
|
|
315
|
+
if (schedulesItem) {
|
|
316
|
+
schedulesItem.addEventListener('click', () => {
|
|
317
|
+
closeAll();
|
|
318
|
+
emit('automations:open-schedules');
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const newItem = $('menu-automations-new');
|
|
322
|
+
if (newItem) {
|
|
323
|
+
newItem.addEventListener('click', () => {
|
|
324
|
+
closeAll();
|
|
325
|
+
emit('automations:open-wizard');
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const importItem = $('menu-automations-import');
|
|
329
|
+
if (importItem) {
|
|
330
|
+
importItem.addEventListener('click', () => {
|
|
331
|
+
closeAll();
|
|
332
|
+
emit('automations:import');
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
registerAction('open-automations', () => emit('automations:open'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── Apps Menu (formerly Terminal) ──
|
|
340
|
+
|
|
341
|
+
function wireTerminalMenu() {
|
|
342
|
+
const items = {
|
|
343
|
+
'menu-terminal-claude': () => emit('terminal:open', { profile: 'claude-code' }),
|
|
344
|
+
'menu-terminal-codex': () => emit('terminal:open', { profile: 'codex' }),
|
|
345
|
+
'menu-terminal-gemini': () => emit('terminal:open', { profile: 'gemini' }),
|
|
346
|
+
'menu-terminal-shell': () => emit('terminal:open', { profile: 'shell' }),
|
|
347
|
+
'menu-terminal-browser': () => emit('browser:open'),
|
|
348
|
+
'menu-terminal-youtube': () => emit('browser:open', { url: 'https://www.youtube.com' }),
|
|
349
|
+
'menu-terminal-discord': () => emit('browser:open', { url: 'https://discord.com/app' }),
|
|
350
|
+
'menu-terminal-x': () => emit('browser:open', { url: 'https://x.com' }),
|
|
351
|
+
'menu-terminal-whatsapp': () => emit('browser:open', { url: 'https://web.whatsapp.com' }),
|
|
352
|
+
'menu-app-leonardo': () => { emit('browser:open', { url: 'https://app.leonardo.ai' }); emit('leonardo:launch'); },
|
|
353
|
+
'menu-command-runner': () => emit('command-runner:open'),
|
|
354
|
+
'menu-terminal-link': () => emit('link:toggle'),
|
|
355
|
+
'menu-terminal-toggle': () => emit('terminal:toggle'),
|
|
356
|
+
'menu-install-app': async () => {
|
|
357
|
+
if (window._pwaInstallPrompt) {
|
|
358
|
+
window._pwaInstallPrompt.prompt();
|
|
359
|
+
const { outcome } = await window._pwaInstallPrompt.userChoice;
|
|
360
|
+
if (outcome === 'accepted') {
|
|
361
|
+
window._pwaInstallPrompt = null;
|
|
362
|
+
const el = $('menu-install-app');
|
|
363
|
+
if (el) el.style.display = 'none';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
'menu-restart-server': async () => {
|
|
368
|
+
try {
|
|
369
|
+
// Cache project path before server goes down
|
|
370
|
+
try {
|
|
371
|
+
const h = await fetch('/api/health');
|
|
372
|
+
const hd = await h.json();
|
|
373
|
+
if (hd.projectDir) localStorage.setItem('synabun-project-dir', hd.projectDir);
|
|
374
|
+
} catch {}
|
|
375
|
+
await fetch('/api/server/restart', { method: 'POST' });
|
|
376
|
+
// Show brief message then attempt reconnect after delay
|
|
377
|
+
document.title = 'Restarting...';
|
|
378
|
+
setTimeout(() => location.reload(), 2000);
|
|
379
|
+
} catch {}
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
Object.entries(items).forEach(([id, handler]) => {
|
|
383
|
+
const el = $(id);
|
|
384
|
+
if (el) el.addEventListener('click', () => { closeAll(); handler(); });
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Leonardo.AI — open panel with /leonardo skill after browser launches
|
|
388
|
+
on('leonardo:launch', () => {
|
|
389
|
+
setTimeout(() => sendToPanel('/leonardo', { newTab: true, tabLabel: 'Leonardo.AI', autoSubmit: true }), 500);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Keyboard shortcuts: toggle terminal (via central keybinds)
|
|
393
|
+
registerAction('toggle-terminal', () => emit('terminal:toggle'));
|
|
394
|
+
registerAction('toggle-terminal-alt', () => emit('terminal:toggle'));
|
|
395
|
+
|
|
396
|
+
// PWA install prompt — show "Install as App" when the browser supports it
|
|
397
|
+
window.addEventListener('beforeinstallprompt', (e) => {
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
window._pwaInstallPrompt = e;
|
|
400
|
+
const el = $('menu-install-app');
|
|
401
|
+
if (el) el.style.display = '';
|
|
402
|
+
});
|
|
403
|
+
window.addEventListener('appinstalled', () => {
|
|
404
|
+
window._pwaInstallPrompt = null;
|
|
405
|
+
const el = $('menu-install-app');
|
|
406
|
+
if (el) el.style.display = 'none';
|
|
407
|
+
});
|
|
408
|
+
// Hide if already running as installed PWA
|
|
409
|
+
if (window.matchMedia('(display-mode: standalone)').matches || navigator.standalone) {
|
|
410
|
+
const el = $('menu-install-app');
|
|
411
|
+
if (el) el.style.display = 'none';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ── Variant menu items injection ──
|
|
416
|
+
|
|
417
|
+
function injectVariantMenuItems() {
|
|
418
|
+
const slot = $('menu-variant-slot');
|
|
419
|
+
const sep = $('menu-variant-sep');
|
|
420
|
+
const items = getMenuItems();
|
|
421
|
+
|
|
422
|
+
if (!slot || items.length === 0) {
|
|
423
|
+
if (sep) sep.style.display = 'none';
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
items.forEach(spec => {
|
|
428
|
+
const el = document.createElement('div');
|
|
429
|
+
el.className = `menu-item${spec.type === 'toggle' ? ' menu-toggle' : ''}`;
|
|
430
|
+
if (spec.id) el.id = spec.id;
|
|
431
|
+
el.innerHTML = `
|
|
432
|
+
<span class="menu-check">✓</span>
|
|
433
|
+
<span class="menu-text">${spec.label}</span>
|
|
434
|
+
${spec.shortcut ? `<span class="menu-shortcut">${spec.shortcut}</span>` : ''}
|
|
435
|
+
`;
|
|
436
|
+
slot.appendChild(el);
|
|
437
|
+
if (spec.init) spec.init(el);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
if (sep) sep.style.display = '';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ── Dynamic link type population ──
|
|
444
|
+
|
|
445
|
+
function populateLinkTypes() {
|
|
446
|
+
on('data-loaded', () => {
|
|
447
|
+
const slot = $('menu-link-types-slot');
|
|
448
|
+
if (!slot) return;
|
|
449
|
+
slot.innerHTML = '';
|
|
450
|
+
|
|
451
|
+
// Gather unique link types (links may have .type or .types array)
|
|
452
|
+
const types = new Set();
|
|
453
|
+
state.allLinks.forEach(l => {
|
|
454
|
+
if (l.types && Array.isArray(l.types)) {
|
|
455
|
+
l.types.forEach(t => types.add(t));
|
|
456
|
+
} else if (l.type) {
|
|
457
|
+
types.add(l.type);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// "All Types" is already in the static HTML
|
|
462
|
+
// Add discovered types below it
|
|
463
|
+
const sortedTypes = [...types].sort();
|
|
464
|
+
sortedTypes.forEach(type => {
|
|
465
|
+
const item = document.createElement('div');
|
|
466
|
+
item.className = 'menu-item menu-radio';
|
|
467
|
+
item.dataset.group = 'link-type';
|
|
468
|
+
item.dataset.value = type;
|
|
469
|
+
item.innerHTML = `
|
|
470
|
+
<span class="menu-check">✓</span>
|
|
471
|
+
<span class="menu-text">${type}</span>
|
|
472
|
+
`;
|
|
473
|
+
item.addEventListener('click', () => {
|
|
474
|
+
state.linkTypeFilter = type;
|
|
475
|
+
syncLinkTypeRadios();
|
|
476
|
+
emit('link-type-changed', type);
|
|
477
|
+
emit('graph:refresh');
|
|
478
|
+
});
|
|
479
|
+
slot.appendChild(item);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Wire the static "All Types" radio too
|
|
483
|
+
const allTypesItem = document.querySelector('.menu-radio[data-group="link-type"][data-value="all"]');
|
|
484
|
+
if (allTypesItem) {
|
|
485
|
+
allTypesItem.addEventListener('click', () => {
|
|
486
|
+
state.linkTypeFilter = 'all';
|
|
487
|
+
syncLinkTypeRadios();
|
|
488
|
+
emit('link-type-changed', 'all');
|
|
489
|
+
emit('graph:refresh');
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function syncLinkTypeRadios() {
|
|
496
|
+
document.querySelectorAll('.menu-radio[data-group="link-type"]').forEach(r => {
|
|
497
|
+
r.classList.toggle('active', r.dataset.value === state.linkTypeFilter);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Multi-Select Bar
|
|
3
|
+
// Shows bulk actions when multiple nodes are selected
|
|
4
|
+
// ═══════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { state, emit, on } from './state.js';
|
|
7
|
+
|
|
8
|
+
const $ = (id) => document.getElementById(id);
|
|
9
|
+
|
|
10
|
+
export function updateMultiSelectBar() {
|
|
11
|
+
const bar = $('multi-select-bar');
|
|
12
|
+
const count = $('multi-select-count');
|
|
13
|
+
if (!bar) return;
|
|
14
|
+
|
|
15
|
+
if (state.multiSelected.size > 0) {
|
|
16
|
+
bar.classList.add('open');
|
|
17
|
+
if (count) count.textContent = `${state.multiSelected.size} selected`;
|
|
18
|
+
} else {
|
|
19
|
+
bar.classList.remove('open');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function clearMultiSelect() {
|
|
24
|
+
state.multiSelected.clear();
|
|
25
|
+
updateMultiSelectBar();
|
|
26
|
+
emit('multiselect:cleared');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function initMultiSelect() {
|
|
30
|
+
const exportBtn = $('multi-select-export');
|
|
31
|
+
const trashBtn = $('multi-select-trash');
|
|
32
|
+
const clearBtn = $('multi-select-clear');
|
|
33
|
+
const moveCatBtn = $('multi-select-move-cat');
|
|
34
|
+
|
|
35
|
+
if (exportBtn) {
|
|
36
|
+
exportBtn.addEventListener('click', () => {
|
|
37
|
+
// Export all selected memories as markdown
|
|
38
|
+
const nodes = state.allNodes.filter(n => state.multiSelected.has(n.id));
|
|
39
|
+
if (!nodes.length) return;
|
|
40
|
+
|
|
41
|
+
let md = `# Exported Memories (${nodes.length})\n\n`;
|
|
42
|
+
nodes.forEach(n => {
|
|
43
|
+
const p = n.payload || n;
|
|
44
|
+
md += `## ${p.category || 'uncategorized'}\n`;
|
|
45
|
+
md += `**Importance:** ${p.importance || 5}\n`;
|
|
46
|
+
if (p.tags?.length) md += `**Tags:** ${p.tags.join(', ')}\n`;
|
|
47
|
+
md += `\n${p.content || ''}\n\n---\n\n`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const blob = new Blob([md], { type: 'text/markdown' });
|
|
51
|
+
const url = URL.createObjectURL(blob);
|
|
52
|
+
const a = document.createElement('a');
|
|
53
|
+
a.href = url;
|
|
54
|
+
a.download = `memories-export-${Date.now()}.md`;
|
|
55
|
+
a.click();
|
|
56
|
+
URL.revokeObjectURL(url);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (trashBtn) {
|
|
61
|
+
trashBtn.addEventListener('click', async () => {
|
|
62
|
+
const ids = [...state.multiSelected];
|
|
63
|
+
if (!ids.length) return;
|
|
64
|
+
if (!confirm(`Move ${ids.length} memories to trash?`)) return;
|
|
65
|
+
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
try {
|
|
68
|
+
await fetch(`/api/memory/${id}`, { method: 'DELETE' });
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
clearMultiSelect();
|
|
72
|
+
emit('graph:reload');
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (clearBtn) {
|
|
77
|
+
clearBtn.addEventListener('click', clearMultiSelect);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (moveCatBtn) {
|
|
81
|
+
moveCatBtn.addEventListener('click', () => {
|
|
82
|
+
emit('multiselect:move-category', [...state.multiSelected]);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
on('multiselect:update', updateMultiSelectBar);
|
|
87
|
+
|
|
88
|
+
// Focus mode — clear selection when entering focus
|
|
89
|
+
on('focus:enter', () => {
|
|
90
|
+
state.multiSelected.clear();
|
|
91
|
+
updateMultiSelectBar();
|
|
92
|
+
});
|
|
93
|
+
}
|