claudeck 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/cli.js +2 -0
  4. package/config/agent-chains.json +16 -0
  5. package/config/agent-dags.json +16 -0
  6. package/config/agents.json +46 -0
  7. package/config/bot-prompt.json +3 -0
  8. package/config/folders.json +66 -0
  9. package/config/prompts.json +92 -0
  10. package/config/repos.json +86 -0
  11. package/config/telegram-config.json +17 -0
  12. package/config/workflows.json +90 -0
  13. package/db.js +1198 -0
  14. package/package.json +55 -0
  15. package/plugins/claude-editor/client.css +171 -0
  16. package/plugins/claude-editor/client.js +183 -0
  17. package/plugins/event-stream/client.css +207 -0
  18. package/plugins/event-stream/client.js +271 -0
  19. package/plugins/linear/client.css +345 -0
  20. package/plugins/linear/client.js +380 -0
  21. package/plugins/linear/config.json +5 -0
  22. package/plugins/linear/server.js +312 -0
  23. package/plugins/repos/client.css +549 -0
  24. package/plugins/repos/client.js +663 -0
  25. package/plugins/repos/server.js +232 -0
  26. package/plugins/sudoku/client.css +196 -0
  27. package/plugins/sudoku/client.js +329 -0
  28. package/plugins/tasks/client.css +414 -0
  29. package/plugins/tasks/client.js +394 -0
  30. package/plugins/tasks/server.js +116 -0
  31. package/plugins/tic-tac-toe/client.css +167 -0
  32. package/plugins/tic-tac-toe/client.js +241 -0
  33. package/public/css/core/components.css +232 -0
  34. package/public/css/core/layout.css +330 -0
  35. package/public/css/core/print.css +18 -0
  36. package/public/css/core/reset.css +36 -0
  37. package/public/css/core/responsive.css +378 -0
  38. package/public/css/core/theme.css +116 -0
  39. package/public/css/core/variables.css +93 -0
  40. package/public/css/features/agent-monitor.css +297 -0
  41. package/public/css/features/agent-sidebar.css +525 -0
  42. package/public/css/features/agents.css +996 -0
  43. package/public/css/features/analytics.css +181 -0
  44. package/public/css/features/background-sessions.css +321 -0
  45. package/public/css/features/cost-dashboard.css +168 -0
  46. package/public/css/features/home.css +313 -0
  47. package/public/css/features/retro-terminal.css +88 -0
  48. package/public/css/features/telegram.css +127 -0
  49. package/public/css/features/tour.css +148 -0
  50. package/public/css/features/voice-input.css +60 -0
  51. package/public/css/features/welcome.css +241 -0
  52. package/public/css/panels/assistant-bot.css +442 -0
  53. package/public/css/panels/dev-docs.css +292 -0
  54. package/public/css/panels/file-explorer.css +322 -0
  55. package/public/css/panels/git-panel.css +221 -0
  56. package/public/css/panels/mcp-manager.css +199 -0
  57. package/public/css/panels/tips-feed.css +353 -0
  58. package/public/css/ui/commands.css +273 -0
  59. package/public/css/ui/context-gauge.css +76 -0
  60. package/public/css/ui/file-picker.css +69 -0
  61. package/public/css/ui/image-attachments.css +106 -0
  62. package/public/css/ui/messages.css +884 -0
  63. package/public/css/ui/modals.css +122 -0
  64. package/public/css/ui/parallel.css +217 -0
  65. package/public/css/ui/permissions.css +110 -0
  66. package/public/css/ui/right-panel.css +481 -0
  67. package/public/css/ui/sessions.css +689 -0
  68. package/public/css/ui/status-bar.css +425 -0
  69. package/public/css/ui/toolbox.css +206 -0
  70. package/public/data/tips.json +218 -0
  71. package/public/icons/favicon.png +0 -0
  72. package/public/icons/icon-192.png +0 -0
  73. package/public/icons/icon-512.png +0 -0
  74. package/public/icons/whaly.png +0 -0
  75. package/public/index.html +1140 -0
  76. package/public/js/core/api.js +591 -0
  77. package/public/js/core/constants.js +3 -0
  78. package/public/js/core/dom.js +270 -0
  79. package/public/js/core/events.js +10 -0
  80. package/public/js/core/plugin-loader.js +153 -0
  81. package/public/js/core/store.js +39 -0
  82. package/public/js/core/utils.js +25 -0
  83. package/public/js/core/ws.js +64 -0
  84. package/public/js/features/agent-monitor.js +222 -0
  85. package/public/js/features/agents.js +1209 -0
  86. package/public/js/features/analytics.js +397 -0
  87. package/public/js/features/attachments.js +251 -0
  88. package/public/js/features/background-sessions.js +475 -0
  89. package/public/js/features/chat.js +589 -0
  90. package/public/js/features/cost-dashboard.js +152 -0
  91. package/public/js/features/dag-editor.js +399 -0
  92. package/public/js/features/easter-egg.js +46 -0
  93. package/public/js/features/home.js +270 -0
  94. package/public/js/features/projects.js +372 -0
  95. package/public/js/features/prompts.js +228 -0
  96. package/public/js/features/sessions.js +332 -0
  97. package/public/js/features/telegram.js +131 -0
  98. package/public/js/features/tour.js +210 -0
  99. package/public/js/features/voice-input.js +185 -0
  100. package/public/js/features/welcome.js +43 -0
  101. package/public/js/features/workflows.js +277 -0
  102. package/public/js/main.js +51 -0
  103. package/public/js/panels/assistant-bot.js +445 -0
  104. package/public/js/panels/dev-docs.js +380 -0
  105. package/public/js/panels/file-explorer.js +486 -0
  106. package/public/js/panels/git-panel.js +285 -0
  107. package/public/js/panels/mcp-manager.js +311 -0
  108. package/public/js/panels/tips-feed.js +303 -0
  109. package/public/js/ui/commands.js +114 -0
  110. package/public/js/ui/context-gauge.js +100 -0
  111. package/public/js/ui/diff.js +124 -0
  112. package/public/js/ui/disabled-tools.js +36 -0
  113. package/public/js/ui/export.js +74 -0
  114. package/public/js/ui/formatting.js +206 -0
  115. package/public/js/ui/header-dropdowns.js +72 -0
  116. package/public/js/ui/input-meta.js +71 -0
  117. package/public/js/ui/max-turns.js +21 -0
  118. package/public/js/ui/messages.js +387 -0
  119. package/public/js/ui/model-selector.js +20 -0
  120. package/public/js/ui/notifications.js +232 -0
  121. package/public/js/ui/parallel.js +176 -0
  122. package/public/js/ui/permissions.js +168 -0
  123. package/public/js/ui/right-panel.js +173 -0
  124. package/public/js/ui/shortcuts.js +143 -0
  125. package/public/js/ui/sidebar-toggle.js +29 -0
  126. package/public/js/ui/status-bar.js +172 -0
  127. package/public/js/ui/tab-sdk.js +623 -0
  128. package/public/js/ui/theme.js +38 -0
  129. package/public/manifest.json +13 -0
  130. package/public/offline.html +190 -0
  131. package/public/style.css +42 -0
  132. package/public/sw.js +91 -0
  133. package/server/agent-loop.js +385 -0
  134. package/server/dag-executor.js +265 -0
  135. package/server/orchestrator.js +514 -0
  136. package/server/paths.js +61 -0
  137. package/server/plugin-mount.js +56 -0
  138. package/server/push-sender.js +31 -0
  139. package/server/routes/agents.js +294 -0
  140. package/server/routes/bot.js +45 -0
  141. package/server/routes/exec.js +35 -0
  142. package/server/routes/files.js +218 -0
  143. package/server/routes/mcp.js +82 -0
  144. package/server/routes/messages.js +36 -0
  145. package/server/routes/notifications.js +37 -0
  146. package/server/routes/projects.js +207 -0
  147. package/server/routes/prompts.js +53 -0
  148. package/server/routes/sessions.js +103 -0
  149. package/server/routes/stats.js +143 -0
  150. package/server/routes/telegram.js +71 -0
  151. package/server/routes/tips.js +135 -0
  152. package/server/routes/workflows.js +81 -0
  153. package/server/summarizer.js +55 -0
  154. package/server/telegram-poller.js +205 -0
  155. package/server/telegram-sender.js +304 -0
  156. package/server/ws-handler.js +926 -0
  157. package/server.js +179 -0
@@ -0,0 +1,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;