aidevops 3.26.1 → 3.28.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/README.md +1 -1
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-web/src/App.tsx +2 -0
- package/packages/gui-web/src/AppNavigation.tsx +3 -0
- package/packages/gui-web/src/AppWorkspace.tsx +82 -133
- package/packages/gui-web/src/CommandPalette.tsx +286 -0
- package/packages/gui-web/src/app-model.ts +6 -0
- package/packages/gui-web/src/dashboard.ts +1 -1
- package/packages/gui-web/src/styles.css +37 -3
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
58
58
|
[](https://github.com/marcusquinn)
|
|
59
59
|
|
|
60
60
|
<!-- Release & Version Info -->
|
|
61
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
62
62
|
[](https://www.npmjs.com/package/aidevops)
|
|
63
63
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
64
64
|
[](https://github.com/marcusquinn/aidevops)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.28.0
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -212,6 +212,8 @@ export function App(): ReactElement {
|
|
|
212
212
|
goForward={goForward}
|
|
213
213
|
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
214
214
|
setActiveSurface={setActiveSurface}
|
|
215
|
+
setConversationMode={setConversationMode}
|
|
216
|
+
setShellMode={setShellMode}
|
|
215
217
|
shellMode={shellMode}
|
|
216
218
|
status={status.data}
|
|
217
219
|
/>
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
FiGrid,
|
|
21
21
|
FiHash,
|
|
22
22
|
FiHardDrive,
|
|
23
|
+
FiHelpCircle,
|
|
23
24
|
FiLink,
|
|
24
25
|
FiLink2,
|
|
25
26
|
FiList,
|
|
@@ -54,6 +55,8 @@ const surfaceIcons: Record<SurfaceIconName, IconType> = {
|
|
|
54
55
|
globe: FiGlobe,
|
|
55
56
|
grid: FiGrid,
|
|
56
57
|
hardDrive: FiHardDrive,
|
|
58
|
+
hash: FiHash,
|
|
59
|
+
help: FiHelpCircle,
|
|
57
60
|
link: FiLink,
|
|
58
61
|
list: FiList,
|
|
59
62
|
lock: FiLock,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
|
-
import { type ReactElement, type ReactNode, useEffect,
|
|
3
|
-
import { FiBell, FiChevronLeft, FiChevronRight, FiCommand,
|
|
2
|
+
import { type ReactElement, type ReactNode, useEffect, useState } from "react";
|
|
3
|
+
import { FiBell, FiChevronLeft, FiChevronRight, FiCommand, FiGlobe, FiHash, FiHelpCircle, FiLogOut, FiMessageSquare, FiSearch, FiSettings, FiShield, FiTerminal, FiUser } from "react-icons/fi";
|
|
4
4
|
import type { GuiFileRootId, GuiStatusData } from "../../gui-shared/src";
|
|
5
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
6
|
import type { ConversationMode, ShellMode, SurfaceId, SurfaceNavItem } from "./app-model";
|
|
7
|
-
import { inventorySurfaceConfigs,
|
|
7
|
+
import { inventorySurfaceConfigs, text } from "./app-model";
|
|
8
|
+
import { CommandPalette, commandPaletteShortcutQuery } from "./CommandPalette";
|
|
8
9
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
9
10
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
10
11
|
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
@@ -15,7 +16,7 @@ const communityLinks = {
|
|
|
15
16
|
x: "https://x.com/marcuswquinn",
|
|
16
17
|
} as const;
|
|
17
18
|
|
|
18
|
-
export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, conversationMode, fileRoot, goBack, goForward, selectedLocalRepoIndex, setActiveSurface, shellMode, status }: {
|
|
19
|
+
export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, conversationMode, fileRoot, goBack, goForward, selectedLocalRepoIndex, setActiveSurface, setConversationMode, setShellMode, shellMode, status }: {
|
|
19
20
|
activeItem: SurfaceNavItem;
|
|
20
21
|
activeSectionLabel: string;
|
|
21
22
|
activeSurface: SurfaceId;
|
|
@@ -27,12 +28,14 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
27
28
|
goForward: () => void;
|
|
28
29
|
selectedLocalRepoIndex: number;
|
|
29
30
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
31
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
32
|
+
setShellMode: (mode: ShellMode) => void;
|
|
30
33
|
shellMode: ShellMode;
|
|
31
34
|
status: GuiStatusData;
|
|
32
35
|
}) {
|
|
33
36
|
return (
|
|
34
37
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
35
|
-
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} status={status} />
|
|
38
|
+
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setShellMode={setShellMode} status={status} />
|
|
36
39
|
<div className="workspace-scroll">
|
|
37
40
|
{shellMode === "sessions"
|
|
38
41
|
? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} status={status} />
|
|
@@ -42,7 +45,7 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, goBack, goForward, setActiveSurface, status }: {
|
|
48
|
+
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, goBack, goForward, setActiveSurface, setConversationMode, setShellMode, status }: {
|
|
46
49
|
activeItem: SurfaceNavItem;
|
|
47
50
|
activeSectionLabel: string;
|
|
48
51
|
activeSurface: SurfaceId;
|
|
@@ -51,6 +54,8 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
51
54
|
goBack: () => void;
|
|
52
55
|
goForward: () => void;
|
|
53
56
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
57
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
58
|
+
setShellMode: (mode: ShellMode) => void;
|
|
54
59
|
status: GuiStatusData;
|
|
55
60
|
}): ReactElement {
|
|
56
61
|
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
@@ -63,30 +68,30 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
63
68
|
|
|
64
69
|
useEffect(() => {
|
|
65
70
|
const openCommandPalette = (event: KeyboardEvent) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
event.preventDefault();
|
|
75
|
-
setCommandInitialQuery("/");
|
|
76
|
-
setCommandOpen(true);
|
|
77
|
-
}
|
|
71
|
+
handleWorkspaceShortcut(event, {
|
|
72
|
+
goBack,
|
|
73
|
+
goForward,
|
|
74
|
+
openCommand: (query) => {
|
|
75
|
+
setCommandInitialQuery(query);
|
|
76
|
+
setCommandOpen(true);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
window.addEventListener("keydown", openCommandPalette);
|
|
81
82
|
return () => window.removeEventListener("keydown", openCommandPalette);
|
|
82
|
-
}, []);
|
|
83
|
+
}, [goBack, goForward]);
|
|
83
84
|
|
|
84
85
|
const closeMenus = () => {
|
|
85
86
|
setNotificationsOpen(false);
|
|
86
87
|
setProfileOpen(false);
|
|
87
88
|
};
|
|
88
89
|
|
|
89
|
-
const
|
|
90
|
+
const openItem = ({ conversationMode, shellMode, surface }: { conversationMode?: ConversationMode; shellMode?: ShellMode; surface: SurfaceId }) => {
|
|
91
|
+
setShellMode(shellMode ?? "devices");
|
|
92
|
+
if (conversationMode) {
|
|
93
|
+
setConversationMode(conversationMode);
|
|
94
|
+
}
|
|
90
95
|
setActiveSurface(surface);
|
|
91
96
|
closeMenus();
|
|
92
97
|
setCommandOpen(false);
|
|
@@ -114,28 +119,45 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
114
119
|
</div>
|
|
115
120
|
<div className="header-actions">
|
|
116
121
|
<div className="header-action-menu">
|
|
122
|
+
<button aria-label="Open help" className="header-icon-button" onClick={() => openItem({ surface: "help" })} title="Help (?)" type="button">
|
|
123
|
+
<FiHelpCircle aria-hidden="true" />
|
|
124
|
+
</button>
|
|
117
125
|
<button aria-expanded={notificationsOpen} aria-label="Open notifications" className="header-icon-button" onClick={() => { setNotificationsOpen((current) => !current); setProfileOpen(false); }} type="button">
|
|
118
126
|
<FiBell aria-hidden="true" />
|
|
119
127
|
<span className="notification-dot" aria-hidden="true" />
|
|
120
128
|
</button>
|
|
121
|
-
{notificationsOpen ? <NotificationsMenu openSurface={
|
|
129
|
+
{notificationsOpen ? <NotificationsMenu openSurface={(surface) => openItem({ surface })} /> : null}
|
|
122
130
|
</div>
|
|
123
|
-
<button aria-pressed={assistantOpen} aria-label="Toggle AI Assistant" className={assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={() => setAssistantOpen((current) => !current)} type="button">
|
|
124
|
-
<
|
|
131
|
+
<button aria-pressed={assistantOpen} aria-label="Toggle AI Assistant" className={assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={() => setAssistantOpen((current) => !current)} title="AI sessions (_)" type="button">
|
|
132
|
+
<FiTerminal aria-hidden="true" />
|
|
125
133
|
</button>
|
|
126
134
|
<div className="header-action-menu">
|
|
127
135
|
<button aria-expanded={profileOpen} aria-label={`Open profile menu for ${userName}`} className="profile-avatar-button" onClick={() => { setProfileOpen((current) => !current); setNotificationsOpen(false); }} title={userName} type="button">
|
|
128
136
|
<span>{userInitials}</span>
|
|
129
137
|
</button>
|
|
130
|
-
{profileOpen ? <ProfileMenu openSurface={
|
|
138
|
+
{profileOpen ? <ProfileMenu openSurface={(surface) => openItem({ surface })} userName={userName} /> : null}
|
|
131
139
|
</div>
|
|
132
140
|
</div>
|
|
133
141
|
{assistantOpen ? <AssistantPanel activeSurface={activeSurface} userName={userName} /> : null}
|
|
134
|
-
{commandOpen ? <CommandPalette close={() => setCommandOpen(false)} initialQuery={commandInitialQuery}
|
|
142
|
+
{commandOpen ? <CommandPalette activeSurface={activeSurface} close={() => setCommandOpen(false)} initialQuery={commandInitialQuery} openItem={openItem} status={status} /> : null}
|
|
135
143
|
</header>
|
|
136
144
|
);
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
function handleWorkspaceShortcut(event: KeyboardEvent, actions: { goBack: () => void; goForward: () => void; openCommand: (query: string) => void }): void {
|
|
148
|
+
const shortcutQuery = commandPaletteShortcutQuery(event);
|
|
149
|
+
const isEditableTarget = isEditableShortcutTarget(event.target);
|
|
150
|
+
const commandQuery = (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k" ? "" : shortcutQuery;
|
|
151
|
+
|
|
152
|
+
if (commandQuery !== undefined && !isEditableTarget) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
actions.openCommand(commandQuery);
|
|
155
|
+
} else if (event.key === "Backspace" && !isEditableTarget) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
(event.shiftKey ? actions.goForward : actions.goBack)();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
function isEditableShortcutTarget(target: EventTarget | null): boolean {
|
|
140
162
|
if (!(target instanceof HTMLElement)) {
|
|
141
163
|
return false;
|
|
@@ -235,114 +257,6 @@ function AssistantPanel({ activeSurface, userName }: { activeSurface: SurfaceId;
|
|
|
235
257
|
);
|
|
236
258
|
}
|
|
237
259
|
|
|
238
|
-
interface CommandPaletteItem {
|
|
239
|
-
description: string;
|
|
240
|
-
icon: SurfaceNavItem["icon"];
|
|
241
|
-
id: string;
|
|
242
|
-
label: string;
|
|
243
|
-
searchText: string;
|
|
244
|
-
surface: SurfaceId;
|
|
245
|
-
tag: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const slashCommandItems: CommandPaletteItem[] = [
|
|
249
|
-
{ id: "slash-add-device", label: "/add-device", description: "Start device pairing setup", icon: "device", searchText: "/add-device add device pair machine", surface: "devices", tag: "Command" },
|
|
250
|
-
{ id: "slash-add-local-repo", label: "/add-local-repo", description: "Add a local repository path", icon: "folder", searchText: "/add-local-repo add local repo git", surface: "git", tag: "Command" },
|
|
251
|
-
{ id: "slash-add-remote-repo", label: "/add-remote-repo", description: "Register a remote repository", icon: "git", searchText: "/add-remote-repo add remote repo", surface: "projects", tag: "Command" },
|
|
252
|
-
{ id: "slash-add-secret", label: "/add-secret", description: "Add a protected secret reference", icon: "shield", searchText: "/add-secret add secret vault", surface: "security", tag: "Command" },
|
|
253
|
-
{ id: "slash-add-ai-provider", label: "/add-ai-provider", description: "Connect an AI provider account", icon: "users", searchText: "/add-ai-provider add ai provider oauth", surface: "aiProviders", tag: "Command" },
|
|
254
|
-
];
|
|
255
|
-
|
|
256
|
-
const plannedChannelItems: CommandPaletteItem[] = [
|
|
257
|
-
{ id: "channel-devops", label: "#devops", description: "Chat channel for aidevops operations", icon: "message", searchText: "#devops devops channel chat", surface: "messagingAccounts", tag: "Channel" },
|
|
258
|
-
{ id: "channel-comms", label: "#comms", description: "Chat channel for communications", icon: "message", searchText: "#comms comms channel chat", surface: "messagingAccounts", tag: "Channel" },
|
|
259
|
-
];
|
|
260
|
-
|
|
261
|
-
function CommandPalette({ close, initialQuery, openSurface, status }: { close: () => void; initialQuery: string; openSurface: (surface: SurfaceId) => void; status: GuiStatusData }): ReactElement {
|
|
262
|
-
const [query, setQuery] = useState(initialQuery);
|
|
263
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
264
|
-
const matches = useMemo(() => {
|
|
265
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
266
|
-
const surfaceItems: CommandPaletteItem[] = orderedNavItems.map((item) => ({
|
|
267
|
-
description: item.description,
|
|
268
|
-
icon: item.icon,
|
|
269
|
-
id: `surface-${item.id}`,
|
|
270
|
-
label: item.label,
|
|
271
|
-
searchText: `${item.label} ${item.description}`,
|
|
272
|
-
surface: item.id,
|
|
273
|
-
tag: "Surface",
|
|
274
|
-
}));
|
|
275
|
-
const sessionItems: CommandPaletteItem[] = status.opencode_sessions.sessions.map((session) => ({
|
|
276
|
-
description: `AI session in ${session.repo_path_ref}`,
|
|
277
|
-
icon: "terminal",
|
|
278
|
-
id: `session-${session.id_ref}`,
|
|
279
|
-
label: `#${session.title}`,
|
|
280
|
-
searchText: `#${session.title} ${session.title} ${session.repo_path_ref} ${session.agent} ${session.model}`,
|
|
281
|
-
surface: "git",
|
|
282
|
-
tag: "AI session",
|
|
283
|
-
}));
|
|
284
|
-
const directMessageItems: CommandPaletteItem[] = [{
|
|
285
|
-
description: "Direct message thread placeholder",
|
|
286
|
-
icon: "users",
|
|
287
|
-
id: "dm-local-user",
|
|
288
|
-
label: `@${status.machine.username || "local-user"}`,
|
|
289
|
-
searchText: `@${status.machine.username || "local-user"} direct message dm person local user`,
|
|
290
|
-
surface: "messagingAccounts",
|
|
291
|
-
tag: "Direct message",
|
|
292
|
-
}];
|
|
293
|
-
const items = [...surfaceItems, ...sessionItems, ...plannedChannelItems, ...directMessageItems, ...slashCommandItems];
|
|
294
|
-
const prefix = normalizedQuery.charAt(0);
|
|
295
|
-
const scopedItems = prefix === "/"
|
|
296
|
-
? slashCommandItems
|
|
297
|
-
: prefix === "@"
|
|
298
|
-
? directMessageItems
|
|
299
|
-
: prefix === "#"
|
|
300
|
-
? [...sessionItems, ...plannedChannelItems]
|
|
301
|
-
: items;
|
|
302
|
-
|
|
303
|
-
return scopedItems
|
|
304
|
-
.filter((item) => normalizedQuery.length === 0 || item.searchText.toLowerCase().includes(normalizedQuery))
|
|
305
|
-
.slice(0, 8);
|
|
306
|
-
}, [query, status.machine.username, status.opencode_sessions.sessions]);
|
|
307
|
-
|
|
308
|
-
useEffect(() => {
|
|
309
|
-
inputRef.current?.focus();
|
|
310
|
-
}, []);
|
|
311
|
-
|
|
312
|
-
useEffect(() => {
|
|
313
|
-
const closeOnEscape = (event: KeyboardEvent) => {
|
|
314
|
-
if (event.key === "Escape") {
|
|
315
|
-
close();
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
window.addEventListener("keydown", closeOnEscape);
|
|
320
|
-
return () => window.removeEventListener("keydown", closeOnEscape);
|
|
321
|
-
}, [close]);
|
|
322
|
-
|
|
323
|
-
return (
|
|
324
|
-
<div className="command-palette-backdrop" role="presentation">
|
|
325
|
-
<button aria-label="Close command palette" className="command-palette-scrim" onClick={close} type="button" />
|
|
326
|
-
<section aria-label="Command palette" className="command-palette">
|
|
327
|
-
<label className="command-input-row">
|
|
328
|
-
<FiSearch aria-hidden="true" />
|
|
329
|
-
<input ref={inputRef} onChange={(event) => setQuery(event.currentTarget.value)} placeholder="Search commands, #sessions, #channels, @people, or /actions" value={query} />
|
|
330
|
-
</label>
|
|
331
|
-
<ul>
|
|
332
|
-
{matches.map((item) => (
|
|
333
|
-
<li key={item.id}>
|
|
334
|
-
<button onClick={() => openSurface(item.surface)} type="button">
|
|
335
|
-
<span className="surface-icon" aria-hidden="true"><SurfaceGlyph icon={item.icon} /></span>
|
|
336
|
-
<span><strong>{item.label}</strong><small>{item.tag} · {item.description}</small></span>
|
|
337
|
-
</button>
|
|
338
|
-
</li>
|
|
339
|
-
))}
|
|
340
|
-
</ul>
|
|
341
|
-
</section>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
260
|
function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
347
261
|
activeItem: SurfaceNavItem;
|
|
348
262
|
activeSurface: SurfaceId;
|
|
@@ -353,6 +267,7 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
353
267
|
const vaultCollection = vaultCollectionForSurface(status.vault, activeSurface);
|
|
354
268
|
const staticSurfaces: Partial<Record<SurfaceId, ReactNode>> = {
|
|
355
269
|
overview: <OverviewSurface status={status} />,
|
|
270
|
+
help: <HelpSurface />,
|
|
356
271
|
settings: <SettingsSurface status={status} />,
|
|
357
272
|
notifications: <NotificationsSurface />,
|
|
358
273
|
admin: <AdminSurface />,
|
|
@@ -406,6 +321,40 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
406
321
|
return staticSurfaces[activeSurface] ?? null;
|
|
407
322
|
}
|
|
408
323
|
|
|
324
|
+
function HelpSurface(): ReactElement {
|
|
325
|
+
const shortcuts = [
|
|
326
|
+
["#", "Channels"],
|
|
327
|
+
["_", "AI sessions"],
|
|
328
|
+
["&", "Infrastructure"],
|
|
329
|
+
["?", "Help"],
|
|
330
|
+
[">", "New terminal command"],
|
|
331
|
+
["+", "Add commands"],
|
|
332
|
+
["-", "Remove commands"],
|
|
333
|
+
["/", "Slash commands"],
|
|
334
|
+
["=", "Comms"],
|
|
335
|
+
["~", "Settings and config"],
|
|
336
|
+
["!", "Notifications"],
|
|
337
|
+
[".", "Agents"],
|
|
338
|
+
["*", "Secrets and passwords"],
|
|
339
|
+
[":", "Emoji picker"],
|
|
340
|
+
["^", "Copy current page link"],
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<section className="settings-surface help-surface">
|
|
345
|
+
<div className="planned-card">
|
|
346
|
+
<h2>Help</h2>
|
|
347
|
+
<p>Use the command palette shortcuts from anywhere outside text inputs. Recent selections appear first until you keep typing to filter.</p>
|
|
348
|
+
</div>
|
|
349
|
+
<ul className="shortcut-list">
|
|
350
|
+
{shortcuts.map(([shortcut, label]) => <li key={shortcut}><kbd>{shortcut}</kbd><span>{label}</span></li>)}
|
|
351
|
+
<li><kbd>Backspace</kbd><span>Previous page</span></li>
|
|
352
|
+
<li><kbd>Shift Backspace</kbd><span>Next page</span></li>
|
|
353
|
+
</ul>
|
|
354
|
+
</section>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
409
358
|
function SettingsSurface({ status }: { status: GuiStatusData }): ReactElement {
|
|
410
359
|
return (
|
|
411
360
|
<section className="settings-surface">
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/* jshint esversion: 11 */
|
|
2
|
+
import { type ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { FiSearch } from "react-icons/fi";
|
|
5
|
+
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
|
+
import type { ConversationMode, ShellMode, SurfaceId, SurfaceIconName, SurfaceNavItem } from "./app-model";
|
|
7
|
+
import { navGroups, orderedNavItems, utilityNavItems } from "./app-model";
|
|
8
|
+
import type { GuiStatusData } from "../../gui-shared/src";
|
|
9
|
+
|
|
10
|
+
interface CommandPaletteItem {
|
|
11
|
+
action?: "copy-current-link";
|
|
12
|
+
description: string;
|
|
13
|
+
conversationMode?: ConversationMode;
|
|
14
|
+
icon: SurfaceIconName;
|
|
15
|
+
iconGlyph?: string;
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
searchText: string;
|
|
19
|
+
shellMode?: ShellMode;
|
|
20
|
+
surface: SurfaceId;
|
|
21
|
+
tag: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const commandPaletteRecentStorageKey = "aidevops-gui-command-palette-recents";
|
|
25
|
+
const commandPaletteRecentLimit = 5;
|
|
26
|
+
|
|
27
|
+
const slashCommandItems: CommandPaletteItem[] = [
|
|
28
|
+
{ id: "slash-add-device", label: "/add-device", description: "Start device pairing setup", icon: "device", searchText: "/add-device add device pair machine", surface: "devices", tag: "Command" },
|
|
29
|
+
{ id: "slash-add-local-repo", label: "/add-local-repo", description: "Add a local repository path", icon: "folder", searchText: "/add-local-repo add local repo git", surface: "git", tag: "Command" },
|
|
30
|
+
{ id: "slash-add-remote-repo", label: "/add-remote-repo", description: "Register a remote repository", icon: "git", searchText: "/add-remote-repo add remote repo", surface: "projects", tag: "Command" },
|
|
31
|
+
{ id: "slash-add-secret", label: "/add-secret", description: "Add a protected secret reference", icon: "shield", searchText: "/add-secret add secret vault", surface: "security", tag: "Command" },
|
|
32
|
+
{ id: "slash-add-ai-provider", label: "/add-ai-provider", description: "Connect an AI provider account", icon: "users", searchText: "/add-ai-provider add ai provider oauth", surface: "aiProviders", tag: "Command" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const addCommandItems: CommandPaletteItem[] = slashCommandItems.map((item) => {
|
|
36
|
+
const label = item.label.replace("/", "+");
|
|
37
|
+
|
|
38
|
+
return { ...item, id: item.id.replace("slash-", "plus-"), label, searchText: `${label} ${item.searchText}`, tag: "Add" };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const removeCommandItems: CommandPaletteItem[] = [
|
|
42
|
+
{ id: "remove-device", label: "-device", description: "Remove a paired device placeholder", icon: "device", searchText: "-device remove device unpair machine", surface: "devices", tag: "Remove" },
|
|
43
|
+
{ id: "remove-local-repo", label: "-local-repo", description: "Remove a local repository reference placeholder", icon: "folder", searchText: "-local-repo remove local repo git", surface: "git", tag: "Remove" },
|
|
44
|
+
{ id: "remove-secret", label: "-secret", description: "Remove a protected secret reference placeholder", icon: "shield", searchText: "-secret remove secret vault password", surface: "security", tag: "Remove" },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const terminalCommandItems: CommandPaletteItem[] = [
|
|
48
|
+
{ id: "terminal-new-command", label: "> new terminal command", description: "Send a command to a new terminal session", icon: "terminal", searchText: "> terminal command new session shell", surface: "localSetup", tag: "Terminal" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const emojiItems: CommandPaletteItem[] = [
|
|
52
|
+
{ id: "emoji-thumbs-up", label: ":thumbsup:", description: "Copy or insert 👍", icon: "message", iconGlyph: "👍", searchText: ":thumbsup thumbs up approve emoji", surface: "messagingAccounts", tag: "Emoji" },
|
|
53
|
+
{ id: "emoji-check", label: ":white_check_mark:", description: "Copy or insert ✅", icon: "message", iconGlyph: "✅", searchText: ":white_check_mark check done emoji", surface: "messagingAccounts", tag: "Emoji" },
|
|
54
|
+
{ id: "emoji-eyes", label: ":eyes:", description: "Copy or insert 👀", icon: "message", iconGlyph: "👀", searchText: ":eyes review look emoji", surface: "messagingAccounts", tag: "Emoji" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const plannedChannelItems: CommandPaletteItem[] = [
|
|
58
|
+
{ id: "channel-devops", label: "#devops", description: "Chat channel for aidevops operations", icon: "hash", searchText: "#devops devops channel chat", surface: "messagingAccounts", shellMode: "sessions", conversationMode: "people", tag: "Channel" },
|
|
59
|
+
{ id: "channel-comms", label: "#comms", description: "Chat channel for communications", icon: "hash", searchText: "#comms comms channel chat", surface: "messagingAccounts", shellMode: "sessions", conversationMode: "people", tag: "Channel" },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export interface CommandPaletteSelection {
|
|
63
|
+
conversationMode?: ConversationMode;
|
|
64
|
+
shellMode?: ShellMode;
|
|
65
|
+
surface: SurfaceId;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function CommandPalette({ activeSurface, close, initialQuery, openItem, status }: { activeSurface: SurfaceId; close: () => void; initialQuery: string; openItem: (selection: CommandPaletteSelection) => void; status: GuiStatusData }): ReactElement {
|
|
69
|
+
const allItems = useMemo(() => commandPaletteItems(status, activeSurface), [activeSurface, status]);
|
|
70
|
+
const [query, setQuery] = useState(initialQuery);
|
|
71
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
72
|
+
const [recentIds, setRecentIds] = useState<string[]>(() => readCommandPaletteRecentIds());
|
|
73
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
74
|
+
const matches = useMemo(() => commandPaletteMatches(allItems, query, recentIds), [allItems, query, recentIds]);
|
|
75
|
+
|
|
76
|
+
const selectItem = useCallback((item: CommandPaletteItem | undefined) => {
|
|
77
|
+
if (item) {
|
|
78
|
+
const nextRecentIds = rememberCommandPaletteItemId(item.id, recentIds);
|
|
79
|
+
setRecentIds(nextRecentIds);
|
|
80
|
+
if (item.action === "copy-current-link") {
|
|
81
|
+
void copyCurrentSurfaceLink(item.surface);
|
|
82
|
+
}
|
|
83
|
+
openItem({ conversationMode: item.conversationMode, shellMode: item.shellMode, surface: item.surface });
|
|
84
|
+
}
|
|
85
|
+
}, [openItem, recentIds]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
inputRef.current?.focus();
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
setActiveIndex((current) => Math.min(current, Math.max(0, matches.length - 1)));
|
|
93
|
+
}, [matches.length]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const handlePaletteKeys = (event: KeyboardEvent) => {
|
|
97
|
+
if (event.key === "Escape") {
|
|
98
|
+
close();
|
|
99
|
+
} else if (event.key === "ArrowDown") {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
setActiveIndex((current) => Math.min(matches.length - 1, current + 1));
|
|
102
|
+
} else if (event.key === "ArrowUp") {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
setActiveIndex((current) => Math.max(0, current - 1));
|
|
105
|
+
} else if (event.key === "Enter") {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
selectItem(matches[activeIndex]);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
window.addEventListener("keydown", handlePaletteKeys);
|
|
112
|
+
return () => window.removeEventListener("keydown", handlePaletteKeys);
|
|
113
|
+
}, [activeIndex, close, matches, selectItem]);
|
|
114
|
+
|
|
115
|
+
return createPortal(
|
|
116
|
+
<div className="command-palette-backdrop" role="presentation">
|
|
117
|
+
<button aria-label="Close command palette" className="command-palette-scrim" onClick={close} type="button" />
|
|
118
|
+
<section aria-label="Command palette" className="command-palette">
|
|
119
|
+
<label className="command-input-row">
|
|
120
|
+
<FiSearch aria-hidden="true" />
|
|
121
|
+
<input aria-activedescendant={matches[activeIndex]?.id} aria-controls="command-palette-results" aria-expanded="true" aria-autocomplete="list" ref={inputRef} onChange={(event) => { setQuery(event.currentTarget.value); setActiveIndex(0); }} placeholder="Search /commands, #channels, _sessions, &infra, ?help, .agents" role="combobox" value={query} />
|
|
122
|
+
</label>
|
|
123
|
+
<ul id="command-palette-results">
|
|
124
|
+
{matches.map((item, index) => (
|
|
125
|
+
<li key={item.id}>
|
|
126
|
+
<button className={index === activeIndex ? "active" : undefined} id={item.id} onClick={() => selectItem(item)} type="button">
|
|
127
|
+
<span className="surface-icon" aria-hidden="true">{item.iconGlyph ?? <SurfaceGlyph icon={item.icon} />}</span>
|
|
128
|
+
<span><strong>{item.label}</strong><small>{item.tag} · {item.description}</small></span>
|
|
129
|
+
</button>
|
|
130
|
+
</li>
|
|
131
|
+
))}
|
|
132
|
+
</ul>
|
|
133
|
+
</section>
|
|
134
|
+
</div>,
|
|
135
|
+
document.body,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function commandPaletteShortcutQuery(event: Pick<KeyboardEvent, "key" | "metaKey" | "ctrlKey" | "altKey">): string | undefined {
|
|
140
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return new Map(commandPaletteShortcutEntries).get(event.key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const commandPaletteShortcutEntries = ["#", "_", "&", "?", ">", "+", "-", "/", "=", "~", "!", ".", "*", ":", "^"].map((shortcut) => [shortcut, shortcut] as const);
|
|
148
|
+
|
|
149
|
+
export function orderCommandItemsByRecency<T extends { id: string }>(items: T[], recentIds: string[]): T[] {
|
|
150
|
+
const recentItems = recentIds
|
|
151
|
+
.map((id) => items.find((item) => item.id === id))
|
|
152
|
+
.filter((item): item is T => item !== undefined);
|
|
153
|
+
const recentItemIds = new Set(recentItems.map((item) => item.id));
|
|
154
|
+
|
|
155
|
+
return [...recentItems, ...items.filter((item) => !recentItemIds.has(item.id))];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function rememberCommandPaletteItemId(itemId: string, currentIds: string[]): string[] {
|
|
159
|
+
const nextIds = [itemId, ...currentIds.filter((id) => id !== itemId)].slice(0, commandPaletteRecentLimit);
|
|
160
|
+
writeCommandPaletteRecentIds(nextIds);
|
|
161
|
+
return nextIds;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function commandPaletteMatches(items: CommandPaletteItem[], query: string, recentIds: string[]): CommandPaletteItem[] {
|
|
165
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
166
|
+
const prefix = normalizedQuery.charAt(0);
|
|
167
|
+
const scopedItems = itemsForCommandPrefix(items, prefix);
|
|
168
|
+
|
|
169
|
+
if (normalizedQuery.length === 0 || commandPaletteShortcutEntries.some(([shortcut]) => shortcut === normalizedQuery)) {
|
|
170
|
+
return orderCommandItemsByRecency(scopedItems, recentIds).slice(0, 8);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return scopedItems
|
|
174
|
+
.filter((item) => item.searchText.toLowerCase().includes(normalizedQuery))
|
|
175
|
+
.slice(0, 8);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function commandPaletteItems(status: GuiStatusData, activeSurface: SurfaceId): CommandPaletteItem[] {
|
|
179
|
+
const surfaceItems: CommandPaletteItem[] = orderedNavItems.map((item) => ({
|
|
180
|
+
description: item.description,
|
|
181
|
+
icon: item.icon,
|
|
182
|
+
id: `surface-${item.id}`,
|
|
183
|
+
label: item.label,
|
|
184
|
+
searchText: `${item.label} ${item.description}`,
|
|
185
|
+
surface: item.id,
|
|
186
|
+
tag: surfaceTagForItem(item),
|
|
187
|
+
}));
|
|
188
|
+
const helpItems: CommandPaletteItem[] = [
|
|
189
|
+
{ id: "help-shortcuts", label: "? shortcuts", description: "Open command palette and keyboard shortcut help", icon: "help", searchText: "? shortcuts help command palette keyboard", surface: "help", tag: "Help" },
|
|
190
|
+
];
|
|
191
|
+
const currentLinkItems: CommandPaletteItem[] = [
|
|
192
|
+
{ action: "copy-current-link", id: "copy-current-link", label: `^ copy ${activeSurface} link`, description: "Copy a link that can reopen this page in the web or local app", icon: "link", searchText: `^ link copy current page url ${activeSurface} aidevops://surface/${activeSurface}`, surface: activeSurface, tag: "Link" },
|
|
193
|
+
];
|
|
194
|
+
const sessionItems: CommandPaletteItem[] = status.opencode_sessions.sessions.map((session) => ({
|
|
195
|
+
description: `AI session in ${session.repo_path_ref}`,
|
|
196
|
+
icon: "terminal",
|
|
197
|
+
iconGlyph: "_",
|
|
198
|
+
id: `session-${session.id_ref}`,
|
|
199
|
+
label: `_${session.title}`,
|
|
200
|
+
searchText: `_${session.title} ${session.title} ${session.repo_path_ref} ${session.agent} ${session.model}`,
|
|
201
|
+
surface: "git",
|
|
202
|
+
shellMode: "sessions",
|
|
203
|
+
conversationMode: "ai",
|
|
204
|
+
tag: "AI session",
|
|
205
|
+
}));
|
|
206
|
+
const directMessageItems: CommandPaletteItem[] = [{
|
|
207
|
+
description: "Direct message thread placeholder",
|
|
208
|
+
icon: "users",
|
|
209
|
+
id: "dm-local-user",
|
|
210
|
+
label: `@${status.machine.username || "local-user"}`,
|
|
211
|
+
searchText: `@${status.machine.username || "local-user"} direct message dm person local user`,
|
|
212
|
+
surface: "messagingAccounts",
|
|
213
|
+
tag: "Direct message",
|
|
214
|
+
}];
|
|
215
|
+
|
|
216
|
+
return [...surfaceItems, ...helpItems, ...sessionItems, ...plannedChannelItems, ...directMessageItems, ...slashCommandItems, ...addCommandItems, ...removeCommandItems, ...terminalCommandItems, ...emojiItems, ...currentLinkItems];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function itemsForCommandPrefix(items: CommandPaletteItem[], prefix: string): CommandPaletteItem[] {
|
|
220
|
+
const tagsByPrefix = new Map<string, string[]>([
|
|
221
|
+
["/", ["Command"]],
|
|
222
|
+
["@", ["Direct message"]],
|
|
223
|
+
["#", ["Channel"]],
|
|
224
|
+
["_", ["AI session"]],
|
|
225
|
+
["&", ["Infrastructure"]],
|
|
226
|
+
["?", ["Help"]],
|
|
227
|
+
[">", ["Terminal"]],
|
|
228
|
+
["+", ["Add"]],
|
|
229
|
+
["-", ["Remove"]],
|
|
230
|
+
["=", ["Comms"]],
|
|
231
|
+
["~", ["Settings"]],
|
|
232
|
+
["!", ["Notifications"]],
|
|
233
|
+
[".", ["Agent"]],
|
|
234
|
+
["*", ["Secret"]],
|
|
235
|
+
[":", ["Emoji"]],
|
|
236
|
+
["^", ["Link"]],
|
|
237
|
+
]);
|
|
238
|
+
const allowedTags = tagsByPrefix.get(prefix) ?? ["Surface"];
|
|
239
|
+
|
|
240
|
+
return items.filter((item) => allowedTags.includes(item.tag));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function surfaceTagForItem(item: SurfaceNavItem): string {
|
|
244
|
+
const explicitTags: Partial<Record<SurfaceId, string>> = {
|
|
245
|
+
agents: "Agent",
|
|
246
|
+
config: "Settings",
|
|
247
|
+
help: "Help",
|
|
248
|
+
notifications: "Notifications",
|
|
249
|
+
projectConfig: "Settings",
|
|
250
|
+
security: "Secret",
|
|
251
|
+
settings: "Settings",
|
|
252
|
+
vault: "Secret",
|
|
253
|
+
};
|
|
254
|
+
const infrastructureIds = navGroups.find((group) => group.label === "Infrastructure")?.items.map((groupItem) => groupItem.id) ?? [];
|
|
255
|
+
const commsIds = navGroups.filter((group) => group.mode === "comms").flatMap((group) => group.items.map((groupItem) => groupItem.id));
|
|
256
|
+
const utilityFallback = utilityNavItems.some((utilityItem) => utilityItem.id === item.id) ? "Surface" : undefined;
|
|
257
|
+
|
|
258
|
+
return explicitTags[item.id] ?? (infrastructureIds.includes(item.id) ? "Infrastructure" : undefined) ?? (commsIds.includes(item.id) ? "Comms" : undefined) ?? utilityFallback ?? "Surface";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function copyCurrentSurfaceLink(surface: SurfaceId): Promise<void> {
|
|
262
|
+
const browserUrl = typeof window === "undefined" ? "" : window.location.href.split("#")[0];
|
|
263
|
+
const link = browserUrl.length > 0 ? `${browserUrl}#surface=${surface}` : `aidevops://surface/${surface}`;
|
|
264
|
+
|
|
265
|
+
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
266
|
+
await navigator.clipboard.writeText(link);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function readCommandPaletteRecentIds(): string[] {
|
|
271
|
+
try {
|
|
272
|
+
const storedValue = window.localStorage.getItem(commandPaletteRecentStorageKey);
|
|
273
|
+
const parsedValue: unknown = storedValue ? JSON.parse(storedValue) : [];
|
|
274
|
+
return Array.isArray(parsedValue) ? parsedValue.filter((value): value is string => typeof value === "string").slice(0, commandPaletteRecentLimit) : [];
|
|
275
|
+
} catch {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function writeCommandPaletteRecentIds(ids: string[]): void {
|
|
281
|
+
try {
|
|
282
|
+
window.localStorage.setItem(commandPaletteRecentStorageKey, JSON.stringify(ids.slice(0, commandPaletteRecentLimit)));
|
|
283
|
+
} catch {
|
|
284
|
+
// Ignore unavailable storage; command navigation still works without recents.
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -32,6 +32,8 @@ export const surfaceIconNames = {
|
|
|
32
32
|
globe: true,
|
|
33
33
|
grid: true,
|
|
34
34
|
hardDrive: true,
|
|
35
|
+
hash: true,
|
|
36
|
+
help: true,
|
|
35
37
|
link: true,
|
|
36
38
|
list: true,
|
|
37
39
|
lock: true,
|
|
@@ -48,6 +50,7 @@ export const surfaceIconNames = {
|
|
|
48
50
|
export type SurfaceIconName = keyof typeof surfaceIconNames;
|
|
49
51
|
export const surfaceIds = [
|
|
50
52
|
"overview",
|
|
53
|
+
"help",
|
|
51
54
|
"settings",
|
|
52
55
|
"notifications",
|
|
53
56
|
"admin",
|
|
@@ -147,6 +150,8 @@ export const text = {
|
|
|
147
150
|
configIntro: "Read-only file explorer for ~/.config/aidevops. Contents stay hidden until redaction rules land.",
|
|
148
151
|
copyPath: "Copy path",
|
|
149
152
|
dashboard: "Dashboard",
|
|
153
|
+
help: "Help",
|
|
154
|
+
helpIntro: "Command shortcuts, navigation hints, and aidevops workflow guidance will collect here.",
|
|
150
155
|
domains: "Domains",
|
|
151
156
|
domainsIntro: "Draft domain ownership inventory across providers.",
|
|
152
157
|
draftOnly: "Local browser draft only. Saving requires a write-action manifest, confirmation, and audit trail.",
|
|
@@ -396,6 +401,7 @@ export const dashboardNavItem: SurfaceNavItem = {
|
|
|
396
401
|
};
|
|
397
402
|
|
|
398
403
|
export const utilityNavItems: SurfaceNavItem[] = [
|
|
404
|
+
{ id: "help", label: text.help, description: "Shortcuts, command palette symbols, and support", icon: "help", badge: text.planned },
|
|
399
405
|
{ id: "settings", label: "Settings", description: "Profile, login, notifications, language, and theme preferences", icon: "settings", badge: text.planned },
|
|
400
406
|
{ id: "notifications", label: "Notifications", description: "Notification inbox and delivery preferences", icon: "message", badge: text.planned },
|
|
401
407
|
{ id: "admin", label: "Admin", description: "Hosted administration controls", icon: "shield", badge: text.planned },
|
|
@@ -38,7 +38,7 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
38
38
|
.join("");
|
|
39
39
|
const navSections = [
|
|
40
40
|
{ heading: "Development", items: ["Local Repos", "Remote Repos", "Secrets", "AI Providers"] },
|
|
41
|
-
{ heading: "Account", items: ["Settings", "Notifications", "Admin"] },
|
|
41
|
+
{ heading: "Account", items: ["Help", "Settings", "Notifications", "Admin"] },
|
|
42
42
|
{ heading: "Operations", items: ["Dashboard", "Vault", "Agents file explorer", "Config", "Local Setup", "Routines"] },
|
|
43
43
|
{ heading: "Infrastructure", items: ["Devices", "VPNs & Proxies", "Apps", "Installation", "Registrars", "Hosts", "Servers"] },
|
|
44
44
|
{ heading: "Identities", items: ["Brands", "Domains", "Personas"] },
|
|
@@ -1066,12 +1066,14 @@ code {
|
|
|
1066
1066
|
|
|
1067
1067
|
.command-palette-backdrop {
|
|
1068
1068
|
align-items: center;
|
|
1069
|
-
|
|
1069
|
+
backdrop-filter: blur(8px) saturate(120%);
|
|
1070
|
+
background: rgb(0 0 0 / 34%);
|
|
1070
1071
|
display: flex;
|
|
1071
1072
|
inset: 0;
|
|
1072
1073
|
justify-content: center;
|
|
1073
1074
|
position: fixed;
|
|
1074
1075
|
z-index: 20;
|
|
1076
|
+
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
|
1075
1077
|
}
|
|
1076
1078
|
|
|
1077
1079
|
.command-palette-scrim {
|
|
@@ -1092,7 +1094,6 @@ code {
|
|
|
1092
1094
|
max-width: min(680px, calc(100vw - 32px));
|
|
1093
1095
|
padding: 14px;
|
|
1094
1096
|
position: relative;
|
|
1095
|
-
transform: translateY(-2vh);
|
|
1096
1097
|
width: 100%;
|
|
1097
1098
|
}
|
|
1098
1099
|
|
|
@@ -1137,7 +1138,8 @@ code {
|
|
|
1137
1138
|
}
|
|
1138
1139
|
|
|
1139
1140
|
.command-palette button:hover,
|
|
1140
|
-
.command-palette button:focus-visible
|
|
1141
|
+
.command-palette button:focus-visible,
|
|
1142
|
+
.command-palette button.active {
|
|
1141
1143
|
background: var(--surface-muted);
|
|
1142
1144
|
color: var(--accent);
|
|
1143
1145
|
outline: none;
|
|
@@ -1350,6 +1352,38 @@ code {
|
|
|
1350
1352
|
padding: 14px;
|
|
1351
1353
|
}
|
|
1352
1354
|
|
|
1355
|
+
.shortcut-list {
|
|
1356
|
+
display: grid;
|
|
1357
|
+
gap: 8px;
|
|
1358
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
1359
|
+
list-style: none;
|
|
1360
|
+
margin: 0;
|
|
1361
|
+
padding: 0;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.shortcut-list li {
|
|
1365
|
+
align-items: center;
|
|
1366
|
+
background: var(--surface-elevated);
|
|
1367
|
+
border: 1px solid var(--border);
|
|
1368
|
+
border-radius: 16px;
|
|
1369
|
+
display: flex;
|
|
1370
|
+
gap: 12px;
|
|
1371
|
+
padding: 12px 14px;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.shortcut-list kbd {
|
|
1375
|
+
align-items: center;
|
|
1376
|
+
background: var(--surface-muted);
|
|
1377
|
+
border: 1px solid var(--border);
|
|
1378
|
+
border-radius: 10px;
|
|
1379
|
+
color: var(--accent);
|
|
1380
|
+
display: inline-flex;
|
|
1381
|
+
font-weight: 900;
|
|
1382
|
+
justify-content: center;
|
|
1383
|
+
min-width: 34px;
|
|
1384
|
+
padding: 4px 8px;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1353
1387
|
.notification-list span {
|
|
1354
1388
|
color: var(--text-muted);
|
|
1355
1389
|
}
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.
|
|
15
|
+
# Version: 3.28.0
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|