jinzd-ai-cli 0.4.205 → 0.4.207

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 (30) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-RBPABHS7.js → batch-6PORMJWT.js} +2 -2
  3. package/dist/{chunk-XIYSFN4U.js → chunk-2WV6AGHM.js} +1 -1
  4. package/dist/{chunk-43H3D2PK.js → chunk-BQIV4ZQT.js} +1 -1
  5. package/dist/{chunk-JVYH66N2.js → chunk-KJHMRAJU.js} +1 -1
  6. package/dist/{chunk-ZSC4YLEJ.js → chunk-KOU4KX7J.js} +2 -2
  7. package/dist/{chunk-UZISJ3KZ.js → chunk-QMXC327F.js} +3 -3
  8. package/dist/{chunk-3RET7PL7.js → chunk-QNUVZO5X.js} +1 -1
  9. package/dist/{chunk-43MWRT2C.js → chunk-QQYSZMET.js} +20 -17
  10. package/dist/{chunk-VOV2PWZU.js → chunk-SFCNFX42.js} +1 -1
  11. package/dist/{chunk-TZ7SNVSG.js → chunk-TW47X5AO.js} +1 -1
  12. package/dist/{chunk-SMFRJCXB.js → chunk-XPBEJB27.js} +8 -1
  13. package/dist/{ci-ABE74AXH.js → ci-AYMV66P3.js} +9 -9
  14. package/dist/{constants-GMQMJRIO.js → constants-NHGTSHKT.js} +1 -1
  15. package/dist/{doctor-cli-M5PPO3L3.js → doctor-cli-WKH7T4AW.js} +6 -6
  16. package/dist/electron-server.js +46 -41
  17. package/dist/{hub-NCU5JNCK.js → hub-6R3M3NTC.js} +1 -1
  18. package/dist/index.js +102 -98
  19. package/dist/{run-tests-ZFAEFEYK.js → run-tests-5F2OBWIK.js} +2 -2
  20. package/dist/{run-tests-FGRWBNVF.js → run-tests-NBCA4KEG.js} +1 -1
  21. package/dist/{server-RKP7JRRE.js → server-37EXC4EB.js} +5 -5
  22. package/dist/{server-ML6ZL3J5.js → server-L2MXWYF7.js} +26 -26
  23. package/dist/{task-orchestrator-LJP5XZSC.js → task-orchestrator-2DTDVCMR.js} +5 -5
  24. package/dist/{usage-YX2235W5.js → usage-SZWZFX3T.js} +2 -2
  25. package/dist/web/client/actions.js +84 -0
  26. package/dist/web/client/app.js +23 -205
  27. package/dist/web/client/index.html +27 -42
  28. package/dist/web/client/sw.js +1 -1
  29. package/dist/web/client/templates.js +188 -0
  30. package/package.json +1 -1
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-43MWRT2C.js";
4
+ } from "./chunk-QQYSZMET.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-VOV2PWZU.js";
7
+ import "./chunk-SFCNFX42.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-XIYSFN4U.js";
10
+ } from "./chunk-2WV6AGHM.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
14
- } from "./chunk-SMFRJCXB.js";
14
+ } from "./chunk-XPBEJB27.js";
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-3RET7PL7.js";
18
+ } from "./chunk-QNUVZO5X.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -19,7 +19,7 @@ import {
19
19
  loadDevState,
20
20
  persistToolRound,
21
21
  setupProxy
22
- } from "./chunk-ZSC4YLEJ.js";
22
+ } from "./chunk-KOU4KX7J.js";
23
23
  import {
24
24
  ToolExecutor,
25
25
  ToolRegistry,
@@ -38,10 +38,10 @@ import {
38
38
  spawnAgentContext,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-43MWRT2C.js";
41
+ } from "./chunk-QQYSZMET.js";
42
42
  import "./chunk-T2NL5ZIA.js";
43
43
  import "./chunk-BXP6YZ2P.js";
44
- import "./chunk-VOV2PWZU.js";
44
+ import "./chunk-SFCNFX42.js";
45
45
  import {
46
46
  SessionManager,
47
47
  getContentText
@@ -52,10 +52,10 @@ import {
52
52
  } from "./chunk-E44DTERW.js";
53
53
  import {
54
54
  ProviderRegistry
55
- } from "./chunk-UZISJ3KZ.js";
55
+ } from "./chunk-QMXC327F.js";
56
56
  import {
57
57
  runTool
58
- } from "./chunk-XIYSFN4U.js";
58
+ } from "./chunk-2WV6AGHM.js";
59
59
  import {
60
60
  CONTENT_ONLY_STREAM_REMINDER,
61
61
  TEE_FINAL_USER_NUDGE,
@@ -71,10 +71,10 @@ import {
71
71
  stripOuterCodeFence,
72
72
  stripToolCallReminder,
73
73
  teeStreamErrorSummary
74
- } from "./chunk-SMFRJCXB.js";
74
+ } from "./chunk-XPBEJB27.js";
75
75
  import {
76
76
  ConfigManager
77
- } from "./chunk-TZ7SNVSG.js";
77
+ } from "./chunk-TW47X5AO.js";
78
78
  import "./chunk-TZQHYZKT.js";
79
79
  import {
80
80
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -94,7 +94,7 @@ import {
94
94
  SKILLS_DIR_NAME,
95
95
  VERSION,
96
96
  buildUserIdentityPrompt
97
- } from "./chunk-3RET7PL7.js";
97
+ } from "./chunk-QNUVZO5X.js";
98
98
  import {
99
99
  formatGitContextForPrompt,
100
100
  getGitContext,
@@ -106,7 +106,9 @@ import "./chunk-KHYD3WXE.js";
106
106
  import "./chunk-SLSWPBK3.js";
107
107
  import "./chunk-O6UFCEUZ.js";
108
108
  import "./chunk-CKH4KQ4E.js";
109
- import "./chunk-IW3Q7AE5.js";
109
+ import {
110
+ atomicWriteFileSync
111
+ } from "./chunk-IW3Q7AE5.js";
110
112
 
111
113
  // src/web/server.ts
112
114
  import express from "express";
@@ -346,7 +348,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
346
348
  }
347
349
  }
348
350
  this.sendToolCallStart(call);
349
- if (this.sessionAutoApprove && (dangerLevel === "write" || dangerLevel === "destructive")) {
351
+ if (this.sessionAutoApprove && dangerLevel === "write") {
350
352
  try {
351
353
  const rawContent = await runTool(tool, call.arguments, call.name);
352
354
  const content = truncateOutput(rawContent, call.name);
@@ -1176,7 +1178,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
1176
1178
  try {
1177
1179
  mkdirSync(dirname(pendingTeeSave), { recursive: true });
1178
1180
  const bodyToSave = stripOuterCodeFence(content);
1179
- writeFileSync(pendingTeeSave, bodyToSave, "utf-8");
1181
+ atomicWriteFileSync(pendingTeeSave, bodyToSave);
1180
1182
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
1181
1183
  const lines = bodyToSave.split("\n").length;
1182
1184
  this.send({ type: "info", message: `\u2705 Saved (from reply): ${pendingTeeSave} (${lines} lines, ${bodyToSave.length} chars)` });
@@ -1351,7 +1353,7 @@ ${summaryContent}`,
1351
1353
  let summary;
1352
1354
  try {
1353
1355
  mkdirSync(dirname(saveToFile), { recursive: true });
1354
- fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
1356
+ fileStream = createWriteStream(saveToFile);
1355
1357
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
1356
1358
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
1357
1359
  const chatRequest = {
@@ -1391,7 +1393,7 @@ ${summaryContent}`,
1391
1393
  fullContent = "";
1392
1394
  } else {
1393
1395
  if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
1394
- writeFileSync(saveToFile, verdict.content, "utf-8");
1396
+ atomicWriteFileSync(saveToFile, verdict.content);
1395
1397
  fullContent = verdict.content;
1396
1398
  }
1397
1399
  summary = verdict.summary;
@@ -1843,7 +1845,7 @@ ${activated.meta.description || ""}` });
1843
1845
  this.send({ type: "info", message: "\u{1F512} Auto-approve disabled \u2014 confirmations restored for this session." });
1844
1846
  } else {
1845
1847
  this.toolExecutor.sessionAutoApprove = true;
1846
- this.send({ type: "info", message: "\u26A1 YOLO mode ON \u2014 all write/destructive tools auto-approved for this session.\nUse /yolo off to re-enable confirmations." });
1848
+ this.send({ type: "info", message: "\u26A1 YOLO mode ON \u2014 write tools auto-approved; destructive tools still require confirmation.\nUse /yolo off to re-enable write confirmations." });
1847
1849
  }
1848
1850
  break;
1849
1851
  }
@@ -2456,7 +2458,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2456
2458
  case "test": {
2457
2459
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2458
2460
  try {
2459
- const { executeTests } = await import("./run-tests-ZFAEFEYK.js");
2461
+ const { executeTests } = await import("./run-tests-5F2OBWIK.js");
2460
2462
  const argStr = args.join(" ").trim();
2461
2463
  let testArgs = {};
2462
2464
  if (argStr) {
@@ -3526,12 +3528,9 @@ async function startWebServer(options = {}) {
3526
3528
  "Content-Security-Policy",
3527
3529
  [
3528
3530
  "default-src 'self'",
3529
- // v0.4.162: the client UI is driven by inline onclick= handlers and
3530
- // inline <script> blocks (SW registration, theme init). 'self' alone
3531
- // silently blocked EVERY button (click → nothing, no JS error, just a
3532
- // CSP violation in the console). localhost dev/admin tool — the
3533
- // unsafe-inline XSS surface is acceptable under that threat model.
3534
- "script-src 'self' 'unsafe-inline'",
3531
+ // Inline event handlers and startup scripts live in app.js, so script
3532
+ // execution stays locked to same-origin files only.
3533
+ "script-src 'self'",
3535
3534
  "style-src 'self' 'unsafe-inline'",
3536
3535
  "img-src 'self' data: blob:",
3537
3536
  "font-src 'self' data:",
@@ -3891,7 +3890,7 @@ async function startWebServer(options = {}) {
3891
3890
  handlers.set(tabId, handler);
3892
3891
  clearPreAuthTimer();
3893
3892
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
3894
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
3893
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
3895
3894
  return;
3896
3895
  }
3897
3896
  if (action === "token") {
@@ -3907,7 +3906,7 @@ async function startWebServer(options = {}) {
3907
3906
  handlers.set(tabId, handler);
3908
3907
  clearPreAuthTimer();
3909
3908
  console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
3910
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: clientToken, username: verifiedUser }));
3909
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
3911
3910
  return;
3912
3911
  }
3913
3912
  if (action === "login") {
@@ -3922,7 +3921,7 @@ async function startWebServer(options = {}) {
3922
3921
  handlers.set(tabId, handler);
3923
3922
  clearPreAuthTimer();
3924
3923
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
3925
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
3924
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
3926
3925
  return;
3927
3926
  }
3928
3927
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -3948,9 +3947,10 @@ async function startWebServer(options = {}) {
3948
3947
  });
3949
3948
  ws.on("close", () => {
3950
3949
  clearPreAuthTimer();
3951
- console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size - 1})`);
3950
+ const wasTracked = handlers.has(tabId);
3952
3951
  if (handler) handler.onDisconnect();
3953
- handlers.delete(tabId);
3952
+ if (wasTracked) handlers.delete(tabId);
3953
+ console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size})`);
3954
3954
  });
3955
3955
  ws.on("error", (err) => {
3956
3956
  console.error(` WebSocket error: ${err.message}`);
@@ -3,21 +3,21 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-43MWRT2C.js";
6
+ } from "./chunk-QQYSZMET.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-VOV2PWZU.js";
9
+ import "./chunk-SFCNFX42.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-XIYSFN4U.js";
12
+ } from "./chunk-2WV6AGHM.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
16
- } from "./chunk-SMFRJCXB.js";
16
+ } from "./chunk-XPBEJB27.js";
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-3RET7PL7.js";
20
+ } from "./chunk-QNUVZO5X.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-TZ7SNVSG.js";
11
+ } from "./chunk-TW47X5AO.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-3RET7PL7.js";
13
+ import "./chunk-QNUVZO5X.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
- navigator.clipboard.writeText(block.textContent || '');
719
- btn.textContent = 'Copied';
720
- setTimeout(() => btn.textContent = '📋 Copy', 1500);
721
- };
718
+ btn.addEventListener('click', () => {
719
+ navigator.clipboard.writeText(block.textContent || '');
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 {}
@@ -2805,8 +2623,8 @@ async function handleAuth(event) {
2805
2623
  }
2806
2624
  }
2807
2625
 
2808
- // Legacy WS auth_result handler — kept for backwards-compat with clients/
2809
- // flows that still hit the WS auth path. Token field, if present, is ignored.
2626
+ // Legacy WS auth_result handler — kept for flows that still hit the WS auth path.
2627
+ // The server no longer echoes tokens; auth state is carried by httpOnly cookie.
2810
2628
  function handleAuthResult(msg) {
2811
2629
  const errorEl = document.getElementById('auth-error');
2812
2630
  const submitBtn = document.getElementById('auth-submit');
@@ -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
  }