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,159 @@
1
+ /**
2
+ * Typewriter animation engine for text diffs.
3
+ * Drives a textarea through delete → insert transitions for each changed region.
4
+ */
5
+
6
+ import { computeLineDiff, type DiffOp } from "./line-diff";
7
+
8
+ interface AnimateOptions {
9
+ /** Ms per character batch during deletion (default 1) */
10
+ deleteSpeed?: number;
11
+ /** Ms per character batch during insertion (default 2) */
12
+ typeSpeed?: number;
13
+ /** Characters per animation frame (default 4) */
14
+ batchSize?: number;
15
+ /** AbortSignal to cancel the animation */
16
+ signal?: AbortSignal;
17
+ }
18
+
19
+ function delay(ms: number): Promise<void> {
20
+ return new Promise((r) => requestAnimationFrame(() => setTimeout(r, ms)));
21
+ }
22
+
23
+ /**
24
+ * Animate the transition from oldText to newText in a textarea.
25
+ * Calls onContentChange on every frame so React state stays in sync.
26
+ * Returns the final text (always newText unless aborted).
27
+ */
28
+ export async function animateDiff(
29
+ textarea: HTMLTextAreaElement | null,
30
+ oldText: string,
31
+ newText: string,
32
+ onContentChange: (text: string) => void,
33
+ options: AnimateOptions = {}
34
+ ): Promise<string> {
35
+ const {
36
+ deleteSpeed = 1,
37
+ typeSpeed = 2,
38
+ batchSize = 4,
39
+ signal,
40
+ } = options;
41
+
42
+ if (!textarea || oldText === newText) {
43
+ onContentChange(newText);
44
+ return newText;
45
+ }
46
+
47
+ const ops = computeLineDiff(oldText, newText);
48
+
49
+ // Flatten ops into an animation plan: list of { charOffset, action } sequences
50
+ // We'll work through the current text, applying ops one by one.
51
+ let currentText = oldText;
52
+ let cursor = 0; // character position in currentText
53
+
54
+ for (const op of ops) {
55
+ if (signal?.aborted) break;
56
+
57
+ if (op.type === "keep") {
58
+ // Advance cursor past these lines + the \n joining to next
59
+ cursor += op.text.length;
60
+ // Account for the \n between this keep block and the next op
61
+ if (cursor < currentText.length && currentText[cursor] === "\n") {
62
+ cursor += 1;
63
+ }
64
+ continue;
65
+ }
66
+
67
+ if (op.type === "delete" || op.type === "replace") {
68
+ const deleteText = op.type === "delete" ? op.text : op.oldText;
69
+ // Also account for the trailing newline that separates this block
70
+ let deleteLen = deleteText.length;
71
+ // Check if there's a \n after this block that should also be removed
72
+ if (cursor + deleteLen < currentText.length && currentText[cursor + deleteLen] === "\n") {
73
+ deleteLen += 1;
74
+ }
75
+
76
+ // Delete phase: remove characters from end of region backward
77
+ let remaining = deleteLen;
78
+ while (remaining > 0 && !signal?.aborted) {
79
+ const chunk = Math.min(batchSize, remaining);
80
+ const deleteAt = cursor + remaining - chunk;
81
+ currentText =
82
+ currentText.slice(0, deleteAt) + currentText.slice(deleteAt + chunk);
83
+ remaining -= chunk;
84
+ onContentChange(currentText);
85
+ // Position cursor in textarea
86
+ textarea.value = currentText;
87
+ textarea.selectionStart = textarea.selectionEnd = cursor;
88
+ scrollToCursor(textarea, cursor);
89
+ await delay(deleteSpeed);
90
+ }
91
+
92
+ if (op.type === "replace") {
93
+ // Insert phase: type new text character by character
94
+ const insertText = op.newText + (deleteLen > op.oldText.length ? "\n" : "");
95
+ let inserted = 0;
96
+ while (inserted < insertText.length && !signal?.aborted) {
97
+ const chunk = Math.min(batchSize, insertText.length - inserted);
98
+ const chars = insertText.slice(inserted, inserted + chunk);
99
+ currentText =
100
+ currentText.slice(0, cursor + inserted) +
101
+ chars +
102
+ currentText.slice(cursor + inserted);
103
+ inserted += chunk;
104
+ onContentChange(currentText);
105
+ textarea.value = currentText;
106
+ textarea.selectionStart = textarea.selectionEnd = cursor + inserted;
107
+ scrollToCursor(textarea, cursor + inserted);
108
+ await delay(typeSpeed);
109
+ }
110
+ cursor += insertText.length;
111
+ }
112
+ continue;
113
+ }
114
+
115
+ if (op.type === "insert") {
116
+ // Pure insertion — add a newline before if needed
117
+ const prefix = cursor > 0 && currentText[cursor - 1] !== "\n" ? "\n" : "";
118
+ const insertText = prefix + op.text + "\n";
119
+ let inserted = 0;
120
+ while (inserted < insertText.length && !signal?.aborted) {
121
+ const chunk = Math.min(batchSize, insertText.length - inserted);
122
+ const chars = insertText.slice(inserted, inserted + chunk);
123
+ currentText =
124
+ currentText.slice(0, cursor + inserted) +
125
+ chars +
126
+ currentText.slice(cursor + inserted);
127
+ inserted += chunk;
128
+ onContentChange(currentText);
129
+ textarea.value = currentText;
130
+ textarea.selectionStart = textarea.selectionEnd = cursor + inserted;
131
+ scrollToCursor(textarea, cursor + inserted);
132
+ await delay(typeSpeed);
133
+ }
134
+ cursor += insertText.length;
135
+ continue;
136
+ }
137
+ }
138
+
139
+ // Safety: ensure we land exactly on newText
140
+ onContentChange(newText);
141
+ if (textarea) textarea.value = newText;
142
+ return newText;
143
+ }
144
+
145
+ function scrollToCursor(textarea: HTMLTextAreaElement, cursorPos: number) {
146
+ // Approximate line from cursor position
147
+ const textBefore = textarea.value.slice(0, cursorPos);
148
+ const lineNumber = textBefore.split("\n").length;
149
+ const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 20;
150
+ const targetScroll = lineNumber * lineHeight - textarea.clientHeight / 2;
151
+ if (Math.abs(textarea.scrollTop - targetScroll) > lineHeight * 3) {
152
+ textarea.scrollTop = Math.max(0, targetScroll);
153
+ }
154
+ }
155
+
156
+ /** Quick check: does the diff have any actual changes? */
157
+ export function hasChanges(ops: DiffOp[]): boolean {
158
+ return ops.some((op) => op.type !== "keep");
159
+ }
@@ -0,0 +1,13 @@
1
+ import { type NextRequest } from "next/server";
2
+ import { updateSession } from "@/lib/supabase-middleware";
3
+
4
+ export async function middleware(request: NextRequest) {
5
+ return await updateSession(request);
6
+ }
7
+
8
+ export const config = {
9
+ matcher: [
10
+ // Match all paths except static files and Next.js internals
11
+ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
12
+ ],
13
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./src/*"] }
18
+ },
19
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
20
+ "exclude": ["node_modules"]
21
+ }
File without changes
@@ -0,0 +1,342 @@
1
+ import { Worker, Queue } from "bullmq";
2
+ import { createClient } from "@supabase/supabase-js";
3
+ import path from "path";
4
+
5
+ // Load env from .env.local if available
6
+ import { config } from "dotenv";
7
+ config({ path: path.join(__dirname, "..", ".env.local") });
8
+
9
+ const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
10
+ const SUPABASE_URL = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL!;
11
+ const SUPABASE_KEY = process.env.SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
12
+ const PROJECT_ROOT = process.env.PROJECT_ROOT || path.resolve(__dirname, "../..");
13
+ const QUEUE_NAME = "agent-runs";
14
+
15
+ function getRedisConfig() {
16
+ const url = new URL(REDIS_URL);
17
+ return {
18
+ host: url.hostname,
19
+ port: parseInt(url.port || "6379", 10),
20
+ password: url.password || undefined,
21
+ maxRetriesPerRequest: null as null,
22
+ };
23
+ }
24
+
25
+ const connection = getRedisConfig();
26
+ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
27
+
28
+ // Dynamic import for ESM compatibility with agent SDK
29
+ async function loadAgentRunner() {
30
+ // We need to load agents and execute them
31
+ const fs = await import("fs");
32
+ const matter = (await import("gray-matter")).default;
33
+
34
+ const agentsDir = path.join(PROJECT_ROOT, ".claude", "agents");
35
+
36
+ function getAgentMeta(slug: string) {
37
+ if (slug === "main") {
38
+ return {
39
+ slug: "main",
40
+ name: "My Assistant",
41
+ description: "Personal AI assistant with full access to all tools and subagents.",
42
+ tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch", "Agent"],
43
+ model: undefined,
44
+ filePath: "",
45
+ };
46
+ }
47
+ const filePath = path.join(agentsDir, `${slug}.md`);
48
+ if (!fs.existsSync(filePath)) return null;
49
+ const raw = fs.readFileSync(filePath, "utf-8");
50
+ const { data } = matter(raw);
51
+ return {
52
+ slug,
53
+ name: data.name || slug,
54
+ description: data.description || "",
55
+ tools: data.tools ? String(data.tools).split(",").map((t: string) => t.trim()).filter(Boolean) : [],
56
+ color: data.color,
57
+ emoji: data.emoji,
58
+ vibe: data.vibe,
59
+ model: data.model,
60
+ filePath,
61
+ };
62
+ }
63
+
64
+ function getProjectInstructions(): string {
65
+ try {
66
+ return fs.readFileSync(path.join(PROJECT_ROOT, "CLAUDE.md"), "utf-8").trim();
67
+ } catch { return ""; }
68
+ }
69
+
70
+ function getAgentSystemPrompt(slug: string): string {
71
+ const projectInstructions = getProjectInstructions();
72
+ if (slug === "main") return projectInstructions;
73
+ const filePath = path.join(agentsDir, `${slug}.md`);
74
+ const raw = fs.readFileSync(filePath, "utf-8");
75
+ const { content } = matter(raw);
76
+ return [projectInstructions, content.trim()].filter(Boolean).join("\n\n---\n\n");
77
+ }
78
+
79
+ function getSubagentDefinitions(): Record<string, { description: string; prompt: string; tools: string[] }> {
80
+ const defs: Record<string, { description: string; prompt: string; tools: string[] }> = {};
81
+ const files = fs.readdirSync(agentsDir).filter((f: string) => f.endsWith(".md"));
82
+ for (const file of files) {
83
+ const filePath = path.join(agentsDir, file);
84
+ const raw = fs.readFileSync(filePath, "utf-8");
85
+ const { data, content } = matter(raw);
86
+ const slug = path.basename(file, ".md");
87
+ const tools = data.tools ? String(data.tools).split(",").map((t: string) => t.trim()).filter(Boolean) : ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];
88
+ defs[slug] = { description: data.description || slug, prompt: content.trim(), tools };
89
+ }
90
+ return defs;
91
+ }
92
+
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ function getMcpServers(): Record<string, any> {
95
+ try {
96
+ const mcpPath = path.join(PROJECT_ROOT, ".mcp.json");
97
+ const raw = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
98
+ return raw.mcpServers || {};
99
+ } catch { return {}; }
100
+ }
101
+
102
+ function getSkillContent(slug: string): string | null {
103
+ try {
104
+ const skillFile = path.join(PROJECT_ROOT, ".claude", "skills", slug, "SKILL.md");
105
+ if (!fs.existsSync(skillFile)) return null;
106
+ const raw = fs.readFileSync(skillFile, "utf-8");
107
+ const { content } = matter(raw);
108
+ return content.trim() || null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ return { getAgentMeta, getAgentSystemPrompt, getSubagentDefinitions, getMcpServers, getSkillContent };
115
+ }
116
+
117
+ async function main() {
118
+ const { getAgentMeta, getAgentSystemPrompt, getSubagentDefinitions, getMcpServers, getSkillContent } = await loadAgentRunner();
119
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
120
+
121
+ // Sync schedules from Supabase to BullMQ on startup
122
+ const queue = new Queue(QUEUE_NAME, { connection });
123
+
124
+ const { data: schedules } = await supabase
125
+ .from("agent_schedules")
126
+ .select("*")
127
+ .eq("enabled", true);
128
+
129
+ if (schedules) {
130
+ for (const schedule of schedules) {
131
+ await queue.upsertJobScheduler(
132
+ `schedule-${schedule.id}`,
133
+ { pattern: schedule.cron },
134
+ {
135
+ name: "scheduled-agent-run",
136
+ data: {
137
+ scheduleId: schedule.id,
138
+ agentSlug: schedule.agent_slug,
139
+ agentName: schedule.agent_name,
140
+ prompt: schedule.prompt,
141
+ skillSlug: schedule.skill_slug || undefined,
142
+ },
143
+ }
144
+ );
145
+ console.log(`[worker] Registered scheduler: schedule-${schedule.id} (${schedule.cron})`);
146
+ }
147
+ }
148
+
149
+ const worker = new Worker(
150
+ QUEUE_NAME,
151
+ async (job) => {
152
+ const { scheduleId, agentSlug, agentName, prompt, skillSlug } = job.data;
153
+ console.log(`[worker] Processing job ${job.id}: agent=${agentSlug}`);
154
+
155
+ const agent = getAgentMeta(agentSlug);
156
+ if (!agent) {
157
+ throw new Error(`Agent ${agentSlug} not found`);
158
+ }
159
+
160
+ // Create a run record in Supabase
161
+ const { data: run, error: insertError } = await supabase
162
+ .from("agent_runs")
163
+ .insert({
164
+ agent_slug: agentSlug,
165
+ agent_name: agentName,
166
+ prompt,
167
+ status: "running",
168
+ schedule_id: scheduleId || null,
169
+ started_at: new Date().toISOString(),
170
+ })
171
+ .select()
172
+ .single();
173
+
174
+ if (insertError || !run) {
175
+ throw new Error(`Failed to create run record: ${insertError?.message}`);
176
+ }
177
+
178
+ const startTime = Date.now();
179
+ let output = "";
180
+ let sessionId = "";
181
+ let costUsd = 0;
182
+
183
+ try {
184
+ const isMainAgent = agentSlug === "main";
185
+ const mcpServers = getMcpServers();
186
+ let systemPrompt = getAgentSystemPrompt(agentSlug);
187
+
188
+ // Inject skill content into system prompt if a skill is specified
189
+ if (skillSlug) {
190
+ const skillContent = getSkillContent(skillSlug);
191
+ if (skillContent) {
192
+ systemPrompt = [systemPrompt, `## Active Skill: ${skillSlug}\n\n${skillContent}`]
193
+ .filter(Boolean).join("\n\n---\n\n");
194
+ }
195
+ }
196
+ const subagents = isMainAgent ? getSubagentDefinitions() : undefined;
197
+
198
+ const q = query({
199
+ prompt,
200
+ options: {
201
+ cwd: PROJECT_ROOT,
202
+ permissionMode: "bypassPermissions",
203
+ allowDangerouslySkipPermissions: true,
204
+ systemPrompt,
205
+ maxBudgetUsd: isMainAgent ? 5.0 : 2.0,
206
+ model: agent.model || undefined,
207
+ mcpServers,
208
+ ...(subagents ? { agents: subagents, allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch", "Agent"] } : {}),
209
+ env: {
210
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
211
+ },
212
+ },
213
+ });
214
+
215
+ for await (const message of q) {
216
+ if (message.type === "system" && "subtype" in message && message.subtype === "init") {
217
+ sessionId = message.session_id;
218
+ }
219
+
220
+ if (message.type === "result") {
221
+ const result = message as {
222
+ total_cost_usd: number;
223
+ session_id: string;
224
+ result?: string;
225
+ subtype: string;
226
+ errors?: string[];
227
+ };
228
+ costUsd = result.total_cost_usd;
229
+ sessionId = result.session_id || sessionId;
230
+
231
+ if (result.subtype !== "success") {
232
+ throw new Error(result.errors?.join("; ") || `Agent ended with ${result.subtype}`);
233
+ }
234
+ if (result.result) {
235
+ output = result.result;
236
+ }
237
+ }
238
+ }
239
+
240
+ const durationMs = Date.now() - startTime;
241
+
242
+ await supabase
243
+ .from("agent_runs")
244
+ .update({
245
+ status: "completed",
246
+ output,
247
+ cost_usd: costUsd,
248
+ duration_ms: durationMs,
249
+ session_id: sessionId,
250
+ completed_at: new Date().toISOString(),
251
+ })
252
+ .eq("id", run.id);
253
+
254
+ // Update schedule last_run_at
255
+ if (scheduleId) {
256
+ await supabase
257
+ .from("agent_schedules")
258
+ .update({ last_run_at: new Date().toISOString() })
259
+ .eq("id", scheduleId);
260
+ }
261
+
262
+ // Create notification
263
+ const contentSkills = ["create-blog-post", "create-linkedin-post", "create-post-designs", "publish-wordpress"];
264
+ const notifType = skillSlug && contentSkills.includes(skillSlug) ? "needs_review" : "completed";
265
+ await supabase.from("notifications").insert({
266
+ run_id: run.id,
267
+ schedule_id: scheduleId || null,
268
+ agent_slug: agentSlug,
269
+ session_id: sessionId || null,
270
+ type: notifType,
271
+ title: `${agentName}: ${notifType === "needs_review" ? "Ready for review" : "Completed"}`,
272
+ summary: output ? output.slice(0, 200) : null,
273
+ });
274
+
275
+ console.log(`[worker] Job ${job.id} completed: cost=$${costUsd.toFixed(4)}, duration=${durationMs}ms`);
276
+ return { runId: run.id, costUsd, durationMs };
277
+ } catch (err) {
278
+ const errorMsg = err instanceof Error ? err.message : String(err);
279
+
280
+ await supabase
281
+ .from("agent_runs")
282
+ .update({
283
+ status: "failed",
284
+ error: errorMsg,
285
+ completed_at: new Date().toISOString(),
286
+ })
287
+ .eq("id", run.id);
288
+
289
+ // Create failure notification
290
+ await supabase.from("notifications").insert({
291
+ run_id: run.id,
292
+ schedule_id: scheduleId || null,
293
+ agent_slug: agentSlug,
294
+ session_id: sessionId || null,
295
+ type: "failed",
296
+ title: `${agentName}: Failed`,
297
+ summary: errorMsg.slice(0, 200),
298
+ });
299
+
300
+ console.error(`[worker] Job ${job.id} failed: ${errorMsg}`);
301
+ throw err;
302
+ }
303
+ },
304
+ {
305
+ connection,
306
+ concurrency: 2,
307
+ lockDuration: 600_000, // 10 min for long-running agents
308
+ stalledInterval: 600_000,
309
+ maxStalledCount: 2,
310
+ }
311
+ );
312
+
313
+ worker.on("completed", (job) => {
314
+ console.log(`[worker] Job ${job?.id} completed`);
315
+ });
316
+
317
+ worker.on("failed", (job, error) => {
318
+ console.error(`[worker] Job ${job?.id} failed:`, error.message);
319
+ });
320
+
321
+ worker.on("error", (error) => {
322
+ console.error("[worker] Worker error:", error);
323
+ });
324
+
325
+ // Graceful shutdown
326
+ const shutdown = async () => {
327
+ console.log("[worker] Shutting down...");
328
+ await worker.close();
329
+ await queue.close();
330
+ process.exit(0);
331
+ };
332
+
333
+ process.on("SIGTERM", shutdown);
334
+ process.on("SIGINT", shutdown);
335
+
336
+ console.log(`[worker] Started. Listening on queue: ${QUEUE_NAME}`);
337
+ }
338
+
339
+ main().catch((err) => {
340
+ console.error("[worker] Fatal error:", err);
341
+ process.exit(1);
342
+ });
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: AI Trends Scout
3
+ description: >-
4
+ Researches the latest AI models, tools, and trending content to keep you
5
+ updated on what's new and popular in the AI community.
6
+ tools: 'WebSearch, WebFetch, Read'
7
+ color: '#8B5CF6'
8
+ emoji: "\U0001F52E"
9
+ vibe: Spotting tomorrow's AI breakthroughs today
10
+ ---
11
+ # AI Trends Scout Agent
12
+
13
+ ## Identity
14
+ You are an AI Trends Scout - a dedicated researcher who stays on top of the rapidly evolving artificial intelligence landscape. Your mission is to uncover what's new in AI, identify emerging models and tools that are gaining traction, and discover what content is resonating with audiences on platforms like YouTube. You provide timely, accurate intelligence on the AI ecosystem.
15
+
16
+ ## Core Responsibilities
17
+
18
+ ### Research Focus Areas
19
+ 1. **New AI Models & Releases**: Track announcements of new language models, vision models, multimodal systems, and specialized AI tools
20
+ 2. **Emerging Tools & Platforms**: Identify new AI applications, frameworks, and services that developers and businesses are adopting
21
+ 3. **Community Conversations**: Monitor what people are actively discussing - trending topics, breakthroughs, controversies
22
+ 4. **YouTube Trends**: Research popular AI-related video content, what tutorials are getting views, and what creators are covering
23
+
24
+ ## Operational Guidelines
25
+
26
+ ### Search Strategy
27
+ - Conduct multiple targeted searches using keywords like "AI news," "new AI models 2024," "trending AI tools," "AI YouTube trending"
28
+ - Search for specific platforms and sources: Reddit AI communities, Product Hunt, Hacker News, ArXiv papers, YouTube trending AI content
29
+ - Look for both technical depth and broader adoption signals
30
+ - Search for "AI tutorial YouTube trending" and "most viewed AI content" to identify popular educational videos
31
+
32
+ ### Research Methodology
33
+ 1. Start with broad searches to identify current hot topics
34
+ 2. Follow up with specific searches on high-interest items
35
+ 3. Fetch and read content from credible sources
36
+ 4. Cross-reference information across multiple sources
37
+ 5. Identify both hype and substantive developments
38
+
39
+ ### Content Analysis
40
+ - Distinguish between genuine innovation and marketing hype
41
+ - Note engagement metrics when available (views, discussion volume)
42
+ - Identify patterns in what content resonates (tutorials, reviews, debates, how-tos)
43
+ - Pay attention to YouTube video titles, view counts, and channel focus areas
44
+
45
+ ## Output Format
46
+
47
+ When presenting findings, structure your response as:
48
+
49
+ 1. **Latest Model Releases** - New AI systems announced/released recently with key capabilities
50
+ 2. **Trending Tools & Platforms** - Tools gaining momentum and what makes them notable
51
+ 3. **Community Hot Topics** - What AI discussions are getting the most attention
52
+ 4. **YouTube Trending Content** - Top-performing AI videos, creators, and content types with approximate view counts
53
+ 5. **Key Insights** - Patterns and significance of current trends
54
+
55
+ ## Constraints & Guidelines
56
+
57
+ - Be specific with dates and timeframes (e.g., "in the last week", "this month")
58
+ - Include source attribution when possible
59
+ - Focus on publicly available, verified information
60
+ - Note if information is recent (within days) vs. older
61
+ - Distinguish between official releases and community/unofficial projects
62
+ - Be honest about information limitations or uncertainty
63
+ - Avoid speculation; stick to what's actually being discussed and released
64
+
65
+ ## Tone
66
+ Be conversational yet informative. You're an expert scout helping someone stay current, not an academic paper. Make insights accessible and highlight what's genuinely important vs. just noisy trending topics.
@@ -0,0 +1,56 @@
1
+ ---
2
+ description: Add todo item to TO-DOS.md with context from conversation
3
+ argument-hint: <todo-description> (optional - infers from conversation if omitted)
4
+ allowed-tools:
5
+ - Read
6
+ - Edit
7
+ - Write
8
+ ---
9
+
10
+ # Add Todo Item
11
+
12
+ ## Context
13
+
14
+ - Current timestamp: !`date "+%Y-%m-%d %H:%M"`
15
+
16
+ ## Instructions
17
+
18
+ 1. Read TO-DOS.md in the working directory (create with Write tool if it doesn't exist)
19
+
20
+ 2. Check for duplicates:
21
+ - Extract key concept/action from the new todo
22
+ - Search existing todos for similar titles or overlapping scope
23
+ - If found, ask user: "A similar todo already exists: [title]. Would you like to:\n\n1. Skip adding (keep existing)\n2. Replace existing with new version\n3. Add anyway as separate item\n\nReply with the number of your choice."
24
+ - Wait for user response before proceeding
25
+
26
+ 3. Extract todo content:
27
+ - **With $ARGUMENTS**: Use as the focus/title for the todo and context heading
28
+ - **Without $ARGUMENTS**: Analyze recent conversation to extract:
29
+ - Specific problem or task discussed
30
+ - Relevant file paths that need attention
31
+ - Technical details (line numbers, error messages, conflicting specifications)
32
+ - Root cause if identified
33
+
34
+ 4. Append new section to bottom of file:
35
+ - **Heading**: `## Brief Context Title - YYYY-MM-DD HH:MM` (3-8 word title, current timestamp)
36
+ - **Todo format**: `- **[Action verb] [Component]** - [Brief description]. **Problem:** [What's wrong/why needed]. **Files:** [Comma-separated paths with line numbers]. **Solution:** [Approach hints or constraints, if applicable].`
37
+ - **Required fields**: Problem and Files (with line numbers like `path/to/file.ts:123-145`)
38
+ - **Optional field**: Solution
39
+ - Make each section self-contained for future Claude to understand weeks later
40
+ - Use simple list items (not checkboxes) - todos are removed when work begins
41
+
42
+ 5. Confirm and offer to continue with original work:
43
+ - Identify what the user was working on before `/add-to-todos` was called
44
+ - Confirm the todo was saved: "✓ Saved to todos."
45
+ - Ask if they want to continue with the original work: "Would you like to continue with [original task]?"
46
+ - Wait for user response
47
+
48
+ ## Format Example
49
+
50
+ ```markdown
51
+ ## Add Todo Command Improvements - 2025-11-15 14:23
52
+
53
+ - **Add structured format to add-to-todos** - Standardize todo entries with Problem/Files/Solution pattern. **Problem:** Current todos lack consistent structure, making it hard for Claude to have enough context when revisiting tasks later. **Files:** `commands/add-to-todos.md:22-29`. **Solution:** Use inline bold labels with required Problem and Files fields, optional Solution field.
54
+
55
+ - **Create check-todos command** - Build companion command to list and select todos. **Problem:** Need workflow to review outstanding todos and load context for selected item. **Files:** `commands/check-todos.md` (new), `TO-DOS.md` (reads from). **Solution:** Parse markdown list, display numbered list, accept selection to load full context and remove item.
56
+ ```