stagent 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 (333) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +399 -0
  3. package/components.json +21 -0
  4. package/dist/cli.js +171 -0
  5. package/drizzle.config.ts +12 -0
  6. package/next.config.mjs +15 -0
  7. package/package.json +114 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/icon-512.png +0 -0
  10. package/public/icon.svg +13 -0
  11. package/public/readme/home-workspace.png +0 -0
  12. package/public/readme/inbox-approvals.png +0 -0
  13. package/public/readme/workflow-blueprints.png +0 -0
  14. package/public/stagent-s-128.png +0 -0
  15. package/public/stagent-s-64.png +0 -0
  16. package/src/app/api/blueprints/[id]/instantiate/route.ts +27 -0
  17. package/src/app/api/blueprints/[id]/route.ts +39 -0
  18. package/src/app/api/blueprints/import/route.ts +68 -0
  19. package/src/app/api/blueprints/route.ts +29 -0
  20. package/src/app/api/command-palette/recent/route.ts +31 -0
  21. package/src/app/api/data/clear/route.ts +22 -0
  22. package/src/app/api/data/seed/route.ts +22 -0
  23. package/src/app/api/documents/[id]/file/route.ts +44 -0
  24. package/src/app/api/documents/[id]/route.ts +123 -0
  25. package/src/app/api/documents/route.ts +59 -0
  26. package/src/app/api/logs/stream/route.ts +101 -0
  27. package/src/app/api/notifications/[id]/route.ts +36 -0
  28. package/src/app/api/notifications/mark-all-read/route.ts +13 -0
  29. package/src/app/api/notifications/pending-approvals/route.ts +10 -0
  30. package/src/app/api/notifications/pending-approvals/stream/route.ts +101 -0
  31. package/src/app/api/notifications/route.ts +34 -0
  32. package/src/app/api/permissions/route.ts +46 -0
  33. package/src/app/api/profiles/[id]/route.ts +79 -0
  34. package/src/app/api/profiles/[id]/test/route.ts +42 -0
  35. package/src/app/api/profiles/import/route.ts +108 -0
  36. package/src/app/api/profiles/route.ts +50 -0
  37. package/src/app/api/projects/[id]/route.ts +72 -0
  38. package/src/app/api/projects/route.ts +53 -0
  39. package/src/app/api/schedules/[id]/route.ts +185 -0
  40. package/src/app/api/schedules/route.ts +117 -0
  41. package/src/app/api/settings/budgets/route.ts +24 -0
  42. package/src/app/api/settings/openai/route.ts +24 -0
  43. package/src/app/api/settings/route.ts +21 -0
  44. package/src/app/api/settings/test/route.ts +26 -0
  45. package/src/app/api/tasks/[id]/cancel/route.ts +21 -0
  46. package/src/app/api/tasks/[id]/execute/route.ts +90 -0
  47. package/src/app/api/tasks/[id]/logs/route.ts +95 -0
  48. package/src/app/api/tasks/[id]/output/route.ts +47 -0
  49. package/src/app/api/tasks/[id]/respond/route.ts +64 -0
  50. package/src/app/api/tasks/[id]/resume/route.ts +76 -0
  51. package/src/app/api/tasks/[id]/route.ts +77 -0
  52. package/src/app/api/tasks/assist/route.ts +35 -0
  53. package/src/app/api/tasks/route.ts +82 -0
  54. package/src/app/api/uploads/[id]/route.ts +81 -0
  55. package/src/app/api/uploads/cleanup/route.ts +7 -0
  56. package/src/app/api/uploads/route.ts +66 -0
  57. package/src/app/api/workflows/[id]/execute/route.ts +82 -0
  58. package/src/app/api/workflows/[id]/route.ts +133 -0
  59. package/src/app/api/workflows/[id]/status/route.ts +54 -0
  60. package/src/app/api/workflows/[id]/steps/[stepId]/retry/route.ts +22 -0
  61. package/src/app/api/workflows/route.ts +61 -0
  62. package/src/app/apple-icon.tsx +31 -0
  63. package/src/app/costs/page.tsx +256 -0
  64. package/src/app/dashboard/page.tsx +44 -0
  65. package/src/app/documents/[id]/page.tsx +46 -0
  66. package/src/app/documents/page.tsx +45 -0
  67. package/src/app/error.tsx +26 -0
  68. package/src/app/global-error.tsx +23 -0
  69. package/src/app/globals.css +733 -0
  70. package/src/app/icon.tsx +30 -0
  71. package/src/app/inbox/loading.tsx +15 -0
  72. package/src/app/inbox/page.tsx +35 -0
  73. package/src/app/layout.tsx +78 -0
  74. package/src/app/manifest.ts +32 -0
  75. package/src/app/monitor/page.tsx +37 -0
  76. package/src/app/page.tsx +162 -0
  77. package/src/app/profiles/[id]/edit/page.tsx +39 -0
  78. package/src/app/profiles/[id]/page.tsx +33 -0
  79. package/src/app/profiles/new/page.tsx +22 -0
  80. package/src/app/profiles/page.tsx +19 -0
  81. package/src/app/projects/[id]/page.tsx +134 -0
  82. package/src/app/projects/loading.tsx +17 -0
  83. package/src/app/projects/page.tsx +32 -0
  84. package/src/app/schedules/[id]/page.tsx +47 -0
  85. package/src/app/schedules/page.tsx +18 -0
  86. package/src/app/settings/loading.tsx +24 -0
  87. package/src/app/settings/page.tsx +27 -0
  88. package/src/app/tasks/[id]/page.tsx +45 -0
  89. package/src/app/tasks/new/page.tsx +27 -0
  90. package/src/app/workflows/[id]/edit/page.tsx +66 -0
  91. package/src/app/workflows/[id]/page.tsx +37 -0
  92. package/src/app/workflows/blueprints/[id]/page.tsx +40 -0
  93. package/src/app/workflows/blueprints/new/page.tsx +20 -0
  94. package/src/app/workflows/blueprints/page.tsx +11 -0
  95. package/src/app/workflows/new/page.tsx +36 -0
  96. package/src/app/workflows/page.tsx +18 -0
  97. package/src/components/charts/donut-ring.tsx +64 -0
  98. package/src/components/charts/mini-bar.tsx +75 -0
  99. package/src/components/charts/sparkline.tsx +107 -0
  100. package/src/components/costs/cost-dashboard.tsx +877 -0
  101. package/src/components/costs/cost-filters.tsx +179 -0
  102. package/src/components/dashboard/activity-feed.tsx +95 -0
  103. package/src/components/dashboard/greeting.tsx +30 -0
  104. package/src/components/dashboard/priority-queue.tsx +79 -0
  105. package/src/components/dashboard/quick-actions.tsx +62 -0
  106. package/src/components/dashboard/recent-projects.tsx +79 -0
  107. package/src/components/dashboard/stats-cards.tsx +114 -0
  108. package/src/components/documents/document-browser.tsx +235 -0
  109. package/src/components/documents/document-detail-view.tsx +367 -0
  110. package/src/components/documents/document-grid.tsx +78 -0
  111. package/src/components/documents/document-preview.tsx +68 -0
  112. package/src/components/documents/document-table.tsx +119 -0
  113. package/src/components/documents/document-upload-dialog.tsx +153 -0
  114. package/src/components/documents/types.ts +6 -0
  115. package/src/components/documents/utils.ts +57 -0
  116. package/src/components/monitoring/connection-indicator.tsx +14 -0
  117. package/src/components/monitoring/log-entry.tsx +79 -0
  118. package/src/components/monitoring/log-filters.tsx +57 -0
  119. package/src/components/monitoring/log-stream.tsx +144 -0
  120. package/src/components/monitoring/monitor-overview-wrapper.tsx +64 -0
  121. package/src/components/monitoring/monitor-overview.tsx +119 -0
  122. package/src/components/notifications/failure-action.tsx +38 -0
  123. package/src/components/notifications/inbox-list.tsx +165 -0
  124. package/src/components/notifications/message-response.tsx +196 -0
  125. package/src/components/notifications/notification-item.tsx +250 -0
  126. package/src/components/notifications/pending-approval-host.tsx +478 -0
  127. package/src/components/notifications/permission-action.tsx +37 -0
  128. package/src/components/notifications/permission-response-actions.tsx +126 -0
  129. package/src/components/notifications/unread-badge.tsx +35 -0
  130. package/src/components/profiles/profile-browser.tsx +117 -0
  131. package/src/components/profiles/profile-card.tsx +78 -0
  132. package/src/components/profiles/profile-detail-view.tsx +564 -0
  133. package/src/components/profiles/profile-form-view.tsx +480 -0
  134. package/src/components/profiles/profile-import-dialog.tsx +113 -0
  135. package/src/components/projects/project-card.tsx +58 -0
  136. package/src/components/projects/project-create-dialog.tsx +140 -0
  137. package/src/components/projects/project-detail.tsx +68 -0
  138. package/src/components/projects/project-edit-dialog.tsx +219 -0
  139. package/src/components/projects/project-list.tsx +108 -0
  140. package/src/components/schedules/schedule-create-dialog.tsx +403 -0
  141. package/src/components/schedules/schedule-detail-view.tsx +274 -0
  142. package/src/components/schedules/schedule-list.tsx +242 -0
  143. package/src/components/schedules/schedule-status-badge.tsx +16 -0
  144. package/src/components/settings/api-key-form.tsx +141 -0
  145. package/src/components/settings/auth-config-section.tsx +141 -0
  146. package/src/components/settings/auth-method-selector.tsx +67 -0
  147. package/src/components/settings/auth-status-badge.tsx +40 -0
  148. package/src/components/settings/auth-status-dot.tsx +59 -0
  149. package/src/components/settings/budget-guardrails-section.tsx +842 -0
  150. package/src/components/settings/data-management-section.tsx +141 -0
  151. package/src/components/settings/openai-runtime-section.tsx +104 -0
  152. package/src/components/settings/permissions-section.tsx +91 -0
  153. package/src/components/shared/app-sidebar.tsx +123 -0
  154. package/src/components/shared/card-skeleton.tsx +42 -0
  155. package/src/components/shared/command-palette.tsx +250 -0
  156. package/src/components/shared/confirm-dialog.tsx +52 -0
  157. package/src/components/shared/empty-state.tsx +24 -0
  158. package/src/components/shared/error-state.tsx +32 -0
  159. package/src/components/shared/form-section-card.tsx +33 -0
  160. package/src/components/shared/section-heading.tsx +14 -0
  161. package/src/components/shared/stagent-logo.tsx +21 -0
  162. package/src/components/shared/theme-toggle.tsx +46 -0
  163. package/src/components/tasks/ai-assist-panel.tsx +210 -0
  164. package/src/components/tasks/content-preview.tsx +89 -0
  165. package/src/components/tasks/empty-board.tsx +12 -0
  166. package/src/components/tasks/file-upload.tsx +120 -0
  167. package/src/components/tasks/kanban-board.tsx +275 -0
  168. package/src/components/tasks/kanban-column.tsx +75 -0
  169. package/src/components/tasks/skeleton-board.tsx +21 -0
  170. package/src/components/tasks/task-attachments.tsx +114 -0
  171. package/src/components/tasks/task-card.tsx +101 -0
  172. package/src/components/tasks/task-create-panel.tsx +360 -0
  173. package/src/components/tasks/task-detail-view.tsx +356 -0
  174. package/src/components/ui/alert-dialog.tsx +196 -0
  175. package/src/components/ui/badge.tsx +50 -0
  176. package/src/components/ui/button.tsx +71 -0
  177. package/src/components/ui/card.tsx +92 -0
  178. package/src/components/ui/checkbox.tsx +32 -0
  179. package/src/components/ui/command.tsx +184 -0
  180. package/src/components/ui/dialog.tsx +158 -0
  181. package/src/components/ui/dropdown-menu.tsx +257 -0
  182. package/src/components/ui/form.tsx +167 -0
  183. package/src/components/ui/input.tsx +21 -0
  184. package/src/components/ui/label.tsx +24 -0
  185. package/src/components/ui/popover.tsx +89 -0
  186. package/src/components/ui/progress.tsx +31 -0
  187. package/src/components/ui/radio-group.tsx +45 -0
  188. package/src/components/ui/scroll-area.tsx +58 -0
  189. package/src/components/ui/select.tsx +190 -0
  190. package/src/components/ui/separator.tsx +28 -0
  191. package/src/components/ui/sheet.tsx +143 -0
  192. package/src/components/ui/sidebar.tsx +726 -0
  193. package/src/components/ui/skeleton.tsx +13 -0
  194. package/src/components/ui/slider.tsx +63 -0
  195. package/src/components/ui/sonner.tsx +36 -0
  196. package/src/components/ui/switch.tsx +35 -0
  197. package/src/components/ui/table.tsx +116 -0
  198. package/src/components/ui/tabs.tsx +91 -0
  199. package/src/components/ui/textarea.tsx +18 -0
  200. package/src/components/ui/tooltip.tsx +57 -0
  201. package/src/components/workflows/blueprint-editor.tsx +109 -0
  202. package/src/components/workflows/blueprint-gallery.tsx +155 -0
  203. package/src/components/workflows/blueprint-preview.tsx +240 -0
  204. package/src/components/workflows/loop-status-view.tsx +272 -0
  205. package/src/components/workflows/swarm-dashboard.tsx +185 -0
  206. package/src/components/workflows/workflow-form-view.tsx +1376 -0
  207. package/src/components/workflows/workflow-list.tsx +230 -0
  208. package/src/components/workflows/workflow-status-view.tsx +477 -0
  209. package/src/hooks/use-mobile.ts +19 -0
  210. package/src/instrumentation.ts +7 -0
  211. package/src/lib/agents/claude-agent.ts +737 -0
  212. package/src/lib/agents/execution-manager.ts +27 -0
  213. package/src/lib/agents/profiles/assignment-validation.ts +75 -0
  214. package/src/lib/agents/profiles/builtins/code-reviewer/SKILL.md +21 -0
  215. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +28 -0
  216. package/src/lib/agents/profiles/builtins/data-analyst/SKILL.md +25 -0
  217. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +27 -0
  218. package/src/lib/agents/profiles/builtins/devops-engineer/SKILL.md +34 -0
  219. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +27 -0
  220. package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +16 -0
  221. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +27 -0
  222. package/src/lib/agents/profiles/builtins/general/SKILL.md +13 -0
  223. package/src/lib/agents/profiles/builtins/general/profile.yaml +18 -0
  224. package/src/lib/agents/profiles/builtins/health-fitness-coach/SKILL.md +34 -0
  225. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +26 -0
  226. package/src/lib/agents/profiles/builtins/learning-coach/SKILL.md +35 -0
  227. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +26 -0
  228. package/src/lib/agents/profiles/builtins/project-manager/SKILL.md +26 -0
  229. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +26 -0
  230. package/src/lib/agents/profiles/builtins/researcher/SKILL.md +15 -0
  231. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +27 -0
  232. package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +34 -0
  233. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +26 -0
  234. package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +31 -0
  235. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +29 -0
  236. package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +23 -0
  237. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +26 -0
  238. package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +24 -0
  239. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +26 -0
  240. package/src/lib/agents/profiles/compatibility.ts +109 -0
  241. package/src/lib/agents/profiles/registry.ts +293 -0
  242. package/src/lib/agents/profiles/test-runner.ts +18 -0
  243. package/src/lib/agents/profiles/test-types.ts +20 -0
  244. package/src/lib/agents/profiles/types.ts +43 -0
  245. package/src/lib/agents/router.ts +56 -0
  246. package/src/lib/agents/runtime/catalog.ts +85 -0
  247. package/src/lib/agents/runtime/claude-sdk.ts +12 -0
  248. package/src/lib/agents/runtime/claude.ts +370 -0
  249. package/src/lib/agents/runtime/codex-app-server-client.ts +289 -0
  250. package/src/lib/agents/runtime/index.ts +167 -0
  251. package/src/lib/agents/runtime/openai-codex.ts +1089 -0
  252. package/src/lib/agents/runtime/task-assist-types.ts +8 -0
  253. package/src/lib/agents/runtime/types.ts +30 -0
  254. package/src/lib/constants/settings.ts +13 -0
  255. package/src/lib/constants/status-colors.ts +44 -0
  256. package/src/lib/constants/task-status.ts +49 -0
  257. package/src/lib/data/clear.ts +63 -0
  258. package/src/lib/data/seed-data/documents.ts +715 -0
  259. package/src/lib/data/seed-data/logs.ts +195 -0
  260. package/src/lib/data/seed-data/notifications.ts +141 -0
  261. package/src/lib/data/seed-data/profiles.ts +175 -0
  262. package/src/lib/data/seed-data/projects.ts +61 -0
  263. package/src/lib/data/seed-data/schedules.ts +108 -0
  264. package/src/lib/data/seed-data/tasks.ts +341 -0
  265. package/src/lib/data/seed-data/usage-ledger.ts +130 -0
  266. package/src/lib/data/seed-data/workflows.ts +213 -0
  267. package/src/lib/data/seed.ts +129 -0
  268. package/src/lib/db/index.ts +221 -0
  269. package/src/lib/db/migrations/0000_aromatic_gargoyle.sql +59 -0
  270. package/src/lib/db/migrations/0001_first_iron_patriot.sql +6 -0
  271. package/src/lib/db/migrations/0002_add_resume_count.sql +1 -0
  272. package/src/lib/db/migrations/0003_add_settings.sql +5 -0
  273. package/src/lib/db/migrations/0004_add_documents.sql +20 -0
  274. package/src/lib/db/migrations/0005_add_document_preprocessing.sql +4 -0
  275. package/src/lib/db/migrations/0006_add_agent_profile.sql +2 -0
  276. package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +30 -0
  277. package/src/lib/db/migrations/0008_add_document_version.sql +1 -0
  278. package/src/lib/db/migrations/meta/0000_snapshot.json +416 -0
  279. package/src/lib/db/migrations/meta/0001_snapshot.json +461 -0
  280. package/src/lib/db/migrations/meta/0002_snapshot.json +469 -0
  281. package/src/lib/db/migrations/meta/_journal.json +27 -0
  282. package/src/lib/db/schema.ts +227 -0
  283. package/src/lib/documents/cleanup.ts +50 -0
  284. package/src/lib/documents/context-builder.ts +75 -0
  285. package/src/lib/documents/output-scanner.ts +166 -0
  286. package/src/lib/documents/processor.ts +120 -0
  287. package/src/lib/documents/processors/image.ts +21 -0
  288. package/src/lib/documents/processors/office.ts +36 -0
  289. package/src/lib/documents/processors/pdf.ts +12 -0
  290. package/src/lib/documents/processors/spreadsheet.ts +18 -0
  291. package/src/lib/documents/processors/text.ts +8 -0
  292. package/src/lib/documents/registry.ts +25 -0
  293. package/src/lib/notifications/actionable.ts +108 -0
  294. package/src/lib/notifications/permissions.ts +169 -0
  295. package/src/lib/queries/chart-data.ts +184 -0
  296. package/src/lib/schedules/interval-parser.ts +110 -0
  297. package/src/lib/schedules/scheduler.ts +220 -0
  298. package/src/lib/settings/auth.ts +98 -0
  299. package/src/lib/settings/budget-guardrails.ts +590 -0
  300. package/src/lib/settings/helpers.ts +23 -0
  301. package/src/lib/settings/openai-auth.ts +80 -0
  302. package/src/lib/settings/permissions.ts +102 -0
  303. package/src/lib/usage/ledger.ts +489 -0
  304. package/src/lib/usage/pricing.ts +68 -0
  305. package/src/lib/utils/crypto.ts +90 -0
  306. package/src/lib/utils/format-timestamp.ts +46 -0
  307. package/src/lib/utils/session-cleanup.ts +26 -0
  308. package/src/lib/utils/stagent-paths.ts +18 -0
  309. package/src/lib/utils.ts +6 -0
  310. package/src/lib/validators/blueprint.ts +43 -0
  311. package/src/lib/validators/profile.ts +64 -0
  312. package/src/lib/validators/project.ts +17 -0
  313. package/src/lib/validators/settings.ts +57 -0
  314. package/src/lib/validators/task.ts +30 -0
  315. package/src/lib/workflows/blueprints/builtins/code-review-pipeline.yaml +72 -0
  316. package/src/lib/workflows/blueprints/builtins/documentation-generation.yaml +62 -0
  317. package/src/lib/workflows/blueprints/builtins/investment-research.yaml +81 -0
  318. package/src/lib/workflows/blueprints/builtins/meal-planning.yaml +73 -0
  319. package/src/lib/workflows/blueprints/builtins/product-research.yaml +72 -0
  320. package/src/lib/workflows/blueprints/builtins/research-report.yaml +77 -0
  321. package/src/lib/workflows/blueprints/builtins/sprint-planning.yaml +77 -0
  322. package/src/lib/workflows/blueprints/builtins/travel-planning.yaml +80 -0
  323. package/src/lib/workflows/blueprints/instantiator.ts +131 -0
  324. package/src/lib/workflows/blueprints/registry.ts +128 -0
  325. package/src/lib/workflows/blueprints/template.ts +58 -0
  326. package/src/lib/workflows/blueprints/types.ts +38 -0
  327. package/src/lib/workflows/definition-validation.ts +121 -0
  328. package/src/lib/workflows/engine.ts +1113 -0
  329. package/src/lib/workflows/loop-executor.ts +270 -0
  330. package/src/lib/workflows/parallel.ts +55 -0
  331. package/src/lib/workflows/swarm.ts +97 -0
  332. package/src/lib/workflows/types.ts +112 -0
  333. package/tsconfig.json +41 -0
@@ -0,0 +1,480 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Input } from "@/components/ui/input";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Label } from "@/components/ui/label";
8
+ import { Textarea } from "@/components/ui/textarea";
9
+ import { Badge } from "@/components/ui/badge";
10
+ import { Slider } from "@/components/ui/slider";
11
+ import { Switch } from "@/components/ui/switch";
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from "@/components/ui/select";
19
+ import { Skeleton } from "@/components/ui/skeleton";
20
+ import {
21
+ User,
22
+ Tag,
23
+ SlidersHorizontal,
24
+ Wrench,
25
+ FileCode,
26
+ Cpu,
27
+ } from "lucide-react";
28
+ import { toast } from "sonner";
29
+ import { FormSectionCard } from "@/components/shared/form-section-card";
30
+ import { listRuntimeCatalog } from "@/lib/agents/runtime/catalog";
31
+ import type { AgentProfile } from "@/lib/agents/profiles/types";
32
+
33
+ interface ProfileFormViewProps {
34
+ profileId?: string;
35
+ duplicate?: boolean;
36
+ }
37
+
38
+ function toSlug(name: string): string {
39
+ return name
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, "-")
42
+ .replace(/^-+|-+$/g, "");
43
+ }
44
+
45
+ function parseCommaSeparated(value: string): string[] {
46
+ return value
47
+ .split(",")
48
+ .map((t) => t.trim())
49
+ .filter(Boolean);
50
+ }
51
+
52
+ export function ProfileFormView({
53
+ profileId,
54
+ duplicate = false,
55
+ }: ProfileFormViewProps) {
56
+ const runtimeOptions = listRuntimeCatalog();
57
+ const router = useRouter();
58
+ const isEdit = !!profileId && !duplicate;
59
+
60
+ const [fetching, setFetching] = useState(!!profileId);
61
+ const [name, setName] = useState("");
62
+ const [id, setId] = useState("");
63
+ const [domain, setDomain] = useState<"work" | "personal">("work");
64
+ const [version, setVersion] = useState("1.0.0");
65
+ const [author, setAuthor] = useState("");
66
+ const [tags, setTags] = useState("");
67
+ const [skillMd, setSkillMd] = useState("");
68
+ const [supportedRuntimes, setSupportedRuntimes] = useState<string[]>([
69
+ "claude-code",
70
+ ]);
71
+ const [codexInstructions, setCodexInstructions] = useState("");
72
+ const [allowedTools, setAllowedTools] = useState("");
73
+ const [temperature, setTemperature] = useState(0.5);
74
+ const [maxTurns, setMaxTurns] = useState(30);
75
+ const [outputFormat, setOutputFormat] = useState("");
76
+ const [submitting, setSubmitting] = useState(false);
77
+
78
+ // Fetch existing profile for edit/duplicate
79
+ useEffect(() => {
80
+ if (!profileId) return;
81
+
82
+ fetch(`/api/profiles/${profileId}`)
83
+ .then((r) => r.json())
84
+ .then((profile: AgentProfile) => {
85
+ setName(duplicate ? `${profile.name} (Copy)` : profile.name);
86
+ setId(duplicate ? `${profile.id}-copy` : profile.id);
87
+ setDomain(profile.domain as "work" | "personal");
88
+ setVersion(profile.version ?? "1.0.0");
89
+ setAuthor(profile.author ?? "");
90
+ setTags(profile.tags.join(", "));
91
+ setSkillMd(profile.skillMd ?? "");
92
+ setSupportedRuntimes(profile.supportedRuntimes ?? ["claude-code"]);
93
+ setCodexInstructions(
94
+ profile.runtimeOverrides?.["openai-codex-app-server"]?.instructions ?? ""
95
+ );
96
+ setAllowedTools(profile.allowedTools?.join(", ") ?? "");
97
+ setTemperature(profile.temperature ?? 0.5);
98
+ setMaxTurns(profile.maxTurns ?? 30);
99
+ setOutputFormat(profile.outputFormat ?? "");
100
+ })
101
+ .catch(() => {
102
+ toast.error("Failed to load profile");
103
+ })
104
+ .finally(() => setFetching(false));
105
+ }, [profileId, duplicate]);
106
+
107
+ // Auto-slug from name (only for create/duplicate)
108
+ const handleNameChange = useCallback(
109
+ (value: string) => {
110
+ setName(value);
111
+ if (!isEdit) {
112
+ setId(toSlug(value));
113
+ }
114
+ },
115
+ [isEdit]
116
+ );
117
+
118
+ const handleSubmit = async () => {
119
+ if (!name.trim() || !id.trim()) {
120
+ toast.error("Name and ID are required");
121
+ return;
122
+ }
123
+ if (supportedRuntimes.length === 0) {
124
+ toast.error("Select at least one supported runtime");
125
+ return;
126
+ }
127
+
128
+ setSubmitting(true);
129
+
130
+ const payload = {
131
+ id,
132
+ name: name.trim(),
133
+ domain,
134
+ version: version.trim() || "1.0.0",
135
+ author: author.trim() || undefined,
136
+ tags: parseCommaSeparated(tags),
137
+ skillMd: skillMd.trim(),
138
+ supportedRuntimes,
139
+ runtimeOverrides: supportedRuntimes.includes("openai-codex-app-server") &&
140
+ codexInstructions.trim()
141
+ ? {
142
+ "openai-codex-app-server": {
143
+ instructions: codexInstructions.trim(),
144
+ },
145
+ }
146
+ : undefined,
147
+ allowedTools: parseCommaSeparated(allowedTools),
148
+ temperature,
149
+ maxTurns,
150
+ outputFormat: outputFormat.trim() || undefined,
151
+ };
152
+
153
+ try {
154
+ const url = isEdit ? `/api/profiles/${profileId}` : "/api/profiles";
155
+ const method = isEdit ? "PUT" : "POST";
156
+ const res = await fetch(url, {
157
+ method,
158
+ headers: { "Content-Type": "application/json" },
159
+ body: JSON.stringify(payload),
160
+ });
161
+
162
+ if (!res.ok) {
163
+ const data = await res.json().catch(() => ({}));
164
+ throw new Error(data.error ?? "Failed to save profile");
165
+ }
166
+
167
+ toast.success(isEdit ? "Profile updated" : "Profile created");
168
+
169
+ if (isEdit) {
170
+ router.push(`/profiles/${profileId}`);
171
+ } else {
172
+ router.push(`/profiles/${id}`);
173
+ }
174
+ } catch (err) {
175
+ toast.error(
176
+ err instanceof Error ? err.message : "Failed to save profile"
177
+ );
178
+ } finally {
179
+ setSubmitting(false);
180
+ }
181
+ };
182
+
183
+ if (fetching) {
184
+ return (
185
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
186
+ <Skeleton className="h-48 rounded-lg" />
187
+ <Skeleton className="h-48 rounded-lg" />
188
+ <Skeleton className="h-48 rounded-lg" />
189
+ <Skeleton className="h-64 rounded-lg md:col-span-2" />
190
+ </div>
191
+ );
192
+ }
193
+
194
+ const title = isEdit
195
+ ? "Edit Profile"
196
+ : duplicate
197
+ ? "Duplicate Profile"
198
+ : "Create Profile";
199
+
200
+ const parsedTags = parseCommaSeparated(tags);
201
+ const parsedTools = parseCommaSeparated(allowedTools);
202
+ const lineCount = skillMd.split("\n").length;
203
+
204
+ function toggleRuntime(runtimeId: string, checked: boolean) {
205
+ setSupportedRuntimes((current) => {
206
+ if (checked) {
207
+ return current.includes(runtimeId) ? current : [...current, runtimeId];
208
+ }
209
+
210
+ return current.filter((candidate) => candidate !== runtimeId);
211
+ });
212
+ }
213
+
214
+ return (
215
+ <div className="space-y-4">
216
+ <h2 className="text-xl font-semibold">{title}</h2>
217
+
218
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
219
+ {/* Identity */}
220
+ <FormSectionCard icon={User} title="Identity">
221
+ <div className="space-y-3">
222
+ <div className="space-y-1.5">
223
+ <Label htmlFor="profile-name">Name</Label>
224
+ <Input
225
+ id="profile-name"
226
+ value={name}
227
+ onChange={(e) => handleNameChange(e.target.value)}
228
+ placeholder="My Custom Agent"
229
+ />
230
+ <p className="text-xs text-muted-foreground">Display name for this agent</p>
231
+ </div>
232
+ <div className="space-y-1.5">
233
+ <Label htmlFor="profile-id">ID</Label>
234
+ <Input
235
+ id="profile-id"
236
+ value={id}
237
+ onChange={(e) => setId(e.target.value)}
238
+ placeholder="my-custom-agent"
239
+ disabled={isEdit}
240
+ />
241
+ <p className="text-xs text-muted-foreground">Auto-generated slug</p>
242
+ </div>
243
+ <div className="space-y-1.5">
244
+ <Label htmlFor="profile-domain">Domain</Label>
245
+ <Select
246
+ value={domain}
247
+ onValueChange={(v) => setDomain(v as "work" | "personal")}
248
+ >
249
+ <SelectTrigger id="profile-domain">
250
+ <SelectValue />
251
+ </SelectTrigger>
252
+ <SelectContent>
253
+ <SelectItem value="work">Work</SelectItem>
254
+ <SelectItem value="personal">Personal</SelectItem>
255
+ </SelectContent>
256
+ </Select>
257
+ <p className="text-xs text-muted-foreground">Sets default tool permissions</p>
258
+ </div>
259
+ </div>
260
+ </FormSectionCard>
261
+
262
+ {/* Metadata */}
263
+ <FormSectionCard icon={Tag} title="Metadata">
264
+ <div className="space-y-3">
265
+ <div className="space-y-1.5">
266
+ <Label htmlFor="profile-version">Version</Label>
267
+ <Input
268
+ id="profile-version"
269
+ value={version}
270
+ onChange={(e) => setVersion(e.target.value)}
271
+ placeholder="1.0.0"
272
+ />
273
+ <p className="text-xs text-muted-foreground">Semantic version number</p>
274
+ </div>
275
+ <div className="space-y-1.5">
276
+ <Label htmlFor="profile-author">Author</Label>
277
+ <Input
278
+ id="profile-author"
279
+ value={author}
280
+ onChange={(e) => setAuthor(e.target.value)}
281
+ placeholder="Optional"
282
+ />
283
+ <p className="text-xs text-muted-foreground">Profile creator attribution</p>
284
+ </div>
285
+ <div className="space-y-1.5">
286
+ <Label htmlFor="profile-tags">Tags</Label>
287
+ <Input
288
+ id="profile-tags"
289
+ value={tags}
290
+ onChange={(e) => setTags(e.target.value)}
291
+ placeholder="coding, review, analysis"
292
+ />
293
+ <p className="text-xs text-muted-foreground">Comma-separated categories</p>
294
+ {parsedTags.length > 0 && (
295
+ <div className="flex flex-wrap gap-1 pt-1">
296
+ {parsedTags.map((tag, i) => (
297
+ <Badge key={i} variant="outline" className="text-xs">
298
+ {tag}
299
+ </Badge>
300
+ ))}
301
+ </div>
302
+ )}
303
+ </div>
304
+ </div>
305
+ </FormSectionCard>
306
+
307
+ {/* Model Tuning */}
308
+ <FormSectionCard icon={SlidersHorizontal} title="Model Tuning">
309
+ <div className="space-y-4">
310
+ <div className="space-y-2">
311
+ <div className="flex items-center justify-between">
312
+ <Label htmlFor="profile-temp">Temperature</Label>
313
+ <Badge variant="secondary" className="tabular-nums text-xs">
314
+ {temperature.toFixed(2)}
315
+ </Badge>
316
+ </div>
317
+ <div className="slider-temperature">
318
+ <Slider
319
+ id="profile-temp"
320
+ min={0}
321
+ max={1}
322
+ step={0.05}
323
+ value={[temperature]}
324
+ onValueChange={([v]) => setTemperature(v)}
325
+ />
326
+ </div>
327
+ <p className="text-xs text-muted-foreground">Lower = deterministic, higher = creative</p>
328
+ </div>
329
+ <div className="space-y-2">
330
+ <div className="flex items-center justify-between">
331
+ <Label htmlFor="profile-turns">Max Turns</Label>
332
+ <Badge variant="secondary" className="tabular-nums text-xs">
333
+ {maxTurns}
334
+ </Badge>
335
+ </div>
336
+ <Slider
337
+ id="profile-turns"
338
+ min={1}
339
+ max={100}
340
+ step={1}
341
+ value={[maxTurns]}
342
+ onValueChange={([v]) => setMaxTurns(v)}
343
+ />
344
+ <p className="text-xs text-muted-foreground">Max agent-tool cycles per execution</p>
345
+ </div>
346
+ <div className="space-y-1.5">
347
+ <Label htmlFor="profile-format">Output Format</Label>
348
+ <Input
349
+ id="profile-format"
350
+ value={outputFormat}
351
+ onChange={(e) => setOutputFormat(e.target.value)}
352
+ placeholder="e.g., markdown, json"
353
+ />
354
+ <p className="text-xs text-muted-foreground">Preferred response format</p>
355
+ </div>
356
+ </div>
357
+ </FormSectionCard>
358
+
359
+ {/* Runtime Coverage */}
360
+ <FormSectionCard icon={Cpu} title="Runtime Coverage">
361
+ <div className="space-y-3">
362
+ {runtimeOptions.map((runtime) => (
363
+ <div
364
+ key={runtime.id}
365
+ className="surface-card-muted flex items-center justify-between rounded-lg border border-border/60 px-3 py-2"
366
+ >
367
+ <div>
368
+ <p className="text-sm font-medium">{runtime.label}</p>
369
+ <p className="text-xs text-muted-foreground">
370
+ {runtime.id === "claude-code"
371
+ ? "Shared SKILL.md instructions apply here by default"
372
+ : "Enable when this profile should be selectable on Codex"}
373
+ </p>
374
+ </div>
375
+ <Switch
376
+ checked={supportedRuntimes.includes(runtime.id)}
377
+ onCheckedChange={(checked) => toggleRuntime(runtime.id, checked)}
378
+ />
379
+ </div>
380
+ ))}
381
+ <p className="text-xs text-muted-foreground">
382
+ Runtime support is enforced across tasks, schedules, workflow steps, and profile tests.
383
+ </p>
384
+ </div>
385
+ </FormSectionCard>
386
+
387
+ {/* Tools */}
388
+ <FormSectionCard icon={Wrench} title="Tools" className="lg:col-span-1 md:col-span-2">
389
+ <div className="space-y-1.5">
390
+ <Label htmlFor="profile-tools">Allowed Tools</Label>
391
+ <Input
392
+ id="profile-tools"
393
+ value={allowedTools}
394
+ onChange={(e) => setAllowedTools(e.target.value)}
395
+ placeholder="Read, Edit, Bash, Grep"
396
+ />
397
+ <p className="text-xs text-muted-foreground">Leave empty to allow all tools</p>
398
+ {parsedTools.length > 0 && (
399
+ <div className="flex flex-wrap gap-1 pt-1">
400
+ {parsedTools.map((tool, i) => (
401
+ <Badge key={i} variant="outline" className="text-xs">
402
+ {tool}
403
+ </Badge>
404
+ ))}
405
+ </div>
406
+ )}
407
+ </div>
408
+ </FormSectionCard>
409
+
410
+ {/* Codex Override */}
411
+ {supportedRuntimes.includes("openai-codex-app-server") && (
412
+ <FormSectionCard
413
+ icon={Cpu}
414
+ title="Codex Override"
415
+ className="md:col-span-2 lg:col-span-1"
416
+ >
417
+ <div className="space-y-1.5">
418
+ <Label htmlFor="profile-codex-instructions">
419
+ OpenAI Codex Instructions
420
+ </Label>
421
+ <Textarea
422
+ id="profile-codex-instructions"
423
+ value={codexInstructions}
424
+ onChange={(e) => setCodexInstructions(e.target.value)}
425
+ placeholder="Optional runtime-specific override. Leave empty to reuse SKILL.md."
426
+ rows={8}
427
+ />
428
+ <p className="text-xs text-muted-foreground">
429
+ Optional provider-specific instructions for Codex. Shared tools and policies still apply unless overridden in profile metadata.
430
+ </p>
431
+ </div>
432
+ </FormSectionCard>
433
+ )}
434
+
435
+ {/* SKILL.md */}
436
+ <FormSectionCard
437
+ icon={FileCode}
438
+ title="SKILL.md"
439
+ className="md:col-span-2 lg:col-span-2 lg:row-span-2"
440
+ >
441
+ <div className="space-y-1.5">
442
+ <div className="flex items-center justify-between">
443
+ <Label htmlFor="profile-skillmd">Instructions</Label>
444
+ <Badge variant="secondary" className="text-xs">
445
+ {lineCount} {lineCount === 1 ? "line" : "lines"}
446
+ </Badge>
447
+ </div>
448
+ <Textarea
449
+ id="profile-skillmd"
450
+ value={skillMd}
451
+ onChange={(e) => setSkillMd(e.target.value)}
452
+ placeholder="Behavioral instructions for the agent..."
453
+ className="font-mono"
454
+ rows={12}
455
+ />
456
+ <p className="text-xs text-muted-foreground">Markdown instructions defining agent behavior</p>
457
+ </div>
458
+ </FormSectionCard>
459
+
460
+ {/* Actions */}
461
+ <div className="col-span-full flex items-center gap-3 pt-2">
462
+ <Button onClick={handleSubmit} disabled={submitting}>
463
+ {submitting
464
+ ? "Saving..."
465
+ : isEdit
466
+ ? "Update Profile"
467
+ : "Create Profile"}
468
+ </Button>
469
+ <Button
470
+ variant="outline"
471
+ onClick={() => router.back()}
472
+ disabled={submitting}
473
+ >
474
+ Cancel
475
+ </Button>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ );
480
+ }
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Download, Loader2, Github } from "lucide-react";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+ import { Label } from "@/components/ui/label";
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from "@/components/ui/dialog";
16
+
17
+ interface ProfileImportDialogProps {
18
+ open: boolean;
19
+ onOpenChange: (open: boolean) => void;
20
+ onImported: () => void;
21
+ }
22
+
23
+ export function ProfileImportDialog({
24
+ open,
25
+ onOpenChange,
26
+ onImported,
27
+ }: ProfileImportDialogProps) {
28
+ const [url, setUrl] = useState("");
29
+ const [loading, setLoading] = useState(false);
30
+ const [error, setError] = useState<string | null>(null);
31
+
32
+ async function handleImport() {
33
+ if (!url.trim()) return;
34
+
35
+ setLoading(true);
36
+ setError(null);
37
+
38
+ try {
39
+ const res = await fetch("/api/profiles/import", {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify({ url: url.trim() }),
43
+ });
44
+
45
+ const data = await res.json();
46
+
47
+ if (!res.ok) {
48
+ setError(data.error || "Import failed");
49
+ return;
50
+ }
51
+
52
+ setUrl("");
53
+ onOpenChange(false);
54
+ onImported();
55
+ } catch {
56
+ setError("Failed to connect to import API");
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ }
61
+
62
+ return (
63
+ <Dialog open={open} onOpenChange={onOpenChange}>
64
+ <DialogContent className="sm:max-w-md">
65
+ <DialogHeader>
66
+ <DialogTitle>Import Profile from GitHub</DialogTitle>
67
+ <DialogDescription>
68
+ Paste a GitHub URL to a profile directory containing profile.yaml
69
+ and SKILL.md files.
70
+ </DialogDescription>
71
+ </DialogHeader>
72
+ <div className="space-y-4 py-4">
73
+ <div className="space-y-2">
74
+ <Label htmlFor="import-url" className="flex items-center gap-1.5">
75
+ <Github className="h-3.5 w-3.5 text-muted-foreground" />
76
+ GitHub URL
77
+ </Label>
78
+ <Input
79
+ id="import-url"
80
+ placeholder="https://github.com/user/repo/tree/main/.claude/skills/my-profile"
81
+ value={url}
82
+ onChange={(e) => setUrl(e.target.value)}
83
+ onKeyDown={(e) => {
84
+ if (e.key === "Enter" && !loading) handleImport();
85
+ }}
86
+ />
87
+ <p className="text-xs text-muted-foreground">Directory must contain profile.yaml and SKILL.md</p>
88
+ </div>
89
+ {error && (
90
+ <p className="text-sm text-destructive">{error}</p>
91
+ )}
92
+ </div>
93
+ <DialogFooter>
94
+ <Button
95
+ variant="outline"
96
+ onClick={() => onOpenChange(false)}
97
+ disabled={loading}
98
+ >
99
+ Cancel
100
+ </Button>
101
+ <Button onClick={handleImport} disabled={loading || !url.trim()}>
102
+ {loading ? (
103
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
104
+ ) : (
105
+ <Download className="mr-2 h-4 w-4" />
106
+ )}
107
+ Import
108
+ </Button>
109
+ </DialogFooter>
110
+ </DialogContent>
111
+ </Dialog>
112
+ );
113
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { FolderKanban, FolderOpen } from "lucide-react";
6
+ import { projectStatusVariant } from "@/lib/constants/status-colors";
7
+
8
+ interface ProjectCardProps {
9
+ project: {
10
+ id: string;
11
+ name: string;
12
+ description: string | null;
13
+ workingDirectory: string | null;
14
+ status: string;
15
+ taskCount: number;
16
+ };
17
+ onEdit: (id: string, trigger: HTMLElement | null) => void;
18
+ }
19
+
20
+ export function ProjectCard({ project, onEdit }: ProjectCardProps) {
21
+ return (
22
+ <Card
23
+ tabIndex={0}
24
+ className="surface-card cursor-pointer transition-colors hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
25
+ onKeyDown={(e) => {
26
+ if (e.key === "Enter" || e.key === " ") {
27
+ e.preventDefault();
28
+ onEdit(project.id, e.currentTarget);
29
+ }
30
+ }}
31
+ onClick={(e) => onEdit(project.id, e.currentTarget)}
32
+ >
33
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
34
+ <CardTitle className="text-base font-medium">{project.name}</CardTitle>
35
+ <Badge variant={projectStatusVariant[project.status] ?? "secondary"}>
36
+ {project.status}
37
+ </Badge>
38
+ </CardHeader>
39
+ <CardContent>
40
+ {project.description && (
41
+ <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
42
+ {project.description}
43
+ </p>
44
+ )}
45
+ <div className="flex items-center gap-1 text-xs text-muted-foreground">
46
+ <FolderKanban className="h-3 w-3" />
47
+ <span>{project.taskCount} tasks</span>
48
+ </div>
49
+ {project.workingDirectory && (
50
+ <div className="flex items-center gap-1 text-xs text-muted-foreground mt-1">
51
+ <FolderOpen className="h-3 w-3" />
52
+ <span className="truncate">{project.workingDirectory}</span>
53
+ </div>
54
+ )}
55
+ </CardContent>
56
+ </Card>
57
+ );
58
+ }