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