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,270 @@
|
|
|
1
|
+
// Home page — AI Activity contribution grid + summary cards
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
import { on as onState, setState, getState } from '../core/store.js';
|
|
4
|
+
import { fetchHomeData } from '../core/api.js';
|
|
5
|
+
import { loadHomeAnalytics } from './analytics.js';
|
|
6
|
+
|
|
7
|
+
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
8
|
+
|
|
9
|
+
// ── View switching ─────────────────────────────────────
|
|
10
|
+
const chatAreaMain = document.querySelector('.chat-area-main');
|
|
11
|
+
|
|
12
|
+
// ── Data loading ───────────────────────────────────────
|
|
13
|
+
let loaded = false;
|
|
14
|
+
|
|
15
|
+
async function loadHome() {
|
|
16
|
+
loaded = false;
|
|
17
|
+
try {
|
|
18
|
+
const data = await fetchHomeData();
|
|
19
|
+
renderGrid(data.yearlyActivity);
|
|
20
|
+
renderCards(data.yearlyActivity, data.overview);
|
|
21
|
+
loadHomeAnalytics();
|
|
22
|
+
loaded = true;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error('Failed to load home data:', err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onState('view', (view) => {
|
|
29
|
+
$.homePage.classList.toggle('hidden', view !== 'home');
|
|
30
|
+
chatAreaMain.classList.toggle('hidden', view !== 'chat');
|
|
31
|
+
$.homeBtn.classList.toggle('active', view === 'home');
|
|
32
|
+
if (view === 'home') loadHome();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
onState('sessionId', (id) => {
|
|
36
|
+
if (id) setState('view', 'chat');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
$.homeBtn.addEventListener('click', () => {
|
|
40
|
+
setState('view', 'home');
|
|
41
|
+
setState('sessionId', null);
|
|
42
|
+
$.projectSelect.value = '';
|
|
43
|
+
localStorage.removeItem('claudeck-cwd');
|
|
44
|
+
$.sessionList.innerHTML = '';
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ── Grid rendering ─────────────────────────────────────
|
|
48
|
+
function renderGrid(activity) {
|
|
49
|
+
const grid = document.getElementById('home-activity-grid');
|
|
50
|
+
const monthsRow = document.getElementById('home-grid-months');
|
|
51
|
+
const yearLabel = document.getElementById('home-year-label');
|
|
52
|
+
|
|
53
|
+
// Build date map
|
|
54
|
+
const dateMap = {};
|
|
55
|
+
for (const row of activity) {
|
|
56
|
+
dateMap[row.date] = row;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Today and start date (364 days ago, aligned to Sunday)
|
|
60
|
+
const today = new Date();
|
|
61
|
+
today.setHours(0, 0, 0, 0);
|
|
62
|
+
const start = new Date(today);
|
|
63
|
+
start.setDate(start.getDate() - 364);
|
|
64
|
+
// Align to Sunday (day 0)
|
|
65
|
+
const dayOfWeek = start.getDay();
|
|
66
|
+
start.setDate(start.getDate() - dayOfWeek);
|
|
67
|
+
|
|
68
|
+
yearLabel.textContent = `${start.getFullYear()} – ${today.getFullYear()}`;
|
|
69
|
+
|
|
70
|
+
// Collect all cells with their scores
|
|
71
|
+
const cells = [];
|
|
72
|
+
const scores = [];
|
|
73
|
+
const d = new Date(start);
|
|
74
|
+
|
|
75
|
+
while (d <= today) {
|
|
76
|
+
const key = localDateStr(d);
|
|
77
|
+
const row = dateMap[key];
|
|
78
|
+
let score = 0;
|
|
79
|
+
if (row) {
|
|
80
|
+
score = row.sessions + row.queries + (row.cost * 50) + ((row.input_tokens + row.output_tokens) / 5000);
|
|
81
|
+
}
|
|
82
|
+
cells.push({ date: key, row, score });
|
|
83
|
+
if (score > 0) scores.push(score);
|
|
84
|
+
d.setDate(d.getDate() + 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Quartile thresholds
|
|
88
|
+
scores.sort((a, b) => a - b);
|
|
89
|
+
const p25 = scores[Math.floor(scores.length * 0.25)] || 0;
|
|
90
|
+
const p50 = scores[Math.floor(scores.length * 0.50)] || 0;
|
|
91
|
+
const p75 = scores[Math.floor(scores.length * 0.75)] || 0;
|
|
92
|
+
|
|
93
|
+
function getLevel(score) {
|
|
94
|
+
if (score === 0) return 0;
|
|
95
|
+
if (score <= p25) return 1;
|
|
96
|
+
if (score <= p50) return 2;
|
|
97
|
+
if (score <= p75) return 3;
|
|
98
|
+
return 4;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Render cells
|
|
102
|
+
grid.innerHTML = '';
|
|
103
|
+
for (const cell of cells) {
|
|
104
|
+
const div = document.createElement('div');
|
|
105
|
+
div.className = 'home-grid-cell';
|
|
106
|
+
div.dataset.level = getLevel(cell.score);
|
|
107
|
+
div.dataset.date = cell.date;
|
|
108
|
+
if (cell.row) {
|
|
109
|
+
div.dataset.sessions = cell.row.sessions;
|
|
110
|
+
div.dataset.queries = cell.row.queries;
|
|
111
|
+
div.dataset.cost = cell.row.cost.toFixed(2);
|
|
112
|
+
div.dataset.tokens = (cell.row.input_tokens + cell.row.output_tokens).toString();
|
|
113
|
+
}
|
|
114
|
+
grid.appendChild(div);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Month labels
|
|
118
|
+
monthsRow.innerHTML = '';
|
|
119
|
+
let currentMonth = -1;
|
|
120
|
+
let weekIndex = 0;
|
|
121
|
+
const cellSize = 15; // 12px + 3px gap
|
|
122
|
+
const dc = new Date(start);
|
|
123
|
+
|
|
124
|
+
while (dc <= today) {
|
|
125
|
+
if (dc.getDay() === 0) { // start of week
|
|
126
|
+
const m = dc.getMonth();
|
|
127
|
+
if (m !== currentMonth) {
|
|
128
|
+
const span = document.createElement('span');
|
|
129
|
+
span.textContent = MONTHS[m];
|
|
130
|
+
span.style.marginLeft = (weekIndex * cellSize) + 'px';
|
|
131
|
+
span.style.position = 'absolute';
|
|
132
|
+
monthsRow.appendChild(span);
|
|
133
|
+
currentMonth = m;
|
|
134
|
+
}
|
|
135
|
+
weekIndex++;
|
|
136
|
+
}
|
|
137
|
+
dc.setDate(dc.getDate() + 1);
|
|
138
|
+
}
|
|
139
|
+
monthsRow.style.position = 'relative';
|
|
140
|
+
monthsRow.style.height = '16px';
|
|
141
|
+
|
|
142
|
+
// Tooltip
|
|
143
|
+
setupTooltip(grid);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Tooltip ────────────────────────────────────────────
|
|
147
|
+
let tooltip = null;
|
|
148
|
+
|
|
149
|
+
function setupTooltip(grid) {
|
|
150
|
+
grid.addEventListener('mouseover', (e) => {
|
|
151
|
+
const cell = e.target.closest('.home-grid-cell');
|
|
152
|
+
if (!cell) return;
|
|
153
|
+
if (!tooltip) {
|
|
154
|
+
tooltip = document.createElement('div');
|
|
155
|
+
tooltip.className = 'home-grid-tooltip';
|
|
156
|
+
document.body.appendChild(tooltip);
|
|
157
|
+
}
|
|
158
|
+
const date = cell.dataset.date;
|
|
159
|
+
const sessions = cell.dataset.sessions || '0';
|
|
160
|
+
const queries = cell.dataset.queries || '0';
|
|
161
|
+
const cost = cell.dataset.cost || '0.00';
|
|
162
|
+
const tokens = cell.dataset.tokens || '0';
|
|
163
|
+
|
|
164
|
+
tooltip.innerHTML = `<strong>${date}</strong><br>` +
|
|
165
|
+
`${sessions} sessions · ${queries} queries<br>` +
|
|
166
|
+
`${formatTokens(+tokens)} tokens · $${cost}`;
|
|
167
|
+
tooltip.style.display = 'block';
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
grid.addEventListener('mousemove', (e) => {
|
|
171
|
+
if (tooltip) {
|
|
172
|
+
tooltip.style.left = (e.clientX + 12) + 'px';
|
|
173
|
+
tooltip.style.top = (e.clientY - 40) + 'px';
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
grid.addEventListener('mouseout', (e) => {
|
|
178
|
+
if (!e.target.closest('.home-grid-cell')) return;
|
|
179
|
+
if (tooltip) tooltip.style.display = 'none';
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Summary cards ──────────────────────────────────────
|
|
184
|
+
function renderCards(activity, overview) {
|
|
185
|
+
const cards = document.getElementById('home-cards');
|
|
186
|
+
|
|
187
|
+
// Streak calculations
|
|
188
|
+
const activeDates = new Set(activity.map(r => r.date));
|
|
189
|
+
const today = new Date();
|
|
190
|
+
today.setHours(0, 0, 0, 0);
|
|
191
|
+
const todayKey = localDateStr(today);
|
|
192
|
+
|
|
193
|
+
// Current streak
|
|
194
|
+
let currentStreak = 0;
|
|
195
|
+
const d = new Date(today);
|
|
196
|
+
while (true) {
|
|
197
|
+
const key = localDateStr(d);
|
|
198
|
+
if (activeDates.has(key)) {
|
|
199
|
+
currentStreak++;
|
|
200
|
+
d.setDate(d.getDate() - 1);
|
|
201
|
+
} else {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Longest streak
|
|
207
|
+
let longestStreak = 0;
|
|
208
|
+
let streak = 0;
|
|
209
|
+
const sortedDates = [...activeDates].sort();
|
|
210
|
+
for (let i = 0; i < sortedDates.length; i++) {
|
|
211
|
+
if (i === 0) {
|
|
212
|
+
streak = 1;
|
|
213
|
+
} else {
|
|
214
|
+
const prev = new Date(sortedDates[i - 1]);
|
|
215
|
+
const curr = new Date(sortedDates[i]);
|
|
216
|
+
const diff = (curr - prev) / 86400000;
|
|
217
|
+
streak = diff === 1 ? streak + 1 : 1;
|
|
218
|
+
}
|
|
219
|
+
longestStreak = Math.max(longestStreak, streak);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Today's stats
|
|
223
|
+
const todayData = activity.find(r => r.date === todayKey);
|
|
224
|
+
const todayCost = todayData ? todayData.cost : 0;
|
|
225
|
+
const todayQueries = todayData ? todayData.queries : 0;
|
|
226
|
+
|
|
227
|
+
const totalTokens = overview.totalOutputTokens || 0;
|
|
228
|
+
const totalSessions = overview.sessions || 0;
|
|
229
|
+
const totalCost = overview.totalCost || 0;
|
|
230
|
+
|
|
231
|
+
const cardData = [
|
|
232
|
+
{ label: 'Sessions', value: totalSessions.toLocaleString(), sub: 'all time' },
|
|
233
|
+
{ label: 'Total Cost', value: '$' + totalCost.toFixed(2), sub: 'all time' },
|
|
234
|
+
{ label: 'Output Tokens', value: formatTokens(totalTokens), sub: 'all time' },
|
|
235
|
+
{ label: 'Current Streak', value: currentStreak + 'd', sub: currentStreak > 0 ? 'active' : 'no activity today' },
|
|
236
|
+
{ label: 'Longest Streak', value: longestStreak + 'd', sub: 'consecutive days' },
|
|
237
|
+
{ label: 'Today', value: todayQueries + ' queries', sub: '$' + todayCost.toFixed(2) },
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
cards.innerHTML = cardData.map(c => `
|
|
241
|
+
<div class="home-card">
|
|
242
|
+
<div class="home-card-label">${c.label}</div>
|
|
243
|
+
<div class="home-card-value">${c.value}</div>
|
|
244
|
+
<div class="home-card-sub">${c.sub}</div>
|
|
245
|
+
</div>
|
|
246
|
+
`).join('');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Helpers ────────────────────────────────────────────
|
|
250
|
+
function localDateStr(d) {
|
|
251
|
+
const y = d.getFullYear();
|
|
252
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
253
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
254
|
+
return `${y}-${m}-${day}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function formatTokens(n) {
|
|
258
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
259
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
|
|
260
|
+
return n.toString();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// If a session was already restored from localStorage before this module loaded,
|
|
264
|
+
// or a project was previously selected, switch to chat view; otherwise load home.
|
|
265
|
+
const savedProject = localStorage.getItem('claudeck-cwd');
|
|
266
|
+
if (getState('sessionId') || savedProject) {
|
|
267
|
+
setState('view', 'chat');
|
|
268
|
+
} else if (getState('view') === 'home') {
|
|
269
|
+
loadHome();
|
|
270
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// Project selection & system prompts
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
import { getState, setState } from '../core/store.js';
|
|
4
|
+
import { CHAT_IDS } from '../core/constants.js';
|
|
5
|
+
import * as api from '../core/api.js';
|
|
6
|
+
import { commandRegistry, registerCommand } from '../ui/commands.js';
|
|
7
|
+
import { panes } from '../ui/parallel.js';
|
|
8
|
+
import { loadSessions } from './sessions.js';
|
|
9
|
+
import { loadStats } from './cost-dashboard.js';
|
|
10
|
+
import { showWhalyPlaceholder } from '../ui/messages.js';
|
|
11
|
+
|
|
12
|
+
export async function loadProjects() {
|
|
13
|
+
try {
|
|
14
|
+
const projects = await api.fetchProjects();
|
|
15
|
+
setState("projectsData", projects);
|
|
16
|
+
const saved = localStorage.getItem("claudeck-cwd") || "";
|
|
17
|
+
|
|
18
|
+
for (const p of projects) {
|
|
19
|
+
const opt = document.createElement("option");
|
|
20
|
+
opt.value = p.path;
|
|
21
|
+
opt.textContent = p.name;
|
|
22
|
+
$.projectSelect.appendChild(opt);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (saved && [...$.projectSelect.options].some((o) => o.value === saved)) {
|
|
26
|
+
$.projectSelect.value = saved;
|
|
27
|
+
}
|
|
28
|
+
updateSystemPromptIndicator();
|
|
29
|
+
updateHeaderProjectName();
|
|
30
|
+
updateSessionControls();
|
|
31
|
+
loadProjectCommands();
|
|
32
|
+
// Load sessions after project dropdown is populated so they filter correctly
|
|
33
|
+
loadSessions();
|
|
34
|
+
|
|
35
|
+
// If a session was restored from localStorage, load its messages
|
|
36
|
+
const { getState } = await import('../core/store.js');
|
|
37
|
+
const { loadMessages } = await import('./sessions.js');
|
|
38
|
+
const restoredSid = getState("sessionId");
|
|
39
|
+
if (restoredSid) {
|
|
40
|
+
loadMessages(restoredSid);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("Failed to load projects:", err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sessionControls = document.getElementById("session-controls");
|
|
48
|
+
|
|
49
|
+
function updateSessionControls() {
|
|
50
|
+
if ($.projectSelect.value) {
|
|
51
|
+
sessionControls.classList.remove("hidden");
|
|
52
|
+
} else {
|
|
53
|
+
sessionControls.classList.add("hidden");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function updateSystemPromptIndicator() {
|
|
58
|
+
const cwd = $.projectSelect.value;
|
|
59
|
+
const project = getState("projectsData").find((p) => p.path === cwd);
|
|
60
|
+
if (project && project.systemPrompt) {
|
|
61
|
+
$.spBadge.classList.remove("hidden");
|
|
62
|
+
} else {
|
|
63
|
+
$.spBadge.classList.add("hidden");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function openSystemPromptModal() {
|
|
68
|
+
const cwd = $.projectSelect.value;
|
|
69
|
+
if (!cwd) return;
|
|
70
|
+
const project = getState("projectsData").find((p) => p.path === cwd);
|
|
71
|
+
$.spTextarea.value = project?.systemPrompt || "";
|
|
72
|
+
$.spModal.classList.remove("hidden");
|
|
73
|
+
$.spTextarea.focus();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function saveSystemPrompt(prompt) {
|
|
77
|
+
const cwd = $.projectSelect.value;
|
|
78
|
+
if (!cwd) return;
|
|
79
|
+
try {
|
|
80
|
+
await api.saveSystemPromptApi(cwd, prompt);
|
|
81
|
+
const project = getState("projectsData").find((p) => p.path === cwd);
|
|
82
|
+
if (project) project.systemPrompt = prompt;
|
|
83
|
+
updateSystemPromptIndicator();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error("Failed to save system prompt:", err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function updateHeaderProjectName() {
|
|
90
|
+
const opt = $.projectSelect.options[$.projectSelect.selectedIndex];
|
|
91
|
+
$.headerProjectName.textContent = opt && opt.value ? opt.textContent : "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function loadProjectCommands() {
|
|
95
|
+
// Remove old project commands and skills
|
|
96
|
+
for (const [name, cmd] of Object.entries(commandRegistry)) {
|
|
97
|
+
if (cmd.category === "project" || cmd.category === "skill") delete commandRegistry[name];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const cwd = $.projectSelect.value;
|
|
101
|
+
if (!cwd) return;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const commands = await api.fetchProjectCommands(cwd);
|
|
105
|
+
if (!Array.isArray(commands) || commands.length === 0) return;
|
|
106
|
+
|
|
107
|
+
for (const c of commands) {
|
|
108
|
+
const slug = c.command;
|
|
109
|
+
if (!slug || commandRegistry[slug]) continue;
|
|
110
|
+
const hasArgs = c.prompt.includes("$ARGUMENTS");
|
|
111
|
+
const label = c.source === "skill" ? `${c.description}` : (c.description || c.command);
|
|
112
|
+
registerCommand(slug, {
|
|
113
|
+
category: c.source === "skill" ? "skill" : "project",
|
|
114
|
+
description: label,
|
|
115
|
+
needsArgs: hasArgs,
|
|
116
|
+
argumentHint: c.argumentHint || "",
|
|
117
|
+
execute(args, pane) {
|
|
118
|
+
let prompt = c.prompt;
|
|
119
|
+
if (hasArgs) {
|
|
120
|
+
prompt = prompt.replace(/\$ARGUMENTS/g, args || "");
|
|
121
|
+
}
|
|
122
|
+
pane.messageInput.value = prompt;
|
|
123
|
+
pane.messageInput.style.height = "auto";
|
|
124
|
+
pane.messageInput.style.height = Math.min(pane.messageInput.scrollHeight, 200) + "px";
|
|
125
|
+
// Lazy import to avoid circular dep
|
|
126
|
+
import('./chat.js').then(({ sendMessage }) => sendMessage(pane));
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("Failed to load project commands:", err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Add Project (folder browser) ────────────────────────
|
|
136
|
+
let currentBrowsePath = "";
|
|
137
|
+
|
|
138
|
+
function openAddProjectModal() {
|
|
139
|
+
$.addProjectModal.classList.remove("hidden");
|
|
140
|
+
$.addProjectName.value = "";
|
|
141
|
+
navigateToDir(""); // defaults to $HOME on server
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function closeAddProjectModal() {
|
|
145
|
+
$.addProjectModal.classList.add("hidden");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function navigateToDir(dir) {
|
|
149
|
+
$.folderList.innerHTML = '<div class="folder-list-loading">Loading...</div>';
|
|
150
|
+
try {
|
|
151
|
+
const data = await api.browseFolders(dir || undefined);
|
|
152
|
+
currentBrowsePath = data.current;
|
|
153
|
+
renderBreadcrumb(data.current);
|
|
154
|
+
renderFolderList(data);
|
|
155
|
+
// Auto-fill name from last segment
|
|
156
|
+
const base = data.current.split(/[/\\]/).filter(Boolean).pop() || "";
|
|
157
|
+
$.addProjectName.value = base;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
$.folderList.innerHTML = `<div class="folder-list-empty">Error: ${err.message}</div>`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderBreadcrumb(pathStr) {
|
|
164
|
+
$.folderBreadcrumb.innerHTML = "";
|
|
165
|
+
const parts = pathStr.split(/[/\\]/).filter(Boolean);
|
|
166
|
+
// Root
|
|
167
|
+
const rootSeg = document.createElement("span");
|
|
168
|
+
rootSeg.className = "folder-breadcrumb-seg";
|
|
169
|
+
rootSeg.textContent = "/";
|
|
170
|
+
rootSeg.addEventListener("click", () => navigateToDir("/"));
|
|
171
|
+
$.folderBreadcrumb.appendChild(rootSeg);
|
|
172
|
+
|
|
173
|
+
let accumulated = "";
|
|
174
|
+
for (const part of parts) {
|
|
175
|
+
accumulated += "/" + part;
|
|
176
|
+
const sep = document.createElement("span");
|
|
177
|
+
sep.className = "folder-breadcrumb-sep";
|
|
178
|
+
sep.textContent = "/";
|
|
179
|
+
$.folderBreadcrumb.appendChild(sep);
|
|
180
|
+
|
|
181
|
+
const seg = document.createElement("span");
|
|
182
|
+
seg.className = "folder-breadcrumb-seg";
|
|
183
|
+
seg.textContent = part;
|
|
184
|
+
const target = accumulated;
|
|
185
|
+
seg.addEventListener("click", () => navigateToDir(target));
|
|
186
|
+
$.folderBreadcrumb.appendChild(seg);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function renderFolderList(data) {
|
|
191
|
+
$.folderList.innerHTML = "";
|
|
192
|
+
|
|
193
|
+
// Parent directory entry
|
|
194
|
+
if (data.parent) {
|
|
195
|
+
const parentItem = document.createElement("div");
|
|
196
|
+
parentItem.className = "folder-list-item";
|
|
197
|
+
parentItem.innerHTML = '<span class="folder-icon">..</span><span>Parent directory</span>';
|
|
198
|
+
parentItem.addEventListener("click", () => navigateToDir(data.parent));
|
|
199
|
+
$.folderList.appendChild(parentItem);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (data.dirs.length === 0 && !data.parent) {
|
|
203
|
+
$.folderList.innerHTML = '<div class="folder-list-empty">No subdirectories</div>';
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (data.dirs.length === 0) {
|
|
208
|
+
const empty = document.createElement("div");
|
|
209
|
+
empty.className = "folder-list-empty";
|
|
210
|
+
empty.textContent = "No subdirectories";
|
|
211
|
+
$.folderList.appendChild(empty);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
for (const dir of data.dirs) {
|
|
216
|
+
const item = document.createElement("div");
|
|
217
|
+
item.className = "folder-list-item";
|
|
218
|
+
item.innerHTML = `<span class="folder-icon">\u{1F4C1}</span><span>${dir.name}</span>`;
|
|
219
|
+
item.addEventListener("click", () => navigateToDir(dir.path));
|
|
220
|
+
$.folderList.appendChild(item);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function confirmAddProject() {
|
|
225
|
+
const name = $.addProjectName.value.trim();
|
|
226
|
+
if (!name) {
|
|
227
|
+
$.addProjectName.focus();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!currentBrowsePath) return;
|
|
231
|
+
|
|
232
|
+
// Check for duplicate in dropdown
|
|
233
|
+
const existing = [...$.projectSelect.options].find((o) => o.value === currentBrowsePath);
|
|
234
|
+
if (existing) {
|
|
235
|
+
alert("This project path is already added.");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const result = await api.addProject(name, currentBrowsePath);
|
|
241
|
+
const project = result.project;
|
|
242
|
+
|
|
243
|
+
// Add to dropdown and select it
|
|
244
|
+
const opt = document.createElement("option");
|
|
245
|
+
opt.value = project.path;
|
|
246
|
+
opt.textContent = project.name;
|
|
247
|
+
$.projectSelect.appendChild(opt);
|
|
248
|
+
$.projectSelect.value = project.path;
|
|
249
|
+
|
|
250
|
+
// Update state
|
|
251
|
+
const projects = getState("projectsData");
|
|
252
|
+
projects.push({ name: project.name, path: project.path });
|
|
253
|
+
|
|
254
|
+
localStorage.setItem("claudeck-cwd", project.path);
|
|
255
|
+
updateSystemPromptIndicator();
|
|
256
|
+
updateHeaderProjectName();
|
|
257
|
+
updateSessionControls();
|
|
258
|
+
loadProjectCommands();
|
|
259
|
+
loadSessions();
|
|
260
|
+
loadStats();
|
|
261
|
+
|
|
262
|
+
closeAddProjectModal();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
alert("Failed to add project: " + err.message);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Open in VS Code
|
|
269
|
+
$.openVscodeBtn.addEventListener("click", async () => {
|
|
270
|
+
const path = $.projectSelect.value;
|
|
271
|
+
if (!path) return;
|
|
272
|
+
try {
|
|
273
|
+
await fetch("/api/exec", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "Content-Type": "application/json" },
|
|
276
|
+
body: JSON.stringify({ command: "code .", cwd: path }),
|
|
277
|
+
});
|
|
278
|
+
} catch { /* ignore */ }
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Add project button & modal event listeners
|
|
282
|
+
$.addProjectBtn.addEventListener("click", openAddProjectModal);
|
|
283
|
+
$.addProjectClose.addEventListener("click", closeAddProjectModal);
|
|
284
|
+
$.addProjectConfirm.addEventListener("click", confirmAddProject);
|
|
285
|
+
$.addProjectModal.addEventListener("click", (e) => {
|
|
286
|
+
if (e.target === $.addProjectModal) closeAddProjectModal();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// System prompt modal event listeners
|
|
290
|
+
$.spEditBtn.addEventListener("click", openSystemPromptModal);
|
|
291
|
+
$.spForm.addEventListener("submit", async (e) => {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
await saveSystemPrompt($.spTextarea.value.trim());
|
|
294
|
+
$.spModal.classList.add("hidden");
|
|
295
|
+
});
|
|
296
|
+
document.getElementById("sp-cancel-btn").addEventListener("click", () => {
|
|
297
|
+
$.spModal.classList.add("hidden");
|
|
298
|
+
});
|
|
299
|
+
document.getElementById("sp-modal-close").addEventListener("click", () => {
|
|
300
|
+
$.spModal.classList.add("hidden");
|
|
301
|
+
});
|
|
302
|
+
document.getElementById("sp-clear-btn").addEventListener("click", async () => {
|
|
303
|
+
$.spTextarea.value = "";
|
|
304
|
+
await saveSystemPrompt("");
|
|
305
|
+
$.spModal.classList.add("hidden");
|
|
306
|
+
});
|
|
307
|
+
$.spModal.addEventListener("click", (e) => {
|
|
308
|
+
if (e.target === $.spModal) $.spModal.classList.add("hidden");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Project change handler
|
|
312
|
+
$.projectSelect.addEventListener("change", async () => {
|
|
313
|
+
const { guardSwitch } = await import('./background-sessions.js');
|
|
314
|
+
guardSwitch(() => {
|
|
315
|
+
localStorage.setItem("claudeck-cwd", $.projectSelect.value);
|
|
316
|
+
setState("sessionId", null);
|
|
317
|
+
if ($.projectSelect.value) {
|
|
318
|
+
setState("view", "chat");
|
|
319
|
+
}
|
|
320
|
+
updateSystemPromptIndicator();
|
|
321
|
+
updateHeaderProjectName();
|
|
322
|
+
updateSessionControls();
|
|
323
|
+
loadProjectCommands();
|
|
324
|
+
if (getState("parallelMode")) {
|
|
325
|
+
for (const chatId of CHAT_IDS) {
|
|
326
|
+
const pane = panes.get(chatId);
|
|
327
|
+
if (pane) {
|
|
328
|
+
pane.messagesDiv.innerHTML = "";
|
|
329
|
+
showWhalyPlaceholder(pane);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
$.messagesDiv.innerHTML = "";
|
|
334
|
+
showWhalyPlaceholder();
|
|
335
|
+
}
|
|
336
|
+
loadSessions();
|
|
337
|
+
loadStats();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// New session button
|
|
342
|
+
$.newSessionBtn.addEventListener("click", async () => {
|
|
343
|
+
const { guardSwitch } = await import('./background-sessions.js');
|
|
344
|
+
guardSwitch(() => {
|
|
345
|
+
setState("view", "chat");
|
|
346
|
+
setState("sessionId", null);
|
|
347
|
+
if (getState("parallelMode")) {
|
|
348
|
+
for (const chatId of CHAT_IDS) {
|
|
349
|
+
const pane = panes.get(chatId);
|
|
350
|
+
if (pane) {
|
|
351
|
+
pane.messagesDiv.innerHTML = "";
|
|
352
|
+
pane.currentAssistantMsg = null;
|
|
353
|
+
showWhalyPlaceholder(pane);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
$.messagesDiv.innerHTML = "";
|
|
358
|
+
showWhalyPlaceholder();
|
|
359
|
+
}
|
|
360
|
+
loadSessions();
|
|
361
|
+
if (!getState("parallelMode")) $.messageInput.focus();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Parallel mode toggle
|
|
366
|
+
$.toggleParallelBtn.addEventListener("change", () => {
|
|
367
|
+
if ($.toggleParallelBtn.checked) {
|
|
368
|
+
import('../ui/parallel.js').then(({ enterParallelMode }) => enterParallelMode());
|
|
369
|
+
} else {
|
|
370
|
+
import('../ui/parallel.js').then(({ exitParallelMode }) => exitParallelMode());
|
|
371
|
+
}
|
|
372
|
+
});
|