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 +1 -1
- package/core/__pycache__/trash.cpython-313.pyc +0 -0
- package/dashboard/app/components/GlobalSearch.vue +154 -0
- package/dashboard/app/components/KeyboardShortcutsHelp.vue +90 -0
- package/dashboard/app/composables/useDashboard.ts +21 -2
- package/dashboard/app/layouts/default.vue +5 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +93 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.14.0
|
|
Binary file
|
|
@@ -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
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -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")
|