aidevops 3.27.0 → 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 +79 -24
- package/packages/gui-web/src/CommandPalette.tsx +107 -21
- 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 +32 -0
- 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,6 +1,6 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { type ReactElement, type ReactNode, useEffect, useState } from "react";
|
|
3
|
-
import { FiBell, FiChevronLeft, FiChevronRight, FiCommand,
|
|
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";
|
|
@@ -16,7 +16,7 @@ const communityLinks = {
|
|
|
16
16
|
x: "https://x.com/marcuswquinn",
|
|
17
17
|
} as const;
|
|
18
18
|
|
|
19
|
-
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 }: {
|
|
20
20
|
activeItem: SurfaceNavItem;
|
|
21
21
|
activeSectionLabel: string;
|
|
22
22
|
activeSurface: SurfaceId;
|
|
@@ -28,12 +28,14 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
28
28
|
goForward: () => void;
|
|
29
29
|
selectedLocalRepoIndex: number;
|
|
30
30
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
31
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
32
|
+
setShellMode: (mode: ShellMode) => void;
|
|
31
33
|
shellMode: ShellMode;
|
|
32
34
|
status: GuiStatusData;
|
|
33
35
|
}) {
|
|
34
36
|
return (
|
|
35
37
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
36
|
-
<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} />
|
|
37
39
|
<div className="workspace-scroll">
|
|
38
40
|
{shellMode === "sessions"
|
|
39
41
|
? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} status={status} />
|
|
@@ -43,7 +45,7 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
43
45
|
);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
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 }: {
|
|
47
49
|
activeItem: SurfaceNavItem;
|
|
48
50
|
activeSectionLabel: string;
|
|
49
51
|
activeSurface: SurfaceId;
|
|
@@ -52,6 +54,8 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
52
54
|
goBack: () => void;
|
|
53
55
|
goForward: () => void;
|
|
54
56
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
57
|
+
setConversationMode: (mode: ConversationMode) => void;
|
|
58
|
+
setShellMode: (mode: ShellMode) => void;
|
|
55
59
|
status: GuiStatusData;
|
|
56
60
|
}): ReactElement {
|
|
57
61
|
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
@@ -64,31 +68,30 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
64
68
|
|
|
65
69
|
useEffect(() => {
|
|
66
70
|
const openCommandPalette = (event: KeyboardEvent) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (shortcutQuery !== undefined && !isEditableShortcutTarget(event.target)) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
setCommandInitialQuery(shortcutQuery);
|
|
78
|
-
setCommandOpen(true);
|
|
79
|
-
}
|
|
71
|
+
handleWorkspaceShortcut(event, {
|
|
72
|
+
goBack,
|
|
73
|
+
goForward,
|
|
74
|
+
openCommand: (query) => {
|
|
75
|
+
setCommandInitialQuery(query);
|
|
76
|
+
setCommandOpen(true);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
80
79
|
};
|
|
81
80
|
|
|
82
81
|
window.addEventListener("keydown", openCommandPalette);
|
|
83
82
|
return () => window.removeEventListener("keydown", openCommandPalette);
|
|
84
|
-
}, []);
|
|
83
|
+
}, [goBack, goForward]);
|
|
85
84
|
|
|
86
85
|
const closeMenus = () => {
|
|
87
86
|
setNotificationsOpen(false);
|
|
88
87
|
setProfileOpen(false);
|
|
89
88
|
};
|
|
90
89
|
|
|
91
|
-
const
|
|
90
|
+
const openItem = ({ conversationMode, shellMode, surface }: { conversationMode?: ConversationMode; shellMode?: ShellMode; surface: SurfaceId }) => {
|
|
91
|
+
setShellMode(shellMode ?? "devices");
|
|
92
|
+
if (conversationMode) {
|
|
93
|
+
setConversationMode(conversationMode);
|
|
94
|
+
}
|
|
92
95
|
setActiveSurface(surface);
|
|
93
96
|
closeMenus();
|
|
94
97
|
setCommandOpen(false);
|
|
@@ -116,28 +119,45 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
116
119
|
</div>
|
|
117
120
|
<div className="header-actions">
|
|
118
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>
|
|
119
125
|
<button aria-expanded={notificationsOpen} aria-label="Open notifications" className="header-icon-button" onClick={() => { setNotificationsOpen((current) => !current); setProfileOpen(false); }} type="button">
|
|
120
126
|
<FiBell aria-hidden="true" />
|
|
121
127
|
<span className="notification-dot" aria-hidden="true" />
|
|
122
128
|
</button>
|
|
123
|
-
{notificationsOpen ? <NotificationsMenu openSurface={
|
|
129
|
+
{notificationsOpen ? <NotificationsMenu openSurface={(surface) => openItem({ surface })} /> : null}
|
|
124
130
|
</div>
|
|
125
|
-
<button aria-pressed={assistantOpen} aria-label="Toggle AI Assistant" className={assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={() => setAssistantOpen((current) => !current)} type="button">
|
|
126
|
-
<
|
|
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" />
|
|
127
133
|
</button>
|
|
128
134
|
<div className="header-action-menu">
|
|
129
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">
|
|
130
136
|
<span>{userInitials}</span>
|
|
131
137
|
</button>
|
|
132
|
-
{profileOpen ? <ProfileMenu openSurface={
|
|
138
|
+
{profileOpen ? <ProfileMenu openSurface={(surface) => openItem({ surface })} userName={userName} /> : null}
|
|
133
139
|
</div>
|
|
134
140
|
</div>
|
|
135
141
|
{assistantOpen ? <AssistantPanel activeSurface={activeSurface} userName={userName} /> : null}
|
|
136
|
-
{commandOpen ? <CommandPalette close={() => setCommandOpen(false)} initialQuery={commandInitialQuery}
|
|
142
|
+
{commandOpen ? <CommandPalette activeSurface={activeSurface} close={() => setCommandOpen(false)} initialQuery={commandInitialQuery} openItem={openItem} status={status} /> : null}
|
|
137
143
|
</header>
|
|
138
144
|
);
|
|
139
145
|
}
|
|
140
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
|
+
|
|
141
161
|
function isEditableShortcutTarget(target: EventTarget | null): boolean {
|
|
142
162
|
if (!(target instanceof HTMLElement)) {
|
|
143
163
|
return false;
|
|
@@ -247,6 +267,7 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
247
267
|
const vaultCollection = vaultCollectionForSurface(status.vault, activeSurface);
|
|
248
268
|
const staticSurfaces: Partial<Record<SurfaceId, ReactNode>> = {
|
|
249
269
|
overview: <OverviewSurface status={status} />,
|
|
270
|
+
help: <HelpSurface />,
|
|
250
271
|
settings: <SettingsSurface status={status} />,
|
|
251
272
|
notifications: <NotificationsSurface />,
|
|
252
273
|
admin: <AdminSurface />,
|
|
@@ -300,6 +321,40 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
300
321
|
return staticSurfaces[activeSurface] ?? null;
|
|
301
322
|
}
|
|
302
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
|
+
|
|
303
358
|
function SettingsSurface({ status }: { status: GuiStatusData }): ReactElement {
|
|
304
359
|
return (
|
|
305
360
|
<section className="settings-surface">
|
|
@@ -3,22 +3,26 @@ import { type ReactElement, useCallback, useEffect, useMemo, useRef, useState }
|
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { FiSearch } from "react-icons/fi";
|
|
5
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
|
-
import type { SurfaceId, SurfaceNavItem } from "./app-model";
|
|
7
|
-
import { orderedNavItems } from "./app-model";
|
|
6
|
+
import type { ConversationMode, ShellMode, SurfaceId, SurfaceIconName, SurfaceNavItem } from "./app-model";
|
|
7
|
+
import { navGroups, orderedNavItems, utilityNavItems } from "./app-model";
|
|
8
8
|
import type { GuiStatusData } from "../../gui-shared/src";
|
|
9
9
|
|
|
10
10
|
interface CommandPaletteItem {
|
|
11
|
+
action?: "copy-current-link";
|
|
11
12
|
description: string;
|
|
12
|
-
|
|
13
|
+
conversationMode?: ConversationMode;
|
|
14
|
+
icon: SurfaceIconName;
|
|
15
|
+
iconGlyph?: string;
|
|
13
16
|
id: string;
|
|
14
17
|
label: string;
|
|
15
18
|
searchText: string;
|
|
19
|
+
shellMode?: ShellMode;
|
|
16
20
|
surface: SurfaceId;
|
|
17
21
|
tag: string;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const commandPaletteRecentStorageKey = "aidevops-gui-command-palette-recents";
|
|
21
|
-
const commandPaletteRecentLimit =
|
|
25
|
+
const commandPaletteRecentLimit = 5;
|
|
22
26
|
|
|
23
27
|
const slashCommandItems: CommandPaletteItem[] = [
|
|
24
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" },
|
|
@@ -28,13 +32,41 @@ const slashCommandItems: CommandPaletteItem[] = [
|
|
|
28
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" },
|
|
29
33
|
];
|
|
30
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
|
+
|
|
31
57
|
const plannedChannelItems: CommandPaletteItem[] = [
|
|
32
|
-
{ id: "channel-devops", label: "#devops", description: "Chat channel for aidevops operations", icon: "
|
|
33
|
-
{ id: "channel-comms", label: "#comms", description: "Chat channel for communications", icon: "
|
|
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" },
|
|
34
60
|
];
|
|
35
61
|
|
|
36
|
-
export
|
|
37
|
-
|
|
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]);
|
|
38
70
|
const [query, setQuery] = useState(initialQuery);
|
|
39
71
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
40
72
|
const [recentIds, setRecentIds] = useState<string[]>(() => readCommandPaletteRecentIds());
|
|
@@ -45,9 +77,12 @@ export function CommandPalette({ close, initialQuery, openSurface, status }: { c
|
|
|
45
77
|
if (item) {
|
|
46
78
|
const nextRecentIds = rememberCommandPaletteItemId(item.id, recentIds);
|
|
47
79
|
setRecentIds(nextRecentIds);
|
|
48
|
-
|
|
80
|
+
if (item.action === "copy-current-link") {
|
|
81
|
+
void copyCurrentSurfaceLink(item.surface);
|
|
82
|
+
}
|
|
83
|
+
openItem({ conversationMode: item.conversationMode, shellMode: item.shellMode, surface: item.surface });
|
|
49
84
|
}
|
|
50
|
-
}, [
|
|
85
|
+
}, [openItem, recentIds]);
|
|
51
86
|
|
|
52
87
|
useEffect(() => {
|
|
53
88
|
inputRef.current?.focus();
|
|
@@ -83,13 +118,13 @@ export function CommandPalette({ close, initialQuery, openSurface, status }: { c
|
|
|
83
118
|
<section aria-label="Command palette" className="command-palette">
|
|
84
119
|
<label className="command-input-row">
|
|
85
120
|
<FiSearch aria-hidden="true" />
|
|
86
|
-
<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, #
|
|
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} />
|
|
87
122
|
</label>
|
|
88
123
|
<ul id="command-palette-results">
|
|
89
124
|
{matches.map((item, index) => (
|
|
90
125
|
<li key={item.id}>
|
|
91
126
|
<button className={index === activeIndex ? "active" : undefined} id={item.id} onClick={() => selectItem(item)} type="button">
|
|
92
|
-
<span className="surface-icon" aria-hidden="true"
|
|
127
|
+
<span className="surface-icon" aria-hidden="true">{item.iconGlyph ?? <SurfaceGlyph icon={item.icon} />}</span>
|
|
93
128
|
<span><strong>{item.label}</strong><small>{item.tag} · {item.description}</small></span>
|
|
94
129
|
</button>
|
|
95
130
|
</li>
|
|
@@ -106,9 +141,11 @@ export function commandPaletteShortcutQuery(event: Pick<KeyboardEvent, "key" | "
|
|
|
106
141
|
return undefined;
|
|
107
142
|
}
|
|
108
143
|
|
|
109
|
-
return new Map(
|
|
144
|
+
return new Map(commandPaletteShortcutEntries).get(event.key);
|
|
110
145
|
}
|
|
111
146
|
|
|
147
|
+
export const commandPaletteShortcutEntries = ["#", "_", "&", "?", ">", "+", "-", "/", "=", "~", "!", ".", "*", ":", "^"].map((shortcut) => [shortcut, shortcut] as const);
|
|
148
|
+
|
|
112
149
|
export function orderCommandItemsByRecency<T extends { id: string }>(items: T[], recentIds: string[]): T[] {
|
|
113
150
|
const recentItems = recentIds
|
|
114
151
|
.map((id) => items.find((item) => item.id === id))
|
|
@@ -124,12 +161,12 @@ export function rememberCommandPaletteItemId(itemId: string, currentIds: string[
|
|
|
124
161
|
return nextIds;
|
|
125
162
|
}
|
|
126
163
|
|
|
127
|
-
function commandPaletteMatches(items: CommandPaletteItem[], query: string, recentIds: string[]): CommandPaletteItem[] {
|
|
164
|
+
export function commandPaletteMatches(items: CommandPaletteItem[], query: string, recentIds: string[]): CommandPaletteItem[] {
|
|
128
165
|
const normalizedQuery = query.trim().toLowerCase();
|
|
129
166
|
const prefix = normalizedQuery.charAt(0);
|
|
130
167
|
const scopedItems = itemsForCommandPrefix(items, prefix);
|
|
131
168
|
|
|
132
|
-
if (normalizedQuery.length === 0 || [
|
|
169
|
+
if (normalizedQuery.length === 0 || commandPaletteShortcutEntries.some(([shortcut]) => shortcut === normalizedQuery)) {
|
|
133
170
|
return orderCommandItemsByRecency(scopedItems, recentIds).slice(0, 8);
|
|
134
171
|
}
|
|
135
172
|
|
|
@@ -138,7 +175,7 @@ function commandPaletteMatches(items: CommandPaletteItem[], query: string, recen
|
|
|
138
175
|
.slice(0, 8);
|
|
139
176
|
}
|
|
140
177
|
|
|
141
|
-
function commandPaletteItems(status: GuiStatusData): CommandPaletteItem[] {
|
|
178
|
+
function commandPaletteItems(status: GuiStatusData, activeSurface: SurfaceId): CommandPaletteItem[] {
|
|
142
179
|
const surfaceItems: CommandPaletteItem[] = orderedNavItems.map((item) => ({
|
|
143
180
|
description: item.description,
|
|
144
181
|
icon: item.icon,
|
|
@@ -146,15 +183,24 @@ function commandPaletteItems(status: GuiStatusData): CommandPaletteItem[] {
|
|
|
146
183
|
label: item.label,
|
|
147
184
|
searchText: `${item.label} ${item.description}`,
|
|
148
185
|
surface: item.id,
|
|
149
|
-
tag:
|
|
186
|
+
tag: surfaceTagForItem(item),
|
|
150
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
|
+
];
|
|
151
194
|
const sessionItems: CommandPaletteItem[] = status.opencode_sessions.sessions.map((session) => ({
|
|
152
195
|
description: `AI session in ${session.repo_path_ref}`,
|
|
153
196
|
icon: "terminal",
|
|
197
|
+
iconGlyph: "_",
|
|
154
198
|
id: `session-${session.id_ref}`,
|
|
155
|
-
label:
|
|
156
|
-
searchText:
|
|
199
|
+
label: `_${session.title}`,
|
|
200
|
+
searchText: `_${session.title} ${session.title} ${session.repo_path_ref} ${session.agent} ${session.model}`,
|
|
157
201
|
surface: "git",
|
|
202
|
+
shellMode: "sessions",
|
|
203
|
+
conversationMode: "ai",
|
|
158
204
|
tag: "AI session",
|
|
159
205
|
}));
|
|
160
206
|
const directMessageItems: CommandPaletteItem[] = [{
|
|
@@ -167,20 +213,60 @@ function commandPaletteItems(status: GuiStatusData): CommandPaletteItem[] {
|
|
|
167
213
|
tag: "Direct message",
|
|
168
214
|
}];
|
|
169
215
|
|
|
170
|
-
return [...surfaceItems, ...sessionItems, ...plannedChannelItems, ...directMessageItems, ...slashCommandItems];
|
|
216
|
+
return [...surfaceItems, ...helpItems, ...sessionItems, ...plannedChannelItems, ...directMessageItems, ...slashCommandItems, ...addCommandItems, ...removeCommandItems, ...terminalCommandItems, ...emojiItems, ...currentLinkItems];
|
|
171
217
|
}
|
|
172
218
|
|
|
173
219
|
function itemsForCommandPrefix(items: CommandPaletteItem[], prefix: string): CommandPaletteItem[] {
|
|
174
220
|
const tagsByPrefix = new Map<string, string[]>([
|
|
175
221
|
["/", ["Command"]],
|
|
176
222
|
["@", ["Direct message"]],
|
|
177
|
-
["#", ["
|
|
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"]],
|
|
178
237
|
]);
|
|
179
238
|
const allowedTags = tagsByPrefix.get(prefix) ?? ["Surface"];
|
|
180
239
|
|
|
181
240
|
return items.filter((item) => allowedTags.includes(item.tag));
|
|
182
241
|
}
|
|
183
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
|
+
|
|
184
270
|
function readCommandPaletteRecentIds(): string[] {
|
|
185
271
|
try {
|
|
186
272
|
const storedValue = window.localStorage.getItem(commandPaletteRecentStorageKey);
|
|
@@ -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"] },
|
|
@@ -1352,6 +1352,38 @@ code {
|
|
|
1352
1352
|
padding: 14px;
|
|
1353
1353
|
}
|
|
1354
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
|
+
|
|
1355
1387
|
.notification-list span {
|
|
1356
1388
|
color: var(--text-muted);
|
|
1357
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)
|