aidevops 3.23.1 → 3.24.1
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 +1 -1
- package/packages/gui-web/src/AppWorkspace.tsx +199 -17
- package/packages/gui-web/src/app-model.ts +19 -0
- package/packages/gui-web/src/dashboard.ts +2 -1
- package/packages/gui-web/src/styles.css +288 -0
- package/setup.sh +3 -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.24.1
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -191,7 +191,7 @@ export function App(): ReactElement {
|
|
|
191
191
|
status={status.data}
|
|
192
192
|
themePreference={themePreference}
|
|
193
193
|
/>
|
|
194
|
-
<Workspace activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} fileRoot={fileRoot} status={status.data} />
|
|
194
|
+
<Workspace activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} fileRoot={fileRoot} setActiveSurface={setActiveSurface} status={status.data} />
|
|
195
195
|
<DesktopStatusBar status={status.data} />
|
|
196
196
|
</main>
|
|
197
197
|
);
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
|
-
import type
|
|
2
|
+
import { type ReactElement, type ReactNode, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { FiBell, FiCommand, FiCpu, FiGlobe, FiLogOut, FiMessageSquare, FiSearch, FiSettings, FiShield, FiUser } from "react-icons/fi";
|
|
3
4
|
import type { GuiFileRootId, GuiStatusData } from "../../gui-shared/src";
|
|
4
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
5
6
|
import type { SurfaceId, SurfaceNavItem } from "./app-model";
|
|
6
|
-
import { inventorySurfaceConfigs, text } from "./app-model";
|
|
7
|
+
import { inventorySurfaceConfigs, orderedNavItems, text } from "./app-model";
|
|
7
8
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
8
9
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
9
10
|
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
10
11
|
import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
const communityLinks = {
|
|
14
|
+
github: "https://github.com/marcusquinn/aidevops",
|
|
15
|
+
x: "https://x.com/marcuswquinn",
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileRoot, setActiveSurface, status }: {
|
|
13
19
|
activeItem: SurfaceNavItem;
|
|
14
20
|
activeSectionLabel: string;
|
|
15
21
|
activeSurface: SurfaceId;
|
|
16
22
|
fileRoot: GuiFileRootId | undefined;
|
|
23
|
+
setActiveSurface: (surface: SurfaceId) => void;
|
|
17
24
|
status: GuiStatusData;
|
|
18
25
|
}) {
|
|
19
26
|
return (
|
|
20
27
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
21
|
-
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel}
|
|
28
|
+
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} setActiveSurface={setActiveSurface} status={status} />
|
|
22
29
|
<div className="workspace-scroll">
|
|
23
30
|
<SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} status={status} />
|
|
24
31
|
</div>
|
|
@@ -26,12 +33,42 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileR
|
|
|
26
33
|
);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
function WorkspaceHeader({ activeItem, activeSectionLabel,
|
|
36
|
+
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, setActiveSurface, status }: {
|
|
30
37
|
activeItem: SurfaceNavItem;
|
|
31
38
|
activeSectionLabel: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
activeSurface: SurfaceId;
|
|
40
|
+
setActiveSurface: (surface: SurfaceId) => void;
|
|
41
|
+
status: GuiStatusData;
|
|
42
|
+
}): ReactElement {
|
|
43
|
+
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
44
|
+
const [commandOpen, setCommandOpen] = useState(false);
|
|
45
|
+
const [notificationsOpen, setNotificationsOpen] = useState(false);
|
|
46
|
+
const [profileOpen, setProfileOpen] = useState(false);
|
|
47
|
+
const userInitials = status.machine.initials || "AI";
|
|
48
|
+
const userName = status.machine.username || "Local user";
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const openCommandPalette = (event: KeyboardEvent) => {
|
|
52
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
setCommandOpen(true);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
window.addEventListener("keydown", openCommandPalette);
|
|
59
|
+
return () => window.removeEventListener("keydown", openCommandPalette);
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const closeMenus = () => {
|
|
63
|
+
setNotificationsOpen(false);
|
|
64
|
+
setProfileOpen(false);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const openSurface = (surface: SurfaceId) => {
|
|
68
|
+
setActiveSurface(surface);
|
|
69
|
+
closeMenus();
|
|
70
|
+
setCommandOpen(false);
|
|
71
|
+
};
|
|
35
72
|
|
|
36
73
|
return (
|
|
37
74
|
<header className="workspace-header">
|
|
@@ -43,22 +80,118 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, version }: {
|
|
|
43
80
|
</div>
|
|
44
81
|
</div>
|
|
45
82
|
<div className="header-actions">
|
|
46
|
-
<
|
|
83
|
+
<button className="workspace-search command-trigger" onClick={() => setCommandOpen(true)} type="button">
|
|
84
|
+
<FiSearch aria-hidden="true" />
|
|
47
85
|
<span>⌘K</span>
|
|
48
|
-
<
|
|
49
|
-
</
|
|
50
|
-
<
|
|
86
|
+
<strong>{text.searchPlaceholder}</strong>
|
|
87
|
+
</button>
|
|
88
|
+
<div className="header-action-menu">
|
|
89
|
+
<button aria-expanded={notificationsOpen} aria-label="Open notifications" className="header-icon-button" onClick={() => { setNotificationsOpen((current) => !current); setProfileOpen(false); }} type="button">
|
|
90
|
+
<FiBell aria-hidden="true" />
|
|
91
|
+
<span className="notification-dot" aria-hidden="true" />
|
|
92
|
+
</button>
|
|
93
|
+
{notificationsOpen ? <NotificationsMenu openSurface={openSurface} /> : null}
|
|
94
|
+
</div>
|
|
95
|
+
<button aria-pressed={assistantOpen} aria-label="Toggle AI Assistant" className={assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={() => setAssistantOpen((current) => !current)} type="button">
|
|
96
|
+
<FiCpu aria-hidden="true" />
|
|
97
|
+
</button>
|
|
98
|
+
<div className="header-action-menu">
|
|
99
|
+
<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">
|
|
100
|
+
<span>{userInitials}</span>
|
|
101
|
+
</button>
|
|
102
|
+
{profileOpen ? <ProfileMenu openSurface={openSurface} userName={userName} /> : null}
|
|
103
|
+
</div>
|
|
51
104
|
</div>
|
|
105
|
+
{assistantOpen ? <AssistantPanel activeSurface={activeSurface} userName={userName} /> : null}
|
|
106
|
+
{commandOpen ? <CommandPalette close={() => setCommandOpen(false)} openSurface={openSurface} /> : null}
|
|
52
107
|
</header>
|
|
53
108
|
);
|
|
54
109
|
}
|
|
55
110
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
111
|
+
function NotificationsMenu({ openSurface }: { openSurface: (surface: SurfaceId) => void }): ReactElement {
|
|
112
|
+
return (
|
|
113
|
+
<div className="popover-menu notifications-menu" role="menu">
|
|
114
|
+
<strong>Notifications</strong>
|
|
115
|
+
<p>Local readiness updates, release activity, and account alerts will collect here.</p>
|
|
116
|
+
<button onClick={() => openSurface("notifications")} role="menuitem" type="button">Open notifications</button>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
60
120
|
|
|
61
|
-
|
|
121
|
+
function ProfileMenu({ openSurface, userName }: { openSurface: (surface: SurfaceId) => void; userName: string }): ReactElement {
|
|
122
|
+
return (
|
|
123
|
+
<div className="popover-menu profile-menu" role="menu">
|
|
124
|
+
<div className="profile-menu-heading">
|
|
125
|
+
<FiUser aria-hidden="true" />
|
|
126
|
+
<span>{userName}</span>
|
|
127
|
+
</div>
|
|
128
|
+
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiSettings aria-hidden="true" /> Settings</button>
|
|
129
|
+
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiCommand aria-hidden="true" /> Theme</button>
|
|
130
|
+
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiGlobe aria-hidden="true" /> Language</button>
|
|
131
|
+
<div className="menu-separator" />
|
|
132
|
+
<strong>Community</strong>
|
|
133
|
+
<a href={communityLinks.github} rel="noreferrer" role="menuitem" target="_blank"><FiMessageSquare aria-hidden="true" /> GitHub</a>
|
|
134
|
+
<a href={communityLinks.x} rel="noreferrer" role="menuitem" target="_blank"><FiGlobe aria-hidden="true" /> X</a>
|
|
135
|
+
<div className="menu-separator" />
|
|
136
|
+
<button onClick={() => openSurface("admin")} role="menuitem" type="button"><FiShield aria-hidden="true" /> Admin</button>
|
|
137
|
+
<button className="disabled-menu-item" disabled role="menuitem" title="Hosted login is planned" type="button"><FiLogOut aria-hidden="true" /> Logout</button>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function AssistantPanel({ activeSurface, userName }: { activeSurface: SurfaceId; userName: string }): ReactElement {
|
|
143
|
+
return (
|
|
144
|
+
<aside className="assistant-panel" aria-label="AI Assistant">
|
|
145
|
+
<div>
|
|
146
|
+
<strong>AI Assistant</strong>
|
|
147
|
+
<p>Ready to help {userName} with the current {activeSurface} surface.</p>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="assistant-message">Hosted chat, local context, and workflow hand-off controls are planned.</div>
|
|
150
|
+
</aside>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function CommandPalette({ close, openSurface }: { close: () => void; openSurface: (surface: SurfaceId) => void }): ReactElement {
|
|
155
|
+
const [query, setQuery] = useState("");
|
|
156
|
+
const matches = useMemo(() => {
|
|
157
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
158
|
+
return orderedNavItems
|
|
159
|
+
.filter((item) => normalizedQuery.length === 0 || `${item.label} ${item.description}`.toLowerCase().includes(normalizedQuery))
|
|
160
|
+
.slice(0, 8);
|
|
161
|
+
}, [query]);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const closeOnEscape = (event: KeyboardEvent) => {
|
|
165
|
+
if (event.key === "Escape") {
|
|
166
|
+
close();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
window.addEventListener("keydown", closeOnEscape);
|
|
171
|
+
return () => window.removeEventListener("keydown", closeOnEscape);
|
|
172
|
+
}, [close]);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className="command-palette-backdrop" role="presentation">
|
|
176
|
+
<button aria-label="Close command palette" className="command-palette-scrim" onClick={close} type="button" />
|
|
177
|
+
<section aria-label="Command palette" className="command-palette">
|
|
178
|
+
<label className="command-input-row">
|
|
179
|
+
<FiSearch aria-hidden="true" />
|
|
180
|
+
<input onChange={(event) => setQuery(event.currentTarget.value)} placeholder="Search commands and surfaces" value={query} />
|
|
181
|
+
</label>
|
|
182
|
+
<ul>
|
|
183
|
+
{matches.map((item) => (
|
|
184
|
+
<li key={item.id}>
|
|
185
|
+
<button onClick={() => openSurface(item.id)} type="button">
|
|
186
|
+
<span className="surface-icon" aria-hidden="true"><SurfaceGlyph icon={item.icon} /></span>
|
|
187
|
+
<span><strong>{item.label}</strong><small>{item.description}</small></span>
|
|
188
|
+
</button>
|
|
189
|
+
</li>
|
|
190
|
+
))}
|
|
191
|
+
</ul>
|
|
192
|
+
</section>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
62
195
|
}
|
|
63
196
|
|
|
64
197
|
function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
@@ -71,6 +204,9 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
71
204
|
const vaultCollection = vaultCollectionForSurface(status.vault, activeSurface);
|
|
72
205
|
const staticSurfaces: Partial<Record<SurfaceId, ReactNode>> = {
|
|
73
206
|
overview: <OverviewSurface status={status} />,
|
|
207
|
+
settings: <SettingsSurface status={status} />,
|
|
208
|
+
notifications: <NotificationsSurface />,
|
|
209
|
+
admin: <AdminSurface />,
|
|
74
210
|
vault: <VaultSurface status={status} />,
|
|
75
211
|
routines: <PlannedSurface label={text.routines} detail={text.routineDetail} />,
|
|
76
212
|
devices: <PlannedSurface label={text.devices} detail={text.devicesIntro} />,
|
|
@@ -120,3 +256,49 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, status }: {
|
|
|
120
256
|
|
|
121
257
|
return staticSurfaces[activeSurface] ?? null;
|
|
122
258
|
}
|
|
259
|
+
|
|
260
|
+
function SettingsSurface({ status }: { status: GuiStatusData }): ReactElement {
|
|
261
|
+
return (
|
|
262
|
+
<section className="settings-surface">
|
|
263
|
+
<div className="planned-card">
|
|
264
|
+
<h2>General settings</h2>
|
|
265
|
+
<p>{text.settingsAccountIntro}</p>
|
|
266
|
+
</div>
|
|
267
|
+
<form className="settings-form">
|
|
268
|
+
<label><span>Name</span><input defaultValue={status.machine.username} disabled /></label>
|
|
269
|
+
<label><span>Login email</span><input disabled placeholder="email sign-in planned" type="email" /></label>
|
|
270
|
+
<label><span>Login password</span><input disabled placeholder="password management planned" type="password" /></label>
|
|
271
|
+
<label><span>Alternative notification email</span><input disabled placeholder="notifications email planned" type="email" /></label>
|
|
272
|
+
<label><span>Language</span><select disabled defaultValue="en"><option value="en">English</option></select></label>
|
|
273
|
+
<label><span>Theme</span><select disabled defaultValue="system"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select></label>
|
|
274
|
+
</form>
|
|
275
|
+
</section>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function NotificationsSurface(): ReactElement {
|
|
280
|
+
return (
|
|
281
|
+
<section className="settings-surface">
|
|
282
|
+
<div className="planned-card">
|
|
283
|
+
<h2>Notifications</h2>
|
|
284
|
+
<p>Release updates, workflow failures, security notices, and hosted account messages will appear here.</p>
|
|
285
|
+
</div>
|
|
286
|
+
<ul className="notification-list">
|
|
287
|
+
<li><strong>Readiness</strong><span>Local status and restart notices.</span></li>
|
|
288
|
+
<li><strong>Workflow</strong><span>PR, release, and routine activity.</span></li>
|
|
289
|
+
<li><strong>Security</strong><span>Vault and credentials posture reminders.</span></li>
|
|
290
|
+
</ul>
|
|
291
|
+
</section>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function AdminSurface(): ReactElement {
|
|
296
|
+
return (
|
|
297
|
+
<section className="settings-surface">
|
|
298
|
+
<div className="planned-card">
|
|
299
|
+
<h2>Admin</h2>
|
|
300
|
+
<p>Hosted administration, user access, billing, audit, and deployment controls are placeholders until authenticated server routes land.</p>
|
|
301
|
+
</div>
|
|
302
|
+
</section>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -46,6 +46,9 @@ export const surfaceIconNames = {
|
|
|
46
46
|
export type SurfaceIconName = keyof typeof surfaceIconNames;
|
|
47
47
|
export const surfaceIds = [
|
|
48
48
|
"overview",
|
|
49
|
+
"settings",
|
|
50
|
+
"notifications",
|
|
51
|
+
"admin",
|
|
49
52
|
"vault",
|
|
50
53
|
"agents",
|
|
51
54
|
"config",
|
|
@@ -221,6 +224,7 @@ export const text = {
|
|
|
221
224
|
secrets: "Secrets",
|
|
222
225
|
security: "Secrets",
|
|
223
226
|
securityBoundary: "No write routes, no shell bridge, no hosted app control, no pairing, no secret values. Vault policy distinguishes Provider AI, Local AI, and Hybrid modes before protected data leaves the device.",
|
|
227
|
+
settingsAccountIntro: "Hosted account settings are prepared as disabled controls until authenticated write routes, confirmation, and audit trails land.",
|
|
224
228
|
servers: "Servers",
|
|
225
229
|
serversIntro: "Server inventory by provider, operating system, orchestrator, and installed apps.",
|
|
226
230
|
setup: "Setup",
|
|
@@ -379,8 +383,15 @@ export const dashboardNavItem: SurfaceNavItem = {
|
|
|
379
383
|
icon: "grid",
|
|
380
384
|
};
|
|
381
385
|
|
|
386
|
+
export const utilityNavItems: SurfaceNavItem[] = [
|
|
387
|
+
{ id: "settings", label: "Settings", description: "Profile, login, notifications, language, and theme preferences", icon: "settings", badge: text.planned },
|
|
388
|
+
{ id: "notifications", label: "Notifications", description: "Notification inbox and delivery preferences", icon: "message", badge: text.planned },
|
|
389
|
+
{ id: "admin", label: "Admin", description: "Hosted administration controls", icon: "shield", badge: text.planned },
|
|
390
|
+
];
|
|
391
|
+
|
|
382
392
|
export const orderedNavItems: SurfaceNavItem[] = [
|
|
383
393
|
dashboardNavItem,
|
|
394
|
+
...utilityNavItems,
|
|
384
395
|
...navGroups.flatMap((group) => group.items),
|
|
385
396
|
];
|
|
386
397
|
|
|
@@ -516,6 +527,10 @@ export function isFontSizePreference(value: string | null): value is FontSizePre
|
|
|
516
527
|
}
|
|
517
528
|
|
|
518
529
|
export function sidebarModeForSurface(id: SurfaceId): SidebarMode {
|
|
530
|
+
if (utilityNavItems.some((item) => item.id === id)) {
|
|
531
|
+
return "comms";
|
|
532
|
+
}
|
|
533
|
+
|
|
519
534
|
return navGroups.find((group) => group.items.some((item) => item.id === id))?.mode ?? "devops";
|
|
520
535
|
}
|
|
521
536
|
|
|
@@ -528,6 +543,10 @@ export function findSurfaceSectionLabel(id: SurfaceId): string {
|
|
|
528
543
|
return text.aidevops;
|
|
529
544
|
}
|
|
530
545
|
|
|
546
|
+
if (utilityNavItems.some((item) => item.id === id)) {
|
|
547
|
+
return "Account";
|
|
548
|
+
}
|
|
549
|
+
|
|
531
550
|
for (const group of navGroups) {
|
|
532
551
|
for (const item of group.items) {
|
|
533
552
|
if (item.id === id) {
|
|
@@ -38,6 +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
42
|
{ heading: "Operations", items: ["Dashboard", "Vault", "Agents file explorer", "Config", "Local Setup", "Routines"] },
|
|
42
43
|
{ heading: "Infrastructure", items: ["Devices", "VPNs & Proxies", "Apps", "Installation", "Registrars", "Hosts", "Servers"] },
|
|
43
44
|
{ heading: "Identities", items: ["Brands", "Domains", "Personas"] },
|
|
@@ -54,7 +55,7 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
54
55
|
`<h1>aidevops app interface</h1>`,
|
|
55
56
|
`<p>Made for creators.</p>`,
|
|
56
57
|
`<p>AI-assisted development workflows, code quality, and deployment automation.</p>`,
|
|
57
|
-
`<p>Theme follows system preferences with light and dark overrides. Sidebar modes: DevOps and Comms. Appearance controls can be hidden or shown, and include editable Hue, icon Reset, Show borders toggle, Show counts toggle, Font size choices xs, s, m, lg, xl, and Font options: IBM Plex Mono, IBM Plex Sans, IBM Plex Serif, Inter, Menlo (default), Playpen Sans, Poppins, Source Sans, Source Serif, Tilt Neon, Ubuntu Mono. A desktop status bar shows local readiness, repo totals, secret reference count, provider accounts, and update state.</p>`,
|
|
58
|
+
`<p>Theme follows system preferences with light and dark overrides. Sidebar modes: DevOps and Comms. Appearance controls can be hidden or shown, and include editable Hue, icon Reset, Show borders toggle, Show counts toggle, Font size choices xs, s, m, lg, xl, and Font options: IBM Plex Mono, IBM Plex Sans, IBM Plex Serif, Inter, Menlo (default), Playpen Sans, Poppins, Source Sans, Source Serif, Tilt Neon, Ubuntu Mono. The workspace header includes a command palette, notifications menu, AI Assistant panel, and profile menu with Settings, Theme, Language, Community, Admin, and Logout entries. A desktop status bar shows local readiness, repo totals, secret reference count, provider accounts, and update state.</p>`,
|
|
58
59
|
`<p>Secret references stay read-only and redacted.</p>`,
|
|
59
60
|
`<p>Vault surfaces show padlock indicators. Encrypted by aidevops Vault; contents visible only when unlocked through app or authorised vault commands.</p>`,
|
|
60
61
|
`<p>${escapeHtml(status.data.update.message)}</p>`,
|
|
@@ -729,13 +729,29 @@ code {
|
|
|
729
729
|
|
|
730
730
|
.workspace-search {
|
|
731
731
|
background: var(--surface-muted);
|
|
732
|
+
border: 1px solid var(--border);
|
|
732
733
|
border-radius: 999px;
|
|
734
|
+
color: var(--text-secondary);
|
|
733
735
|
flex: 1 1 auto;
|
|
734
736
|
gap: 8px;
|
|
735
737
|
min-width: 160px;
|
|
736
738
|
padding: 8px 12px;
|
|
737
739
|
}
|
|
738
740
|
|
|
741
|
+
.command-trigger {
|
|
742
|
+
justify-content: flex-start;
|
|
743
|
+
max-width: 340px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.command-trigger strong {
|
|
747
|
+
color: var(--text-muted);
|
|
748
|
+
font-size: 0.86rem;
|
|
749
|
+
font-weight: 700;
|
|
750
|
+
overflow: hidden;
|
|
751
|
+
text-overflow: ellipsis;
|
|
752
|
+
white-space: nowrap;
|
|
753
|
+
}
|
|
754
|
+
|
|
739
755
|
.workspace-search span,
|
|
740
756
|
.workspace-search input {
|
|
741
757
|
color: var(--text-muted);
|
|
@@ -759,6 +775,234 @@ code {
|
|
|
759
775
|
white-space: nowrap;
|
|
760
776
|
}
|
|
761
777
|
|
|
778
|
+
.header-action-menu {
|
|
779
|
+
position: relative;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.header-icon-button,
|
|
783
|
+
.profile-avatar-button {
|
|
784
|
+
align-items: center;
|
|
785
|
+
background: var(--surface-muted);
|
|
786
|
+
border: 1px solid var(--border);
|
|
787
|
+
border-radius: 999px;
|
|
788
|
+
color: var(--text-secondary);
|
|
789
|
+
display: inline-flex;
|
|
790
|
+
height: 38px;
|
|
791
|
+
justify-content: center;
|
|
792
|
+
padding: 0;
|
|
793
|
+
position: relative;
|
|
794
|
+
width: 38px;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.header-icon-button:hover,
|
|
798
|
+
.header-icon-button:focus-visible,
|
|
799
|
+
.header-icon-button.active,
|
|
800
|
+
.profile-avatar-button:hover,
|
|
801
|
+
.profile-avatar-button:focus-visible,
|
|
802
|
+
.command-trigger:hover,
|
|
803
|
+
.command-trigger:focus-visible {
|
|
804
|
+
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
|
805
|
+
color: var(--accent);
|
|
806
|
+
outline: none;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.header-icon-button svg {
|
|
810
|
+
height: 19px;
|
|
811
|
+
width: 19px;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.profile-avatar-button {
|
|
815
|
+
background: color-mix(in srgb, var(--accent) 18%, var(--surface-muted));
|
|
816
|
+
color: var(--accent);
|
|
817
|
+
font-weight: 900;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.notification-dot {
|
|
821
|
+
background: var(--accent);
|
|
822
|
+
border: 2px solid var(--surface-elevated);
|
|
823
|
+
border-radius: 999px;
|
|
824
|
+
height: 10px;
|
|
825
|
+
position: absolute;
|
|
826
|
+
right: 7px;
|
|
827
|
+
top: 7px;
|
|
828
|
+
width: 10px;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.popover-menu {
|
|
832
|
+
background: var(--surface-elevated);
|
|
833
|
+
border: 1px solid var(--border);
|
|
834
|
+
border-radius: 18px;
|
|
835
|
+
box-shadow: 0 18px 60px rgb(0 0 0 / 18%);
|
|
836
|
+
display: grid;
|
|
837
|
+
gap: 8px;
|
|
838
|
+
min-width: 220px;
|
|
839
|
+
padding: 12px;
|
|
840
|
+
position: absolute;
|
|
841
|
+
right: 0;
|
|
842
|
+
top: calc(100% + 10px);
|
|
843
|
+
z-index: 8;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.profile-menu {
|
|
847
|
+
min-width: 240px;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.popover-menu button,
|
|
851
|
+
.popover-menu a {
|
|
852
|
+
align-items: center;
|
|
853
|
+
background: transparent;
|
|
854
|
+
border: 0;
|
|
855
|
+
border-radius: 12px;
|
|
856
|
+
color: var(--text-secondary);
|
|
857
|
+
display: flex;
|
|
858
|
+
gap: 10px;
|
|
859
|
+
padding: 9px 10px;
|
|
860
|
+
text-align: left;
|
|
861
|
+
text-decoration: none;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.popover-menu button:hover:not(:disabled),
|
|
865
|
+
.popover-menu a:hover,
|
|
866
|
+
.popover-menu button:focus-visible,
|
|
867
|
+
.popover-menu a:focus-visible {
|
|
868
|
+
background: var(--surface-muted);
|
|
869
|
+
color: var(--accent);
|
|
870
|
+
outline: none;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.popover-menu p {
|
|
874
|
+
color: var(--text-muted);
|
|
875
|
+
font-size: 0.88rem;
|
|
876
|
+
margin: 0;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.profile-menu-heading {
|
|
880
|
+
align-items: center;
|
|
881
|
+
display: flex;
|
|
882
|
+
gap: 10px;
|
|
883
|
+
padding: 4px 6px 8px;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.menu-separator {
|
|
887
|
+
border-top: 1px solid var(--border);
|
|
888
|
+
margin: 2px 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.disabled-menu-item {
|
|
892
|
+
opacity: 0.55;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.assistant-panel {
|
|
896
|
+
background: color-mix(in srgb, var(--surface-elevated) 98%, transparent);
|
|
897
|
+
border: 1px solid var(--border);
|
|
898
|
+
border-radius: 20px;
|
|
899
|
+
box-shadow: var(--glass-panel-shadow);
|
|
900
|
+
display: grid;
|
|
901
|
+
gap: 12px;
|
|
902
|
+
max-width: 360px;
|
|
903
|
+
padding: 16px;
|
|
904
|
+
position: absolute;
|
|
905
|
+
right: 14px;
|
|
906
|
+
top: calc(100% + 10px);
|
|
907
|
+
z-index: 7;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.assistant-panel p,
|
|
911
|
+
.assistant-message {
|
|
912
|
+
color: var(--text-muted);
|
|
913
|
+
margin: 0;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.assistant-message {
|
|
917
|
+
background: var(--surface-muted);
|
|
918
|
+
border-radius: 14px;
|
|
919
|
+
padding: 12px;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.command-palette-backdrop {
|
|
923
|
+
align-items: flex-start;
|
|
924
|
+
background: rgb(0 0 0 / 28%);
|
|
925
|
+
display: flex;
|
|
926
|
+
inset: 0;
|
|
927
|
+
justify-content: center;
|
|
928
|
+
padding-top: 12vh;
|
|
929
|
+
position: fixed;
|
|
930
|
+
z-index: 20;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
.command-palette-scrim {
|
|
934
|
+
background: transparent;
|
|
935
|
+
border: 0;
|
|
936
|
+
inset: 0;
|
|
937
|
+
padding: 0;
|
|
938
|
+
position: absolute;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.command-palette {
|
|
942
|
+
background: var(--surface-elevated);
|
|
943
|
+
border: 1px solid var(--border);
|
|
944
|
+
border-radius: 22px;
|
|
945
|
+
box-shadow: 0 26px 90px rgb(0 0 0 / 32%);
|
|
946
|
+
display: grid;
|
|
947
|
+
gap: 12px;
|
|
948
|
+
max-width: min(680px, calc(100vw - 32px));
|
|
949
|
+
padding: 14px;
|
|
950
|
+
position: relative;
|
|
951
|
+
width: 100%;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.command-input-row {
|
|
955
|
+
align-items: center;
|
|
956
|
+
background: var(--surface-muted);
|
|
957
|
+
border: 1px solid var(--border);
|
|
958
|
+
border-radius: 16px;
|
|
959
|
+
display: flex;
|
|
960
|
+
gap: 10px;
|
|
961
|
+
padding: 12px;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.command-input-row input {
|
|
965
|
+
background: transparent;
|
|
966
|
+
border: 0;
|
|
967
|
+
color: var(--text-primary);
|
|
968
|
+
flex: 1 1 auto;
|
|
969
|
+
outline: none;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
.command-palette ul,
|
|
973
|
+
.notification-list {
|
|
974
|
+
display: grid;
|
|
975
|
+
gap: 8px;
|
|
976
|
+
list-style: none;
|
|
977
|
+
margin: 0;
|
|
978
|
+
padding: 0;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.command-palette button {
|
|
982
|
+
align-items: center;
|
|
983
|
+
background: transparent;
|
|
984
|
+
border: 0;
|
|
985
|
+
border-radius: 14px;
|
|
986
|
+
color: var(--text-secondary);
|
|
987
|
+
display: flex;
|
|
988
|
+
gap: 12px;
|
|
989
|
+
padding: 10px;
|
|
990
|
+
text-align: left;
|
|
991
|
+
width: 100%;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.command-palette button:hover,
|
|
995
|
+
.command-palette button:focus-visible {
|
|
996
|
+
background: var(--surface-muted);
|
|
997
|
+
color: var(--accent);
|
|
998
|
+
outline: none;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.command-palette small {
|
|
1002
|
+
color: var(--text-muted);
|
|
1003
|
+
display: block;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
762
1006
|
.workspace-scroll {
|
|
763
1007
|
display: grid;
|
|
764
1008
|
gap: 16px;
|
|
@@ -774,6 +1018,7 @@ code {
|
|
|
774
1018
|
.hero-panel,
|
|
775
1019
|
.panel,
|
|
776
1020
|
.metric-card,
|
|
1021
|
+
.planned-card,
|
|
777
1022
|
.planned-home {
|
|
778
1023
|
background: var(--surface-elevated);
|
|
779
1024
|
border: 1px solid var(--border);
|
|
@@ -829,11 +1074,54 @@ code {
|
|
|
829
1074
|
}
|
|
830
1075
|
|
|
831
1076
|
.metric-card,
|
|
1077
|
+
.planned-card,
|
|
832
1078
|
.planned-home,
|
|
833
1079
|
.panel {
|
|
834
1080
|
padding: 18px;
|
|
835
1081
|
}
|
|
836
1082
|
|
|
1083
|
+
.settings-surface,
|
|
1084
|
+
.settings-form {
|
|
1085
|
+
display: grid;
|
|
1086
|
+
gap: 16px;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.settings-form {
|
|
1090
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.settings-form label {
|
|
1094
|
+
display: grid;
|
|
1095
|
+
gap: 8px;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.settings-form span {
|
|
1099
|
+
color: var(--text-secondary);
|
|
1100
|
+
font-weight: 800;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.settings-form input,
|
|
1104
|
+
.settings-form select {
|
|
1105
|
+
background: var(--surface-muted);
|
|
1106
|
+
border: 1px solid var(--border);
|
|
1107
|
+
border-radius: 14px;
|
|
1108
|
+
color: var(--text-secondary);
|
|
1109
|
+
padding: 11px 12px;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.notification-list li {
|
|
1113
|
+
background: var(--surface-elevated);
|
|
1114
|
+
border: 1px solid var(--border);
|
|
1115
|
+
border-radius: 16px;
|
|
1116
|
+
display: grid;
|
|
1117
|
+
gap: 4px;
|
|
1118
|
+
padding: 14px;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.notification-list span {
|
|
1122
|
+
color: var(--text-muted);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
837
1125
|
.metric-card span,
|
|
838
1126
|
.planned-home span,
|
|
839
1127
|
.data-row.header-row,
|
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.24.1
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|
|
@@ -1205,6 +1205,7 @@ _setup_run_scoped_stage() {
|
|
|
1205
1205
|
;;
|
|
1206
1206
|
"$SETUP_STAGE_AGENTS")
|
|
1207
1207
|
_time_step "$SETUP_STAGE_AGENTS" deploy_aidevops_agents
|
|
1208
|
+
_time_step "setup_opencode_plugins" setup_opencode_plugins
|
|
1208
1209
|
_time_step "_deploy_hotfix_config" _deploy_hotfix_config
|
|
1209
1210
|
;;
|
|
1210
1211
|
"$SETUP_STAGE_HOOKS")
|
|
@@ -1264,6 +1265,7 @@ _setup_run_non_interactive() {
|
|
|
1264
1265
|
_time_step "$SETUP_STAGE_OPENCODE" setup_opencode_cli
|
|
1265
1266
|
_time_step "validate_opencode_config" validate_opencode_config
|
|
1266
1267
|
_time_step "$SETUP_STAGE_AGENTS" deploy_aidevops_agents
|
|
1268
|
+
_time_step "setup_opencode_plugins" setup_opencode_plugins
|
|
1267
1269
|
_time_step "_setup_install_pulse_plist_early" _setup_install_pulse_plist_early
|
|
1268
1270
|
_time_step "_deploy_hotfix_config" _deploy_hotfix_config
|
|
1269
1271
|
_time_step "setup_opencode_desktop_launcher" setup_opencode_desktop_launcher
|