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.
- package/index.js +393 -0
- package/package.json +31 -0
- package/templates/CLAUDE.md +108 -0
- package/templates/app/.env.local.example +14 -0
- package/templates/app/ecosystem.config.js +29 -0
- package/templates/app/next-env.d.ts +6 -0
- package/templates/app/next.config.ts +16 -0
- package/templates/app/package-lock.json +4581 -0
- package/templates/app/package.json +38 -0
- package/templates/app/postcss.config.js +5 -0
- package/templates/app/src/app/agents/[slug]/chat/loading.tsx +26 -0
- package/templates/app/src/app/agents/[slug]/chat/page.tsx +579 -0
- package/templates/app/src/app/agents/[slug]/loading.tsx +19 -0
- package/templates/app/src/app/agents/page.tsx +8 -0
- package/templates/app/src/app/api/agents/[slug]/capabilities/route.ts +11 -0
- package/templates/app/src/app/api/agents/[slug]/route.ts +57 -0
- package/templates/app/src/app/api/agents/route.ts +28 -0
- package/templates/app/src/app/api/ai/generate-agent/route.ts +87 -0
- package/templates/app/src/app/api/ai/improve-claude-md/route.ts +78 -0
- package/templates/app/src/app/api/ai/suggestions/route.ts +64 -0
- package/templates/app/src/app/api/ai/title/route.ts +88 -0
- package/templates/app/src/app/api/auth/role/route.ts +17 -0
- package/templates/app/src/app/api/commands/[slug]/route.ts +61 -0
- package/templates/app/src/app/api/commands/route.ts +6 -0
- package/templates/app/src/app/api/governance/costs/route.ts +117 -0
- package/templates/app/src/app/api/governance/sessions/route.ts +335 -0
- package/templates/app/src/app/api/notifications/route.ts +62 -0
- package/templates/app/src/app/api/preferences/route.ts +44 -0
- package/templates/app/src/app/api/runs/[id]/approve/route.ts +38 -0
- package/templates/app/src/app/api/runs/[id]/events/route.ts +28 -0
- package/templates/app/src/app/api/runs/[id]/metadata/route.ts +30 -0
- package/templates/app/src/app/api/runs/[id]/route.ts +21 -0
- package/templates/app/src/app/api/runs/[id]/start/route.ts +61 -0
- package/templates/app/src/app/api/runs/[id]/stop/route.ts +16 -0
- package/templates/app/src/app/api/runs/[id]/stream/route.ts +201 -0
- package/templates/app/src/app/api/runs/route.ts +95 -0
- package/templates/app/src/app/api/schedules/[id]/route.ts +81 -0
- package/templates/app/src/app/api/schedules/route.ts +75 -0
- package/templates/app/src/app/api/settings/access-logs/route.ts +33 -0
- package/templates/app/src/app/api/settings/claude-md/route.ts +44 -0
- package/templates/app/src/app/api/settings/env-keys/route.ts +271 -0
- package/templates/app/src/app/api/settings/users/route.ts +108 -0
- package/templates/app/src/app/api/skills/[slug]/route.ts +43 -0
- package/templates/app/src/app/api/skills/route.ts +6 -0
- package/templates/app/src/app/api/tools/route.ts +65 -0
- package/templates/app/src/app/api/uploads/cleanup/route.ts +29 -0
- package/templates/app/src/app/api/uploads/route.ts +77 -0
- package/templates/app/src/app/auth/callback/route.ts +19 -0
- package/templates/app/src/app/globals.css +115 -0
- package/templates/app/src/app/layout.tsx +24 -0
- package/templates/app/src/app/loading.tsx +16 -0
- package/templates/app/src/app/login/page.tsx +64 -0
- package/templates/app/src/app/not-authorized/page.tsx +33 -0
- package/templates/app/src/app/runs/page.tsx +55 -0
- package/templates/app/src/app/schedules/page.tsx +110 -0
- package/templates/app/src/app/settings/page.tsx +1294 -0
- package/templates/app/src/app/skills/page.tsx +7 -0
- package/templates/app/src/components/agent-card.tsx +58 -0
- package/templates/app/src/components/agent-grid.tsx +90 -0
- package/templates/app/src/components/auth/auth-context.tsx +79 -0
- package/templates/app/src/components/chat-thread.tsx +50 -0
- package/templates/app/src/components/chat-view.tsx +670 -0
- package/templates/app/src/components/commands-browser.tsx +349 -0
- package/templates/app/src/components/create-agent-modal.tsx +388 -0
- package/templates/app/src/components/governance-dashboard.tsx +397 -0
- package/templates/app/src/components/icons.tsx +401 -0
- package/templates/app/src/components/layout/agent-sidebar.tsx +504 -0
- package/templates/app/src/components/layout/app-shell.tsx +29 -0
- package/templates/app/src/components/layout/nav.tsx +87 -0
- package/templates/app/src/components/layout/overview-inner.tsx +14 -0
- package/templates/app/src/components/layout/profile-menu.tsx +95 -0
- package/templates/app/src/components/layout/sidebar.tsx +30 -0
- package/templates/app/src/components/markdown.tsx +57 -0
- package/templates/app/src/components/message-bar.tsx +161 -0
- package/templates/app/src/components/notifications/notification-bell.tsx +104 -0
- package/templates/app/src/components/notifications/notification-panel.tsx +116 -0
- package/templates/app/src/components/overview/overview-content.tsx +287 -0
- package/templates/app/src/components/overview/overview-context.tsx +88 -0
- package/templates/app/src/components/preferences-modal.tsx +112 -0
- package/templates/app/src/components/run-form.tsx +73 -0
- package/templates/app/src/components/run-history-table.tsx +226 -0
- package/templates/app/src/components/run-output.tsx +187 -0
- package/templates/app/src/components/schedule-form.tsx +148 -0
- package/templates/app/src/components/skills-browser.tsx +338 -0
- package/templates/app/src/components/tool-tooltip.tsx +82 -0
- package/templates/app/src/hooks/use-sse.ts +115 -0
- package/templates/app/src/instrumentation.ts +9 -0
- package/templates/app/src/lib/agent-cache.ts +19 -0
- package/templates/app/src/lib/agent-runner.ts +411 -0
- package/templates/app/src/lib/agents.ts +168 -0
- package/templates/app/src/lib/ai.ts +40 -0
- package/templates/app/src/lib/approval-store.ts +70 -0
- package/templates/app/src/lib/auth-guard.ts +116 -0
- package/templates/app/src/lib/capabilities.ts +191 -0
- package/templates/app/src/lib/line-diff.ts +96 -0
- package/templates/app/src/lib/queue.ts +22 -0
- package/templates/app/src/lib/redis.ts +12 -0
- package/templates/app/src/lib/role-permissions.ts +166 -0
- package/templates/app/src/lib/run-agent.ts +442 -0
- package/templates/app/src/lib/supabase-browser.ts +8 -0
- package/templates/app/src/lib/supabase-middleware.ts +63 -0
- package/templates/app/src/lib/supabase-server.ts +28 -0
- package/templates/app/src/lib/supabase.ts +6 -0
- package/templates/app/src/lib/tool-descriptions.ts +29 -0
- package/templates/app/src/lib/types.ts +73 -0
- package/templates/app/src/lib/typewriter-animation.ts +159 -0
- package/templates/app/src/middleware.ts +13 -0
- package/templates/app/tsconfig.json +21 -0
- package/templates/app/uploads/.gitkeep +0 -0
- package/templates/app/worker/index.ts +342 -0
- package/templates/claude/agents/ai-trends-scout.md +66 -0
- package/templates/claude/commands/add-to-todos.md +56 -0
- package/templates/claude/commands/check-todos.md +56 -0
- package/templates/claude/hooks/auto-approve-safe.sh +34 -0
- package/templates/claude/hooks/auto-format.sh +25 -0
- package/templates/claude/hooks/block-destructive.sh +32 -0
- package/templates/claude/hooks/compaction-preserver.sh +16 -0
- package/templates/claude/hooks/notify.sh +26 -0
- package/templates/claude/settings.local.json +66 -0
- package/templates/claude/skills/frontend-design/SKILL.md +127 -0
- package/templates/claude/skills/frontend-design/reference/color-and-contrast.md +132 -0
- package/templates/claude/skills/frontend-design/reference/interaction-design.md +123 -0
- package/templates/claude/skills/frontend-design/reference/motion-design.md +99 -0
- package/templates/claude/skills/frontend-design/reference/responsive-design.md +114 -0
- package/templates/claude/skills/frontend-design/reference/spatial-design.md +100 -0
- package/templates/claude/skills/frontend-design/reference/typography.md +131 -0
- package/templates/claude/skills/frontend-design/reference/ux-writing.md +107 -0
- package/templates/claude/skills/gws-admin-reports/SKILL.md +57 -0
- package/templates/claude/skills/gws-calendar/SKILL.md +108 -0
- package/templates/claude/skills/gws-calendar-agenda/SKILL.md +52 -0
- package/templates/claude/skills/gws-calendar-insert/SKILL.md +55 -0
- package/templates/claude/skills/gws-chat/SKILL.md +73 -0
- package/templates/claude/skills/gws-chat-send/SKILL.md +49 -0
- package/templates/claude/skills/gws-classroom/SKILL.md +75 -0
- package/templates/claude/skills/gws-docs/SKILL.md +48 -0
- package/templates/claude/skills/gws-docs-write/SKILL.md +49 -0
- package/templates/claude/skills/gws-drive/SKILL.md +137 -0
- package/templates/claude/skills/gws-drive-upload/SKILL.md +52 -0
- package/templates/claude/skills/gws-events/SKILL.md +67 -0
- package/templates/claude/skills/gws-events-renew/SKILL.md +48 -0
- package/templates/claude/skills/gws-events-subscribe/SKILL.md +59 -0
- package/templates/claude/skills/gws-forms/SKILL.md +45 -0
- package/templates/claude/skills/gws-gmail/SKILL.md +59 -0
- package/templates/claude/skills/gws-gmail-forward/SKILL.md +53 -0
- package/templates/claude/skills/gws-gmail-reply/SKILL.md +56 -0
- package/templates/claude/skills/gws-gmail-reply-all/SKILL.md +60 -0
- package/templates/claude/skills/gws-gmail-send/SKILL.md +55 -0
- package/templates/claude/skills/gws-gmail-triage/SKILL.md +50 -0
- package/templates/claude/skills/gws-gmail-watch/SKILL.md +58 -0
- package/templates/claude/skills/gws-keep/SKILL.md +48 -0
- package/templates/claude/skills/gws-meet/SKILL.md +51 -0
- package/templates/claude/skills/gws-modelarmor/SKILL.md +42 -0
- package/templates/claude/skills/gws-modelarmor-create-template/SKILL.md +53 -0
- package/templates/claude/skills/gws-modelarmor-sanitize-prompt/SKILL.md +48 -0
- package/templates/claude/skills/gws-modelarmor-sanitize-response/SKILL.md +48 -0
- package/templates/claude/skills/gws-people/SKILL.md +67 -0
- package/templates/claude/skills/gws-shared/SKILL.md +66 -0
- package/templates/claude/skills/gws-sheets/SKILL.md +53 -0
- package/templates/claude/skills/gws-sheets-append/SKILL.md +51 -0
- package/templates/claude/skills/gws-sheets-read/SKILL.md +47 -0
- package/templates/claude/skills/gws-slides/SKILL.md +43 -0
- package/templates/claude/skills/gws-tasks/SKILL.md +56 -0
- package/templates/claude/skills/gws-workflow/SKILL.md +44 -0
- package/templates/claude/skills/gws-workflow-email-to-task/SKILL.md +47 -0
- package/templates/claude/skills/gws-workflow-file-announce/SKILL.md +50 -0
- package/templates/claude/skills/gws-workflow-meeting-prep/SKILL.md +47 -0
- package/templates/claude/skills/gws-workflow-standup-report/SKILL.md +46 -0
- package/templates/claude/skills/gws-workflow-weekly-digest/SKILL.md +46 -0
- package/templates/claude/skills/persona-content-creator/SKILL.md +33 -0
- package/templates/claude/skills/persona-customer-support/SKILL.md +34 -0
- package/templates/claude/skills/persona-event-coordinator/SKILL.md +35 -0
- package/templates/claude/skills/persona-exec-assistant/SKILL.md +35 -0
- package/templates/claude/skills/persona-hr-coordinator/SKILL.md +33 -0
- package/templates/claude/skills/persona-it-admin/SKILL.md +30 -0
- package/templates/claude/skills/persona-project-manager/SKILL.md +35 -0
- package/templates/claude/skills/persona-researcher/SKILL.md +33 -0
- package/templates/claude/skills/persona-sales-ops/SKILL.md +35 -0
- package/templates/claude/skills/persona-team-lead/SKILL.md +36 -0
- package/templates/claude/skills/recipe-backup-sheet-as-csv/SKILL.md +25 -0
- package/templates/claude/skills/recipe-batch-invite-to-event/SKILL.md +25 -0
- package/templates/claude/skills/recipe-block-focus-time/SKILL.md +24 -0
- package/templates/claude/skills/recipe-bulk-download-folder/SKILL.md +25 -0
- package/templates/claude/skills/recipe-collect-form-responses/SKILL.md +25 -0
- package/templates/claude/skills/recipe-compare-sheet-tabs/SKILL.md +25 -0
- package/templates/claude/skills/recipe-copy-sheet-for-new-month/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-classroom-course/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-doc-from-template/SKILL.md +29 -0
- package/templates/claude/skills/recipe-create-events-from-sheet/SKILL.md +24 -0
- package/templates/claude/skills/recipe-create-expense-tracker/SKILL.md +26 -0
- package/templates/claude/skills/recipe-create-feedback-form/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-gmail-filter/SKILL.md +26 -0
- package/templates/claude/skills/recipe-create-meet-space/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-presentation/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-shared-drive/SKILL.md +25 -0
- package/templates/claude/skills/recipe-create-task-list/SKILL.md +26 -0
- package/templates/claude/skills/recipe-create-vacation-responder/SKILL.md +25 -0
- package/templates/claude/skills/recipe-draft-email-from-doc/SKILL.md +25 -0
- package/templates/claude/skills/recipe-email-drive-link/SKILL.md +25 -0
- package/templates/claude/skills/recipe-find-free-time/SKILL.md +25 -0
- package/templates/claude/skills/recipe-find-large-files/SKILL.md +24 -0
- package/templates/claude/skills/recipe-forward-labeled-emails/SKILL.md +27 -0
- package/templates/claude/skills/recipe-generate-report-from-sheet/SKILL.md +34 -0
- package/templates/claude/skills/recipe-label-and-archive-emails/SKILL.md +25 -0
- package/templates/claude/skills/recipe-log-deal-update/SKILL.md +25 -0
- package/templates/claude/skills/recipe-organize-drive-folder/SKILL.md +26 -0
- package/templates/claude/skills/recipe-plan-weekly-schedule/SKILL.md +26 -0
- package/templates/claude/skills/recipe-post-mortem-setup/SKILL.md +25 -0
- package/templates/claude/skills/recipe-reschedule-meeting/SKILL.md +25 -0
- package/templates/claude/skills/recipe-review-meet-participants/SKILL.md +25 -0
- package/templates/claude/skills/recipe-review-overdue-tasks/SKILL.md +25 -0
- package/templates/claude/skills/recipe-save-email-attachments/SKILL.md +26 -0
- package/templates/claude/skills/recipe-save-email-to-doc/SKILL.md +29 -0
- package/templates/claude/skills/recipe-schedule-recurring-event/SKILL.md +24 -0
- package/templates/claude/skills/recipe-send-team-announcement/SKILL.md +24 -0
- package/templates/claude/skills/recipe-share-doc-and-notify/SKILL.md +25 -0
- package/templates/claude/skills/recipe-share-event-materials/SKILL.md +25 -0
- package/templates/claude/skills/recipe-share-folder-with-team/SKILL.md +26 -0
- package/templates/claude/skills/recipe-sync-contacts-to-sheet/SKILL.md +25 -0
- package/templates/claude/skills/recipe-watch-drive-changes/SKILL.md +25 -0
- 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
|
+
```
|