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.
- package/.env.example +58 -0
- package/Claude/CLAUDE.md +92 -0
- package/Claude/hooks/lib/__init__.py +1 -0
- package/Claude/hooks/lib/hook_client.py +207 -0
- package/Claude/hooks/log-event.py +97 -0
- package/Claude/hooks/pre-compact.py +46 -0
- package/Claude/hooks/session-end.py +26 -0
- package/Claude/hooks/session-start.py +35 -0
- package/Claude/hooks/stop-extract.py +40 -0
- package/Claude/rules/frontmatter.md +54 -0
- package/Claude/rules/markdown.md +43 -0
- package/Claude/rules/note-organization.md +33 -0
- package/Claude/settings.json +54 -0
- package/Claude/skills/README.md +136 -0
- package/Claude/skills/_lib/__init__.py +1 -0
- package/Claude/skills/_lib/api.py +164 -0
- package/Claude/skills/_lib/output.py +95 -0
- package/Claude/skills/environment/SKILL.md +73 -0
- package/Claude/skills/environment/environment.py +239 -0
- package/Claude/skills/memory/SKILL.md +153 -0
- package/Claude/skills/memory/memory.py +270 -0
- package/Claude/skills/project/SKILL.md +105 -0
- package/Claude/skills/project/project.py +203 -0
- package/Claude/skills/skill-creator/SKILL.md +261 -0
- package/Claude/skills/task/SKILL.md +135 -0
- package/Claude/skills/task/task.py +310 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +39 -0
- package/app/assets/css/main.css +10 -0
- package/app/components/AppLogo.vue +40 -0
- package/app/components/AssistantPanel.client.vue +518 -0
- package/app/components/ConfirmModal.vue +84 -0
- package/app/components/TemplateMenu.vue +49 -0
- package/app/components/agents/AgentActivityChart.client.vue +105 -0
- package/app/components/agents/AgentActivityChart.server.vue +25 -0
- package/app/components/agents/AgentForm.vue +304 -0
- package/app/components/agents/AgentRunModal.vue +154 -0
- package/app/components/agents/AgentStatsCards.vue +98 -0
- package/app/components/chat/ChatInput.vue +85 -0
- package/app/components/chat/ConversationList.vue +78 -0
- package/app/components/chat/MessageBubble.vue +81 -0
- package/app/components/chat/StreamingMessage.vue +36 -0
- package/app/components/chat/ToolCallBlock.vue +77 -0
- package/app/components/editor/CodeEditor.client.vue +212 -0
- package/app/components/editor/CodeEditorFallback.vue +12 -0
- package/app/components/editor/DocumentEditor.vue +326 -0
- package/app/components/editor/DocumentMetadata.vue +140 -0
- package/app/components/editor/MarkdownEditor.vue +146 -0
- package/app/components/files/FileTree.vue +436 -0
- package/app/components/hooks/HookActivityChart.client.vue +117 -0
- package/app/components/hooks/HookActivityChart.server.vue +25 -0
- package/app/components/hooks/HookStatsCards.vue +63 -0
- package/app/components/hooks/RecentEventsTable.vue +123 -0
- package/app/components/hooks/ToolBreakdownTable.vue +72 -0
- package/app/components/search/DashboardSearch.vue +122 -0
- package/app/components/tasks/ProjectSelect.vue +35 -0
- package/app/components/tasks/TaskCard.vue +182 -0
- package/app/components/tasks/TaskDetail.vue +160 -0
- package/app/components/tasks/TaskForm.vue +280 -0
- package/app/components/tasks/TaskList.vue +69 -0
- package/app/components/view/ViewToc.vue +85 -0
- package/app/composables/useAgents.ts +153 -0
- package/app/composables/useAuth.ts +73 -0
- package/app/composables/useChat.ts +298 -0
- package/app/composables/useDocument.ts +141 -0
- package/app/composables/useEditor.ts +100 -0
- package/app/composables/useFileTree.ts +220 -0
- package/app/composables/useHookEvents.ts +68 -0
- package/app/composables/useMemories.ts +83 -0
- package/app/composables/useNotificationBus.ts +154 -0
- package/app/composables/usePreferences.ts +131 -0
- package/app/composables/useProjects.ts +97 -0
- package/app/composables/useSearch.ts +52 -0
- package/app/composables/useTasks.ts +201 -0
- package/app/composables/useTerminal.ts +135 -0
- package/app/layouts/auth.vue +20 -0
- package/app/layouts/dashboard.vue +186 -0
- package/app/layouts/view.vue +60 -0
- package/app/middleware/auth.ts +9 -0
- package/app/pages/agents/[id].vue +602 -0
- package/app/pages/agents/index.vue +412 -0
- package/app/pages/chat.vue +146 -0
- package/app/pages/dashboard.vue +80 -0
- package/app/pages/docs.vue +131 -0
- package/app/pages/hooks.vue +163 -0
- package/app/pages/index.vue +249 -0
- package/app/pages/login.vue +60 -0
- package/app/pages/memories.vue +282 -0
- package/app/pages/settings.vue +625 -0
- package/app/pages/tasks.vue +312 -0
- package/app/pages/view/[uuid].vue +376 -0
- package/dist/cli/index.js +2711 -0
- package/drizzle.config.ts +10 -0
- package/nuxt.config.ts +98 -0
- package/package.json +107 -0
- package/server/api/agents/[id]/cancel.post.ts +27 -0
- package/server/api/agents/[id]/run.post.ts +34 -0
- package/server/api/agents/[id]/runs.get.ts +45 -0
- package/server/api/agents/[id]/stats.get.ts +94 -0
- package/server/api/agents/[id].delete.ts +29 -0
- package/server/api/agents/[id].get.ts +25 -0
- package/server/api/agents/[id].patch.ts +55 -0
- package/server/api/agents/index.get.ts +15 -0
- package/server/api/agents/index.post.ts +48 -0
- package/server/api/agents/stats.get.ts +86 -0
- package/server/api/auth/[...all].ts +5 -0
- package/server/api/conversations/[id].delete.ts +16 -0
- package/server/api/conversations/[id].get.ts +34 -0
- package/server/api/conversations/index.get.ts +17 -0
- package/server/api/documents/[id]/index.delete.ts +47 -0
- package/server/api/documents/[id]/index.put.ts +102 -0
- package/server/api/documents/[id]/public.get.ts +60 -0
- package/server/api/documents/[id]/restore.post.ts +65 -0
- package/server/api/documents/by-path.post.ts +168 -0
- package/server/api/documents/index.get.ts +48 -0
- package/server/api/fs/delete.post.ts +41 -0
- package/server/api/fs/list.get.ts +99 -0
- package/server/api/fs/mkdir.post.ts +44 -0
- package/server/api/fs/move.post.ts +68 -0
- package/server/api/fs/read.post.ts +48 -0
- package/server/api/fs/rename.post.ts +55 -0
- package/server/api/fs/write.post.ts +51 -0
- package/server/api/health.get.ts +40 -0
- package/server/api/home.get.ts +26 -0
- package/server/api/hooks/events/index.get.ts +56 -0
- package/server/api/hooks/events/index.post.ts +36 -0
- package/server/api/hooks/stats.get.ts +99 -0
- package/server/api/memory/[id].delete.ts +26 -0
- package/server/api/memory/context.get.ts +83 -0
- package/server/api/memory/extract.post.ts +42 -0
- package/server/api/memory/search.get.ts +70 -0
- package/server/api/memory/store.post.ts +31 -0
- package/server/api/projects/[id]/index.delete.ts +40 -0
- package/server/api/projects/[id]/index.get.ts +25 -0
- package/server/api/projects/[id]/index.put.ts +50 -0
- package/server/api/projects/index.get.ts +20 -0
- package/server/api/projects/index.post.ts +34 -0
- package/server/api/secrets/[key].delete.ts +31 -0
- package/server/api/secrets/[key].get.ts +30 -0
- package/server/api/secrets/[key].put.ts +52 -0
- package/server/api/secrets/index.get.ts +20 -0
- package/server/api/secrets/index.post.ts +58 -0
- package/server/api/tasks/[id]/index.delete.ts +46 -0
- package/server/api/tasks/[id]/index.get.ts +24 -0
- package/server/api/tasks/[id]/index.put.ts +70 -0
- package/server/api/tasks/[id]/restore.post.ts +49 -0
- package/server/api/tasks/index.get.ts +53 -0
- package/server/api/tasks/index.post.ts +47 -0
- package/server/api/tasks/tags.get.ts +21 -0
- package/server/api/user/email.patch.ts +56 -0
- package/server/db/index.ts +76 -0
- package/server/db/migrate.ts +41 -0
- package/server/db/schema.ts +345 -0
- package/server/db/seed.ts +46 -0
- package/server/db/types.ts +28 -0
- package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
- package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
- package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
- package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
- package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
- package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
- package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
- package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
- package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
- package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
- package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
- package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
- package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
- package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
- package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
- package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
- package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
- package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
- package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
- package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
- package/server/drizzle/migrations/meta/_journal.json +76 -0
- package/server/middleware/auth.ts +79 -0
- package/server/plugins/00.env-validate.ts +38 -0
- package/server/plugins/01.api-token.ts +31 -0
- package/server/plugins/02.database.ts +54 -0
- package/server/plugins/03.file-watcher.ts +65 -0
- package/server/plugins/04.cron-agents.ts +26 -0
- package/server/routes/_ws/chat.ts +252 -0
- package/server/routes/notifications.ts +47 -0
- package/server/routes/terminal.ts +98 -0
- package/server/services/agent-executor.ts +218 -0
- package/server/services/cron-scheduler.ts +78 -0
- package/server/services/memory-extractor.ts +120 -0
- package/server/utils/agent-cleanup.ts +91 -0
- package/server/utils/agent-registry.ts +95 -0
- package/server/utils/auth.ts +33 -0
- package/server/utils/chat-session-manager.ts +59 -0
- package/server/utils/crypto.ts +40 -0
- package/server/utils/db-guard.ts +12 -0
- package/server/utils/db-state.ts +63 -0
- package/server/utils/document-sync.ts +207 -0
- package/server/utils/frontmatter.ts +84 -0
- package/server/utils/notification-bus.ts +60 -0
- package/server/utils/path-validator.ts +55 -0
- package/server/utils/pty-manager.ts +130 -0
- package/shared/types/index.ts +604 -0
- package/shared/utils/language-detection.ts +87 -0
- 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
|
+
})
|