arkaos 3.71.0 → 3.72.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.
@@ -1,4 +1,7 @@
1
1
  <script setup lang="ts">
2
+ // PR92d v3.42.0 — primary color picker.
3
+ import { THEME_COLOR_OPTIONS, useThemeColor } from '~/composables/useThemeColor'
4
+
2
5
  interface ProfileResponse {
3
6
  version: string
4
7
  name: string
@@ -22,7 +25,7 @@ const {
22
25
  data: profile,
23
26
  status: profileStatus,
24
27
  error: profileError,
25
- refresh: refreshProfile,
28
+ refresh: refreshProfile
26
29
  } = await fetchApi<ProfileResponse>('/api/profile')
27
30
 
28
31
  const profileDraft = ref({
@@ -32,7 +35,7 @@ const profileDraft = ref({
32
35
  market: profile.value?.market ?? '',
33
36
  language: profile.value?.language ?? 'en',
34
37
  vaultPath: profile.value?.vaultPath ?? '',
35
- projectsDir: profile.value?.projectsDir ?? '',
38
+ projectsDir: profile.value?.projectsDir ?? ''
36
39
  })
37
40
 
38
41
  watch(profile, (p) => {
@@ -44,7 +47,7 @@ watch(profile, (p) => {
44
47
  market: p.market,
45
48
  language: p.language,
46
49
  vaultPath: p.vaultPath,
47
- projectsDir: p.projectsDir,
50
+ projectsDir: p.projectsDir
48
51
  }
49
52
  }, { immediate: true })
50
53
 
@@ -68,7 +71,7 @@ async function testVault() {
68
71
  if (profileDraft.value.vaultPath !== profile.value?.vaultPath) {
69
72
  await $fetch(`${apiBase}/api/profile`, {
70
73
  method: 'POST',
71
- body: { vaultPath: profileDraft.value.vaultPath },
74
+ body: { vaultPath: profileDraft.value.vaultPath }
72
75
  })
73
76
  }
74
77
  vaultStatus.value = await $fetch<VaultStatus>(`${apiBase}/api/settings/vault`)
@@ -76,13 +79,13 @@ async function testVault() {
76
79
  title: vaultStatus.value.exists ? 'Vault reachable' : 'Vault not found',
77
80
  description: vaultStatus.value.vault_path || 'Set a path first',
78
81
  color: vaultStatus.value.exists ? 'success' : 'warning',
79
- icon: vaultStatus.value.exists ? 'i-lucide-check-circle' : 'i-lucide-alert-circle',
82
+ icon: vaultStatus.value.exists ? 'i-lucide-check-circle' : 'i-lucide-alert-circle'
80
83
  })
81
84
  } catch (err) {
82
85
  toast.add({
83
86
  title: 'Test failed',
84
87
  description: err instanceof Error ? err.message : 'unknown error',
85
- color: 'error',
88
+ color: 'error'
86
89
  })
87
90
  } finally {
88
91
  testingVault.value = false
@@ -94,19 +97,19 @@ async function saveProfile() {
94
97
  try {
95
98
  await $fetch<ProfileResponse>(`${apiBase}/api/profile`, {
96
99
  method: 'POST',
97
- body: profileDraft.value,
100
+ body: profileDraft.value
98
101
  })
99
102
  await refreshProfile()
100
103
  toast.add({
101
104
  title: 'Profile saved',
102
105
  description: 'Settings written to ~/.arkaos/profile.json',
103
- color: 'success',
106
+ color: 'success'
104
107
  })
105
108
  } catch (err) {
106
109
  toast.add({
107
110
  title: 'Save failed',
108
111
  description: err instanceof Error ? err.message : 'unknown error',
109
- color: 'error',
112
+ color: 'error'
110
113
  })
111
114
  } finally {
112
115
  savingProfile.value = false
@@ -115,7 +118,7 @@ async function saveProfile() {
115
118
 
116
119
  const languageOptions = [
117
120
  { label: 'English', value: 'en' },
118
- { label: 'Português', value: 'pt' },
121
+ { label: 'Português', value: 'pt' }
119
122
  ]
120
123
 
121
124
  const roleOptions = [
@@ -125,16 +128,23 @@ const roleOptions = [
125
128
  { label: 'Engineer', value: 'engineer' },
126
129
  { label: 'Designer', value: 'designer' },
127
130
  { label: 'Operator', value: 'operator' },
128
- { label: 'Consultant', value: 'consultant' },
131
+ { label: 'Consultant', value: 'consultant' }
129
132
  ]
130
133
 
131
134
  // ─── API Keys (preserved from earlier) ──────────────────────────────────
132
135
 
136
+ interface KeyRow {
137
+ key: string
138
+ provider: string
139
+ configured: boolean
140
+ used_for?: string
141
+ }
142
+
133
143
  const {
134
144
  data: keysData,
135
145
  status: keysStatus,
136
- refresh: refreshKeys,
137
- } = fetchApi<any>('/api/keys')
146
+ refresh: refreshKeys
147
+ } = fetchApi<{ keys: KeyRow[] }>('/api/keys')
138
148
 
139
149
  const keys = computed(() => keysData.value?.keys ?? [])
140
150
 
@@ -154,13 +164,13 @@ async function saveKey() {
154
164
  try {
155
165
  await $fetch(`${apiBase}/api/keys`, {
156
166
  method: 'POST',
157
- body: { key: keyName, value: newValue.value },
167
+ body: { key: keyName, value: newValue.value }
158
168
  })
159
169
  newKey.value = ''
160
170
  newValue.value = ''
161
171
  customKeyName.value = ''
162
172
  await refreshKeys()
163
- } catch {}
173
+ } catch { /* best-effort; surfaced via list refresh */ }
164
174
  saving.value = false
165
175
  }
166
176
 
@@ -169,7 +179,7 @@ async function deleteKey(keyName: string) {
169
179
  try {
170
180
  await $fetch(`${apiBase}/api/keys/${keyName}`, { method: 'DELETE' })
171
181
  await refreshKeys()
172
- } catch {}
182
+ } catch { /* best-effort; surfaced via list refresh */ }
173
183
  deletingKey.value = null
174
184
  }
175
185
 
@@ -177,7 +187,7 @@ const keyOptions = [
177
187
  { label: 'OPENAI_API_KEY', value: 'OPENAI_API_KEY' },
178
188
  { label: 'FAL_API_KEY', value: 'FAL_API_KEY' },
179
189
  { label: 'GOOGLE_API_KEY', value: 'GOOGLE_API_KEY' },
180
- { label: 'Custom...', value: 'custom' },
190
+ { label: 'Custom...', value: 'custom' }
181
191
  ]
182
192
 
183
193
  // ─── PR63b v2.89.0 — MCPs / Hooks / Plugins / Theme sections ────────────
@@ -231,11 +241,8 @@ const colorMode = useColorMode()
231
241
  const themeOptions = [
232
242
  { label: 'System (auto)', value: 'system' },
233
243
  { label: 'Light', value: 'light' },
234
- { label: 'Dark', value: 'dark' },
244
+ { label: 'Dark', value: 'dark' }
235
245
  ]
236
-
237
- // PR92d v3.42.0 — primary color picker.
238
- import { THEME_COLOR_OPTIONS, useThemeColor } from '~/composables/useThemeColor'
239
246
  const themeColor = useThemeColor()
240
247
  const themeColorOptions = THEME_COLOR_OPTIONS
241
248
 
@@ -256,19 +263,52 @@ function formatInstalledAt(iso: string): string {
256
263
 
257
264
  // ─── Section nav ────────────────────────────────────────────────────────
258
265
 
259
- type SectionId = 'profile' | 'projects' | 'keys' | 'mcps' | 'hooks' | 'plugins' | 'theme'
260
-
261
- const sections: { id: SectionId; label: string; icon: string }[] = [
262
- { id: 'profile', label: 'Profile', icon: 'i-lucide-user-circle' },
263
- { id: 'projects', label: 'Projects', icon: 'i-lucide-folders' },
264
- { id: 'keys', label: 'API Keys', icon: 'i-lucide-key' },
265
- { id: 'mcps', label: 'MCPs', icon: 'i-lucide-plug-2' },
266
- { id: 'hooks', label: 'Hooks', icon: 'i-lucide-webhook' },
267
- { id: 'plugins', label: 'Plugins', icon: 'i-lucide-puzzle' },
268
- { id: 'theme', label: 'Theme', icon: 'i-lucide-palette' },
266
+ type SectionId = 'profile' | 'projects' | 'keys' | 'mcps' | 'hooks' | 'plugins' | 'theme' | 'updates'
267
+
268
+ const sections: { id: SectionId, label: string, icon: string }[] = [
269
+ { id: 'profile', label: 'Profile', icon: 'i-lucide-user-circle' },
270
+ { id: 'projects', label: 'Projects', icon: 'i-lucide-folders' },
271
+ { id: 'keys', label: 'API Keys', icon: 'i-lucide-key' },
272
+ { id: 'mcps', label: 'MCPs', icon: 'i-lucide-plug-2' },
273
+ { id: 'hooks', label: 'Hooks', icon: 'i-lucide-webhook' },
274
+ { id: 'plugins', label: 'Plugins', icon: 'i-lucide-puzzle' },
275
+ { id: 'theme', label: 'Theme', icon: 'i-lucide-palette' },
276
+ { id: 'updates', label: 'Updates', icon: 'i-lucide-download' }
269
277
  ]
270
278
 
271
279
  const activeSection = ref<SectionId>('profile')
280
+
281
+ // ─── Updates (v3.72.0) — version check + one-click core update ──────────
282
+ const ver = ref<{ current: string, latest: string | null, update_available: boolean } | null>(null)
283
+ const checkingVer = ref(false)
284
+ const updating = ref(false)
285
+ const updateResult = ref<{ ok: boolean, output: string } | null>(null)
286
+
287
+ async function checkVersion() {
288
+ checkingVer.value = true
289
+ try {
290
+ ver.value = await $fetch(`${apiBase}/api/system/version`)
291
+ } catch {
292
+ ver.value = null
293
+ } finally {
294
+ checkingVer.value = false
295
+ }
296
+ }
297
+
298
+ async function runUpdate() {
299
+ updating.value = true
300
+ updateResult.value = null
301
+ try {
302
+ updateResult.value = await $fetch(`${apiBase}/api/system/update`, { method: 'POST' })
303
+ } catch (e) {
304
+ updateResult.value = { ok: false, output: e instanceof Error ? e.message : String(e) }
305
+ } finally {
306
+ updating.value = false
307
+ checkVersion()
308
+ }
309
+ }
310
+
311
+ onMounted(checkVersion)
272
312
  </script>
273
313
 
274
314
  <template>
@@ -299,8 +339,8 @@ const activeSection = ref<SectionId>('profile')
299
339
  <span>{{ s.label }}</span>
300
340
  </button>
301
341
  <p class="text-xs text-muted px-3 mt-6">
302
- 7 sections. Profile + Projects edit data; everything else is
303
- read-only diagnostics until an explicit edit endpoint lands.
342
+ 8 sections. Profile + Projects edit data; Updates runs the core
343
+ update; everything else is read-only diagnostics.
304
344
  </p>
305
345
  </nav>
306
346
 
@@ -308,7 +348,9 @@ const activeSection = ref<SectionId>('profile')
308
348
  <div>
309
349
  <!-- Profile -->
310
350
  <section v-if="activeSection === 'profile'">
311
- <h2 class="text-lg font-semibold mb-1">Profile</h2>
351
+ <h2 class="text-lg font-semibold mb-1">
352
+ Profile
353
+ </h2>
312
354
  <p class="text-sm text-muted mb-6">
313
355
  Your identity, role, and language. Stored locally at
314
356
  <code class="font-mono text-xs">~/.arkaos/profile.json</code>.
@@ -448,7 +490,9 @@ const activeSection = ref<SectionId>('profile')
448
490
 
449
491
  <!-- Projects -->
450
492
  <section v-else-if="activeSection === 'projects'">
451
- <h2 class="text-lg font-semibold mb-1">Project directories</h2>
493
+ <h2 class="text-lg font-semibold mb-1">
494
+ Project directories
495
+ </h2>
452
496
  <p class="text-sm text-muted mb-6">
453
497
  Directories the sync engine scans for projects.
454
498
  Comma-separated absolute paths (e.g.
@@ -500,7 +544,9 @@ const activeSection = ref<SectionId>('profile')
500
544
 
501
545
  <!-- API Keys -->
502
546
  <section v-else-if="activeSection === 'keys'">
503
- <h2 class="text-lg font-semibold mb-1">API Keys</h2>
547
+ <h2 class="text-lg font-semibold mb-1">
548
+ API Keys
549
+ </h2>
504
550
  <p class="text-sm text-muted mb-6">
505
551
  Configure API keys for external services. Keys are stored
506
552
  locally at <code class="font-mono text-xs">~/.arkaos/keys.json</code>.
@@ -508,11 +554,18 @@ const activeSection = ref<SectionId>('profile')
508
554
 
509
555
  <UCard class="mb-6">
510
556
  <div class="space-y-4">
511
- <p class="text-xs font-semibold text-muted uppercase tracking-wider">Add API Key</p>
557
+ <p class="text-xs font-semibold text-muted uppercase tracking-wider">
558
+ Add API Key
559
+ </p>
512
560
  <div class="grid grid-cols-1 md:grid-cols-3 gap-3 items-end">
513
561
  <div>
514
562
  <label class="text-xs text-muted mb-1 block">Provider</label>
515
- <USelect v-model="newKey" :items="keyOptions" class="w-full" placeholder="Select key..." />
563
+ <USelect
564
+ v-model="newKey"
565
+ :items="keyOptions"
566
+ class="w-full"
567
+ placeholder="Select key..."
568
+ />
516
569
  </div>
517
570
  <div v-if="isCustom">
518
571
  <label class="text-xs text-muted mb-1 block">Key Name</label>
@@ -520,7 +573,12 @@ const activeSection = ref<SectionId>('profile')
520
573
  </div>
521
574
  <div :class="isCustom ? '' : 'md:col-span-1'">
522
575
  <label class="text-xs text-muted mb-1 block">Value</label>
523
- <UInput v-model="newValue" type="password" class="w-full" placeholder="sk-..." />
576
+ <UInput
577
+ v-model="newValue"
578
+ type="password"
579
+ class="w-full"
580
+ placeholder="sk-..."
581
+ />
524
582
  </div>
525
583
  <div>
526
584
  <UButton
@@ -560,9 +618,17 @@ const activeSection = ref<SectionId>('profile')
560
618
  variant="subtle"
561
619
  size="xs"
562
620
  />
563
- <UBadge v-else label="Not Set" color="neutral" variant="outline" size="xs" />
621
+ <UBadge
622
+ v-else
623
+ label="Not Set"
624
+ color="neutral"
625
+ variant="outline"
626
+ size="xs"
627
+ />
564
628
  </div>
565
- <p v-if="k.used_for" class="text-xs text-muted mt-0.5">{{ k.used_for }}</p>
629
+ <p v-if="k.used_for" class="text-xs text-muted mt-0.5">
630
+ {{ k.used_for }}
631
+ </p>
566
632
  <p v-if="k.masked_value && k.configured" class="text-xs font-mono text-muted/60 mt-0.5">
567
633
  {{ k.masked_value }}
568
634
  </p>
@@ -585,7 +651,9 @@ const activeSection = ref<SectionId>('profile')
585
651
  <!-- MCPs -->
586
652
  <section v-else-if="activeSection === 'mcps'">
587
653
  <div class="flex items-baseline justify-between mb-1">
588
- <h2 class="text-lg font-semibold">MCPs</h2>
654
+ <h2 class="text-lg font-semibold">
655
+ MCPs
656
+ </h2>
589
657
  <UButton
590
658
  label="Refresh"
591
659
  variant="ghost"
@@ -601,7 +669,9 @@ const activeSection = ref<SectionId>('profile')
601
669
  </p>
602
670
  <div v-if="!mcps.length" class="rounded-lg border border-default p-6 text-center">
603
671
  <UIcon name="i-lucide-plug-2" class="size-10 text-muted mx-auto mb-2" />
604
- <p class="text-sm text-muted">No MCP servers configured.</p>
672
+ <p class="text-sm text-muted">
673
+ No MCP servers configured.
674
+ </p>
605
675
  </div>
606
676
  <div v-else class="space-y-2">
607
677
  <div
@@ -632,7 +702,9 @@ const activeSection = ref<SectionId>('profile')
632
702
  <!-- Hooks -->
633
703
  <section v-else-if="activeSection === 'hooks'">
634
704
  <div class="flex items-baseline justify-between mb-1">
635
- <h2 class="text-lg font-semibold">Hooks</h2>
705
+ <h2 class="text-lg font-semibold">
706
+ Hooks
707
+ </h2>
636
708
  <UButton
637
709
  label="Refresh"
638
710
  variant="ghost"
@@ -657,7 +729,9 @@ const activeSection = ref<SectionId>('profile')
657
729
  </div>
658
730
  <div v-if="!hooks.length" class="rounded-lg border border-default p-6 text-center">
659
731
  <UIcon name="i-lucide-webhook" class="size-10 text-muted mx-auto mb-2" />
660
- <p class="text-sm text-muted">No hooks wired in settings.json.</p>
732
+ <p class="text-sm text-muted">
733
+ No hooks wired in settings.json.
734
+ </p>
661
735
  </div>
662
736
  <div v-else class="space-y-3">
663
737
  <div
@@ -691,7 +765,9 @@ const activeSection = ref<SectionId>('profile')
691
765
  <!-- Plugins -->
692
766
  <section v-else-if="activeSection === 'plugins'">
693
767
  <div class="flex items-baseline justify-between mb-1">
694
- <h2 class="text-lg font-semibold">Plugins</h2>
768
+ <h2 class="text-lg font-semibold">
769
+ Plugins
770
+ </h2>
695
771
  <UButton
696
772
  label="Refresh"
697
773
  variant="ghost"
@@ -707,7 +783,9 @@ const activeSection = ref<SectionId>('profile')
707
783
  </p>
708
784
  <div v-if="!plugins.length" class="rounded-lg border border-default p-6 text-center">
709
785
  <UIcon name="i-lucide-puzzle" class="size-10 text-muted mx-auto mb-2" />
710
- <p class="text-sm text-muted">No plugins installed.</p>
786
+ <p class="text-sm text-muted">
787
+ No plugins installed.
788
+ </p>
711
789
  <p class="text-xs text-muted mt-2">
712
790
  Try <code class="font-mono">/plugin marketplace add andreagroferreira/arka-os</code>
713
791
  from Claude Code.
@@ -724,8 +802,18 @@ const activeSection = ref<SectionId>('profile')
724
802
  <div class="flex items-center gap-2 mb-0.5">
725
803
  <span class="text-sm font-semibold">{{ p.name }}</span>
726
804
  <UBadge :label="p.marketplace" variant="outline" size="xs" />
727
- <UBadge v-if="p.scope" :label="p.scope" variant="soft" size="xs" />
728
- <UBadge v-if="p.version" :label="`v${p.version}`" variant="subtle" size="xs" />
805
+ <UBadge
806
+ v-if="p.scope"
807
+ :label="p.scope"
808
+ variant="soft"
809
+ size="xs"
810
+ />
811
+ <UBadge
812
+ v-if="p.version"
813
+ :label="`v${p.version}`"
814
+ variant="subtle"
815
+ size="xs"
816
+ />
729
817
  </div>
730
818
  <p v-if="p.installed_at" class="text-xs text-muted">
731
819
  Installed {{ formatInstalledAt(p.installed_at) }}
@@ -737,7 +825,9 @@ const activeSection = ref<SectionId>('profile')
737
825
 
738
826
  <!-- Theme -->
739
827
  <section v-else-if="activeSection === 'theme'">
740
- <h2 class="text-lg font-semibold mb-1">Theme</h2>
828
+ <h2 class="text-lg font-semibold mb-1">
829
+ Theme
830
+ </h2>
741
831
  <p class="text-sm text-muted mb-6">
742
832
  Light / dark / system (follows OS preference).
743
833
  Stored locally by your browser.
@@ -779,7 +869,7 @@ const activeSection = ref<SectionId>('profile')
779
869
  'bg-rose-500': opt.value === 'rose',
780
870
  'bg-amber-500': opt.value === 'amber',
781
871
  'bg-teal-500': opt.value === 'teal',
782
- 'bg-cyan-500': opt.value === 'cyan',
872
+ 'bg-cyan-500': opt.value === 'cyan'
783
873
  }"
784
874
  />
785
875
  {{ opt.label }}
@@ -789,6 +879,80 @@ const activeSection = ref<SectionId>('profile')
789
879
  </div>
790
880
  </UCard>
791
881
  </section>
882
+
883
+ <section v-else-if="activeSection === 'updates'">
884
+ <h2 class="text-lg font-semibold mb-1">
885
+ Updates
886
+ </h2>
887
+ <p class="text-sm text-muted mb-6">
888
+ Keep ArkaOS current. The button runs the core update
889
+ (<code class="text-xs">npx arkaos@latest update</code>); finish
890
+ the project sync by running <code class="text-xs">/arka update</code>
891
+ in Claude Code.
892
+ </p>
893
+ <UCard>
894
+ <div class="space-y-4">
895
+ <div class="flex items-center gap-3 flex-wrap">
896
+ <div class="text-sm">
897
+ Installed:
898
+ <UBadge :label="`v${ver?.current ?? '—'}`" variant="subtle" size="sm" />
899
+ </div>
900
+ <div class="text-sm">
901
+ Latest:
902
+ <UBadge
903
+ :label="ver?.latest ? `v${ver.latest}` : '—'"
904
+ :color="ver?.update_available ? 'warning' : 'success'"
905
+ variant="subtle"
906
+ size="sm"
907
+ />
908
+ </div>
909
+ <UButton
910
+ size="xs"
911
+ variant="ghost"
912
+ icon="i-lucide-refresh-cw"
913
+ :loading="checkingVer"
914
+ @click="checkVersion"
915
+ >
916
+ Check
917
+ </UButton>
918
+ </div>
919
+
920
+ <div v-if="ver?.update_available" class="flex items-center gap-3">
921
+ <UButton
922
+ icon="i-lucide-download"
923
+ color="primary"
924
+ :loading="updating"
925
+ @click="runUpdate"
926
+ >
927
+ {{ updating ? 'Updating…' : `Update to v${ver.latest}` }}
928
+ </UButton>
929
+ <span class="text-xs text-muted">Runs the core update, then asks you to finish in Claude Code.</span>
930
+ </div>
931
+ <p v-else-if="ver && !ver.update_available" class="text-sm text-success flex items-center gap-1.5">
932
+ <UIcon name="i-lucide-check-circle" class="size-4" />
933
+ You're on the latest version.
934
+ </p>
935
+ <p v-else class="text-sm text-muted">
936
+ Couldn't reach the version service.
937
+ </p>
938
+
939
+ <div
940
+ v-if="updateResult"
941
+ class="rounded-lg border p-3 text-xs"
942
+ :class="updateResult.ok ? 'border-success/40 bg-success/5' : 'border-error/40 bg-error/5'"
943
+ >
944
+ <div class="flex items-center gap-1.5 font-medium mb-1">
945
+ <UIcon :name="updateResult.ok ? 'i-lucide-check-circle' : 'i-lucide-alert-triangle'" class="size-4" />
946
+ {{ updateResult.ok ? 'Core updated' : 'Update failed' }}
947
+ </div>
948
+ <p v-if="updateResult.ok" class="text-muted">
949
+ Now run <code>/arka update</code> in Claude Code to sync all projects (step 2).
950
+ </p>
951
+ <pre class="mt-2 whitespace-pre-wrap font-mono text-[11px] text-muted max-h-48 overflow-y-auto">{{ updateResult.output }}</pre>
952
+ </div>
953
+ </div>
954
+ </UCard>
955
+ </section>
792
956
  </div>
793
957
  </div>
794
958
  </template>
@@ -8,7 +8,8 @@
8
8
  "preview": "nuxt preview",
9
9
  "postinstall": "nuxt prepare",
10
10
  "lint": "eslint .",
11
- "typecheck": "nuxt typecheck"
11
+ "typecheck": "nuxt typecheck",
12
+ "test:e2e": "playwright test"
12
13
  },
13
14
  "dependencies": {
14
15
  "@iconify-json/lucide": "^1.2.100",
@@ -34,6 +35,7 @@
34
35
  },
35
36
  "devDependencies": {
36
37
  "@nuxt/eslint": "^1.15.2",
38
+ "@playwright/test": "^1.60.0",
37
39
  "eslint": "^10.1.0",
38
40
  "typescript": "^6.0.2",
39
41
  "vue-tsc": "^3.2.6"