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,436 @@
1
+ <script setup lang="ts">
2
+ import type { TreeItem } from '~/composables/useFileTree'
3
+
4
+ const emit = defineEmits<{
5
+ select: [path: string]
6
+ }>()
7
+
8
+ const {
9
+ filteredItems,
10
+ selectedFile,
11
+ loading,
12
+ searchQuery,
13
+ loadTree,
14
+ createFile,
15
+ createFolder,
16
+ renameItem,
17
+ deleteItem,
18
+ moveItem
19
+ } = useFileTree()
20
+
21
+ const draggedItem = ref<TreeItem | null>(null)
22
+ const dropTarget = ref<TreeItem | null>(null)
23
+
24
+ function handleDragStart(event: DragEvent, item: TreeItem) {
25
+ draggedItem.value = item
26
+ if (event.dataTransfer) {
27
+ event.dataTransfer.effectAllowed = 'move'
28
+ event.dataTransfer.setData('text/plain', item.path)
29
+ }
30
+ }
31
+
32
+ function handleDragOver(event: DragEvent, item: TreeItem) {
33
+ if (!draggedItem.value) return
34
+ if (item.type !== 'directory') return
35
+ if (draggedItem.value.path === item.path) return
36
+ // Prevent dropping a folder into itself or its children
37
+ if (item.path.startsWith(draggedItem.value.path + '/')) return
38
+
39
+ event.preventDefault()
40
+ dropTarget.value = item
41
+ }
42
+
43
+ function handleDragLeave() {
44
+ dropTarget.value = null
45
+ }
46
+
47
+ function handleDragEnd() {
48
+ draggedItem.value = null
49
+ dropTarget.value = null
50
+ }
51
+
52
+ async function handleDrop(event: DragEvent, item: TreeItem) {
53
+ event.preventDefault()
54
+ if (!draggedItem.value || item.type !== 'directory') return
55
+
56
+ try {
57
+ await moveItem(draggedItem.value.path, item.path)
58
+ } catch {
59
+ // Error handled in composable
60
+ }
61
+
62
+ draggedItem.value = null
63
+ dropTarget.value = null
64
+ }
65
+
66
+ const contextMenuTarget = ref<TreeItem | null>(null)
67
+ const showNewFileModal = ref(false)
68
+ const showNewFolderModal = ref(false)
69
+ const showRenameModal = ref(false)
70
+ const showDeleteConfirm = ref(false)
71
+ const newItemName = ref('')
72
+
73
+ const contextMenuItems = computed(() => [
74
+ [{
75
+ label: 'New File',
76
+ icon: 'i-lucide-file-plus',
77
+ onSelect: () => {
78
+ newItemName.value = ''
79
+ showNewFileModal.value = true
80
+ }
81
+ }, {
82
+ label: 'New Folder',
83
+ icon: 'i-lucide-folder-plus',
84
+ onSelect: () => {
85
+ newItemName.value = ''
86
+ showNewFolderModal.value = true
87
+ }
88
+ }],
89
+ [{
90
+ label: 'Rename',
91
+ icon: 'i-lucide-pencil',
92
+ onSelect: () => {
93
+ if (contextMenuTarget.value) {
94
+ newItemName.value = contextMenuTarget.value.label
95
+ showRenameModal.value = true
96
+ }
97
+ }
98
+ }, {
99
+ label: 'Delete',
100
+ icon: 'i-lucide-trash-2',
101
+ color: 'error' as const,
102
+ onSelect: () => {
103
+ showDeleteConfirm.value = true
104
+ }
105
+ }]
106
+ ])
107
+
108
+ function handleSelect(item: TreeItem) {
109
+ if (item.type === 'file') {
110
+ selectedFile.value = item.path
111
+ emit('select', item.path)
112
+ }
113
+ }
114
+
115
+ async function handleCreateFile() {
116
+ if (!newItemName.value.trim()) return
117
+
118
+ const parentPath = contextMenuTarget.value?.type === 'directory'
119
+ ? contextMenuTarget.value.path
120
+ : '/'
121
+
122
+ // Add .md extension if not present
123
+ let filename = newItemName.value.trim()
124
+ if (!filename.includes('.')) {
125
+ filename += '.md'
126
+ }
127
+
128
+ try {
129
+ const path = await createFile(parentPath, filename)
130
+ selectedFile.value = path
131
+ emit('select', path)
132
+ } catch {
133
+ // Error handled in composable
134
+ }
135
+
136
+ showNewFileModal.value = false
137
+ newItemName.value = ''
138
+ }
139
+
140
+ async function handleCreateFolder() {
141
+ if (!newItemName.value.trim()) return
142
+
143
+ const parentPath = contextMenuTarget.value?.type === 'directory'
144
+ ? contextMenuTarget.value.path
145
+ : '/'
146
+
147
+ try {
148
+ await createFolder(parentPath, newItemName.value.trim())
149
+ } catch {
150
+ // Error handled in composable
151
+ }
152
+
153
+ showNewFolderModal.value = false
154
+ newItemName.value = ''
155
+ }
156
+
157
+ async function handleRename() {
158
+ if (!contextMenuTarget.value || !newItemName.value.trim()) return
159
+
160
+ try {
161
+ await renameItem(contextMenuTarget.value.path, newItemName.value.trim())
162
+ } catch {
163
+ // Error handled in composable
164
+ }
165
+
166
+ showRenameModal.value = false
167
+ newItemName.value = ''
168
+ }
169
+
170
+ async function handleDelete() {
171
+ if (!contextMenuTarget.value) return
172
+
173
+ try {
174
+ await deleteItem(contextMenuTarget.value.path)
175
+ } catch {
176
+ // Error handled in composable
177
+ }
178
+
179
+ showDeleteConfirm.value = false
180
+ }
181
+
182
+ onMounted(() => {
183
+ loadTree()
184
+ })
185
+ </script>
186
+
187
+ <template>
188
+ <div class="h-full flex flex-col">
189
+ <div class="-mt-1 pb-2">
190
+ <UInput
191
+ v-model="searchQuery"
192
+ placeholder="Search files..."
193
+ icon="i-lucide-search"
194
+ size="sm"
195
+ class="w-full"
196
+ />
197
+ </div>
198
+ <USeparator class="mt-2" />
199
+
200
+ <UContextMenu
201
+ :items="contextMenuItems"
202
+ class="flex-1 h-full overflow-auto p-2"
203
+ @contextmenu="contextMenuTarget = null"
204
+ >
205
+ <div
206
+ v-if="loading"
207
+ class="flex items-center justify-center py-8"
208
+ >
209
+ <UIcon
210
+ name="i-lucide-loader-2"
211
+ class="size-6 animate-spin text-dimmed"
212
+ />
213
+ </div>
214
+
215
+ <div
216
+ v-else-if="filteredItems.length === 0"
217
+ class="h-full min-h-32 flex flex-col items-center justify-center text-dimmed text-sm"
218
+ >
219
+ <UIcon
220
+ name="i-lucide-folder-open"
221
+ class="size-8 mx-auto mb-2 opacity-50"
222
+ />
223
+ <p>No files found</p>
224
+ <p class="text-xs mt-1">
225
+ Right-click to create a file
226
+ </p>
227
+ </div>
228
+
229
+ <UTree
230
+ v-else
231
+ :items="filteredItems"
232
+ :get-key="(item: TreeItem) => item.id"
233
+ color="primary"
234
+ expanded-icon="i-lucide-folder-open"
235
+ collapsed-icon="i-lucide-folder"
236
+ @select="(_e: unknown, item: TreeItem) => handleSelect(item)"
237
+ >
238
+ <template #item="{ item, expanded }">
239
+ <div
240
+ class="flex items-center gap-2 w-full"
241
+ :class="{
242
+ 'bg-primary/10 rounded': dropTarget?.path === item.path
243
+ }"
244
+ draggable="true"
245
+ @contextmenu="contextMenuTarget = item"
246
+ @dragstart="handleDragStart($event, item)"
247
+ @dragover="handleDragOver($event, item)"
248
+ @dragleave="handleDragLeave"
249
+ @dragend="handleDragEnd"
250
+ @drop="handleDrop($event, item)"
251
+ >
252
+ <UIcon
253
+ v-if="item.children?.length"
254
+ :name="expanded ? 'i-lucide-folder-open' : 'i-lucide-folder'"
255
+ class="size-4 shrink-0 text-dimmed"
256
+ />
257
+ <UIcon
258
+ v-else-if="item.icon"
259
+ :name="item.icon"
260
+ class="size-4 shrink-0 text-dimmed"
261
+ />
262
+ <UIcon
263
+ v-else
264
+ name="i-lucide-file"
265
+ class="size-4 shrink-0 text-dimmed"
266
+ />
267
+ <span class="truncate">{{ item.label }}</span>
268
+ </div>
269
+ </template>
270
+ </UTree>
271
+ </UContextMenu>
272
+
273
+ <!-- New File Modal -->
274
+ <UModal v-model:open="showNewFileModal">
275
+ <template #content>
276
+ <UCard>
277
+ <template #header>
278
+ <div class="flex items-center gap-2">
279
+ <UIcon
280
+ name="i-lucide-file-plus"
281
+ class="size-5"
282
+ />
283
+ <span class="font-semibold">New File</span>
284
+ </div>
285
+ </template>
286
+
287
+ <UInput
288
+ v-model="newItemName"
289
+ placeholder="filename.md"
290
+ autofocus
291
+ @keyup.enter="handleCreateFile"
292
+ />
293
+
294
+ <template #footer>
295
+ <div class="flex justify-end gap-2">
296
+ <UButton
297
+ color="neutral"
298
+ variant="ghost"
299
+ @click="showNewFileModal = false"
300
+ >
301
+ Cancel
302
+ </UButton>
303
+ <UButton @click="handleCreateFile">
304
+ Create
305
+ </UButton>
306
+ </div>
307
+ </template>
308
+ </UCard>
309
+ </template>
310
+ </UModal>
311
+
312
+ <!-- New Folder Modal -->
313
+ <UModal v-model:open="showNewFolderModal">
314
+ <template #content>
315
+ <UCard>
316
+ <template #header>
317
+ <div class="flex items-center gap-2">
318
+ <UIcon
319
+ name="i-lucide-folder-plus"
320
+ class="size-5"
321
+ />
322
+ <span class="font-semibold">New Folder</span>
323
+ </div>
324
+ </template>
325
+
326
+ <UInput
327
+ v-model="newItemName"
328
+ placeholder="folder-name"
329
+ autofocus
330
+ @keyup.enter="handleCreateFolder"
331
+ />
332
+
333
+ <template #footer>
334
+ <div class="flex justify-end gap-2">
335
+ <UButton
336
+ color="neutral"
337
+ variant="ghost"
338
+ @click="showNewFolderModal = false"
339
+ >
340
+ Cancel
341
+ </UButton>
342
+ <UButton @click="handleCreateFolder">
343
+ Create
344
+ </UButton>
345
+ </div>
346
+ </template>
347
+ </UCard>
348
+ </template>
349
+ </UModal>
350
+
351
+ <!-- Rename Modal -->
352
+ <UModal v-model:open="showRenameModal">
353
+ <template #content>
354
+ <UCard>
355
+ <template #header>
356
+ <div class="flex items-center gap-2">
357
+ <UIcon
358
+ name="i-lucide-pencil"
359
+ class="size-5"
360
+ />
361
+ <span class="font-semibold">Rename</span>
362
+ </div>
363
+ </template>
364
+
365
+ <UInput
366
+ v-model="newItemName"
367
+ placeholder="new-name"
368
+ autofocus
369
+ @keyup.enter="handleRename"
370
+ />
371
+
372
+ <template #footer>
373
+ <div class="flex justify-end gap-2">
374
+ <UButton
375
+ color="neutral"
376
+ variant="ghost"
377
+ @click="showRenameModal = false"
378
+ >
379
+ Cancel
380
+ </UButton>
381
+ <UButton @click="handleRename">
382
+ Rename
383
+ </UButton>
384
+ </div>
385
+ </template>
386
+ </UCard>
387
+ </template>
388
+ </UModal>
389
+
390
+ <!-- Delete Confirmation -->
391
+ <UModal v-model:open="showDeleteConfirm">
392
+ <template #content>
393
+ <UCard>
394
+ <template #header>
395
+ <div class="flex items-center gap-2 text-error">
396
+ <UIcon
397
+ name="i-lucide-trash-2"
398
+ class="size-5"
399
+ />
400
+ <span class="font-semibold">Delete</span>
401
+ </div>
402
+ </template>
403
+
404
+ <p>
405
+ Are you sure you want to delete
406
+ <strong>{{ contextMenuTarget?.label }}</strong>?
407
+ </p>
408
+ <p
409
+ v-if="contextMenuTarget?.type === 'directory'"
410
+ class="text-sm text-dimmed mt-1"
411
+ >
412
+ This will delete all files and folders inside.
413
+ </p>
414
+
415
+ <template #footer>
416
+ <div class="flex justify-end gap-2">
417
+ <UButton
418
+ color="neutral"
419
+ variant="ghost"
420
+ @click="showDeleteConfirm = false"
421
+ >
422
+ Cancel
423
+ </UButton>
424
+ <UButton
425
+ color="error"
426
+ @click="handleDelete"
427
+ >
428
+ Delete
429
+ </UButton>
430
+ </div>
431
+ </template>
432
+ </UCard>
433
+ </template>
434
+ </UModal>
435
+ </div>
436
+ </template>
@@ -0,0 +1,117 @@
1
+ <script setup lang="ts">
2
+ import { format } from 'date-fns'
3
+ import { useElementSize } from '@vueuse/core'
4
+ import { VisXYContainer, VisStackedBar, VisAxis, VisCrosshair, VisTooltip } from '@unovis/vue'
5
+ import type { HookDailyData } from '~~/shared/types'
6
+
7
+ const props = defineProps<{
8
+ data: HookDailyData[]
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 = (_: HookDailyData, i: number) => i
18
+
19
+ // Y accessors for stacked bar - blocked and allowed
20
+ const y = [
21
+ (d: HookDailyData) => d.allowed,
22
+ (d: HookDailyData) => d.blocked
23
+ ]
24
+
25
+ // Colors for the stacked bars
26
+ const colors = ['var(--ui-success)', 'var(--ui-error)']
27
+
28
+ // Format X axis ticks
29
+ const xTicks = (i: number) => {
30
+ const item = props.data[i]
31
+ if (!item) return ''
32
+ return format(new Date(item.date), 'd MMM')
33
+ }
34
+
35
+ // Tooltip template
36
+ const template = (d: HookDailyData) => {
37
+ return `<div class="p-2 text-sm">
38
+ <div class="font-medium">${format(new Date(d.date), 'MMM d, yyyy')}</div>
39
+ <div class="text-success">Allowed: ${d.allowed}</div>
40
+ <div class="text-error">Blocked: ${d.blocked}</div>
41
+ <div class="text-muted">Total: ${d.total}</div>
42
+ </div>`
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <UCard
48
+ ref="cardRef"
49
+ :ui="{ root: 'overflow-visible', body: '!px-0 !pt-0 !pb-3' }"
50
+ >
51
+ <template
52
+ v-if="title"
53
+ #header
54
+ >
55
+ <div class="flex items-center justify-between">
56
+ <p class="text-sm font-medium">
57
+ {{ title }}
58
+ </p>
59
+ <div class="flex items-center gap-4 text-xs">
60
+ <span class="flex items-center gap-1">
61
+ <span class="w-3 h-3 rounded bg-success" />
62
+ Allowed
63
+ </span>
64
+ <span class="flex items-center gap-1">
65
+ <span class="w-3 h-3 rounded bg-error" />
66
+ Blocked
67
+ </span>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <div
73
+ v-if="data.length === 0"
74
+ class="flex items-center justify-center h-48 text-muted"
75
+ >
76
+ No activity data
77
+ </div>
78
+
79
+ <VisXYContainer
80
+ v-else
81
+ :data="data"
82
+ :padding="{ top: 10, left: 10, right: 10, bottom: 10 }"
83
+ :class="height || 'h-48'"
84
+ :width="width"
85
+ >
86
+ <VisStackedBar
87
+ :x="x"
88
+ :y="y"
89
+ :color="colors"
90
+ :bar-padding="0.2"
91
+ />
92
+ <VisAxis
93
+ type="x"
94
+ :x="x"
95
+ :tick-format="xTicks"
96
+ />
97
+ <VisCrosshair
98
+ color="var(--ui-primary)"
99
+ :template="template"
100
+ />
101
+ <VisTooltip />
102
+ </VisXYContainer>
103
+ </UCard>
104
+ </template>
105
+
106
+ <style scoped>
107
+ .unovis-xy-container {
108
+ --vis-crosshair-line-stroke-color: var(--ui-primary);
109
+ --vis-crosshair-circle-stroke-color: var(--ui-bg);
110
+ --vis-axis-grid-color: var(--ui-border);
111
+ --vis-axis-tick-color: var(--ui-border);
112
+ --vis-axis-tick-label-color: var(--ui-text-dimmed);
113
+ --vis-tooltip-background-color: var(--ui-bg);
114
+ --vis-tooltip-border-color: var(--ui-border);
115
+ --vis-tooltip-text-color: var(--ui-text-highlighted);
116
+ }
117
+ </style>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import type { HookDailyData } from '~~/shared/types'
3
+
4
+ defineProps<{
5
+ data: HookDailyData[]
6
+ title?: string
7
+ height?: string
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <UCard>
13
+ <template
14
+ v-if="title"
15
+ #header
16
+ >
17
+ <p class="text-sm font-medium">
18
+ {{ title }}
19
+ </p>
20
+ </template>
21
+ <div class="flex items-center justify-center h-48 text-muted">
22
+ <USkeleton class="w-full h-full" />
23
+ </div>
24
+ </UCard>
25
+ </template>
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import type { HookEventStats } from '~~/shared/types'
3
+
4
+ const props = defineProps<{
5
+ stats: HookEventStats | null
6
+ loading?: boolean
7
+ }>()
8
+
9
+ function formatDuration(ms: number): string {
10
+ if (ms < 1000) return `${ms}ms`
11
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
12
+ return `${(ms / 60000).toFixed(1)}m`
13
+ }
14
+
15
+ const displayStats = computed(() => {
16
+ if (!props.stats) return []
17
+ const s = props.stats
18
+ return [
19
+ { title: 'Total Events', icon: 'i-lucide-activity', value: s.totalEvents },
20
+ { title: 'Blocked', icon: 'i-lucide-shield-alert', value: s.blockedEvents, color: 'text-error' },
21
+ { title: 'Block Rate', icon: 'i-lucide-percent', value: `${s.blockRate}%` },
22
+ { title: 'Avg Duration', icon: 'i-lucide-clock', value: formatDuration(s.avgDurationMs) },
23
+ { title: 'Sessions', icon: 'i-lucide-users', value: s.recentSessions.length }
24
+ ]
25
+ })
26
+ </script>
27
+
28
+ <template>
29
+ <div class="grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5">
30
+ <template v-if="loading">
31
+ <div
32
+ v-for="i in 5"
33
+ :key="i"
34
+ class="p-4 rounded-lg bg-elevated border border-default"
35
+ >
36
+ <USkeleton class="h-4 w-16 mb-2" />
37
+ <USkeleton class="h-8 w-12" />
38
+ </div>
39
+ </template>
40
+
41
+ <template v-else-if="stats">
42
+ <div
43
+ v-for="stat in displayStats"
44
+ :key="stat.title"
45
+ class="p-4 rounded-lg bg-elevated border border-default"
46
+ >
47
+ <div class="flex items-center gap-2 text-muted text-sm mb-1">
48
+ <UIcon
49
+ :name="stat.icon"
50
+ class="size-4"
51
+ />
52
+ <span>{{ stat.title }}</span>
53
+ </div>
54
+ <p
55
+ class="text-2xl font-semibold"
56
+ :class="stat.color"
57
+ >
58
+ {{ stat.value }}
59
+ </p>
60
+ </div>
61
+ </template>
62
+ </div>
63
+ </template>