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,280 @@
1
+ <script setup lang="ts">
2
+ import type { EditorToolbarItem } from '@nuxt/ui'
3
+ import type { Task, Project, CreateTaskInput, UpdateTaskInput, TaskStatus } from '~~/shared/types'
4
+
5
+ const props = defineProps<{
6
+ open: boolean
7
+ task?: Task | null
8
+ projects: Project[]
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ 'update:open': [value: boolean]
13
+ 'submit': [data: CreateTaskInput | UpdateTaskInput]
14
+ }>()
15
+
16
+ const isEditing = computed(() => !!props.task?.id)
17
+ const title = computed(() => isEditing.value ? 'Edit Task' : 'Create Task')
18
+
19
+ // Form state
20
+ const formTitle = ref('')
21
+ const description = ref('')
22
+ const status = ref<TaskStatus>('todo')
23
+ const priority = ref(2)
24
+ const projectId = ref<string | null>(null)
25
+ const dueDate = ref('')
26
+ const tags = ref<string[]>([])
27
+
28
+ // Status options
29
+ const statusOptions = [
30
+ { value: 'todo', label: 'Todo' },
31
+ { value: 'in_progress', label: 'In Progress' },
32
+ { value: 'done', label: 'Done' },
33
+ { value: 'blocked', label: 'Blocked' }
34
+ ]
35
+
36
+ // Priority options
37
+ const priorityOptions = [
38
+ { value: 1, label: 'Low' },
39
+ { value: 2, label: 'Medium' },
40
+ { value: 3, label: 'High' }
41
+ ]
42
+
43
+ // Editor toolbar
44
+ const editorToolbar: EditorToolbarItem[][] = [
45
+ [
46
+ { kind: 'mark', mark: 'bold', icon: 'i-lucide-bold' },
47
+ { kind: 'mark', mark: 'italic', icon: 'i-lucide-italic' },
48
+ { kind: 'mark', mark: 'code', icon: 'i-lucide-code' }
49
+ ],
50
+ [
51
+ { kind: 'bulletList', icon: 'i-lucide-list' },
52
+ { kind: 'orderedList', icon: 'i-lucide-list-ordered' }
53
+ ],
54
+ [
55
+ { kind: 'link', icon: 'i-lucide-link' }
56
+ ]
57
+ ]
58
+
59
+ // Editor content (use undefined for empty to avoid TipTap issues)
60
+ const editorContent = computed(() => description.value || undefined)
61
+
62
+ // Initialize form when task changes
63
+ watch(() => props.task, (task) => {
64
+ if (task) {
65
+ formTitle.value = task.title
66
+ description.value = task.description || ''
67
+ status.value = task.status
68
+ priority.value = task.priority
69
+ projectId.value = task.projectId || null
70
+ dueDate.value = task.dueDate
71
+ ? new Date(task.dueDate).toISOString().split('T')[0] || ''
72
+ : ''
73
+ tags.value = task.tags || []
74
+ } else {
75
+ resetForm()
76
+ }
77
+ }, { immediate: true })
78
+
79
+ // Reset form when closing
80
+ watch(() => props.open, (open) => {
81
+ if (!open) resetForm()
82
+ })
83
+
84
+ function resetForm() {
85
+ formTitle.value = ''
86
+ description.value = ''
87
+ status.value = 'todo'
88
+ priority.value = 2
89
+ projectId.value = null
90
+ dueDate.value = ''
91
+ tags.value = []
92
+ }
93
+
94
+ function close() {
95
+ emit('update:open', false)
96
+ }
97
+
98
+ function handleSubmit() {
99
+ if (!formTitle.value.trim()) return
100
+
101
+ const data: CreateTaskInput | UpdateTaskInput = {
102
+ title: formTitle.value.trim(),
103
+ description: description.value.trim() || undefined,
104
+ status: status.value,
105
+ priority: priority.value,
106
+ projectId: projectId.value,
107
+ dueDate: dueDate.value || undefined,
108
+ tags: tags.value
109
+ }
110
+
111
+ emit('submit', data)
112
+ close()
113
+ }
114
+ </script>
115
+
116
+ <template>
117
+ <USlideover
118
+ :open="open"
119
+ :title="title"
120
+ @update:open="emit('update:open', $event)"
121
+ >
122
+ <template #body>
123
+ <div class="task-form-body">
124
+ <!-- Title -->
125
+ <UFormField
126
+ label="Title"
127
+ required
128
+ >
129
+ <UInput
130
+ v-model="formTitle"
131
+ placeholder="Task title..."
132
+ autofocus
133
+ class="w-full"
134
+ />
135
+ </UFormField>
136
+
137
+ <!-- Description - grows to fill available space -->
138
+ <div class="task-form-description">
139
+ <label class="block text-sm font-medium text-default mb-1.5">Description</label>
140
+ <UEditor
141
+ v-slot="{ editor }"
142
+ :model-value="editorContent"
143
+ content-type="markdown"
144
+ placeholder="Add a description..."
145
+ class="task-editor"
146
+ @update:model-value="description = $event"
147
+ >
148
+ <UEditorToolbar
149
+ :editor="editor"
150
+ :items="editorToolbar"
151
+ class="border-b border-default"
152
+ />
153
+ </UEditor>
154
+ </div>
155
+
156
+ <!-- Other fields -->
157
+ <div class="task-form-fields">
158
+ <!-- Status & Priority -->
159
+ <div class="grid grid-cols-2 gap-4">
160
+ <UFormField label="Status">
161
+ <USelect
162
+ v-model="status"
163
+ :items="statusOptions"
164
+ value-key="value"
165
+ class="w-full"
166
+ />
167
+ </UFormField>
168
+
169
+ <UFormField label="Priority">
170
+ <USelect
171
+ v-model="priority"
172
+ :items="priorityOptions"
173
+ value-key="value"
174
+ class="w-full"
175
+ />
176
+ </UFormField>
177
+ </div>
178
+
179
+ <!-- Project -->
180
+ <UFormField label="Project">
181
+ <TasksProjectSelect
182
+ v-model="projectId"
183
+ :projects="projects"
184
+ class="w-full"
185
+ />
186
+ </UFormField>
187
+
188
+ <!-- Due Date -->
189
+ <UFormField label="Due Date">
190
+ <UInput
191
+ v-model="dueDate"
192
+ type="date"
193
+ class="w-full"
194
+ />
195
+ </UFormField>
196
+
197
+ <!-- Tags -->
198
+ <UFormField label="Tags">
199
+ <UInputTags
200
+ v-model="tags"
201
+ placeholder="Add tags..."
202
+ class="w-full"
203
+ />
204
+ </UFormField>
205
+ </div>
206
+ </div>
207
+ </template>
208
+
209
+ <template #footer>
210
+ <div class="flex justify-end gap-2 w-full">
211
+ <UButton
212
+ color="neutral"
213
+ variant="ghost"
214
+ @click="close"
215
+ >
216
+ Cancel
217
+ </UButton>
218
+ <UButton
219
+ :disabled="!formTitle.trim()"
220
+ @click="handleSubmit"
221
+ >
222
+ {{ isEditing ? 'Save Changes' : 'Create Task' }}
223
+ </UButton>
224
+ </div>
225
+ </template>
226
+ </USlideover>
227
+ </template>
228
+
229
+ <style>
230
+ /* Body fills available space with flex layout */
231
+ .task-form-body {
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: 1rem;
235
+ height: 100%;
236
+ min-height: 0;
237
+ }
238
+
239
+ /* Description section - grows to fill available space */
240
+ .task-form-description {
241
+ flex: 1;
242
+ display: flex;
243
+ flex-direction: column;
244
+ min-height: 8rem;
245
+ overflow: hidden;
246
+ }
247
+
248
+ /* Other form fields - fixed height */
249
+ .task-form-fields {
250
+ flex-shrink: 0;
251
+ display: flex;
252
+ flex-direction: column;
253
+ gap: 1rem;
254
+ }
255
+
256
+ /* Editor fills available space in description */
257
+ .task-editor {
258
+ flex: 1;
259
+ display: flex;
260
+ flex-direction: column;
261
+ min-height: 0;
262
+ border: 1px solid var(--ui-border);
263
+ border-radius: 0.375rem;
264
+ }
265
+
266
+ .task-editor [data-slot="content"] {
267
+ flex: 1;
268
+ overflow: auto;
269
+ min-height: 0;
270
+ }
271
+
272
+ .task-editor .tiptap {
273
+ min-height: 100%;
274
+ padding: 0.75rem;
275
+ }
276
+
277
+ .task-editor .tiptap:focus {
278
+ outline: none;
279
+ }
280
+ </style>
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import type { Task } from '~~/shared/types'
3
+
4
+ defineProps<{
5
+ tasks: Task[]
6
+ loading?: boolean
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ toggle: [id: string]
11
+ edit: [task: Task]
12
+ delete: [id: string]
13
+ create: []
14
+ view: [task: Task]
15
+ }>()
16
+ </script>
17
+
18
+ <template>
19
+ <div>
20
+ <!-- Loading state -->
21
+ <div
22
+ v-if="loading"
23
+ class="flex items-center justify-center py-12"
24
+ >
25
+ <UIcon
26
+ name="i-lucide-loader-2"
27
+ class="size-8 animate-spin text-dimmed"
28
+ />
29
+ </div>
30
+
31
+ <!-- Empty state -->
32
+ <UCard v-else-if="tasks.length === 0">
33
+ <div class="text-center py-8 text-dimmed">
34
+ <UIcon
35
+ name="i-lucide-check-square"
36
+ class="size-12 mx-auto mb-2 opacity-50"
37
+ />
38
+ <p class="font-medium">
39
+ No tasks yet
40
+ </p>
41
+ <p class="text-sm mt-1">
42
+ Create your first task to get started.
43
+ </p>
44
+ <UButton
45
+ class="mt-4"
46
+ icon="i-lucide-plus"
47
+ label="Add Task"
48
+ @click="emit('create')"
49
+ />
50
+ </div>
51
+ </UCard>
52
+
53
+ <!-- Task list -->
54
+ <div
55
+ v-else
56
+ class="space-y-2"
57
+ >
58
+ <TasksTaskCard
59
+ v-for="task in tasks"
60
+ :key="task.id"
61
+ :task="task"
62
+ @toggle="emit('toggle', $event)"
63
+ @edit="emit('edit', $event)"
64
+ @delete="emit('delete', $event)"
65
+ @view="emit('view', $event)"
66
+ />
67
+ </div>
68
+ </div>
69
+ </template>
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ import type { TocLink } from '~~/shared/types'
3
+
4
+ defineProps<{
5
+ links: TocLink[]
6
+ }>()
7
+
8
+ const activeId = ref<string | null>(null)
9
+
10
+ function scrollToHeading(id: string) {
11
+ const element = document.getElementById(id)
12
+ if (element)
13
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' })
14
+ }
15
+
16
+ onMounted(() => {
17
+ const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')
18
+
19
+ const observer = new IntersectionObserver(
20
+ (entries) => {
21
+ for (const entry of entries) {
22
+ if (entry.isIntersecting) {
23
+ activeId.value = entry.target.id
24
+ break
25
+ }
26
+ }
27
+ },
28
+ { rootMargin: '-80px 0px -80% 0px' }
29
+ )
30
+
31
+ headings.forEach(heading => observer.observe(heading))
32
+
33
+ onUnmounted(() => observer.disconnect())
34
+ })
35
+ </script>
36
+
37
+ <template>
38
+ <nav
39
+ v-if="links.length"
40
+ class="space-y-1"
41
+ >
42
+ <p class="text-sm font-semibold text-muted mb-3">
43
+ On this page
44
+ </p>
45
+
46
+ <template
47
+ v-for="link in links"
48
+ :key="link.id"
49
+ >
50
+ <button
51
+ type="button"
52
+ class="block w-full text-left text-sm py-1 hover:text-primary transition-colors"
53
+ :class="[
54
+ activeId === link.id ? 'text-primary font-medium' : 'text-dimmed',
55
+ link.depth === 2 && 'pl-0',
56
+ link.depth === 3 && 'pl-4',
57
+ link.depth >= 4 && 'pl-8'
58
+ ]"
59
+ @click="scrollToHeading(link.id)"
60
+ >
61
+ {{ link.text }}
62
+ </button>
63
+
64
+ <template v-if="link.children?.length">
65
+ <button
66
+ v-for="child in link.children"
67
+ :key="child.id"
68
+ type="button"
69
+ class="block w-full text-left text-sm py-1 hover:text-primary transition-colors pl-4"
70
+ :class="activeId === child.id ? 'text-primary font-medium' : 'text-dimmed'"
71
+ @click="scrollToHeading(child.id)"
72
+ >
73
+ {{ child.text }}
74
+ </button>
75
+ </template>
76
+ </template>
77
+ </nav>
78
+
79
+ <div
80
+ v-else
81
+ class="text-sm text-dimmed italic"
82
+ >
83
+ No headings found
84
+ </div>
85
+ </template>
@@ -0,0 +1,153 @@
1
+ import type { CronAgent, CronAgentRun, CreateAgentInput, UpdateAgentInput, StatsPeriod, AgentGlobalStats, AgentDetailStats } from '~~/shared/types'
2
+
3
+ export function useAgents() {
4
+ const agents = ref<CronAgent[]>([])
5
+ const loading = ref(false)
6
+ const error = ref<string | null>(null)
7
+
8
+ async function fetchAgents() {
9
+ loading.value = true
10
+ error.value = null
11
+
12
+ try {
13
+ const response = await $fetch<{ data: CronAgent[] }>('/api/agents')
14
+ agents.value = response.data
15
+ return response.data
16
+ } catch (e) {
17
+ error.value = 'Failed to load agents'
18
+ console.error('Failed to load agents:', e)
19
+ throw e
20
+ } finally {
21
+ loading.value = false
22
+ }
23
+ }
24
+
25
+ async function createAgent(input: CreateAgentInput) {
26
+ try {
27
+ const response = await $fetch<{ data: CronAgent }>('/api/agents', {
28
+ method: 'POST',
29
+ body: input
30
+ })
31
+ agents.value = [response.data, ...agents.value]
32
+ return response.data
33
+ } catch (e) {
34
+ console.error('Failed to create agent:', e)
35
+ throw e
36
+ }
37
+ }
38
+
39
+ async function updateAgent(id: string, input: UpdateAgentInput) {
40
+ try {
41
+ const response = await $fetch<{ data: CronAgent }>(`/api/agents/${id}`, {
42
+ method: 'PATCH',
43
+ body: input
44
+ })
45
+ const index = agents.value.findIndex(a => a.id === id)
46
+ if (index !== -1)
47
+ agents.value[index] = response.data
48
+ return response.data
49
+ } catch (e) {
50
+ console.error('Failed to update agent:', e)
51
+ throw e
52
+ }
53
+ }
54
+
55
+ async function deleteAgent(id: string) {
56
+ try {
57
+ // @ts-expect-error - Nitro route type inference issue with multiple methods on same path
58
+ await $fetch(`/api/agents/${id}`, { method: 'DELETE' })
59
+ agents.value = agents.value.filter(a => a.id !== id)
60
+ } catch (e) {
61
+ console.error('Failed to delete agent:', e)
62
+ throw e
63
+ }
64
+ }
65
+
66
+ async function toggleEnabled(id: string) {
67
+ const agent = agents.value.find(a => a.id === id)
68
+ if (!agent) return
69
+
70
+ return updateAgent(id, { enabled: !agent.enabled })
71
+ }
72
+
73
+ async function runAgent(id: string) {
74
+ try {
75
+ await $fetch(`/api/agents/${id}/run`, { method: 'POST' })
76
+ } catch (e) {
77
+ console.error('Failed to run agent:', e)
78
+ throw e
79
+ }
80
+ }
81
+
82
+ async function cancelAgent(id: string) {
83
+ try {
84
+ await $fetch(`/api/agents/${id}/cancel`, { method: 'POST' })
85
+ } catch (e) {
86
+ console.error('Failed to cancel agent:', e)
87
+ throw e
88
+ }
89
+ }
90
+
91
+ async function fetchRuns(agentId: string, period?: StatsPeriod, limit = 100) {
92
+ try {
93
+ const response = await $fetch<{ data: CronAgentRun[] }>(`/api/agents/${agentId}/runs`, {
94
+ query: { limit, period }
95
+ })
96
+ return response.data
97
+ } catch (e) {
98
+ console.error('Failed to fetch runs:', e)
99
+ throw e
100
+ }
101
+ }
102
+
103
+ async function fetchAgent(agentId: string) {
104
+ try {
105
+ const response = await $fetch<{ data: CronAgent }>(`/api/agents/${agentId}`)
106
+ return response.data
107
+ } catch (e) {
108
+ console.error('Failed to fetch agent:', e)
109
+ throw e
110
+ }
111
+ }
112
+
113
+ async function fetchGlobalStats(period: StatsPeriod = '7d') {
114
+ try {
115
+ const response = await $fetch<{ data: AgentGlobalStats }>('/api/agents/stats', {
116
+ query: { period }
117
+ })
118
+ return response.data
119
+ } catch (e) {
120
+ console.error('Failed to fetch global stats:', e)
121
+ throw e
122
+ }
123
+ }
124
+
125
+ async function fetchAgentStats(agentId: string, period: StatsPeriod = '7d') {
126
+ try {
127
+ const response = await $fetch<{ data: AgentDetailStats }>(`/api/agents/${agentId}/stats`, {
128
+ query: { period }
129
+ })
130
+ return response.data
131
+ } catch (e) {
132
+ console.error('Failed to fetch agent stats:', e)
133
+ throw e
134
+ }
135
+ }
136
+
137
+ return {
138
+ agents,
139
+ loading,
140
+ error,
141
+ fetchAgents,
142
+ fetchAgent,
143
+ createAgent,
144
+ updateAgent,
145
+ deleteAgent,
146
+ toggleEnabled,
147
+ runAgent,
148
+ cancelAgent,
149
+ fetchRuns,
150
+ fetchGlobalStats,
151
+ fetchAgentStats
152
+ }
153
+ }
@@ -0,0 +1,73 @@
1
+ import { createAuthClient } from 'better-auth/vue'
2
+
3
+ let _authClient: ReturnType<typeof createAuthClient> | null = null
4
+
5
+ function getAuthClient() {
6
+ if (!_authClient) {
7
+ const baseURL = import.meta.server
8
+ ? (process.env.BETTER_AUTH_URL || 'http://localhost:3000')
9
+ : window.location.origin
10
+
11
+ _authClient = createAuthClient({ baseURL })
12
+ }
13
+ return _authClient
14
+ }
15
+
16
+ export function useAuth() {
17
+ const authClient = getAuthClient()
18
+ const session = authClient.useSession()
19
+
20
+ async function login(email: string, password: string) {
21
+ const result = await authClient.signIn.email({ email, password })
22
+ return result
23
+ }
24
+
25
+ async function logout() {
26
+ await authClient.signOut()
27
+ navigateTo('/login')
28
+ }
29
+
30
+ async function register(email: string, password: string, name: string) {
31
+ const result = await authClient.signUp.email({ email, password, name })
32
+ return result
33
+ }
34
+
35
+ async function updateProfile(data: { name?: string, image?: string }) {
36
+ const result = await authClient.updateUser(data)
37
+ return result
38
+ }
39
+
40
+ async function changeEmail(newEmail: string) {
41
+ // Use custom endpoint to bypass BetterAuth verification flow
42
+ try {
43
+ await $fetch('/api/user/email', {
44
+ method: 'PATCH',
45
+ body: { email: newEmail }
46
+ })
47
+ // Refresh session to get updated user data
48
+ await authClient.getSession({ fetchOptions: { cache: 'no-store' } })
49
+ return { data: { success: true }, error: null }
50
+ } catch (err: unknown) {
51
+ const error = err as { data?: { message?: string } }
52
+ return { data: null, error: { message: error.data?.message || 'Failed to change email' } }
53
+ }
54
+ }
55
+
56
+ async function changePassword(data: { currentPassword: string, newPassword: string }) {
57
+ const result = await authClient.changePassword(data)
58
+ return result
59
+ }
60
+
61
+ return {
62
+ session,
63
+ user: computed(() => session.value?.data?.user),
64
+ isAuthenticated: computed(() => !!session.value?.data?.user),
65
+ isPending: computed(() => session.value?.isPending ?? true),
66
+ login,
67
+ logout,
68
+ register,
69
+ updateProfile,
70
+ changeEmail,
71
+ changePassword
72
+ }
73
+ }