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,388 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback, useEffect } from "react";
4
+ import { TOOL_DESCRIPTIONS } from "@/lib/tool-descriptions";
5
+ import { XIcon, CheckIcon, ChevronLeftIcon } from "@/components/icons";
6
+
7
+ // All available SDK tools, grouped by category
8
+ const TOOL_CATALOG = [
9
+ {
10
+ label: "Core",
11
+ tools: [
12
+ { name: "Read", hint: TOOL_DESCRIPTIONS.Read },
13
+ { name: "Write", hint: TOOL_DESCRIPTIONS.Write },
14
+ { name: "Edit", hint: TOOL_DESCRIPTIONS.Edit },
15
+ { name: "Bash", hint: TOOL_DESCRIPTIONS.Bash },
16
+ { name: "Glob", hint: TOOL_DESCRIPTIONS.Glob },
17
+ { name: "Grep", hint: TOOL_DESCRIPTIONS.Grep },
18
+ ],
19
+ },
20
+ {
21
+ label: "Web",
22
+ tools: [
23
+ { name: "WebSearch", hint: TOOL_DESCRIPTIONS.WebSearch },
24
+ { name: "WebFetch", hint: TOOL_DESCRIPTIONS.WebFetch },
25
+ ],
26
+ },
27
+ {
28
+ label: "Agent",
29
+ tools: [
30
+ { name: "Agent", hint: TOOL_DESCRIPTIONS.Agent },
31
+ { name: "Task", hint: TOOL_DESCRIPTIONS.Task },
32
+ ],
33
+ },
34
+ {
35
+ label: "Notebook",
36
+ tools: [
37
+ { name: "NotebookEdit", hint: TOOL_DESCRIPTIONS.NotebookEdit },
38
+ ],
39
+ },
40
+ {
41
+ label: "MCP",
42
+ tools: [
43
+ { name: "Mcp", hint: TOOL_DESCRIPTIONS.Mcp },
44
+ { name: "ListMcpResources", hint: TOOL_DESCRIPTIONS.ListMcpResources },
45
+ { name: "ReadMcpResource", hint: TOOL_DESCRIPTIONS.ReadMcpResource },
46
+ ],
47
+ },
48
+ {
49
+ label: "User",
50
+ tools: [
51
+ { name: "AskUserQuestion", hint: TOOL_DESCRIPTIONS.AskUserQuestion },
52
+ ],
53
+ },
54
+ ] as const;
55
+
56
+ const TOOL_PRESETS: Record<string, string[]> = {
57
+ "Researcher": ["Read", "WebSearch", "WebFetch", "Grep", "Glob"],
58
+ "Coder": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
59
+ "Full Access": ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch", "Agent"],
60
+ };
61
+
62
+ interface McpServer {
63
+ name: string;
64
+ type: "local" | "remote";
65
+ }
66
+
67
+ interface GeneratedAgent {
68
+ name: string;
69
+ description: string;
70
+ tools: string;
71
+ mcpServers: string;
72
+ color: string;
73
+ emoji: string;
74
+ vibe: string;
75
+ content: string;
76
+ }
77
+
78
+ // --- Tool Selector ---
79
+
80
+ function ToolSelector({ value, onChange }: { value: string; onChange: (v: string) => void }) {
81
+ const selected = new Set(value.split(",").map((t) => t.trim()).filter(Boolean));
82
+
83
+ const toggle = useCallback(
84
+ (name: string) => {
85
+ const next = new Set(selected);
86
+ if (next.has(name)) next.delete(name);
87
+ else next.add(name);
88
+ onChange(Array.from(next).join(", "));
89
+ },
90
+ [selected, onChange]
91
+ );
92
+
93
+ const applyPreset = useCallback((presetName: string) => { onChange(TOOL_PRESETS[presetName].join(", ")); }, [onChange]);
94
+ const selectAll = useCallback(() => { onChange(TOOL_CATALOG.flatMap((g) => g.tools.map((t) => t.name)).join(", ")); }, [onChange]);
95
+ const clearAll = useCallback(() => { onChange(""); }, [onChange]);
96
+
97
+ return (
98
+ <div>
99
+ <div className="flex items-center justify-between mb-2">
100
+ <label className="text-[10px] uppercase tracking-wider text-[var(--text-tertiary)]">
101
+ Tools ({selected.size})
102
+ </label>
103
+ <div className="flex items-center gap-1">
104
+ {Object.keys(TOOL_PRESETS).map((preset) => (
105
+ <button key={preset} type="button" onClick={() => applyPreset(preset)}
106
+ className="rounded-md px-2 py-0.5 text-[10px] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] hover:bg-[var(--bg-hover)] transition-colors">
107
+ {preset}
108
+ </button>
109
+ ))}
110
+ <span className="w-px h-3 bg-[var(--border-subtle)] mx-0.5" />
111
+ <button type="button" onClick={selectAll} className="rounded-md px-1.5 py-0.5 text-[10px] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] transition-colors">All</button>
112
+ <button type="button" onClick={clearAll} className="rounded-md px-1.5 py-0.5 text-[10px] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] transition-colors">None</button>
113
+ </div>
114
+ </div>
115
+
116
+ <div className="rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-raised)] p-3 space-y-2.5">
117
+ {TOOL_CATALOG.map((group) => (
118
+ <div key={group.label}>
119
+ <span className="text-[9px] uppercase tracking-widest text-[var(--text-muted)] font-medium">{group.label}</span>
120
+ <div className="flex flex-wrap gap-1.5 mt-1">
121
+ {group.tools.map((tool) => {
122
+ const isSelected = selected.has(tool.name);
123
+ return (
124
+ <button key={tool.name} type="button" onClick={() => toggle(tool.name)}
125
+ className={`group/tool relative inline-flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-mono border transition-colors cursor-pointer ${
126
+ isSelected
127
+ ? "bg-[var(--accent-muted)] border-[var(--accent-muted)] text-[var(--accent-text)] hover:bg-[var(--accent-muted)]"
128
+ : "bg-[var(--bg-base)] border-[var(--border-subtle)] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] hover:border-[var(--border-default)]"
129
+ }`}
130
+ >
131
+ {isSelected && <CheckIcon size={10} />}
132
+ {tool.name}
133
+ {tool.hint && (
134
+ <span className="pointer-events-none absolute bottom-full left-1/2 -translate-x-1/2 mb-1.5 whitespace-nowrap rounded-md bg-[#111113] border border-[var(--border-default)] px-2.5 py-1 text-[11px] text-[var(--text-secondary)] font-sans font-normal opacity-0 group-hover/tool:opacity-100 transition-opacity shadow-lg z-50">
135
+ {tool.hint}
136
+ </span>
137
+ )}
138
+ </button>
139
+ );
140
+ })}
141
+ </div>
142
+ </div>
143
+ ))}
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ // --- MCP Server Selector ---
150
+
151
+ function McpServerSelector({ value, onChange }: { value: string; onChange: (v: string) => void }) {
152
+ const [servers, setServers] = useState<McpServer[]>([]);
153
+
154
+ useEffect(() => {
155
+ fetch("/api/agents/main/capabilities")
156
+ .then((r) => r.json())
157
+ .then((data) => { if (data.mcpServers) setServers(data.mcpServers); })
158
+ .catch(() => {});
159
+ }, []);
160
+
161
+ const selected = new Set(value.split(",").map((s) => s.trim()).filter(Boolean));
162
+ const toggle = useCallback((name: string) => {
163
+ const next = new Set(selected);
164
+ if (next.has(name)) next.delete(name);
165
+ else next.add(name);
166
+ onChange(Array.from(next).join(", "));
167
+ }, [selected, onChange]);
168
+
169
+ const selectAll = useCallback(() => { onChange(servers.map((s) => s.name).join(", ")); }, [servers, onChange]);
170
+ const clearAll = useCallback(() => { onChange(""); }, [onChange]);
171
+
172
+ if (servers.length === 0) return null;
173
+
174
+ return (
175
+ <div>
176
+ <div className="flex items-center justify-between mb-2">
177
+ <label className="text-[10px] uppercase tracking-wider text-[var(--text-tertiary)]">MCP Servers ({selected.size})</label>
178
+ <div className="flex items-center gap-1">
179
+ <button type="button" onClick={selectAll} className="rounded-md px-1.5 py-0.5 text-[10px] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] transition-colors">All</button>
180
+ <button type="button" onClick={clearAll} className="rounded-md px-1.5 py-0.5 text-[10px] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] transition-colors">None</button>
181
+ </div>
182
+ </div>
183
+
184
+ <div className="rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-raised)] p-3">
185
+ <div className="flex flex-wrap gap-1.5">
186
+ {servers.map((server) => {
187
+ const isSelected = selected.has(server.name);
188
+ return (
189
+ <button key={server.name} type="button" onClick={() => toggle(server.name)}
190
+ className={`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium border transition-colors cursor-pointer ${
191
+ isSelected
192
+ ? "border-green-500/20 bg-green-500/10 text-green-400 hover:bg-green-500/15"
193
+ : "bg-[var(--bg-base)] border-[var(--border-subtle)] text-[var(--text-muted)] hover:text-[var(--text-tertiary)] hover:border-[var(--border-default)]"
194
+ }`}
195
+ >
196
+ {isSelected && <CheckIcon size={10} />}
197
+ <span className={`text-[9px] opacity-50 ${isSelected ? "" : "hidden"}`}>{server.type === "remote" ? "CLOUD" : "LOCAL"}</span>
198
+ {server.name}
199
+ </button>
200
+ );
201
+ })}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ // --- Main Modal ---
209
+
210
+ type Step = "prompt" | "generating" | "review" | "saving";
211
+
212
+ export function CreateAgentModal({ open, onClose, onCreated }: { open: boolean; onClose: () => void; onCreated: () => void }) {
213
+ const [step, setStep] = useState<Step>("prompt");
214
+ const [prompt, setPrompt] = useState("");
215
+ const [error, setError] = useState("");
216
+ const [agent, setAgent] = useState<GeneratedAgent | null>(null);
217
+
218
+ const reset = () => { setStep("prompt"); setPrompt(""); setError(""); setAgent(null); };
219
+ const handleClose = () => { reset(); onClose(); };
220
+
221
+ const handleGenerate = async () => {
222
+ if (!prompt.trim()) return;
223
+ setStep("generating");
224
+ setError("");
225
+ try {
226
+ const res = await fetch("/api/ai/generate-agent", {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json" },
229
+ body: JSON.stringify({ prompt: prompt.trim() }),
230
+ });
231
+ if (!res.ok) { const data = await res.json(); throw new Error(data.error || "Failed to generate agent"); }
232
+ const data = await res.json();
233
+ setAgent({ mcpServers: "", ...data });
234
+ setStep("review");
235
+ } catch (err) {
236
+ setError(err instanceof Error ? err.message : "Something went wrong");
237
+ setStep("prompt");
238
+ }
239
+ };
240
+
241
+ const handleSave = async () => {
242
+ if (!agent) return;
243
+ setStep("saving");
244
+ setError("");
245
+ try {
246
+ const res = await fetch("/api/agents", {
247
+ method: "POST",
248
+ headers: { "Content-Type": "application/json" },
249
+ body: JSON.stringify(agent),
250
+ });
251
+ if (!res.ok) { const data = await res.json(); throw new Error(data.error || "Failed to create agent"); }
252
+ onCreated();
253
+ handleClose();
254
+ } catch (err) {
255
+ setError(err instanceof Error ? err.message : "Something went wrong");
256
+ setStep("review");
257
+ }
258
+ };
259
+
260
+ if (!open) return null;
261
+
262
+ return (
263
+ <div className="fixed inset-0 z-50 flex items-center justify-center" onClick={handleClose}>
264
+ <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
265
+ <div
266
+ className="relative z-10 w-[560px] max-h-[85vh] overflow-y-auto rounded-lg border border-[var(--border-default)] bg-[#111113] shadow-2xl shadow-black/60"
267
+ onClick={(e) => e.stopPropagation()}
268
+ >
269
+ {/* Header */}
270
+ <div className="flex items-center justify-between px-6 pt-5 pb-4 border-b border-[var(--border-subtle)]">
271
+ <h2 className="text-[15px] font-semibold text-[var(--text-primary)]">Create Agent</h2>
272
+ <button onClick={handleClose} className="flex items-center justify-center h-6 w-6 rounded-md text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] transition-colors">
273
+ <XIcon size={14} />
274
+ </button>
275
+ </div>
276
+
277
+ <div className="p-6">
278
+ {/* Step 1: Prompt */}
279
+ {(step === "prompt" || step === "generating") && (
280
+ <div className="space-y-4">
281
+ <div>
282
+ <label className="text-[11px] uppercase tracking-wider text-[var(--text-tertiary)] block mb-2">What should this agent do?</label>
283
+ <textarea
284
+ value={prompt}
285
+ onChange={(e) => setPrompt(e.target.value)}
286
+ placeholder="e.g., Research competitors and summarize their pricing pages..."
287
+ rows={4}
288
+ className="w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-raised)] px-4 py-3 text-[13px] text-[var(--text-primary)] outline-none focus:border-[var(--border-focus)] resize-none leading-relaxed placeholder-[var(--text-muted)]"
289
+ disabled={step === "generating"}
290
+ autoFocus
291
+ />
292
+ </div>
293
+ {error && <p className="text-[12px] text-red-400 bg-red-500/10 rounded-md px-3 py-2">{error}</p>}
294
+ <div className="flex justify-end gap-3">
295
+ <button onClick={handleClose} className="rounded-md px-4 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors">Cancel</button>
296
+ <button
297
+ onClick={handleGenerate}
298
+ disabled={!prompt.trim() || step === "generating"}
299
+ className="rounded-md bg-[var(--accent)] px-5 py-2 text-[12px] font-medium text-white hover:bg-[var(--accent-hover)] transition-colors disabled:opacity-50 flex items-center gap-2"
300
+ >
301
+ {step === "generating" ? (
302
+ <>
303
+ <svg className="animate-spin h-3.5 w-3.5" viewBox="0 0 24 24" fill="none">
304
+ <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" className="opacity-20" />
305
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
306
+ </svg>
307
+ Generating...
308
+ </>
309
+ ) : "Generate Agent"}
310
+ </button>
311
+ </div>
312
+ </div>
313
+ )}
314
+
315
+ {/* Step 2: Review & Edit */}
316
+ {(step === "review" || step === "saving") && agent && (
317
+ <div className="space-y-4">
318
+ <div className="rounded-md border border-[var(--border-subtle)] bg-[var(--bg-raised)] p-4">
319
+ <div className="flex items-start gap-3">
320
+ <input value={agent.emoji} onChange={(e) => setAgent({ ...agent, emoji: e.target.value })}
321
+ className="w-12 h-10 text-center text-2xl rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] text-[var(--text-primary)] outline-none focus:border-[var(--border-focus)] shrink-0"
322
+ maxLength={4} />
323
+ <div className="flex-1 min-w-0 space-y-2">
324
+ <input value={agent.name} onChange={(e) => setAgent({ ...agent, name: e.target.value })}
325
+ className="w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-1.5 text-[14px] font-semibold text-[var(--text-primary)] outline-none focus:border-[var(--border-focus)]" />
326
+ <input value={agent.vibe} onChange={(e) => setAgent({ ...agent, vibe: e.target.value })} placeholder="Vibe / tagline..."
327
+ className="w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-1.5 text-[12px] italic text-[var(--text-secondary)] outline-none focus:border-[var(--border-focus)] placeholder-[var(--text-muted)]" />
328
+ </div>
329
+ </div>
330
+ </div>
331
+
332
+ <div>
333
+ <label className="text-[10px] uppercase tracking-wider text-[var(--text-tertiary)] block mb-1">Description</label>
334
+ <textarea value={agent.description} onChange={(e) => setAgent({ ...agent, description: e.target.value })} rows={2}
335
+ className="w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-raised)] px-3 py-2 text-[13px] text-[var(--text-secondary)] outline-none focus:border-[var(--border-focus)] resize-none leading-relaxed" />
336
+ </div>
337
+
338
+ <ToolSelector value={agent.tools} onChange={(tools) => setAgent({ ...agent, tools })} />
339
+ <McpServerSelector value={agent.mcpServers} onChange={(mcpServers) => setAgent({ ...agent, mcpServers })} />
340
+
341
+ <div>
342
+ <label className="text-[10px] uppercase tracking-wider text-[var(--text-tertiary)] block mb-1">Color</label>
343
+ <div className="flex items-center gap-2">
344
+ <input value={agent.color} onChange={(e) => setAgent({ ...agent, color: e.target.value })}
345
+ className="flex-1 rounded-md border border-[var(--border-default)] bg-[var(--bg-raised)] px-3 py-1.5 text-[12px] text-[var(--text-tertiary)] outline-none focus:border-[var(--border-focus)]" />
346
+ <span className="h-7 w-7 rounded-md border border-[var(--border-default)] shrink-0" style={{ backgroundColor: agent.color }} />
347
+ </div>
348
+ </div>
349
+
350
+ <div>
351
+ <label className="text-[10px] uppercase tracking-wider text-[var(--text-tertiary)] block mb-1">System Prompt</label>
352
+ <textarea value={agent.content} onChange={(e) => setAgent({ ...agent, content: e.target.value })} rows={10}
353
+ className="w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-raised)] px-3 py-2 text-[12px] text-[var(--text-tertiary)] outline-none focus:border-[var(--border-focus)] resize-y font-mono leading-relaxed" />
354
+ </div>
355
+
356
+ {error && <p className="text-[12px] text-red-400 bg-red-500/10 rounded-md px-3 py-2">{error}</p>}
357
+
358
+ <div className="flex justify-between">
359
+ <button onClick={() => { setStep("prompt"); setError(""); }}
360
+ className="rounded-md px-3 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors flex items-center gap-1.5">
361
+ <ChevronLeftIcon size={12} /> Back
362
+ </button>
363
+ <div className="flex gap-2">
364
+ <button onClick={handleGenerate} disabled={step === "saving"}
365
+ className="rounded-md border border-[var(--border-default)] px-4 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-strong)] transition-colors disabled:opacity-50">
366
+ Regenerate
367
+ </button>
368
+ <button onClick={handleSave} disabled={step === "saving" || !agent.name.trim()}
369
+ className="rounded-md bg-[var(--accent)] px-5 py-2 text-[12px] font-medium text-white hover:bg-[var(--accent-hover)] transition-colors disabled:opacity-50 flex items-center gap-2">
370
+ {step === "saving" ? (
371
+ <>
372
+ <svg className="animate-spin h-3.5 w-3.5" viewBox="0 0 24 24" fill="none">
373
+ <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" className="opacity-20" />
374
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
375
+ </svg>
376
+ Creating...
377
+ </>
378
+ ) : "Create Agent"}
379
+ </button>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ )}
384
+ </div>
385
+ </div>
386
+ </div>
387
+ );
388
+ }