jinzd-ai-cli 0.4.206 → 0.4.208

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 (27) hide show
  1. package/dist/{batch-6PORMJWT.js → batch-3T44NFLG.js} +2 -2
  2. package/dist/{chunk-QQYSZMET.js → chunk-2OVMMSGQ.js} +3 -3
  3. package/dist/{chunk-2WV6AGHM.js → chunk-J22B3OSQ.js} +1 -1
  4. package/dist/{chunk-QNUVZO5X.js → chunk-KV4GYNEA.js} +1 -1
  5. package/dist/{chunk-KJHMRAJU.js → chunk-P7JY3SWA.js} +1 -1
  6. package/dist/{chunk-BQIV4ZQT.js → chunk-QDY72ABW.js} +1 -1
  7. package/dist/{chunk-KOU4KX7J.js → chunk-TD7JEHCR.js} +2 -2
  8. package/dist/{chunk-TW47X5AO.js → chunk-TM4MS63K.js} +1 -1
  9. package/dist/{chunk-SFCNFX42.js → chunk-VWYUB22Y.js} +1 -1
  10. package/dist/{ci-AYMV66P3.js → ci-6QWBVRJX.js} +2 -2
  11. package/dist/{constants-NHGTSHKT.js → constants-SZTQNN7K.js} +1 -1
  12. package/dist/{doctor-cli-WKH7T4AW.js → doctor-cli-I2Y2YR4V.js} +4 -4
  13. package/dist/electron-server.js +5 -8
  14. package/dist/{hub-6R3M3NTC.js → hub-XVPFBODB.js} +1 -1
  15. package/dist/index.js +90 -88
  16. package/dist/{run-tests-5F2OBWIK.js → run-tests-D5SNUBYX.js} +2 -2
  17. package/dist/{run-tests-NBCA4KEG.js → run-tests-HZ2CRCTZ.js} +1 -1
  18. package/dist/{server-LDPQ3DLK.js → server-4EFTRFTN.js} +10 -13
  19. package/dist/{server-37EXC4EB.js → server-PVKLPRUP.js} +4 -4
  20. package/dist/{task-orchestrator-2DTDVCMR.js → task-orchestrator-2A5VCFLS.js} +4 -4
  21. package/dist/{usage-SZWZFX3T.js → usage-Q24E5636.js} +2 -2
  22. package/dist/web/client/actions.js +84 -0
  23. package/dist/web/client/app.js +20 -202
  24. package/dist/web/client/index.html +26 -41
  25. package/dist/web/client/sw.js +1 -1
  26. package/dist/web/client/templates.js +188 -0
  27. package/package.json +1 -1
@@ -3,13 +3,13 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-QQYSZMET.js";
6
+ } from "./chunk-2OVMMSGQ.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-SFCNFX42.js";
9
+ import "./chunk-VWYUB22Y.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-2WV6AGHM.js";
12
+ } from "./chunk-J22B3OSQ.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
@@ -17,7 +17,7 @@ import {
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-QNUVZO5X.js";
20
+ } from "./chunk-KV4GYNEA.js";
21
21
  import "./chunk-4BKXL7SM.js";
22
22
  import "./chunk-TB4W4Y4T.js";
23
23
  import "./chunk-KHYD3WXE.js";
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-TW47X5AO.js";
11
+ } from "./chunk-TM4MS63K.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-QNUVZO5X.js";
13
+ import "./chunk-KV4GYNEA.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Declarative Web UI actions and startup hooks.
3
+ * Kept separate from app.js so static and dynamic UI markup can avoid inline handlers.
4
+ */
5
+
6
+ function handleDeclarativeAction(action, el) {
7
+ switch (action) {
8
+ case 'toggle-auth-mode': toggleAuthMode(); break;
9
+ case 'show-enable-auth': showEnableAuthDialog(); break;
10
+ case 'logout': handleLogout(); break;
11
+ case 'toggle-batch-select': toggleBatchSelect(); break;
12
+ case 'select-all-sessions': selectAllSessions(); break;
13
+ case 'batch-delete-selected': batchDeleteSelected(); break;
14
+ case 'start-hub': window.startHub(); break;
15
+ case 'abort-hub': window.abortHub(); break;
16
+ case 'close-sidebar': closeSidebar(); break;
17
+ case 'import-templates': importTemplates(); break;
18
+ case 'export-templates': exportTemplates(); break;
19
+ case 'show-add-template': showAddTemplate(); break;
20
+ case 'cancel-template-form': cancelTemplateForm(); break;
21
+ case 'save-template-form': saveTemplateForm(); break;
22
+ case 'confirm-response': window.respondConfirm(el.dataset.requestId, el.dataset.approved === 'true'); break;
23
+ case 'batch-confirm-response': window.respondBatchConfirm(el.dataset.requestId, el); break;
24
+ case 'batch-deny-response': window.respondBatchDeny(el.dataset.requestId, el); break;
25
+ case 'ask-user-response': window.respondAskUser(el.dataset.requestId); break;
26
+ case 'auto-pause-response': window.respondAutoPause(el.dataset.requestId, el.dataset.pauseAction); break;
27
+ case 'use-template': useTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
28
+ case 'edit-template': editTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
29
+ case 'delete-template': deleteTemplate(el.closest('[data-tpl-id]')?.dataset.tplId); break;
30
+ case 'insert-file-ref': insertFileRef(el.dataset.filePath || ''); break;
31
+ case 'show-full-preview':
32
+ if (el.previousElementSibling) el.previousElementSibling.style.maxHeight = 'none';
33
+ el.remove();
34
+ break;
35
+ case 'load-session': send({ type: 'command', name: 'session', args: ['load', el.dataset.sessionId] }); break;
36
+ }
37
+ }
38
+
39
+ document.addEventListener('click', (event) => {
40
+ const themeEl = event.target.closest('[data-theme-choice]');
41
+ if (themeEl) {
42
+ event.preventDefault();
43
+ window.setTheme(themeEl.dataset.themeChoice);
44
+ return;
45
+ }
46
+
47
+ const actionEl = event.target.closest('[data-action]');
48
+ if (!actionEl) return;
49
+ event.preventDefault();
50
+ handleDeclarativeAction(actionEl.dataset.action, actionEl);
51
+ });
52
+
53
+ document.addEventListener('keydown', (event) => {
54
+ if (event.key !== 'Enter') return;
55
+ const actionEl = event.target.closest('[data-enter-action]');
56
+ if (!actionEl) return;
57
+ event.preventDefault();
58
+ handleDeclarativeAction(actionEl.dataset.enterAction, actionEl);
59
+ });
60
+
61
+ document.getElementById('auth-form')?.addEventListener('submit', handleAuth);
62
+
63
+ function registerServiceWorker() {
64
+ // Self-updating service worker (v0.4.159). A new SW postMessages
65
+ // 'sw-updated' (and triggers controllerchange) when it takes over; reload
66
+ // once so users do not stay on stale index.html/app.js after an upgrade.
67
+ if (!('serviceWorker' in navigator)) return;
68
+ let swReloaded = false;
69
+ const reloadOnce = () => {
70
+ if (!swReloaded) {
71
+ swReloaded = true;
72
+ location.reload();
73
+ }
74
+ };
75
+ navigator.serviceWorker.addEventListener('message', (e) => {
76
+ if (e.data && e.data.type === 'sw-updated') reloadOnce();
77
+ });
78
+ navigator.serviceWorker.addEventListener('controllerchange', reloadOnce);
79
+ navigator.serviceWorker.register('sw.js').then((reg) => {
80
+ reg.update();
81
+ }).catch(() => {});
82
+ }
83
+
84
+ registerServiceWorker();
@@ -336,8 +336,8 @@ function handleConfirmRequest(msg) {
336
336
  </div>
337
337
  ${msg.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
338
338
  <div class="flex gap-2 mt-2">
339
- <button class="btn btn-success btn-sm btn-outline" onclick="respondConfirm('${msg.requestId}', true)">✓ Approve</button>
340
- <button class="btn btn-error btn-sm btn-outline" onclick="respondConfirm('${msg.requestId}', false)">✗ Deny</button>
339
+ <button class="btn btn-success btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="true">✓ Approve</button>
340
+ <button class="btn btn-error btn-sm btn-outline" data-action="confirm-response" data-request-id="${msg.requestId}" data-approved="false">✗ Deny</button>
341
341
  </div>
342
342
  `;
343
343
  messagesEl.appendChild(el);
@@ -363,8 +363,8 @@ function handleBatchConfirmRequest(msg) {
363
363
  </div>
364
364
  <div class="w-full my-1">${fileListHtml}</div>
365
365
  <div class="flex gap-2 mt-2">
366
- <button class="btn btn-success btn-sm btn-outline" onclick="respondBatchConfirm('${msg.requestId}', this)">✓ Approve Selected</button>
367
- <button class="btn btn-error btn-sm btn-outline" onclick="respondBatchDeny('${msg.requestId}', this)">✗ Reject All</button>
366
+ <button class="btn btn-success btn-sm btn-outline" data-action="batch-confirm-response" data-request-id="${msg.requestId}">✓ Approve Selected</button>
367
+ <button class="btn btn-error btn-sm btn-outline" data-action="batch-deny-response" data-request-id="${msg.requestId}">✗ Reject All</button>
368
368
  </div>
369
369
  `;
370
370
  messagesEl.appendChild(el);
@@ -383,8 +383,8 @@ function handleAskUserRequest(msg) {
383
383
  <input type="text" id="ask-input-${msg.requestId}"
384
384
  class="input input-sm input-bordered flex-1"
385
385
  placeholder="Your answer..."
386
- onkeydown="if(event.key==='Enter')respondAskUser('${msg.requestId}')">
387
- <button class="btn btn-primary btn-sm" onclick="respondAskUser('${msg.requestId}')">Send</button>
386
+ data-enter-action="ask-user-response" data-request-id="${msg.requestId}">
387
+ <button class="btn btn-primary btn-sm" data-action="ask-user-response" data-request-id="${msg.requestId}">Send</button>
388
388
  </div>
389
389
  `;
390
390
  messagesEl.appendChild(el);
@@ -407,12 +407,12 @@ function handleAutoPauseRequest(msg) {
407
407
  <input type="text" id="pause-input-${msg.requestId}"
408
408
  class="input input-sm input-bordered flex-1"
409
409
  placeholder="Optional: redirect AI with a new instruction..."
410
- onkeydown="if(event.key==='Enter')respondAutoPause('${msg.requestId}','redirect')">
410
+ data-enter-action="auto-pause-response" data-request-id="${msg.requestId}" data-pause-action="redirect">
411
411
  </div>
412
412
  <div class="flex gap-2">
413
- <button class="btn btn-success btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','continue')">▶ Continue</button>
414
- <button class="btn btn-primary btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','redirect')">↪ Redirect</button>
415
- <button class="btn btn-error btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','stop')">⏹ Stop</button>
413
+ <button class="btn btn-success btn-sm flex-1" data-action="auto-pause-response" data-request-id="${msg.requestId}" data-pause-action="continue">▶ Continue</button>
414
+ <button class="btn btn-primary btn-sm flex-1" data-action="auto-pause-response" data-request-id="${msg.requestId}" data-pause-action="redirect">↪ Redirect</button>
415
+ <button class="btn btn-error btn-sm flex-1" data-action="auto-pause-response" data-request-id="${msg.requestId}" data-pause-action="stop">⏹ Stop</button>
416
416
  </div>
417
417
  `;
418
418
  messagesEl.appendChild(el);
@@ -666,6 +666,7 @@ window.setTheme = function(theme) {
666
666
 
667
667
  // ── UI helpers ─────────────────────────────────────────────────────
668
668
 
669
+
669
670
  function createAssistantMessage() {
670
671
  const el = document.createElement('div');
671
672
  el.className = 'msg-assistant';
@@ -714,11 +715,11 @@ function renderMarkdown(el, text) {
714
715
  const btn = document.createElement('button');
715
716
  btn.className = 'copy-code-btn btn btn-ghost btn-xs';
716
717
  btn.textContent = '📋 Copy';
717
- btn.onclick = () => {
718
+ btn.addEventListener('click', () => {
718
719
  navigator.clipboard.writeText(block.textContent || '');
719
- btn.textContent = 'Copied';
720
- setTimeout(() => btn.textContent = '📋 Copy', 1500);
721
- };
720
+ btn.textContent = 'Copied';
721
+ setTimeout(() => btn.textContent = 'Copy', 1500);
722
+ });
722
723
  pre.style.position = 'relative';
723
724
  pre.appendChild(btn);
724
725
  }
@@ -1920,12 +1921,12 @@ function addImageToPreview(file) {
1920
1921
  <img src="${reader.result}" class="rounded max-h-16 max-w-[100px] object-contain border border-base-content/20" alt="${escapeHtml(file.name)}">
1921
1922
  <button class="btn btn-xs btn-circle btn-error absolute -top-1 -right-1 opacity-80" title="Remove">✕</button>
1922
1923
  `;
1923
- thumb.querySelector('button').onclick = () => {
1924
+ thumb.querySelector('button').addEventListener('click', () => {
1924
1925
  const idx = pendingImages.findIndex(img => img.name === file.name && img.data === base64);
1925
1926
  if (idx >= 0) pendingImages.splice(idx, 1);
1926
1927
  thumb.remove();
1927
1928
  if (pendingImages.length === 0) imagePreviewArea.classList.add('hidden');
1928
- };
1929
+ });
1929
1930
  imagePreviewArea.appendChild(thumb);
1930
1931
  imagePreviewArea.classList.remove('hidden');
1931
1932
  };
@@ -1991,189 +1992,6 @@ function handleMemoryContent(msg) {
1991
1992
 
1992
1993
  // ── Prompt Templates ───────────────────────────────────────────────
1993
1994
 
1994
- const TEMPLATES_KEY = 'aicli-templates';
1995
-
1996
- function loadTemplates() {
1997
- try {
1998
- return JSON.parse(localStorage.getItem(TEMPLATES_KEY) || '[]');
1999
- } catch { return []; }
2000
- }
2001
-
2002
- function saveTemplatesToStorage(templates) {
2003
- localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates));
2004
- }
2005
-
2006
- function openTemplatesModal() {
2007
- const modal = document.getElementById('templates-modal');
2008
- if (!modal) return;
2009
- cancelTemplateForm();
2010
- renderTemplateList();
2011
- document.getElementById('template-search').value = '';
2012
- modal.showModal();
2013
- }
2014
-
2015
- function renderTemplateList(filter) {
2016
- const listEl = document.getElementById('template-list');
2017
- if (!listEl) return;
2018
- const templates = loadTemplates();
2019
- const q = (filter || '').toLowerCase();
2020
- const filtered = q
2021
- ? templates.filter(t => t.name.toLowerCase().includes(q) || (t.tags || []).some(tag => tag.toLowerCase().includes(q)) || t.content.toLowerCase().includes(q))
2022
- : templates;
2023
-
2024
- if (filtered.length === 0) {
2025
- listEl.innerHTML = `<div class="template-empty">${q ? 'No matching templates' : 'No templates yet. Click + New to create one.'}</div>`;
2026
- return;
2027
- }
2028
-
2029
- listEl.innerHTML = filtered.map(t => {
2030
- const preview = t.content.length > 80 ? t.content.slice(0, 80) + '...' : t.content;
2031
- const tagsHtml = (t.tags || []).map(tag => `<span class="template-item-tag">${escapeHtml(tag)}</span>`).join('');
2032
- return `
2033
- <div class="template-item" data-tpl-id="${t.id}" onclick="useTemplate('${t.id}')">
2034
- <div class="template-item-body">
2035
- <div class="template-item-name">${escapeHtml(t.name)}</div>
2036
- <div class="template-item-preview">${escapeHtml(preview)}</div>
2037
- ${tagsHtml ? `<div class="template-item-tags">${tagsHtml}</div>` : ''}
2038
- </div>
2039
- <div class="template-item-actions">
2040
- <button class="btn btn-xs btn-ghost" onclick="event.stopPropagation(); editTemplate('${t.id}')" title="Edit">✏️</button>
2041
- <button class="btn btn-xs btn-ghost" onclick="event.stopPropagation(); deleteTemplate('${t.id}')" title="Delete">🗑️</button>
2042
- </div>
2043
- </div>`;
2044
- }).join('');
2045
- }
2046
-
2047
- function useTemplate(id) {
2048
- const templates = loadTemplates();
2049
- const tpl = templates.find(t => t.id === id);
2050
- if (!tpl) return;
2051
- userInput.value = tpl.content;
2052
- userInput.focus();
2053
- userInput.style.height = 'auto';
2054
- userInput.style.height = Math.min(userInput.scrollHeight, 200) + 'px';
2055
- document.getElementById('templates-modal')?.close();
2056
- }
2057
-
2058
- function showAddTemplate() {
2059
- const form = document.getElementById('template-form');
2060
- form.classList.remove('hidden');
2061
- document.getElementById('tpl-edit-id').value = '';
2062
- document.getElementById('tpl-name').value = '';
2063
- document.getElementById('tpl-content').value = '';
2064
- document.getElementById('tpl-tags').value = '';
2065
- document.getElementById('tpl-name').focus();
2066
- }
2067
-
2068
- function editTemplate(id) {
2069
- const templates = loadTemplates();
2070
- const tpl = templates.find(t => t.id === id);
2071
- if (!tpl) return;
2072
- const form = document.getElementById('template-form');
2073
- form.classList.remove('hidden');
2074
- document.getElementById('tpl-edit-id').value = tpl.id;
2075
- document.getElementById('tpl-name').value = tpl.name;
2076
- document.getElementById('tpl-content').value = tpl.content;
2077
- document.getElementById('tpl-tags').value = (tpl.tags || []).join(', ');
2078
- document.getElementById('tpl-name').focus();
2079
- }
2080
-
2081
- function cancelTemplateForm() {
2082
- document.getElementById('template-form')?.classList.add('hidden');
2083
- }
2084
-
2085
- function saveTemplateForm() {
2086
- const name = document.getElementById('tpl-name').value.trim();
2087
- const content = document.getElementById('tpl-content').value.trim();
2088
- const tagsRaw = document.getElementById('tpl-tags').value.trim();
2089
- const editId = document.getElementById('tpl-edit-id').value;
2090
-
2091
- if (!name || !content) return;
2092
-
2093
- const tags = tagsRaw ? tagsRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
2094
- const templates = loadTemplates();
2095
-
2096
- if (editId) {
2097
- const idx = templates.findIndex(t => t.id === editId);
2098
- if (idx >= 0) {
2099
- templates[idx].name = name;
2100
- templates[idx].content = content;
2101
- templates[idx].tags = tags;
2102
- }
2103
- } else {
2104
- templates.unshift({
2105
- id: 'tpl-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
2106
- name,
2107
- content,
2108
- tags,
2109
- createdAt: new Date().toISOString(),
2110
- });
2111
- }
2112
-
2113
- saveTemplatesToStorage(templates);
2114
- cancelTemplateForm();
2115
- renderTemplateList();
2116
- }
2117
-
2118
- function deleteTemplate(id) {
2119
- const templates = loadTemplates().filter(t => t.id !== id);
2120
- saveTemplatesToStorage(templates);
2121
- renderTemplateList(document.getElementById('template-search')?.value);
2122
- }
2123
-
2124
- function exportTemplates() {
2125
- const templates = loadTemplates();
2126
- if (templates.length === 0) return;
2127
- const blob = new Blob([JSON.stringify(templates, null, 2)], { type: 'application/json' });
2128
- const url = URL.createObjectURL(blob);
2129
- const a = document.createElement('a');
2130
- a.href = url;
2131
- a.download = `aicli-templates-${new Date().toISOString().slice(0, 10)}.json`;
2132
- a.click();
2133
- URL.revokeObjectURL(url);
2134
- }
2135
-
2136
- function importTemplates() {
2137
- const input = document.createElement('input');
2138
- input.type = 'file';
2139
- input.accept = '.json';
2140
- input.onchange = () => {
2141
- const file = input.files[0];
2142
- if (!file) return;
2143
- const reader = new FileReader();
2144
- reader.onload = () => {
2145
- try {
2146
- const imported = JSON.parse(reader.result);
2147
- if (!Array.isArray(imported)) throw new Error('Not an array');
2148
- const existing = loadTemplates();
2149
- const existingIds = new Set(existing.map(t => t.id));
2150
- let added = 0;
2151
- for (const t of imported) {
2152
- if (t.id && t.name && t.content && !existingIds.has(t.id)) {
2153
- existing.push(t);
2154
- added++;
2155
- }
2156
- }
2157
- saveTemplatesToStorage(existing);
2158
- renderTemplateList();
2159
- addInfoMessage(`📥 Imported ${added} template(s).`);
2160
- } catch {
2161
- addErrorMessage('Failed to import templates: invalid JSON format.');
2162
- }
2163
- };
2164
- reader.readAsText(file);
2165
- };
2166
- input.click();
2167
- }
2168
-
2169
- // Template button + search binding
2170
- document.getElementById('btn-templates')?.addEventListener('click', openTemplatesModal);
2171
- document.getElementById('template-search')?.addEventListener('input', (e) => {
2172
- renderTemplateList(e.target.value);
2173
- });
2174
-
2175
- // ── File Tree ──────────────────────────────────────────────────────
2176
-
2177
1995
  const fileTreeEl = document.getElementById('file-tree');
2178
1996
  const fileTreeCwdEl = document.getElementById('file-tree-cwd');
2179
1997
  const btnFileTreeRefresh = document.getElementById('btn-file-tree-refresh');
@@ -2324,10 +2142,10 @@ async function previewFileInChat(filePath) {
2324
2142
  <div class="flex items-center gap-2 mb-1">
2325
2143
  <span class="text-xs font-semibold opacity-70">📄 ${escapeHtml(filePath)}</span>
2326
2144
  <span class="badge badge-ghost badge-xs">${sizeKb} KB</span>
2327
- <button class="btn btn-xs btn-ghost ml-auto opacity-50" onclick="insertFileRef('${escapeHtml(filePath).replace(/'/g, "\\'")}')">Insert @ref</button>
2145
+ <button class="btn btn-xs btn-ghost ml-auto opacity-50" data-action="insert-file-ref" data-file-path="${escapeHtml(filePath)}">Insert @ref</button>
2328
2146
  </div>
2329
2147
  <pre style="max-height:300px"><code class="language-${escapeHtml(ext)}">${escapeHtml(data.content)}</code></pre>
2330
- <button class="btn btn-xs btn-ghost mt-1 opacity-50 text-xs" onclick="this.previousElementSibling.style.maxHeight='none';this.remove()">▼ Show all</button>
2148
+ <button class="btn btn-xs btn-ghost mt-1 opacity-50 text-xs" data-action="show-full-preview">▼ Show all</button>
2331
2149
  `;
2332
2150
  el.querySelectorAll('pre code').forEach(block => {
2333
2151
  try { hljs.highlightElement(block); } catch {}
@@ -3308,7 +3126,7 @@ function hubDone(msg) {
3308
3126
  document.getElementById('hub-abort-btn').classList.add('hidden');
3309
3127
  const status = document.getElementById('hub-status');
3310
3128
  if (msg.saved && msg.sessionId) {
3311
- status.innerHTML = `Saved. <a class="link link-primary" onclick="send({type:'command',name:'session',args:['load','${msg.sessionId}']})">Open session</a>`;
3129
+ status.innerHTML = `Saved. <a class="link link-primary" data-action="load-session" data-session-id="${msg.sessionId}">Open session</a>`;
3312
3130
  } else {
3313
3131
  status.textContent = msg.saved ? 'Saved to history.' : 'Done (not saved).';
3314
3132
  }
@@ -36,7 +36,7 @@
36
36
  <p id="auth-subtitle" class="text-sm text-base-content/60 mt-1">Sign in to continue</p>
37
37
  </div>
38
38
  <div id="auth-error" class="alert alert-error text-sm mb-4 hidden"></div>
39
- <form id="auth-form" onsubmit="handleAuth(event)">
39
+ <form id="auth-form" >
40
40
  <div class="form-control mb-3">
41
41
  <label class="label"><span class="label-text">Username</span></label>
42
42
  <input id="auth-username" type="text" class="input input-bordered w-full" placeholder="username" autocomplete="username" required minlength="2" maxlength="32">
@@ -48,7 +48,7 @@
48
48
  <button id="auth-submit" type="submit" class="btn btn-primary w-full">Sign In</button>
49
49
  </form>
50
50
  <div class="text-center mt-4">
51
- <button id="auth-toggle" class="btn btn-ghost btn-sm" onclick="toggleAuthMode()">
51
+ <button id="auth-toggle" class="btn btn-ghost btn-sm" data-action="toggle-auth-mode" type="button">
52
52
  Don't have an account? <span class="text-primary">Register</span>
53
53
  </button>
54
54
  </div>
@@ -76,18 +76,18 @@
76
76
  <div class="dropdown dropdown-end">
77
77
  <div tabindex="0" role="button" class="btn btn-sm btn-ghost">🎨</div>
78
78
  <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
79
- <li><a onclick="setTheme('dark')">🌙 Dark</a></li>
80
- <li><a onclick="setTheme('night')">🌃 Night</a></li>
81
- <li><a onclick="setTheme('dim')">🌑 Dim</a></li>
82
- <li><a onclick="setTheme('synthwave')">🎵 Synthwave</a></li>
83
- <li><a onclick="setTheme('cyberpunk')">🤖 Cyberpunk</a></li>
84
- <li><a onclick="setTheme('light')">☀️ Light</a></li>
85
- <li><a onclick="setTheme('cupcake')">🧁 Cupcake</a></li>
86
- <li><a onclick="setTheme('nord')">❄️ Nord</a></li>
79
+ <li><a data-theme-choice="dark">🌙 Dark</a></li>
80
+ <li><a data-theme-choice="night">🌃 Night</a></li>
81
+ <li><a data-theme-choice="dim">🌑 Dim</a></li>
82
+ <li><a data-theme-choice="synthwave">🎵 Synthwave</a></li>
83
+ <li><a data-theme-choice="cyberpunk">🤖 Cyberpunk</a></li>
84
+ <li><a data-theme-choice="light">☀️ Light</a></li>
85
+ <li><a data-theme-choice="cupcake">🧁 Cupcake</a></li>
86
+ <li><a data-theme-choice="nord">❄️ Nord</a></li>
87
87
  </ul>
88
88
  </div>
89
89
  <!-- Enable Auth button (shown when no users registered) -->
90
- <button id="btn-enable-auth" class="btn btn-sm btn-ghost hidden" title="Enable multi-user auth" onclick="showEnableAuthDialog()">👤 Enable Auth</button>
90
+ <button id="btn-enable-auth" class="btn btn-sm btn-ghost hidden" title="Enable multi-user auth" data-action="show-enable-auth">👤 Enable Auth</button>
91
91
  <!-- User menu (shown when authenticated) -->
92
92
  <div id="user-menu" class="dropdown dropdown-end hidden">
93
93
  <div tabindex="0" role="button" class="btn btn-sm btn-ghost gap-1">
@@ -95,7 +95,7 @@
95
95
  </div>
96
96
  <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
97
97
  <li><a id="user-label" class="text-sm font-semibold pointer-events-none"></a></li>
98
- <li><a onclick="handleLogout()">🚪 Logout</a></li>
98
+ <li><a data-action="logout">🚪 Logout</a></li>
99
99
  </ul>
100
100
  </div>
101
101
  </div>
@@ -119,16 +119,16 @@
119
119
  <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
120
120
  <div class="p-2 border-b border-base-content/10 flex items-center justify-between gap-1">
121
121
  <input id="session-search" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search...">
122
- <button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" onclick="toggleBatchSelect()">☑</button>
122
+ <button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" data-action="toggle-batch-select">☑</button>
123
123
  <button id="btn-new-session" class="btn btn-xs btn-primary btn-outline flex-shrink-0 whitespace-nowrap" title="New session">+ New</button>
124
124
  </div>
125
125
  <!-- Batch action bar (hidden by default) -->
126
126
  <div id="batch-bar" class="hidden px-2 py-1 border-b border-base-content/10 flex items-center gap-1 bg-base-300 text-xs">
127
127
  <span class="batch-count opacity-60">0 selected</span>
128
128
  <span class="flex-1"></span>
129
- <button class="btn btn-xs btn-ghost" onclick="selectAllSessions()">All</button>
130
- <button class="btn btn-xs btn-error btn-outline" onclick="batchDeleteSelected()">🗑 Delete</button>
131
- <button class="btn btn-xs btn-ghost" onclick="toggleBatchSelect()">Cancel</button>
129
+ <button class="btn btn-xs btn-ghost" data-action="select-all-sessions">All</button>
130
+ <button class="btn btn-xs btn-error btn-outline" data-action="batch-delete-selected">🗑 Delete</button>
131
+ <button class="btn btn-xs btn-ghost" data-action="toggle-batch-select">Cancel</button>
132
132
  </div>
133
133
  <div id="session-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
134
134
  <div class="text-xs opacity-40 text-center py-4">No sessions yet</div>
@@ -216,8 +216,8 @@
216
216
  <label class="flex items-center gap-2 text-xs cursor-pointer">
217
217
  <input id="hub-lanes" type="checkbox" class="checkbox checkbox-xs"> Lanes view (one column per agent)
218
218
  </label>
219
- <button id="hub-start-btn" class="btn btn-primary btn-sm w-full" onclick="startHub()">🏛 Start discussion</button>
220
- <button id="hub-abort-btn" class="btn btn-error btn-outline btn-xs w-full hidden" onclick="abortHub()">⏹ Stop</button>
219
+ <button id="hub-start-btn" class="btn btn-primary btn-sm w-full" data-action="start-hub">🏛 Start discussion</button>
220
+ <button id="hub-abort-btn" class="btn btn-error btn-outline btn-xs w-full hidden" data-action="abort-hub">⏹ Stop</button>
221
221
  <div id="hub-status" class="text-xs opacity-60"></div>
222
222
  </div>
223
223
  </div>
@@ -225,7 +225,7 @@
225
225
  <!-- Sidebar resize handle -->
226
226
  <div id="sidebar-resize" class="sidebar-resize-handle" title="Drag to resize sidebar"></div>
227
227
  <!-- Sidebar backdrop (mobile overlay) -->
228
- <div id="sidebar-backdrop" class="sidebar-backdrop hidden" onclick="closeSidebar()"></div>
228
+ <div id="sidebar-backdrop" class="sidebar-backdrop hidden" data-action="close-sidebar"></div>
229
229
 
230
230
  <!-- Chat Area -->
231
231
  <div class="flex-1 flex flex-col overflow-hidden">
@@ -293,9 +293,9 @@
293
293
  <div class="flex items-center justify-between mb-3">
294
294
  <h3 class="font-bold text-lg">📝 Prompt Templates</h3>
295
295
  <div class="flex gap-1">
296
- <button class="btn btn-xs btn-ghost" onclick="importTemplates()" title="Import">📥 Import</button>
297
- <button class="btn btn-xs btn-ghost" onclick="exportTemplates()" title="Export">📤 Export</button>
298
- <button class="btn btn-xs btn-ghost" onclick="showAddTemplate()" title="New template">+ New</button>
296
+ <button class="btn btn-xs btn-ghost" data-action="import-templates" title="Import">📥 Import</button>
297
+ <button class="btn btn-xs btn-ghost" data-action="export-templates" title="Export">📤 Export</button>
298
+ <button class="btn btn-xs btn-ghost" data-action="show-add-template" title="New template">+ New</button>
299
299
  </div>
300
300
  </div>
301
301
 
@@ -305,8 +305,8 @@
305
305
  <textarea id="tpl-content" class="textarea textarea-bordered w-full text-sm mb-2" rows="4" placeholder="Prompt content..."></textarea>
306
306
  <input id="tpl-tags" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Tags (comma-separated, optional)">
307
307
  <div class="flex gap-2 justify-end">
308
- <button class="btn btn-sm btn-ghost" onclick="cancelTemplateForm()">Cancel</button>
309
- <button class="btn btn-sm btn-primary" onclick="saveTemplateForm()">Save</button>
308
+ <button class="btn btn-sm btn-ghost" data-action="cancel-template-form">Cancel</button>
309
+ <button class="btn btn-sm btn-primary" data-action="save-template-form">Save</button>
310
310
  </div>
311
311
  <input type="hidden" id="tpl-edit-id">
312
312
  </div>
@@ -339,22 +339,7 @@
339
339
  </dialog>
340
340
 
341
341
  <script src="app.js"></script>
342
- <script>
343
- // Self-updating service worker (v0.4.159). A new SW postMessages
344
- // 'sw-updated' (and triggers controllerchange) when it takes over; we
345
- // reload once so the user never gets stuck on a stale index.html/app.js
346
- // after upgrading aicli. index.html/app.js are also network-only in sw.js.
347
- if ('serviceWorker' in navigator) {
348
- let swReloaded = false;
349
- const reloadOnce = () => { if (!swReloaded) { swReloaded = true; location.reload(); } };
350
- navigator.serviceWorker.addEventListener('message', (e) => {
351
- if (e.data && e.data.type === 'sw-updated') reloadOnce();
352
- });
353
- navigator.serviceWorker.addEventListener('controllerchange', reloadOnce);
354
- navigator.serviceWorker.register('sw.js').then((reg) => {
355
- reg.update();
356
- }).catch(() => {});
357
- }
358
- </script>
342
+ <script src="templates.js"></script>
343
+ <script src="actions.js"></script>
359
344
  </body>
360
345
  </html>
@@ -16,7 +16,7 @@ const CACHE_NAME = 'aicli-v3';
16
16
  // HTML/JS that must always be fresh — never served from the SW cache, so an
17
17
  // upgraded server is reflected on the very next load. Other assets (vendored
18
18
  // CSS/JS, icons) are large + content-stable and fine to cache.
19
- const NEVER_CACHE = new Set(['/', '/index.html', '/app.js']);
19
+ const NEVER_CACHE = new Set(['/', '/index.html', '/app.js', '/templates.js', '/actions.js']);
20
20
 
21
21
  // App shell to precache (offline-capable startup). Deliberately EXCLUDES the
22
22
  // always-fresh HTML/JS in NEVER_CACHE — only large content-stable assets.