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,176 @@
|
|
|
1
|
+
// Parallel mode — 2x2 chat panes
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
import { getState, setState } from '../core/store.js';
|
|
4
|
+
import { CHAT_IDS } from '../core/constants.js';
|
|
5
|
+
import { handleAutocompleteKeydown, handleSlashAutocomplete } from './commands.js';
|
|
6
|
+
|
|
7
|
+
// Panes map — chatId -> pane state object
|
|
8
|
+
export const panes = new Map();
|
|
9
|
+
|
|
10
|
+
export function getPane(chatId) {
|
|
11
|
+
if (!getState("parallelMode")) return panes.get(null);
|
|
12
|
+
return panes.get(chatId) || panes.get(null);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function initSinglePane() {
|
|
16
|
+
panes.clear();
|
|
17
|
+
panes.set(null, {
|
|
18
|
+
chatId: null,
|
|
19
|
+
messagesDiv: $.messagesDiv,
|
|
20
|
+
messageInput: $.messageInput,
|
|
21
|
+
sendBtn: $.sendBtn,
|
|
22
|
+
stopBtn: $.stopBtn,
|
|
23
|
+
isStreaming: false,
|
|
24
|
+
currentAssistantMsg: null,
|
|
25
|
+
autocompleteEl: document.getElementById("slash-autocomplete"),
|
|
26
|
+
_autocompleteIndex: -1,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Initialize on load
|
|
31
|
+
initSinglePane();
|
|
32
|
+
|
|
33
|
+
export function createChatPane(chatId, index) {
|
|
34
|
+
// Lazy import to avoid circular dependency at module parse time
|
|
35
|
+
const { sendMessage, stopGeneration } = _getLazyChatFns();
|
|
36
|
+
|
|
37
|
+
const container = document.createElement("div");
|
|
38
|
+
container.className = "chat-pane";
|
|
39
|
+
container.dataset.chatId = chatId;
|
|
40
|
+
|
|
41
|
+
const header = document.createElement("div");
|
|
42
|
+
header.className = "chat-pane-header";
|
|
43
|
+
header.innerHTML = `
|
|
44
|
+
<span class="chat-pane-label">Chat ${index + 1}</span>
|
|
45
|
+
<span class="chat-pane-status">idle</span>
|
|
46
|
+
`;
|
|
47
|
+
container.appendChild(header);
|
|
48
|
+
|
|
49
|
+
const msgs = document.createElement("div");
|
|
50
|
+
msgs.className = "messages";
|
|
51
|
+
container.appendChild(msgs);
|
|
52
|
+
|
|
53
|
+
const inputBar = document.createElement("div");
|
|
54
|
+
inputBar.className = "input-bar";
|
|
55
|
+
|
|
56
|
+
const textarea = document.createElement("textarea");
|
|
57
|
+
textarea.placeholder = `Ask Claude... (Chat ${index + 1})`;
|
|
58
|
+
textarea.rows = 1;
|
|
59
|
+
inputBar.appendChild(textarea);
|
|
60
|
+
|
|
61
|
+
const paneSendBtn = document.createElement("button");
|
|
62
|
+
paneSendBtn.className = "pane-send-btn";
|
|
63
|
+
paneSendBtn.title = "Send";
|
|
64
|
+
paneSendBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="M3 10l7-7m0 0l7 7m-7-7v14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="rotate(90, 10, 10)"/></svg>`;
|
|
65
|
+
inputBar.appendChild(paneSendBtn);
|
|
66
|
+
|
|
67
|
+
const paneStopBtn = document.createElement("button");
|
|
68
|
+
paneStopBtn.className = "pane-stop-btn hidden";
|
|
69
|
+
paneStopBtn.title = "Stop";
|
|
70
|
+
paneStopBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 20 20" fill="none"><rect x="4" y="4" width="12" height="12" rx="2" fill="currentColor"/></svg>`;
|
|
71
|
+
inputBar.appendChild(paneStopBtn);
|
|
72
|
+
|
|
73
|
+
const paneAutocomplete = document.createElement("div");
|
|
74
|
+
paneAutocomplete.className = "slash-autocomplete hidden";
|
|
75
|
+
inputBar.appendChild(paneAutocomplete);
|
|
76
|
+
|
|
77
|
+
container.appendChild(inputBar);
|
|
78
|
+
|
|
79
|
+
const state = {
|
|
80
|
+
chatId,
|
|
81
|
+
messagesDiv: msgs,
|
|
82
|
+
messageInput: textarea,
|
|
83
|
+
sendBtn: paneSendBtn,
|
|
84
|
+
stopBtn: paneStopBtn,
|
|
85
|
+
isStreaming: false,
|
|
86
|
+
currentAssistantMsg: null,
|
|
87
|
+
statusEl: header.querySelector(".chat-pane-status"),
|
|
88
|
+
autocompleteEl: paneAutocomplete,
|
|
89
|
+
_autocompleteIndex: -1,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
paneSendBtn.addEventListener("click", () => sendMessage(state));
|
|
93
|
+
paneStopBtn.addEventListener("click", () => stopGeneration(state));
|
|
94
|
+
|
|
95
|
+
textarea.addEventListener("keydown", (e) => {
|
|
96
|
+
if (handleAutocompleteKeydown(e, state)) return;
|
|
97
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
sendMessage(state);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
textarea.addEventListener("input", () => {
|
|
104
|
+
textarea.style.height = "auto";
|
|
105
|
+
textarea.style.height = Math.min(textarea.scrollHeight, 80) + "px";
|
|
106
|
+
handleSlashAutocomplete(state);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return { container, state };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function enterParallelMode() {
|
|
113
|
+
setState("parallelMode", true);
|
|
114
|
+
$.toggleParallelBtn.checked = true;
|
|
115
|
+
|
|
116
|
+
const chatArea = document.querySelector(".chat-area");
|
|
117
|
+
setState("savedChatArea", chatArea);
|
|
118
|
+
|
|
119
|
+
const grid = document.createElement("div");
|
|
120
|
+
grid.className = "chat-grid";
|
|
121
|
+
grid.id = "chat-grid";
|
|
122
|
+
|
|
123
|
+
panes.clear();
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < CHAT_IDS.length; i++) {
|
|
126
|
+
const { container, state } = createChatPane(CHAT_IDS[i], i);
|
|
127
|
+
grid.appendChild(container);
|
|
128
|
+
panes.set(CHAT_IDS[i], state);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
chatArea.replaceWith(grid);
|
|
132
|
+
|
|
133
|
+
const sessionId = getState("sessionId");
|
|
134
|
+
if (sessionId) {
|
|
135
|
+
// Lazy import to avoid circular dependency
|
|
136
|
+
import('../features/sessions.js').then(({ loadPaneMessages }) => {
|
|
137
|
+
for (const chatId of CHAT_IDS) {
|
|
138
|
+
loadPaneMessages(sessionId, chatId);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function exitParallelMode() {
|
|
145
|
+
setState("parallelMode", false);
|
|
146
|
+
$.toggleParallelBtn.checked = false;
|
|
147
|
+
|
|
148
|
+
const grid = document.getElementById("chat-grid");
|
|
149
|
+
const savedChatArea = getState("savedChatArea");
|
|
150
|
+
if (grid && savedChatArea) {
|
|
151
|
+
grid.replaceWith(savedChatArea);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
initSinglePane();
|
|
155
|
+
|
|
156
|
+
const sessionId = getState("sessionId");
|
|
157
|
+
if (sessionId) {
|
|
158
|
+
import('../features/sessions.js').then(({ loadMessages }) => {
|
|
159
|
+
loadMessages(sessionId);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Lazy getter for chat.js functions to avoid circular dependency
|
|
165
|
+
let _chatFns = null;
|
|
166
|
+
function _getLazyChatFns() {
|
|
167
|
+
if (!_chatFns) {
|
|
168
|
+
// These are set by chat.js during init
|
|
169
|
+
_chatFns = { sendMessage: () => {}, stopGeneration: () => {} };
|
|
170
|
+
}
|
|
171
|
+
return _chatFns;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function _setChatFns(fns) {
|
|
175
|
+
_chatFns = fns;
|
|
176
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Permission / Tool Approval system
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
import { getState } from '../core/store.js';
|
|
4
|
+
import { on as onEvent } from '../core/events.js';
|
|
5
|
+
import { sendNotification } from './notifications.js';
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEY = 'claudeck-perm-mode';
|
|
8
|
+
|
|
9
|
+
// Per-session always-allow set (cleared on refresh or /new)
|
|
10
|
+
const alwaysAllowTools = new Set();
|
|
11
|
+
|
|
12
|
+
// Permission request queue — one modal at a time
|
|
13
|
+
const permissionQueue = [];
|
|
14
|
+
let activeRequest = null;
|
|
15
|
+
|
|
16
|
+
export function getPermissionMode() {
|
|
17
|
+
return $.permModeSelect?.value || 'confirmDangerous';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function clearSessionPermissions() {
|
|
21
|
+
alwaysAllowTools.clear();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function enqueuePermissionRequest(msg) {
|
|
25
|
+
// Check if tool is in always-allow set — auto-approve without modal
|
|
26
|
+
if (alwaysAllowTools.has(msg.toolName)) {
|
|
27
|
+
sendPermissionResponse(msg.id, 'allow');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
permissionQueue.push(msg);
|
|
32
|
+
if (!activeRequest) {
|
|
33
|
+
processNext();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function processNext() {
|
|
38
|
+
if (permissionQueue.length === 0) {
|
|
39
|
+
activeRequest = null;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
activeRequest = permissionQueue.shift();
|
|
43
|
+
showPermissionModal(activeRequest);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function showPermissionModal(req) {
|
|
47
|
+
$.permModalToolName.textContent = `Tool Approval: ${req.toolName}`;
|
|
48
|
+
$.permModalSummary.textContent = getToolSummary(req.toolName, req.input);
|
|
49
|
+
$.permModalInput.textContent = JSON.stringify(req.input, null, 2);
|
|
50
|
+
$.permAlwaysAllowTool.textContent = req.toolName;
|
|
51
|
+
$.permAlwaysAllowCb.checked = false;
|
|
52
|
+
|
|
53
|
+
// Show background session badge if request is from a bg session
|
|
54
|
+
const bgBadge = document.getElementById('perm-bg-badge');
|
|
55
|
+
if (bgBadge) {
|
|
56
|
+
if (req._bgSessionTitle) {
|
|
57
|
+
bgBadge.textContent = `bg: ${req._bgSessionTitle}`;
|
|
58
|
+
bgBadge.classList.remove('hidden');
|
|
59
|
+
} else {
|
|
60
|
+
bgBadge.classList.add('hidden');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$.permModal.classList.remove('hidden');
|
|
65
|
+
$.permAllowBtn.focus();
|
|
66
|
+
|
|
67
|
+
// Notify when tab is not focused
|
|
68
|
+
const bgLabel = req._bgSessionTitle ? ` (${req._bgSessionTitle})` : '';
|
|
69
|
+
sendNotification('Tool Approval Needed', `${req.toolName}${bgLabel}`, `perm-${req.id}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getToolSummary(name, input) {
|
|
73
|
+
if (!input) return name;
|
|
74
|
+
if (input.file_path) return input.file_path;
|
|
75
|
+
if (input.command) return input.command.slice(0, 120);
|
|
76
|
+
if (input.pattern) return input.pattern;
|
|
77
|
+
if (input.query) return input.query.slice(0, 120);
|
|
78
|
+
if (input.prompt) return input.prompt.slice(0, 120);
|
|
79
|
+
if (input.url) return input.url;
|
|
80
|
+
return name;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hideModal() {
|
|
84
|
+
$.permModal.classList.add('hidden');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleAllow() {
|
|
88
|
+
if (!activeRequest) return;
|
|
89
|
+
const id = activeRequest.id;
|
|
90
|
+
const toolName = activeRequest.toolName;
|
|
91
|
+
if ($.permAlwaysAllowCb.checked) {
|
|
92
|
+
alwaysAllowTools.add(toolName);
|
|
93
|
+
}
|
|
94
|
+
hideModal();
|
|
95
|
+
sendPermissionResponse(id, 'allow');
|
|
96
|
+
activeRequest = null;
|
|
97
|
+
processNext();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleDeny() {
|
|
101
|
+
if (!activeRequest) return;
|
|
102
|
+
const id = activeRequest.id;
|
|
103
|
+
hideModal();
|
|
104
|
+
sendPermissionResponse(id, 'deny');
|
|
105
|
+
activeRequest = null;
|
|
106
|
+
processNext();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function sendPermissionResponse(id, behavior) {
|
|
110
|
+
const ws = getState('ws');
|
|
111
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
112
|
+
ws.send(JSON.stringify({ type: 'permission_response', id, behavior }));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Handle external permission response (e.g., from Telegram).
|
|
118
|
+
* Auto-dismisses the modal if it's showing the same approval.
|
|
119
|
+
*/
|
|
120
|
+
export function handleExternalPermissionResponse(id, behavior) {
|
|
121
|
+
// If the active modal is for this approval, dismiss it
|
|
122
|
+
if (activeRequest && activeRequest.id === id) {
|
|
123
|
+
hideModal();
|
|
124
|
+
activeRequest = null;
|
|
125
|
+
processNext();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If it's in the queue, remove it
|
|
130
|
+
const idx = permissionQueue.findIndex((r) => r.id === id);
|
|
131
|
+
if (idx !== -1) {
|
|
132
|
+
permissionQueue.splice(idx, 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Block Escape from closing this modal (capture phase, before shortcuts.js closeAllModals)
|
|
137
|
+
document.addEventListener('keydown', (e) => {
|
|
138
|
+
if (e.key === 'Escape' && activeRequest && !$.permModal.classList.contains('hidden')) {
|
|
139
|
+
e.stopImmediatePropagation();
|
|
140
|
+
}
|
|
141
|
+
}, true);
|
|
142
|
+
|
|
143
|
+
// Clean up on disconnect
|
|
144
|
+
onEvent('ws:disconnected', () => {
|
|
145
|
+
hideModal();
|
|
146
|
+
permissionQueue.length = 0;
|
|
147
|
+
activeRequest = null;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ── Init ──
|
|
151
|
+
function initPermissions() {
|
|
152
|
+
// Restore saved mode
|
|
153
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
154
|
+
if (saved && $.permModeSelect) {
|
|
155
|
+
$.permModeSelect.value = saved;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Persist mode changes
|
|
159
|
+
$.permModeSelect?.addEventListener('change', () => {
|
|
160
|
+
localStorage.setItem(STORAGE_KEY, $.permModeSelect.value);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Button handlers
|
|
164
|
+
$.permAllowBtn?.addEventListener('click', handleAllow);
|
|
165
|
+
$.permDenyBtn?.addEventListener('click', handleDeny);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
initPermissions();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Right Panel — tabbed container for Tasks, Files, Git, and plugin tabs
|
|
2
|
+
import { $ } from "../core/dom.js";
|
|
3
|
+
import { emit, on } from "../core/events.js";
|
|
4
|
+
import { initTabSDK } from "./tab-sdk.js";
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY = "claudeck-right-panel";
|
|
7
|
+
const TAB_KEY = "claudeck-right-panel-tab";
|
|
8
|
+
const WIDTH_KEY = "claudeck-right-panel-width";
|
|
9
|
+
const OLD_LINEAR_KEY = "claudeck-linear-panel";
|
|
10
|
+
const MIN_WIDTH = 200;
|
|
11
|
+
const MAX_WIDTH_RATIO = 0.6; // 60vw
|
|
12
|
+
|
|
13
|
+
function isPanelOpen() {
|
|
14
|
+
return !$.rightPanel.classList.contains("hidden");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getActiveTab() {
|
|
18
|
+
const btn = $.rightPanel.querySelector(".right-panel-tab.active");
|
|
19
|
+
return btn ? btn.dataset.tab : TABS[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isValidTab(tabName) {
|
|
23
|
+
return !!$.rightPanel.querySelector(`.right-panel-tab[data-tab="${tabName}"]`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function openRightPanel(tabName) {
|
|
27
|
+
if (tabName && isValidTab(tabName)) {
|
|
28
|
+
switchTab(tabName);
|
|
29
|
+
}
|
|
30
|
+
$.rightPanel.classList.remove("hidden");
|
|
31
|
+
$.rightPanelToggleBtn.classList.add("active");
|
|
32
|
+
localStorage.setItem(STORAGE_KEY, "open");
|
|
33
|
+
emit("rightPanel:opened", getActiveTab());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function closeRightPanel() {
|
|
37
|
+
$.rightPanel.classList.add("hidden");
|
|
38
|
+
$.rightPanelToggleBtn.classList.remove("active");
|
|
39
|
+
localStorage.setItem(STORAGE_KEY, "closed");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function toggleRightPanel(tabName) {
|
|
43
|
+
if (isPanelOpen()) {
|
|
44
|
+
// If clicking same tab, close. If different tab, switch.
|
|
45
|
+
if (tabName && tabName !== getActiveTab()) {
|
|
46
|
+
switchTab(tabName);
|
|
47
|
+
emit("rightPanel:tabChanged", tabName);
|
|
48
|
+
} else {
|
|
49
|
+
closeRightPanel();
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
openRightPanel(tabName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function switchTab(tabName) {
|
|
57
|
+
if (!isValidTab(tabName)) return;
|
|
58
|
+
applyTab(tabName);
|
|
59
|
+
emit("rightPanel:tabChanged", tabName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Visual-only tab switch (no event emitted) — used during init restore
|
|
63
|
+
function restoreTab(tabName) {
|
|
64
|
+
if (!isValidTab(tabName)) return;
|
|
65
|
+
applyTab(tabName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applyTab(tabName) {
|
|
69
|
+
// Update tab buttons
|
|
70
|
+
$.rightPanel.querySelectorAll(".right-panel-tab").forEach((btn) => {
|
|
71
|
+
btn.classList.toggle("active", btn.dataset.tab === tabName);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Update panes
|
|
75
|
+
$.rightPanel.querySelectorAll(".right-panel-pane").forEach((pane) => {
|
|
76
|
+
pane.classList.toggle("active", pane.dataset.tab === tabName);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
localStorage.setItem(TAB_KEY, tabName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function initRightPanel() {
|
|
83
|
+
// Migrate old linear panel localStorage key
|
|
84
|
+
const oldState = localStorage.getItem(OLD_LINEAR_KEY);
|
|
85
|
+
if (oldState && !localStorage.getItem(STORAGE_KEY)) {
|
|
86
|
+
localStorage.setItem(STORAGE_KEY, oldState);
|
|
87
|
+
localStorage.removeItem(OLD_LINEAR_KEY);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Tab click handlers
|
|
91
|
+
$.rightPanel.querySelectorAll(".right-panel-tab").forEach((btn) => {
|
|
92
|
+
btn.addEventListener("click", () => {
|
|
93
|
+
const tab = btn.dataset.tab;
|
|
94
|
+
if (isPanelOpen() && getActiveTab() === tab) return;
|
|
95
|
+
switchTab(tab);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Close button
|
|
100
|
+
$.rightPanelClose.addEventListener("click", () => closeRightPanel());
|
|
101
|
+
|
|
102
|
+
// Header toggle button
|
|
103
|
+
$.rightPanelToggleBtn.addEventListener("click", () => toggleRightPanel());
|
|
104
|
+
|
|
105
|
+
// Initialize Tab SDK — allows plugin tabs to register
|
|
106
|
+
initTabSDK();
|
|
107
|
+
|
|
108
|
+
// Restore saved tab (silently — no event, listeners aren't ready yet)
|
|
109
|
+
const savedTab = localStorage.getItem(TAB_KEY);
|
|
110
|
+
if (savedTab && isValidTab(savedTab)) {
|
|
111
|
+
restoreTab(savedTab);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Restore saved width
|
|
115
|
+
const savedWidth = localStorage.getItem(WIDTH_KEY);
|
|
116
|
+
if (savedWidth) {
|
|
117
|
+
const w = parseInt(savedWidth, 10);
|
|
118
|
+
if (w >= MIN_WIDTH) {
|
|
119
|
+
$.rightPanel.style.width = w + "px";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Restore panel state (silently — defer event so other modules register first)
|
|
124
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
125
|
+
if (saved === "open") {
|
|
126
|
+
$.rightPanel.classList.remove("hidden");
|
|
127
|
+
$.rightPanelToggleBtn.classList.add("active");
|
|
128
|
+
// Emit after all modules have initialized their event listeners
|
|
129
|
+
queueMicrotask(() => emit("rightPanel:opened", getActiveTab()));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Resize by dragging left edge ────────────────────
|
|
133
|
+
initResize();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function initResize() {
|
|
137
|
+
const handle = document.getElementById("right-panel-resize");
|
|
138
|
+
if (!handle) return;
|
|
139
|
+
|
|
140
|
+
let startX = 0;
|
|
141
|
+
let startWidth = 0;
|
|
142
|
+
|
|
143
|
+
function onMouseDown(e) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
startX = e.clientX;
|
|
146
|
+
startWidth = $.rightPanel.offsetWidth;
|
|
147
|
+
handle.classList.add("active");
|
|
148
|
+
$.rightPanel.classList.add("resizing");
|
|
149
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
150
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function onMouseMove(e) {
|
|
154
|
+
const maxWidth = window.innerWidth * MAX_WIDTH_RATIO;
|
|
155
|
+
// Dragging left increases width (panel is on the right)
|
|
156
|
+
const delta = startX - e.clientX;
|
|
157
|
+
const newWidth = Math.min(Math.max(startWidth + delta, MIN_WIDTH), maxWidth);
|
|
158
|
+
$.rightPanel.style.width = newWidth + "px";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function onMouseUp() {
|
|
162
|
+
handle.classList.remove("active");
|
|
163
|
+
$.rightPanel.classList.remove("resizing");
|
|
164
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
165
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
166
|
+
// Persist
|
|
167
|
+
localStorage.setItem(WIDTH_KEY, Math.round($.rightPanel.offsetWidth));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handle.addEventListener("mousedown", onMouseDown);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
initRightPanel();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Keyboard shortcuts
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
import { getState, setState } from '../core/store.js';
|
|
4
|
+
import { CHAT_IDS } from '../core/constants.js';
|
|
5
|
+
import { panes } from './parallel.js';
|
|
6
|
+
import { registerCommand } from './commands.js';
|
|
7
|
+
import { toggleRightPanel, openRightPanel } from './right-panel.js';
|
|
8
|
+
import { toggleTipsFeed } from '../panels/tips-feed.js';
|
|
9
|
+
|
|
10
|
+
function closeAllModals() {
|
|
11
|
+
document.querySelectorAll(".modal-overlay:not([data-persistent])").forEach((m) => m.classList.add("hidden"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
document.getElementById("shortcuts-modal-close").addEventListener("click", () => {
|
|
15
|
+
$.shortcutsModal.classList.add("hidden");
|
|
16
|
+
});
|
|
17
|
+
$.shortcutsModal.addEventListener("click", (e) => {
|
|
18
|
+
if (e.target === $.shortcutsModal) $.shortcutsModal.classList.add("hidden");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
document.addEventListener("keydown", (e) => {
|
|
22
|
+
const isMeta = e.metaKey || e.ctrlKey;
|
|
23
|
+
|
|
24
|
+
if (e.key === "Escape") {
|
|
25
|
+
closeAllModals();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tag = document.activeElement?.tagName;
|
|
30
|
+
if (!isMeta && (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT")) return;
|
|
31
|
+
|
|
32
|
+
if (isMeta && e.key === "k") {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
$.sessionSearchInput.focus();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isMeta && e.key === "n") {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
$.newSessionBtn.click();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isMeta && e.key === "/") {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
$.shortcutsModal.classList.toggle("hidden");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Cmd+B — Toggle right panel
|
|
51
|
+
if (isMeta && e.key === "b") {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
toggleRightPanel();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Cmd+Shift+E — Open Files tab
|
|
58
|
+
if (isMeta && e.shiftKey && e.key === "E") {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
openRightPanel("files");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Cmd+Shift+G — Open Git tab
|
|
65
|
+
if (isMeta && e.shiftKey && e.key === "G") {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
openRightPanel("git");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Cmd+Shift+R — Open Repos tab
|
|
72
|
+
if (isMeta && e.shiftKey && e.key === "R") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
openRightPanel("repos");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Cmd+Shift+V — Open Events tab
|
|
79
|
+
if (isMeta && e.shiftKey && e.key === "V") {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
openRightPanel("events");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Cmd+Shift+A — Go to Home (Analytics)
|
|
86
|
+
if (isMeta && e.shiftKey && e.key === "A") {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
setState("view", "home");
|
|
89
|
+
setState("sessionId", null);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Cmd+Shift+T — Toggle Tips Feed
|
|
94
|
+
if (isMeta && e.shiftKey && e.key === "T") {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
toggleTipsFeed();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isMeta && getState("parallelMode") && e.key >= "1" && e.key <= "4") {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
const idx = parseInt(e.key) - 1;
|
|
103
|
+
const chatId = CHAT_IDS[idx];
|
|
104
|
+
const pane = panes.get(chatId);
|
|
105
|
+
if (pane && pane.messageInput) {
|
|
106
|
+
pane.messageInput.focus();
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
registerCommand("shortcuts", {
|
|
113
|
+
category: "app",
|
|
114
|
+
description: "Show keyboard shortcuts",
|
|
115
|
+
execute() {
|
|
116
|
+
$.shortcutsModal.classList.remove("hidden");
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Register /files and /git slash commands
|
|
121
|
+
registerCommand("files", {
|
|
122
|
+
category: "app",
|
|
123
|
+
description: "Open file explorer",
|
|
124
|
+
execute() {
|
|
125
|
+
openRightPanel("files");
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
registerCommand("git", {
|
|
130
|
+
category: "app",
|
|
131
|
+
description: "Open git panel",
|
|
132
|
+
execute() {
|
|
133
|
+
openRightPanel("git");
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
registerCommand("events", {
|
|
138
|
+
category: "app",
|
|
139
|
+
description: "Open events panel",
|
|
140
|
+
execute() {
|
|
141
|
+
openRightPanel("events");
|
|
142
|
+
},
|
|
143
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Sidebar hamburger toggle for mobile/tablet viewports
|
|
2
|
+
import { $ } from '../core/dom.js';
|
|
3
|
+
|
|
4
|
+
const btn = document.getElementById('sidebar-toggle-btn');
|
|
5
|
+
const backdrop = document.getElementById('sidebar-backdrop');
|
|
6
|
+
const mq = window.matchMedia('(max-width: 1024px)');
|
|
7
|
+
|
|
8
|
+
function openSidebar() { document.body.classList.add('sidebar-open'); }
|
|
9
|
+
function closeSidebar() { document.body.classList.remove('sidebar-open'); }
|
|
10
|
+
|
|
11
|
+
if (btn) btn.addEventListener('click', () => {
|
|
12
|
+
document.body.classList.toggle('sidebar-open');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (backdrop) backdrop.addEventListener('click', closeSidebar);
|
|
16
|
+
|
|
17
|
+
// Close sidebar when a session is selected on mobile
|
|
18
|
+
if ($.sessionList) {
|
|
19
|
+
$.sessionList.addEventListener('click', (e) => {
|
|
20
|
+
if (mq.matches && e.target.closest('li')) {
|
|
21
|
+
closeSidebar();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Close sidebar if viewport grows past tablet breakpoint
|
|
27
|
+
mq.addEventListener('change', (e) => {
|
|
28
|
+
if (!e.matches) closeSidebar();
|
|
29
|
+
});
|