cognova 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 (205) hide show
  1. package/.env.example +58 -0
  2. package/Claude/CLAUDE.md +92 -0
  3. package/Claude/hooks/lib/__init__.py +1 -0
  4. package/Claude/hooks/lib/hook_client.py +207 -0
  5. package/Claude/hooks/log-event.py +97 -0
  6. package/Claude/hooks/pre-compact.py +46 -0
  7. package/Claude/hooks/session-end.py +26 -0
  8. package/Claude/hooks/session-start.py +35 -0
  9. package/Claude/hooks/stop-extract.py +40 -0
  10. package/Claude/rules/frontmatter.md +54 -0
  11. package/Claude/rules/markdown.md +43 -0
  12. package/Claude/rules/note-organization.md +33 -0
  13. package/Claude/settings.json +54 -0
  14. package/Claude/skills/README.md +136 -0
  15. package/Claude/skills/_lib/__init__.py +1 -0
  16. package/Claude/skills/_lib/api.py +164 -0
  17. package/Claude/skills/_lib/output.py +95 -0
  18. package/Claude/skills/environment/SKILL.md +73 -0
  19. package/Claude/skills/environment/environment.py +239 -0
  20. package/Claude/skills/memory/SKILL.md +153 -0
  21. package/Claude/skills/memory/memory.py +270 -0
  22. package/Claude/skills/project/SKILL.md +105 -0
  23. package/Claude/skills/project/project.py +203 -0
  24. package/Claude/skills/skill-creator/SKILL.md +261 -0
  25. package/Claude/skills/task/SKILL.md +135 -0
  26. package/Claude/skills/task/task.py +310 -0
  27. package/LICENSE +21 -0
  28. package/README.md +176 -0
  29. package/app/app.config.ts +8 -0
  30. package/app/app.vue +39 -0
  31. package/app/assets/css/main.css +10 -0
  32. package/app/components/AppLogo.vue +40 -0
  33. package/app/components/AssistantPanel.client.vue +518 -0
  34. package/app/components/ConfirmModal.vue +84 -0
  35. package/app/components/TemplateMenu.vue +49 -0
  36. package/app/components/agents/AgentActivityChart.client.vue +105 -0
  37. package/app/components/agents/AgentActivityChart.server.vue +25 -0
  38. package/app/components/agents/AgentForm.vue +304 -0
  39. package/app/components/agents/AgentRunModal.vue +154 -0
  40. package/app/components/agents/AgentStatsCards.vue +98 -0
  41. package/app/components/chat/ChatInput.vue +85 -0
  42. package/app/components/chat/ConversationList.vue +78 -0
  43. package/app/components/chat/MessageBubble.vue +81 -0
  44. package/app/components/chat/StreamingMessage.vue +36 -0
  45. package/app/components/chat/ToolCallBlock.vue +77 -0
  46. package/app/components/editor/CodeEditor.client.vue +212 -0
  47. package/app/components/editor/CodeEditorFallback.vue +12 -0
  48. package/app/components/editor/DocumentEditor.vue +326 -0
  49. package/app/components/editor/DocumentMetadata.vue +140 -0
  50. package/app/components/editor/MarkdownEditor.vue +146 -0
  51. package/app/components/files/FileTree.vue +436 -0
  52. package/app/components/hooks/HookActivityChart.client.vue +117 -0
  53. package/app/components/hooks/HookActivityChart.server.vue +25 -0
  54. package/app/components/hooks/HookStatsCards.vue +63 -0
  55. package/app/components/hooks/RecentEventsTable.vue +123 -0
  56. package/app/components/hooks/ToolBreakdownTable.vue +72 -0
  57. package/app/components/search/DashboardSearch.vue +122 -0
  58. package/app/components/tasks/ProjectSelect.vue +35 -0
  59. package/app/components/tasks/TaskCard.vue +182 -0
  60. package/app/components/tasks/TaskDetail.vue +160 -0
  61. package/app/components/tasks/TaskForm.vue +280 -0
  62. package/app/components/tasks/TaskList.vue +69 -0
  63. package/app/components/view/ViewToc.vue +85 -0
  64. package/app/composables/useAgents.ts +153 -0
  65. package/app/composables/useAuth.ts +73 -0
  66. package/app/composables/useChat.ts +298 -0
  67. package/app/composables/useDocument.ts +141 -0
  68. package/app/composables/useEditor.ts +100 -0
  69. package/app/composables/useFileTree.ts +220 -0
  70. package/app/composables/useHookEvents.ts +68 -0
  71. package/app/composables/useMemories.ts +83 -0
  72. package/app/composables/useNotificationBus.ts +154 -0
  73. package/app/composables/usePreferences.ts +131 -0
  74. package/app/composables/useProjects.ts +97 -0
  75. package/app/composables/useSearch.ts +52 -0
  76. package/app/composables/useTasks.ts +201 -0
  77. package/app/composables/useTerminal.ts +135 -0
  78. package/app/layouts/auth.vue +20 -0
  79. package/app/layouts/dashboard.vue +186 -0
  80. package/app/layouts/view.vue +60 -0
  81. package/app/middleware/auth.ts +9 -0
  82. package/app/pages/agents/[id].vue +602 -0
  83. package/app/pages/agents/index.vue +412 -0
  84. package/app/pages/chat.vue +146 -0
  85. package/app/pages/dashboard.vue +80 -0
  86. package/app/pages/docs.vue +131 -0
  87. package/app/pages/hooks.vue +163 -0
  88. package/app/pages/index.vue +249 -0
  89. package/app/pages/login.vue +60 -0
  90. package/app/pages/memories.vue +282 -0
  91. package/app/pages/settings.vue +625 -0
  92. package/app/pages/tasks.vue +312 -0
  93. package/app/pages/view/[uuid].vue +376 -0
  94. package/dist/cli/index.js +2711 -0
  95. package/drizzle.config.ts +10 -0
  96. package/nuxt.config.ts +98 -0
  97. package/package.json +107 -0
  98. package/server/api/agents/[id]/cancel.post.ts +27 -0
  99. package/server/api/agents/[id]/run.post.ts +34 -0
  100. package/server/api/agents/[id]/runs.get.ts +45 -0
  101. package/server/api/agents/[id]/stats.get.ts +94 -0
  102. package/server/api/agents/[id].delete.ts +29 -0
  103. package/server/api/agents/[id].get.ts +25 -0
  104. package/server/api/agents/[id].patch.ts +55 -0
  105. package/server/api/agents/index.get.ts +15 -0
  106. package/server/api/agents/index.post.ts +48 -0
  107. package/server/api/agents/stats.get.ts +86 -0
  108. package/server/api/auth/[...all].ts +5 -0
  109. package/server/api/conversations/[id].delete.ts +16 -0
  110. package/server/api/conversations/[id].get.ts +34 -0
  111. package/server/api/conversations/index.get.ts +17 -0
  112. package/server/api/documents/[id]/index.delete.ts +47 -0
  113. package/server/api/documents/[id]/index.put.ts +102 -0
  114. package/server/api/documents/[id]/public.get.ts +60 -0
  115. package/server/api/documents/[id]/restore.post.ts +65 -0
  116. package/server/api/documents/by-path.post.ts +168 -0
  117. package/server/api/documents/index.get.ts +48 -0
  118. package/server/api/fs/delete.post.ts +41 -0
  119. package/server/api/fs/list.get.ts +99 -0
  120. package/server/api/fs/mkdir.post.ts +44 -0
  121. package/server/api/fs/move.post.ts +68 -0
  122. package/server/api/fs/read.post.ts +48 -0
  123. package/server/api/fs/rename.post.ts +55 -0
  124. package/server/api/fs/write.post.ts +51 -0
  125. package/server/api/health.get.ts +40 -0
  126. package/server/api/home.get.ts +26 -0
  127. package/server/api/hooks/events/index.get.ts +56 -0
  128. package/server/api/hooks/events/index.post.ts +36 -0
  129. package/server/api/hooks/stats.get.ts +99 -0
  130. package/server/api/memory/[id].delete.ts +26 -0
  131. package/server/api/memory/context.get.ts +83 -0
  132. package/server/api/memory/extract.post.ts +42 -0
  133. package/server/api/memory/search.get.ts +70 -0
  134. package/server/api/memory/store.post.ts +31 -0
  135. package/server/api/projects/[id]/index.delete.ts +40 -0
  136. package/server/api/projects/[id]/index.get.ts +25 -0
  137. package/server/api/projects/[id]/index.put.ts +50 -0
  138. package/server/api/projects/index.get.ts +20 -0
  139. package/server/api/projects/index.post.ts +34 -0
  140. package/server/api/secrets/[key].delete.ts +31 -0
  141. package/server/api/secrets/[key].get.ts +30 -0
  142. package/server/api/secrets/[key].put.ts +52 -0
  143. package/server/api/secrets/index.get.ts +20 -0
  144. package/server/api/secrets/index.post.ts +58 -0
  145. package/server/api/tasks/[id]/index.delete.ts +46 -0
  146. package/server/api/tasks/[id]/index.get.ts +24 -0
  147. package/server/api/tasks/[id]/index.put.ts +70 -0
  148. package/server/api/tasks/[id]/restore.post.ts +49 -0
  149. package/server/api/tasks/index.get.ts +53 -0
  150. package/server/api/tasks/index.post.ts +47 -0
  151. package/server/api/tasks/tags.get.ts +21 -0
  152. package/server/api/user/email.patch.ts +56 -0
  153. package/server/db/index.ts +76 -0
  154. package/server/db/migrate.ts +41 -0
  155. package/server/db/schema.ts +345 -0
  156. package/server/db/seed.ts +46 -0
  157. package/server/db/types.ts +28 -0
  158. package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
  159. package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
  160. package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
  161. package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
  162. package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
  163. package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
  164. package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
  165. package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
  166. package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
  167. package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
  168. package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
  169. package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
  170. package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
  171. package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
  172. package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
  173. package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
  174. package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
  175. package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
  176. package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
  177. package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
  178. package/server/drizzle/migrations/meta/_journal.json +76 -0
  179. package/server/middleware/auth.ts +79 -0
  180. package/server/plugins/00.env-validate.ts +38 -0
  181. package/server/plugins/01.api-token.ts +31 -0
  182. package/server/plugins/02.database.ts +54 -0
  183. package/server/plugins/03.file-watcher.ts +65 -0
  184. package/server/plugins/04.cron-agents.ts +26 -0
  185. package/server/routes/_ws/chat.ts +252 -0
  186. package/server/routes/notifications.ts +47 -0
  187. package/server/routes/terminal.ts +98 -0
  188. package/server/services/agent-executor.ts +218 -0
  189. package/server/services/cron-scheduler.ts +78 -0
  190. package/server/services/memory-extractor.ts +120 -0
  191. package/server/utils/agent-cleanup.ts +91 -0
  192. package/server/utils/agent-registry.ts +95 -0
  193. package/server/utils/auth.ts +33 -0
  194. package/server/utils/chat-session-manager.ts +59 -0
  195. package/server/utils/crypto.ts +40 -0
  196. package/server/utils/db-guard.ts +12 -0
  197. package/server/utils/db-state.ts +63 -0
  198. package/server/utils/document-sync.ts +207 -0
  199. package/server/utils/frontmatter.ts +84 -0
  200. package/server/utils/notification-bus.ts +60 -0
  201. package/server/utils/path-validator.ts +55 -0
  202. package/server/utils/pty-manager.ts +130 -0
  203. package/shared/types/index.ts +604 -0
  204. package/shared/utils/language-detection.ts +87 -0
  205. package/tsconfig.json +10 -0
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ // Minimal layout for public document viewing
3
+ </script>
4
+
5
+ <template>
6
+ <div>
7
+ <UHeader>
8
+ <template #left>
9
+ <NuxtLink
10
+ to="/"
11
+ class="flex items-center gap-2"
12
+ >
13
+ <UIcon
14
+ name="i-lucide-brain"
15
+ class="size-6 text-primary"
16
+ />
17
+ <span class="font-semibold">Cognova</span>
18
+ </NuxtLink>
19
+ </template>
20
+
21
+ <template #right>
22
+ <UColorModeButton />
23
+ </template>
24
+ </UHeader>
25
+
26
+ <UMain>
27
+ <slot />
28
+ </UMain>
29
+ <USeparator
30
+ icon="i-lucide-brain"
31
+ :ui="{ icon: 'text-primary' }"
32
+ />
33
+ <UFooter>
34
+ <template #left>
35
+ <p class="text-muted text-sm">
36
+ Shared via Cognova
37
+ </p>
38
+ </template>
39
+
40
+ <template #right>
41
+ <UButton
42
+ icon="i-simple-icons-github"
43
+ to="https://github.com/Patrity/cognova"
44
+ target="_blank"
45
+ aria-label="View on GitHub"
46
+ color="neutral"
47
+ variant="ghost"
48
+ />
49
+ <UButton
50
+ icon="i-simple-icons-x"
51
+ to="https://x.com/Patrity/"
52
+ target="_blank"
53
+ aria-label="View Twitter"
54
+ color="neutral"
55
+ variant="ghost"
56
+ />
57
+ </template>
58
+ </UFooter>
59
+ </div>
60
+ </template>
@@ -0,0 +1,9 @@
1
+ export default defineNuxtRouteMiddleware(() => {
2
+ const { isAuthenticated, isPending } = useAuth()
3
+
4
+ // Wait for auth to be determined
5
+ if (isPending.value) return
6
+
7
+ if (!isAuthenticated.value)
8
+ return navigateTo('/login')
9
+ })
@@ -0,0 +1,602 @@
1
+ <script setup lang="ts">
2
+ import type { CronAgent, CronAgentRun, AgentDetailStats, StatsPeriod, CreateAgentInput, UpdateAgentInput } from '~~/shared/types'
3
+
4
+ definePageMeta({
5
+ layout: 'dashboard',
6
+ middleware: 'auth'
7
+ })
8
+
9
+ const route = useRoute()
10
+ const router = useRouter()
11
+ const toast = useToast()
12
+ const { fetchAgentStats, fetchRuns, updateAgent, deleteAgent, toggleEnabled, runAgent, cancelAgent } = useAgents()
13
+ const { isAgentRunning } = useNotificationBus()
14
+
15
+ const agentId = computed(() => route.params.id as string)
16
+
17
+ // Fetch agent data with SSR support
18
+ const { data: agentData, status: agentStatus, error: agentError, refresh: refreshAgent } = await useFetch<{ data: CronAgent }>(
19
+ () => `/api/agents/${agentId.value}`,
20
+ { key: `agent-${agentId.value}` }
21
+ )
22
+
23
+ // Computed agent from response
24
+ const agent = computed(() => agentData.value?.data ?? null)
25
+ const loading = computed(() => agentStatus.value === 'pending')
26
+
27
+ // Handle agent not found
28
+ if (agentError.value) {
29
+ throw createError({
30
+ statusCode: agentError.value.statusCode || 404,
31
+ message: agentError.value.message || 'Agent not found'
32
+ })
33
+ }
34
+
35
+ // Data state for stats and runs (client-side)
36
+ const stats = ref<AgentDetailStats | null>(null)
37
+ const runs = ref<CronAgentRun[]>([])
38
+ const statsLoading = ref(false)
39
+ const runsLoading = ref(false)
40
+
41
+ // Check if agent is running (from WebSocket OR from runs list)
42
+ const agentRunning = computed(() => {
43
+ if (!agent.value) return false
44
+ // Check WebSocket notification state
45
+ if (isAgentRunning(agent.value.id)) return true
46
+ // Also check if any run in the list has 'running' status
47
+ return runs.value.some(r => r.status === 'running')
48
+ })
49
+
50
+ // Period state
51
+ const period = ref<StatsPeriod>('7d')
52
+ const periodOptions = [
53
+ { label: '24h', value: '24h' as StatsPeriod },
54
+ { label: '7d', value: '7d' as StatsPeriod },
55
+ { label: '30d', value: '30d' as StatsPeriod }
56
+ ]
57
+
58
+ // UI state
59
+ const showEditForm = ref(false)
60
+ const showDeleteConfirm = ref(false)
61
+ const selectedRun = ref<CronAgentRun | null>(null)
62
+ const showRunModal = ref(false)
63
+
64
+ // Format cron expression to human readable
65
+ function formatSchedule(schedule: string) {
66
+ const parts = schedule.split(' ')
67
+ if (parts.length !== 5) return schedule
68
+
69
+ const min = parts[0]!
70
+ const hour = parts[1]!
71
+ const dayOfMonth = parts[2]!
72
+ const month = parts[3]!
73
+ const dayOfWeek = parts[4]!
74
+
75
+ if (min === '0' && hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*')
76
+ return 'Every hour'
77
+ if (min === '*/5' && hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*')
78
+ return 'Every 5 minutes'
79
+ if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*' && hour !== '*')
80
+ return `Daily at ${hour}:${min.padStart(2, '0')}`
81
+ if (dayOfWeek === '0' && dayOfMonth === '*' && month === '*')
82
+ return `Weekly on Sunday at ${hour}:${min.padStart(2, '0')}`
83
+
84
+ return schedule
85
+ }
86
+
87
+ // Format relative time
88
+ function formatRelativeTime(date?: Date | string) {
89
+ if (!date) return 'Never'
90
+ const d = new Date(date)
91
+ const now = new Date()
92
+ const diff = now.getTime() - d.getTime()
93
+ const minutes = Math.floor(diff / 60000)
94
+ const hours = Math.floor(diff / 3600000)
95
+ const days = Math.floor(diff / 86400000)
96
+
97
+ if (minutes < 1) return 'Just now'
98
+ if (minutes < 60) return `${minutes}m ago`
99
+ if (hours < 24) return `${hours}h ago`
100
+ return `${days}d ago`
101
+ }
102
+
103
+ // Format duration
104
+ function formatDuration(ms?: number) {
105
+ if (!ms) return '-'
106
+ if (ms < 1000) return `${ms}ms`
107
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
108
+ return `${(ms / 60000).toFixed(1)}m`
109
+ }
110
+
111
+ // Format date time
112
+ function formatDateTime(date?: Date | string) {
113
+ if (!date) return '-'
114
+ const d = new Date(date)
115
+ return d.toLocaleString()
116
+ }
117
+
118
+ // Status badge color
119
+ function getStatusColor(status?: string) {
120
+ switch (status) {
121
+ case 'success': return 'success'
122
+ case 'error': return 'error'
123
+ case 'budget_exceeded': return 'warning'
124
+ case 'running': return 'info'
125
+ case 'cancelled': return 'neutral'
126
+ default: return 'neutral'
127
+ }
128
+ }
129
+
130
+ // Load stats
131
+ async function loadStats() {
132
+ statsLoading.value = true
133
+ try {
134
+ stats.value = await fetchAgentStats(agentId.value, period.value)
135
+ } catch {
136
+ // Silently fail
137
+ } finally {
138
+ statsLoading.value = false
139
+ }
140
+ }
141
+
142
+ // Load runs
143
+ async function loadRuns() {
144
+ runsLoading.value = true
145
+ try {
146
+ runs.value = await fetchRuns(agentId.value, period.value)
147
+ } catch {
148
+ // Silently fail
149
+ } finally {
150
+ runsLoading.value = false
151
+ }
152
+ }
153
+
154
+ // Handle toggle
155
+ async function handleToggle() {
156
+ if (!agent.value) return
157
+ try {
158
+ await toggleEnabled(agent.value.id)
159
+ await refreshAgent()
160
+ } catch {
161
+ toast.add({
162
+ title: 'Failed to toggle agent',
163
+ color: 'error',
164
+ icon: 'i-lucide-alert-circle'
165
+ })
166
+ }
167
+ }
168
+
169
+ // Handle run
170
+ async function handleRun() {
171
+ if (!agent.value) return
172
+ try {
173
+ await runAgent(agent.value.id)
174
+ // Refresh runs after a short delay
175
+ setTimeout(loadRuns, 1000)
176
+ } catch {
177
+ toast.add({
178
+ title: 'Failed to run agent',
179
+ color: 'error',
180
+ icon: 'i-lucide-alert-circle'
181
+ })
182
+ }
183
+ }
184
+
185
+ // Handle cancel
186
+ async function handleCancel() {
187
+ if (!agent.value) return
188
+ try {
189
+ await cancelAgent(agent.value.id)
190
+ toast.add({
191
+ title: 'Agent cancelled',
192
+ color: 'warning',
193
+ icon: 'i-lucide-x-circle'
194
+ })
195
+ // Refresh runs after a short delay
196
+ setTimeout(() => {
197
+ loadRuns()
198
+ loadStats()
199
+ }, 1000)
200
+ } catch {
201
+ toast.add({
202
+ title: 'Failed to cancel agent',
203
+ description: 'No running execution found',
204
+ color: 'error',
205
+ icon: 'i-lucide-alert-circle'
206
+ })
207
+ }
208
+ }
209
+
210
+ // Handle edit submit
211
+ async function handleEditSubmit(data: CreateAgentInput | UpdateAgentInput) {
212
+ if (!agent.value) return
213
+ try {
214
+ await updateAgent(agent.value.id, data as UpdateAgentInput)
215
+ toast.add({
216
+ title: 'Agent updated',
217
+ color: 'success',
218
+ icon: 'i-lucide-check'
219
+ })
220
+ showEditForm.value = false
221
+ refreshAgent()
222
+ loadStats()
223
+ } catch (e) {
224
+ toast.add({
225
+ title: 'Failed to update agent',
226
+ description: e instanceof Error ? e.message : 'An unexpected error occurred',
227
+ color: 'error',
228
+ icon: 'i-lucide-alert-circle'
229
+ })
230
+ }
231
+ }
232
+
233
+ // Handle delete
234
+ async function handleDelete() {
235
+ if (!agent.value) return
236
+ try {
237
+ await deleteAgent(agent.value.id)
238
+ toast.add({
239
+ title: 'Agent deleted',
240
+ color: 'success',
241
+ icon: 'i-lucide-check'
242
+ })
243
+ router.push('/agents')
244
+ } catch {
245
+ toast.add({
246
+ title: 'Failed to delete agent',
247
+ color: 'error',
248
+ icon: 'i-lucide-alert-circle'
249
+ })
250
+ }
251
+ }
252
+
253
+ // Open run modal
254
+ function openRunModal(run: CronAgentRun) {
255
+ selectedRun.value = run
256
+ showRunModal.value = true
257
+ }
258
+
259
+ // Truncate output for list display
260
+ function truncateOutput(output?: string, maxLength = 100) {
261
+ if (!output) return '-'
262
+ if (output.length <= maxLength) return output
263
+ return output.substring(0, maxLength) + '...'
264
+ }
265
+
266
+ // Watch period changes
267
+ watch(period, () => {
268
+ loadStats()
269
+ loadRuns()
270
+ })
271
+
272
+ // Load stats and runs on mount (agent is already fetched via useFetch)
273
+ onMounted(() => {
274
+ loadStats()
275
+ loadRuns()
276
+ })
277
+ </script>
278
+
279
+ <template>
280
+ <div class="contents">
281
+ <UDashboardPanel
282
+ id="agent-detail"
283
+ grow
284
+ >
285
+ <template #header>
286
+ <UDashboardNavbar>
287
+ <template #left>
288
+ <div class="flex items-center gap-3">
289
+ <UButton
290
+ icon="i-lucide-arrow-left"
291
+ variant="ghost"
292
+ size="sm"
293
+ @click="router.push('/agents')"
294
+ />
295
+ <template v-if="agent">
296
+ <h1 class="font-semibold text-lg truncate">
297
+ {{ agent.name }}
298
+ </h1>
299
+ <UBadge
300
+ v-if="agentRunning"
301
+ color="info"
302
+ variant="subtle"
303
+ >
304
+ <UIcon
305
+ name="i-lucide-loader-2"
306
+ class="w-3 h-3 animate-spin mr-1"
307
+ />
308
+ Running
309
+ </UBadge>
310
+ <UBadge
311
+ v-else-if="!agent.enabled"
312
+ color="neutral"
313
+ variant="subtle"
314
+ >
315
+ Disabled
316
+ </UBadge>
317
+ </template>
318
+ <USkeleton
319
+ v-else
320
+ class="h-6 w-32"
321
+ />
322
+ </div>
323
+ </template>
324
+
325
+ <template #right>
326
+ <template v-if="agent">
327
+ <USwitch
328
+ :model-value="agent.enabled"
329
+ @update:model-value="handleToggle"
330
+ />
331
+ <UButton
332
+ v-if="agentRunning"
333
+ icon="i-lucide-square"
334
+ variant="ghost"
335
+ color="error"
336
+ @click="handleCancel"
337
+ />
338
+ <UButton
339
+ v-else
340
+ icon="i-lucide-play"
341
+ variant="ghost"
342
+ :disabled="!agent.enabled"
343
+ @click="handleRun"
344
+ />
345
+ <UButton
346
+ icon="i-lucide-pencil"
347
+ variant="ghost"
348
+ @click="showEditForm = true"
349
+ />
350
+ <UButton
351
+ icon="i-lucide-trash-2"
352
+ variant="ghost"
353
+ color="error"
354
+ @click="showDeleteConfirm = true"
355
+ />
356
+ </template>
357
+ </template>
358
+ </UDashboardNavbar>
359
+
360
+ <UDashboardToolbar>
361
+ <template #left>
362
+ <UFieldGroup>
363
+ <UButton
364
+ v-for="opt in periodOptions"
365
+ :key="opt.value"
366
+ :color="period === opt.value ? 'primary' : 'neutral'"
367
+ :variant="period === opt.value ? 'solid' : 'ghost'"
368
+ size="sm"
369
+ @click="period = opt.value"
370
+ >
371
+ {{ opt.label }}
372
+ </UButton>
373
+ </UFieldGroup>
374
+ </template>
375
+ </UDashboardToolbar>
376
+ </template>
377
+
378
+ <template #body>
379
+ <!-- Loading state -->
380
+ <div
381
+ v-if="loading"
382
+ class="flex items-center justify-center h-64"
383
+ >
384
+ <UIcon
385
+ name="i-lucide-loader-2"
386
+ class="w-8 h-8 animate-spin text-neutral-400"
387
+ />
388
+ </div>
389
+
390
+ <div
391
+ v-else-if="agent"
392
+ class="p-4 space-y-6"
393
+ >
394
+ <!-- Agent Info -->
395
+ <UCard>
396
+ <div class="space-y-3">
397
+ <div v-if="agent.description">
398
+ <p class="text-sm text-muted uppercase mb-1">
399
+ Description
400
+ </p>
401
+ <p>{{ agent.description }}</p>
402
+ </div>
403
+
404
+ <div class="flex flex-wrap gap-6">
405
+ <div>
406
+ <p class="text-sm text-muted uppercase mb-1">
407
+ Schedule
408
+ </p>
409
+ <p class="flex items-center gap-2">
410
+ <UIcon name="i-lucide-clock" />
411
+ {{ formatSchedule(agent.schedule) }}
412
+ <span class="text-sm text-muted">({{ agent.schedule }})</span>
413
+ </p>
414
+ </div>
415
+
416
+ <div v-if="agent.maxBudgetUsd">
417
+ <p class="text-sm text-muted uppercase mb-1">
418
+ Budget Limit
419
+ </p>
420
+ <p class="flex items-center gap-2">
421
+ <UIcon name="i-lucide-dollar-sign" />
422
+ ${{ agent.maxBudgetUsd }} per run
423
+ </p>
424
+ </div>
425
+
426
+ <div v-if="agent.lastRunAt">
427
+ <p class="text-sm text-muted uppercase mb-1">
428
+ Last Run
429
+ </p>
430
+ <p class="flex items-center gap-2">
431
+ <UIcon name="i-lucide-activity" />
432
+ {{ formatRelativeTime(agent.lastRunAt) }}
433
+ </p>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ </UCard>
438
+
439
+ <!-- Stats Cards -->
440
+ <AgentsAgentStatsCards
441
+ :stats="stats"
442
+ :loading="statsLoading"
443
+ variant="detail"
444
+ />
445
+
446
+ <!-- Activity Chart -->
447
+ <AgentsAgentActivityChart
448
+ v-if="stats && stats.dailyRuns.length > 0"
449
+ :data="stats.dailyRuns"
450
+ title="Run Activity"
451
+ />
452
+
453
+ <!-- Run History -->
454
+ <div class="space-y-4">
455
+ <h2 class="text-lg font-semibold">
456
+ Run History
457
+ </h2>
458
+
459
+ <!-- Loading runs -->
460
+ <div
461
+ v-if="runsLoading"
462
+ class="flex items-center justify-center h-32"
463
+ >
464
+ <UIcon
465
+ name="i-lucide-loader-2"
466
+ class="w-6 h-6 animate-spin text-neutral-400"
467
+ />
468
+ </div>
469
+
470
+ <!-- Empty state -->
471
+ <div
472
+ v-else-if="runs.length === 0"
473
+ class="flex flex-col items-center justify-center h-32 text-neutral-500"
474
+ >
475
+ <UIcon
476
+ name="i-lucide-history"
477
+ class="w-8 h-8 mb-2"
478
+ />
479
+ <p>No runs yet</p>
480
+ </div>
481
+
482
+ <!-- Run list -->
483
+ <div
484
+ v-else
485
+ class="space-y-2"
486
+ >
487
+ <UCard
488
+ v-for="run in runs"
489
+ :key="run.id"
490
+ class="cursor-pointer transition-all hover:ring-2 hover:ring-primary/50"
491
+ @click="openRunModal(run)"
492
+ >
493
+ <div class="flex items-start justify-between gap-4">
494
+ <div class="flex-1 min-w-0">
495
+ <div class="flex items-center gap-2 mb-1">
496
+ <UBadge
497
+ v-if="run.status === 'running'"
498
+ color="info"
499
+ variant="subtle"
500
+ size="sm"
501
+ >
502
+ <UIcon
503
+ name="i-lucide-loader-2"
504
+ class="w-3 h-3 animate-spin mr-1"
505
+ />
506
+ Running
507
+ </UBadge>
508
+ <UBadge
509
+ v-else
510
+ :color="getStatusColor(run.status)"
511
+ size="sm"
512
+ >
513
+ {{ run.status }}
514
+ </UBadge>
515
+ <span class="text-sm text-muted">
516
+ {{ formatDateTime(run.startedAt) }}
517
+ </span>
518
+ </div>
519
+
520
+ <p class="text-sm text-muted truncate">
521
+ {{ run.error ? truncateOutput(run.error) : truncateOutput(run.output) }}
522
+ </p>
523
+ </div>
524
+
525
+ <div class="text-right text-sm text-muted shrink-0">
526
+ <p>{{ formatDuration(run.durationMs) }}</p>
527
+ <p v-if="run.costUsd">
528
+ ${{ run.costUsd.toFixed(4) }}
529
+ </p>
530
+ </div>
531
+ </div>
532
+ </UCard>
533
+ </div>
534
+ </div>
535
+ </div>
536
+ </template>
537
+ </UDashboardPanel>
538
+
539
+ <!-- Edit Slideover -->
540
+ <USlideover v-model:open="showEditForm">
541
+ <template #title>
542
+ Edit Agent
543
+ </template>
544
+
545
+ <template #body>
546
+ <AgentsAgentForm
547
+ :agent="agent"
548
+ @submit="handleEditSubmit"
549
+ @cancel="showEditForm = false"
550
+ />
551
+ </template>
552
+ </USlideover>
553
+
554
+ <!-- Delete Confirmation Modal -->
555
+ <UModal v-model:open="showDeleteConfirm">
556
+ <template #content>
557
+ <div class="p-4 space-y-4">
558
+ <div class="flex items-center gap-3">
559
+ <div class="p-2 rounded-full bg-error-500/10">
560
+ <UIcon
561
+ name="i-lucide-alert-triangle"
562
+ class="w-6 h-6 text-error-500"
563
+ />
564
+ </div>
565
+ <div>
566
+ <h3 class="font-semibold">
567
+ Delete Agent
568
+ </h3>
569
+ <p class="text-sm text-muted">
570
+ Are you sure you want to delete "{{ agent?.name }}"? This action cannot be undone.
571
+ </p>
572
+ </div>
573
+ </div>
574
+ </div>
575
+ </template>
576
+
577
+ <template #footer>
578
+ <div class="flex justify-end gap-2">
579
+ <UButton
580
+ color="neutral"
581
+ variant="ghost"
582
+ @click="showDeleteConfirm = false"
583
+ >
584
+ Cancel
585
+ </UButton>
586
+ <UButton
587
+ color="error"
588
+ @click="handleDelete"
589
+ >
590
+ Delete
591
+ </UButton>
592
+ </div>
593
+ </template>
594
+ </UModal>
595
+
596
+ <!-- Run Detail Modal -->
597
+ <AgentsAgentRunModal
598
+ v-model:open="showRunModal"
599
+ :run="selectedRun"
600
+ />
601
+ </div>
602
+ </template>