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,514 @@
1
+ /**
2
+ * Meta-Orchestrator — decomposes a user task and delegates to specialist agents.
3
+ *
4
+ * Flow:
5
+ * 1. Run a planner query() with available agent descriptions
6
+ * 2. Parse response for ```agent-dispatch code blocks
7
+ * 3. Execute each dispatched agent via runAgent()
8
+ * 4. Feed results back to orchestrator for synthesis (via session resume)
9
+ */
10
+
11
+ import { query } from "@anthropic-ai/claude-code";
12
+ import { execPath } from "process";
13
+ import { existsSync } from "fs";
14
+ import { homedir } from "os";
15
+ import {
16
+ createSession,
17
+ updateClaudeSessionId,
18
+ getSession,
19
+ addCost,
20
+ addMessage,
21
+ getTotalCost,
22
+ updateSessionTitle,
23
+ setAgentContext,
24
+ getAllAgentContext,
25
+ } from "../db.js";
26
+ import { getProjectSystemPrompt } from "./routes/projects.js";
27
+ import { runAgent } from "./agent-loop.js";
28
+ import { sendPushNotification } from "./push-sender.js";
29
+ import { sendTelegramNotification } from "./telegram-sender.js";
30
+
31
+ const MODEL_MAP = {
32
+ haiku: "claude-haiku-4-5-20251001",
33
+ sonnet: "claude-sonnet-4-6",
34
+ opus: "claude-opus-4-6",
35
+ };
36
+ function resolveModel(name) {
37
+ if (!name) return undefined;
38
+ return MODEL_MAP[name] || name;
39
+ }
40
+
41
+ function buildOrchestratorPrompt(task, agents) {
42
+ const agentList = agents
43
+ .map((a) => `- **${a.id}**: ${a.title} — ${a.description}`)
44
+ .join("\n");
45
+
46
+ return `You are a task orchestrator. Your job is to analyze a task, break it into sub-tasks, and delegate each to the most appropriate specialist agent.
47
+
48
+ ## Available Agents
49
+ ${agentList}
50
+
51
+ ## How to Delegate
52
+ For each sub-task you want to delegate, output a fenced code block with the language tag \`agent-dispatch\`:
53
+
54
+ \`\`\`agent-dispatch
55
+ {"agent": "agent-id", "context": "Specific instructions for what this agent should focus on"}
56
+ \`\`\`
57
+
58
+ ## Rules
59
+ - You may dispatch multiple agents. They will run sequentially.
60
+ - Each agent will see the outputs of agents that ran before it.
61
+ - Choose the most appropriate agent for each sub-task.
62
+ - Provide specific, actionable context for each agent — not generic instructions.
63
+ - If no agent is suitable for a sub-task, handle it yourself directly.
64
+ - After dispatching agents, briefly explain your delegation plan.
65
+
66
+ ## Task
67
+ ${task}`;
68
+ }
69
+
70
+ function buildSynthesisPrompt(task, agentResults) {
71
+ let prompt = `You previously decomposed the following task and delegated sub-tasks to specialist agents. All agents have completed. Review their outputs and provide a final synthesis.\n\n`;
72
+ prompt += `## Original Task\n${task}\n\n`;
73
+ prompt += `## Agent Results\n`;
74
+ for (const { agentId, agentTitle, output } of agentResults) {
75
+ prompt += `### ${agentTitle} (${agentId})\n${output}\n\n`;
76
+ }
77
+ prompt += `## Instructions\nProvide a concise summary of what was accomplished across all agents. Highlight key findings, changes made, and any remaining issues.`;
78
+ return prompt;
79
+ }
80
+
81
+ function parseDispatchBlocks(text) {
82
+ const dispatches = [];
83
+ const regex = /```agent-dispatch\s*\n([\s\S]*?)```/g;
84
+ let match;
85
+ while ((match = regex.exec(text)) !== null) {
86
+ try {
87
+ const parsed = JSON.parse(match[1].trim());
88
+ if (parsed.agent) {
89
+ dispatches.push({
90
+ agentId: parsed.agent,
91
+ context: parsed.context || "",
92
+ });
93
+ }
94
+ } catch {
95
+ // Skip malformed dispatch blocks
96
+ }
97
+ }
98
+ return dispatches;
99
+ }
100
+
101
+ export async function runOrchestrator({
102
+ ws,
103
+ task,
104
+ agents,
105
+ cwd,
106
+ sessionId: clientSid,
107
+ projectName,
108
+ permissionMode,
109
+ model,
110
+ sessionIds,
111
+ pendingApprovals,
112
+ makeCanUseTool,
113
+ activeQueries,
114
+ }) {
115
+ const runId = crypto.randomUUID();
116
+
117
+ function orchSend(payload) {
118
+ if (ws.readyState !== 1) return;
119
+ ws.send(JSON.stringify(payload));
120
+ }
121
+
122
+ orchSend({
123
+ type: "orchestrator_started",
124
+ runId,
125
+ task: task.slice(0, 200),
126
+ });
127
+
128
+ const abortController = new AbortController();
129
+ const queryKey = `orchestrator-${runId}`;
130
+ if (activeQueries) {
131
+ activeQueries.set(queryKey, { abort: () => abortController.abort() });
132
+ }
133
+
134
+ const effectivePermMode = permissionMode || "bypass";
135
+ const useBypass = effectivePermMode === "bypass";
136
+ const usePlan = effectivePermMode === "plan";
137
+ const resolvedCwd = cwd && existsSync(cwd) ? cwd : homedir();
138
+
139
+ const plannerOpts = {
140
+ cwd: resolvedCwd,
141
+ permissionMode: usePlan
142
+ ? "plan"
143
+ : useBypass
144
+ ? "bypassPermissions"
145
+ : "default",
146
+ abortController,
147
+ maxTurns: 3, // Planner should just think, not use many tools
148
+ executable: execPath,
149
+ };
150
+
151
+ if (!useBypass && !usePlan) {
152
+ plannerOpts.canUseTool = makeCanUseTool(
153
+ ws,
154
+ pendingApprovals,
155
+ effectivePermMode,
156
+ null,
157
+ `Orchestrator: ${task.slice(0, 40)}`,
158
+ );
159
+ }
160
+ if (model) plannerOpts.model = resolveModel(model);
161
+
162
+ const projectPrompt = getProjectSystemPrompt(cwd);
163
+ if (projectPrompt) plannerOpts.appendSystemPrompt = projectPrompt;
164
+
165
+ const resumeId = clientSid ? sessionIds.get(clientSid) : undefined;
166
+ if (resumeId) plannerOpts.resume = resumeId;
167
+
168
+ let resolvedSid = clientSid;
169
+ let claudeSessionId = null;
170
+ let plannerText = "";
171
+
172
+ // ── Phase 1: Planning ──
173
+ orchSend({ type: "orchestrator_phase", phase: "planning" });
174
+
175
+ try {
176
+ const prompt = buildOrchestratorPrompt(task, agents);
177
+ const q = query({ prompt, options: plannerOpts });
178
+
179
+ for await (const sdkMsg of q) {
180
+ if (ws.readyState !== 1) break;
181
+
182
+ if (sdkMsg.type === "system" && sdkMsg.subtype === "init") {
183
+ claudeSessionId = sdkMsg.session_id;
184
+ const ourSid = clientSid || crypto.randomUUID();
185
+ resolvedSid = ourSid;
186
+ sessionIds.set(ourSid, claudeSessionId);
187
+
188
+ if (!getSession(ourSid)) {
189
+ createSession(
190
+ ourSid,
191
+ claudeSessionId,
192
+ projectName || "Orchestrator",
193
+ cwd || "",
194
+ );
195
+ updateSessionTitle(ourSid, `Orchestrator: ${task.slice(0, 60)}`);
196
+ } else {
197
+ updateClaudeSessionId(ourSid, claudeSessionId);
198
+ }
199
+
200
+ orchSend({ type: "session", sessionId: ourSid });
201
+ addMessage(
202
+ resolvedSid,
203
+ "user",
204
+ JSON.stringify({ text: `[Orchestrator] ${task}` }),
205
+ null,
206
+ );
207
+ continue;
208
+ }
209
+
210
+ if (sdkMsg.type === "assistant" && sdkMsg.message?.content) {
211
+ for (const block of sdkMsg.message.content) {
212
+ if (block.type === "text" && block.text) {
213
+ plannerText += block.text;
214
+ orchSend({ type: "text", text: block.text });
215
+ if (resolvedSid) {
216
+ addMessage(
217
+ resolvedSid,
218
+ "assistant",
219
+ JSON.stringify({ text: block.text }),
220
+ null,
221
+ );
222
+ }
223
+ }
224
+ }
225
+ continue;
226
+ }
227
+
228
+ if (sdkMsg.type === "result") {
229
+ const costUsd = sdkMsg.total_cost_usd || 0;
230
+ const durationMs = sdkMsg.duration_ms || 0;
231
+ const numTurns = sdkMsg.num_turns || 0;
232
+ const inputTokens = sdkMsg.usage?.input_tokens || 0;
233
+ const outputTokens = sdkMsg.usage?.output_tokens || 0;
234
+ const cacheReadTokens =
235
+ sdkMsg.usage?.cache_read_input_tokens || 0;
236
+ const cacheCreationTokens =
237
+ sdkMsg.usage?.cache_creation_input_tokens || 0;
238
+ const resultModel =
239
+ Object.keys(sdkMsg.modelUsage || {})[0] || null;
240
+
241
+ if (resolvedSid) {
242
+ addCost(
243
+ resolvedSid,
244
+ costUsd,
245
+ durationMs,
246
+ numTurns,
247
+ inputTokens,
248
+ outputTokens,
249
+ {
250
+ model: resultModel,
251
+ stopReason: sdkMsg.subtype,
252
+ isError: sdkMsg.subtype?.startsWith("error") ? 1 : 0,
253
+ cacheReadTokens,
254
+ cacheCreationTokens,
255
+ },
256
+ );
257
+ }
258
+
259
+ orchSend({
260
+ type: "result",
261
+ duration_ms: durationMs,
262
+ num_turns: numTurns,
263
+ cost_usd: costUsd,
264
+ totalCost: getTotalCost(),
265
+ input_tokens: inputTokens,
266
+ output_tokens: outputTokens,
267
+ model: resultModel,
268
+ stop_reason: sdkMsg.subtype,
269
+ });
270
+
271
+ if (sdkMsg.subtype?.startsWith("error")) {
272
+ const errMsg = sdkMsg.errors?.join(", ") || "Planning failed";
273
+ orchSend({ type: "error", error: errMsg });
274
+ orchSend({
275
+ type: "orchestrator_error",
276
+ runId,
277
+ error: errMsg,
278
+ });
279
+ return;
280
+ }
281
+ continue;
282
+ }
283
+ }
284
+ } catch (err) {
285
+ orchSend({
286
+ type: "orchestrator_error",
287
+ runId,
288
+ error: err.message,
289
+ });
290
+ orchSend({ type: "error", error: err.message });
291
+ return;
292
+ }
293
+
294
+ // ── Phase 2: Parse dispatches and execute agents ──
295
+ const dispatches = parseDispatchBlocks(plannerText);
296
+
297
+ if (dispatches.length === 0) {
298
+ // No agents dispatched — orchestrator handled it directly
299
+ orchSend({ type: "orchestrator_completed", runId, dispatched: 0 });
300
+ orchSend({ type: "done" });
301
+ if (activeQueries) activeQueries.delete(queryKey);
302
+ return;
303
+ }
304
+
305
+ orchSend({
306
+ type: "orchestrator_dispatching",
307
+ runId,
308
+ dispatches: dispatches.map((d) => ({
309
+ agentId: d.agentId,
310
+ context: d.context.slice(0, 100),
311
+ })),
312
+ totalAgents: dispatches.length,
313
+ });
314
+
315
+ const agentResults = [];
316
+ let chainResumeId = claudeSessionId;
317
+
318
+ for (let i = 0; i < dispatches.length; i++) {
319
+ const dispatch = dispatches[i];
320
+ const agentDef = agents.find((a) => a.id === dispatch.agentId);
321
+
322
+ if (!agentDef) {
323
+ orchSend({
324
+ type: "orchestrator_dispatch_skip",
325
+ runId,
326
+ stepIndex: i,
327
+ agentId: dispatch.agentId,
328
+ reason: "Agent not found",
329
+ });
330
+ continue;
331
+ }
332
+
333
+ orchSend({
334
+ type: "orchestrator_dispatch",
335
+ runId,
336
+ stepIndex: i,
337
+ agentId: agentDef.id,
338
+ agentTitle: agentDef.title,
339
+ context: dispatch.context.slice(0, 200),
340
+ status: "running",
341
+ });
342
+
343
+ try {
344
+ const result = await runAgent({
345
+ ws,
346
+ agentDef,
347
+ cwd,
348
+ sessionId: resolvedSid,
349
+ projectName: projectName || "Orchestrator",
350
+ permissionMode,
351
+ model,
352
+ sessionIds,
353
+ pendingApprovals,
354
+ makeCanUseTool,
355
+ userContext: dispatch.context,
356
+ activeQueries,
357
+ chainResumeId,
358
+ runId,
359
+ });
360
+
361
+ if (result?.resolvedSid) resolvedSid = result.resolvedSid;
362
+ if (result?.claudeSessionId) chainResumeId = result.claudeSessionId;
363
+
364
+ // Read context that the agent stored
365
+ const ctx = getAllAgentContext(runId).find(
366
+ (c) => c.agent_id === agentDef.id,
367
+ );
368
+
369
+ agentResults.push({
370
+ agentId: agentDef.id,
371
+ agentTitle: agentDef.title,
372
+ output: ctx?.value || "(no output captured)",
373
+ });
374
+
375
+ orchSend({
376
+ type: "orchestrator_dispatch",
377
+ runId,
378
+ stepIndex: i,
379
+ agentId: agentDef.id,
380
+ agentTitle: agentDef.title,
381
+ status: "completed",
382
+ });
383
+ } catch (err) {
384
+ orchSend({
385
+ type: "orchestrator_dispatch",
386
+ runId,
387
+ stepIndex: i,
388
+ agentId: agentDef.id,
389
+ agentTitle: agentDef.title,
390
+ status: "error",
391
+ error: err.message,
392
+ });
393
+ if (err.name === "AbortError") break; // Stop dispatching on abort
394
+ }
395
+ }
396
+
397
+ // ── Phase 3: Synthesis ──
398
+ if (agentResults.length > 0) {
399
+ orchSend({ type: "orchestrator_phase", phase: "synthesizing" });
400
+
401
+ try {
402
+ const synthPrompt = buildSynthesisPrompt(task, agentResults);
403
+ const synthOpts = {
404
+ ...plannerOpts,
405
+ maxTurns: 3,
406
+ resume: chainResumeId,
407
+ };
408
+ delete synthOpts.abortController;
409
+ synthOpts.abortController = new AbortController();
410
+
411
+ const sq = query({ prompt: synthPrompt, options: synthOpts });
412
+
413
+ for await (const sdkMsg of sq) {
414
+ if (ws.readyState !== 1) break;
415
+
416
+ if (sdkMsg.type === "system" && sdkMsg.subtype === "init") {
417
+ if (sdkMsg.session_id) {
418
+ claudeSessionId = sdkMsg.session_id;
419
+ if (resolvedSid) sessionIds.set(resolvedSid, claudeSessionId);
420
+ }
421
+ continue;
422
+ }
423
+
424
+ if (sdkMsg.type === "assistant" && sdkMsg.message?.content) {
425
+ for (const block of sdkMsg.message.content) {
426
+ if (block.type === "text" && block.text) {
427
+ orchSend({ type: "text", text: block.text });
428
+ if (resolvedSid) {
429
+ addMessage(
430
+ resolvedSid,
431
+ "assistant",
432
+ JSON.stringify({ text: block.text }),
433
+ null,
434
+ );
435
+ }
436
+ }
437
+ }
438
+ continue;
439
+ }
440
+
441
+ if (sdkMsg.type === "result") {
442
+ const costUsd = sdkMsg.total_cost_usd || 0;
443
+ const durationMs = sdkMsg.duration_ms || 0;
444
+ const numTurns = sdkMsg.num_turns || 0;
445
+ const inputTokens = sdkMsg.usage?.input_tokens || 0;
446
+ const outputTokens = sdkMsg.usage?.output_tokens || 0;
447
+ const cacheReadTokens =
448
+ sdkMsg.usage?.cache_read_input_tokens || 0;
449
+ const cacheCreationTokens =
450
+ sdkMsg.usage?.cache_creation_input_tokens || 0;
451
+ const resultModel =
452
+ Object.keys(sdkMsg.modelUsage || {})[0] || null;
453
+
454
+ if (resolvedSid) {
455
+ addCost(
456
+ resolvedSid,
457
+ costUsd,
458
+ durationMs,
459
+ numTurns,
460
+ inputTokens,
461
+ outputTokens,
462
+ {
463
+ model: resultModel,
464
+ stopReason: sdkMsg.subtype,
465
+ isError: 0,
466
+ cacheReadTokens,
467
+ cacheCreationTokens,
468
+ },
469
+ );
470
+ }
471
+
472
+ orchSend({
473
+ type: "result",
474
+ duration_ms: durationMs,
475
+ num_turns: numTurns,
476
+ cost_usd: costUsd,
477
+ totalCost: getTotalCost(),
478
+ input_tokens: inputTokens,
479
+ output_tokens: outputTokens,
480
+ model: resultModel,
481
+ stop_reason: sdkMsg.subtype,
482
+ });
483
+ continue;
484
+ }
485
+ }
486
+ } catch (err) {
487
+ orchSend({ type: "error", error: `Synthesis failed: ${err.message}` });
488
+ }
489
+ }
490
+
491
+ orchSend({
492
+ type: "orchestrator_completed",
493
+ runId,
494
+ dispatched: agentResults.length,
495
+ });
496
+ orchSend({ type: "done" });
497
+
498
+ if (activeQueries) activeQueries.delete(queryKey);
499
+
500
+ sendPushNotification(
501
+ "Claudeck",
502
+ `Orchestrator completed (${agentResults.length} agents)`,
503
+ `orch-${resolvedSid}`,
504
+ );
505
+ const agentSummary = agentResults
506
+ .map((r, i) => ` ${i + 1}. ${r.agentTitle || r.agentId}`)
507
+ .join("\n");
508
+ sendTelegramNotification(
509
+ "orchestrator",
510
+ "Orchestrator Completed",
511
+ `${task.slice(0, 200)}\n\nDispatched ${agentResults.length} agents:\n${agentSummary}`,
512
+ { steps: agentResults.length },
513
+ );
514
+ }
@@ -0,0 +1,61 @@
1
+ // Centralized path resolution for Claudeck
2
+ // All user data lives in ~/.claudeck/ (persists across NPX updates)
3
+ import { homedir } from "os";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { mkdirSync, existsSync, copyFileSync, readdirSync } from "fs";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Package root (where defaults ship)
12
+ export const packageRoot = join(__dirname, "..");
13
+
14
+ // User data directory (override with CLAUDECK_HOME for testing)
15
+ export const userDir = process.env.CLAUDECK_HOME || join(homedir(), ".claudeck");
16
+ export const userConfigDir = join(userDir, "config");
17
+ export const userPluginsDir = join(userDir, "plugins");
18
+ export const dbPath = join(userDir, "data.db");
19
+
20
+ // Default config (ships with the package, read-only reference)
21
+ export const defaultConfigDir = join(packageRoot, "config");
22
+
23
+ // Config file helper
24
+ export function configPath(filename) {
25
+ return join(userConfigDir, filename);
26
+ }
27
+
28
+ // ── Bootstrap ~/.claudeck/ on first import (synchronous) ────
29
+
30
+ mkdirSync(userConfigDir, { recursive: true });
31
+ mkdirSync(userPluginsDir, { recursive: true });
32
+
33
+ // Copy default config files if missing in user dir
34
+ if (existsSync(defaultConfigDir)) {
35
+ for (const file of readdirSync(defaultConfigDir).filter(f => f.endsWith(".json"))) {
36
+ const dest = join(userConfigDir, file);
37
+ if (!existsSync(dest)) {
38
+ copyFileSync(join(defaultConfigDir, file), dest);
39
+ console.log(`Copied default ${file} to ${userConfigDir}`);
40
+ }
41
+ }
42
+ }
43
+
44
+ // Migrate existing data.db from package root (for existing users)
45
+ const packageDb = join(packageRoot, "data.db");
46
+ if (!existsSync(dbPath) && existsSync(packageDb)) {
47
+ copyFileSync(packageDb, dbPath);
48
+ for (const ext of ["-shm", "-wal"]) {
49
+ const src = packageDb + ext;
50
+ if (existsSync(src)) copyFileSync(src, dbPath + ext);
51
+ }
52
+ console.log(`Migrated database to ${dbPath}`);
53
+ }
54
+
55
+ // Migrate existing .env from package root
56
+ const userEnv = join(userDir, ".env");
57
+ const packageEnv = join(packageRoot, ".env");
58
+ if (!existsSync(userEnv) && existsSync(packageEnv)) {
59
+ copyFileSync(packageEnv, userEnv);
60
+ console.log(`Migrated .env to ${userDir}`);
61
+ }
@@ -0,0 +1,56 @@
1
+ // Full-stack plugin loader — discovers and mounts plugin server routes
2
+ import { readdirSync, existsSync, statSync, copyFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { pathToFileURL } from "url";
5
+ import { userConfigDir, userPluginsDir } from "./paths.js";
6
+
7
+ export async function mountPluginRoutes(app, pluginsDir) {
8
+ const dirs = [];
9
+
10
+ // Built-in full-stack plugins (shipped with package)
11
+ if (existsSync(pluginsDir)) {
12
+ for (const name of readdirSync(pluginsDir)) {
13
+ const dir = join(pluginsDir, name);
14
+ if (statSync(dir).isDirectory()) {
15
+ dirs.push({ name, dir, source: "builtin" });
16
+ }
17
+ }
18
+ }
19
+
20
+ // User full-stack plugins (~/.claudeck/plugins/<dir>/)
21
+ const allowUserServer = process.env.CLAUDECK_USER_SERVER_PLUGINS === "true";
22
+ if (existsSync(userPluginsDir)) {
23
+ for (const name of readdirSync(userPluginsDir)) {
24
+ const dir = join(userPluginsDir, name);
25
+ if (!statSync(dir).isDirectory()) continue;
26
+ if (dirs.some(d => d.name === name)) continue; // builtin wins
27
+ dirs.push({ name, dir, source: "user" });
28
+ }
29
+ }
30
+
31
+ for (const { name, dir, source } of dirs) {
32
+ // Copy default config if plugin ships one and user doesn't have it yet
33
+ const configSrc = join(dir, "config.json");
34
+ const configDst = join(userConfigDir, `${name}-config.json`);
35
+ if (existsSync(configSrc) && !existsSync(configDst)) {
36
+ copyFileSync(configSrc, configDst);
37
+ console.log(`Copied default config for plugin: ${name}`);
38
+ }
39
+
40
+ // Mount server routes if plugin has server.js
41
+ const serverFile = join(dir, "server.js");
42
+ if (existsSync(serverFile)) {
43
+ if (source === "user" && !allowUserServer) {
44
+ console.log(`Skipping server routes for user plugin: ${name} (set CLAUDECK_USER_SERVER_PLUGINS=true to enable)`);
45
+ continue;
46
+ }
47
+ try {
48
+ const mod = await import(pathToFileURL(serverFile).href);
49
+ app.use(`/api/plugins/${name}`, mod.default);
50
+ console.log(`Mounted plugin routes: /api/plugins/${name}`);
51
+ } catch (err) {
52
+ console.error(`Failed to load plugin server: ${name}`, err.message);
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,31 @@
1
+ import { getAllPushSubscriptions, deletePushSubscription } from "../db.js";
2
+
3
+ let webpushInstance = null;
4
+
5
+ export function initPushSender(webpush) {
6
+ webpushInstance = webpush;
7
+ }
8
+
9
+ export async function sendPushNotification(title, body, tag) {
10
+ if (!webpushInstance) return;
11
+
12
+ const subs = getAllPushSubscriptions();
13
+ if (!subs.length) return;
14
+
15
+ const payload = JSON.stringify({ title, body, tag });
16
+
17
+ await Promise.allSettled(
18
+ subs.map(async (sub) => {
19
+ try {
20
+ await webpushInstance.sendNotification(
21
+ { endpoint: sub.endpoint, keys: { p256dh: sub.keys_p256dh, auth: sub.keys_auth } },
22
+ payload
23
+ );
24
+ } catch (err) {
25
+ if (err.statusCode === 404 || err.statusCode === 410) {
26
+ deletePushSubscription(sub.endpoint);
27
+ }
28
+ }
29
+ })
30
+ );
31
+ }