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,105 @@
1
+ <script setup lang="ts">
2
+ import { format } from 'date-fns'
3
+ import { useElementSize } from '@vueuse/core'
4
+ import { VisXYContainer, VisLine, VisArea, VisAxis, VisCrosshair, VisTooltip } from '@unovis/vue'
5
+ import type { DailyRunData } from '~~/shared/types'
6
+
7
+ const props = defineProps<{
8
+ data: DailyRunData[]
9
+ title?: string
10
+ height?: string
11
+ }>()
12
+
13
+ const cardRef = useTemplateRef<HTMLElement | null>('cardRef')
14
+ const { width } = useElementSize(cardRef)
15
+
16
+ // X accessor - use index
17
+ const x = (_: DailyRunData, i: number) => i
18
+
19
+ // Y accessor - total runs
20
+ const y = (d: DailyRunData) => d.total
21
+
22
+ // Format X axis ticks
23
+ const xTicks = (i: number) => {
24
+ const item = props.data[i]
25
+ if (!item) return ''
26
+ return format(new Date(item.date), 'd MMM')
27
+ }
28
+
29
+ // Tooltip template
30
+ const template = (d: DailyRunData) => {
31
+ return `<div class="p-2 text-sm">
32
+ <div class="font-medium">${format(new Date(d.date), 'MMM d, yyyy')}</div>
33
+ <div class="text-success">Success: ${d.success}</div>
34
+ <div class="text-error">Errors: ${d.error}</div>
35
+ <div class="text-muted">Total: ${d.total}</div>
36
+ <div class="text-muted">Cost: $${d.costUsd.toFixed(4)}</div>
37
+ </div>`
38
+ }
39
+ </script>
40
+
41
+ <template>
42
+ <UCard
43
+ ref="cardRef"
44
+ :ui="{ root: 'overflow-visible', body: '!px-0 !pt-0 !pb-3' }"
45
+ >
46
+ <template
47
+ v-if="title"
48
+ #header
49
+ >
50
+ <p class="text-sm font-medium">
51
+ {{ title }}
52
+ </p>
53
+ </template>
54
+
55
+ <div
56
+ v-if="data.length === 0"
57
+ class="flex items-center justify-center h-48 text-muted"
58
+ >
59
+ No activity data
60
+ </div>
61
+
62
+ <VisXYContainer
63
+ v-else
64
+ :data="data"
65
+ :padding="{ top: 10, left: 10, right: 10, bottom: 10 }"
66
+ :class="height || 'h-48'"
67
+ :width="width"
68
+ >
69
+ <VisLine
70
+ :x="x"
71
+ :y="y"
72
+ color="var(--ui-primary)"
73
+ />
74
+ <VisArea
75
+ :x="x"
76
+ :y="y"
77
+ color="var(--ui-primary)"
78
+ :opacity="0.1"
79
+ />
80
+ <VisAxis
81
+ type="x"
82
+ :x="x"
83
+ :tick-format="xTicks"
84
+ />
85
+ <VisCrosshair
86
+ color="var(--ui-primary)"
87
+ :template="template"
88
+ />
89
+ <VisTooltip />
90
+ </VisXYContainer>
91
+ </UCard>
92
+ </template>
93
+
94
+ <style scoped>
95
+ .unovis-xy-container {
96
+ --vis-crosshair-line-stroke-color: var(--ui-primary);
97
+ --vis-crosshair-circle-stroke-color: var(--ui-bg);
98
+ --vis-axis-grid-color: var(--ui-border);
99
+ --vis-axis-tick-color: var(--ui-border);
100
+ --vis-axis-tick-label-color: var(--ui-text-dimmed);
101
+ --vis-tooltip-background-color: var(--ui-bg);
102
+ --vis-tooltip-border-color: var(--ui-border);
103
+ --vis-tooltip-text-color: var(--ui-text-highlighted);
104
+ }
105
+ </style>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ data?: unknown[]
4
+ title?: string
5
+ height?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <UCard :ui="{ body: '!p-0' }">
11
+ <template
12
+ v-if="title"
13
+ #header
14
+ >
15
+ <p class="text-sm font-medium">
16
+ {{ title }}
17
+ </p>
18
+ </template>
19
+
20
+ <div
21
+ :class="height || 'h-48'"
22
+ class="bg-muted/5 rounded animate-pulse"
23
+ />
24
+ </UCard>
25
+ </template>
@@ -0,0 +1,304 @@
1
+ <script setup lang="ts">
2
+ import type { EditorToolbarItem } from '@nuxt/ui'
3
+ import type { CronAgent, CreateAgentInput, UpdateAgentInput } from '~~/shared/types'
4
+
5
+ const props = defineProps<{
6
+ agent?: CronAgent | null
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ submit: [data: CreateAgentInput | UpdateAgentInput]
11
+ cancel: []
12
+ }>()
13
+
14
+ // Form state
15
+ const name = ref('')
16
+ const description = ref('')
17
+ const schedule = ref('0 8 * * *')
18
+ const prompt = ref('')
19
+ const enabled = ref(true)
20
+ const maxTurns = ref<number | undefined>(50)
21
+ const maxBudgetUsd = ref<number | undefined>(undefined)
22
+
23
+ // Common schedule presets
24
+ const schedulePresets = [
25
+ { label: 'Every 5 minutes', value: '*/5 * * * *' },
26
+ { label: 'Every hour', value: '0 * * * *' },
27
+ { label: 'Every 6 hours', value: '0 */6 * * *' },
28
+ { label: 'Daily at 8am', value: '0 8 * * *' },
29
+ { label: 'Daily at midnight', value: '0 0 * * *' },
30
+ { label: 'Weekly (Sunday midnight)', value: '0 0 * * 0' },
31
+ { label: 'Custom', value: 'custom' }
32
+ ]
33
+
34
+ const selectedPreset = ref('0 8 * * *')
35
+ const customSchedule = ref('')
36
+
37
+ // Editor toolbar
38
+ const editorToolbar: EditorToolbarItem[][] = [
39
+ [
40
+ { kind: 'mark', mark: 'bold', icon: 'i-lucide-bold' },
41
+ { kind: 'mark', mark: 'italic', icon: 'i-lucide-italic' },
42
+ { kind: 'mark', mark: 'code', icon: 'i-lucide-code' }
43
+ ],
44
+ [
45
+ { kind: 'bulletList', icon: 'i-lucide-list' },
46
+ { kind: 'orderedList', icon: 'i-lucide-list-ordered' }
47
+ ],
48
+ [
49
+ { kind: 'codeBlock', icon: 'i-lucide-square-code' }
50
+ ]
51
+ ]
52
+
53
+ // Editor content (use undefined for empty to avoid TipTap issues)
54
+ const editorContent = computed(() => prompt.value || undefined)
55
+
56
+ // Watch preset selection
57
+ watch(selectedPreset, (value) => {
58
+ if (value !== 'custom') {
59
+ schedule.value = value
60
+ }
61
+ })
62
+
63
+ watch(customSchedule, (value) => {
64
+ if (selectedPreset.value === 'custom' && value) {
65
+ schedule.value = value
66
+ }
67
+ })
68
+
69
+ // Initialize form when agent prop changes
70
+ watch(() => props.agent, (agent) => {
71
+ if (agent) {
72
+ name.value = agent.name
73
+ description.value = agent.description || ''
74
+ schedule.value = agent.schedule
75
+ prompt.value = agent.prompt
76
+ enabled.value = agent.enabled
77
+ maxTurns.value = agent.maxTurns ?? 50
78
+ maxBudgetUsd.value = agent.maxBudgetUsd ?? undefined
79
+
80
+ // Check if schedule matches a preset
81
+ const preset = schedulePresets.find(p => p.value === agent.schedule)
82
+ if (preset) {
83
+ selectedPreset.value = agent.schedule
84
+ } else {
85
+ selectedPreset.value = 'custom'
86
+ customSchedule.value = agent.schedule
87
+ }
88
+ } else {
89
+ // Reset form for new agent
90
+ name.value = ''
91
+ description.value = ''
92
+ schedule.value = '0 8 * * *'
93
+ selectedPreset.value = '0 8 * * *'
94
+ customSchedule.value = ''
95
+ prompt.value = ''
96
+ enabled.value = true
97
+ maxTurns.value = 50
98
+ maxBudgetUsd.value = undefined
99
+ }
100
+ }, { immediate: true })
101
+
102
+ // Validation
103
+ const isValid = computed(() => {
104
+ return name.value.trim() && schedule.value.trim() && prompt.value.trim()
105
+ })
106
+
107
+ function handleSubmit() {
108
+ if (!isValid.value) return
109
+
110
+ const data: CreateAgentInput | UpdateAgentInput = {
111
+ name: name.value.trim(),
112
+ description: description.value.trim() || undefined,
113
+ schedule: schedule.value.trim(),
114
+ prompt: prompt.value.trim(),
115
+ enabled: enabled.value,
116
+ maxTurns: maxTurns.value,
117
+ maxBudgetUsd: maxBudgetUsd.value
118
+ }
119
+
120
+ emit('submit', data)
121
+ }
122
+ </script>
123
+
124
+ <template>
125
+ <div class="agent-form-body">
126
+ <!-- Name -->
127
+ <UFormField
128
+ label="Name"
129
+ required
130
+ >
131
+ <UInput
132
+ v-model="name"
133
+ placeholder="Email Digest Agent"
134
+ class="w-full"
135
+ />
136
+ </UFormField>
137
+
138
+ <!-- Description -->
139
+ <UFormField label="Description">
140
+ <UInput
141
+ v-model="description"
142
+ placeholder="Scans emails and surfaces important items"
143
+ class="w-full"
144
+ />
145
+ </UFormField>
146
+
147
+ <!-- Schedule -->
148
+ <UFormField
149
+ label="Schedule"
150
+ required
151
+ >
152
+ <USelect
153
+ v-model="selectedPreset"
154
+ :items="schedulePresets"
155
+ value-key="value"
156
+ class="w-full"
157
+ />
158
+ </UFormField>
159
+
160
+ <!-- Custom cron expression -->
161
+ <UFormField
162
+ v-if="selectedPreset === 'custom'"
163
+ label="Custom Cron Expression"
164
+ >
165
+ <UInput
166
+ v-model="customSchedule"
167
+ placeholder="0 4 * * *"
168
+ class="w-full"
169
+ />
170
+ <template #hint>
171
+ <span class="text-xs text-neutral-500">
172
+ Format: minute hour day-of-month month day-of-week
173
+ </span>
174
+ </template>
175
+ </UFormField>
176
+
177
+ <!-- Prompt - grows to fill available space -->
178
+ <div class="agent-form-prompt">
179
+ <label class="block text-sm font-medium text-default mb-1.5">
180
+ Prompt <span class="text-error-500">*</span>
181
+ </label>
182
+ <UEditor
183
+ v-slot="{ editor }"
184
+ :model-value="editorContent"
185
+ content-type="markdown"
186
+ placeholder="You are an email scanning agent..."
187
+ class="agent-editor"
188
+ @update:model-value="prompt = $event"
189
+ >
190
+ <UEditorToolbar
191
+ :editor="editor"
192
+ :items="editorToolbar"
193
+ class="border-b border-default"
194
+ />
195
+ </UEditor>
196
+ </div>
197
+
198
+ <!-- Other fields -->
199
+ <div class="agent-form-fields">
200
+ <!-- Max Turns & Budget -->
201
+ <div class="grid grid-cols-2 gap-4">
202
+ <UFormField label="Max Turns">
203
+ <UInput
204
+ v-model.number="maxTurns"
205
+ type="number"
206
+ :min="1"
207
+ :max="200"
208
+ placeholder="50"
209
+ class="w-full"
210
+ />
211
+ </UFormField>
212
+
213
+ <UFormField label="Max Budget (USD)">
214
+ <UInput
215
+ v-model.number="maxBudgetUsd"
216
+ type="number"
217
+ :min="0.01"
218
+ :step="0.01"
219
+ placeholder="No limit"
220
+ class="w-full"
221
+ />
222
+ </UFormField>
223
+ </div>
224
+
225
+ <!-- Enabled toggle -->
226
+ <UFormField>
227
+ <div class="flex items-center gap-2">
228
+ <USwitch v-model="enabled" />
229
+ <span class="text-sm">Enabled</span>
230
+ </div>
231
+ </UFormField>
232
+ </div>
233
+
234
+ <!-- Actions -->
235
+ <div class="flex justify-end gap-2 pt-4">
236
+ <UButton
237
+ color="neutral"
238
+ variant="ghost"
239
+ @click="emit('cancel')"
240
+ >
241
+ Cancel
242
+ </UButton>
243
+ <UButton
244
+ :disabled="!isValid"
245
+ @click="handleSubmit"
246
+ >
247
+ {{ agent ? 'Update Agent' : 'Create Agent' }}
248
+ </UButton>
249
+ </div>
250
+ </div>
251
+ </template>
252
+
253
+ <style>
254
+ /* Body fills available space with flex layout */
255
+ .agent-form-body {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 1rem;
259
+ height: 100%;
260
+ min-height: 0;
261
+ }
262
+
263
+ /* Prompt section - grows to fill available space */
264
+ .agent-form-prompt {
265
+ flex: 1;
266
+ display: flex;
267
+ flex-direction: column;
268
+ min-height: 12rem;
269
+ overflow: hidden;
270
+ }
271
+
272
+ /* Other form fields - fixed height */
273
+ .agent-form-fields {
274
+ flex-shrink: 0;
275
+ display: flex;
276
+ flex-direction: column;
277
+ gap: 1rem;
278
+ }
279
+
280
+ /* Editor fills available space in prompt */
281
+ .agent-editor {
282
+ flex: 1;
283
+ display: flex;
284
+ flex-direction: column;
285
+ min-height: 0;
286
+ border: 1px solid var(--ui-border);
287
+ border-radius: 0.375rem;
288
+ }
289
+
290
+ .agent-editor [data-slot="content"] {
291
+ flex: 1;
292
+ overflow: auto;
293
+ min-height: 0;
294
+ }
295
+
296
+ .agent-editor .tiptap {
297
+ min-height: 100%;
298
+ padding: 0.75rem;
299
+ }
300
+
301
+ .agent-editor .tiptap:focus {
302
+ outline: none;
303
+ }
304
+ </style>
@@ -0,0 +1,154 @@
1
+ <script setup lang="ts">
2
+ import type { CronAgentRun } from '~~/shared/types'
3
+
4
+ defineProps<{
5
+ run: CronAgentRun | null
6
+ }>()
7
+
8
+ const open = defineModel<boolean>('open', { default: false })
9
+
10
+ function formatDuration(ms?: number) {
11
+ if (!ms) return '-'
12
+ if (ms < 1000) return `${ms}ms`
13
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
14
+ return `${(ms / 60000).toFixed(1)}m`
15
+ }
16
+
17
+ function formatDateTime(date?: Date | string) {
18
+ if (!date) return '-'
19
+ const d = new Date(date)
20
+ return d.toLocaleString()
21
+ }
22
+
23
+ function getStatusColor(status?: string) {
24
+ switch (status) {
25
+ case 'success': return 'success'
26
+ case 'error': return 'error'
27
+ case 'budget_exceeded': return 'warning'
28
+ case 'running': return 'info'
29
+ case 'cancelled': return 'neutral'
30
+ default: return 'neutral'
31
+ }
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <UModal
37
+ v-model:open="open"
38
+ :title="run ? `Run Details` : 'Run Details'"
39
+ :ui="{ content: 'sm:max-w-3xl' }"
40
+ >
41
+ <template #content>
42
+ <div
43
+ v-if="run"
44
+ class="p-4 space-y-4"
45
+ >
46
+ <!-- Status and metadata -->
47
+ <div class="flex flex-wrap items-center gap-4">
48
+ <UBadge
49
+ :color="getStatusColor(run.status)"
50
+ size="lg"
51
+ >
52
+ {{ run.status }}
53
+ </UBadge>
54
+ <span class="text-sm text-muted">
55
+ Started: {{ formatDateTime(run.startedAt) }}
56
+ </span>
57
+ <span
58
+ v-if="run.completedAt"
59
+ class="text-sm text-muted"
60
+ >
61
+ Completed: {{ formatDateTime(run.completedAt) }}
62
+ </span>
63
+ </div>
64
+
65
+ <!-- Metrics -->
66
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-4 p-4 rounded-lg bg-muted/5">
67
+ <div>
68
+ <p class="text-xs text-muted uppercase">
69
+ Duration
70
+ </p>
71
+ <p class="font-medium">
72
+ {{ formatDuration(run.durationMs) }}
73
+ </p>
74
+ </div>
75
+ <div>
76
+ <p class="text-xs text-muted uppercase">
77
+ Cost
78
+ </p>
79
+ <p class="font-medium">
80
+ {{ run.costUsd ? `$${run.costUsd.toFixed(4)}` : '-' }}
81
+ </p>
82
+ </div>
83
+ <div>
84
+ <p class="text-xs text-muted uppercase">
85
+ Input Tokens
86
+ </p>
87
+ <p class="font-medium">
88
+ {{ run.inputTokens?.toLocaleString() || '-' }}
89
+ </p>
90
+ </div>
91
+ <div>
92
+ <p class="text-xs text-muted uppercase">
93
+ Output Tokens
94
+ </p>
95
+ <p class="font-medium">
96
+ {{ run.outputTokens?.toLocaleString() || '-' }}
97
+ </p>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Error message -->
102
+ <div
103
+ v-if="run.error"
104
+ class="p-4 rounded-lg bg-error-500/10 border border-error-500/20"
105
+ >
106
+ <p class="text-sm font-medium text-error-500 mb-2">
107
+ Error
108
+ </p>
109
+ <pre class="text-sm text-error-400 whitespace-pre-wrap font-mono overflow-x-auto">{{ run.error }}</pre>
110
+ </div>
111
+
112
+ <!-- Output -->
113
+ <div v-if="run.output">
114
+ <p class="text-sm font-medium mb-2">
115
+ Output
116
+ </p>
117
+ <div class="max-h-96 overflow-auto rounded-lg bg-muted/5 border border-default">
118
+ <pre class="p-4 text-sm whitespace-pre-wrap font-mono">{{ run.output }}</pre>
119
+ </div>
120
+ </div>
121
+
122
+ <div
123
+ v-if="!run.output && !run.error && run.status === 'running'"
124
+ class="text-center text-muted py-8"
125
+ >
126
+ <UIcon
127
+ name="i-lucide-loader-2"
128
+ class="size-8 animate-spin mb-2"
129
+ />
130
+ <p>Agent is currently running...</p>
131
+ </div>
132
+ </div>
133
+
134
+ <div
135
+ v-else
136
+ class="p-4 text-center text-muted"
137
+ >
138
+ No run selected
139
+ </div>
140
+ </template>
141
+
142
+ <template #footer>
143
+ <div class="flex justify-end">
144
+ <UButton
145
+ color="neutral"
146
+ variant="ghost"
147
+ @click="open = false"
148
+ >
149
+ Close
150
+ </UButton>
151
+ </div>
152
+ </template>
153
+ </UModal>
154
+ </template>
@@ -0,0 +1,98 @@
1
+ <script setup lang="ts">
2
+ import type { AgentGlobalStats, AgentDetailStats } from '~~/shared/types'
3
+
4
+ const props = defineProps<{
5
+ stats: AgentGlobalStats | AgentDetailStats | null
6
+ variant: 'global' | 'detail'
7
+ loading?: boolean
8
+ }>()
9
+
10
+ function formatCurrency(value: number): string {
11
+ if (value < 0.01) return '<$0.01'
12
+ return `$${value.toFixed(2)}`
13
+ }
14
+
15
+ function formatDuration(ms: number): string {
16
+ if (ms < 1000) return `${ms}ms`
17
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
18
+ return `${(ms / 60000).toFixed(1)}m`
19
+ }
20
+
21
+ function formatRelativeTime(dateStr: string | null): string {
22
+ if (!dateStr) return 'Never'
23
+ const date = new Date(dateStr)
24
+ const now = new Date()
25
+ const diff = now.getTime() - date.getTime()
26
+ const minutes = Math.floor(diff / 60000)
27
+ const hours = Math.floor(diff / 3600000)
28
+ const days = Math.floor(diff / 86400000)
29
+
30
+ if (minutes < 1) return 'Just now'
31
+ if (minutes < 60) return `${minutes}m ago`
32
+ if (hours < 24) return `${hours}h ago`
33
+ return `${days}d ago`
34
+ }
35
+
36
+ const globalStats = computed(() => {
37
+ if (props.variant !== 'global' || !props.stats) return []
38
+ const s = props.stats as AgentGlobalStats
39
+ return [
40
+ { title: 'Total Agents', icon: 'i-lucide-bot', value: s.totalAgents },
41
+ { title: 'Active', icon: 'i-lucide-play-circle', value: s.activeAgents },
42
+ { title: 'Runs', icon: 'i-lucide-activity', value: s.runsInPeriod },
43
+ { title: 'Success Rate', icon: 'i-lucide-check-circle', value: `${s.successRate}%` },
44
+ { title: 'Total Cost', icon: 'i-lucide-dollar-sign', value: formatCurrency(s.totalCostUsd) }
45
+ ]
46
+ })
47
+
48
+ const detailStats = computed(() => {
49
+ if (props.variant !== 'detail' || !props.stats) return []
50
+ const s = props.stats as AgentDetailStats
51
+ return [
52
+ { title: 'Total Runs', icon: 'i-lucide-activity', value: s.totalRuns },
53
+ { title: 'Success Rate', icon: 'i-lucide-check-circle', value: `${s.successRate}%` },
54
+ { title: 'Avg Duration', icon: 'i-lucide-clock', value: formatDuration(s.avgDurationMs) },
55
+ { title: 'Total Cost', icon: 'i-lucide-dollar-sign', value: formatCurrency(s.totalCostUsd) },
56
+ { title: 'Last Run', icon: 'i-lucide-calendar', value: formatRelativeTime(s.lastRunAt) }
57
+ ]
58
+ })
59
+
60
+ const displayStats = computed(() => props.variant === 'global' ? globalStats.value : detailStats.value)
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ class="grid gap-4"
66
+ :class="variant === 'global' ? 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5' : 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5'"
67
+ >
68
+ <template v-if="loading">
69
+ <div
70
+ v-for="i in (variant === 'global' ? 5 : 5)"
71
+ :key="i"
72
+ class="p-4 rounded-lg bg-elevated border border-default"
73
+ >
74
+ <USkeleton class="h-4 w-16 mb-2" />
75
+ <USkeleton class="h-8 w-12" />
76
+ </div>
77
+ </template>
78
+
79
+ <template v-else-if="stats">
80
+ <div
81
+ v-for="stat in displayStats"
82
+ :key="stat.title"
83
+ class="p-4 rounded-lg bg-elevated border border-default"
84
+ >
85
+ <div class="flex items-center gap-2 text-muted text-sm mb-1">
86
+ <UIcon
87
+ :name="stat.icon"
88
+ class="size-4"
89
+ />
90
+ <span>{{ stat.title }}</span>
91
+ </div>
92
+ <p class="text-2xl font-semibold">
93
+ {{ stat.value }}
94
+ </p>
95
+ </div>
96
+ </template>
97
+ </div>
98
+ </template>