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.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/cli.js +2 -0
- package/config/agent-chains.json +16 -0
- package/config/agent-dags.json +16 -0
- package/config/agents.json +46 -0
- package/config/bot-prompt.json +3 -0
- package/config/folders.json +66 -0
- package/config/prompts.json +92 -0
- package/config/repos.json +86 -0
- package/config/telegram-config.json +17 -0
- package/config/workflows.json +90 -0
- package/db.js +1198 -0
- package/package.json +55 -0
- package/plugins/claude-editor/client.css +171 -0
- package/plugins/claude-editor/client.js +183 -0
- package/plugins/event-stream/client.css +207 -0
- package/plugins/event-stream/client.js +271 -0
- package/plugins/linear/client.css +345 -0
- package/plugins/linear/client.js +380 -0
- package/plugins/linear/config.json +5 -0
- package/plugins/linear/server.js +312 -0
- package/plugins/repos/client.css +549 -0
- package/plugins/repos/client.js +663 -0
- package/plugins/repos/server.js +232 -0
- package/plugins/sudoku/client.css +196 -0
- package/plugins/sudoku/client.js +329 -0
- package/plugins/tasks/client.css +414 -0
- package/plugins/tasks/client.js +394 -0
- package/plugins/tasks/server.js +116 -0
- package/plugins/tic-tac-toe/client.css +167 -0
- package/plugins/tic-tac-toe/client.js +241 -0
- package/public/css/core/components.css +232 -0
- package/public/css/core/layout.css +330 -0
- package/public/css/core/print.css +18 -0
- package/public/css/core/reset.css +36 -0
- package/public/css/core/responsive.css +378 -0
- package/public/css/core/theme.css +116 -0
- package/public/css/core/variables.css +93 -0
- package/public/css/features/agent-monitor.css +297 -0
- package/public/css/features/agent-sidebar.css +525 -0
- package/public/css/features/agents.css +996 -0
- package/public/css/features/analytics.css +181 -0
- package/public/css/features/background-sessions.css +321 -0
- package/public/css/features/cost-dashboard.css +168 -0
- package/public/css/features/home.css +313 -0
- package/public/css/features/retro-terminal.css +88 -0
- package/public/css/features/telegram.css +127 -0
- package/public/css/features/tour.css +148 -0
- package/public/css/features/voice-input.css +60 -0
- package/public/css/features/welcome.css +241 -0
- package/public/css/panels/assistant-bot.css +442 -0
- package/public/css/panels/dev-docs.css +292 -0
- package/public/css/panels/file-explorer.css +322 -0
- package/public/css/panels/git-panel.css +221 -0
- package/public/css/panels/mcp-manager.css +199 -0
- package/public/css/panels/tips-feed.css +353 -0
- package/public/css/ui/commands.css +273 -0
- package/public/css/ui/context-gauge.css +76 -0
- package/public/css/ui/file-picker.css +69 -0
- package/public/css/ui/image-attachments.css +106 -0
- package/public/css/ui/messages.css +884 -0
- package/public/css/ui/modals.css +122 -0
- package/public/css/ui/parallel.css +217 -0
- package/public/css/ui/permissions.css +110 -0
- package/public/css/ui/right-panel.css +481 -0
- package/public/css/ui/sessions.css +689 -0
- package/public/css/ui/status-bar.css +425 -0
- package/public/css/ui/toolbox.css +206 -0
- package/public/data/tips.json +218 -0
- package/public/icons/favicon.png +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/whaly.png +0 -0
- package/public/index.html +1140 -0
- package/public/js/core/api.js +591 -0
- package/public/js/core/constants.js +3 -0
- package/public/js/core/dom.js +270 -0
- package/public/js/core/events.js +10 -0
- package/public/js/core/plugin-loader.js +153 -0
- package/public/js/core/store.js +39 -0
- package/public/js/core/utils.js +25 -0
- package/public/js/core/ws.js +64 -0
- package/public/js/features/agent-monitor.js +222 -0
- package/public/js/features/agents.js +1209 -0
- package/public/js/features/analytics.js +397 -0
- package/public/js/features/attachments.js +251 -0
- package/public/js/features/background-sessions.js +475 -0
- package/public/js/features/chat.js +589 -0
- package/public/js/features/cost-dashboard.js +152 -0
- package/public/js/features/dag-editor.js +399 -0
- package/public/js/features/easter-egg.js +46 -0
- package/public/js/features/home.js +270 -0
- package/public/js/features/projects.js +372 -0
- package/public/js/features/prompts.js +228 -0
- package/public/js/features/sessions.js +332 -0
- package/public/js/features/telegram.js +131 -0
- package/public/js/features/tour.js +210 -0
- package/public/js/features/voice-input.js +185 -0
- package/public/js/features/welcome.js +43 -0
- package/public/js/features/workflows.js +277 -0
- package/public/js/main.js +51 -0
- package/public/js/panels/assistant-bot.js +445 -0
- package/public/js/panels/dev-docs.js +380 -0
- package/public/js/panels/file-explorer.js +486 -0
- package/public/js/panels/git-panel.js +285 -0
- package/public/js/panels/mcp-manager.js +311 -0
- package/public/js/panels/tips-feed.js +303 -0
- package/public/js/ui/commands.js +114 -0
- package/public/js/ui/context-gauge.js +100 -0
- package/public/js/ui/diff.js +124 -0
- package/public/js/ui/disabled-tools.js +36 -0
- package/public/js/ui/export.js +74 -0
- package/public/js/ui/formatting.js +206 -0
- package/public/js/ui/header-dropdowns.js +72 -0
- package/public/js/ui/input-meta.js +71 -0
- package/public/js/ui/max-turns.js +21 -0
- package/public/js/ui/messages.js +387 -0
- package/public/js/ui/model-selector.js +20 -0
- package/public/js/ui/notifications.js +232 -0
- package/public/js/ui/parallel.js +176 -0
- package/public/js/ui/permissions.js +168 -0
- package/public/js/ui/right-panel.js +173 -0
- package/public/js/ui/shortcuts.js +143 -0
- package/public/js/ui/sidebar-toggle.js +29 -0
- package/public/js/ui/status-bar.js +172 -0
- package/public/js/ui/tab-sdk.js +623 -0
- package/public/js/ui/theme.js +38 -0
- package/public/manifest.json +13 -0
- package/public/offline.html +190 -0
- package/public/style.css +42 -0
- package/public/sw.js +91 -0
- package/server/agent-loop.js +385 -0
- package/server/dag-executor.js +265 -0
- package/server/orchestrator.js +514 -0
- package/server/paths.js +61 -0
- package/server/plugin-mount.js +56 -0
- package/server/push-sender.js +31 -0
- package/server/routes/agents.js +294 -0
- package/server/routes/bot.js +45 -0
- package/server/routes/exec.js +35 -0
- package/server/routes/files.js +218 -0
- package/server/routes/mcp.js +82 -0
- package/server/routes/messages.js +36 -0
- package/server/routes/notifications.js +37 -0
- package/server/routes/projects.js +207 -0
- package/server/routes/prompts.js +53 -0
- package/server/routes/sessions.js +103 -0
- package/server/routes/stats.js +143 -0
- package/server/routes/telegram.js +71 -0
- package/server/routes/tips.js +135 -0
- package/server/routes/workflows.js +81 -0
- package/server/summarizer.js +55 -0
- package/server/telegram-poller.js +205 -0
- package/server/telegram-sender.js +304 -0
- package/server/ws-handler.js +926 -0
- 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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();
|