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.
- package/README.ko.md +44 -13
- package/README.md +12 -11
- package/README.zh-CN.md +43 -12
- package/dist/bin/commands/doctor.js +13 -2
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/mcp.js +15 -18
- package/dist/bin/commands/mcp.js.map +1 -1
- package/dist/bin/commands/serve.js +3 -28
- package/dist/bin/commands/serve.js.map +1 -1
- package/dist/bin/commands/skill.js +9 -6
- package/dist/bin/commands/skill.js.map +1 -1
- package/dist/lib/mcp-sync.js +123 -31
- package/dist/lib/mcp-sync.js.map +1 -1
- package/{scripts → dist/scripts}/check-copilot-gap.js +24 -17
- package/dist/scripts/check-copilot-gap.js.map +1 -0
- package/{scripts/check-deps-offline.mjs → dist/scripts/check-deps-offline.js} +24 -20
- package/dist/scripts/check-deps-offline.js.map +1 -0
- package/dist/scripts/fresh-install-smoke.js +120 -0
- package/dist/scripts/fresh-install-smoke.js.map +1 -0
- package/{scripts/i18n-registry.py → dist/scripts/i18n-registry.js} +115 -122
- package/dist/scripts/i18n-registry.js.map +1 -0
- package/dist/server.js +34 -26
- package/dist/server.js.map +1 -1
- package/dist/src/cli/command-context.js +13 -3
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/prompt/builder.js +28 -1
- package/dist/src/prompt/builder.js.map +1 -1
- package/package.json +9 -5
- package/public/dist/bundle.js +72 -77
- package/public/dist/bundle.js.map +4 -4
- package/public/index.html +1 -3
- package/public/js/{api.js → api.ts} +18 -12
- package/public/js/{constants.js → constants.ts} +44 -24
- package/public/js/features/{appname.js → appname.ts} +13 -12
- package/public/js/features/{chat.js → chat.ts} +46 -37
- package/public/js/features/{employees.js → employees.ts} +67 -38
- package/public/js/features/heartbeat.ts +90 -0
- package/public/js/features/{i18n.js → i18n.ts} +20 -20
- package/public/js/features/memory.ts +125 -0
- package/public/js/features/{settings.js → settings.ts} +125 -93
- package/public/js/features/{sidebar.js → sidebar.ts} +15 -16
- package/public/js/features/{skills.js → skills.ts} +29 -16
- package/public/js/features/{slash-commands.js → slash-commands.ts} +34 -29
- package/public/js/features/{theme.js → theme.ts} +4 -4
- package/public/js/{locale.js → locale.ts} +3 -3
- package/public/js/main.ts +280 -0
- package/public/js/{render.js → render.ts} +34 -107
- package/public/js/state.ts +38 -0
- package/public/js/{ui.js → ui.ts} +60 -63
- package/public/js/{ws.js → ws.ts} +46 -20
- package/public/locales/en.json +1 -0
- package/public/locales/ko.json +1 -0
- package/scripts/check-copilot-gap.ts +75 -0
- package/scripts/check-deps-offline.ts +98 -0
- package/scripts/fresh-install-smoke.ts +130 -0
- package/scripts/i18n-registry.ts +230 -0
- package/scripts/postinstall-guard.cjs +5 -0
- package/dist/bin/cli-claw.js +0 -96
- package/dist/bin/cli-claw.js.map +0 -1
- package/public/js/features/heartbeat.js +0 -80
- package/public/js/features/memory.js +0 -85
- package/public/js/main.js +0 -278
- 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);
|
package/public/js/state.js
DELETED
|
@@ -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
|
-
};
|