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,99 @@
1
+ import { readdir, stat } from 'fs/promises'
2
+ import { join } from 'path'
3
+ import type { FileEntry } from '~/../../shared/types'
4
+
5
+ export default defineEventHandler(async (event) => {
6
+ const query = getQuery(event)
7
+ const requestedPath = (query.path as string) || '/'
8
+ const recursive = query.recursive === 'true'
9
+
10
+ const absolutePath = validatePath(requestedPath)
11
+
12
+ try {
13
+ const entries = await readdir(absolutePath, { withFileTypes: true })
14
+ const result: FileEntry[] = []
15
+
16
+ for (const entry of entries) {
17
+ // Skip hidden files/folders (starting with .)
18
+ if (entry.name.startsWith('.')) continue
19
+
20
+ const entryPath = join(absolutePath, entry.name)
21
+ const stats = await stat(entryPath)
22
+
23
+ const fileEntry: FileEntry = {
24
+ name: entry.name,
25
+ path: toRelativePath(entryPath),
26
+ type: entry.isDirectory() ? 'directory' : 'file',
27
+ modifiedAt: stats.mtime
28
+ }
29
+
30
+ if (entry.isFile()) {
31
+ fileEntry.size = stats.size
32
+ }
33
+
34
+ if (entry.isDirectory() && recursive) {
35
+ // Recursively get children
36
+ const children = await getDirectoryContents(entryPath)
37
+ fileEntry.children = children
38
+ }
39
+
40
+ result.push(fileEntry)
41
+ }
42
+
43
+ // Sort: directories first, then alphabetically
44
+ result.sort((a, b) => {
45
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
46
+ return a.name.localeCompare(b.name)
47
+ })
48
+
49
+ return { data: result }
50
+ } catch (error: unknown) {
51
+ const err = error as NodeJS.ErrnoException
52
+ if (err.code === 'ENOENT') {
53
+ throw createError({
54
+ statusCode: 404,
55
+ message: 'Directory not found'
56
+ })
57
+ }
58
+ throw createError({
59
+ statusCode: 500,
60
+ message: 'Failed to list directory'
61
+ })
62
+ }
63
+ })
64
+
65
+ async function getDirectoryContents(dirPath: string): Promise<FileEntry[]> {
66
+ const entries = await readdir(dirPath, { withFileTypes: true })
67
+ const result: FileEntry[] = []
68
+
69
+ for (const entry of entries) {
70
+ if (entry.name.startsWith('.')) continue
71
+
72
+ const entryPath = join(dirPath, entry.name)
73
+ const stats = await stat(entryPath)
74
+
75
+ const fileEntry: FileEntry = {
76
+ name: entry.name,
77
+ path: toRelativePath(entryPath),
78
+ type: entry.isDirectory() ? 'directory' : 'file',
79
+ modifiedAt: stats.mtime
80
+ }
81
+
82
+ if (entry.isFile()) {
83
+ fileEntry.size = stats.size
84
+ }
85
+
86
+ if (entry.isDirectory()) {
87
+ fileEntry.children = await getDirectoryContents(entryPath)
88
+ }
89
+
90
+ result.push(fileEntry)
91
+ }
92
+
93
+ result.sort((a, b) => {
94
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
95
+ return a.name.localeCompare(b.name)
96
+ })
97
+
98
+ return result
99
+ }
@@ -0,0 +1,44 @@
1
+ import { mkdir, stat } from 'fs/promises'
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ const body = await readBody(event)
5
+ const requestedPath = body.path
6
+
7
+ if (!requestedPath) {
8
+ throw createError({
9
+ statusCode: 400,
10
+ message: 'Path is required'
11
+ })
12
+ }
13
+
14
+ const absolutePath = validatePath(requestedPath)
15
+
16
+ // Check if path already exists
17
+ try {
18
+ await stat(absolutePath)
19
+ throw createError({
20
+ statusCode: 409,
21
+ message: 'Path already exists'
22
+ })
23
+ } catch (error: unknown) {
24
+ const err = error as NodeJS.ErrnoException & { statusCode?: number }
25
+ if (err.statusCode === 409) throw error
26
+ // ENOENT is expected - path doesn't exist yet
27
+ }
28
+
29
+ try {
30
+ await mkdir(absolutePath, { recursive: true })
31
+
32
+ return {
33
+ data: {
34
+ path: requestedPath,
35
+ created: true
36
+ }
37
+ }
38
+ } catch {
39
+ throw createError({
40
+ statusCode: 500,
41
+ message: 'Failed to create directory'
42
+ })
43
+ }
44
+ })
@@ -0,0 +1,68 @@
1
+ import { rename, stat } from 'fs/promises'
2
+ import { join, basename } from 'path'
3
+
4
+ export default defineEventHandler(async (event) => {
5
+ const body = await readBody(event)
6
+ const { sourcePath, destinationPath } = body
7
+
8
+ if (!sourcePath || !destinationPath) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ message: 'sourcePath and destinationPath are required'
12
+ })
13
+ }
14
+
15
+ const absoluteSource = validatePath(sourcePath)
16
+ const absoluteDestination = validatePath(destinationPath)
17
+
18
+ // Check if source exists
19
+ try {
20
+ await stat(absoluteSource)
21
+ } catch {
22
+ throw createError({
23
+ statusCode: 404,
24
+ message: 'Source path not found'
25
+ })
26
+ }
27
+
28
+ // Check if destination is a directory
29
+ let targetPath: string
30
+ try {
31
+ const destStats = await stat(absoluteDestination)
32
+ if (destStats.isDirectory()) {
33
+ // Moving into a directory - append the source filename
34
+ targetPath = join(absoluteDestination, basename(absoluteSource))
35
+ } else {
36
+ throw createError({
37
+ statusCode: 400,
38
+ message: 'Destination must be a directory'
39
+ })
40
+ }
41
+ } catch (error: unknown) {
42
+ const err = error as NodeJS.ErrnoException
43
+ if (err.code === 'ENOENT') {
44
+ throw createError({
45
+ statusCode: 404,
46
+ message: 'Destination directory not found'
47
+ })
48
+ }
49
+ throw error
50
+ }
51
+
52
+ try {
53
+ await rename(absoluteSource, targetPath)
54
+
55
+ return {
56
+ data: {
57
+ oldPath: sourcePath,
58
+ newPath: toRelativePath(targetPath),
59
+ moved: true
60
+ }
61
+ }
62
+ } catch {
63
+ throw createError({
64
+ statusCode: 500,
65
+ message: 'Failed to move file'
66
+ })
67
+ }
68
+ })
@@ -0,0 +1,48 @@
1
+ import { readFile, stat } from 'fs/promises'
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ const body = await readBody(event)
5
+ const requestedPath = body.path
6
+
7
+ if (!requestedPath) {
8
+ throw createError({
9
+ statusCode: 400,
10
+ message: 'Path is required'
11
+ })
12
+ }
13
+
14
+ const absolutePath = validatePath(requestedPath)
15
+
16
+ try {
17
+ const [content, stats] = await Promise.all([
18
+ readFile(absolutePath, 'utf-8'),
19
+ stat(absolutePath)
20
+ ])
21
+
22
+ return {
23
+ data: {
24
+ path: requestedPath,
25
+ content,
26
+ modifiedAt: stats.mtime
27
+ }
28
+ }
29
+ } catch (error: unknown) {
30
+ const err = error as NodeJS.ErrnoException
31
+ if (err.code === 'ENOENT') {
32
+ throw createError({
33
+ statusCode: 404,
34
+ message: 'File not found'
35
+ })
36
+ }
37
+ if (err.code === 'EISDIR') {
38
+ throw createError({
39
+ statusCode: 400,
40
+ message: 'Path is a directory, not a file'
41
+ })
42
+ }
43
+ throw createError({
44
+ statusCode: 500,
45
+ message: 'Failed to read file'
46
+ })
47
+ }
48
+ })
@@ -0,0 +1,55 @@
1
+ import { rename, stat } from 'fs/promises'
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ const body = await readBody(event)
5
+ const { oldPath, newPath } = body
6
+
7
+ if (!oldPath || !newPath) {
8
+ throw createError({
9
+ statusCode: 400,
10
+ message: 'Both oldPath and newPath are required'
11
+ })
12
+ }
13
+
14
+ const absoluteOldPath = validatePath(oldPath)
15
+ const absoluteNewPath = validatePath(newPath)
16
+
17
+ // Check if source exists
18
+ try {
19
+ await stat(absoluteOldPath)
20
+ } catch {
21
+ throw createError({
22
+ statusCode: 404,
23
+ message: 'Source path not found'
24
+ })
25
+ }
26
+
27
+ // Check if destination already exists
28
+ try {
29
+ await stat(absoluteNewPath)
30
+ throw createError({
31
+ statusCode: 409,
32
+ message: 'Destination path already exists'
33
+ })
34
+ } catch (error: unknown) {
35
+ const err = error as { statusCode?: number }
36
+ if (err.statusCode === 409) throw error
37
+ // ENOENT is expected - destination doesn't exist
38
+ }
39
+
40
+ try {
41
+ await rename(absoluteOldPath, absoluteNewPath)
42
+
43
+ return {
44
+ data: {
45
+ oldPath,
46
+ newPath
47
+ }
48
+ }
49
+ } catch {
50
+ throw createError({
51
+ statusCode: 500,
52
+ message: 'Failed to rename/move'
53
+ })
54
+ }
55
+ })
@@ -0,0 +1,51 @@
1
+ import { writeFile, stat, mkdir } from 'fs/promises'
2
+ import { dirname } from 'path'
3
+
4
+ export default defineEventHandler(async (event) => {
5
+ const body = await readBody(event)
6
+ const { path: requestedPath, content } = body
7
+
8
+ if (!requestedPath) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ message: 'Path is required'
12
+ })
13
+ }
14
+
15
+ if (content === undefined) {
16
+ throw createError({
17
+ statusCode: 400,
18
+ message: 'Content is required'
19
+ })
20
+ }
21
+
22
+ const absolutePath = validatePath(requestedPath)
23
+
24
+ // Check if file exists
25
+ let created = false
26
+ try {
27
+ await stat(absolutePath)
28
+ } catch {
29
+ created = true
30
+ }
31
+
32
+ try {
33
+ // Ensure parent directory exists
34
+ await mkdir(dirname(absolutePath), { recursive: true })
35
+
36
+ // Write the file
37
+ await writeFile(absolutePath, content, 'utf-8')
38
+
39
+ return {
40
+ data: {
41
+ path: requestedPath,
42
+ created
43
+ }
44
+ }
45
+ } catch {
46
+ throw createError({
47
+ statusCode: 500,
48
+ message: 'Failed to write file'
49
+ })
50
+ }
51
+ })
@@ -0,0 +1,40 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import { getDbState, isDbAvailable } from '~~/server/utils/db-state'
3
+ import { getDb } from '~~/server/db'
4
+
5
+ function getDeploymentType(url: string): 'local' | 'remote' | null {
6
+ if (!url) return null
7
+ const isLocal = url.includes('localhost') || url.includes('127.0.0.1') || url.includes('db:5432')
8
+ return isLocal ? 'local' : 'remote'
9
+ }
10
+
11
+ export default defineEventHandler(async (event) => {
12
+ const config = useRuntimeConfig(event)
13
+ const dbState = getDbState()
14
+
15
+ let dbLatency: number | null = null
16
+ let dbError: string | null = null
17
+
18
+ if (isDbAvailable()) {
19
+ try {
20
+ const start = Date.now()
21
+ const db = getDb()
22
+ await db.execute(sql`SELECT 1`)
23
+ dbLatency = Date.now() - start
24
+ } catch (e) {
25
+ dbError = e instanceof Error ? e.message : 'Unknown error'
26
+ }
27
+ }
28
+
29
+ return {
30
+ status: 'ok',
31
+ timestamp: new Date().toISOString(),
32
+ database: {
33
+ configured: !!config.databaseUrl,
34
+ deployment: getDeploymentType(config.databaseUrl),
35
+ available: dbState.available,
36
+ latency: dbLatency,
37
+ error: dbError || dbState.error || null
38
+ }
39
+ }
40
+ })
@@ -0,0 +1,26 @@
1
+ import { readFile } from 'fs/promises'
2
+ import { join } from 'path'
3
+ import { getVaultRoot } from '~~/server/utils/path-validator'
4
+
5
+ export default defineEventHandler(async () => {
6
+ const vaultRoot = getVaultRoot()
7
+ const indexPath = join(vaultRoot, 'index.md')
8
+
9
+ try {
10
+ const content = await readFile(indexPath, 'utf-8')
11
+ return {
12
+ data: {
13
+ hasCustomHome: true,
14
+ content
15
+ }
16
+ }
17
+ } catch {
18
+ // index.md doesn't exist, return default
19
+ return {
20
+ data: {
21
+ hasCustomHome: false,
22
+ content: null
23
+ }
24
+ }
25
+ }
26
+ })
@@ -0,0 +1,56 @@
1
+ import { eq, gte, inArray, and, desc } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import type { HookEventFilters, HookEventType } from '~~/shared/types'
5
+
6
+ export default defineEventHandler(async (event) => {
7
+ requireDb(event)
8
+
9
+ const query = getQuery(event) as HookEventFilters
10
+ const limit = Math.min(Number(query.limit) || 100, 500)
11
+
12
+ const db = getDb()
13
+ const conditions = []
14
+
15
+ // Event type filter
16
+ if (query.eventType) {
17
+ const types = Array.isArray(query.eventType) ? query.eventType : [query.eventType]
18
+ conditions.push(inArray(schema.hookEvents.eventType, types as HookEventType[]))
19
+ }
20
+
21
+ // Session filter
22
+ if (query.sessionId)
23
+ conditions.push(eq(schema.hookEvents.sessionId, query.sessionId))
24
+
25
+ // Tool filter
26
+ if (query.toolName)
27
+ conditions.push(eq(schema.hookEvents.toolName, query.toolName))
28
+
29
+ // Blocked filter
30
+ if (query.blocked !== undefined) {
31
+ const blocked = query.blocked === true || query.blocked === 'true'
32
+ conditions.push(eq(schema.hookEvents.blocked, blocked))
33
+ }
34
+
35
+ // Since filter
36
+ if (query.since)
37
+ conditions.push(gte(schema.hookEvents.createdAt, new Date(query.since)))
38
+
39
+ let dbQuery = db.select()
40
+ .from(schema.hookEvents)
41
+
42
+ if (conditions.length > 0)
43
+ dbQuery = dbQuery.where(and(...conditions)) as typeof dbQuery
44
+
45
+ const events = await dbQuery
46
+ .orderBy(desc(schema.hookEvents.createdAt))
47
+ .limit(limit)
48
+
49
+ // Parse eventData JSON
50
+ const parsedEvents = events.map(e => ({
51
+ ...e,
52
+ eventData: e.eventData ? JSON.parse(e.eventData) : undefined
53
+ }))
54
+
55
+ return { data: parsedEvents }
56
+ })
@@ -0,0 +1,36 @@
1
+ import { getDb, schema } from '~~/server/db'
2
+ import { requireDb } from '~~/server/utils/db-guard'
3
+ import type { CreateHookEventInput } from '~~/shared/types'
4
+
5
+ export default defineEventHandler(async (event) => {
6
+ requireDb(event)
7
+
8
+ const body = await readBody<CreateHookEventInput>(event)
9
+
10
+ if (!body.eventType)
11
+ throw createError({ statusCode: 400, message: 'eventType is required' })
12
+
13
+ const db = getDb()
14
+
15
+ const [hookEvent] = await db
16
+ .insert(schema.hookEvents)
17
+ .values({
18
+ eventType: body.eventType,
19
+ sessionId: body.sessionId || null,
20
+ projectDir: body.projectDir || null,
21
+ toolName: body.toolName || null,
22
+ toolMatcher: body.toolMatcher || null,
23
+ eventData: body.eventData ? JSON.stringify(body.eventData) : null,
24
+ exitCode: body.exitCode ?? null,
25
+ blocked: body.blocked ?? false,
26
+ blockReason: body.blockReason || null,
27
+ durationMs: body.durationMs ?? null,
28
+ hookScript: body.hookScript || null
29
+ })
30
+ .returning()
31
+
32
+ if (!hookEvent)
33
+ throw createError({ statusCode: 500, message: 'Failed to create hook event' })
34
+
35
+ return { data: hookEvent }
36
+ })
@@ -0,0 +1,99 @@
1
+ import { gte } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import type { StatsPeriod, HookEventStats, HookDailyData, HookToolBreakdown, HookEventType } from '~~/shared/types'
5
+
6
+ function getPeriodInterval(period: StatsPeriod): Date {
7
+ const now = new Date()
8
+ switch (period) {
9
+ case '24h':
10
+ return new Date(now.getTime() - 24 * 60 * 60 * 1000)
11
+ case '7d':
12
+ return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
13
+ case '30d':
14
+ return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
15
+ }
16
+ }
17
+
18
+ export default defineEventHandler(async (event) => {
19
+ requireDb(event)
20
+
21
+ const query = getQuery(event)
22
+ const period = (query.period as StatsPeriod) || '7d'
23
+ const periodStart = getPeriodInterval(period)
24
+
25
+ const db = getDb()
26
+
27
+ // Get all events in period
28
+ const events = await db.select()
29
+ .from(schema.hookEvents)
30
+ .where(gte(schema.hookEvents.createdAt, periodStart))
31
+
32
+ const totalEvents = events.length
33
+ const blockedEvents = events.filter(e => e.blocked).length
34
+ const blockRate = totalEvents > 0 ? Math.round((blockedEvents / totalEvents) * 100) : 0
35
+
36
+ // Average duration
37
+ const withDuration = events.filter(e => e.durationMs !== null)
38
+ const avgDurationMs = withDuration.length > 0
39
+ ? Math.round(withDuration.reduce((sum, e) => sum + (e.durationMs || 0), 0) / withDuration.length)
40
+ : 0
41
+
42
+ // Events by type
43
+ const eventsByType: Partial<Record<HookEventType, number>> = {}
44
+ for (const e of events)
45
+ eventsByType[e.eventType as HookEventType] = (eventsByType[e.eventType as HookEventType] || 0) + 1
46
+
47
+ // Tool breakdown
48
+ const toolMap = new Map<string, { total: number, blocked: number, totalDuration: number, count: number }>()
49
+ for (const e of events) {
50
+ if (!e.toolName) continue
51
+ const existing = toolMap.get(e.toolName) || { total: 0, blocked: 0, totalDuration: 0, count: 0 }
52
+ existing.total++
53
+ if (e.blocked) existing.blocked++
54
+ if (e.durationMs) {
55
+ existing.totalDuration += e.durationMs
56
+ existing.count++
57
+ }
58
+ toolMap.set(e.toolName, existing)
59
+ }
60
+
61
+ const toolBreakdown: HookToolBreakdown[] = Array.from(toolMap.entries())
62
+ .map(([toolName, data]) => ({
63
+ toolName,
64
+ total: data.total,
65
+ blocked: data.blocked,
66
+ avgDurationMs: data.count > 0 ? Math.round(data.totalDuration / data.count) : 0
67
+ }))
68
+ .sort((a, b) => b.total - a.total)
69
+
70
+ // Daily activity
71
+ const dailyMap = new Map<string, HookDailyData>()
72
+ for (const e of events) {
73
+ const date = e.createdAt.toISOString().split('T')[0]!
74
+ const existing = dailyMap.get(date) || { date, total: 0, blocked: 0, allowed: 0, avgDurationMs: 0 }
75
+ existing.total++
76
+ if (e.blocked) existing.blocked++
77
+ else existing.allowed++
78
+ dailyMap.set(date, existing)
79
+ }
80
+
81
+ const dailyActivity = Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date))
82
+
83
+ // Recent sessions (unique, last 10)
84
+ const recentSessions = [...new Set(events.filter(e => e.sessionId).map(e => e.sessionId!))]
85
+ .slice(0, 10)
86
+
87
+ const stats: HookEventStats = {
88
+ totalEvents,
89
+ blockedEvents,
90
+ blockRate,
91
+ avgDurationMs,
92
+ eventsByType,
93
+ toolBreakdown,
94
+ dailyActivity,
95
+ recentSessions
96
+ }
97
+
98
+ return { data: stats }
99
+ })
@@ -0,0 +1,26 @@
1
+ import { eq } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+
5
+ export default defineEventHandler(async (event) => {
6
+ requireDb(event)
7
+
8
+ const id = getRouterParam(event, 'id')
9
+ if (!id)
10
+ throw createError({ statusCode: 400, message: 'Memory ID required' })
11
+
12
+ const db = getDb()
13
+
14
+ // Verify memory exists
15
+ const existing = await db.select({ id: schema.memoryChunks.id })
16
+ .from(schema.memoryChunks)
17
+ .where(eq(schema.memoryChunks.id, id))
18
+ .limit(1)
19
+
20
+ if (existing.length === 0)
21
+ throw createError({ statusCode: 404, message: 'Memory not found' })
22
+
23
+ await db.delete(schema.memoryChunks).where(eq(schema.memoryChunks.id, id))
24
+
25
+ return { data: { success: true } }
26
+ })