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,10 @@
1
+ import { defineConfig } from 'drizzle-kit'
2
+
3
+ export default defineConfig({
4
+ schema: './server/db/schema.ts',
5
+ out: './server/drizzle/migrations',
6
+ dialect: 'postgresql',
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL!
9
+ }
10
+ })
package/nuxt.config.ts ADDED
@@ -0,0 +1,98 @@
1
+ // https://nuxt.com/docs/api/configuration/nuxt-config
2
+ export default defineNuxtConfig({
3
+ modules: [
4
+ '@nuxt/eslint',
5
+ '@nuxt/ui',
6
+ '@nuxtjs/mdc'
7
+ ],
8
+
9
+ devtools: {
10
+ enabled: true
11
+ },
12
+
13
+ css: ['~/assets/css/main.css'],
14
+
15
+ mdc: {
16
+ highlight: {
17
+ theme: {
18
+ default: 'github-light',
19
+ dark: 'github-dark'
20
+ },
21
+ langs: [
22
+ 'typescript',
23
+ 'javascript',
24
+ 'ts',
25
+ 'js',
26
+ 'bash',
27
+ 'shell',
28
+ 'vue',
29
+ 'html',
30
+ 'css',
31
+ 'scss',
32
+ 'json',
33
+ 'yaml',
34
+ 'toml',
35
+ 'xml',
36
+ 'markdown',
37
+ 'md',
38
+ 'sql',
39
+ 'graphql',
40
+ 'python',
41
+ 'go',
42
+ 'rust',
43
+ 'c',
44
+ 'cpp',
45
+ 'cs',
46
+ 'java',
47
+ 'dockerfile',
48
+ 'diff',
49
+ 'kotlin'
50
+ ]
51
+ }
52
+ },
53
+
54
+ runtimeConfig: {
55
+ databaseUrl: process.env.DATABASE_URL || ''
56
+ },
57
+
58
+ alias: {
59
+ '@shared': '~/../../shared'
60
+ },
61
+
62
+ routeRules: {},
63
+
64
+ compatibilityDate: '2025-01-15',
65
+
66
+ // Enable WebSocket support for terminal
67
+ nitro: {
68
+ experimental: {
69
+ websocket: true
70
+ },
71
+ // node-pty is a native module that can't be bundled
72
+ externals: {
73
+ external: ['node-pty']
74
+ }
75
+ },
76
+
77
+ // Required for UEditor to avoid prosemirror plugin conflicts
78
+ vite: {
79
+ optimizeDeps: {
80
+ include: [
81
+ '@nuxt/ui > prosemirror-state',
82
+ '@nuxt/ui > prosemirror-transform',
83
+ '@nuxt/ui > prosemirror-model',
84
+ '@nuxt/ui > prosemirror-view',
85
+ '@nuxt/ui > prosemirror-gapcursor'
86
+ ]
87
+ }
88
+ },
89
+
90
+ eslint: {
91
+ config: {
92
+ stylistic: {
93
+ commaDangle: 'never',
94
+ braceStyle: '1tbs'
95
+ }
96
+ }
97
+ }
98
+ })
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "cognova",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Personal knowledge management system with Claude Code integration",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Patrity/cognova.git"
9
+ },
10
+ "bin": {
11
+ "cognova": "./dist/cli/index.js"
12
+ },
13
+ "files": [
14
+ "dist/cli",
15
+ "app",
16
+ "shared",
17
+ "server",
18
+ "Claude",
19
+ "nuxt.config.ts",
20
+ "tsconfig.json",
21
+ "drizzle.config.ts",
22
+ ".env.example"
23
+ ],
24
+ "scripts": {
25
+ "cli:build": "pnpm --filter cognova-cli build",
26
+ "cli:dev": "pnpm --filter cognova-cli dev",
27
+ "build": "nuxt build",
28
+ "dev": "nuxt dev",
29
+ "preview": "nuxt preview",
30
+ "prepare": "nuxt prepare",
31
+ "lint": "eslint .",
32
+ "typecheck": "nuxt typecheck",
33
+ "db:generate": "drizzle-kit generate",
34
+ "db:migrate": "drizzle-kit migrate",
35
+ "db:push": "drizzle-kit push",
36
+ "db:studio": "drizzle-kit studio",
37
+ "db:up": "docker compose --profile local up -d db",
38
+ "db:down": "docker compose --profile local down",
39
+ "docker:up": "./scripts/docker-up.sh -d",
40
+ "docker:down": "docker compose --profile local down",
41
+ "auth:create-admin": "tsx scripts/create-admin.ts",
42
+ "auth:reset-pw": "tsx scripts/reset-password.ts"
43
+ },
44
+ "dependencies": {
45
+ "@anthropic-ai/claude-agent-sdk": "^0.2.23",
46
+ "@anthropic-ai/sdk": "^0.72.1",
47
+ "@codemirror/lang-cpp": "^6.0.3",
48
+ "@codemirror/lang-css": "^6.3.1",
49
+ "@codemirror/lang-html": "^6.4.11",
50
+ "@codemirror/lang-java": "^6.0.2",
51
+ "@codemirror/lang-javascript": "^6.2.4",
52
+ "@codemirror/lang-json": "^6.0.2",
53
+ "@codemirror/lang-markdown": "^6.5.0",
54
+ "@codemirror/lang-python": "^6.2.1",
55
+ "@codemirror/lang-rust": "^6.0.2",
56
+ "@codemirror/lang-sql": "^6.10.0",
57
+ "@codemirror/lang-vue": "^0.1.3",
58
+ "@codemirror/lang-xml": "^6.1.0",
59
+ "@codemirror/lang-yaml": "^6.1.2",
60
+ "@codemirror/language": "^6.12.1",
61
+ "@codemirror/legacy-modes": "^6.5.2",
62
+ "@codemirror/state": "^6.5.4",
63
+ "@codemirror/theme-one-dark": "^6.1.3",
64
+ "@codemirror/view": "^6.39.11",
65
+ "@iconify-json/lucide": "^1.2.82",
66
+ "@iconify-json/simple-icons": "^1.2.63",
67
+ "@nuxt/ui": "^4.3.0",
68
+ "@nuxtjs/mdc": "^0.20.0",
69
+ "@unovis/ts": "^1.6.2",
70
+ "@unovis/vue": "^1.6.2",
71
+ "@vueuse/core": "^14.1.0",
72
+ "@vueuse/integrations": "^14.1.0",
73
+ "@xterm/addon-fit": "^0.11.0",
74
+ "@xterm/addon-web-links": "^0.12.0",
75
+ "@xterm/xterm": "^6.0.0",
76
+ "better-auth": "^1.4.17",
77
+ "chokidar": "^5.0.0",
78
+ "codemirror": "^6.0.2",
79
+ "cron": "^4.4.0",
80
+ "date-fns": "^4.1.0",
81
+ "dotenv": "^17.2.3",
82
+ "drizzle-orm": "^0.45.1",
83
+ "gray-matter": "^4.0.3",
84
+ "node-pty": "^1.1.0",
85
+ "nuxt": "^4.2.2",
86
+ "postgres": "^3.4.8",
87
+ "shiki": "^3.21.0",
88
+ "sortablejs": "^1.15.6",
89
+ "tailwindcss": "^4.0.0"
90
+ },
91
+ "devDependencies": {
92
+ "@nuxt/eslint": "^1.12.1",
93
+ "@types/node": "^25.0.8",
94
+ "@types/sortablejs": "^1.15.9",
95
+ "drizzle-kit": "^0.31.8",
96
+ "eslint": "^9.39.2",
97
+ "tsx": "^4.21.0",
98
+ "typescript": "^5.9.3",
99
+ "vue-tsc": "^3.2.1"
100
+ },
101
+ "packageManager": "pnpm@10.26.1",
102
+ "pnpm": {
103
+ "onlyBuiltDependencies": [
104
+ "node-pty"
105
+ ]
106
+ }
107
+ }
@@ -0,0 +1,27 @@
1
+ import { agentRegistry } from '~~/server/utils/agent-registry'
2
+ import { requireDb } from '~~/server/utils/db-guard'
3
+
4
+ export default defineEventHandler(async (event) => {
5
+ requireDb(event)
6
+
7
+ const id = getRouterParam(event, 'id')
8
+ if (!id) {
9
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
10
+ }
11
+
12
+ // Cancel all running executions for this agent
13
+ const cancelledCount = agentRegistry.cancelByAgentId(id)
14
+
15
+ if (cancelledCount === 0) {
16
+ throw createError({
17
+ statusCode: 404,
18
+ message: 'No running executions found for this agent'
19
+ })
20
+ }
21
+
22
+ return {
23
+ success: true,
24
+ message: `Cancelled ${cancelledCount} running execution(s)`,
25
+ cancelledCount
26
+ }
27
+ })
@@ -0,0 +1,34 @@
1
+ import { eq } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import { executeAgent } from '~~/server/services/agent-executor'
5
+
6
+ export default defineEventHandler(async (event) => {
7
+ requireDb(event)
8
+
9
+ const id = getRouterParam(event, 'id')
10
+ if (!id) {
11
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
12
+ }
13
+
14
+ const db = getDb()
15
+
16
+ const agent = await db.query.cronAgents.findFirst({
17
+ where: eq(schema.cronAgents.id, id)
18
+ })
19
+
20
+ if (!agent) {
21
+ throw createError({ statusCode: 404, message: 'Agent not found' })
22
+ }
23
+
24
+ // Execute asynchronously (don't wait for completion)
25
+ executeAgent({
26
+ id: agent.id,
27
+ name: agent.name,
28
+ prompt: agent.prompt,
29
+ maxTurns: agent.maxTurns ?? 50,
30
+ maxBudgetUsd: agent.maxBudgetUsd ?? undefined
31
+ }).catch(err => console.error(`[agent] Manual run failed for ${agent.name}:`, err))
32
+
33
+ return { message: 'Agent started' }
34
+ })
@@ -0,0 +1,45 @@
1
+ import { eq, desc, and, gte } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import type { StatsPeriod } 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 id = getRouterParam(event, 'id')
22
+ if (!id) {
23
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
24
+ }
25
+
26
+ const query = getQuery(event)
27
+ const limit = parseInt(query.limit as string) || 100
28
+ const period = query.period as StatsPeriod | undefined
29
+
30
+ const db = getDb()
31
+
32
+ // Build where clause
33
+ const whereConditions = [eq(schema.cronAgentRuns.agentId, id)]
34
+ if (period) {
35
+ whereConditions.push(gte(schema.cronAgentRuns.startedAt, getPeriodInterval(period)))
36
+ }
37
+
38
+ const runs = await db.query.cronAgentRuns.findMany({
39
+ where: and(...whereConditions),
40
+ orderBy: [desc(schema.cronAgentRuns.startedAt)],
41
+ limit
42
+ })
43
+
44
+ return { data: runs }
45
+ })
@@ -0,0 +1,94 @@
1
+ import { eq, and, gte } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import type { StatsPeriod, AgentDetailStats, DailyRunData } 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 id = getRouterParam(event, 'id')
22
+ if (!id) {
23
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
24
+ }
25
+
26
+ const query = getQuery(event)
27
+ const period = (query.period as StatsPeriod) || '7d'
28
+ const periodStart = getPeriodInterval(period)
29
+
30
+ const db = getDb()
31
+
32
+ // Get all runs for this agent in period
33
+ const runs = await db.query.cronAgentRuns.findMany({
34
+ where: and(
35
+ eq(schema.cronAgentRuns.agentId, id),
36
+ gte(schema.cronAgentRuns.startedAt, periodStart)
37
+ )
38
+ })
39
+
40
+ const totalRuns = runs.length
41
+ const successfulRuns = runs.filter(r => r.status === 'success').length
42
+ const successRate = totalRuns > 0 ? Math.round((successfulRuns / totalRuns) * 100) : 0
43
+ const totalCostUsd = runs.reduce((sum, r) => sum + (r.costUsd || 0), 0)
44
+
45
+ // Calculate average duration (only completed runs)
46
+ const completedRuns = runs.filter(r => r.durationMs != null)
47
+ const avgDurationMs = completedRuns.length > 0
48
+ ? Math.round(completedRuns.reduce((sum, r) => sum + (r.durationMs || 0), 0) / completedRuns.length)
49
+ : 0
50
+
51
+ // Get last run time
52
+ const lastRun = runs.length > 0
53
+ ? runs.reduce((latest, r) => r.startedAt > latest.startedAt ? r : latest)
54
+ : null
55
+ const lastRunAt = lastRun?.startedAt.toISOString() || null
56
+
57
+ // Get daily breakdown for chart
58
+ const dailyRunsMap = new Map<string, DailyRunData>()
59
+
60
+ for (const run of runs) {
61
+ const date = run.startedAt.toISOString().split('T')[0]!
62
+ const existing = dailyRunsMap.get(date) || {
63
+ date,
64
+ success: 0,
65
+ error: 0,
66
+ total: 0,
67
+ costUsd: 0
68
+ }
69
+
70
+ existing.total++
71
+ if (run.status === 'success') {
72
+ existing.success++
73
+ } else if (run.status === 'error' || run.status === 'budget_exceeded' || run.status === 'cancelled') {
74
+ existing.error++
75
+ }
76
+ existing.costUsd += run.costUsd || 0
77
+
78
+ dailyRunsMap.set(date, existing)
79
+ }
80
+
81
+ // Sort by date
82
+ const dailyRuns = Array.from(dailyRunsMap.values()).sort((a, b) => a.date.localeCompare(b.date))
83
+
84
+ const stats: AgentDetailStats = {
85
+ totalRuns,
86
+ successRate,
87
+ avgDurationMs,
88
+ totalCostUsd,
89
+ lastRunAt,
90
+ dailyRuns
91
+ }
92
+
93
+ return { data: stats }
94
+ })
@@ -0,0 +1,29 @@
1
+ import { eq } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import { unscheduleAgent } from '~~/server/services/cron-scheduler'
5
+
6
+ export default defineEventHandler(async (event) => {
7
+ requireDb(event)
8
+
9
+ const id = getRouterParam(event, 'id')
10
+ if (!id) {
11
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
12
+ }
13
+
14
+ const db = getDb()
15
+
16
+ // Unschedule before deleting
17
+ unscheduleAgent(id)
18
+
19
+ // Delete agent (runs will cascade delete)
20
+ const [deleted] = await db.delete(schema.cronAgents)
21
+ .where(eq(schema.cronAgents.id, id))
22
+ .returning()
23
+
24
+ if (!deleted) {
25
+ throw createError({ statusCode: 404, message: 'Agent not found' })
26
+ }
27
+
28
+ return { data: deleted }
29
+ })
@@ -0,0 +1,25 @@
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: 'Agent ID is required' })
11
+ }
12
+
13
+ const db = getDb()
14
+
15
+ const agent = await db.query.cronAgents.findFirst({
16
+ where: eq(schema.cronAgents.id, id),
17
+ with: { creator: true }
18
+ })
19
+
20
+ if (!agent) {
21
+ throw createError({ statusCode: 404, message: 'Agent not found' })
22
+ }
23
+
24
+ return { data: agent }
25
+ })
@@ -0,0 +1,55 @@
1
+ import { eq } from 'drizzle-orm'
2
+ import { getDb, schema } from '~~/server/db'
3
+ import { requireDb } from '~~/server/utils/db-guard'
4
+ import { scheduleAgent, unscheduleAgent } from '~~/server/services/cron-scheduler'
5
+
6
+ interface UpdateAgentBody {
7
+ name?: string
8
+ description?: string
9
+ schedule?: string
10
+ prompt?: string
11
+ enabled?: boolean
12
+ maxTurns?: number
13
+ maxBudgetUsd?: number | null
14
+ }
15
+
16
+ export default defineEventHandler(async (event) => {
17
+ requireDb(event)
18
+
19
+ const id = getRouterParam(event, 'id')
20
+ if (!id) {
21
+ throw createError({ statusCode: 400, message: 'Agent ID is required' })
22
+ }
23
+
24
+ const body = await readBody<UpdateAgentBody>(event)
25
+ const db = getDb()
26
+
27
+ // Get existing agent
28
+ const existing = await db.query.cronAgents.findFirst({
29
+ where: eq(schema.cronAgents.id, id)
30
+ })
31
+
32
+ if (!existing) {
33
+ throw createError({ statusCode: 404, message: 'Agent not found' })
34
+ }
35
+
36
+ // Update agent
37
+ const [agent] = await db.update(schema.cronAgents)
38
+ .set({
39
+ ...body,
40
+ updatedAt: new Date()
41
+ })
42
+ .where(eq(schema.cronAgents.id, id))
43
+ .returning()
44
+
45
+ // Reschedule if schedule or enabled changed
46
+ if (body.schedule !== undefined || body.enabled !== undefined) {
47
+ if (agent!.enabled) {
48
+ scheduleAgent(agent!)
49
+ } else {
50
+ unscheduleAgent(agent!.id)
51
+ }
52
+ }
53
+
54
+ return { data: agent }
55
+ })
@@ -0,0 +1,15 @@
1
+ import { getDb } from '~~/server/db'
2
+ import { requireDb } from '~~/server/utils/db-guard'
3
+
4
+ export default defineEventHandler(async (event) => {
5
+ requireDb(event)
6
+
7
+ const db = getDb()
8
+
9
+ const agents = await db.query.cronAgents.findMany({
10
+ with: { creator: true },
11
+ orderBy: (agents, { asc }) => [asc(agents.name)]
12
+ })
13
+
14
+ return { data: agents }
15
+ })
@@ -0,0 +1,48 @@
1
+ import { getDb, schema } from '~~/server/db'
2
+ import { requireDb } from '~~/server/utils/db-guard'
3
+ import { scheduleAgent } from '~~/server/services/cron-scheduler'
4
+
5
+ interface CreateAgentBody {
6
+ name: string
7
+ description?: string
8
+ schedule: string
9
+ prompt: string
10
+ enabled?: boolean
11
+ maxTurns?: number
12
+ maxBudgetUsd?: number
13
+ }
14
+
15
+ export default defineEventHandler(async (event) => {
16
+ requireDb(event)
17
+
18
+ const body = await readBody<CreateAgentBody>(event)
19
+
20
+ if (!body.name || !body.schedule || !body.prompt) {
21
+ throw createError({
22
+ statusCode: 400,
23
+ message: 'Name, schedule, and prompt are required'
24
+ })
25
+ }
26
+
27
+ const db = getDb()
28
+
29
+ const [agent] = await db.insert(schema.cronAgents)
30
+ .values({
31
+ name: body.name,
32
+ description: body.description,
33
+ schedule: body.schedule,
34
+ prompt: body.prompt,
35
+ enabled: body.enabled ?? true,
36
+ maxTurns: body.maxTurns ?? 50,
37
+ maxBudgetUsd: body.maxBudgetUsd,
38
+ createdBy: event.context.user?.id
39
+ })
40
+ .returning()
41
+
42
+ // Schedule if enabled
43
+ if (agent!.enabled) {
44
+ scheduleAgent(agent!)
45
+ }
46
+
47
+ return { data: agent }
48
+ })
@@ -0,0 +1,86 @@
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, AgentGlobalStats, DailyRunData } 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 agent counts
28
+ const agents = await db.query.cronAgents.findMany()
29
+ const totalAgents = agents.length
30
+ const activeAgents = agents.filter(a => a.enabled).length
31
+
32
+ // Get runs in period with aggregations
33
+ const runsInPeriod = await db.query.cronAgentRuns.findMany({
34
+ where: gte(schema.cronAgentRuns.startedAt, periodStart)
35
+ })
36
+
37
+ const totalRuns = runsInPeriod.length
38
+ const successfulRuns = runsInPeriod.filter(r => r.status === 'success').length
39
+ const successRate = totalRuns > 0 ? Math.round((successfulRuns / totalRuns) * 100) : 0
40
+ const totalCostUsd = runsInPeriod.reduce((sum, r) => sum + (r.costUsd || 0), 0)
41
+
42
+ // Get running agent IDs
43
+ const runningAgentIds = runsInPeriod
44
+ .filter(r => r.status === 'running')
45
+ .map(r => r.agentId)
46
+ .filter((id, index, arr) => arr.indexOf(id) === index) // unique
47
+
48
+ // Get daily breakdown for chart
49
+ const dailyRunsMap = new Map<string, DailyRunData>()
50
+
51
+ for (const run of runsInPeriod) {
52
+ const date = run.startedAt.toISOString().split('T')[0]!
53
+ const existing = dailyRunsMap.get(date) || {
54
+ date,
55
+ success: 0,
56
+ error: 0,
57
+ total: 0,
58
+ costUsd: 0
59
+ }
60
+
61
+ existing.total++
62
+ if (run.status === 'success') {
63
+ existing.success++
64
+ } else if (run.status === 'error' || run.status === 'budget_exceeded' || run.status === 'cancelled') {
65
+ existing.error++
66
+ }
67
+ existing.costUsd += run.costUsd || 0
68
+
69
+ dailyRunsMap.set(date, existing)
70
+ }
71
+
72
+ // Sort by date
73
+ const dailyRuns = Array.from(dailyRunsMap.values()).sort((a, b) => a.date.localeCompare(b.date))
74
+
75
+ const stats: AgentGlobalStats = {
76
+ totalAgents,
77
+ activeAgents,
78
+ runsInPeriod: totalRuns,
79
+ successRate,
80
+ totalCostUsd,
81
+ runningAgentIds,
82
+ dailyRuns
83
+ }
84
+
85
+ return { data: stats }
86
+ })
@@ -0,0 +1,5 @@
1
+ import { auth } from '~~/server/utils/auth'
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ return auth.handler(toWebRequest(event))
5
+ })