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,36 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { getMessages, getMessagesByChatId, getMessagesNoChatId } from "../../db.js";
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
// Get all messages for a session
|
|
7
|
+
router.get("/:id/messages", (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const messages = getMessages(req.params.id);
|
|
10
|
+
res.json(messages);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
res.status(500).json({ error: err.message });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Get messages filtered by chatId
|
|
17
|
+
router.get("/:id/messages/:chatId", (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const messages = getMessagesByChatId(req.params.id, req.params.chatId);
|
|
20
|
+
res.json(messages);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
res.status(500).json({ error: err.message });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Get messages where chat_id IS NULL (single-mode)
|
|
27
|
+
router.get("/:id/messages-single", (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const messages = getMessagesNoChatId(req.params.id);
|
|
30
|
+
res.json(messages);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
res.status(500).json({ error: err.message });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default router;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { upsertPushSubscription, deletePushSubscription } from "../../db.js";
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
let vapidPublicKey = null;
|
|
7
|
+
|
|
8
|
+
export function setVapidPublicKey(key) {
|
|
9
|
+
vapidPublicKey = key;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
router.get("/vapid-public-key", (req, res) => {
|
|
13
|
+
if (!vapidPublicKey) {
|
|
14
|
+
return res.status(404).json({ error: "Push notifications not configured" });
|
|
15
|
+
}
|
|
16
|
+
res.json({ key: vapidPublicKey });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
router.post("/subscribe", (req, res) => {
|
|
20
|
+
const { endpoint, keys } = req.body;
|
|
21
|
+
if (!endpoint || !keys?.p256dh || !keys?.auth) {
|
|
22
|
+
return res.status(400).json({ error: "Invalid subscription" });
|
|
23
|
+
}
|
|
24
|
+
upsertPushSubscription(endpoint, keys.p256dh, keys.auth);
|
|
25
|
+
res.json({ ok: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
router.post("/unsubscribe", (req, res) => {
|
|
29
|
+
const { endpoint } = req.body;
|
|
30
|
+
if (!endpoint) {
|
|
31
|
+
return res.status(400).json({ error: "Missing endpoint" });
|
|
32
|
+
}
|
|
33
|
+
deletePushSubscription(endpoint);
|
|
34
|
+
res.json({ ok: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default router;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { readFile, readdir, stat, writeFile } from "fs/promises";
|
|
3
|
+
import { join, resolve, isAbsolute, dirname } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { configPath } from "../paths.js";
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
// Load project configs into memory
|
|
10
|
+
let projectConfigs = [];
|
|
11
|
+
export async function loadProjectConfigs() {
|
|
12
|
+
try {
|
|
13
|
+
const data = await readFile(configPath("folders.json"), "utf-8");
|
|
14
|
+
projectConfigs = JSON.parse(data);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
console.error("Failed to load project configs:", err.message);
|
|
17
|
+
projectConfigs = [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
loadProjectConfigs();
|
|
21
|
+
|
|
22
|
+
export function getProjectSystemPrompt(cwd) {
|
|
23
|
+
const project = projectConfigs.find((p) => p.path === cwd);
|
|
24
|
+
return project?.systemPrompt || "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Serve configured project folders
|
|
28
|
+
router.get("/", async (req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
const data = await readFile(configPath("folders.json"), "utf-8");
|
|
31
|
+
res.json(JSON.parse(data));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
res.status(500).json({ error: err.message });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Save/clear system prompt for a project
|
|
38
|
+
router.put("/system-prompt", async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { path: projectPath, systemPrompt } = req.body;
|
|
41
|
+
if (!projectPath) return res.status(400).json({ error: "path is required" });
|
|
42
|
+
const filePath = configPath("folders.json");
|
|
43
|
+
const data = JSON.parse(await readFile(filePath, "utf-8"));
|
|
44
|
+
const project = data.find((p) => p.path === projectPath);
|
|
45
|
+
if (!project) return res.status(404).json({ error: "Project not found" });
|
|
46
|
+
project.systemPrompt = systemPrompt || "";
|
|
47
|
+
const { writeFile } = await import("fs/promises");
|
|
48
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
49
|
+
await loadProjectConfigs();
|
|
50
|
+
res.json({ ok: true });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(500).json({ error: err.message });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Browse directories on the server filesystem
|
|
57
|
+
router.get("/browse", async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const requestedDir = req.query.dir || homedir();
|
|
60
|
+
const current = resolve(requestedDir);
|
|
61
|
+
|
|
62
|
+
// Security: ensure resolved path is valid
|
|
63
|
+
if (!isAbsolute(current)) {
|
|
64
|
+
return res.status(400).json({ error: "Invalid path" });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const s = await stat(current);
|
|
68
|
+
if (!s.isDirectory()) {
|
|
69
|
+
return res.status(400).json({ error: "Not a directory" });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
73
|
+
const dirs = entries
|
|
74
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
75
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
76
|
+
.map((e) => ({ name: e.name, path: join(current, e.name) }));
|
|
77
|
+
|
|
78
|
+
const parent = current === "/" ? null : dirname(current);
|
|
79
|
+
res.json({ current, parent, dirs });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
res.status(500).json({ error: err.message });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Add a new project to folders.json
|
|
86
|
+
router.post("/", async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const { name, path: projectPath } = req.body;
|
|
89
|
+
if (!name || !projectPath) {
|
|
90
|
+
return res.status(400).json({ error: "name and path are required" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resolvedPath = resolve(projectPath);
|
|
94
|
+
|
|
95
|
+
// Validate path exists and is a directory
|
|
96
|
+
const s = await stat(resolvedPath);
|
|
97
|
+
if (!s.isDirectory()) {
|
|
98
|
+
return res.status(400).json({ error: "Path is not a directory" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const filePath = configPath("folders.json");
|
|
102
|
+
const data = JSON.parse(await readFile(filePath, "utf-8"));
|
|
103
|
+
|
|
104
|
+
// Check for duplicate path
|
|
105
|
+
if (data.some((p) => p.path === resolvedPath)) {
|
|
106
|
+
return res.status(409).json({ error: "Project with this path already exists" });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
data.push({ name, path: resolvedPath });
|
|
110
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
111
|
+
await loadProjectConfigs();
|
|
112
|
+
res.json({ ok: true, project: { name, path: resolvedPath } });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
res.status(500).json({ error: err.message });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Remove a project from folders.json
|
|
119
|
+
router.delete("/", async (req, res) => {
|
|
120
|
+
try {
|
|
121
|
+
const { path: projectPath } = req.body;
|
|
122
|
+
if (!projectPath) {
|
|
123
|
+
return res.status(400).json({ error: "path is required" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const filePath = configPath("folders.json");
|
|
127
|
+
const data = JSON.parse(await readFile(filePath, "utf-8"));
|
|
128
|
+
const filtered = data.filter((p) => p.path !== projectPath);
|
|
129
|
+
|
|
130
|
+
if (filtered.length === data.length) {
|
|
131
|
+
return res.status(404).json({ error: "Project not found" });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await writeFile(filePath, JSON.stringify(filtered, null, 2) + "\n");
|
|
135
|
+
await loadProjectConfigs();
|
|
136
|
+
res.json({ ok: true });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
res.status(500).json({ error: err.message });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Read project commands from .claude/commands/*.md and .claude/skills/*/SKILL.md
|
|
143
|
+
router.get("/commands", async (req, res) => {
|
|
144
|
+
const projectPath = req.query.path;
|
|
145
|
+
if (!projectPath) return res.status(400).json({ error: "path is required" });
|
|
146
|
+
|
|
147
|
+
const { readdir, stat } = await import("fs/promises");
|
|
148
|
+
const commands = [];
|
|
149
|
+
|
|
150
|
+
// 1. Read .claude/commands/*.md and .claude/commands/<subfolder>/*.md
|
|
151
|
+
const commandsDir = join(projectPath, ".claude", "commands");
|
|
152
|
+
async function readCommandsRecursive(dir, prefix) {
|
|
153
|
+
try {
|
|
154
|
+
const entries = await readdir(dir);
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const entryPath = join(dir, entry);
|
|
157
|
+
if (!entryPath.startsWith(commandsDir)) continue;
|
|
158
|
+
try {
|
|
159
|
+
const s = await stat(entryPath);
|
|
160
|
+
if (s.isDirectory()) {
|
|
161
|
+
await readCommandsRecursive(entryPath, prefix ? `${prefix}:${entry}` : entry);
|
|
162
|
+
} else if (entry.endsWith(".md")) {
|
|
163
|
+
const content = await readFile(entryPath, "utf-8");
|
|
164
|
+
const name = prefix ? `${prefix}:${entry.replace(/\.md$/, "")}` : entry.replace(/\.md$/, "");
|
|
165
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
166
|
+
const description = titleMatch ? titleMatch[1].trim() : name;
|
|
167
|
+
commands.push({ command: name, description, prompt: content, source: "command" });
|
|
168
|
+
}
|
|
169
|
+
} catch { /* skip unreadable entries */ }
|
|
170
|
+
}
|
|
171
|
+
} catch { /* directory doesn't exist or unreadable */ }
|
|
172
|
+
}
|
|
173
|
+
await readCommandsRecursive(commandsDir, "");
|
|
174
|
+
|
|
175
|
+
// 2. Read .claude/skills/*/SKILL.md
|
|
176
|
+
const skillsDir = join(projectPath, ".claude", "skills");
|
|
177
|
+
try {
|
|
178
|
+
const entries = await readdir(skillsDir);
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
try {
|
|
181
|
+
const entryPath = join(skillsDir, entry);
|
|
182
|
+
const s = await stat(entryPath);
|
|
183
|
+
if (!s.isDirectory()) continue;
|
|
184
|
+
const skillFile = join(entryPath, "SKILL.md");
|
|
185
|
+
const content = await readFile(skillFile, "utf-8");
|
|
186
|
+
let name = entry;
|
|
187
|
+
let description = entry;
|
|
188
|
+
let argumentHint = "";
|
|
189
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
190
|
+
if (fmMatch) {
|
|
191
|
+
const fm = fmMatch[1];
|
|
192
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
193
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
194
|
+
const argMatch = fm.match(/^argument-hint:\s*"?(.+?)"?\s*$/m);
|
|
195
|
+
if (nameMatch) name = nameMatch[1].trim();
|
|
196
|
+
if (descMatch) description = descMatch[1].trim();
|
|
197
|
+
if (argMatch) argumentHint = argMatch[1].trim();
|
|
198
|
+
}
|
|
199
|
+
commands.push({ command: name, description, prompt: content, source: "skill", argumentHint });
|
|
200
|
+
} catch { /* skip unreadable skill dirs */ }
|
|
201
|
+
}
|
|
202
|
+
} catch { /* .claude/skills/ doesn't exist */ }
|
|
203
|
+
|
|
204
|
+
res.json(commands);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export default router;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { configPath } from "../paths.js";
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
// Serve prompt toolbox
|
|
8
|
+
router.get("/", async (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const data = await readFile(configPath("prompts.json"), "utf-8");
|
|
11
|
+
res.json(JSON.parse(data));
|
|
12
|
+
} catch (err) {
|
|
13
|
+
res.status(500).json({ error: err.message });
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Add a new prompt
|
|
18
|
+
router.post("/", async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const { title, description, prompt } = req.body;
|
|
21
|
+
if (!title || !description || !prompt) {
|
|
22
|
+
return res.status(400).json({ error: "title, description, and prompt are required" });
|
|
23
|
+
}
|
|
24
|
+
const filePath = configPath("prompts.json");
|
|
25
|
+
const data = JSON.parse(await readFile(filePath, "utf-8"));
|
|
26
|
+
data.push({ title, description, prompt });
|
|
27
|
+
const { writeFile } = await import("fs/promises");
|
|
28
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
29
|
+
res.json({ ok: true });
|
|
30
|
+
} catch (err) {
|
|
31
|
+
res.status(500).json({ error: err.message });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Delete a prompt by index
|
|
36
|
+
router.delete("/:index", async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const idx = parseInt(req.params.index, 10);
|
|
39
|
+
const filePath = configPath("prompts.json");
|
|
40
|
+
const data = JSON.parse(await readFile(filePath, "utf-8"));
|
|
41
|
+
if (idx < 0 || idx >= data.length) {
|
|
42
|
+
return res.status(404).json({ error: "Prompt not found" });
|
|
43
|
+
}
|
|
44
|
+
data.splice(idx, 1);
|
|
45
|
+
const { writeFile } = await import("fs/promises");
|
|
46
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
47
|
+
res.json({ ok: true });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
res.status(500).json({ error: err.message });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default router;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import {
|
|
3
|
+
listSessions,
|
|
4
|
+
deleteSession as dbDeleteSession,
|
|
5
|
+
updateSessionTitle,
|
|
6
|
+
toggleSessionPin,
|
|
7
|
+
searchSessions,
|
|
8
|
+
} from "../../db.js";
|
|
9
|
+
import { getActiveSessionIds } from "../ws-handler.js";
|
|
10
|
+
import { generateSessionSummary } from "../summarizer.js";
|
|
11
|
+
|
|
12
|
+
const router = Router();
|
|
13
|
+
|
|
14
|
+
// sessionIds map is passed in from the parent
|
|
15
|
+
let sessionIds;
|
|
16
|
+
export function setSessionIds(map) {
|
|
17
|
+
sessionIds = map;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// List sessions (optionally filtered by project_path)
|
|
21
|
+
router.get("/", (req, res) => {
|
|
22
|
+
try {
|
|
23
|
+
const projectPath = req.query.project_path || undefined;
|
|
24
|
+
const sessions = listSessions(20, projectPath);
|
|
25
|
+
res.json(sessions);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
res.status(500).json({ error: err.message });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Search sessions
|
|
32
|
+
router.get("/search", (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const q = req.query.q || "";
|
|
35
|
+
const projectPath = req.query.project_path || undefined;
|
|
36
|
+
const sessions = searchSessions(q, 20, projectPath);
|
|
37
|
+
res.json(sessions);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
res.status(500).json({ error: err.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// List session IDs with active (in-flight) queries
|
|
44
|
+
router.get("/active", (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
res.json({ activeSessionIds: getActiveSessionIds() });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
res.status(500).json({ error: err.message });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Delete a session
|
|
53
|
+
router.delete("/:id", (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const id = req.params.id;
|
|
56
|
+
dbDeleteSession(id);
|
|
57
|
+
// Clean up sessionIds map entries for this session
|
|
58
|
+
for (const [key] of sessionIds) {
|
|
59
|
+
if (key === id || key.startsWith(id + "::")) {
|
|
60
|
+
sessionIds.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
res.json({ ok: true });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
res.status(500).json({ error: err.message });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Update session title
|
|
70
|
+
router.put("/:id/title", (req, res) => {
|
|
71
|
+
try {
|
|
72
|
+
const { title } = req.body;
|
|
73
|
+
if (typeof title !== "string") {
|
|
74
|
+
return res.status(400).json({ error: "title is required" });
|
|
75
|
+
}
|
|
76
|
+
updateSessionTitle(req.params.id, title.slice(0, 200));
|
|
77
|
+
res.json({ ok: true });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
res.status(500).json({ error: err.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Toggle session pin
|
|
84
|
+
router.put("/:id/pin", (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
toggleSessionPin(req.params.id);
|
|
87
|
+
res.json({ ok: true });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
res.status(500).json({ error: err.message });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Generate/regenerate summary on demand
|
|
94
|
+
router.post("/:id/summary", async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
const summary = await generateSessionSummary(req.params.id);
|
|
97
|
+
res.json({ ok: true, summary });
|
|
98
|
+
} catch (err) {
|
|
99
|
+
res.status(500).json({ error: err.message });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export default router;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import {
|
|
7
|
+
getTotalCost, getProjectCost, getSessionCosts, getCostTimeline, getTotalTokens, getProjectTokens,
|
|
8
|
+
getAnalyticsOverview, getDailyBreakdown, getHourlyActivity, getProjectBreakdown,
|
|
9
|
+
getTopSessionsByCost, getToolUsage, getToolErrors, getSessionDepth,
|
|
10
|
+
getMsgLengthDistribution, getTopBashCommands, getTopFiles,
|
|
11
|
+
getErrorCategories, getErrorTimeline, getErrorsByTool, getRecentErrors,
|
|
12
|
+
getModelUsage, getCacheEfficiency, getYearlyActivity,
|
|
13
|
+
getAgentRunsOverview, getAgentRunsSummary, getAgentRunsByType, getAgentRunsDaily, getAgentRunsRecent,
|
|
14
|
+
} from "../../db.js";
|
|
15
|
+
|
|
16
|
+
const router = Router();
|
|
17
|
+
|
|
18
|
+
// Account info — cached in memory
|
|
19
|
+
let cachedAccountInfo = null;
|
|
20
|
+
|
|
21
|
+
// Resolve claude binary — check common locations if not on PATH
|
|
22
|
+
function findClaudeBinary() {
|
|
23
|
+
const home = homedir();
|
|
24
|
+
const candidates = [
|
|
25
|
+
join(home, ".local", "bin", "claude"), // Linux
|
|
26
|
+
"/usr/local/bin/claude", // macOS Homebrew
|
|
27
|
+
];
|
|
28
|
+
if (process.platform === "win32") {
|
|
29
|
+
candidates.push(join(home, "AppData", "Local", "Programs", "claude", "claude.exe"));
|
|
30
|
+
candidates.push(join(home, ".claude", "local", "claude.exe"));
|
|
31
|
+
}
|
|
32
|
+
for (const p of candidates) {
|
|
33
|
+
if (existsSync(p)) return p;
|
|
34
|
+
}
|
|
35
|
+
return "claude"; // fallback to PATH
|
|
36
|
+
}
|
|
37
|
+
const claudeBin = findClaudeBinary();
|
|
38
|
+
|
|
39
|
+
router.get("/account", async (req, res) => {
|
|
40
|
+
if (cachedAccountInfo) {
|
|
41
|
+
return res.json(cachedAccountInfo);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const data = await new Promise((resolve, reject) => {
|
|
45
|
+
execFile(claudeBin, ["auth", "status"], { timeout: 10000 }, (err, stdout) => {
|
|
46
|
+
if (err) return reject(err);
|
|
47
|
+
try { resolve(JSON.parse(stdout)); } catch (e) { reject(e); }
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
cachedAccountInfo = {
|
|
51
|
+
email: data.email || null,
|
|
52
|
+
plan: data.subscriptionType || null,
|
|
53
|
+
};
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error("Failed to fetch account info:", err.message);
|
|
56
|
+
cachedAccountInfo = { email: null, plan: null };
|
|
57
|
+
}
|
|
58
|
+
res.json(cachedAccountInfo);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Cost dashboard
|
|
62
|
+
router.get("/dashboard", (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
const projectPath = req.query.project_path || undefined;
|
|
65
|
+
const sessions = getSessionCosts(projectPath);
|
|
66
|
+
const timeline = getCostTimeline(projectPath);
|
|
67
|
+
const totalCost = getTotalCost();
|
|
68
|
+
const projectCost = projectPath ? getProjectCost(projectPath) : null;
|
|
69
|
+
const totalTokens = getTotalTokens();
|
|
70
|
+
const projectTokens = projectPath ? getProjectTokens(projectPath) : null;
|
|
71
|
+
res.json({ sessions, timeline, totalCost, projectCost, totalTokens, projectTokens });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
res.status(500).json({ error: err.message });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Analytics dashboard
|
|
78
|
+
router.get("/analytics", (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const projectPath = req.query.project_path || undefined;
|
|
81
|
+
res.json({
|
|
82
|
+
overview: getAnalyticsOverview(projectPath),
|
|
83
|
+
dailyBreakdown: getDailyBreakdown(projectPath),
|
|
84
|
+
hourlyActivity: getHourlyActivity(projectPath),
|
|
85
|
+
projectBreakdown: getProjectBreakdown(),
|
|
86
|
+
topSessions: getTopSessionsByCost(projectPath),
|
|
87
|
+
toolUsage: getToolUsage(projectPath),
|
|
88
|
+
toolErrors: getToolErrors(projectPath),
|
|
89
|
+
sessionDepth: getSessionDepth(projectPath),
|
|
90
|
+
msgLength: getMsgLengthDistribution(projectPath),
|
|
91
|
+
topBashCommands: getTopBashCommands(projectPath),
|
|
92
|
+
topFiles: getTopFiles(projectPath),
|
|
93
|
+
errorCategories: getErrorCategories(projectPath),
|
|
94
|
+
errorTimeline: getErrorTimeline(projectPath),
|
|
95
|
+
errorsByTool: getErrorsByTool(projectPath),
|
|
96
|
+
recentErrors: getRecentErrors(projectPath),
|
|
97
|
+
modelUsage: getModelUsage(projectPath),
|
|
98
|
+
cacheEfficiency: getCacheEfficiency(projectPath),
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.status(500).json({ error: err.message });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Home page data — yearly activity grid + overview
|
|
106
|
+
router.get("/home", (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const yearlyActivity = getYearlyActivity();
|
|
109
|
+
const overview = getAnalyticsOverview();
|
|
110
|
+
res.json({ yearlyActivity, overview });
|
|
111
|
+
} catch (err) {
|
|
112
|
+
res.status(500).json({ error: err.message });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Agent monitoring dashboard
|
|
117
|
+
router.get("/agent-metrics", (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
res.json({
|
|
120
|
+
overview: getAgentRunsOverview(),
|
|
121
|
+
agents: getAgentRunsSummary(),
|
|
122
|
+
byType: getAgentRunsByType(),
|
|
123
|
+
daily: getAgentRunsDaily(),
|
|
124
|
+
recent: getAgentRunsRecent(30),
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
res.status(500).json({ error: err.message });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Stats — total cost (optionally filtered by project_path)
|
|
132
|
+
router.get("/", (req, res) => {
|
|
133
|
+
try {
|
|
134
|
+
const projectPath = req.query.project_path;
|
|
135
|
+
const totalCost = getTotalCost();
|
|
136
|
+
const projectCost = projectPath ? getProjectCost(projectPath) : null;
|
|
137
|
+
res.json({ totalCost, projectCost });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
res.status(500).json({ error: err.message });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export default router;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
getTelegramConfig,
|
|
5
|
+
saveTelegramConfig,
|
|
6
|
+
sendTelegramNotification,
|
|
7
|
+
} from "../telegram-sender.js";
|
|
8
|
+
import { restartTelegramPoller } from "../telegram-poller.js";
|
|
9
|
+
import { configPath } from "../paths.js";
|
|
10
|
+
|
|
11
|
+
const router = Router();
|
|
12
|
+
|
|
13
|
+
// GET /config — return current config (token masked)
|
|
14
|
+
router.get("/config", (req, res) => {
|
|
15
|
+
res.json(getTelegramConfig());
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// PUT /config — save new config
|
|
19
|
+
router.put("/config", async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const { enabled, botToken, chatId, afkTimeoutMinutes, notify } = req.body;
|
|
22
|
+
|
|
23
|
+
if (typeof enabled !== "boolean") {
|
|
24
|
+
return res.status(400).json({ error: "enabled must be a boolean" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If botToken looks masked (starts with ****), keep the old one
|
|
28
|
+
const configFile = configPath("telegram-config.json");
|
|
29
|
+
|
|
30
|
+
let existingToken = "";
|
|
31
|
+
try {
|
|
32
|
+
const raw = await readFile(configFile, "utf-8");
|
|
33
|
+
existingToken = JSON.parse(raw).botToken || "";
|
|
34
|
+
} catch {}
|
|
35
|
+
|
|
36
|
+
const finalToken =
|
|
37
|
+
botToken && !botToken.startsWith("****") ? botToken : existingToken;
|
|
38
|
+
|
|
39
|
+
await saveTelegramConfig({
|
|
40
|
+
enabled,
|
|
41
|
+
botToken: finalToken,
|
|
42
|
+
chatId: chatId || "",
|
|
43
|
+
afkTimeoutMinutes: afkTimeoutMinutes || 15,
|
|
44
|
+
notify: notify || {},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Restart poller if config changed
|
|
48
|
+
restartTelegramPoller();
|
|
49
|
+
|
|
50
|
+
res.json({ ok: true });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(500).json({ error: err.message });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// POST /test — send a test message
|
|
57
|
+
router.post("/test", async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
await sendTelegramNotification(
|
|
60
|
+
"session",
|
|
61
|
+
"Claudeck Test",
|
|
62
|
+
"Telegram notifications are working!",
|
|
63
|
+
{ durationMs: 1234, costUsd: 0.0042, inputTokens: 1500, outputTokens: 800, model: "claude-sonnet-4-6", turns: 3 }
|
|
64
|
+
);
|
|
65
|
+
res.json({ ok: true });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
res.status(500).json({ error: err.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default router;
|