create-claude-code-visualizer 0.1.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 (220) hide show
  1. package/index.js +393 -0
  2. package/package.json +31 -0
  3. package/templates/CLAUDE.md +108 -0
  4. package/templates/app/.env.local.example +14 -0
  5. package/templates/app/ecosystem.config.js +29 -0
  6. package/templates/app/next-env.d.ts +6 -0
  7. package/templates/app/next.config.ts +16 -0
  8. package/templates/app/package-lock.json +4581 -0
  9. package/templates/app/package.json +38 -0
  10. package/templates/app/postcss.config.js +5 -0
  11. package/templates/app/src/app/agents/[slug]/chat/loading.tsx +26 -0
  12. package/templates/app/src/app/agents/[slug]/chat/page.tsx +579 -0
  13. package/templates/app/src/app/agents/[slug]/loading.tsx +19 -0
  14. package/templates/app/src/app/agents/page.tsx +8 -0
  15. package/templates/app/src/app/api/agents/[slug]/capabilities/route.ts +11 -0
  16. package/templates/app/src/app/api/agents/[slug]/route.ts +57 -0
  17. package/templates/app/src/app/api/agents/route.ts +28 -0
  18. package/templates/app/src/app/api/ai/generate-agent/route.ts +87 -0
  19. package/templates/app/src/app/api/ai/improve-claude-md/route.ts +78 -0
  20. package/templates/app/src/app/api/ai/suggestions/route.ts +64 -0
  21. package/templates/app/src/app/api/ai/title/route.ts +88 -0
  22. package/templates/app/src/app/api/auth/role/route.ts +17 -0
  23. package/templates/app/src/app/api/commands/[slug]/route.ts +61 -0
  24. package/templates/app/src/app/api/commands/route.ts +6 -0
  25. package/templates/app/src/app/api/governance/costs/route.ts +117 -0
  26. package/templates/app/src/app/api/governance/sessions/route.ts +335 -0
  27. package/templates/app/src/app/api/notifications/route.ts +62 -0
  28. package/templates/app/src/app/api/preferences/route.ts +44 -0
  29. package/templates/app/src/app/api/runs/[id]/approve/route.ts +38 -0
  30. package/templates/app/src/app/api/runs/[id]/events/route.ts +28 -0
  31. package/templates/app/src/app/api/runs/[id]/metadata/route.ts +30 -0
  32. package/templates/app/src/app/api/runs/[id]/route.ts +21 -0
  33. package/templates/app/src/app/api/runs/[id]/start/route.ts +61 -0
  34. package/templates/app/src/app/api/runs/[id]/stop/route.ts +16 -0
  35. package/templates/app/src/app/api/runs/[id]/stream/route.ts +201 -0
  36. package/templates/app/src/app/api/runs/route.ts +95 -0
  37. package/templates/app/src/app/api/schedules/[id]/route.ts +81 -0
  38. package/templates/app/src/app/api/schedules/route.ts +75 -0
  39. package/templates/app/src/app/api/settings/access-logs/route.ts +33 -0
  40. package/templates/app/src/app/api/settings/claude-md/route.ts +44 -0
  41. package/templates/app/src/app/api/settings/env-keys/route.ts +271 -0
  42. package/templates/app/src/app/api/settings/users/route.ts +108 -0
  43. package/templates/app/src/app/api/skills/[slug]/route.ts +43 -0
  44. package/templates/app/src/app/api/skills/route.ts +6 -0
  45. package/templates/app/src/app/api/tools/route.ts +65 -0
  46. package/templates/app/src/app/api/uploads/cleanup/route.ts +29 -0
  47. package/templates/app/src/app/api/uploads/route.ts +77 -0
  48. package/templates/app/src/app/auth/callback/route.ts +19 -0
  49. package/templates/app/src/app/globals.css +115 -0
  50. package/templates/app/src/app/layout.tsx +24 -0
  51. package/templates/app/src/app/loading.tsx +16 -0
  52. package/templates/app/src/app/login/page.tsx +64 -0
  53. package/templates/app/src/app/not-authorized/page.tsx +33 -0
  54. package/templates/app/src/app/runs/page.tsx +55 -0
  55. package/templates/app/src/app/schedules/page.tsx +110 -0
  56. package/templates/app/src/app/settings/page.tsx +1294 -0
  57. package/templates/app/src/app/skills/page.tsx +7 -0
  58. package/templates/app/src/components/agent-card.tsx +58 -0
  59. package/templates/app/src/components/agent-grid.tsx +90 -0
  60. package/templates/app/src/components/auth/auth-context.tsx +79 -0
  61. package/templates/app/src/components/chat-thread.tsx +50 -0
  62. package/templates/app/src/components/chat-view.tsx +670 -0
  63. package/templates/app/src/components/commands-browser.tsx +349 -0
  64. package/templates/app/src/components/create-agent-modal.tsx +388 -0
  65. package/templates/app/src/components/governance-dashboard.tsx +397 -0
  66. package/templates/app/src/components/icons.tsx +401 -0
  67. package/templates/app/src/components/layout/agent-sidebar.tsx +504 -0
  68. package/templates/app/src/components/layout/app-shell.tsx +29 -0
  69. package/templates/app/src/components/layout/nav.tsx +87 -0
  70. package/templates/app/src/components/layout/overview-inner.tsx +14 -0
  71. package/templates/app/src/components/layout/profile-menu.tsx +95 -0
  72. package/templates/app/src/components/layout/sidebar.tsx +30 -0
  73. package/templates/app/src/components/markdown.tsx +57 -0
  74. package/templates/app/src/components/message-bar.tsx +161 -0
  75. package/templates/app/src/components/notifications/notification-bell.tsx +104 -0
  76. package/templates/app/src/components/notifications/notification-panel.tsx +116 -0
  77. package/templates/app/src/components/overview/overview-content.tsx +287 -0
  78. package/templates/app/src/components/overview/overview-context.tsx +88 -0
  79. package/templates/app/src/components/preferences-modal.tsx +112 -0
  80. package/templates/app/src/components/run-form.tsx +73 -0
  81. package/templates/app/src/components/run-history-table.tsx +226 -0
  82. package/templates/app/src/components/run-output.tsx +187 -0
  83. package/templates/app/src/components/schedule-form.tsx +148 -0
  84. package/templates/app/src/components/skills-browser.tsx +338 -0
  85. package/templates/app/src/components/tool-tooltip.tsx +82 -0
  86. package/templates/app/src/hooks/use-sse.ts +115 -0
  87. package/templates/app/src/instrumentation.ts +9 -0
  88. package/templates/app/src/lib/agent-cache.ts +19 -0
  89. package/templates/app/src/lib/agent-runner.ts +411 -0
  90. package/templates/app/src/lib/agents.ts +168 -0
  91. package/templates/app/src/lib/ai.ts +40 -0
  92. package/templates/app/src/lib/approval-store.ts +70 -0
  93. package/templates/app/src/lib/auth-guard.ts +116 -0
  94. package/templates/app/src/lib/capabilities.ts +191 -0
  95. package/templates/app/src/lib/line-diff.ts +96 -0
  96. package/templates/app/src/lib/queue.ts +22 -0
  97. package/templates/app/src/lib/redis.ts +12 -0
  98. package/templates/app/src/lib/role-permissions.ts +166 -0
  99. package/templates/app/src/lib/run-agent.ts +442 -0
  100. package/templates/app/src/lib/supabase-browser.ts +8 -0
  101. package/templates/app/src/lib/supabase-middleware.ts +63 -0
  102. package/templates/app/src/lib/supabase-server.ts +28 -0
  103. package/templates/app/src/lib/supabase.ts +6 -0
  104. package/templates/app/src/lib/tool-descriptions.ts +29 -0
  105. package/templates/app/src/lib/types.ts +73 -0
  106. package/templates/app/src/lib/typewriter-animation.ts +159 -0
  107. package/templates/app/src/middleware.ts +13 -0
  108. package/templates/app/tsconfig.json +21 -0
  109. package/templates/app/uploads/.gitkeep +0 -0
  110. package/templates/app/worker/index.ts +342 -0
  111. package/templates/claude/agents/ai-trends-scout.md +66 -0
  112. package/templates/claude/commands/add-to-todos.md +56 -0
  113. package/templates/claude/commands/check-todos.md +56 -0
  114. package/templates/claude/hooks/auto-approve-safe.sh +34 -0
  115. package/templates/claude/hooks/auto-format.sh +25 -0
  116. package/templates/claude/hooks/block-destructive.sh +32 -0
  117. package/templates/claude/hooks/compaction-preserver.sh +16 -0
  118. package/templates/claude/hooks/notify.sh +26 -0
  119. package/templates/claude/settings.local.json +66 -0
  120. package/templates/claude/skills/frontend-design/SKILL.md +127 -0
  121. package/templates/claude/skills/frontend-design/reference/color-and-contrast.md +132 -0
  122. package/templates/claude/skills/frontend-design/reference/interaction-design.md +123 -0
  123. package/templates/claude/skills/frontend-design/reference/motion-design.md +99 -0
  124. package/templates/claude/skills/frontend-design/reference/responsive-design.md +114 -0
  125. package/templates/claude/skills/frontend-design/reference/spatial-design.md +100 -0
  126. package/templates/claude/skills/frontend-design/reference/typography.md +131 -0
  127. package/templates/claude/skills/frontend-design/reference/ux-writing.md +107 -0
  128. package/templates/claude/skills/gws-admin-reports/SKILL.md +57 -0
  129. package/templates/claude/skills/gws-calendar/SKILL.md +108 -0
  130. package/templates/claude/skills/gws-calendar-agenda/SKILL.md +52 -0
  131. package/templates/claude/skills/gws-calendar-insert/SKILL.md +55 -0
  132. package/templates/claude/skills/gws-chat/SKILL.md +73 -0
  133. package/templates/claude/skills/gws-chat-send/SKILL.md +49 -0
  134. package/templates/claude/skills/gws-classroom/SKILL.md +75 -0
  135. package/templates/claude/skills/gws-docs/SKILL.md +48 -0
  136. package/templates/claude/skills/gws-docs-write/SKILL.md +49 -0
  137. package/templates/claude/skills/gws-drive/SKILL.md +137 -0
  138. package/templates/claude/skills/gws-drive-upload/SKILL.md +52 -0
  139. package/templates/claude/skills/gws-events/SKILL.md +67 -0
  140. package/templates/claude/skills/gws-events-renew/SKILL.md +48 -0
  141. package/templates/claude/skills/gws-events-subscribe/SKILL.md +59 -0
  142. package/templates/claude/skills/gws-forms/SKILL.md +45 -0
  143. package/templates/claude/skills/gws-gmail/SKILL.md +59 -0
  144. package/templates/claude/skills/gws-gmail-forward/SKILL.md +53 -0
  145. package/templates/claude/skills/gws-gmail-reply/SKILL.md +56 -0
  146. package/templates/claude/skills/gws-gmail-reply-all/SKILL.md +60 -0
  147. package/templates/claude/skills/gws-gmail-send/SKILL.md +55 -0
  148. package/templates/claude/skills/gws-gmail-triage/SKILL.md +50 -0
  149. package/templates/claude/skills/gws-gmail-watch/SKILL.md +58 -0
  150. package/templates/claude/skills/gws-keep/SKILL.md +48 -0
  151. package/templates/claude/skills/gws-meet/SKILL.md +51 -0
  152. package/templates/claude/skills/gws-modelarmor/SKILL.md +42 -0
  153. package/templates/claude/skills/gws-modelarmor-create-template/SKILL.md +53 -0
  154. package/templates/claude/skills/gws-modelarmor-sanitize-prompt/SKILL.md +48 -0
  155. package/templates/claude/skills/gws-modelarmor-sanitize-response/SKILL.md +48 -0
  156. package/templates/claude/skills/gws-people/SKILL.md +67 -0
  157. package/templates/claude/skills/gws-shared/SKILL.md +66 -0
  158. package/templates/claude/skills/gws-sheets/SKILL.md +53 -0
  159. package/templates/claude/skills/gws-sheets-append/SKILL.md +51 -0
  160. package/templates/claude/skills/gws-sheets-read/SKILL.md +47 -0
  161. package/templates/claude/skills/gws-slides/SKILL.md +43 -0
  162. package/templates/claude/skills/gws-tasks/SKILL.md +56 -0
  163. package/templates/claude/skills/gws-workflow/SKILL.md +44 -0
  164. package/templates/claude/skills/gws-workflow-email-to-task/SKILL.md +47 -0
  165. package/templates/claude/skills/gws-workflow-file-announce/SKILL.md +50 -0
  166. package/templates/claude/skills/gws-workflow-meeting-prep/SKILL.md +47 -0
  167. package/templates/claude/skills/gws-workflow-standup-report/SKILL.md +46 -0
  168. package/templates/claude/skills/gws-workflow-weekly-digest/SKILL.md +46 -0
  169. package/templates/claude/skills/persona-content-creator/SKILL.md +33 -0
  170. package/templates/claude/skills/persona-customer-support/SKILL.md +34 -0
  171. package/templates/claude/skills/persona-event-coordinator/SKILL.md +35 -0
  172. package/templates/claude/skills/persona-exec-assistant/SKILL.md +35 -0
  173. package/templates/claude/skills/persona-hr-coordinator/SKILL.md +33 -0
  174. package/templates/claude/skills/persona-it-admin/SKILL.md +30 -0
  175. package/templates/claude/skills/persona-project-manager/SKILL.md +35 -0
  176. package/templates/claude/skills/persona-researcher/SKILL.md +33 -0
  177. package/templates/claude/skills/persona-sales-ops/SKILL.md +35 -0
  178. package/templates/claude/skills/persona-team-lead/SKILL.md +36 -0
  179. package/templates/claude/skills/recipe-backup-sheet-as-csv/SKILL.md +25 -0
  180. package/templates/claude/skills/recipe-batch-invite-to-event/SKILL.md +25 -0
  181. package/templates/claude/skills/recipe-block-focus-time/SKILL.md +24 -0
  182. package/templates/claude/skills/recipe-bulk-download-folder/SKILL.md +25 -0
  183. package/templates/claude/skills/recipe-collect-form-responses/SKILL.md +25 -0
  184. package/templates/claude/skills/recipe-compare-sheet-tabs/SKILL.md +25 -0
  185. package/templates/claude/skills/recipe-copy-sheet-for-new-month/SKILL.md +25 -0
  186. package/templates/claude/skills/recipe-create-classroom-course/SKILL.md +25 -0
  187. package/templates/claude/skills/recipe-create-doc-from-template/SKILL.md +29 -0
  188. package/templates/claude/skills/recipe-create-events-from-sheet/SKILL.md +24 -0
  189. package/templates/claude/skills/recipe-create-expense-tracker/SKILL.md +26 -0
  190. package/templates/claude/skills/recipe-create-feedback-form/SKILL.md +25 -0
  191. package/templates/claude/skills/recipe-create-gmail-filter/SKILL.md +26 -0
  192. package/templates/claude/skills/recipe-create-meet-space/SKILL.md +25 -0
  193. package/templates/claude/skills/recipe-create-presentation/SKILL.md +25 -0
  194. package/templates/claude/skills/recipe-create-shared-drive/SKILL.md +25 -0
  195. package/templates/claude/skills/recipe-create-task-list/SKILL.md +26 -0
  196. package/templates/claude/skills/recipe-create-vacation-responder/SKILL.md +25 -0
  197. package/templates/claude/skills/recipe-draft-email-from-doc/SKILL.md +25 -0
  198. package/templates/claude/skills/recipe-email-drive-link/SKILL.md +25 -0
  199. package/templates/claude/skills/recipe-find-free-time/SKILL.md +25 -0
  200. package/templates/claude/skills/recipe-find-large-files/SKILL.md +24 -0
  201. package/templates/claude/skills/recipe-forward-labeled-emails/SKILL.md +27 -0
  202. package/templates/claude/skills/recipe-generate-report-from-sheet/SKILL.md +34 -0
  203. package/templates/claude/skills/recipe-label-and-archive-emails/SKILL.md +25 -0
  204. package/templates/claude/skills/recipe-log-deal-update/SKILL.md +25 -0
  205. package/templates/claude/skills/recipe-organize-drive-folder/SKILL.md +26 -0
  206. package/templates/claude/skills/recipe-plan-weekly-schedule/SKILL.md +26 -0
  207. package/templates/claude/skills/recipe-post-mortem-setup/SKILL.md +25 -0
  208. package/templates/claude/skills/recipe-reschedule-meeting/SKILL.md +25 -0
  209. package/templates/claude/skills/recipe-review-meet-participants/SKILL.md +25 -0
  210. package/templates/claude/skills/recipe-review-overdue-tasks/SKILL.md +25 -0
  211. package/templates/claude/skills/recipe-save-email-attachments/SKILL.md +26 -0
  212. package/templates/claude/skills/recipe-save-email-to-doc/SKILL.md +29 -0
  213. package/templates/claude/skills/recipe-schedule-recurring-event/SKILL.md +24 -0
  214. package/templates/claude/skills/recipe-send-team-announcement/SKILL.md +24 -0
  215. package/templates/claude/skills/recipe-share-doc-and-notify/SKILL.md +25 -0
  216. package/templates/claude/skills/recipe-share-event-materials/SKILL.md +25 -0
  217. package/templates/claude/skills/recipe-share-folder-with-team/SKILL.md +26 -0
  218. package/templates/claude/skills/recipe-sync-contacts-to-sheet/SKILL.md +25 -0
  219. package/templates/claude/skills/recipe-watch-drive-changes/SKILL.md +25 -0
  220. package/templates/mcp.json +12 -0
@@ -0,0 +1,201 @@
1
+ import { NextRequest } from "next/server";
2
+ import { supabase } from "@/lib/supabase";
3
+ import { getRunContext } from "@/lib/agent-runner";
4
+
5
+ export const maxDuration = 300; // 5 min max for long-running SSE observation
6
+
7
+ export async function GET(
8
+ req: NextRequest,
9
+ { params }: { params: Promise<{ id: string }> }
10
+ ) {
11
+ const { id: runId } = await params;
12
+
13
+ // Fetch the run record
14
+ const { data: run, error } = await supabase
15
+ .from("agent_runs")
16
+ .select("*")
17
+ .eq("id", runId)
18
+ .single();
19
+
20
+ if (error || !run) {
21
+ return new Response(`data: ${JSON.stringify({ type: "error", error: "Run not found" })}\n\n`, {
22
+ status: 404,
23
+ headers: { "Content-Type": "text/event-stream" },
24
+ });
25
+ }
26
+
27
+ // If already completed/failed/stopped and no RunContext, return final state
28
+ const ctx = getRunContext(runId);
29
+ if (!ctx && (run.status === "completed" || run.status === "failed" || run.status === "stopped")) {
30
+ // Map DB status to the SSE event types the client expects
31
+ const eventType = run.status === "completed" ? "done"
32
+ : run.status === "stopped" ? "stopped"
33
+ : "error";
34
+ const payload = eventType === "error"
35
+ ? { type: "error", error: run.error || "Run failed" }
36
+ : { type: eventType, output: run.output, cost_usd: run.cost_usd, duration_ms: run.duration_ms };
37
+ const encoder = new TextEncoder();
38
+ const body = encoder.encode(`data: ${JSON.stringify(payload)}\n\n`);
39
+ return new Response(body, {
40
+ headers: {
41
+ "Content-Type": "text/event-stream",
42
+ "Cache-Control": "no-cache",
43
+ Connection: "keep-alive",
44
+ },
45
+ });
46
+ }
47
+
48
+ const stream = new ReadableStream({
49
+ async start(controller) {
50
+ const encoder = new TextEncoder();
51
+ let closed = false;
52
+
53
+ const send = (data: Record<string, unknown>) => {
54
+ if (closed) return;
55
+ try {
56
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
57
+ } catch {
58
+ // stream closed
59
+ closed = true;
60
+ }
61
+ };
62
+
63
+ // --- Mode 1: Live attach (RunContext exists) ---
64
+ if (ctx) {
65
+ ctx.subscriberCount++;
66
+
67
+ // Load historical events written before this connection (for mid-run reconnection)
68
+ const afterSeq = parseInt(req.nextUrl.searchParams.get("after_seq") || "0", 10);
69
+ if (afterSeq > 0) {
70
+ const { data: events } = await supabase
71
+ .from("run_events")
72
+ .select("*")
73
+ .eq("run_id", runId)
74
+ .gt("seq", afterSeq)
75
+ .order("seq", { ascending: true });
76
+
77
+ if (events) {
78
+ for (const evt of events) {
79
+ send({ type: evt.event_type, seq: evt.seq, ...evt.payload });
80
+ }
81
+ }
82
+ }
83
+
84
+ // Subscribe to live emitter
85
+ const onEvent = (data: Record<string, unknown>) => {
86
+ send(data);
87
+ };
88
+
89
+ ctx.emitter.on("event", onEvent);
90
+
91
+ // Wait for client disconnect or run completion
92
+ const cleanup = () => {
93
+ ctx.emitter.off("event", onEvent);
94
+ ctx.subscriberCount = Math.max(0, ctx.subscriberCount - 1);
95
+ if (!closed) {
96
+ closed = true;
97
+ try { controller.close(); } catch { /* already closed */ }
98
+ }
99
+ };
100
+
101
+ // Watch for client disconnect
102
+ req.signal.addEventListener("abort", cleanup);
103
+
104
+ // Also close when run finishes (after a small delay to flush final events)
105
+ const checkDone = () => {
106
+ if (ctx.status === "done") {
107
+ setTimeout(() => cleanup(), 500);
108
+ } else {
109
+ setTimeout(checkDone, 1000);
110
+ }
111
+ };
112
+ checkDone();
113
+
114
+ return; // keep stream open
115
+ }
116
+
117
+ // --- Mode 2: Historical replay (RunContext not found) ---
118
+ // Load all events from DB
119
+ const { data: events } = await supabase
120
+ .from("run_events")
121
+ .select("*")
122
+ .eq("run_id", runId)
123
+ .order("seq", { ascending: true });
124
+
125
+ if (events && events.length > 0) {
126
+ for (const evt of events) {
127
+ send({ type: evt.event_type, seq: evt.seq, ...evt.payload });
128
+ }
129
+ }
130
+
131
+ // If run is still "running" but no RunContext (server restarted), poll for new events
132
+ if (run.status === "running" || run.status === "queued") {
133
+ let lastSeq = events?.length ? events[events.length - 1].seq : 0;
134
+ const pollInterval = setInterval(async () => {
135
+ if (closed || req.signal.aborted) {
136
+ clearInterval(pollInterval);
137
+ if (!closed) {
138
+ closed = true;
139
+ try { controller.close(); } catch { /* ok */ }
140
+ }
141
+ return;
142
+ }
143
+
144
+ // Check for new events
145
+ const { data: newEvents } = await supabase
146
+ .from("run_events")
147
+ .select("*")
148
+ .eq("run_id", runId)
149
+ .gt("seq", lastSeq)
150
+ .order("seq", { ascending: true });
151
+
152
+ if (newEvents && newEvents.length > 0) {
153
+ for (const evt of newEvents) {
154
+ send({ type: evt.event_type, seq: evt.seq, ...evt.payload });
155
+ }
156
+ lastSeq = newEvents[newEvents.length - 1].seq;
157
+
158
+ // Check if run is done
159
+ const doneEvt = newEvents.find((e) => e.event_type === "done" || e.event_type === "stopped" || e.event_type === "error");
160
+ if (doneEvt) {
161
+ clearInterval(pollInterval);
162
+ closed = true;
163
+ try { controller.close(); } catch { /* ok */ }
164
+ }
165
+ }
166
+
167
+ // Also check if the run status changed (stale cleanup may have updated it)
168
+ const { data: freshRun } = await supabase
169
+ .from("agent_runs")
170
+ .select("status")
171
+ .eq("id", runId)
172
+ .single();
173
+
174
+ if (freshRun && (freshRun.status === "completed" || freshRun.status === "failed" || freshRun.status === "stopped")) {
175
+ clearInterval(pollInterval);
176
+ // Send final event if no done event was sent
177
+ if (!newEvents?.some((e) => e.event_type === "done" || e.event_type === "stopped")) {
178
+ const eventType = freshRun.status === "completed" ? "done" : freshRun.status === "stopped" ? "stopped" : "error";
179
+ send({ type: eventType });
180
+ }
181
+ closed = true;
182
+ try { controller.close(); } catch { /* ok */ }
183
+ }
184
+ }, 2000);
185
+
186
+ return; // keep stream open for polling
187
+ }
188
+
189
+ // Run is terminal and no events — just close
190
+ controller.close();
191
+ },
192
+ });
193
+
194
+ return new Response(stream, {
195
+ headers: {
196
+ "Content-Type": "text/event-stream",
197
+ "Cache-Control": "no-cache",
198
+ Connection: "keep-alive",
199
+ },
200
+ });
201
+ }
@@ -0,0 +1,95 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { supabase } from "@/lib/supabase";
3
+ import { requireRole } from "@/lib/auth-guard";
4
+
5
+ export async function GET(req: NextRequest) {
6
+ const { searchParams } = req.nextUrl;
7
+ const agentSlug = searchParams.get("agent_slug");
8
+ const limit = parseInt(searchParams.get("limit") || "50", 10);
9
+ const offset = parseInt(searchParams.get("offset") || "0", 10);
10
+
11
+ let query = supabase
12
+ .from("agent_runs")
13
+ .select("*, run_events(count)")
14
+ .order("created_at", { ascending: false })
15
+ .range(offset, offset + limit - 1);
16
+
17
+ if (agentSlug) {
18
+ query = query.eq("agent_slug", agentSlug);
19
+ }
20
+
21
+ const { data, error } = await query;
22
+
23
+ if (error) {
24
+ return NextResponse.json({ error: error.message }, { status: 500 });
25
+ }
26
+
27
+ // Flatten the nested run_events count into a top-level event_count field
28
+ const normalized = (data || []).map((run: Record<string, unknown>) => {
29
+ const events = run.run_events as { count: number }[] | undefined;
30
+ const { run_events: _, ...rest } = run;
31
+ return { ...rest, event_count: events?.[0]?.count ?? 0 };
32
+ });
33
+
34
+ return NextResponse.json(normalized);
35
+ }
36
+
37
+ export async function DELETE(req: NextRequest) {
38
+ const { searchParams } = req.nextUrl;
39
+ const sessionId = searchParams.get("session_id");
40
+
41
+ if (!sessionId) {
42
+ return NextResponse.json(
43
+ { error: "session_id is required" },
44
+ { status: 400 }
45
+ );
46
+ }
47
+
48
+ const { error } = await supabase
49
+ .from("agent_runs")
50
+ .delete()
51
+ .eq("session_id", sessionId);
52
+
53
+ if (error) {
54
+ return NextResponse.json({ error: error.message }, { status: 500 });
55
+ }
56
+
57
+ return NextResponse.json({ deleted: true });
58
+ }
59
+
60
+ export async function POST(req: NextRequest) {
61
+ const auth = await requireRole(req, ["admin", "operator"], "create_run", "agent_runs");
62
+ if (!auth.authorized) return auth.response;
63
+
64
+ const body = await req.json();
65
+ const { agent_slug, agent_name, prompt, session_id } = body;
66
+
67
+ if (!agent_slug || !agent_name || !prompt) {
68
+ return NextResponse.json(
69
+ { error: "agent_slug, agent_name, and prompt are required" },
70
+ { status: 400 }
71
+ );
72
+ }
73
+
74
+ const { data, error } = await supabase
75
+ .from("agent_runs")
76
+ .insert({
77
+ agent_slug,
78
+ agent_name,
79
+ prompt,
80
+ status: "queued",
81
+ ...(session_id ? { session_id } : {}),
82
+ metadata: {
83
+ created_by: auth.user!.email,
84
+ created_by_role: auth.user!.role,
85
+ },
86
+ })
87
+ .select()
88
+ .single();
89
+
90
+ if (error) {
91
+ return NextResponse.json({ error: error.message }, { status: 500 });
92
+ }
93
+
94
+ return NextResponse.json(data, { status: 201 });
95
+ }
@@ -0,0 +1,81 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { supabase } from "@/lib/supabase";
3
+ import { getQueue } from "@/lib/queue";
4
+
5
+ export async function PATCH(
6
+ req: NextRequest,
7
+ { params }: { params: Promise<{ id: string }> }
8
+ ) {
9
+ const { id } = await params;
10
+ const body = await req.json();
11
+
12
+ const updates: Record<string, unknown> = { updated_at: new Date().toISOString() };
13
+ if (body.prompt !== undefined) updates.prompt = body.prompt;
14
+ if (body.cron !== undefined) updates.cron = body.cron;
15
+ if (body.enabled !== undefined) updates.enabled = body.enabled;
16
+ if (body.skill_slug !== undefined) updates.skill_slug = body.skill_slug || null;
17
+
18
+ const { data, error } = await supabase
19
+ .from("agent_schedules")
20
+ .update(updates)
21
+ .eq("id", id)
22
+ .select()
23
+ .single();
24
+
25
+ if (error) {
26
+ return NextResponse.json({ error: error.message }, { status: 500 });
27
+ }
28
+
29
+ // Sync with BullMQ
30
+ try {
31
+ const queue = getQueue();
32
+ if (data.enabled) {
33
+ await queue.upsertJobScheduler(
34
+ `schedule-${id}`,
35
+ { pattern: data.cron },
36
+ {
37
+ name: "scheduled-agent-run",
38
+ data: {
39
+ scheduleId: data.id,
40
+ agentSlug: data.agent_slug,
41
+ agentName: data.agent_name,
42
+ prompt: data.prompt,
43
+ skillSlug: data.skill_slug || undefined,
44
+ },
45
+ }
46
+ );
47
+ } else {
48
+ await queue.removeJobScheduler(`schedule-${id}`);
49
+ }
50
+ } catch (err) {
51
+ console.error("Failed to sync BullMQ scheduler:", err);
52
+ }
53
+
54
+ return NextResponse.json(data);
55
+ }
56
+
57
+ export async function DELETE(
58
+ _req: NextRequest,
59
+ { params }: { params: Promise<{ id: string }> }
60
+ ) {
61
+ const { id } = await params;
62
+
63
+ // Remove from BullMQ first
64
+ try {
65
+ const queue = getQueue();
66
+ await queue.removeJobScheduler(`schedule-${id}`);
67
+ } catch (err) {
68
+ console.error("Failed to remove BullMQ scheduler:", err);
69
+ }
70
+
71
+ const { error } = await supabase
72
+ .from("agent_schedules")
73
+ .delete()
74
+ .eq("id", id);
75
+
76
+ if (error) {
77
+ return NextResponse.json({ error: error.message }, { status: 500 });
78
+ }
79
+
80
+ return NextResponse.json({ ok: true });
81
+ }
@@ -0,0 +1,75 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { supabase } from "@/lib/supabase";
3
+ import { getQueue } from "@/lib/queue";
4
+
5
+ export async function GET() {
6
+ const { data, error } = await supabase
7
+ .from("agent_schedules")
8
+ .select("*")
9
+ .order("created_at", { ascending: false });
10
+
11
+ if (error) {
12
+ return NextResponse.json({ error: error.message }, { status: 500 });
13
+ }
14
+
15
+ return NextResponse.json(data);
16
+ }
17
+
18
+ export async function POST(req: NextRequest) {
19
+ const body = await req.json();
20
+ const { agent_slug, agent_name, prompt, cron, skill_slug } = body;
21
+
22
+ if (!agent_slug || !agent_name || !cron) {
23
+ return NextResponse.json(
24
+ { error: "agent_slug, agent_name, and cron are required" },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ if (!prompt && !skill_slug) {
30
+ return NextResponse.json(
31
+ { error: "Either prompt or skill_slug is required" },
32
+ { status: 400 }
33
+ );
34
+ }
35
+
36
+ const { data, error } = await supabase
37
+ .from("agent_schedules")
38
+ .insert({
39
+ agent_slug,
40
+ agent_name,
41
+ prompt: prompt || "",
42
+ cron,
43
+ skill_slug: skill_slug || null,
44
+ enabled: true,
45
+ })
46
+ .select()
47
+ .single();
48
+
49
+ if (error) {
50
+ return NextResponse.json({ error: error.message }, { status: 500 });
51
+ }
52
+
53
+ // Register with BullMQ
54
+ try {
55
+ const queue = getQueue();
56
+ await queue.upsertJobScheduler(
57
+ `schedule-${data.id}`,
58
+ { pattern: cron },
59
+ {
60
+ name: "scheduled-agent-run",
61
+ data: {
62
+ scheduleId: data.id,
63
+ agentSlug: data.agent_slug,
64
+ agentName: data.agent_name,
65
+ prompt: data.prompt,
66
+ skillSlug: data.skill_slug || undefined,
67
+ },
68
+ }
69
+ );
70
+ } catch (err) {
71
+ console.error("Failed to register BullMQ scheduler:", err);
72
+ }
73
+
74
+ return NextResponse.json(data, { status: 201 });
75
+ }
@@ -0,0 +1,33 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { createClient } from "@/lib/supabase-server";
3
+ import { requireRole } from "@/lib/auth-guard";
4
+
5
+ export async function GET(req: NextRequest) {
6
+ const auth = await requireRole(req, ["admin"], "view_access_logs", "settings/access-logs");
7
+ if (!auth.authorized) return auth.response;
8
+
9
+ const { searchParams } = req.nextUrl;
10
+ const limit = parseInt(searchParams.get("limit") || "100", 10);
11
+ const filter = searchParams.get("filter"); // "blocked" | "allowed" | null
12
+
13
+ const supabase = await createClient();
14
+ let query = supabase
15
+ .from("access_logs")
16
+ .select("*")
17
+ .order("created_at", { ascending: false })
18
+ .limit(limit);
19
+
20
+ if (filter === "blocked") {
21
+ query = query.eq("allowed", false);
22
+ } else if (filter === "allowed") {
23
+ query = query.eq("allowed", true);
24
+ }
25
+
26
+ const { data, error } = await query;
27
+
28
+ if (error) {
29
+ return NextResponse.json({ error: error.message }, { status: 500 });
30
+ }
31
+
32
+ return NextResponse.json(data);
33
+ }
@@ -0,0 +1,44 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { requireRole } from "@/lib/auth-guard";
3
+ import { readFile, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
+
6
+ const PROJECT_ROOT = process.env.PROJECT_ROOT || "/mnt/c/Users/Admin/Documents/PersonalAIssistant";
7
+ const CLAUDE_MD_PATH = join(PROJECT_ROOT, "CLAUDE.md");
8
+
9
+ export async function GET(req: NextRequest) {
10
+ const auth = await requireRole(req, ["admin"], "view_claude_md", "settings/claude-md");
11
+ if (!auth.authorized) return auth.response;
12
+
13
+ try {
14
+ const content = await readFile(CLAUDE_MD_PATH, "utf-8");
15
+ return NextResponse.json({ content, path: CLAUDE_MD_PATH });
16
+ } catch (err: unknown) {
17
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
18
+ return NextResponse.json({ content: "", path: CLAUDE_MD_PATH });
19
+ }
20
+ return NextResponse.json(
21
+ { error: "Failed to read CLAUDE.md" },
22
+ { status: 500 }
23
+ );
24
+ }
25
+ }
26
+
27
+ export async function PUT(req: NextRequest) {
28
+ const auth = await requireRole(req, ["admin"], "edit_claude_md", "settings/claude-md");
29
+ if (!auth.authorized) return auth.response;
30
+
31
+ try {
32
+ const { content } = await req.json();
33
+ if (typeof content !== "string") {
34
+ return NextResponse.json({ error: "content must be a string" }, { status: 400 });
35
+ }
36
+ await writeFile(CLAUDE_MD_PATH, content, "utf-8");
37
+ return NextResponse.json({ success: true });
38
+ } catch {
39
+ return NextResponse.json(
40
+ { error: "Failed to write CLAUDE.md" },
41
+ { status: 500 }
42
+ );
43
+ }
44
+ }