cli-jaw 0.1.11 → 0.1.12

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.
Files changed (63) hide show
  1. package/README.ko.md +44 -13
  2. package/README.md +12 -11
  3. package/README.zh-CN.md +43 -12
  4. package/dist/bin/commands/doctor.js +13 -2
  5. package/dist/bin/commands/doctor.js.map +1 -1
  6. package/dist/bin/commands/mcp.js +15 -18
  7. package/dist/bin/commands/mcp.js.map +1 -1
  8. package/dist/bin/commands/serve.js +3 -28
  9. package/dist/bin/commands/serve.js.map +1 -1
  10. package/dist/bin/commands/skill.js +9 -6
  11. package/dist/bin/commands/skill.js.map +1 -1
  12. package/dist/lib/mcp-sync.js +123 -31
  13. package/dist/lib/mcp-sync.js.map +1 -1
  14. package/{scripts → dist/scripts}/check-copilot-gap.js +24 -17
  15. package/dist/scripts/check-copilot-gap.js.map +1 -0
  16. package/{scripts/check-deps-offline.mjs → dist/scripts/check-deps-offline.js} +24 -20
  17. package/dist/scripts/check-deps-offline.js.map +1 -0
  18. package/dist/scripts/fresh-install-smoke.js +120 -0
  19. package/dist/scripts/fresh-install-smoke.js.map +1 -0
  20. package/{scripts/i18n-registry.py → dist/scripts/i18n-registry.js} +115 -122
  21. package/dist/scripts/i18n-registry.js.map +1 -0
  22. package/dist/server.js +34 -26
  23. package/dist/server.js.map +1 -1
  24. package/dist/src/cli/command-context.js +13 -3
  25. package/dist/src/cli/command-context.js.map +1 -1
  26. package/dist/src/prompt/builder.js +28 -1
  27. package/dist/src/prompt/builder.js.map +1 -1
  28. package/package.json +9 -5
  29. package/public/dist/bundle.js +72 -77
  30. package/public/dist/bundle.js.map +4 -4
  31. package/public/index.html +1 -3
  32. package/public/js/{api.js → api.ts} +18 -12
  33. package/public/js/{constants.js → constants.ts} +44 -24
  34. package/public/js/features/{appname.js → appname.ts} +13 -12
  35. package/public/js/features/{chat.js → chat.ts} +46 -37
  36. package/public/js/features/{employees.js → employees.ts} +67 -38
  37. package/public/js/features/heartbeat.ts +90 -0
  38. package/public/js/features/{i18n.js → i18n.ts} +20 -20
  39. package/public/js/features/memory.ts +125 -0
  40. package/public/js/features/{settings.js → settings.ts} +125 -93
  41. package/public/js/features/{sidebar.js → sidebar.ts} +15 -16
  42. package/public/js/features/{skills.js → skills.ts} +29 -16
  43. package/public/js/features/{slash-commands.js → slash-commands.ts} +34 -29
  44. package/public/js/features/{theme.js → theme.ts} +4 -4
  45. package/public/js/{locale.js → locale.ts} +3 -3
  46. package/public/js/main.ts +280 -0
  47. package/public/js/{render.js → render.ts} +34 -107
  48. package/public/js/state.ts +38 -0
  49. package/public/js/{ui.js → ui.ts} +60 -63
  50. package/public/js/{ws.js → ws.ts} +46 -20
  51. package/public/locales/en.json +1 -0
  52. package/public/locales/ko.json +1 -0
  53. package/scripts/check-copilot-gap.ts +75 -0
  54. package/scripts/check-deps-offline.ts +98 -0
  55. package/scripts/fresh-install-smoke.ts +130 -0
  56. package/scripts/i18n-registry.ts +230 -0
  57. package/scripts/postinstall-guard.cjs +5 -0
  58. package/dist/bin/cli-claw.js +0 -96
  59. package/dist/bin/cli-claw.js.map +0 -1
  60. package/public/js/features/heartbeat.js +0 -80
  61. package/public/js/features/memory.js +0 -85
  62. package/public/js/main.js +0 -278
  63. package/public/js/state.js +0 -16
@@ -1,80 +0,0 @@
1
- // ── Heartbeat Feature ──
2
- import { state } from '../state.js';
3
- import { t } from './i18n.js';
4
- import { api, apiJson } from '../api.js';
5
- import { escapeHtml } from '../render.js';
6
-
7
- export async function openHeartbeatModal() {
8
- const data = await api('/api/heartbeat');
9
- state.heartbeatJobs = data?.jobs || [];
10
- renderHeartbeatJobs();
11
- document.getElementById('heartbeatModal').classList.add('open');
12
- }
13
-
14
- export function closeHeartbeatModal(e) {
15
- if (e && e.target !== e.currentTarget) return;
16
- document.getElementById('heartbeatModal').classList.remove('open');
17
- }
18
-
19
- export function renderHeartbeatJobs() {
20
- const container = document.getElementById('hbJobsList');
21
- if (state.heartbeatJobs.length === 0) {
22
- container.innerHTML = `<p style="color:var(--text-dim);font-size:12px;text-align:center">${t('hb.empty')}</p>`;
23
- } else {
24
- container.innerHTML = state.heartbeatJobs.map((job, i) => `
25
- <div class="hb-job-card">
26
- <div class="hb-job-header">
27
- <input type="text" value="${escapeHtml(job.name || '')}" placeholder="${t('hb.name')}"
28
- data-hb-name="${i}">
29
- <span style="font-size:11px;color:var(--text-dim)">every</span>
30
- <input type="number" value="${job.schedule?.minutes || 5}" min="1"
31
- data-hb-minutes="${i}">
32
- <span style="font-size:11px;color:var(--text-dim)">min</span>
33
- <button class="hb-toggle ${job.enabled ? 'on' : 'off'}"
34
- data-hb-toggle="${i}"></button>
35
- <button class="hb-del" data-hb-remove="${i}">✕</button>
36
- </div>
37
- <textarea class="hb-prompt" rows="2" placeholder="${t('hb.prompt')}"
38
- data-hb-prompt="${i}">${escapeHtml(job.prompt || '')}</textarea>
39
- </div>
40
- `).join('');
41
- }
42
- const active = state.heartbeatJobs.filter(j => j.enabled).length;
43
- document.getElementById('hbSidebarBtn').textContent = `💓 Heartbeat (${active})`;
44
- }
45
-
46
- export function addHeartbeatJob() {
47
- state.heartbeatJobs.push({
48
- id: 'hb_' + Date.now(),
49
- name: '',
50
- enabled: true,
51
- schedule: { kind: 'every', minutes: 5 },
52
- prompt: ''
53
- });
54
- renderHeartbeatJobs();
55
- saveHeartbeatJobs();
56
- }
57
-
58
- export function removeHeartbeatJob(i) {
59
- state.heartbeatJobs.splice(i, 1);
60
- renderHeartbeatJobs();
61
- saveHeartbeatJobs();
62
- }
63
-
64
- export function toggleHeartbeatJob(i) {
65
- state.heartbeatJobs[i].enabled = !state.heartbeatJobs[i].enabled;
66
- renderHeartbeatJobs();
67
- saveHeartbeatJobs();
68
- }
69
-
70
- export async function saveHeartbeatJobs() {
71
- await apiJson('/api/heartbeat', 'PUT', { jobs: state.heartbeatJobs });
72
- }
73
-
74
- export async function initHeartbeatBadge() {
75
- try {
76
- const d = await api('/api/heartbeat');
77
- const active = (d?.jobs || []).filter(j => j.enabled).length;
78
- document.getElementById('hbSidebarBtn').textContent = `💓 Heartbeat (${active})`;
79
- } catch { }
80
- }
@@ -1,85 +0,0 @@
1
- // ── Memory Feature ──
2
- import { escapeHtml } from '../render.js';
3
- import { api, apiJson, apiFire } from '../api.js';
4
-
5
- export async function openMemoryModal() {
6
- const data = await api('/api/memory-files');
7
- if (!data) return;
8
- document.getElementById('memOn').classList.toggle('active', data.enabled);
9
- document.getElementById('memOff').classList.toggle('active', !data.enabled);
10
- document.getElementById('memFlushEvery').value = data.flushEvery;
11
- document.getElementById('memCli').value = data.cli || '';
12
- document.getElementById('memModel').value = data.model || '';
13
- document.getElementById('memRetention').value = data.retentionDays;
14
- document.getElementById('memPath').textContent = data.path;
15
- document.getElementById('memCounter').textContent = data.counter;
16
- document.getElementById('memThreshold').textContent = data.flushEvery;
17
- renderMemFiles(data.files);
18
- document.getElementById('memorySidebarBtn').textContent = `🧠 Memory (${data.files.length})`;
19
- document.getElementById('memoryModal').classList.add('open');
20
- }
21
-
22
- export function closeMemoryModal(e) {
23
- if (e && e.target !== e.currentTarget) return;
24
- document.getElementById('memoryModal').classList.remove('open');
25
- }
26
-
27
- export function switchMemTab(tab) {
28
- document.getElementById('memTabSettings').style.display = tab === 'settings' ? '' : 'none';
29
- document.getElementById('memTabFiles').style.display = tab === 'files' ? '' : 'none';
30
- document.getElementById('memTabBtnSettings').classList.toggle('active', tab === 'settings');
31
- document.getElementById('memTabBtnFiles').classList.toggle('active', tab === 'files');
32
- }
33
-
34
- export async function setMemEnabled(v) {
35
- document.getElementById('memOn').classList.toggle('active', v);
36
- document.getElementById('memOff').classList.toggle('active', !v);
37
- await apiJson('/api/memory-files/settings', 'PUT', { enabled: v });
38
- }
39
-
40
- export async function saveMemSettings() {
41
- await apiJson('/api/memory-files/settings', 'PUT', {
42
- flushEvery: +document.getElementById('memFlushEvery').value,
43
- cli: document.getElementById('memCli').value,
44
- model: document.getElementById('memModel').value,
45
- retentionDays: +document.getElementById('memRetention').value,
46
- });
47
- document.getElementById('memThreshold').textContent = document.getElementById('memFlushEvery').value;
48
- }
49
-
50
- function renderMemFiles(files) {
51
- const container = document.getElementById('memFilesList');
52
- if (!files || files.length === 0) {
53
- container.innerHTML = '<p style="color:var(--text-dim);font-size:12px;text-align:center">No memory files yet</p>';
54
- return;
55
- }
56
- container.innerHTML = files.map(f => `
57
- <div style="display:flex;align-items:center;justify-content:space-between;padding:6px 8px;border:1px solid var(--border);border-radius:4px;margin-bottom:4px;cursor:pointer"
58
- data-mem-view="${escapeHtml(f.name)}">
59
- <div>
60
- <span style="font-size:12px;font-family:monospace">${escapeHtml(f.name)}</span>
61
- <span style="font-size:10px;color:var(--accent);margin-left:6px">${f.entries} entries</span>
62
- </div>
63
- <button data-mem-delete="${escapeHtml(f.name)}" style="background:none;border:none;color:#f55;cursor:pointer;font-size:14px">🗑️</button>
64
- </div>
65
- `).join('');
66
- }
67
-
68
- export async function deleteMemFile(name) {
69
- if (!confirm('Delete ' + name + '?')) return;
70
- await apiJson('/api/memory-files/' + name, 'DELETE');
71
- openMemoryModal();
72
- }
73
-
74
- export async function viewMemFile(name) {
75
- const data = await api('/api/memory-files/' + name);
76
- if (!data) return;
77
- const container = document.getElementById('memFilesList');
78
- container.innerHTML = `
79
- <div style="margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
80
- <span style="font-size:12px;font-weight:600">${data.name}</span>
81
- <button data-mem-back style="background:none;border:none;color:var(--accent);cursor:pointer;font-size:11px">← back</button>
82
- </div>
83
- <pre style="background:var(--bg);padding:8px;border-radius:4px;font-size:11px;white-space:pre-wrap;max-height:50vh;overflow-y:auto;color:var(--text)">${escapeHtml(data.content)}</pre>
84
- `;
85
- }
package/public/js/main.js DELETED
@@ -1,278 +0,0 @@
1
- // ── App Entry Point ──
2
- // All event bindings happen here (no inline onclick in HTML)
3
-
4
- // ── Global Error Boundary ──
5
- window.addEventListener('unhandledrejection', (e) => {
6
- console.error('[unhandled]', e.reason);
7
- e.preventDefault();
8
- });
9
- window.addEventListener('error', (e) => {
10
- console.error('[error]', e.message, e.filename, e.lineno);
11
- });
12
-
13
- import { connect } from './ws.js';
14
- import { switchTab, handleSave, loadStats, loadMessages, loadMemory, initMsgCopy } from './ui.js';
15
- import { sendMessage, handleKey, clearAttachedFiles, removeAttachedFile, clearChat, initDragDrop, initAutoResize } from './features/chat.js';
16
- import {
17
- loadCommands, update as updateSlashDropdown, handleKeydown as handleSlashKeydown,
18
- handleClick as handleSlashClick, handleOutsideClick as handleSlashOutsideClick,
19
- } from './features/slash-commands.js';
20
- import { loadSkills, toggleSkill, filterSkills } from './features/skills.js';
21
- import {
22
- loadSettings, setPerm, handleModelSelect, applyCustomModel, onCliChange,
23
- saveActiveCliSettings, savePerCli, updateSettings, openPromptModal,
24
- closePromptModal, savePromptFromModal, syncMcpServers, installMcpGlobal,
25
- loadCliStatus, setTelegram, saveTelegramSettings, saveFallbackOrder
26
- } from './features/settings.js';
27
- import {
28
- loadEmployees, addEmployee, deleteEmployee, updateEmployee,
29
- onEmpCliChange, onEmpRoleChange
30
- } from './features/employees.js';
31
- import {
32
- openHeartbeatModal, closeHeartbeatModal, addHeartbeatJob,
33
- removeHeartbeatJob, toggleHeartbeatJob, saveHeartbeatJobs,
34
- initHeartbeatBadge
35
- } from './features/heartbeat.js';
36
- import {
37
- openMemoryModal, closeMemoryModal, switchMemTab, setMemEnabled,
38
- saveMemSettings, deleteMemFile, viewMemFile
39
- } from './features/memory.js';
40
- import { state } from './state.js';
41
- import { loadCliRegistry, getCliKeys } from './constants.js';
42
- import { initAppName } from './features/appname.js';
43
- import { initSidebar, toggleLeft, toggleRight } from './features/sidebar.js';
44
- import { initTheme } from './features/theme.js';
45
- import { initI18n, setLang, getLang, t } from './features/i18n.js';
46
-
47
- // ── Chat Actions ──
48
- document.getElementById('btnSend').addEventListener('click', sendMessage);
49
- const chatInput = document.getElementById('chatInput');
50
- chatInput.addEventListener('keydown', (e) => {
51
- if (handleSlashKeydown(e)) return;
52
- handleKey(e);
53
- });
54
- let slashInputRaf = 0;
55
- chatInput.addEventListener('input', (e) => {
56
- if (e.isComposing) return;
57
- if (slashInputRaf) cancelAnimationFrame(slashInputRaf);
58
- slashInputRaf = requestAnimationFrame(() => {
59
- updateSlashDropdown(e.target.value);
60
- slashInputRaf = 0;
61
- });
62
- });
63
- chatInput.addEventListener('cmd-execute', () => {
64
- void sendMessage();
65
- });
66
- document.getElementById('cmdDropdown')?.addEventListener('click', handleSlashClick);
67
- document.addEventListener('click', handleSlashOutsideClick);
68
- document.getElementById('filePreviewClear').addEventListener('click', clearAttachedFiles);
69
- document.getElementById('filePreviewList').addEventListener('click', (e) => {
70
- const btn = e.target.closest('[data-file-idx]');
71
- if (btn) removeAttachedFile(+btn.dataset.fileIdx);
72
- });
73
- document.querySelector('.btn-attach').addEventListener('click', () => {
74
- document.getElementById('fileInput').click();
75
- });
76
-
77
- // ── Left Sidebar ──
78
- document.getElementById('memorySidebarBtn').addEventListener('click', openMemoryModal);
79
- document.getElementById('btnClearChat').addEventListener('click', clearChat);
80
- document.getElementById('hbSidebarBtn').addEventListener('click', openHeartbeatModal);
81
-
82
- // Language toggle
83
- document.getElementById('langToggle')?.addEventListener('click', async () => {
84
- const next = getLang() === 'ko' ? 'en' : 'ko';
85
- await setLang(next);
86
- const btn = document.getElementById('langToggle');
87
- if (btn) btn.textContent = `🌐 ${t('lang.' + next)}`;
88
- // Reconnect WS with new locale
89
- if (state.ws) { state.ws.close(); }
90
- });
91
-
92
- // ── Tab Bar (event delegation) ──
93
- document.querySelector('.tab-bar').addEventListener('click', (e) => {
94
- const btn = e.target.closest('.tab-btn');
95
- if (!btn) return;
96
- const tabs = [...btn.parentElement.children].filter(c => c.classList.contains('tab-btn'));
97
- const idx = tabs.indexOf(btn);
98
- const names = ['agents', 'skills', 'settings'];
99
- if (names[idx]) switchTab(names[idx], btn);
100
- });
101
-
102
- // ── Save Button ──
103
- document.querySelector('.sidebar-save-bar .btn-save').addEventListener('click', handleSave);
104
-
105
- // ── Agents Tab ──
106
- document.getElementById('selCli').addEventListener('change', () => onCliChange());
107
- document.getElementById('selModel').addEventListener('change', () => saveActiveCliSettings());
108
- document.getElementById('selEffort').addEventListener('change', () => saveActiveCliSettings());
109
- document.querySelector('[data-action="addEmployee"]').addEventListener('click', addEmployee);
110
-
111
- // ── Employees (Event Delegation) ──
112
- document.getElementById('employeesList').addEventListener('click', (e) => {
113
- const del = e.target.closest('[data-emp-delete]');
114
- if (del) { deleteEmployee(del.dataset.empDelete); return; }
115
- });
116
- document.getElementById('employeesList').addEventListener('change', (e) => {
117
- const name = e.target.closest('[data-emp-name]');
118
- if (name) { updateEmployee(name.dataset.empName, { name: e.target.value }); return; }
119
- const cli = e.target.closest('[data-emp-cli]');
120
- if (cli) { onEmpCliChange(cli.dataset.empCli, e.target.value); return; }
121
- const model = e.target.closest('[data-emp-model]');
122
- if (model) {
123
- if (e.target.value === '__custom__') {
124
- const val = prompt(t('model.promptInput'));
125
- if (val?.trim()) {
126
- const opt = document.createElement('option');
127
- opt.value = val.trim(); opt.textContent = val.trim();
128
- const customOpt = e.target.querySelector('option[value="__custom__"]');
129
- e.target.insertBefore(opt, customOpt);
130
- e.target.value = val.trim();
131
- updateEmployee(model.dataset.empModel, { model: val.trim() });
132
- } else { e.target.value = 'default'; }
133
- } else { updateEmployee(model.dataset.empModel, { model: e.target.value }); }
134
- return;
135
- }
136
- const role = e.target.closest('[data-emp-role]');
137
- if (role) { onEmpRoleChange(role.dataset.empRole, e.target.value); return; }
138
- const custom = e.target.closest('[data-emp-custom]');
139
- if (custom) { updateEmployee(custom.dataset.empCustom, { role: e.target.value }); return; }
140
- });
141
-
142
- // ── Skills Tab (Event Delegation) ──
143
- document.getElementById('skillsList').addEventListener('click', (e) => {
144
- const toggle = e.target.closest('[data-skill-id]');
145
- if (toggle) {
146
- toggleSkill(toggle.dataset.skillId, toggle.dataset.skillEnabled === 'true');
147
- }
148
- });
149
- // Skill filter buttons (event delegation)
150
- document.querySelector('#tabSkills').addEventListener('click', (e) => {
151
- const filterBtn = e.target.closest('.skill-filter');
152
- if (filterBtn) {
153
- const cat = filterBtn.dataset.filter;
154
- filterSkills(cat, filterBtn);
155
- }
156
- });
157
-
158
- // ── Settings Tab ──
159
- document.querySelector('[data-action="openPrompt"]').addEventListener('click', openPromptModal);
160
- document.getElementById('tgOff').addEventListener('click', () => setTelegram(false));
161
- document.getElementById('tgOn').addEventListener('click', () => setTelegram(true));
162
- document.getElementById('tgToken').addEventListener('change', saveTelegramSettings);
163
- document.getElementById('tgChatIds').addEventListener('change', saveTelegramSettings);
164
- document.getElementById('fallbackOrderList').addEventListener('change', saveFallbackOrder);
165
-
166
- // Per-CLI model selects
167
- function bindPerCliControlEvents() {
168
- for (const cli of getCliKeys()) {
169
- const cap = cli.charAt(0).toUpperCase() + cli.slice(1);
170
- const sel = document.getElementById('model' + cap);
171
- if (sel) sel.addEventListener('change', function () { handleModelSelect(cli, this); });
172
- const custom = document.getElementById('customModel' + cap);
173
- if (custom) custom.addEventListener('change', function () { applyCustomModel(cli, this); });
174
- const effort = document.getElementById('effort' + cap);
175
- if (effort) effort.addEventListener('change', savePerCli);
176
- }
177
- }
178
-
179
- // MCP
180
- document.querySelector('[data-action="syncMcp"]').addEventListener('click', syncMcpServers);
181
- document.querySelector('[data-action="installMcp"]').addEventListener('click', installMcpGlobal);
182
- document.querySelector('[data-action="refreshCli"]').addEventListener('click', () => loadCliStatus(true));
183
- document.getElementById('cliStatusInterval').addEventListener('change', function () {
184
- localStorage.setItem('cliStatusInterval', this.value);
185
- });
186
-
187
- // ── Prompt Modal ──
188
- document.getElementById('promptModal').addEventListener('click', (e) => closePromptModal(e));
189
- document.querySelector('#promptModal .modal-box').addEventListener('click', (e) => e.stopPropagation());
190
- document.querySelector('[data-action="closePrompt"]').addEventListener('click', () => closePromptModal());
191
- document.querySelector('[data-action="cancelPrompt"]').addEventListener('click', () => closePromptModal());
192
- document.querySelector('[data-action="savePrompt"]').addEventListener('click', savePromptFromModal);
193
- document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closePromptModal(); });
194
-
195
- // ── Heartbeat Modal ──
196
- document.getElementById('heartbeatModal').addEventListener('click', (e) => closeHeartbeatModal(e));
197
- document.querySelector('#heartbeatModal .modal-box').addEventListener('click', (e) => e.stopPropagation());
198
- document.querySelector('[data-action="closeHeartbeat"]').addEventListener('click', () => closeHeartbeatModal());
199
- document.querySelector('[data-action="addHeartbeat"]').addEventListener('click', addHeartbeatJob);
200
-
201
- // Heartbeat jobs (event delegation)
202
- document.getElementById('hbJobsList').addEventListener('click', (e) => {
203
- const toggle = e.target.closest('[data-hb-toggle]');
204
- if (toggle) { toggleHeartbeatJob(+toggle.dataset.hbToggle); return; }
205
- const remove = e.target.closest('[data-hb-remove]');
206
- if (remove) { removeHeartbeatJob(+remove.dataset.hbRemove); return; }
207
- });
208
- document.getElementById('hbJobsList').addEventListener('change', (e) => {
209
- const name = e.target.closest('[data-hb-name]');
210
- if (name) { state.heartbeatJobs[+name.dataset.hbName].name = e.target.value; saveHeartbeatJobs(); return; }
211
- const min = e.target.closest('[data-hb-minutes]');
212
- if (min) { state.heartbeatJobs[+min.dataset.hbMinutes].schedule = { kind: 'every', minutes: +e.target.value }; saveHeartbeatJobs(); return; }
213
- const prompt = e.target.closest('[data-hb-prompt]');
214
- if (prompt) { state.heartbeatJobs[+prompt.dataset.hbPrompt].prompt = e.target.value; saveHeartbeatJobs(); return; }
215
- });
216
-
217
- // ── Memory Modal ──
218
- document.getElementById('memoryModal').addEventListener('click', (e) => closeMemoryModal(e));
219
- document.querySelector('#memoryModal .modal-box').addEventListener('click', (e) => e.stopPropagation());
220
- document.querySelector('[data-action="closeMemory"]').addEventListener('click', () => closeMemoryModal());
221
- document.getElementById('memTabBtnSettings').addEventListener('click', () => switchMemTab('settings'));
222
- document.getElementById('memTabBtnFiles').addEventListener('click', () => switchMemTab('files'));
223
- document.getElementById('memOn').addEventListener('click', () => setMemEnabled(true));
224
- document.getElementById('memOff').addEventListener('click', () => setMemEnabled(false));
225
- document.getElementById('memFlushEvery').addEventListener('change', saveMemSettings);
226
- document.getElementById('memCli').addEventListener('change', saveMemSettings);
227
- document.getElementById('memModel').addEventListener('change', saveMemSettings);
228
- document.getElementById('memRetention').addEventListener('change', saveMemSettings);
229
-
230
- // Memory files (event delegation)
231
- document.getElementById('memFilesList').addEventListener('click', (e) => {
232
- const del = e.target.closest('[data-mem-delete]');
233
- if (del) { e.stopPropagation(); deleteMemFile(del.dataset.memDelete); return; }
234
- const view = e.target.closest('[data-mem-view]');
235
- if (view) { viewMemFile(view.dataset.memView); return; }
236
- const back = e.target.closest('[data-mem-back]');
237
- if (back) { openMemoryModal(); return; }
238
- });
239
-
240
- // ── Init ──
241
- async function bootstrap() {
242
- await initI18n();
243
- const langBtn = document.getElementById('langToggle');
244
- if (langBtn) langBtn.textContent = `🌐 ${t('lang.' + getLang())}`;
245
- await loadCliRegistry();
246
- bindPerCliControlEvents();
247
- connect();
248
- initDragDrop();
249
- initAutoResize();
250
- await loadCommands();
251
- await loadSettings();
252
- loadCliStatus();
253
- loadMemory();
254
- // loadMessages() is handled by ws.js onopen (clear + reload)
255
- loadEmployees();
256
- initHeartbeatBadge();
257
- initAppName();
258
- initSidebar();
259
- initTheme();
260
- initMsgCopy();
261
- }
262
-
263
- void bootstrap().catch((err) => {
264
- console.error('[bootstrap]', err);
265
- });
266
-
267
- // ── Keyboard: Escape closes modals ──────────────────
268
- document.addEventListener('keydown', (e) => {
269
- if (e.key === 'Escape') {
270
- document.querySelectorAll('.modal-overlay.open').forEach(m => {
271
- m.classList.remove('open');
272
- });
273
- }
274
- });
275
-
276
- // ── Mobile sidebar toggle (sidebar.js functions reuse) ──
277
- document.getElementById('mobileMenuLeft')?.addEventListener('click', toggleLeft);
278
- document.getElementById('mobileMenuRight')?.addEventListener('click', toggleRight);
@@ -1,16 +0,0 @@
1
- // ── Shared State Module ──
2
- // All modules import this to access/modify shared state.
3
- // Object reference ensures mutations are seen across modules.
4
-
5
- export const state = {
6
- ws: null,
7
- agentBusy: false,
8
- employees: [],
9
- allSkills: [],
10
- currentSkillFilter: 'all',
11
- currentAgentDiv: null,
12
- attachedFiles: [],
13
- heartbeatJobs: [],
14
- cliStatusCache: null,
15
- cliStatusTs: 0,
16
- };