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,97 @@
|
|
|
1
|
+
import type { Project, CreateProjectInput, UpdateProjectInput } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
export function useProjects() {
|
|
4
|
+
const projects = ref<Project[]>([])
|
|
5
|
+
const loading = ref(false)
|
|
6
|
+
const error = ref<string | null>(null)
|
|
7
|
+
|
|
8
|
+
async function fetchProjects(includeDeleted = false) {
|
|
9
|
+
loading.value = true
|
|
10
|
+
error.value = null
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const response = await $fetch<{ data: Project[] }>('/api/projects', {
|
|
14
|
+
query: { includeDeleted: includeDeleted.toString() }
|
|
15
|
+
})
|
|
16
|
+
projects.value = response.data
|
|
17
|
+
return response.data
|
|
18
|
+
} catch (e) {
|
|
19
|
+
error.value = 'Failed to load projects'
|
|
20
|
+
console.error('Failed to load projects:', e)
|
|
21
|
+
throw e
|
|
22
|
+
} finally {
|
|
23
|
+
loading.value = false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getProject(id: string) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await $fetch<{ data: Project }>(`/api/projects/${id}`)
|
|
30
|
+
return response.data
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('Failed to get project:', e)
|
|
33
|
+
throw e
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function createProject(input: CreateProjectInput) {
|
|
38
|
+
try {
|
|
39
|
+
const response = await $fetch<{ data: Project }>('/api/projects', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
body: input
|
|
42
|
+
})
|
|
43
|
+
projects.value = [...projects.value, response.data]
|
|
44
|
+
return response.data
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error('Failed to create project:', e)
|
|
47
|
+
throw e
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function updateProject(id: string, input: UpdateProjectInput) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await $fetch<{ data: Project }>(`/api/projects/${id}`, {
|
|
54
|
+
method: 'PUT',
|
|
55
|
+
body: input
|
|
56
|
+
})
|
|
57
|
+
const index = projects.value.findIndex(p => p.id === id)
|
|
58
|
+
if (index !== -1)
|
|
59
|
+
projects.value[index] = response.data
|
|
60
|
+
return response.data
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error('Failed to update project:', e)
|
|
63
|
+
throw e
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function deleteProject(id: string) {
|
|
68
|
+
try {
|
|
69
|
+
const response = await $fetch<{ data: Project }>(`/api/projects/${id}`, {
|
|
70
|
+
method: 'DELETE'
|
|
71
|
+
})
|
|
72
|
+
projects.value = projects.value.filter(p => p.id !== id)
|
|
73
|
+
return response.data
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error('Failed to delete project:', e)
|
|
76
|
+
throw e
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get project by ID from local state
|
|
81
|
+
function getProjectById(id: string | undefined | null): Project | undefined {
|
|
82
|
+
if (!id) return undefined
|
|
83
|
+
return projects.value.find(p => p.id === id)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
projects,
|
|
88
|
+
loading,
|
|
89
|
+
error,
|
|
90
|
+
fetchProjects,
|
|
91
|
+
getProject,
|
|
92
|
+
createProject,
|
|
93
|
+
updateProject,
|
|
94
|
+
deleteProject,
|
|
95
|
+
getProjectById
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { refDebounced } from '@vueuse/core'
|
|
2
|
+
import type { Task, Document } from '~~/shared/types'
|
|
3
|
+
|
|
4
|
+
export interface SearchResults {
|
|
5
|
+
tasks: Task[]
|
|
6
|
+
documents: Document[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useSearch() {
|
|
10
|
+
const searchTerm = ref('')
|
|
11
|
+
const debouncedSearchTerm = refDebounced(searchTerm, 300)
|
|
12
|
+
const loading = ref(false)
|
|
13
|
+
const results = ref<SearchResults>({
|
|
14
|
+
tasks: [],
|
|
15
|
+
documents: []
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
watch(debouncedSearchTerm, async (term) => {
|
|
19
|
+
if (!term || term.length < 2) {
|
|
20
|
+
results.value = { tasks: [], documents: [] }
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
loading.value = true
|
|
25
|
+
try {
|
|
26
|
+
const [tasksRes, docsRes] = await Promise.all([
|
|
27
|
+
$fetch<{ data: Task[] }>('/api/tasks', { query: { search: term } }),
|
|
28
|
+
$fetch<{ data: Document[] }>('/api/documents', { query: { search: term } })
|
|
29
|
+
])
|
|
30
|
+
results.value = {
|
|
31
|
+
tasks: tasksRes.data.slice(0, 5),
|
|
32
|
+
documents: docsRes.data.slice(0, 5)
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
results.value = { tasks: [], documents: [] }
|
|
36
|
+
} finally {
|
|
37
|
+
loading.value = false
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function reset() {
|
|
42
|
+
searchTerm.value = ''
|
|
43
|
+
results.value = { tasks: [], documents: [] }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
searchTerm,
|
|
48
|
+
loading,
|
|
49
|
+
results,
|
|
50
|
+
reset
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { Task, CreateTaskInput, UpdateTaskInput, TaskFilters, TaskStatus } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
export function useTasks() {
|
|
4
|
+
const tasks = ref<Task[]>([])
|
|
5
|
+
const loading = ref(false)
|
|
6
|
+
const error = ref<string | null>(null)
|
|
7
|
+
const tags = ref<string[]>([])
|
|
8
|
+
|
|
9
|
+
// Filter state
|
|
10
|
+
const filters = reactive<TaskFilters>({
|
|
11
|
+
status: undefined,
|
|
12
|
+
projectId: undefined,
|
|
13
|
+
search: undefined,
|
|
14
|
+
includeDeleted: false
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
async function fetchTasks(customFilters?: TaskFilters) {
|
|
18
|
+
loading.value = true
|
|
19
|
+
error.value = null
|
|
20
|
+
|
|
21
|
+
const queryFilters = customFilters || filters
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const query: Record<string, string> = {}
|
|
25
|
+
|
|
26
|
+
if (queryFilters.status) {
|
|
27
|
+
if (Array.isArray(queryFilters.status))
|
|
28
|
+
queryFilters.status.forEach(s => query.status = s)
|
|
29
|
+
else
|
|
30
|
+
query.status = queryFilters.status
|
|
31
|
+
}
|
|
32
|
+
if (queryFilters.projectId)
|
|
33
|
+
query.projectId = queryFilters.projectId
|
|
34
|
+
if (queryFilters.search)
|
|
35
|
+
query.search = queryFilters.search
|
|
36
|
+
if (queryFilters.includeDeleted)
|
|
37
|
+
query.includeDeleted = 'true'
|
|
38
|
+
|
|
39
|
+
const response = await $fetch<{ data: Task[] }>('/api/tasks', { query })
|
|
40
|
+
tasks.value = response.data
|
|
41
|
+
return response.data
|
|
42
|
+
} catch (e) {
|
|
43
|
+
error.value = 'Failed to load tasks'
|
|
44
|
+
console.error('Failed to load tasks:', e)
|
|
45
|
+
throw e
|
|
46
|
+
} finally {
|
|
47
|
+
loading.value = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function fetchTags() {
|
|
52
|
+
try {
|
|
53
|
+
const response = await $fetch<{ data: string[] }>('/api/tasks/tags')
|
|
54
|
+
tags.value = response.data
|
|
55
|
+
return response.data
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error('Failed to fetch tags:', e)
|
|
58
|
+
throw e
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function getTask(id: string) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await $fetch<{ data: Task }>(`/api/tasks/${id}`)
|
|
65
|
+
return response.data
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error('Failed to get task:', e)
|
|
68
|
+
throw e
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function createTask(input: CreateTaskInput) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await $fetch<{ data: Task }>('/api/tasks', {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
body: input
|
|
77
|
+
})
|
|
78
|
+
tasks.value = [response.data, ...tasks.value]
|
|
79
|
+
return response.data
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error('Failed to create task:', e)
|
|
82
|
+
throw e
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function updateTask(id: string, input: UpdateTaskInput) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await $fetch<{ data: Task }>(`/api/tasks/${id}`, {
|
|
89
|
+
method: 'PUT',
|
|
90
|
+
body: input
|
|
91
|
+
})
|
|
92
|
+
const index = tasks.value.findIndex(t => t.id === id)
|
|
93
|
+
if (index !== -1)
|
|
94
|
+
tasks.value[index] = response.data
|
|
95
|
+
return response.data
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error('Failed to update task:', e)
|
|
98
|
+
throw e
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function deleteTask(id: string) {
|
|
103
|
+
try {
|
|
104
|
+
const response = await $fetch<{ data: Task }>(`/api/tasks/${id}`, {
|
|
105
|
+
method: 'DELETE'
|
|
106
|
+
})
|
|
107
|
+
tasks.value = tasks.value.filter(t => t.id !== id)
|
|
108
|
+
return response.data
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error('Failed to delete task:', e)
|
|
111
|
+
throw e
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function restoreTask(id: string) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await $fetch<{ data: Task }>(`/api/tasks/${id}/restore`, {
|
|
118
|
+
method: 'POST'
|
|
119
|
+
})
|
|
120
|
+
// Re-fetch to update the list properly
|
|
121
|
+
await fetchTasks()
|
|
122
|
+
return response.data
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error('Failed to restore task:', e)
|
|
125
|
+
throw e
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Quick status toggle (for checkbox)
|
|
130
|
+
async function toggleComplete(id: string) {
|
|
131
|
+
const task = tasks.value.find(t => t.id === id)
|
|
132
|
+
if (!task) return
|
|
133
|
+
|
|
134
|
+
const newStatus: TaskStatus = task.status === 'done' ? 'todo' : 'done'
|
|
135
|
+
return updateTask(id, { status: newStatus })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Computed filtered tasks (client-side filtering for instant feedback)
|
|
139
|
+
const filteredTasks = computed(() => {
|
|
140
|
+
let result = [...tasks.value]
|
|
141
|
+
|
|
142
|
+
if (filters.status) {
|
|
143
|
+
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status]
|
|
144
|
+
result = result.filter(t => statuses.includes(t.status))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (filters.projectId)
|
|
148
|
+
result = result.filter(t => t.projectId === filters.projectId)
|
|
149
|
+
|
|
150
|
+
if (filters.search) {
|
|
151
|
+
const search = filters.search.toLowerCase()
|
|
152
|
+
result = result.filter(t =>
|
|
153
|
+
t.title.toLowerCase().includes(search)
|
|
154
|
+
|| t.description?.toLowerCase().includes(search)
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!filters.includeDeleted)
|
|
159
|
+
result = result.filter(t => !t.deletedAt)
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Task counts by status
|
|
165
|
+
const taskCounts = computed(() => {
|
|
166
|
+
const counts = {
|
|
167
|
+
todo: 0,
|
|
168
|
+
in_progress: 0,
|
|
169
|
+
done: 0,
|
|
170
|
+
blocked: 0,
|
|
171
|
+
total: 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
tasks.value.forEach((task) => {
|
|
175
|
+
if (!task.deletedAt) {
|
|
176
|
+
counts[task.status]++
|
|
177
|
+
counts.total++
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return counts
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
tasks,
|
|
186
|
+
filteredTasks,
|
|
187
|
+
loading,
|
|
188
|
+
error,
|
|
189
|
+
tags,
|
|
190
|
+
filters,
|
|
191
|
+
taskCounts,
|
|
192
|
+
fetchTasks,
|
|
193
|
+
fetchTags,
|
|
194
|
+
getTask,
|
|
195
|
+
createTask,
|
|
196
|
+
updateTask,
|
|
197
|
+
deleteTask,
|
|
198
|
+
restoreTask,
|
|
199
|
+
toggleComplete
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Terminal } from '@xterm/xterm'
|
|
2
|
+
|
|
3
|
+
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
4
|
+
|
|
5
|
+
interface TerminalMessage {
|
|
6
|
+
type: 'output' | 'pong' | 'error'
|
|
7
|
+
data?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useTerminal() {
|
|
11
|
+
const status = ref<ConnectionStatus>('disconnected')
|
|
12
|
+
const terminal = ref<Terminal | null>(null)
|
|
13
|
+
const ws = ref<WebSocket | null>(null)
|
|
14
|
+
const reconnectAttempts = ref(0)
|
|
15
|
+
const maxReconnectAttempts = 5
|
|
16
|
+
const reconnectDelay = 2000
|
|
17
|
+
|
|
18
|
+
function getWebSocketUrl(): string {
|
|
19
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
20
|
+
return `${protocol}//${window.location.host}/terminal`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function connect(term: Terminal) {
|
|
24
|
+
terminal.value = term
|
|
25
|
+
status.value = 'connecting'
|
|
26
|
+
|
|
27
|
+
const socket = new WebSocket(getWebSocketUrl())
|
|
28
|
+
ws.value = socket
|
|
29
|
+
|
|
30
|
+
socket.onopen = () => {
|
|
31
|
+
status.value = 'connected'
|
|
32
|
+
reconnectAttempts.value = 0
|
|
33
|
+
|
|
34
|
+
// Send initial size
|
|
35
|
+
sendResize(term.cols, term.rows)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
socket.onmessage = (event) => {
|
|
39
|
+
try {
|
|
40
|
+
const msg = JSON.parse(event.data) as TerminalMessage
|
|
41
|
+
|
|
42
|
+
if (msg.type === 'output' && msg.data) {
|
|
43
|
+
term.write(msg.data)
|
|
44
|
+
} else if (msg.type === 'error' && msg.data) {
|
|
45
|
+
status.value = 'error'
|
|
46
|
+
term.write(`\x1b[31m${msg.data}\x1b[0m`)
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Handle raw data if not JSON
|
|
50
|
+
term.write(event.data)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
socket.onclose = () => {
|
|
55
|
+
status.value = 'disconnected'
|
|
56
|
+
|
|
57
|
+
// Attempt reconnection
|
|
58
|
+
if (reconnectAttempts.value < maxReconnectAttempts) {
|
|
59
|
+
reconnectAttempts.value++
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
if (terminal.value) {
|
|
62
|
+
connect(terminal.value)
|
|
63
|
+
}
|
|
64
|
+
}, reconnectDelay)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
socket.onerror = () => {
|
|
69
|
+
status.value = 'error'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function disconnect() {
|
|
74
|
+
if (ws.value) {
|
|
75
|
+
ws.value.close()
|
|
76
|
+
ws.value = null
|
|
77
|
+
}
|
|
78
|
+
status.value = 'disconnected'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sendInput(data: string) {
|
|
82
|
+
if (ws.value?.readyState === WebSocket.OPEN) {
|
|
83
|
+
ws.value.send(JSON.stringify({
|
|
84
|
+
type: 'input',
|
|
85
|
+
data
|
|
86
|
+
}))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sendResize(cols: number, rows: number) {
|
|
91
|
+
if (ws.value?.readyState === WebSocket.OPEN) {
|
|
92
|
+
ws.value.send(JSON.stringify({
|
|
93
|
+
type: 'resize',
|
|
94
|
+
cols,
|
|
95
|
+
rows
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sendPing() {
|
|
101
|
+
if (ws.value?.readyState === WebSocket.OPEN) {
|
|
102
|
+
ws.value.send(JSON.stringify({ type: 'ping' }))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Keep connection alive
|
|
107
|
+
let pingInterval: ReturnType<typeof setInterval> | null = null
|
|
108
|
+
|
|
109
|
+
function startPingInterval() {
|
|
110
|
+
if (pingInterval) clearInterval(pingInterval)
|
|
111
|
+
pingInterval = setInterval(sendPing, 30000)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stopPingInterval() {
|
|
115
|
+
if (pingInterval) {
|
|
116
|
+
clearInterval(pingInterval)
|
|
117
|
+
pingInterval = null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onUnmounted(() => {
|
|
122
|
+
stopPingInterval()
|
|
123
|
+
disconnect()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
status,
|
|
128
|
+
connect,
|
|
129
|
+
disconnect,
|
|
130
|
+
sendInput,
|
|
131
|
+
sendResize,
|
|
132
|
+
startPingInterval,
|
|
133
|
+
stopPingInterval
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<template>
|
|
5
|
+
<div class="min-h-screen flex items-center justify-center bg-default">
|
|
6
|
+
<div class="w-full max-w-md px-4">
|
|
7
|
+
<div class="flex flex-col items-center gap-6 mb-8">
|
|
8
|
+
<div class="flex items-center gap-3">
|
|
9
|
+
<UIcon
|
|
10
|
+
name="i-lucide-brain"
|
|
11
|
+
class="size-10 text-primary"
|
|
12
|
+
/>
|
|
13
|
+
<span class="font-bold text-2xl">Cognova</span>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<slot />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NavigationMenuItem } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
const { user, logout } = useAuth()
|
|
5
|
+
const { sidebarOpen } = usePreferences()
|
|
6
|
+
const open = ref(sidebarOpen.value)
|
|
7
|
+
|
|
8
|
+
// Sync sidebar state to preference
|
|
9
|
+
watch(open, v => sidebarOpen.value = v)
|
|
10
|
+
|
|
11
|
+
// Initialize notification bus on client
|
|
12
|
+
const { connect: connectNotificationBus } = useNotificationBus()
|
|
13
|
+
onMounted(() => {
|
|
14
|
+
connectNotificationBus()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const links = [[{
|
|
18
|
+
label: 'Dashboard',
|
|
19
|
+
icon: 'i-lucide-home',
|
|
20
|
+
to: '/dashboard',
|
|
21
|
+
onSelect: () => {
|
|
22
|
+
open.value = false
|
|
23
|
+
}
|
|
24
|
+
}, {
|
|
25
|
+
label: 'Docs',
|
|
26
|
+
icon: 'i-lucide-file-text',
|
|
27
|
+
to: '/docs',
|
|
28
|
+
onSelect: () => {
|
|
29
|
+
open.value = false
|
|
30
|
+
}
|
|
31
|
+
}, {
|
|
32
|
+
label: 'Tasks',
|
|
33
|
+
icon: 'i-lucide-check-square',
|
|
34
|
+
to: '/tasks',
|
|
35
|
+
onSelect: () => {
|
|
36
|
+
open.value = false
|
|
37
|
+
}
|
|
38
|
+
}, {
|
|
39
|
+
label: 'Agents',
|
|
40
|
+
icon: 'i-lucide-bot',
|
|
41
|
+
to: '/agents',
|
|
42
|
+
onSelect: () => {
|
|
43
|
+
open.value = false
|
|
44
|
+
}
|
|
45
|
+
}, {
|
|
46
|
+
label: 'Hooks',
|
|
47
|
+
icon: 'i-lucide-webhook',
|
|
48
|
+
to: '/hooks',
|
|
49
|
+
onSelect: () => {
|
|
50
|
+
open.value = false
|
|
51
|
+
}
|
|
52
|
+
}, {
|
|
53
|
+
label: 'Memories',
|
|
54
|
+
icon: 'i-lucide-brain',
|
|
55
|
+
to: '/memories',
|
|
56
|
+
onSelect: () => {
|
|
57
|
+
open.value = false
|
|
58
|
+
}
|
|
59
|
+
}, {
|
|
60
|
+
label: 'Chat',
|
|
61
|
+
icon: 'i-lucide-message-square',
|
|
62
|
+
to: '/chat',
|
|
63
|
+
onSelect: () => {
|
|
64
|
+
open.value = false
|
|
65
|
+
}
|
|
66
|
+
}, {
|
|
67
|
+
label: 'Settings',
|
|
68
|
+
icon: 'i-lucide-settings',
|
|
69
|
+
to: '/settings',
|
|
70
|
+
onSelect: () => {
|
|
71
|
+
open.value = false
|
|
72
|
+
}
|
|
73
|
+
}]] satisfies NavigationMenuItem[][]
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<UDashboardGroup unit="rem">
|
|
78
|
+
<UDashboardSidebar
|
|
79
|
+
id="main"
|
|
80
|
+
v-model:open="open"
|
|
81
|
+
collapsible
|
|
82
|
+
resizable
|
|
83
|
+
class="bg-elevated/25"
|
|
84
|
+
:ui="{ footer: 'lg:border-t lg:border-default' }"
|
|
85
|
+
>
|
|
86
|
+
<template #header="{ collapsed }">
|
|
87
|
+
<div class="flex items-center gap-2 px-2 py-1.5">
|
|
88
|
+
<UIcon
|
|
89
|
+
name="i-lucide-brain"
|
|
90
|
+
class="size-6 text-primary shrink-0"
|
|
91
|
+
/>
|
|
92
|
+
<span
|
|
93
|
+
v-if="!collapsed"
|
|
94
|
+
class="font-semibold text-lg"
|
|
95
|
+
>Brain</span>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<template #default="{ collapsed }">
|
|
100
|
+
<ClientOnly>
|
|
101
|
+
<UDashboardSearchButton :collapsed="collapsed" />
|
|
102
|
+
<template #fallback>
|
|
103
|
+
<UButton
|
|
104
|
+
:label="collapsed ? undefined : 'Search...'"
|
|
105
|
+
icon="i-lucide-search"
|
|
106
|
+
color="neutral"
|
|
107
|
+
variant="ghost"
|
|
108
|
+
class="w-full justify-start"
|
|
109
|
+
:ui="{ trailingIcon: 'ms-auto' }"
|
|
110
|
+
>
|
|
111
|
+
<template
|
|
112
|
+
v-if="!collapsed"
|
|
113
|
+
#trailing
|
|
114
|
+
>
|
|
115
|
+
<UKbd>⌘K</UKbd>
|
|
116
|
+
</template>
|
|
117
|
+
</UButton>
|
|
118
|
+
</template>
|
|
119
|
+
</ClientOnly>
|
|
120
|
+
|
|
121
|
+
<UNavigationMenu
|
|
122
|
+
:collapsed="collapsed"
|
|
123
|
+
:items="links[0]"
|
|
124
|
+
orientation="vertical"
|
|
125
|
+
tooltip
|
|
126
|
+
popover
|
|
127
|
+
/>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<template #footer="{ collapsed }">
|
|
131
|
+
<ClientOnly>
|
|
132
|
+
<UDropdownMenu
|
|
133
|
+
:items="[[
|
|
134
|
+
{ label: 'Sign out', icon: 'i-lucide-log-out', onSelect: logout }
|
|
135
|
+
]]"
|
|
136
|
+
>
|
|
137
|
+
<UButton
|
|
138
|
+
variant="ghost"
|
|
139
|
+
class="w-full justify-start"
|
|
140
|
+
:ui="{ leadingIcon: 'size-5' }"
|
|
141
|
+
>
|
|
142
|
+
<UAvatar
|
|
143
|
+
:alt="user?.name || 'User'"
|
|
144
|
+
size="2xs"
|
|
145
|
+
/>
|
|
146
|
+
<span
|
|
147
|
+
v-if="!collapsed"
|
|
148
|
+
class="text-sm truncate"
|
|
149
|
+
>{{ user?.name || user?.email || 'User' }}</span>
|
|
150
|
+
</UButton>
|
|
151
|
+
</UDropdownMenu>
|
|
152
|
+
|
|
153
|
+
<template #fallback>
|
|
154
|
+
<UButton
|
|
155
|
+
variant="ghost"
|
|
156
|
+
class="w-full justify-start"
|
|
157
|
+
:ui="{ leadingIcon: 'size-5' }"
|
|
158
|
+
>
|
|
159
|
+
<USkeleton class="size-5 rounded-full" />
|
|
160
|
+
<USkeleton
|
|
161
|
+
v-if="!collapsed"
|
|
162
|
+
class="h-4 w-20"
|
|
163
|
+
/>
|
|
164
|
+
</UButton>
|
|
165
|
+
</template>
|
|
166
|
+
</ClientOnly>
|
|
167
|
+
</template>
|
|
168
|
+
</UDashboardSidebar>
|
|
169
|
+
|
|
170
|
+
<ClientOnly>
|
|
171
|
+
<SearchDashboardSearch />
|
|
172
|
+
<template #fallback>
|
|
173
|
+
<span />
|
|
174
|
+
</template>
|
|
175
|
+
</ClientOnly>
|
|
176
|
+
|
|
177
|
+
<slot />
|
|
178
|
+
|
|
179
|
+
<ClientOnly>
|
|
180
|
+
<AssistantPanel />
|
|
181
|
+
<template #fallback>
|
|
182
|
+
<span />
|
|
183
|
+
</template>
|
|
184
|
+
</ClientOnly>
|
|
185
|
+
</UDashboardGroup>
|
|
186
|
+
</template>
|