claudeck 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/cli.js +2 -0
  4. package/config/agent-chains.json +16 -0
  5. package/config/agent-dags.json +16 -0
  6. package/config/agents.json +46 -0
  7. package/config/bot-prompt.json +3 -0
  8. package/config/folders.json +66 -0
  9. package/config/prompts.json +92 -0
  10. package/config/repos.json +86 -0
  11. package/config/telegram-config.json +17 -0
  12. package/config/workflows.json +90 -0
  13. package/db.js +1198 -0
  14. package/package.json +55 -0
  15. package/plugins/claude-editor/client.css +171 -0
  16. package/plugins/claude-editor/client.js +183 -0
  17. package/plugins/event-stream/client.css +207 -0
  18. package/plugins/event-stream/client.js +271 -0
  19. package/plugins/linear/client.css +345 -0
  20. package/plugins/linear/client.js +380 -0
  21. package/plugins/linear/config.json +5 -0
  22. package/plugins/linear/server.js +312 -0
  23. package/plugins/repos/client.css +549 -0
  24. package/plugins/repos/client.js +663 -0
  25. package/plugins/repos/server.js +232 -0
  26. package/plugins/sudoku/client.css +196 -0
  27. package/plugins/sudoku/client.js +329 -0
  28. package/plugins/tasks/client.css +414 -0
  29. package/plugins/tasks/client.js +394 -0
  30. package/plugins/tasks/server.js +116 -0
  31. package/plugins/tic-tac-toe/client.css +167 -0
  32. package/plugins/tic-tac-toe/client.js +241 -0
  33. package/public/css/core/components.css +232 -0
  34. package/public/css/core/layout.css +330 -0
  35. package/public/css/core/print.css +18 -0
  36. package/public/css/core/reset.css +36 -0
  37. package/public/css/core/responsive.css +378 -0
  38. package/public/css/core/theme.css +116 -0
  39. package/public/css/core/variables.css +93 -0
  40. package/public/css/features/agent-monitor.css +297 -0
  41. package/public/css/features/agent-sidebar.css +525 -0
  42. package/public/css/features/agents.css +996 -0
  43. package/public/css/features/analytics.css +181 -0
  44. package/public/css/features/background-sessions.css +321 -0
  45. package/public/css/features/cost-dashboard.css +168 -0
  46. package/public/css/features/home.css +313 -0
  47. package/public/css/features/retro-terminal.css +88 -0
  48. package/public/css/features/telegram.css +127 -0
  49. package/public/css/features/tour.css +148 -0
  50. package/public/css/features/voice-input.css +60 -0
  51. package/public/css/features/welcome.css +241 -0
  52. package/public/css/panels/assistant-bot.css +442 -0
  53. package/public/css/panels/dev-docs.css +292 -0
  54. package/public/css/panels/file-explorer.css +322 -0
  55. package/public/css/panels/git-panel.css +221 -0
  56. package/public/css/panels/mcp-manager.css +199 -0
  57. package/public/css/panels/tips-feed.css +353 -0
  58. package/public/css/ui/commands.css +273 -0
  59. package/public/css/ui/context-gauge.css +76 -0
  60. package/public/css/ui/file-picker.css +69 -0
  61. package/public/css/ui/image-attachments.css +106 -0
  62. package/public/css/ui/messages.css +884 -0
  63. package/public/css/ui/modals.css +122 -0
  64. package/public/css/ui/parallel.css +217 -0
  65. package/public/css/ui/permissions.css +110 -0
  66. package/public/css/ui/right-panel.css +481 -0
  67. package/public/css/ui/sessions.css +689 -0
  68. package/public/css/ui/status-bar.css +425 -0
  69. package/public/css/ui/toolbox.css +206 -0
  70. package/public/data/tips.json +218 -0
  71. package/public/icons/favicon.png +0 -0
  72. package/public/icons/icon-192.png +0 -0
  73. package/public/icons/icon-512.png +0 -0
  74. package/public/icons/whaly.png +0 -0
  75. package/public/index.html +1140 -0
  76. package/public/js/core/api.js +591 -0
  77. package/public/js/core/constants.js +3 -0
  78. package/public/js/core/dom.js +270 -0
  79. package/public/js/core/events.js +10 -0
  80. package/public/js/core/plugin-loader.js +153 -0
  81. package/public/js/core/store.js +39 -0
  82. package/public/js/core/utils.js +25 -0
  83. package/public/js/core/ws.js +64 -0
  84. package/public/js/features/agent-monitor.js +222 -0
  85. package/public/js/features/agents.js +1209 -0
  86. package/public/js/features/analytics.js +397 -0
  87. package/public/js/features/attachments.js +251 -0
  88. package/public/js/features/background-sessions.js +475 -0
  89. package/public/js/features/chat.js +589 -0
  90. package/public/js/features/cost-dashboard.js +152 -0
  91. package/public/js/features/dag-editor.js +399 -0
  92. package/public/js/features/easter-egg.js +46 -0
  93. package/public/js/features/home.js +270 -0
  94. package/public/js/features/projects.js +372 -0
  95. package/public/js/features/prompts.js +228 -0
  96. package/public/js/features/sessions.js +332 -0
  97. package/public/js/features/telegram.js +131 -0
  98. package/public/js/features/tour.js +210 -0
  99. package/public/js/features/voice-input.js +185 -0
  100. package/public/js/features/welcome.js +43 -0
  101. package/public/js/features/workflows.js +277 -0
  102. package/public/js/main.js +51 -0
  103. package/public/js/panels/assistant-bot.js +445 -0
  104. package/public/js/panels/dev-docs.js +380 -0
  105. package/public/js/panels/file-explorer.js +486 -0
  106. package/public/js/panels/git-panel.js +285 -0
  107. package/public/js/panels/mcp-manager.js +311 -0
  108. package/public/js/panels/tips-feed.js +303 -0
  109. package/public/js/ui/commands.js +114 -0
  110. package/public/js/ui/context-gauge.js +100 -0
  111. package/public/js/ui/diff.js +124 -0
  112. package/public/js/ui/disabled-tools.js +36 -0
  113. package/public/js/ui/export.js +74 -0
  114. package/public/js/ui/formatting.js +206 -0
  115. package/public/js/ui/header-dropdowns.js +72 -0
  116. package/public/js/ui/input-meta.js +71 -0
  117. package/public/js/ui/max-turns.js +21 -0
  118. package/public/js/ui/messages.js +387 -0
  119. package/public/js/ui/model-selector.js +20 -0
  120. package/public/js/ui/notifications.js +232 -0
  121. package/public/js/ui/parallel.js +176 -0
  122. package/public/js/ui/permissions.js +168 -0
  123. package/public/js/ui/right-panel.js +173 -0
  124. package/public/js/ui/shortcuts.js +143 -0
  125. package/public/js/ui/sidebar-toggle.js +29 -0
  126. package/public/js/ui/status-bar.js +172 -0
  127. package/public/js/ui/tab-sdk.js +623 -0
  128. package/public/js/ui/theme.js +38 -0
  129. package/public/manifest.json +13 -0
  130. package/public/offline.html +190 -0
  131. package/public/style.css +42 -0
  132. package/public/sw.js +91 -0
  133. package/server/agent-loop.js +385 -0
  134. package/server/dag-executor.js +265 -0
  135. package/server/orchestrator.js +514 -0
  136. package/server/paths.js +61 -0
  137. package/server/plugin-mount.js +56 -0
  138. package/server/push-sender.js +31 -0
  139. package/server/routes/agents.js +294 -0
  140. package/server/routes/bot.js +45 -0
  141. package/server/routes/exec.js +35 -0
  142. package/server/routes/files.js +218 -0
  143. package/server/routes/mcp.js +82 -0
  144. package/server/routes/messages.js +36 -0
  145. package/server/routes/notifications.js +37 -0
  146. package/server/routes/projects.js +207 -0
  147. package/server/routes/prompts.js +53 -0
  148. package/server/routes/sessions.js +103 -0
  149. package/server/routes/stats.js +143 -0
  150. package/server/routes/telegram.js +71 -0
  151. package/server/routes/tips.js +135 -0
  152. package/server/routes/workflows.js +81 -0
  153. package/server/summarizer.js +55 -0
  154. package/server/telegram-poller.js +205 -0
  155. package/server/telegram-sender.js +304 -0
  156. package/server/ws-handler.js +926 -0
  157. package/server.js +179 -0
@@ -0,0 +1,74 @@
1
+ // Export functionality
2
+ import { escapeHtml } from '../core/utils.js';
3
+ import { renderMarkdown } from './formatting.js';
4
+
5
+ export function exportAsMarkdown(msgs) {
6
+ let md = "# Chat Export\n\n";
7
+ msgs.forEach((m) => {
8
+ if (m.querySelector(".msg-user")) {
9
+ md += "## User\n" + m.textContent.trim() + "\n\n";
10
+ } else if (m.querySelector(".text-content")) {
11
+ md += "## Assistant\n" + (m.querySelector(".text-content").dataset.raw || m.textContent.trim()) + "\n\n";
12
+ } else if (m.querySelector(".tool-indicator")) {
13
+ const name = m.querySelector(".tool-name")?.textContent || "";
14
+ md += "> Tool: " + name + "\n\n";
15
+ }
16
+ });
17
+ const blob = new Blob([md], { type: "text/markdown" });
18
+ const a = document.createElement("a");
19
+ a.href = URL.createObjectURL(blob);
20
+ a.download = `chat-export-${Date.now()}.md`;
21
+ a.click();
22
+ URL.revokeObjectURL(a.href);
23
+ }
24
+
25
+ export function exportAsHtml(msgs) {
26
+ let body = "";
27
+ msgs.forEach((m) => {
28
+ if (m.querySelector(".msg-user")) {
29
+ body += `<div class="msg msg-user">${escapeHtml(m.textContent.trim())}</div>\n`;
30
+ } else if (m.querySelector(".text-content")) {
31
+ const raw = m.querySelector(".text-content").dataset.raw || m.textContent.trim();
32
+ body += `<div class="msg msg-assistant"><div class="text-content">${renderMarkdown(raw)}</div></div>\n`;
33
+ } else if (m.querySelector(".tool-indicator")) {
34
+ const name = m.querySelector(".tool-name")?.textContent || "";
35
+ body += `<div class="msg tool-use">Tool: ${escapeHtml(name)}</div>\n`;
36
+ }
37
+ });
38
+
39
+ const html = `<!DOCTYPE html>
40
+ <html lang="en">
41
+ <head>
42
+ <meta charset="UTF-8">
43
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
44
+ <title>Chat Export — Claudeck</title>
45
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
46
+ <style>
47
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; background: #0d1117; color: #e6edf3; max-width: 820px; margin: 0 auto; padding: 24px; }
48
+ .msg { margin-bottom: 14px; }
49
+ .msg-user { background: rgba(31, 111, 235, 0.13); border: 1px solid rgba(31, 111, 235, 0.27); border-radius: 8px; padding: 12px 16px; font-size: 14px; line-height: 1.6; white-space: pre-wrap; }
50
+ .msg-assistant { font-size: 14px; line-height: 1.7; }
51
+ .text-content { white-space: pre-wrap; word-wrap: break-word; }
52
+ .text-content code { font-family: "SF Mono", "Fira Code", monospace; font-size: 13px; background: #1c2128; padding: 2px 6px; border-radius: 4px; }
53
+ .text-content pre { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px 16px; overflow-x: auto; margin: 8px 0; }
54
+ .text-content pre code { background: none; padding: 0; }
55
+ .tool-use { font-family: "SF Mono", monospace; font-size: 12px; color: #8b949e; padding: 4px 0; }
56
+ h1, h2, h3 { color: #e6edf3; }
57
+ strong { font-weight: 600; }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <h1>Chat Export</h1>
62
+ ${body}
63
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"><\/script>
64
+ <script>hljs.highlightAll();<\/script>
65
+ </body>
66
+ </html>`;
67
+
68
+ const blob = new Blob([html], { type: "text/html" });
69
+ const a = document.createElement("a");
70
+ a.href = URL.createObjectURL(blob);
71
+ a.download = `chat-export-${Date.now()}.html`;
72
+ a.click();
73
+ URL.revokeObjectURL(a.href);
74
+ }
@@ -0,0 +1,206 @@
1
+ // Markdown rendering + code highlighting + mermaid
2
+ import { escapeHtml } from '../core/utils.js';
3
+ import { getState, setState } from '../core/store.js';
4
+
5
+ // Language display names for the code block header
6
+ const LANG_LABELS = {
7
+ js: "JavaScript", javascript: "JavaScript", ts: "TypeScript", typescript: "TypeScript",
8
+ py: "Python", python: "Python", rb: "Ruby", ruby: "Ruby",
9
+ go: "Go", rust: "Rust", rs: "Rust", java: "Java", c: "C", cpp: "C++",
10
+ cs: "C#", csharp: "C#", swift: "Swift", kt: "Kotlin", kotlin: "Kotlin",
11
+ php: "PHP", sh: "Shell", bash: "Bash", zsh: "Zsh", fish: "Fish",
12
+ sql: "SQL", html: "HTML", css: "CSS", scss: "SCSS", less: "LESS",
13
+ json: "JSON", yaml: "YAML", yml: "YAML", toml: "TOML", xml: "XML",
14
+ md: "Markdown", markdown: "Markdown", txt: "Text", plaintext: "Text",
15
+ jsx: "JSX", tsx: "TSX", vue: "Vue", svelte: "Svelte",
16
+ dockerfile: "Dockerfile", docker: "Dockerfile", makefile: "Makefile",
17
+ graphql: "GraphQL", gql: "GraphQL", lua: "Lua", r: "R",
18
+ perl: "Perl", scala: "Scala", elixir: "Elixir", ex: "Elixir",
19
+ clojure: "Clojure", clj: "Clojure", haskell: "Haskell", hs: "Haskell",
20
+ ocaml: "OCaml", ml: "OCaml", erlang: "Erlang", dart: "Dart",
21
+ powershell: "PowerShell", ps1: "PowerShell", ini: "INI", conf: "Config",
22
+ diff: "Diff", patch: "Diff", mermaid: "Mermaid", proto: "Protobuf",
23
+ terraform: "Terraform", tf: "Terraform", hcl: "HCL", nginx: "Nginx",
24
+ };
25
+
26
+ function getLangLabel(lang) {
27
+ if (!lang) return "";
28
+ return LANG_LABELS[lang.toLowerCase()] || lang.toUpperCase();
29
+ }
30
+
31
+ export function renderMarkdown(text) {
32
+ let html = escapeHtml(text);
33
+
34
+ // ── Code blocks — with language header ──
35
+ html = html.replace(
36
+ /```(\w*)\n([\s\S]*?)```/g,
37
+ (_, lang, code) => {
38
+ const langClass = lang ? `language-${lang}` : "";
39
+ const label = getLangLabel(lang);
40
+ const headerHtml = label
41
+ ? `<div class="code-block-header"><span class="code-lang-label">${escapeHtml(label)}</span></div>`
42
+ : "";
43
+ return `<div class="code-block-wrapper">${headerHtml}<pre><code class="${langClass}" data-lang="${lang}">${code}</code></pre></div>`;
44
+ }
45
+ );
46
+
47
+ // ── Inline code ──
48
+ html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
49
+
50
+ // ── Bold + Italic combined ──
51
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
52
+
53
+ // ── Bold ──
54
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
55
+
56
+ // ── Italic ──
57
+ html = html.replace(/(?<!\*)\*([^*\n]+?)\*(?!\*)/g, "<em>$1</em>");
58
+
59
+ // ── Strikethrough ──
60
+ html = html.replace(/~~(.+?)~~/g, "<del>$1</del>");
61
+
62
+ // ── Headers ──
63
+ html = html.replace(/^#### (.+)$/gm, '<h4 class="md-h4">$1</h4>');
64
+ html = html.replace(/^### (.+)$/gm, '<h3 class="md-h3">$1</h3>');
65
+ html = html.replace(/^## (.+)$/gm, '<h2 class="md-h2">$1</h2>');
66
+ html = html.replace(/^# (.+)$/gm, '<h1 class="md-h1">$1</h1>');
67
+
68
+ // ── Horizontal rules ──
69
+ html = html.replace(/^---+$/gm, '<hr class="md-hr">');
70
+
71
+ // ── Blockquotes ──
72
+ // Match consecutive lines starting with >
73
+ html = html.replace(/(?:^&gt; (.*)$\n?)+/gm, (match) => {
74
+ const lines = match.trim().split("\n").map(l => l.replace(/^&gt; ?/, "")).join("<br>");
75
+ return `<blockquote class="md-blockquote">${lines}</blockquote>\n`;
76
+ });
77
+
78
+ // ── Links ──
79
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="md-link" target="_blank" rel="noopener">$1</a>');
80
+
81
+ // ── Tables ──
82
+ // Match table blocks: header row, separator row, then data rows
83
+ html = html.replace(
84
+ /(?:^\|(.+)\|$\n^\|[-| :]+\|$\n(?:^\|(.+)\|$\n?)*)/gm,
85
+ (match) => {
86
+ const rows = match.trim().split("\n");
87
+ if (rows.length < 2) return match;
88
+
89
+ const parseRow = (row) =>
90
+ row.split("|").filter((_, i, arr) => i > 0 && i < arr.length - 1).map(c => c.trim());
91
+
92
+ // Parse alignment from separator row
93
+ const sepCells = parseRow(rows[1]);
94
+ const aligns = sepCells.map(c => {
95
+ if (c.startsWith(":") && c.endsWith(":")) return "center";
96
+ if (c.endsWith(":")) return "right";
97
+ return "left";
98
+ });
99
+
100
+ const headerCells = parseRow(rows[0]);
101
+ let tableHtml = '<div class="md-table-wrap"><table class="md-table"><thead><tr>';
102
+ headerCells.forEach((cell, i) => {
103
+ tableHtml += `<th style="text-align:${aligns[i] || "left"}">${cell}</th>`;
104
+ });
105
+ tableHtml += "</tr></thead><tbody>";
106
+
107
+ for (let r = 2; r < rows.length; r++) {
108
+ const cells = parseRow(rows[r]);
109
+ tableHtml += "<tr>";
110
+ cells.forEach((cell, i) => {
111
+ tableHtml += `<td style="text-align:${aligns[i] || "left"}">${cell}</td>`;
112
+ });
113
+ tableHtml += "</tr>";
114
+ }
115
+
116
+ tableHtml += "</tbody></table></div>";
117
+ return tableHtml;
118
+ }
119
+ );
120
+
121
+ // ── Ordered lists ──
122
+ // Match consecutive lines starting with digits followed by . or )
123
+ html = html.replace(/(?:^\d+[.)]\s+.+$\n?)+/gm, (match) => {
124
+ const items = match.trim().split("\n").map(l => l.replace(/^\d+[.)]\s+/, ""));
125
+ return '<ol class="md-list md-ol">' + items.map(i => `<li>${i}</li>`).join("") + "</ol>\n";
126
+ });
127
+
128
+ // ── Unordered lists ──
129
+ // Match consecutive lines starting with -, *, or +
130
+ html = html.replace(/(?:^[-*+]\s+.+$\n?)+/gm, (match) => {
131
+ const items = match.trim().split("\n").map(l => l.replace(/^[-*+]\s+/, ""));
132
+ return '<ul class="md-list md-ul">' + items.map(i => `<li>${i}</li>`).join("") + "</ul>\n";
133
+ });
134
+
135
+ // ── Line breaks ──
136
+ html = html.replace(/\n/g, "<br>");
137
+
138
+ return html;
139
+ }
140
+
141
+ export function highlightCodeBlocks(container) {
142
+ if (typeof hljs === "undefined") return;
143
+ container.querySelectorAll("pre code").forEach((block) => {
144
+ if (block.dataset.highlighted === "yes") return;
145
+ try {
146
+ // Highlight both language-tagged and untagged blocks (auto-detect)
147
+ hljs.highlightElement(block);
148
+ } catch { /* ignore unsupported languages */ }
149
+ });
150
+ }
151
+
152
+ export function addCopyButtons(container) {
153
+ container.querySelectorAll(".code-block-wrapper").forEach((wrapper) => {
154
+ if (wrapper.querySelector(".code-copy-btn")) return;
155
+ const btn = document.createElement("button");
156
+ btn.className = "code-copy-btn";
157
+ btn.textContent = "Copy";
158
+ btn.addEventListener("click", (e) => {
159
+ e.stopPropagation();
160
+ const code = wrapper.querySelector("code");
161
+ if (code) {
162
+ navigator.clipboard.writeText(code.textContent).then(() => {
163
+ btn.textContent = "Copied!";
164
+ btn.classList.add("copied");
165
+ setTimeout(() => {
166
+ btn.textContent = "Copy";
167
+ btn.classList.remove("copied");
168
+ }, 2000);
169
+ });
170
+ }
171
+ });
172
+ // Place copy button inside the header if it exists, otherwise in wrapper
173
+ const header = wrapper.querySelector(".code-block-header");
174
+ if (header) {
175
+ header.appendChild(btn);
176
+ } else {
177
+ wrapper.appendChild(btn);
178
+ }
179
+ });
180
+ }
181
+
182
+ export function renderMermaidBlocks(container) {
183
+ if (typeof mermaid === "undefined") return;
184
+ container.querySelectorAll('.code-block-wrapper code[data-lang="mermaid"]').forEach((block) => {
185
+ const wrapper = block.closest(".code-block-wrapper");
186
+ if (!wrapper || wrapper.dataset.mermaidRendered) return;
187
+ wrapper.dataset.mermaidRendered = "true";
188
+
189
+ const source = block.textContent;
190
+ let counter = getState("mermaidCounter") + 1;
191
+ setState("mermaidCounter", counter);
192
+ const id = `mermaid-${counter}`;
193
+ try {
194
+ mermaid.render(id, source).then(({ svg }) => {
195
+ const div = document.createElement("div");
196
+ div.className = "mermaid-container";
197
+ div.innerHTML = svg;
198
+ wrapper.replaceWith(div);
199
+ }).catch(() => {
200
+ // Leave original code block on error
201
+ });
202
+ } catch {
203
+ // Sync error — leave code block
204
+ }
205
+ });
206
+ }
@@ -0,0 +1,72 @@
1
+ // Header dropdown menus with multi-level submenus
2
+
3
+ // Toggle dropdown open/close
4
+ document.querySelectorAll(".header-dropdown-trigger").forEach((trigger) => {
5
+ trigger.addEventListener("click", (e) => {
6
+ e.stopPropagation();
7
+ const dropdown = trigger.closest(".header-dropdown");
8
+ const wasOpen = dropdown.classList.contains("open");
9
+
10
+ // Close all dropdowns
11
+ document.querySelectorAll(".header-dropdown.open").forEach((d) => d.classList.remove("open"));
12
+
13
+ if (!wasOpen) dropdown.classList.add("open");
14
+ });
15
+ });
16
+
17
+ // Close dropdowns on outside click
18
+ document.addEventListener("click", () => {
19
+ document.querySelectorAll(".header-dropdown.open").forEach((d) => d.classList.remove("open"));
20
+ });
21
+
22
+ // Prevent menu clicks from closing the dropdown (except submenu item clicks)
23
+ document.querySelectorAll(".header-dropdown-menu").forEach((menu) => {
24
+ menu.addEventListener("click", (e) => {
25
+ if (!e.target.closest(".header-submenu-item") && !e.target.closest(".header-dropdown-item:not(.has-submenu)")) {
26
+ e.stopPropagation();
27
+ }
28
+ });
29
+ });
30
+
31
+ // Submenu item selection — sync with hidden <select> elements
32
+ document.querySelectorAll(".header-submenu-item").forEach((item) => {
33
+ item.addEventListener("click", () => {
34
+ const targetId = item.dataset.target;
35
+ const value = item.dataset.value;
36
+ const select = document.getElementById(targetId);
37
+ if (!select) return;
38
+
39
+ // Update hidden select and fire change event
40
+ select.value = value;
41
+ select.dispatchEvent(new Event("change", { bubbles: true }));
42
+
43
+ // Update active state in submenu
44
+ const submenu = item.closest(".header-submenu");
45
+ submenu.querySelectorAll(".header-submenu-item").forEach((s) => s.classList.remove("active"));
46
+ item.classList.add("active");
47
+
48
+ // Update display value
49
+ const parent = item.closest(".header-dropdown-item");
50
+ const display = parent.querySelector(".header-dropdown-item-value");
51
+ if (display) display.textContent = item.textContent.trim();
52
+
53
+ // Close dropdown
54
+ document.querySelectorAll(".header-dropdown.open").forEach((d) => d.classList.remove("open"));
55
+ });
56
+ });
57
+
58
+ // Tools dropdown items — close menu after click
59
+ document.querySelectorAll(".header-dropdown-item:not(.has-submenu)").forEach((item) => {
60
+ if (item.id) {
61
+ item.addEventListener("click", () => {
62
+ document.querySelectorAll(".header-dropdown.open").forEach((d) => d.classList.remove("open"));
63
+ });
64
+ }
65
+ });
66
+
67
+ // Close on Escape
68
+ document.addEventListener("keydown", (e) => {
69
+ if (e.key === "Escape") {
70
+ document.querySelectorAll(".header-dropdown.open").forEach((d) => d.classList.remove("open"));
71
+ }
72
+ });
@@ -0,0 +1,71 @@
1
+ // Input meta labels — show model, permissions, max turns below chat input
2
+ import { $ } from '../core/dom.js';
3
+
4
+ const elModel = document.getElementById("input-meta-model");
5
+ const elPerm = document.getElementById("input-meta-perm");
6
+ const elTurns = document.getElementById("input-meta-turns");
7
+
8
+ const permLabels = {
9
+ bypass: "bypass",
10
+ confirmDangerous: "confirm dangerous",
11
+ confirmAll: "confirm all",
12
+ plan: "plan only",
13
+ };
14
+
15
+ function updateModel() {
16
+ if (!elModel) return;
17
+ const val = $.modelSelect?.value || "";
18
+ elModel.textContent = val || "default model";
19
+ }
20
+
21
+ function updatePerm() {
22
+ if (!elPerm) return;
23
+ const val = $.permModeSelect?.value || "confirmDangerous";
24
+ elPerm.textContent = permLabels[val] || val;
25
+ }
26
+
27
+ function updateTurns() {
28
+ if (!elTurns) return;
29
+ const val = $.maxTurnsSelect?.value || "30";
30
+ elTurns.textContent = val === "0" ? "unlimited turns" : `${val} turns`;
31
+ }
32
+
33
+ // Also watch header dropdown display elements (used when dropdowns replace <select>)
34
+ function observeDisplay(id, fn) {
35
+ const el = document.getElementById(id);
36
+ if (el) {
37
+ new MutationObserver(fn).observe(el, { childList: true, characterData: true, subtree: true });
38
+ }
39
+ }
40
+
41
+ $.modelSelect?.addEventListener("change", updateModel);
42
+ $.permModeSelect?.addEventListener("change", updatePerm);
43
+ $.maxTurnsSelect?.addEventListener("change", updateTurns);
44
+
45
+ observeDisplay("model-display", updateModel);
46
+ observeDisplay("perm-mode-display", updatePerm);
47
+ observeDisplay("max-turns-display", updateTurns);
48
+
49
+ updateModel();
50
+ updatePerm();
51
+ updateTurns();
52
+
53
+ // Make shortcut kbd hints clickable — dispatch the matching keyboard shortcut
54
+ document.querySelectorAll(".input-meta-kbd").forEach((kbd) => {
55
+ kbd.style.cursor = "pointer";
56
+ kbd.addEventListener("click", () => {
57
+ const text = kbd.textContent.trim();
58
+ let key = "";
59
+ if (text === "\u2318B") key = "b";
60
+ else if (text === "\u2318N") key = "n";
61
+ else if (text === "\u2318K") key = "k";
62
+ else if (text === "\u2318/") key = "/";
63
+ if (key) {
64
+ document.dispatchEvent(new KeyboardEvent("keydown", {
65
+ key,
66
+ metaKey: true,
67
+ bubbles: true,
68
+ }));
69
+ }
70
+ });
71
+ });
@@ -0,0 +1,21 @@
1
+ // Max turns selector — localStorage persistence + getter
2
+ import { $ } from '../core/dom.js';
3
+
4
+ const STORAGE_KEY = 'claudeck-max-turns';
5
+
6
+ export function getMaxTurns() {
7
+ const val = parseInt($.maxTurnsSelect?.value, 10);
8
+ return val || 0; // 0 = unlimited
9
+ }
10
+
11
+ function init() {
12
+ const saved = localStorage.getItem(STORAGE_KEY);
13
+ if (saved && $.maxTurnsSelect) {
14
+ $.maxTurnsSelect.value = saved;
15
+ }
16
+ $.maxTurnsSelect?.addEventListener('change', () => {
17
+ localStorage.setItem(STORAGE_KEY, $.maxTurnsSelect.value);
18
+ });
19
+ }
20
+
21
+ init();