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,534 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Command Runner
|
|
3
|
+
// Save and one-click execute terminal commands,
|
|
4
|
+
// organized by category groups.
|
|
5
|
+
// ═══════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import { emit, on } from './state.js';
|
|
8
|
+
import { KEYS, COLOR_PALETTE } from './constants.js';
|
|
9
|
+
import { storage } from './storage.js';
|
|
10
|
+
|
|
11
|
+
const $ = (id) => document.getElementById(id);
|
|
12
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
13
|
+
const genId = (prefix) => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
14
|
+
|
|
15
|
+
// ── Module-local state ──
|
|
16
|
+
let _panel = null;
|
|
17
|
+
let _data = { categories: [], commands: [] };
|
|
18
|
+
let _editTarget = null; // { type: 'command'|'category', id: string|null } — null = creating new
|
|
19
|
+
|
|
20
|
+
const PANEL_KEY = KEYS.PANEL_PREFIX + 'command-runner';
|
|
21
|
+
const DATA_KEY = KEYS.COMMAND_RUNNER;
|
|
22
|
+
|
|
23
|
+
// ── SVGs ──
|
|
24
|
+
const SVG_PLAY = `<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><polygon points="4,2 14,8 4,14"/></svg>`;
|
|
25
|
+
const SVG_EDIT = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2.5l2 2L5 13l-3 1 1-3z"/></svg>`;
|
|
26
|
+
const SVG_DELETE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="4" x2="13" y2="4"/><path d="M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1"/><path d="M4.5 4l.5 9a1 1 0 001 1h4a1 1 0 001-1l.5-9"/></svg>`;
|
|
27
|
+
const SVG_CHEVRON = `<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 4 10 8 6 12"/></svg>`;
|
|
28
|
+
const SVG_CLOSE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>`;
|
|
29
|
+
const SVG_TERMINAL = `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`;
|
|
30
|
+
|
|
31
|
+
// ═══════════════════════════════════════════
|
|
32
|
+
// INIT
|
|
33
|
+
// ═══════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
export function initCommandRunner() {
|
|
36
|
+
on('command-runner:open', () => openPanel());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ═══════════════════════════════════════════
|
|
40
|
+
// PANEL LIFECYCLE
|
|
41
|
+
// ═══════════════════════════════════════════
|
|
42
|
+
|
|
43
|
+
function openPanel() {
|
|
44
|
+
if (_panel) { _panel.focus(); return; }
|
|
45
|
+
|
|
46
|
+
_panel = document.createElement('div');
|
|
47
|
+
_panel.className = 'command-runner-panel glass resizable';
|
|
48
|
+
_panel.id = 'command-runner-panel';
|
|
49
|
+
_panel.innerHTML = buildPanelHTML();
|
|
50
|
+
document.body.appendChild(_panel);
|
|
51
|
+
|
|
52
|
+
// Restore saved position
|
|
53
|
+
try {
|
|
54
|
+
const saved = JSON.parse(storage.getItem(PANEL_KEY));
|
|
55
|
+
if (saved) {
|
|
56
|
+
if (saved.x != null) _panel.style.left = saved.x + 'px';
|
|
57
|
+
if (saved.y != null) _panel.style.top = Math.max(48, saved.y) + 'px';
|
|
58
|
+
if (saved.w) _panel.style.width = saved.w + 'px';
|
|
59
|
+
if (saved.h) _panel.style.height = saved.h + 'px';
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
|
|
63
|
+
// Center if no saved position
|
|
64
|
+
if (!_panel.style.left) {
|
|
65
|
+
const vw = window.innerWidth, vh = window.innerHeight;
|
|
66
|
+
_panel.style.left = Math.max(20, (vw - 420) / 2) + 'px';
|
|
67
|
+
_panel.style.top = Math.max(48, (vh - 500) / 2) + 'px';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
loadData();
|
|
71
|
+
wirePanel();
|
|
72
|
+
renderBody();
|
|
73
|
+
|
|
74
|
+
requestAnimationFrame(() => _panel.classList.add('open'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function closePanel() {
|
|
78
|
+
if (!_panel) return;
|
|
79
|
+
// Save position
|
|
80
|
+
try {
|
|
81
|
+
const rect = _panel.getBoundingClientRect();
|
|
82
|
+
storage.setItem(PANEL_KEY, JSON.stringify({
|
|
83
|
+
x: Math.round(rect.left), y: Math.round(rect.top),
|
|
84
|
+
w: Math.round(rect.width), h: Math.round(rect.height),
|
|
85
|
+
}));
|
|
86
|
+
} catch {}
|
|
87
|
+
|
|
88
|
+
_panel.remove();
|
|
89
|
+
_panel = null;
|
|
90
|
+
_editTarget = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ═══════════════════════════════════════════
|
|
94
|
+
// DATA
|
|
95
|
+
// ═══════════════════════════════════════════
|
|
96
|
+
|
|
97
|
+
function loadData() {
|
|
98
|
+
try {
|
|
99
|
+
const raw = storage.getItem(DATA_KEY);
|
|
100
|
+
if (raw) {
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
_data = {
|
|
103
|
+
categories: Array.isArray(parsed.categories) ? parsed.categories : [],
|
|
104
|
+
commands: Array.isArray(parsed.commands) ? parsed.commands : [],
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
_data = { categories: [], commands: [] };
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
_data = { categories: [], commands: [] };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function saveData() {
|
|
115
|
+
storage.setItem(DATA_KEY, JSON.stringify(_data));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getCategoryCommands(catId) {
|
|
119
|
+
return _data.commands
|
|
120
|
+
.filter(c => c.categoryId === catId)
|
|
121
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ═══════════════════════════════════════════
|
|
125
|
+
// HTML BUILDERS
|
|
126
|
+
// ═══════════════════════════════════════════
|
|
127
|
+
|
|
128
|
+
function buildPanelHTML() {
|
|
129
|
+
return `
|
|
130
|
+
<div class="resize-handle resize-handle-t" data-resize="t"></div>
|
|
131
|
+
<div class="resize-handle resize-handle-r" data-resize="r"></div>
|
|
132
|
+
<div class="resize-handle resize-handle-b" data-resize="b"></div>
|
|
133
|
+
<div class="resize-handle resize-handle-l" data-resize="l"></div>
|
|
134
|
+
<div class="resize-handle resize-handle-tl" data-resize="tl"></div>
|
|
135
|
+
<div class="resize-handle resize-handle-tr" data-resize="tr"></div>
|
|
136
|
+
<div class="resize-handle resize-handle-bl" data-resize="bl"></div>
|
|
137
|
+
<div class="resize-handle resize-handle-br" data-resize="br"></div>
|
|
138
|
+
|
|
139
|
+
<div class="cr-header drag-handle" data-drag="command-runner-panel">
|
|
140
|
+
<div class="cr-header-left">
|
|
141
|
+
<span class="cr-header-icon">${SVG_TERMINAL}</span>
|
|
142
|
+
<h3>Command Runner</h3>
|
|
143
|
+
<span class="cr-count" id="cr-total-count"></span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="cr-header-actions">
|
|
146
|
+
<button class="cr-header-btn" id="cr-add-cat-btn">+ Group</button>
|
|
147
|
+
<button class="cr-header-btn cr-primary" id="cr-add-cmd-btn">+ Command</button>
|
|
148
|
+
</div>
|
|
149
|
+
<button class="cr-close" id="cr-close-btn">${SVG_CLOSE}</button>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div class="cr-body" id="cr-body"></div>
|
|
153
|
+
|
|
154
|
+
<div class="cr-modal-overlay hidden" id="cr-modal-overlay">
|
|
155
|
+
<div class="cr-modal glass" id="cr-modal"></div>
|
|
156
|
+
</div>
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderBody() {
|
|
161
|
+
const body = $('cr-body');
|
|
162
|
+
if (!body) return;
|
|
163
|
+
|
|
164
|
+
const countEl = $('cr-total-count');
|
|
165
|
+
if (countEl) countEl.textContent = _data.commands.length || '';
|
|
166
|
+
|
|
167
|
+
const sorted = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
168
|
+
|
|
169
|
+
if (sorted.length === 0) {
|
|
170
|
+
body.innerHTML = `
|
|
171
|
+
<div class="cr-empty">
|
|
172
|
+
<div class="cr-empty-icon">${SVG_TERMINAL}</div>
|
|
173
|
+
<p>No commands yet</p>
|
|
174
|
+
<p class="cr-empty-hint">Click <strong>+ Group</strong> to create a category,<br>then <strong>+ Command</strong> to add commands.</p>
|
|
175
|
+
</div>
|
|
176
|
+
`;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let html = '';
|
|
181
|
+
for (const cat of sorted) {
|
|
182
|
+
const cmds = getCategoryCommands(cat.id);
|
|
183
|
+
const collapsed = cat.collapsed ? ' collapsed' : '';
|
|
184
|
+
const dotColor = cat.color || COLOR_PALETTE[0];
|
|
185
|
+
|
|
186
|
+
html += `<div class="cr-category${collapsed}" data-cat-id="${esc(cat.id)}">`;
|
|
187
|
+
html += ` <div class="cr-cat-header">`;
|
|
188
|
+
html += ` <span class="cr-cat-chevron">${SVG_CHEVRON}</span>`;
|
|
189
|
+
html += ` <span class="cr-cat-dot" style="background:${esc(dotColor)}"></span>`;
|
|
190
|
+
html += ` <span class="cr-cat-name">${esc(cat.name)}</span>`;
|
|
191
|
+
html += ` <span class="cr-cat-count">${cmds.length}</span>`;
|
|
192
|
+
html += ` <span class="cr-cat-actions">`;
|
|
193
|
+
html += ` <button class="cr-icon-btn" data-action="edit-cat" title="Edit group">${SVG_EDIT}</button>`;
|
|
194
|
+
html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cat" title="Delete group">${SVG_DELETE}</button>`;
|
|
195
|
+
html += ` </span>`;
|
|
196
|
+
html += ` </div>`;
|
|
197
|
+
html += ` <div class="cr-cat-body">`;
|
|
198
|
+
|
|
199
|
+
if (cmds.length === 0) {
|
|
200
|
+
html += `<div class="cr-cat-empty">No commands in this group</div>`;
|
|
201
|
+
} else {
|
|
202
|
+
for (const cmd of cmds) {
|
|
203
|
+
html += `<div class="cr-cmd" data-cmd-id="${esc(cmd.id)}">`;
|
|
204
|
+
html += ` <button class="cr-run-btn" title="Run command">${SVG_PLAY}</button>`;
|
|
205
|
+
html += ` <div class="cr-cmd-info">`;
|
|
206
|
+
html += ` <div class="cr-cmd-name">${esc(cmd.name)}</div>`;
|
|
207
|
+
html += ` <div class="cr-cmd-text">${esc(cmd.command)}</div>`;
|
|
208
|
+
if (cmd.cwd) {
|
|
209
|
+
html += ` <div class="cr-cmd-cwd">${esc(cmd.cwd)}</div>`;
|
|
210
|
+
}
|
|
211
|
+
html += ` </div>`;
|
|
212
|
+
html += ` <span class="cr-cmd-actions">`;
|
|
213
|
+
html += ` <button class="cr-icon-btn" data-action="edit-cmd" title="Edit command">${SVG_EDIT}</button>`;
|
|
214
|
+
html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cmd" title="Delete command">${SVG_DELETE}</button>`;
|
|
215
|
+
html += ` </span>`;
|
|
216
|
+
html += `</div>`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
html += ` </div>`;
|
|
221
|
+
html += `</div>`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
body.innerHTML = html;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ═══════════════════════════════════════════
|
|
228
|
+
// MODAL (Add/Edit forms)
|
|
229
|
+
// ═══════════════════════════════════════════
|
|
230
|
+
|
|
231
|
+
function openModal(type, id) {
|
|
232
|
+
_editTarget = { type, id: id || null };
|
|
233
|
+
const overlay = $('cr-modal-overlay');
|
|
234
|
+
const modal = $('cr-modal');
|
|
235
|
+
if (!overlay || !modal) return;
|
|
236
|
+
|
|
237
|
+
if (type === 'category') {
|
|
238
|
+
const existing = id ? _data.categories.find(c => c.id === id) : null;
|
|
239
|
+
modal.innerHTML = buildCategoryForm(existing);
|
|
240
|
+
} else {
|
|
241
|
+
const existing = id ? _data.commands.find(c => c.id === id) : null;
|
|
242
|
+
modal.innerHTML = buildCommandForm(existing);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
overlay.classList.remove('hidden');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function closeModal() {
|
|
249
|
+
const overlay = $('cr-modal-overlay');
|
|
250
|
+
if (overlay) overlay.classList.add('hidden');
|
|
251
|
+
_editTarget = null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function buildCategoryForm(existing) {
|
|
255
|
+
const name = existing ? esc(existing.name) : '';
|
|
256
|
+
const activeColor = existing?.color || COLOR_PALETTE[0];
|
|
257
|
+
|
|
258
|
+
let swatches = '';
|
|
259
|
+
for (const c of COLOR_PALETTE.slice(0, 16)) {
|
|
260
|
+
const sel = c === activeColor ? ' selected' : '';
|
|
261
|
+
swatches += `<button type="button" class="cr-color-swatch${sel}" data-color="${c}" style="background:${c}"></button>`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return `
|
|
265
|
+
<div class="cr-modal-header">
|
|
266
|
+
<h4>${existing ? 'Edit' : 'New'} Group</h4>
|
|
267
|
+
<button class="cr-icon-btn" id="cr-modal-close">${SVG_CLOSE}</button>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="cr-modal-body">
|
|
270
|
+
<label class="cr-form-label">Name</label>
|
|
271
|
+
<input class="cr-form-input" id="cr-cat-name" type="text" value="${name}" placeholder="e.g. Dev Servers" autocomplete="off" />
|
|
272
|
+
<label class="cr-form-label" style="margin-top:12px">Color</label>
|
|
273
|
+
<div class="cr-color-picker" id="cr-cat-color">${swatches}</div>
|
|
274
|
+
<input type="hidden" id="cr-cat-color-val" value="${esc(activeColor)}" />
|
|
275
|
+
</div>
|
|
276
|
+
<div class="cr-modal-footer">
|
|
277
|
+
<button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
|
|
278
|
+
<button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
|
|
279
|
+
</div>
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function buildCommandForm(existing) {
|
|
284
|
+
const name = existing ? esc(existing.name) : '';
|
|
285
|
+
const command = existing ? esc(existing.command) : '';
|
|
286
|
+
const cwd = existing?.cwd ? esc(existing.cwd) : '';
|
|
287
|
+
const catId = existing?.categoryId || '';
|
|
288
|
+
|
|
289
|
+
let catOptions = '';
|
|
290
|
+
const sorted = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
291
|
+
for (const cat of sorted) {
|
|
292
|
+
const sel = cat.id === catId ? ' selected' : '';
|
|
293
|
+
catOptions += `<option value="${esc(cat.id)}"${sel}>${esc(cat.name)}</option>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return `
|
|
297
|
+
<div class="cr-modal-header">
|
|
298
|
+
<h4>${existing ? 'Edit' : 'New'} Command</h4>
|
|
299
|
+
<button class="cr-icon-btn" id="cr-modal-close">${SVG_CLOSE}</button>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="cr-modal-body">
|
|
302
|
+
<label class="cr-form-label">Group</label>
|
|
303
|
+
<select class="cr-form-input cr-form-select" id="cr-cmd-cat">${catOptions}</select>
|
|
304
|
+
|
|
305
|
+
<label class="cr-form-label" style="margin-top:12px">Name</label>
|
|
306
|
+
<input class="cr-form-input" id="cr-cmd-name" type="text" value="${name}" placeholder="e.g. Start Dev Server" autocomplete="off" />
|
|
307
|
+
|
|
308
|
+
<label class="cr-form-label" style="margin-top:12px">Command</label>
|
|
309
|
+
<input class="cr-form-input cr-form-mono" id="cr-cmd-command" type="text" value="${command}" placeholder="e.g. npm run dev" autocomplete="off" />
|
|
310
|
+
|
|
311
|
+
<label class="cr-form-label" style="margin-top:12px">Working Directory <span class="cr-form-hint">(optional)</span></label>
|
|
312
|
+
<input class="cr-form-input cr-form-mono" id="cr-cmd-cwd" type="text" value="${cwd}" placeholder="e.g. /home/user/myproject" autocomplete="off" />
|
|
313
|
+
</div>
|
|
314
|
+
<div class="cr-modal-footer">
|
|
315
|
+
<button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
|
|
316
|
+
<button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
|
|
317
|
+
</div>
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ═══════════════════════════════════════════
|
|
322
|
+
// CRUD
|
|
323
|
+
// ═══════════════════════════════════════════
|
|
324
|
+
|
|
325
|
+
function saveFromModal() {
|
|
326
|
+
if (!_editTarget) return;
|
|
327
|
+
|
|
328
|
+
if (_editTarget.type === 'category') {
|
|
329
|
+
const nameEl = $('cr-cat-name');
|
|
330
|
+
const colorEl = $('cr-cat-color-val');
|
|
331
|
+
const name = nameEl?.value.trim();
|
|
332
|
+
if (!name) { nameEl?.focus(); return; }
|
|
333
|
+
const color = colorEl?.value || COLOR_PALETTE[0];
|
|
334
|
+
|
|
335
|
+
if (_editTarget.id) {
|
|
336
|
+
// Update existing
|
|
337
|
+
const cat = _data.categories.find(c => c.id === _editTarget.id);
|
|
338
|
+
if (cat) { cat.name = name; cat.color = color; }
|
|
339
|
+
} else {
|
|
340
|
+
// Create new
|
|
341
|
+
_data.categories.push({
|
|
342
|
+
id: genId('cat'),
|
|
343
|
+
name,
|
|
344
|
+
color,
|
|
345
|
+
collapsed: false,
|
|
346
|
+
order: _data.categories.length,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
const catEl = $('cr-cmd-cat');
|
|
351
|
+
const nameEl = $('cr-cmd-name');
|
|
352
|
+
const cmdEl = $('cr-cmd-command');
|
|
353
|
+
const cwdEl = $('cr-cmd-cwd');
|
|
354
|
+
|
|
355
|
+
const categoryId = catEl?.value;
|
|
356
|
+
const name = nameEl?.value.trim();
|
|
357
|
+
const command = cmdEl?.value.trim();
|
|
358
|
+
const cwd = cwdEl?.value.trim() || null;
|
|
359
|
+
|
|
360
|
+
if (!categoryId) { catEl?.focus(); return; }
|
|
361
|
+
if (!name) { nameEl?.focus(); return; }
|
|
362
|
+
if (!command) { cmdEl?.focus(); return; }
|
|
363
|
+
|
|
364
|
+
if (_editTarget.id) {
|
|
365
|
+
// Update existing
|
|
366
|
+
const cmd = _data.commands.find(c => c.id === _editTarget.id);
|
|
367
|
+
if (cmd) {
|
|
368
|
+
cmd.categoryId = categoryId;
|
|
369
|
+
cmd.name = name;
|
|
370
|
+
cmd.command = command;
|
|
371
|
+
cmd.cwd = cwd;
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
// Create new
|
|
375
|
+
const sibling = getCategoryCommands(categoryId);
|
|
376
|
+
_data.commands.push({
|
|
377
|
+
id: genId('cmd'),
|
|
378
|
+
categoryId,
|
|
379
|
+
name,
|
|
380
|
+
command,
|
|
381
|
+
cwd,
|
|
382
|
+
order: sibling.length,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
saveData();
|
|
388
|
+
closeModal();
|
|
389
|
+
renderBody();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function deleteCommand(id) {
|
|
393
|
+
_data.commands = _data.commands.filter(c => c.id !== id);
|
|
394
|
+
saveData();
|
|
395
|
+
renderBody();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function deleteCategory(id) {
|
|
399
|
+
const cmds = getCategoryCommands(id);
|
|
400
|
+
if (cmds.length > 0) {
|
|
401
|
+
if (!confirm(`Delete group and its ${cmds.length} command${cmds.length > 1 ? 's' : ''}?`)) return;
|
|
402
|
+
}
|
|
403
|
+
_data.commands = _data.commands.filter(c => c.categoryId !== id);
|
|
404
|
+
_data.categories = _data.categories.filter(c => c.id !== id);
|
|
405
|
+
saveData();
|
|
406
|
+
renderBody();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function toggleCategoryCollapse(catId) {
|
|
410
|
+
const cat = _data.categories.find(c => c.id === catId);
|
|
411
|
+
if (cat) {
|
|
412
|
+
cat.collapsed = !cat.collapsed;
|
|
413
|
+
saveData();
|
|
414
|
+
renderBody();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ═══════════════════════════════════════════
|
|
419
|
+
// RUN COMMAND
|
|
420
|
+
// ═══════════════════════════════════════════
|
|
421
|
+
|
|
422
|
+
function runCommand(cmdId) {
|
|
423
|
+
const cmd = _data.commands.find(c => c.id === cmdId);
|
|
424
|
+
if (!cmd) return;
|
|
425
|
+
emit('terminal:run-command', {
|
|
426
|
+
command: cmd.command,
|
|
427
|
+
cwd: cmd.cwd || null,
|
|
428
|
+
label: cmd.name,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ═══════════════════════════════════════════
|
|
433
|
+
// EVENT WIRING
|
|
434
|
+
// ═══════════════════════════════════════════
|
|
435
|
+
|
|
436
|
+
function wirePanel() {
|
|
437
|
+
if (!_panel) return;
|
|
438
|
+
|
|
439
|
+
// ── Main click delegation ──
|
|
440
|
+
_panel.addEventListener('click', (e) => {
|
|
441
|
+
// Close button
|
|
442
|
+
if (e.target.closest('#cr-close-btn')) return closePanel();
|
|
443
|
+
|
|
444
|
+
// Header add buttons
|
|
445
|
+
if (e.target.closest('#cr-add-cat-btn')) return openModal('category');
|
|
446
|
+
if (e.target.closest('#cr-add-cmd-btn')) {
|
|
447
|
+
if (_data.categories.length === 0) {
|
|
448
|
+
// Need at least one category first
|
|
449
|
+
openModal('category');
|
|
450
|
+
} else {
|
|
451
|
+
openModal('command');
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Run button
|
|
457
|
+
const runBtn = e.target.closest('.cr-run-btn');
|
|
458
|
+
if (runBtn) {
|
|
459
|
+
const cmdEl = runBtn.closest('.cr-cmd');
|
|
460
|
+
if (cmdEl) return runCommand(cmdEl.dataset.cmdId);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Category actions
|
|
464
|
+
const editCat = e.target.closest('[data-action="edit-cat"]');
|
|
465
|
+
if (editCat) {
|
|
466
|
+
const catEl = editCat.closest('.cr-category');
|
|
467
|
+
if (catEl) return openModal('category', catEl.dataset.catId);
|
|
468
|
+
}
|
|
469
|
+
const delCat = e.target.closest('[data-action="delete-cat"]');
|
|
470
|
+
if (delCat) {
|
|
471
|
+
const catEl = delCat.closest('.cr-category');
|
|
472
|
+
if (catEl) return deleteCategory(catEl.dataset.catId);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Command actions
|
|
476
|
+
const editCmd = e.target.closest('[data-action="edit-cmd"]');
|
|
477
|
+
if (editCmd) {
|
|
478
|
+
const cmdEl = editCmd.closest('.cr-cmd');
|
|
479
|
+
if (cmdEl) return openModal('command', cmdEl.dataset.cmdId);
|
|
480
|
+
}
|
|
481
|
+
const delCmd = e.target.closest('[data-action="delete-cmd"]');
|
|
482
|
+
if (delCmd) {
|
|
483
|
+
const cmdEl = delCmd.closest('.cr-cmd');
|
|
484
|
+
if (cmdEl) return deleteCommand(cmdEl.dataset.cmdId);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Category header click → toggle collapse (but not if clicking actions)
|
|
488
|
+
const catHeader = e.target.closest('.cr-cat-header');
|
|
489
|
+
if (catHeader && !e.target.closest('.cr-cat-actions')) {
|
|
490
|
+
const catEl = catHeader.closest('.cr-category');
|
|
491
|
+
if (catEl) return toggleCategoryCollapse(catEl.dataset.catId);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Modal buttons
|
|
495
|
+
if (e.target.closest('#cr-modal-close') || e.target.closest('#cr-modal-cancel')) return closeModal();
|
|
496
|
+
if (e.target.closest('#cr-modal-save')) return saveFromModal();
|
|
497
|
+
|
|
498
|
+
// Color swatch selection
|
|
499
|
+
const swatch = e.target.closest('.cr-color-swatch');
|
|
500
|
+
if (swatch) {
|
|
501
|
+
const picker = swatch.closest('.cr-color-picker');
|
|
502
|
+
if (picker) {
|
|
503
|
+
picker.querySelectorAll('.cr-color-swatch').forEach(s => s.classList.remove('selected'));
|
|
504
|
+
swatch.classList.add('selected');
|
|
505
|
+
const hidden = $('cr-cat-color-val');
|
|
506
|
+
if (hidden) hidden.value = swatch.dataset.color;
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ── Modal overlay background click → close ──
|
|
513
|
+
const overlay = $('cr-modal-overlay');
|
|
514
|
+
if (overlay) {
|
|
515
|
+
overlay.addEventListener('click', (e) => {
|
|
516
|
+
if (e.target === overlay) closeModal();
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ── Enter key submits modal form ──
|
|
521
|
+
_panel.addEventListener('keydown', (e) => {
|
|
522
|
+
if (e.key === 'Enter' && _editTarget) {
|
|
523
|
+
e.preventDefault();
|
|
524
|
+
saveFromModal();
|
|
525
|
+
}
|
|
526
|
+
if (e.key === 'Escape') {
|
|
527
|
+
if (_editTarget) {
|
|
528
|
+
closeModal();
|
|
529
|
+
} else {
|
|
530
|
+
closePanel();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|