aidevops 3.24.0 → 3.24.2

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 CHANGED
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
58
58
  [![Copyright](https://img.shields.io/badge/Copyright-Marcus%20Quinn%202025--2026-blue.svg)](https://github.com/marcusquinn)
59
59
 
60
60
  <!-- Release & Version Info -->
61
- [![Version](https://img.shields.io/badge/Version-3.24.0-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
61
+ [![Version](https://img.shields.io/badge/Version-3.24.2-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
62
62
  [![npm version](https://img.shields.io/npm/v/aidevops)](https://www.npmjs.com/package/aidevops)
63
63
  [![Homebrew](https://img.shields.io/badge/homebrew-marcusquinn%2Ftap-orange)](https://github.com/marcusquinn/homebrew-tap)
64
64
  [![GitHub repository](https://img.shields.io/badge/github-repository-181717.svg?logo=github)](https://github.com/marcusquinn/aidevops)
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.24.0
1
+ 3.24.2
package/aidevops.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # AI DevOps Framework CLI
6
6
  # Usage: aidevops <command> [options]
7
7
  #
8
- # Version: 3.24.0
8
+ # Version: 3.24.2
9
9
 
10
10
  set -euo pipefail
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.24.0",
3
+ "version": "3.24.2",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -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 { ReactNode } from "react";
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
- export function Workspace({ activeItem, activeSectionLabel, activeSurface, fileRoot, status }: {
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} version={displayVersion(status)} />
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, version }: {
36
+ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, setActiveSurface, status }: {
30
37
  activeItem: SurfaceNavItem;
31
38
  activeSectionLabel: string;
32
- version: string;
33
- }) {
34
- const versionLabel = version.startsWith("v") ? version : `v${version}`;
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
- <label className="workspace-search">
83
+ <button className="workspace-search command-trigger" onClick={() => setCommandOpen(true)} type="button">
84
+ <FiSearch aria-hidden="true" />
47
85
  <span>⌘K</span>
48
- <input disabled placeholder={text.searchPlaceholder} />
49
- </label>
50
- <span className="version-pill" title={`aidevops version ${versionLabel}`}>{versionLabel}</span>
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 displayVersion(status: GuiStatusData): string {
57
- if (status.update.installed_version !== "unknown") {
58
- return status.update.installed_version;
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
- return status.aidevops_version;
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.24.0
15
+ # Version: 3.24.2
16
16
  #
17
17
  # Quick Install:
18
18
  # npm install -g aidevops && aidevops update (recommended)