arkaos 3.12.0 → 3.14.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/VERSION CHANGED
@@ -1 +1 @@
1
- 3.12.0
1
+ 3.14.0
@@ -0,0 +1,154 @@
1
+ <script setup lang="ts">
2
+ // PR85d v3.14.0 — global search command palette.
3
+ //
4
+ // Opens via `/` shortcut. Debounced fetch against /api/search hits
5
+ // agents, personas, departments, commands. Navigate by Enter or click.
6
+
7
+ const { searchOpen } = useDashboard()
8
+ const { apiBase } = useApi()
9
+ const router = useRouter()
10
+
11
+ interface SearchResult {
12
+ kind: 'agent' | 'persona' | 'department' | 'command'
13
+ id: string
14
+ label: string
15
+ sublabel: string
16
+ to: string
17
+ }
18
+
19
+ const query = ref('')
20
+ const results = ref<SearchResult[]>([])
21
+ const loading = ref(false)
22
+ let abortCtl: AbortController | null = null
23
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
24
+
25
+ watch(query, (q) => {
26
+ if (debounceTimer) clearTimeout(debounceTimer)
27
+ if (abortCtl) abortCtl.abort()
28
+ if (!q.trim()) {
29
+ results.value = []
30
+ loading.value = false
31
+ return
32
+ }
33
+ debounceTimer = setTimeout(async () => {
34
+ loading.value = true
35
+ abortCtl = new AbortController()
36
+ try {
37
+ const res = await $fetch<{ results: SearchResult[] }>(
38
+ `${apiBase}/api/search`,
39
+ { query: { q, limit: 20 }, signal: abortCtl.signal },
40
+ )
41
+ results.value = res.results ?? []
42
+ } catch {
43
+ results.value = []
44
+ } finally {
45
+ loading.value = false
46
+ }
47
+ }, 180)
48
+ })
49
+
50
+ watch(searchOpen, (open) => {
51
+ if (!open) {
52
+ query.value = ''
53
+ results.value = []
54
+ }
55
+ })
56
+
57
+ function pickResult(r: SearchResult) {
58
+ searchOpen.value = false
59
+ router.push(r.to)
60
+ }
61
+
62
+ const kindMeta: Record<SearchResult['kind'], { icon: string, color: string }> = {
63
+ agent: { icon: 'i-lucide-users', color: 'text-primary' },
64
+ persona: { icon: 'i-lucide-user-plus', color: 'text-emerald-500' },
65
+ department: { icon: 'i-lucide-folder-tree', color: 'text-amber-500' },
66
+ command: { icon: 'i-lucide-terminal', color: 'text-blue-500' },
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <UModal
72
+ v-model:open="searchOpen"
73
+ title="Search"
74
+ :ui="{ content: 'max-w-2xl' }"
75
+ >
76
+ <template #content>
77
+ <UCard :ui="{ body: 'p-0' }">
78
+ <template #header>
79
+ <div class="flex items-center gap-3">
80
+ <UIcon name="i-lucide-search" class="size-5 text-muted shrink-0" />
81
+ <UInput
82
+ v-model="query"
83
+ placeholder="Search agents, personas, departments, commands…"
84
+ size="lg"
85
+ autofocus
86
+ :ui="{ root: 'flex-1', base: 'border-0 shadow-none ring-0 focus:ring-0 px-0' }"
87
+ />
88
+ <kbd class="px-1.5 py-0.5 rounded bg-elevated/50 text-xs font-mono text-muted shrink-0">
89
+ esc
90
+ </kbd>
91
+ </div>
92
+ </template>
93
+
94
+ <div class="max-h-[60vh] overflow-y-auto">
95
+ <div v-if="loading" class="p-6 text-center text-sm text-muted">
96
+ <UIcon name="i-lucide-loader-2" class="size-4 animate-spin inline" />
97
+ Searching…
98
+ </div>
99
+ <div
100
+ v-else-if="!query.trim()"
101
+ class="p-6 text-center text-sm text-muted"
102
+ >
103
+ Start typing to search across the whole workspace.
104
+ </div>
105
+ <div
106
+ v-else-if="results.length === 0"
107
+ class="p-6 text-center text-sm text-muted"
108
+ >
109
+ No matches for <span class="font-mono">{{ query }}</span>.
110
+ </div>
111
+ <ul v-else class="divide-y divide-default">
112
+ <li
113
+ v-for="r in results"
114
+ :key="`${r.kind}:${r.id}`"
115
+ class="px-4 py-2.5 hover:bg-elevated/40 cursor-pointer transition-colors"
116
+ @click="pickResult(r)"
117
+ >
118
+ <div class="flex items-center gap-3">
119
+ <UIcon
120
+ :name="kindMeta[r.kind].icon"
121
+ class="size-4 shrink-0"
122
+ :class="kindMeta[r.kind].color"
123
+ />
124
+ <div class="min-w-0 flex-1">
125
+ <p class="text-sm font-semibold truncate">{{ r.label }}</p>
126
+ <p class="text-xs text-muted truncate">{{ r.sublabel }}</p>
127
+ </div>
128
+ <UBadge
129
+ :label="r.kind"
130
+ variant="subtle"
131
+ size="xs"
132
+ class="capitalize shrink-0"
133
+ />
134
+ </div>
135
+ </li>
136
+ </ul>
137
+ </div>
138
+
139
+ <template #footer>
140
+ <div class="text-xs text-muted flex items-center gap-3">
141
+ <span>
142
+ <kbd class="px-1.5 py-0.5 rounded bg-elevated/50 font-mono">/</kbd>
143
+ opens this
144
+ </span>
145
+ <span>
146
+ <kbd class="px-1.5 py-0.5 rounded bg-elevated/50 font-mono">esc</kbd>
147
+ closes
148
+ </span>
149
+ </div>
150
+ </template>
151
+ </UCard>
152
+ </template>
153
+ </UModal>
154
+ </template>
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ // PR85c v3.13.0 — Keyboard shortcuts help overlay.
3
+ //
4
+ // Triggered by `?` (defineShortcuts in useDashboard). Lists all
5
+ // registered shortcuts grouped by category.
6
+
7
+ const { shortcutsHelpOpen } = useDashboard()
8
+
9
+ const groups = [
10
+ {
11
+ title: 'Navigation',
12
+ items: [
13
+ { keys: ['g', 'h'], label: 'Home / command center' },
14
+ { keys: ['g', 'a'], label: 'Agents' },
15
+ { keys: ['g', 'p'], label: 'Personas' },
16
+ { keys: ['g', 'c'], label: 'Commands' },
17
+ { keys: ['g', 'b'], label: 'Budget' },
18
+ { keys: ['g', 't'], label: 'Tasks' },
19
+ { keys: ['g', 'k'], label: 'Knowledge' },
20
+ { keys: ['g', 'e'], label: 'Health' },
21
+ { keys: ['g', 'r'], label: 'Trash' },
22
+ { keys: ['g', 's'], label: 'Settings' },
23
+ ],
24
+ },
25
+ {
26
+ title: 'Actions',
27
+ items: [
28
+ { keys: ['/'], label: 'Open global search' },
29
+ { keys: ['n'], label: 'New (context-aware — agent / persona)' },
30
+ { keys: ['?'], label: 'Toggle this help' },
31
+ ],
32
+ },
33
+ ]
34
+ </script>
35
+
36
+ <template>
37
+ <UModal
38
+ v-model:open="shortcutsHelpOpen"
39
+ title="Keyboard shortcuts"
40
+ :ui="{ content: 'max-w-lg' }"
41
+ >
42
+ <template #content>
43
+ <UCard>
44
+ <template #header>
45
+ <div class="flex items-center justify-between gap-3">
46
+ <div>
47
+ <h2 class="text-lg font-bold">Keyboard shortcuts</h2>
48
+ <p class="text-xs text-muted mt-0.5">
49
+ Press <kbd class="px-1.5 py-0.5 rounded bg-elevated/50 text-xs font-mono">?</kbd> anywhere to toggle this.
50
+ </p>
51
+ </div>
52
+ <UButton
53
+ icon="i-lucide-x"
54
+ variant="ghost"
55
+ size="sm"
56
+ aria-label="Close"
57
+ @click="shortcutsHelpOpen = false"
58
+ />
59
+ </div>
60
+ </template>
61
+
62
+ <div class="space-y-5">
63
+ <div v-for="g in groups" :key="g.title">
64
+ <h3 class="text-xs font-semibold uppercase tracking-wide text-muted mb-2">
65
+ {{ g.title }}
66
+ </h3>
67
+ <ul class="space-y-1.5">
68
+ <li
69
+ v-for="item in g.items"
70
+ :key="item.label"
71
+ class="flex items-center justify-between gap-3 text-sm"
72
+ >
73
+ <span>{{ item.label }}</span>
74
+ <div class="flex items-center gap-1">
75
+ <kbd
76
+ v-for="(k, idx) in item.keys"
77
+ :key="idx"
78
+ class="px-2 py-0.5 rounded bg-elevated/50 border border-default text-xs font-mono font-semibold"
79
+ >
80
+ {{ k }}
81
+ </kbd>
82
+ </div>
83
+ </li>
84
+ </ul>
85
+ </div>
86
+ </div>
87
+ </UCard>
88
+ </template>
89
+ </UModal>
90
+ </template>
@@ -1,19 +1,38 @@
1
1
  import { createSharedComposable } from '@vueuse/core'
2
2
 
3
+ // PR85c v3.13.0 — extended shortcut map + context-aware `n` + help modal.
3
4
  const _useDashboard = () => {
4
5
  const router = useRouter()
6
+ const route = useRoute()
7
+ const shortcutsHelpOpen = useState('shortcutsHelpOpen', () => false)
8
+ // PR85d v3.14.0 — global search palette state.
9
+ const searchOpen = useState('searchOpen', () => false)
10
+
11
+ function contextualNew() {
12
+ const path = route.path
13
+ if (path.startsWith('/agents')) return router.push('/agents/new')
14
+ if (path.startsWith('/personas')) return router.push('/personas/new')
15
+ // Default: go to agents/new (most common new-thing action)
16
+ return router.push('/agents/new')
17
+ }
5
18
 
6
19
  defineShortcuts({
7
20
  'g-h': () => router.push('/'),
8
21
  'g-a': () => router.push('/agents'),
22
+ 'g-p': () => router.push('/personas'),
9
23
  'g-c': () => router.push('/commands'),
10
24
  'g-b': () => router.push('/budget'),
11
25
  'g-t': () => router.push('/tasks'),
12
26
  'g-k': () => router.push('/knowledge'),
13
- 'g-e': () => router.push('/health')
27
+ 'g-e': () => router.push('/health'),
28
+ 'g-s': () => router.push('/settings'),
29
+ 'g-r': () => router.push('/trash'),
30
+ n: () => contextualNew(),
31
+ '?': () => { shortcutsHelpOpen.value = !shortcutsHelpOpen.value },
32
+ '/': () => { searchOpen.value = true },
14
33
  })
15
34
 
16
- return {}
35
+ return { shortcutsHelpOpen, searchOpen }
17
36
  }
18
37
 
19
38
  export const useDashboard = createSharedComposable(_useDashboard)
@@ -1,6 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from '@nuxt/ui'
3
3
 
4
+ // PR85c v3.13.0 — registers keyboard shortcuts globally.
5
+ useDashboard()
6
+
4
7
  const open = ref(false)
5
8
 
6
9
  const links = [[{
@@ -129,5 +132,7 @@ const links = [[{
129
132
  </UDashboardSidebar>
130
133
 
131
134
  <slot />
135
+ <KeyboardShortcutsHelp />
136
+ <GlobalSearch />
132
137
  </UDashboardGroup>
133
138
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.12.0",
3
+ "version": "3.14.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "3.12.0"
3
+ version = "3.14.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1331,6 +1331,99 @@ def persona_delete(persona_id: str):
1331
1331
  return {"error": "Persona not found"}
1332
1332
 
1333
1333
 
1334
+ # --- Global search (PR85d v3.14.0) ---
1335
+
1336
+ @app.get("/api/search")
1337
+ def global_search(q: str = "", limit: int = 20):
1338
+ """Fuzzy substring search across agents, personas, departments, commands.
1339
+
1340
+ Returns a flat list of result objects with a shape the dashboard
1341
+ UCommandPalette can render directly:
1342
+ {results: [{kind, id, label, sublabel, to}]}
1343
+ """
1344
+ query = (q or "").strip().lower()
1345
+ if not query:
1346
+ return {"results": []}
1347
+ results: list[dict] = []
1348
+
1349
+ # Agents
1350
+ for a in _load_agents():
1351
+ haystack = " ".join(filter(None, [
1352
+ str(a.get("name") or ""),
1353
+ str(a.get("role") or ""),
1354
+ str(a.get("department") or ""),
1355
+ str(a.get("id") or ""),
1356
+ ])).lower()
1357
+ if query in haystack:
1358
+ results.append({
1359
+ "kind": "agent",
1360
+ "id": a.get("id"),
1361
+ "label": a.get("name") or a.get("id"),
1362
+ "sublabel": f"{a.get('role') or '—'} · {a.get('department') or '—'}",
1363
+ "to": f"/agents/{a.get('id')}",
1364
+ })
1365
+
1366
+ # Personas
1367
+ mgr = _get_persona_manager()
1368
+ if mgr:
1369
+ try:
1370
+ for p in (mgr.list() or []):
1371
+ haystack = " ".join(filter(None, [
1372
+ str(p.get("name") or ""),
1373
+ str(p.get("title") or ""),
1374
+ str(p.get("source") or ""),
1375
+ str(p.get("mbti") or ""),
1376
+ str(p.get("id") or ""),
1377
+ ])).lower()
1378
+ if query in haystack:
1379
+ results.append({
1380
+ "kind": "persona",
1381
+ "id": p.get("id"),
1382
+ "label": p.get("name") or p.get("id"),
1383
+ "sublabel": f"{p.get('title') or '—'} · {p.get('mbti') or '—'}",
1384
+ "to": f"/personas/{p.get('id')}",
1385
+ })
1386
+ except Exception:
1387
+ pass
1388
+
1389
+ # Departments — derive distinct list from agents
1390
+ seen_depts: set[str] = set()
1391
+ for a in _load_agents():
1392
+ d = a.get("department")
1393
+ if d:
1394
+ seen_depts.add(d)
1395
+ for d in sorted(seen_depts):
1396
+ if query in d.lower():
1397
+ results.append({
1398
+ "kind": "department",
1399
+ "id": d,
1400
+ "label": d.capitalize(),
1401
+ "sublabel": "Department",
1402
+ "to": f"/agents?department={d}",
1403
+ })
1404
+
1405
+ # Commands
1406
+ try:
1407
+ for c in _load_commands():
1408
+ haystack = " ".join(filter(None, [
1409
+ str(c.get("command") or ""),
1410
+ str(c.get("description") or ""),
1411
+ str(c.get("department") or ""),
1412
+ ])).lower()
1413
+ if query in haystack:
1414
+ results.append({
1415
+ "kind": "command",
1416
+ "id": c.get("command"),
1417
+ "label": c.get("command"),
1418
+ "sublabel": c.get("description") or c.get("department") or "",
1419
+ "to": "/commands",
1420
+ })
1421
+ except Exception:
1422
+ pass
1423
+
1424
+ return {"results": results[: max(0, int(limit))]}
1425
+
1426
+
1334
1427
  # --- Trash / Undo (PR85b v3.12.0) ---
1335
1428
 
1336
1429
  @app.get("/api/trash")