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,625 @@
1
+ <script setup lang="ts">
2
+ interface Secret {
3
+ id: string
4
+ key: string
5
+ description: string | null
6
+ createdAt: string
7
+ updatedAt: string
8
+ }
9
+
10
+ definePageMeta({
11
+ layout: 'dashboard',
12
+ middleware: 'auth'
13
+ })
14
+
15
+ const { user, updateProfile, changeEmail, changePassword } = useAuth()
16
+ const toast = useToast()
17
+
18
+ // Profile form state
19
+ const profileState = reactive({
20
+ name: ''
21
+ })
22
+ const profileLoading = ref(false)
23
+
24
+ // Email form state
25
+ const emailState = reactive({
26
+ newEmail: ''
27
+ })
28
+ const emailLoading = ref(false)
29
+
30
+ // Password form state
31
+ const passwordState = reactive({
32
+ currentPassword: '',
33
+ newPassword: '',
34
+ confirmPassword: ''
35
+ })
36
+ const passwordLoading = ref(false)
37
+
38
+ // Secrets state
39
+ const secretsData = ref<Secret[]>([])
40
+ const secretsLoading = ref(false)
41
+ const secretModal = ref(false)
42
+ const secretDeleteConfirm = ref(false)
43
+ const secretToDelete = ref<Secret | null>(null)
44
+ const secretSaving = ref(false)
45
+ const editingSecret = ref<Secret | null>(null)
46
+
47
+ const secretForm = reactive({
48
+ key: '',
49
+ value: '',
50
+ description: ''
51
+ })
52
+
53
+ // Initialize forms with current user data
54
+ watch(() => user.value, (u) => {
55
+ if (u) {
56
+ profileState.name = u.name || ''
57
+ emailState.newEmail = u.email || ''
58
+ }
59
+ }, { immediate: true })
60
+
61
+ // Fetch secrets
62
+ async function fetchSecrets() {
63
+ secretsLoading.value = true
64
+ try {
65
+ const { data } = await $fetch<{ data: Secret[] }>('/api/secrets')
66
+ secretsData.value = data
67
+ } catch {
68
+ toast.add({
69
+ title: 'Failed to load secrets',
70
+ color: 'error'
71
+ })
72
+ }
73
+ secretsLoading.value = false
74
+ }
75
+
76
+ // Tab items
77
+ const tabs = [
78
+ { label: 'Account', icon: 'i-lucide-user', value: 'account', slot: 'account' },
79
+ { label: 'Secrets', icon: 'i-lucide-key', value: 'secrets', slot: 'secrets' },
80
+ { label: 'App', icon: 'i-lucide-settings', value: 'app', slot: 'app' }
81
+ ]
82
+
83
+ // Secrets table columns
84
+ const secretColumns = [
85
+ { accessorKey: 'key', header: 'Key' },
86
+ { accessorKey: 'description', header: 'Description' },
87
+ { accessorKey: 'updatedAt', header: 'Last Updated' },
88
+ { accessorKey: 'actions', header: '' }
89
+ ]
90
+
91
+ function openCreateSecret() {
92
+ editingSecret.value = null
93
+ secretForm.key = ''
94
+ secretForm.value = ''
95
+ secretForm.description = ''
96
+ secretModal.value = true
97
+ }
98
+
99
+ function openEditSecret(secret: Secret) {
100
+ editingSecret.value = secret
101
+ secretForm.key = secret.key
102
+ secretForm.value = ''
103
+ secretForm.description = secret.description || ''
104
+ secretModal.value = true
105
+ }
106
+
107
+ function confirmDeleteSecret(secret: Secret) {
108
+ secretToDelete.value = secret
109
+ secretDeleteConfirm.value = true
110
+ }
111
+
112
+ async function handleSecretSubmit() {
113
+ if (!secretForm.key || (!editingSecret.value && !secretForm.value)) {
114
+ toast.add({
115
+ title: 'Missing fields',
116
+ description: editingSecret.value ? 'Key is required.' : 'Key and value are required.',
117
+ color: 'error'
118
+ })
119
+ return
120
+ }
121
+
122
+ secretSaving.value = true
123
+ try {
124
+ if (editingSecret.value) {
125
+ await $fetch(`/api/secrets/${editingSecret.value.key}`, {
126
+ method: 'PUT',
127
+ body: {
128
+ value: secretForm.value || undefined,
129
+ description: secretForm.description || undefined
130
+ }
131
+ })
132
+ toast.add({
133
+ title: 'Secret updated',
134
+ color: 'success'
135
+ })
136
+ } else {
137
+ await $fetch('/api/secrets', {
138
+ method: 'POST',
139
+ body: {
140
+ key: secretForm.key,
141
+ value: secretForm.value,
142
+ description: secretForm.description || undefined
143
+ }
144
+ })
145
+ toast.add({
146
+ title: 'Secret created',
147
+ color: 'success'
148
+ })
149
+ }
150
+ secretModal.value = false
151
+ await fetchSecrets()
152
+ } catch (err: unknown) {
153
+ const error = err as { data?: { message?: string } }
154
+ toast.add({
155
+ title: editingSecret.value ? 'Failed to update secret' : 'Failed to create secret',
156
+ description: error.data?.message || 'An error occurred',
157
+ color: 'error'
158
+ })
159
+ }
160
+ secretSaving.value = false
161
+ }
162
+
163
+ async function handleDeleteSecret() {
164
+ if (!secretToDelete.value) return
165
+
166
+ try {
167
+ await $fetch(`/api/secrets/${secretToDelete.value.key}`, {
168
+ method: 'DELETE'
169
+ })
170
+ toast.add({
171
+ title: 'Secret deleted',
172
+ color: 'success'
173
+ })
174
+ secretDeleteConfirm.value = false
175
+ secretToDelete.value = null
176
+ await fetchSecrets()
177
+ } catch {
178
+ toast.add({
179
+ title: 'Failed to delete secret',
180
+ color: 'error'
181
+ })
182
+ }
183
+ }
184
+
185
+ function formatDate(dateStr: string) {
186
+ return new Date(dateStr).toLocaleDateString('en-US', {
187
+ month: 'short',
188
+ day: 'numeric',
189
+ year: 'numeric'
190
+ })
191
+ }
192
+
193
+ // Load secrets when component mounts
194
+ onMounted(() => {
195
+ fetchSecrets()
196
+ })
197
+
198
+ // Form handlers
199
+ async function handleProfileSubmit() {
200
+ profileLoading.value = true
201
+ const result = await updateProfile({
202
+ name: profileState.name
203
+ })
204
+
205
+ if (result.error) {
206
+ toast.add({
207
+ title: 'Update failed',
208
+ description: result.error.message,
209
+ color: 'error'
210
+ })
211
+ } else {
212
+ toast.add({
213
+ title: 'Profile updated',
214
+ description: 'Your name has been updated successfully.',
215
+ color: 'success'
216
+ })
217
+ }
218
+ profileLoading.value = false
219
+ }
220
+
221
+ async function handleEmailSubmit() {
222
+ if (!emailState.newEmail) {
223
+ toast.add({
224
+ title: 'Email required',
225
+ description: 'Please enter a new email address.',
226
+ color: 'error'
227
+ })
228
+ return
229
+ }
230
+
231
+ emailLoading.value = true
232
+ const result = await changeEmail(emailState.newEmail)
233
+
234
+ if (result.error) {
235
+ toast.add({
236
+ title: 'Email change failed',
237
+ description: result.error.message,
238
+ color: 'error'
239
+ })
240
+ } else {
241
+ toast.add({
242
+ title: 'Email updated',
243
+ description: 'Your email has been changed successfully.',
244
+ color: 'success'
245
+ })
246
+ }
247
+ emailLoading.value = false
248
+ }
249
+
250
+ async function handlePasswordSubmit() {
251
+ // Client-side validation
252
+ if (passwordState.newPassword !== passwordState.confirmPassword) {
253
+ toast.add({
254
+ title: 'Passwords do not match',
255
+ description: 'Please make sure your new passwords match.',
256
+ color: 'error'
257
+ })
258
+ return
259
+ }
260
+
261
+ if (!passwordState.currentPassword || !passwordState.newPassword) {
262
+ toast.add({
263
+ title: 'Missing fields',
264
+ description: 'Please fill in all password fields.',
265
+ color: 'error'
266
+ })
267
+ return
268
+ }
269
+
270
+ passwordLoading.value = true
271
+ const result = await changePassword({
272
+ currentPassword: passwordState.currentPassword,
273
+ newPassword: passwordState.newPassword
274
+ })
275
+
276
+ if (result.error) {
277
+ toast.add({
278
+ title: 'Password change failed',
279
+ description: result.error.message,
280
+ color: 'error'
281
+ })
282
+ } else {
283
+ toast.add({
284
+ title: 'Password changed',
285
+ description: 'Your password has been changed successfully.',
286
+ color: 'success'
287
+ })
288
+ // Clear form
289
+ passwordState.currentPassword = ''
290
+ passwordState.newPassword = ''
291
+ passwordState.confirmPassword = ''
292
+ }
293
+ passwordLoading.value = false
294
+ }
295
+ </script>
296
+
297
+ <template>
298
+ <UDashboardPanel
299
+ id="settings"
300
+ grow
301
+ >
302
+ <UDashboardNavbar title="Settings">
303
+ <template #right>
304
+ <UColorModeButton />
305
+ </template>
306
+ </UDashboardNavbar>
307
+
308
+ <div class="p-6">
309
+ <ClientOnly>
310
+ <UTabs
311
+ :items="tabs"
312
+ default-value="account"
313
+ class="w-full mx-auto"
314
+ :ui="{ list: 'max-w-xl' }"
315
+ >
316
+ <!-- Account Tab -->
317
+ <template #account>
318
+ <div class="space-y-8 max-w-2xl mx-auto py-6">
319
+ <!-- Profile Section -->
320
+ <div>
321
+ <h3 class="text-lg font-semibold mb-4">
322
+ Profile
323
+ </h3>
324
+ <UForm
325
+ :state="profileState"
326
+ class="space-y-4"
327
+ @submit="handleProfileSubmit"
328
+ >
329
+ <UFormField
330
+ label="Name"
331
+ name="name"
332
+ >
333
+ <UInput
334
+ v-model="profileState.name"
335
+ placeholder="Your name"
336
+ class="w-full"
337
+ />
338
+ </UFormField>
339
+ <UButton
340
+ type="submit"
341
+ :loading="profileLoading"
342
+ >
343
+ Save Name
344
+ </UButton>
345
+ </UForm>
346
+ </div>
347
+
348
+ <USeparator />
349
+
350
+ <!-- Email Section -->
351
+ <div>
352
+ <h3 class="text-lg font-semibold mb-4">
353
+ Email Address
354
+ </h3>
355
+ <UForm
356
+ :state="emailState"
357
+ class="space-y-4"
358
+ @submit="handleEmailSubmit"
359
+ >
360
+ <UFormField
361
+ label="Email"
362
+ name="newEmail"
363
+ >
364
+ <UInput
365
+ v-model="emailState.newEmail"
366
+ type="email"
367
+ placeholder="your@email.com"
368
+ class="w-full"
369
+ />
370
+ </UFormField>
371
+ <UButton
372
+ type="submit"
373
+ :loading="emailLoading"
374
+ >
375
+ Change Email
376
+ </UButton>
377
+ </UForm>
378
+ </div>
379
+
380
+ <USeparator />
381
+
382
+ <!-- Password Section -->
383
+ <div>
384
+ <h3 class="text-lg font-semibold mb-4">
385
+ Change Password
386
+ </h3>
387
+ <UForm
388
+ :state="passwordState"
389
+ class="space-y-4"
390
+ @submit="handlePasswordSubmit"
391
+ >
392
+ <UFormField
393
+ label="Current Password"
394
+ name="currentPassword"
395
+ >
396
+ <UInput
397
+ v-model="passwordState.currentPassword"
398
+ type="password"
399
+ class="w-full"
400
+ />
401
+ </UFormField>
402
+ <UFormField
403
+ label="New Password"
404
+ name="newPassword"
405
+ >
406
+ <UInput
407
+ v-model="passwordState.newPassword"
408
+ type="password"
409
+ class="w-full"
410
+ />
411
+ </UFormField>
412
+ <UFormField
413
+ label="Confirm New Password"
414
+ name="confirmPassword"
415
+ >
416
+ <UInput
417
+ v-model="passwordState.confirmPassword"
418
+ type="password"
419
+ class="w-full"
420
+ />
421
+ </UFormField>
422
+ <UButton
423
+ type="submit"
424
+ :loading="passwordLoading"
425
+ >
426
+ Change Password
427
+ </UButton>
428
+ </UForm>
429
+ </div>
430
+ </div>
431
+ </template>
432
+
433
+ <!-- Secrets Tab -->
434
+ <template #secrets>
435
+ <div class="py-6">
436
+ <div class="flex items-center justify-between mb-6">
437
+ <div>
438
+ <h3 class="text-lg font-semibold">
439
+ Secrets
440
+ </h3>
441
+ <p class="text-sm text-dimmed">
442
+ Encrypted key-value store for skills and integrations.
443
+ </p>
444
+ </div>
445
+ <UButton
446
+ icon="i-lucide-plus"
447
+ @click="openCreateSecret"
448
+ >
449
+ Add Secret
450
+ </UButton>
451
+ </div>
452
+
453
+ <UTable
454
+ :columns="secretColumns"
455
+ :data="secretsData"
456
+ :loading="secretsLoading"
457
+ >
458
+ <template #key-cell="{ row }">
459
+ <code class="text-sm bg-elevated px-2 py-0.5 rounded">{{ row.original.key }}</code>
460
+ </template>
461
+ <template #description-cell="{ row }">
462
+ <span class="text-dimmed">{{ row.original.description || '—' }}</span>
463
+ </template>
464
+ <template #updatedAt-cell="{ row }">
465
+ <span class="text-dimmed text-sm">{{ formatDate(row.original.updatedAt) }}</span>
466
+ </template>
467
+ <template #actions-cell="{ row }">
468
+ <div class="flex gap-2 justify-end">
469
+ <UButton
470
+ variant="ghost"
471
+ icon="i-lucide-pencil"
472
+ size="xs"
473
+ @click="openEditSecret(row.original)"
474
+ />
475
+ <UButton
476
+ variant="ghost"
477
+ color="error"
478
+ icon="i-lucide-trash-2"
479
+ size="xs"
480
+ @click="confirmDeleteSecret(row.original)"
481
+ />
482
+ </div>
483
+ </template>
484
+ <template #empty>
485
+ <div class="py-12 text-center">
486
+ <UIcon
487
+ name="i-lucide-key"
488
+ class="size-12 mx-auto mb-4 text-dimmed"
489
+ />
490
+ <p class="text-dimmed">
491
+ No secrets yet. Add one to get started.
492
+ </p>
493
+ </div>
494
+ </template>
495
+ </UTable>
496
+ </div>
497
+ </template>
498
+
499
+ <!-- App Tab -->
500
+ <template #app>
501
+ <div class="py-12 text-center">
502
+ <UIcon
503
+ name="i-lucide-settings"
504
+ class="size-16 mx-auto mb-4 text-dimmed"
505
+ />
506
+ <h3 class="text-lg font-semibold mb-2">
507
+ App Settings
508
+ </h3>
509
+ <p class="text-dimmed">
510
+ Coming soon...
511
+ </p>
512
+ </div>
513
+ </template>
514
+ </UTabs>
515
+
516
+ <template #fallback>
517
+ <div class="space-y-8 max-w-xl mx-auto py-6">
518
+ <USkeleton class="h-10 w-full" />
519
+ <div class="space-y-4">
520
+ <USkeleton class="h-5 w-20" />
521
+ <USkeleton class="h-10 w-full" />
522
+ <USkeleton class="h-10 w-28" />
523
+ </div>
524
+ </div>
525
+ </template>
526
+ </ClientOnly>
527
+ </div>
528
+
529
+ <!-- Secret Create/Edit Modal -->
530
+ <UModal v-model:open="secretModal">
531
+ <template #header>
532
+ <h3 class="text-lg font-semibold">
533
+ {{ editingSecret ? 'Edit Secret' : 'Add Secret' }}
534
+ </h3>
535
+ </template>
536
+ <template #body>
537
+ <UForm
538
+ :state="secretForm"
539
+ class="space-y-4"
540
+ @submit="handleSecretSubmit"
541
+ >
542
+ <UFormField
543
+ label="Key"
544
+ name="key"
545
+ :hint="editingSecret ? '' : 'SCREAMING_SNAKE_CASE'"
546
+ >
547
+ <UInput
548
+ v-model="secretForm.key"
549
+ :disabled="!!editingSecret"
550
+ placeholder="MY_API_KEY"
551
+ class="w-full font-mono"
552
+ />
553
+ </UFormField>
554
+ <UFormField
555
+ label="Value"
556
+ name="value"
557
+ :hint="editingSecret ? 'Leave empty to keep current value' : ''"
558
+ >
559
+ <UInput
560
+ v-model="secretForm.value"
561
+ type="password"
562
+ :placeholder="editingSecret ? '••••••••' : 'Secret value'"
563
+ class="w-full"
564
+ />
565
+ </UFormField>
566
+ <UFormField
567
+ label="Description"
568
+ name="description"
569
+ >
570
+ <UInput
571
+ v-model="secretForm.description"
572
+ placeholder="Optional description"
573
+ class="w-full"
574
+ />
575
+ </UFormField>
576
+ <div class="flex justify-end gap-2 pt-4">
577
+ <UButton
578
+ variant="ghost"
579
+ @click="secretModal = false"
580
+ >
581
+ Cancel
582
+ </UButton>
583
+ <UButton
584
+ type="submit"
585
+ :loading="secretSaving"
586
+ >
587
+ {{ editingSecret ? 'Update' : 'Create' }}
588
+ </UButton>
589
+ </div>
590
+ </UForm>
591
+ </template>
592
+ </UModal>
593
+
594
+ <!-- Delete Confirmation Modal -->
595
+ <UModal v-model:open="secretDeleteConfirm">
596
+ <template #header>
597
+ <h3 class="text-lg font-semibold">
598
+ Delete Secret
599
+ </h3>
600
+ </template>
601
+ <template #body>
602
+ <p class="mb-4">
603
+ Are you sure you want to delete <code class="bg-elevated px-2 py-0.5 rounded">{{ secretToDelete?.key }}</code>?
604
+ </p>
605
+ <p class="text-sm text-dimmed">
606
+ This action cannot be undone. Any skills using this secret will stop working.
607
+ </p>
608
+ <div class="flex justify-end gap-2 pt-6">
609
+ <UButton
610
+ variant="ghost"
611
+ @click="secretDeleteConfirm = false"
612
+ >
613
+ Cancel
614
+ </UButton>
615
+ <UButton
616
+ color="error"
617
+ @click="handleDeleteSecret"
618
+ >
619
+ Delete
620
+ </UButton>
621
+ </div>
622
+ </template>
623
+ </UModal>
624
+ </UDashboardPanel>
625
+ </template>