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,303 @@
1
+ // Tips Feed Panel
2
+ import { $ } from '../core/dom.js';
3
+ import { getState, on } from '../core/store.js';
4
+ import { registerCommand } from '../ui/commands.js';
5
+ import { fetchTips, fetchRssFeed } from '../core/api.js';
6
+
7
+ const LS_OPEN = 'claudeck-tips-feed';
8
+ const LS_CATEGORY = 'claudeck-tips-category';
9
+ const LS_WIDTH = 'claudeck-tips-width';
10
+
11
+ let tipsData = null;
12
+ let rssItems = {}; // feedId -> items[]
13
+ let activeCategory = localStorage.getItem(LS_CATEGORY) || 'all';
14
+
15
+ // ── Toggle ──────────────────────────────────────────────
16
+ export function toggleTipsFeed() {
17
+ if (getState('parallelMode')) return;
18
+ const panel = $.tipsFeedPanel;
19
+ if (!panel) return;
20
+ const isHidden = panel.classList.contains('hidden');
21
+ if (isHidden) {
22
+ openTipsFeed();
23
+ } else {
24
+ closeTipsFeed();
25
+ }
26
+ }
27
+
28
+ function openTipsFeed() {
29
+ if (getState('parallelMode')) return;
30
+ const panel = $.tipsFeedPanel;
31
+ if (!panel) return;
32
+ panel.classList.remove('hidden');
33
+ localStorage.setItem(LS_OPEN, '1');
34
+ if ($.tipsFeedToggleBtn) $.tipsFeedToggleBtn.classList.add('active');
35
+
36
+ // Restore width
37
+ const savedWidth = localStorage.getItem(LS_WIDTH);
38
+ if (savedWidth) panel.style.width = savedWidth + 'px';
39
+
40
+ // Load data if not loaded
41
+ if (!tipsData) loadTipsData();
42
+ }
43
+
44
+ function closeTipsFeed() {
45
+ const panel = $.tipsFeedPanel;
46
+ if (!panel) return;
47
+ panel.classList.add('hidden');
48
+ localStorage.setItem(LS_OPEN, '0');
49
+ if ($.tipsFeedToggleBtn) $.tipsFeedToggleBtn.classList.remove('active');
50
+ }
51
+
52
+ // ── Data Loading ────────────────────────────────────────
53
+ async function loadTipsData() {
54
+ const content = $.tipsFeedContent;
55
+ if (!content) return;
56
+ content.innerHTML = '<div class="tips-feed-loading">Loading tips...</div>';
57
+
58
+ try {
59
+ const [tipsResult, ...rssResults] = await Promise.allSettled([
60
+ fetchTips(),
61
+ // RSS feeds will be loaded after we know the feed URLs
62
+ ]);
63
+
64
+ if (tipsResult.status === 'fulfilled') {
65
+ tipsData = tipsResult.value;
66
+ // Now load RSS feeds
67
+ if (tipsData.feeds) {
68
+ const rssPromises = tipsData.feeds.map(feed =>
69
+ fetchRssFeed(feed.url).then(data => ({ feedId: feed.id, feed, items: data.items || [] }))
70
+ );
71
+ const rssSettled = await Promise.allSettled(rssPromises);
72
+ rssSettled.forEach(r => {
73
+ if (r.status === 'fulfilled') {
74
+ rssItems[r.value.feedId] = { feed: r.value.feed, items: r.value.items };
75
+ }
76
+ });
77
+ }
78
+ }
79
+
80
+ renderFeed();
81
+ } catch {
82
+ content.innerHTML = '<div class="tips-feed-loading">Failed to load tips</div>';
83
+ }
84
+ }
85
+
86
+ // ── Rendering ───────────────────────────────────────────
87
+ function renderFeed() {
88
+ const content = $.tipsFeedContent;
89
+ if (!content || !tipsData) return;
90
+
91
+ content.innerHTML = '';
92
+
93
+ // Tip of the day
94
+ const totd = getTipOfTheDay();
95
+ if (totd && (activeCategory === 'all' || activeCategory === totd.category)) {
96
+ content.appendChild(renderTotd(totd));
97
+ }
98
+
99
+ // Category tabs
100
+ renderTabs();
101
+
102
+ // Filter tips
103
+ const tips = activeCategory === 'all'
104
+ ? tipsData.tips
105
+ : tipsData.tips.filter(t => t.category === activeCategory);
106
+
107
+ // Section: Curated Tips
108
+ if (tips.length > 0) {
109
+ const label = document.createElement('div');
110
+ label.className = 'tips-feed-section-label';
111
+ label.textContent = 'Curated Tips';
112
+ content.appendChild(label);
113
+
114
+ tips.forEach(tip => {
115
+ if (totd && tip.id === totd.id) return; // skip totd duplicate
116
+ content.appendChild(renderTipCard(tip));
117
+ });
118
+ }
119
+
120
+ // Section: RSS Feed Items
121
+ const rssEntries = Object.values(rssItems).filter(r =>
122
+ activeCategory === 'all' || r.feed.category === activeCategory
123
+ );
124
+
125
+ if (rssEntries.length > 0) {
126
+ const label = document.createElement('div');
127
+ label.className = 'tips-feed-section-label';
128
+ label.textContent = 'From the Web';
129
+ content.appendChild(label);
130
+
131
+ rssEntries.forEach(({ feed, items }) => {
132
+ items.slice(0, 5).forEach(item => {
133
+ content.appendChild(renderRssCard(item, feed));
134
+ });
135
+ });
136
+ }
137
+ }
138
+
139
+ function getTipOfTheDay() {
140
+ if (!tipsData || !tipsData.tips.length) return null;
141
+ const now = new Date();
142
+ const dayOfYear = Math.floor((now - new Date(now.getFullYear(), 0, 0)) / 86400000);
143
+ return tipsData.tips[dayOfYear % tipsData.tips.length];
144
+ }
145
+
146
+ function renderTotd(tip) {
147
+ const el = document.createElement('div');
148
+ el.className = 'tips-feed-totd';
149
+ const cat = tipsData.categories[tip.category];
150
+ el.innerHTML = `
151
+ <div class="tips-feed-totd-label">
152
+ <span>${cat ? cat.icon : '>'}</span> tip of the day
153
+ </div>
154
+ <div class="tips-feed-totd-title">${escapeHtml(tip.title)}</div>
155
+ <div class="tips-feed-totd-body">${escapeHtml(tip.body)}</div>
156
+ ${renderExtLink(tip.source, 'Reference')}
157
+ `;
158
+ return el;
159
+ }
160
+
161
+ function renderTipCard(tip) {
162
+ const el = document.createElement('div');
163
+ el.className = 'tips-feed-card';
164
+ const cat = tipsData.categories[tip.category];
165
+ const iconColor = cat ? cat.color : 'var(--text-dim)';
166
+ el.innerHTML = `
167
+ <div class="tips-feed-card-header">
168
+ <span class="tips-feed-card-icon" style="color:${iconColor}">${cat ? cat.icon : '?'}</span>
169
+ <span class="tips-feed-card-title">${escapeHtml(tip.title)}</span>
170
+ </div>
171
+ <div class="tips-feed-card-body">${escapeHtml(tip.body)}</div>
172
+ ${tip.tags ? `<div class="tips-feed-card-tags">${tip.tags.map(t => `<span class="tips-feed-card-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
173
+ ${renderExtLink(tip.source, 'Reference')}
174
+ `;
175
+ return el;
176
+ }
177
+
178
+ function renderRssCard(item, feed) {
179
+ const el = document.createElement('div');
180
+ el.className = 'tips-feed-rss-card';
181
+ el.innerHTML = `
182
+ <div class="tips-feed-rss-source">${escapeHtml(feed.name)}</div>
183
+ <div class="tips-feed-rss-title">${escapeHtml(item.title)}</div>
184
+ ${item.description ? `<div class="tips-feed-rss-desc">${escapeHtml(item.description)}</div>` : ''}
185
+ ${renderExtLink(item.link, 'Read more')}
186
+ `;
187
+ return el;
188
+ }
189
+
190
+ function renderTabs() {
191
+ const tabsContainer = document.querySelector('.tips-feed-tabs');
192
+ if (!tabsContainer || !tipsData) return;
193
+
194
+ tabsContainer.innerHTML = '';
195
+
196
+ // "All" tab
197
+ const allTab = document.createElement('button');
198
+ allTab.className = 'tips-feed-tab' + (activeCategory === 'all' ? ' active' : '');
199
+ allTab.textContent = 'All';
200
+ allTab.addEventListener('click', () => setCategory('all'));
201
+ tabsContainer.appendChild(allTab);
202
+
203
+ // Category tabs
204
+ Object.entries(tipsData.categories).forEach(([key, cat]) => {
205
+ const tab = document.createElement('button');
206
+ tab.className = 'tips-feed-tab' + (activeCategory === key ? ' active' : '');
207
+ tab.innerHTML = `<span class="tips-feed-tab-icon">${cat.icon}</span>${cat.label}`;
208
+ tab.addEventListener('click', () => setCategory(key));
209
+ tabsContainer.appendChild(tab);
210
+ });
211
+ }
212
+
213
+ function setCategory(cat) {
214
+ activeCategory = cat;
215
+ localStorage.setItem(LS_CATEGORY, cat);
216
+ renderFeed();
217
+ }
218
+
219
+ // ── Resize ──────────────────────────────────────────────
220
+ function initResize() {
221
+ const handle = $.tipsFeedResize;
222
+ const panel = $.tipsFeedPanel;
223
+ if (!handle || !panel) return;
224
+
225
+ let startX, startWidth;
226
+
227
+ handle.addEventListener('mousedown', (e) => {
228
+ e.preventDefault();
229
+ startX = e.clientX;
230
+ startWidth = panel.offsetWidth;
231
+ handle.classList.add('dragging');
232
+ document.addEventListener('mousemove', onMouseMove);
233
+ document.addEventListener('mouseup', onMouseUp);
234
+ });
235
+
236
+ function onMouseMove(e) {
237
+ // Dragging left edge: increasing width means mouse moves left
238
+ const diff = startX - e.clientX;
239
+ const newWidth = Math.min(Math.max(startWidth + diff, 260), 600);
240
+ panel.style.width = newWidth + 'px';
241
+ }
242
+
243
+ function onMouseUp() {
244
+ handle.classList.remove('dragging');
245
+ document.removeEventListener('mousemove', onMouseMove);
246
+ document.removeEventListener('mouseup', onMouseUp);
247
+ localStorage.setItem(LS_WIDTH, panel.offsetWidth);
248
+ }
249
+ }
250
+
251
+ // ── Parallel Mode Safety ────────────────────────────────
252
+ on('parallelMode', (isParallel) => {
253
+ if (isParallel) {
254
+ closeTipsFeed();
255
+ if ($.tipsFeedToggleBtn) $.tipsFeedToggleBtn.disabled = true;
256
+ } else {
257
+ if ($.tipsFeedToggleBtn) $.tipsFeedToggleBtn.disabled = false;
258
+ }
259
+ });
260
+
261
+ // ── Helpers ─────────────────────────────────────────────
262
+ function escapeHtml(str) {
263
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
264
+ }
265
+
266
+ const extLinkIcon = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
267
+
268
+ function renderExtLink(url, label) {
269
+ if (!url) return '';
270
+ return `<a class="tips-feed-ext-link" href="${escapeHtml(url)}" target="_blank" rel="noopener">${extLinkIcon} ${escapeHtml(label || 'Source')}</a>`;
271
+ }
272
+
273
+ // ── Init ────────────────────────────────────────────────
274
+ function init() {
275
+ // Toggle button
276
+ if ($.tipsFeedToggleBtn) {
277
+ $.tipsFeedToggleBtn.addEventListener('click', toggleTipsFeed);
278
+ }
279
+
280
+ // Close button
281
+ if ($.tipsFeedClose) {
282
+ $.tipsFeedClose.addEventListener('click', closeTipsFeed);
283
+ }
284
+
285
+ // Resize
286
+ initResize();
287
+
288
+ // Restore state
289
+ if (localStorage.getItem(LS_OPEN) === '1' && !getState('parallelMode')) {
290
+ openTipsFeed();
291
+ }
292
+ }
293
+
294
+ init();
295
+
296
+ // Register /tips slash command
297
+ registerCommand('tips', {
298
+ category: 'app',
299
+ description: 'Toggle tips feed panel',
300
+ execute() {
301
+ toggleTipsFeed();
302
+ },
303
+ });
@@ -0,0 +1,114 @@
1
+ // Slash command system
2
+ import { escapeHtml } from '../core/utils.js';
3
+ import { AUTOCOMPLETE_LIMIT } from '../core/constants.js';
4
+
5
+ export const commandRegistry = {};
6
+
7
+ export function registerCommand(name, def) {
8
+ commandRegistry[name] = def;
9
+ }
10
+
11
+ export function handleSlashAutocomplete(pane) {
12
+ const text = pane.messageInput.value;
13
+ const el = pane.autocompleteEl;
14
+ if (!el) return;
15
+
16
+ if (!text.startsWith("/") || text.includes(" ")) {
17
+ dismissAutocomplete(pane);
18
+ return;
19
+ }
20
+
21
+ const partial = text.slice(1).toLowerCase();
22
+ const matches = Object.entries(commandRegistry)
23
+ .filter(([name]) => name.startsWith(partial))
24
+ .sort((a, b) => {
25
+ const catOrder = { project: 0, skill: 1, agent: 2, app: 3, cli: 4, workflow: 5, prompt: 6 };
26
+ const ca = catOrder[a[1].category] ?? 3;
27
+ const cb = catOrder[b[1].category] ?? 3;
28
+ return ca - cb || a[0].localeCompare(b[0]);
29
+ })
30
+ .slice(0, AUTOCOMPLETE_LIMIT);
31
+
32
+ if (matches.length === 0) {
33
+ dismissAutocomplete(pane);
34
+ return;
35
+ }
36
+
37
+ el.innerHTML = "";
38
+ matches.forEach(([name, cmd], i) => {
39
+ const item = document.createElement("div");
40
+ item.className = "slash-autocomplete-item" + (i === 0 ? " active" : "");
41
+ item.dataset.index = i;
42
+ item.dataset.cmd = name;
43
+ item.innerHTML = `
44
+ <span class="cmd-name">/${escapeHtml(name)}</span>
45
+ <span class="cmd-category" data-cat="${cmd.category}">${cmd.category}</span>
46
+ <span class="cmd-desc">${escapeHtml(cmd.description)}</span>
47
+ `;
48
+ item.addEventListener("mousedown", (e) => {
49
+ e.preventDefault();
50
+ const needsArgs = name === "run" || cmd.needsArgs;
51
+ pane.messageInput.value = `/${name}${needsArgs ? " " : ""}`;
52
+ dismissAutocomplete(pane);
53
+ pane.messageInput.focus();
54
+ });
55
+ el.appendChild(item);
56
+ });
57
+
58
+ pane._autocompleteIndex = 0;
59
+ el.classList.remove("hidden");
60
+ }
61
+
62
+ export function dismissAutocomplete(pane) {
63
+ if (pane.autocompleteEl) {
64
+ pane.autocompleteEl.classList.add("hidden");
65
+ pane.autocompleteEl.innerHTML = "";
66
+ pane._autocompleteIndex = -1;
67
+ }
68
+ }
69
+
70
+ export function handleAutocompleteKeydown(e, pane) {
71
+ const el = pane.autocompleteEl;
72
+ if (!el || el.classList.contains("hidden")) return false;
73
+
74
+ const items = el.querySelectorAll(".slash-autocomplete-item");
75
+ if (items.length === 0) return false;
76
+
77
+ if (e.key === "ArrowDown") {
78
+ e.preventDefault();
79
+ pane._autocompleteIndex = Math.min(pane._autocompleteIndex + 1, items.length - 1);
80
+ items.forEach((it, i) => it.classList.toggle("active", i === pane._autocompleteIndex));
81
+ items[pane._autocompleteIndex].scrollIntoView({ block: "nearest" });
82
+ return true;
83
+ }
84
+
85
+ if (e.key === "ArrowUp") {
86
+ e.preventDefault();
87
+ pane._autocompleteIndex = Math.max(pane._autocompleteIndex - 1, 0);
88
+ items.forEach((it, i) => it.classList.toggle("active", i === pane._autocompleteIndex));
89
+ items[pane._autocompleteIndex].scrollIntoView({ block: "nearest" });
90
+ return true;
91
+ }
92
+
93
+ if (e.key === "Tab" || (e.key === "Enter" && pane._autocompleteIndex >= 0)) {
94
+ e.preventDefault();
95
+ const active = items[pane._autocompleteIndex];
96
+ if (active) {
97
+ const cmdName = active.dataset.cmd;
98
+ const cmdDef = commandRegistry[cmdName];
99
+ const needsArgs = cmdName === "run" || (cmdDef && cmdDef.needsArgs);
100
+ pane.messageInput.value = `/${cmdName}${needsArgs ? " " : ""}`;
101
+ dismissAutocomplete(pane);
102
+ pane.messageInput.focus();
103
+ }
104
+ return true;
105
+ }
106
+
107
+ if (e.key === "Escape") {
108
+ e.preventDefault();
109
+ dismissAutocomplete(pane);
110
+ return true;
111
+ }
112
+
113
+ return false;
114
+ }
@@ -0,0 +1,100 @@
1
+ // Context Window Indicator — cumulative session token gauge (status bar)
2
+ import { getState, setState } from '../core/store.js';
3
+ import { $ } from '../core/dom.js';
4
+
5
+ const sbGaugeSep = document.getElementById("sb-gauge-sep");
6
+
7
+ const MODEL_LIMITS = {
8
+ default: 200_000,
9
+ };
10
+
11
+ function getLimit() {
12
+ return MODEL_LIMITS.default;
13
+ }
14
+
15
+ function formatTokens(n) {
16
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
17
+ if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
18
+ return String(n);
19
+ }
20
+
21
+ function renderGauge(tokens) {
22
+ if (!$.contextGauge) return;
23
+
24
+ const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheCreation;
25
+ const limit = getLimit();
26
+ const pct = Math.min((total / limit) * 100, 100);
27
+
28
+ // Show gauge
29
+ $.contextGauge.classList.remove('hidden');
30
+ if (sbGaugeSep) sbGaugeSep.classList.remove('hidden');
31
+
32
+ // Fill width
33
+ $.contextGaugeFill.style.width = pct + '%';
34
+
35
+ // Color class
36
+ $.contextGaugeFill.classList.remove('warning', 'critical');
37
+ $.contextGauge.classList.remove('warning', 'critical');
38
+ if (pct >= 80) {
39
+ $.contextGaugeFill.classList.add('critical');
40
+ $.contextGauge.classList.add('critical');
41
+ } else if (pct >= 50) {
42
+ $.contextGaugeFill.classList.add('warning');
43
+ $.contextGauge.classList.add('warning');
44
+ }
45
+
46
+ // Label
47
+ $.contextGaugeLabel.textContent = `${formatTokens(total)}/${formatTokens(limit)}`;
48
+
49
+ // Tooltip breakdown
50
+ $.contextGauge.title = [
51
+ `Input: ${formatTokens(tokens.input)}`,
52
+ `Output: ${formatTokens(tokens.output)}`,
53
+ `Cache Read: ${formatTokens(tokens.cacheRead)}`,
54
+ `Cache Create: ${formatTokens(tokens.cacheCreation)}`,
55
+ `Total: ${formatTokens(total)} / ${formatTokens(limit)}`,
56
+ ].join('\n');
57
+ }
58
+
59
+ export function updateContextGauge(input, output, cacheRead, cacheCreation) {
60
+ const tokens = getState('sessionTokens');
61
+ tokens.input += (input || 0);
62
+ tokens.output += (output || 0);
63
+ tokens.cacheRead += (cacheRead || 0);
64
+ tokens.cacheCreation += (cacheCreation || 0);
65
+ setState('sessionTokens', { ...tokens });
66
+ renderGauge(tokens);
67
+ }
68
+
69
+ export function resetContextGauge() {
70
+ const fresh = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
71
+ setState('sessionTokens', fresh);
72
+ if ($.contextGauge) $.contextGauge.classList.add('hidden');
73
+ if (sbGaugeSep) sbGaugeSep.classList.add('hidden');
74
+ }
75
+
76
+ export async function loadContextGauge(sessionId) {
77
+ if (!sessionId) return;
78
+ try {
79
+ const messages = await (await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/messages-single`)).json();
80
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
81
+ for (const msg of messages) {
82
+ if (msg.role === 'result') {
83
+ const data = JSON.parse(msg.content);
84
+ tokens.input += (data.input_tokens || 0);
85
+ tokens.output += (data.output_tokens || 0);
86
+ tokens.cacheRead += (data.cache_read_tokens || 0);
87
+ tokens.cacheCreation += (data.cache_creation_tokens || 0);
88
+ }
89
+ }
90
+ setState('sessionTokens', tokens);
91
+ const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheCreation;
92
+ if (total > 0) {
93
+ renderGauge(tokens);
94
+ } else if ($.contextGauge) {
95
+ $.contextGauge.classList.add('hidden');
96
+ }
97
+ } catch (err) {
98
+ console.error('Failed to load context gauge:', err);
99
+ }
100
+ }
@@ -0,0 +1,124 @@
1
+ // Diff rendering
2
+
3
+ export function computeLineDiff(oldLines, newLines) {
4
+ const m = oldLines.length, n = newLines.length;
5
+ // Guard against huge diffs
6
+ if (m + n > 1000) return null;
7
+
8
+ // LCS via DP
9
+ const dp = Array.from({ length: m + 1 }, () => new Uint16Array(n + 1));
10
+ for (let i = 1; i <= m; i++) {
11
+ for (let j = 1; j <= n; j++) {
12
+ dp[i][j] = oldLines[i - 1] === newLines[j - 1]
13
+ ? dp[i - 1][j - 1] + 1
14
+ : Math.max(dp[i - 1][j], dp[i][j - 1]);
15
+ }
16
+ }
17
+
18
+ // Backtrack
19
+ const result = [];
20
+ let i = m, j = n;
21
+ while (i > 0 || j > 0) {
22
+ if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
23
+ result.unshift({ type: "context", line: oldLines[i - 1], oldNum: i, newNum: j });
24
+ i--; j--;
25
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
26
+ result.unshift({ type: "added", line: newLines[j - 1], newNum: j });
27
+ j--;
28
+ } else {
29
+ result.unshift({ type: "removed", line: oldLines[i - 1], oldNum: i });
30
+ i--;
31
+ }
32
+ }
33
+ return result;
34
+ }
35
+
36
+ export function renderDiffView(oldStr, newStr, filePath) {
37
+ const oldLines = oldStr.split("\n");
38
+ const newLines = newStr.split("\n");
39
+ const diff = computeLineDiff(oldLines, newLines);
40
+ if (!diff) return null;
41
+
42
+ const container = document.createElement("div");
43
+ container.className = "diff-view";
44
+
45
+ const header = document.createElement("div");
46
+ header.className = "diff-header";
47
+ header.textContent = filePath || "Edit";
48
+ container.appendChild(header);
49
+
50
+ const body = document.createElement("div");
51
+ body.className = "diff-body";
52
+
53
+ for (const entry of diff) {
54
+ const line = document.createElement("div");
55
+ line.className = `diff-line diff-${entry.type}`;
56
+
57
+ const gutter = document.createElement("span");
58
+ gutter.className = "diff-gutter";
59
+ if (entry.type === "removed") {
60
+ gutter.textContent = entry.oldNum;
61
+ } else if (entry.type === "added") {
62
+ gutter.textContent = entry.newNum;
63
+ } else {
64
+ gutter.textContent = entry.oldNum;
65
+ }
66
+
67
+ const sign = document.createElement("span");
68
+ sign.className = "diff-sign";
69
+ sign.textContent = entry.type === "added" ? "+" : entry.type === "removed" ? "-" : " ";
70
+
71
+ const content = document.createElement("span");
72
+ content.className = "diff-content";
73
+ content.textContent = entry.line;
74
+
75
+ line.appendChild(gutter);
76
+ line.appendChild(sign);
77
+ line.appendChild(content);
78
+ body.appendChild(line);
79
+ }
80
+
81
+ container.appendChild(body);
82
+ return container;
83
+ }
84
+
85
+ export function renderAdditionsView(content, filePath) {
86
+ const lines = content.split("\n");
87
+ if (lines.length > 1000) return null;
88
+
89
+ const container = document.createElement("div");
90
+ container.className = "diff-view";
91
+
92
+ const header = document.createElement("div");
93
+ header.className = "diff-header";
94
+ header.textContent = filePath || "Write (new file)";
95
+ container.appendChild(header);
96
+
97
+ const body = document.createElement("div");
98
+ body.className = "diff-body";
99
+
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = document.createElement("div");
102
+ line.className = "diff-line diff-added";
103
+
104
+ const gutter = document.createElement("span");
105
+ gutter.className = "diff-gutter";
106
+ gutter.textContent = i + 1;
107
+
108
+ const sign = document.createElement("span");
109
+ sign.className = "diff-sign";
110
+ sign.textContent = "+";
111
+
112
+ const lineContent = document.createElement("span");
113
+ lineContent.className = "diff-content";
114
+ lineContent.textContent = lines[i];
115
+
116
+ line.appendChild(gutter);
117
+ line.appendChild(sign);
118
+ line.appendChild(lineContent);
119
+ body.appendChild(line);
120
+ }
121
+
122
+ container.appendChild(body);
123
+ return container;
124
+ }
@@ -0,0 +1,36 @@
1
+ // Disabled tools selector — localStorage persistence + getter
2
+ const STORAGE_KEY = 'claudeck-disabled-tools';
3
+ const display = document.getElementById('disabled-tools-display');
4
+
5
+ export function getDisabledTools() {
6
+ try {
7
+ return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
8
+ } catch {
9
+ return [];
10
+ }
11
+ }
12
+
13
+ function updateDisplay() {
14
+ if (!display) return;
15
+ const tools = getDisabledTools();
16
+ display.textContent = tools.length === 0 ? 'none' : `${tools.length} off`;
17
+ }
18
+
19
+ function init() {
20
+ const saved = getDisabledTools();
21
+ const checkboxes = document.querySelectorAll('.header-submenu--tools input[type="checkbox"]');
22
+
23
+ checkboxes.forEach(cb => {
24
+ cb.checked = saved.includes(cb.value);
25
+ cb.addEventListener('change', () => {
26
+ const current = [];
27
+ checkboxes.forEach(c => { if (c.checked) current.push(c.value); });
28
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(current));
29
+ updateDisplay();
30
+ });
31
+ });
32
+
33
+ updateDisplay();
34
+ }
35
+
36
+ init();