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,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>