aidevops 3.31.13 → 3.31.15

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.31.13-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
61
+ [![Version](https://img.shields.io/badge/Version-3.31.15-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.31.13
1
+ 3.31.15
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.31.13
8
+ # Version: 3.31.15
9
9
 
10
10
  set -euo pipefail
11
11
 
@@ -1126,8 +1126,13 @@ cmd_version() {
1126
1126
  _dispatch_helper() {
1127
1127
  local script_name="$1" error_name="$2"
1128
1128
  shift 2
1129
- local hp="$AGENTS_DIR/scripts/$script_name"
1130
- [[ ! -f "$hp" ]] && hp="$INSTALL_DIR/.agents/scripts/$script_name"
1129
+ local source_hp="$INSTALL_DIR/.agents/scripts/$script_name"
1130
+ local deployed_hp="$AGENTS_DIR/scripts/$script_name"
1131
+ local hp="$deployed_hp"
1132
+ if [[ -f "$source_hp" && -f "$INSTALL_DIR/.agents/scripts/aidevops-cli/aidevops-repos-lib.sh" ]]; then
1133
+ hp="$source_hp"
1134
+ fi
1135
+ [[ ! -f "$hp" ]] && hp="$source_hp"
1131
1136
  if [[ -f "$hp" ]]; then
1132
1137
  bash "$hp" "$@"
1133
1138
  else
@@ -1712,6 +1717,7 @@ main() {
1712
1717
  ;;
1713
1718
  esac
1714
1719
  ;;
1720
+ reach) _dispatch_helper "reach-helper.sh" "reach-helper.sh" "$@" ;;
1715
1721
  config | configure) _dispatch_config "$@" ;;
1716
1722
  uninstall | remove) cmd_uninstall ;;
1717
1723
  version | v | -v | --version) cmd_version ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.31.13",
3
+ "version": "3.31.15",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -32,6 +32,10 @@
32
32
  "gui:test:components": "bun test packages/gui-web/tests",
33
33
  "gui:test:security": "bun test packages/gui-shared/tests/security.test.ts packages/gui-api/tests/security.test.ts packages/gui-web/tests/security.test.ts",
34
34
  "gui:test:smoke": "bun test packages/gui-api/tests/smoke.test.ts",
35
+ "gui:lint": "bun test packages/gui-web/tests/security.test.ts packages/gui-shared/tests/security.test.ts packages/gui-api/tests/security.test.ts",
36
+ "gui:typecheck": "tsc --noEmit",
37
+ "gui:build": "bun ./node_modules/vite/bin/vite.js build --config packages/gui-web/vite.config.ts",
38
+ "gui:ci": "npm run gui:lint && npm run gui:typecheck && npm run gui:test:schema && npm run gui:test:adapters && npm run gui:test:api && npm run gui:test:components && npm run gui:test:security && npm run gui:test:smoke && npm run gui:build",
35
39
  "gui:desktop:check": "bash packages/gui-desktop/scripts/install-macos-app.sh --check",
36
40
  "gui:desktop:install:macos": "bash packages/gui-desktop/scripts/install-macos-app.sh",
37
41
  "postinstall": "node scripts/npm-postinstall.cjs || true"
@@ -1,6 +1,6 @@
1
1
  /* jshint esversion: 11 */
2
2
  import { type ReactElement, type ReactNode, useEffect, useState } from "react";
3
- import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash, FiHelpCircle, FiInfo, FiLogOut, FiMessageSquare, FiPaperclip, FiSearch, FiSettings, FiShield, FiTerminal, FiTool, FiUser } from "react-icons/fi";
3
+ import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash, FiInfo, FiLogOut, FiMap, FiMessageSquare, FiPaperclip, FiSearch, FiSettings, FiShield, FiTerminal, FiTool, FiUser } from "react-icons/fi";
4
4
  import type { GuiFileRootId, GuiNotificationSummary, GuiStatusData } from "../../gui-shared/src";
5
5
  import { SurfaceGlyph } from "./AppNavigation";
6
6
  import type { ConversationMode, ShellMode, SurfaceId, SurfaceNavItem } from "./app-model";
@@ -14,6 +14,7 @@ import { PulseWorkersSurface } from "./PulseWorkersSurface";
14
14
  import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
15
15
  import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
16
16
  import { applyCommandPaletteSelection, useHeaderMenuState } from "./workspace-header-state";
17
+ import { useWorkspaceTour, WorkspaceTourProvider } from "./WorkspaceTour";
17
18
 
18
19
  const communityLinks = {
19
20
  github: "https://github.com/marcusquinn/aidevops",
@@ -42,12 +43,14 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
42
43
  }) {
43
44
  return (
44
45
  <section className="app-inset" aria-label={text.workspaceLabel}>
45
- <WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setSelectedLocalRepoIndex={setSelectedLocalRepoIndex} setSelectedSessionId={setSelectedSessionId} setShellMode={setShellMode} status={status} />
46
- <div className="workspace-scroll">
47
- {shellMode === "sessions"
48
- ? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} selectedSessionId={selectedSessionId} status={status} />
49
- : <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} openSurface={setActiveSurface} status={status} />}
50
- </div>
46
+ <WorkspaceTourProvider activeSurface={activeSurface}>
47
+ <WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setSelectedLocalRepoIndex={setSelectedLocalRepoIndex} setSelectedSessionId={setSelectedSessionId} setShellMode={setShellMode} status={status} />
48
+ <div className="workspace-scroll">
49
+ {shellMode === "sessions"
50
+ ? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} selectedSessionId={selectedSessionId} status={status} />
51
+ : <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} openSurface={setActiveSurface} status={status} />}
52
+ </div>
53
+ </WorkspaceTourProvider>
51
54
  </section>
52
55
  );
53
56
  }
@@ -73,6 +76,7 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
73
76
  const userInitials = status.machine.initials || "AI";
74
77
  const userName = status.machine.username || "Local user";
75
78
  const activeNotifications = status.notifications.filter((notification) => notification.status === "active");
79
+ const tour = useWorkspaceTour();
76
80
 
77
81
  useEffect(() => {
78
82
  const openCommandPalette = (event: KeyboardEvent) => {
@@ -125,13 +129,14 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
125
129
  </div>
126
130
  <div className="header-actions" onPointerEnter={headerMenus.clearScheduledHeaderClose} onPointerLeave={headerMenus.scheduleHeaderMenusClose} ref={headerMenus.headerActionsRef}>
127
131
  <div className="header-action-menu">
128
- <button aria-label="Open signposts and help" className="header-icon-button" onClick={() => { headerMenus.closeHeaderMenus(); openItem({ surface: "help" }); }} title="Signposts and help (?)" type="button">
129
- <FiHelpCircle aria-hidden="true" />
132
+ <button aria-disabled={!tour.hasTour} aria-label={tour.hasTour ? `Start ${activeItem.label} tour` : `No tour available for ${activeItem.label}`} className={tour.activeStep ? "header-icon-button active" : "header-icon-button"} disabled={!tour.hasTour} onClick={() => { headerMenus.closeHeaderMenus(); tour.startTour(); }} title={tour.hasTour ? `Start ${activeItem.label} tour` : `No tour available for ${activeItem.label}`} type="button">
133
+ <FiMap aria-hidden="true" />
130
134
  </button>
131
135
  <button aria-expanded={headerMenus.notificationsOpen} aria-label="Open notifications" className="header-icon-button" onClick={headerMenus.toggleNotifications} type="button">
132
136
  <FiBell aria-hidden="true" />
133
137
  {activeNotifications.length > 0 ? <span className="notification-dot" aria-hidden="true" /> : null}
134
138
  </button>
139
+ {tour.activeStep ? <WorkspaceSignposts label={activeItem.label} /> : null}
135
140
  {headerMenus.notificationsOpen ? <NotificationsMenu notifications={status.notifications} openSurface={(surface) => openItem({ surface })} /> : null}
136
141
  </div>
137
142
  <button aria-pressed={headerMenus.assistantOpen} aria-label="Toggle AI Assistant" className={headerMenus.assistantOpen ? "header-icon-button active" : "header-icon-button"} onClick={headerMenus.toggleAssistant} title="AI sessions (_)" type="button">
@@ -150,6 +155,31 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
150
155
  );
151
156
  }
152
157
 
158
+ function WorkspaceSignposts({ label }: { label: string }): ReactElement | null {
159
+ const tour = useWorkspaceTour();
160
+ const step = tour.activeStep;
161
+
162
+ if (!step) {
163
+ return null;
164
+ }
165
+
166
+ return (
167
+ <div className="popover-menu signposts-tour" role="dialog" aria-label={`${label} tour`}>
168
+ <div className="signposts-heading">
169
+ <strong>{step.title}</strong>
170
+ <span>{tour.activeStepIndex + 1}/{tour.stepCount}</span>
171
+ </div>
172
+ <p>{step.body}</p>
173
+ <code>{step.target}</code>
174
+ {tour.missingTarget ? <p className="tour-missing-target">Target is not visible on this page; continuing safely.</p> : null}
175
+ <div className="signposts-actions">
176
+ <button onClick={tour.nextStep} type="button">Next</button>
177
+ <button onClick={tour.closeTour} type="button">Close</button>
178
+ </div>
179
+ </div>
180
+ );
181
+ }
182
+
153
183
  function handleWorkspaceShortcut(event: KeyboardEvent, actions: { goBack: () => void; goForward: () => void; openCommand: (query: string) => void }): void {
154
184
  const shortcutQuery = commandPaletteShortcutQuery(event);
155
185
  const isEditableTarget = isEditableShortcutTarget(event.target);
@@ -386,9 +416,9 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
386
416
  aiSessions: <AiSessionsSurface selectedRepoIndex={0} status={status} />,
387
417
  channels: <CommsConversationSurface mode="channels" />,
388
418
  directMessages: <CommsConversationSurface mode="directMessages" />,
389
- workers: <PulseWorkersSurface status={status} />,
390
- repos: <PlannedSurface label={text.repos} detail={text.reposIntro} />,
391
- deployments: <PlannedSurface label={text.deployments} detail={text.deploymentsIntro} />,
419
+ workers: <div data-tour="workers-surface"><PulseWorkersSurface status={status} /></div>,
420
+ repos: <PlannedSurface label={text.repos} detail={text.reposIntro} tourId="repos-surface" />,
421
+ deployments: <PlannedSurface label={text.deployments} detail={text.deploymentsIntro} tourId="deployments-surface" />,
392
422
  routines: <PlannedSurface label={text.routines} detail={text.routineDetail} />,
393
423
  devices: <PlannedSurface label={text.devices} detail={text.devicesIntro} />,
394
424
  vpnsProxies: <PlannedSurface label={text.vpnsProxies} detail={text.vpnsProxiesIntro} />,
@@ -474,7 +504,7 @@ function HelpSurface(): ReactElement {
474
504
 
475
505
  function SettingsSurface({ status }: { status: GuiStatusData }): ReactElement {
476
506
  return (
477
- <section className="settings-surface">
507
+ <section className="settings-surface" data-tour="settings-surface">
478
508
  <div className="planned-card">
479
509
  <h2>General settings</h2>
480
510
  <p>{text.settingsAccountIntro}</p>
@@ -532,9 +532,9 @@ function isActionablePathRef(pathRef: string): boolean {
532
532
  return pathRef.startsWith("~/") || pathRef.startsWith("/");
533
533
  }
534
534
 
535
- export function PlannedSurface({ detail, label }: { detail: string; label: string }) {
535
+ export function PlannedSurface({ detail, label, tourId }: { detail: string; label: string; tourId?: string }) {
536
536
  return (
537
- <section className="panel" aria-label={label}>
537
+ <section className="panel" aria-label={label} data-tour={tourId}>
538
538
  <div className="section-heading">
539
539
  <p className="eyebrow">{text.planned}</p>
540
540
  <h2>{label}</h2>
@@ -0,0 +1,108 @@
1
+ import { createContext, type ReactElement, type ReactNode, useContext, useEffect, useMemo, useState } from "react";
2
+ import type { SurfaceId } from "./app-model";
3
+
4
+ export interface WorkspaceTourStep {
5
+ body: string;
6
+ target: string;
7
+ title: string;
8
+ }
9
+
10
+ export type WorkspaceTourRegistry = Partial<Record<SurfaceId, WorkspaceTourStep[]>>;
11
+
12
+ interface WorkspaceTourContextValue {
13
+ activeStep: WorkspaceTourStep | undefined;
14
+ activeStepIndex: number;
15
+ closeTour: () => void;
16
+ hasTour: boolean;
17
+ missingTarget: boolean;
18
+ nextStep: () => void;
19
+ startTour: () => void;
20
+ stepCount: number;
21
+ steps: WorkspaceTourStep[];
22
+ }
23
+
24
+ const WorkspaceTourContext = createContext<WorkspaceTourContextValue | undefined>(undefined);
25
+
26
+ export const workspaceTourRegistry = {
27
+ aiSessions: [
28
+ { body: "Choose a local repo/session context before the Turbostarter AI transport adapter is enabled.", target: '[data-tour="ai-session-list"]', title: "Session list" },
29
+ { body: "Read the current AI transcript, attachments, tool status, and GenUI cards in one MessageScroller-compatible lane.", target: '[data-tour="ai-session-transcript"]', title: "AI transcript" },
30
+ { body: "The prompt composer is disabled until audited write routes land, but its stable selector is ready for the session bridge.", target: '[data-tour="ai-composer"]', title: "Composer" },
31
+ ],
32
+ channels: [
33
+ { body: "Channels use the shared conversation model and encrypted transport placeholder.", target: '[data-tour="comms-conversations"]', title: "Channel workspace" },
34
+ { body: "Messages, system events, reactions, and Tambo DevOps cards render through the shared timeline.", target: '[data-tour="comms-message-timeline"]', title: "Channel timeline" },
35
+ ],
36
+ directMessages: [
37
+ { body: "Direct and group DM threads share the same conversation shell as channels.", target: '[data-tour="comms-conversations"]', title: "DM workspace" },
38
+ { body: "Protected payloads remain read-only until Vault policy and audited transport routes are connected.", target: '[data-tour="comms-message-timeline"]', title: "DM timeline" },
39
+ ],
40
+ workers: [
41
+ { body: "Pulse worker health, systemic findings, and safe diagnostic actions collect on this operational dashboard.", target: '[data-tour="workers-surface"]', title: "Workers dashboard" },
42
+ ],
43
+ repos: [
44
+ { body: "Repository context will connect local and remote repo adapters once audited read/write routes land.", target: '[data-tour="repos-surface"]', title: "Repos" },
45
+ ],
46
+ deployments: [
47
+ { body: "Deployment activity, environments, releases, and rollout checks will appear here after deployment adapters land.", target: '[data-tour="deployments-surface"]', title: "Deployments" },
48
+ ],
49
+ settings: [
50
+ { body: "Account, appearance, language, and notification preferences are grouped here while writes remain disabled.", target: '[data-tour="settings-surface"]', title: "Settings" },
51
+ ],
52
+ } satisfies WorkspaceTourRegistry;
53
+
54
+ export function WorkspaceTourProvider({ activeSurface, children, registry = workspaceTourRegistry }: { activeSurface: SurfaceId; children: ReactNode; registry?: WorkspaceTourRegistry }): ReactElement {
55
+ const steps = registry[activeSurface] ?? [];
56
+ const [activeStepIndex, setActiveStepIndex] = useState(0);
57
+ const [isOpen, setIsOpen] = useState(false);
58
+ const [missingTarget, setMissingTarget] = useState(false);
59
+ const activeStep = isOpen ? steps[activeStepIndex] : undefined;
60
+
61
+ useEffect(() => {
62
+ const nextSurface = activeSurface;
63
+ if (!nextSurface) return;
64
+ setIsOpen(false);
65
+ setActiveStepIndex(0);
66
+ setMissingTarget(false);
67
+ }, [activeSurface]);
68
+
69
+ useEffect(() => {
70
+ if (!activeStep || typeof document === "undefined") {
71
+ setMissingTarget(false);
72
+ return;
73
+ }
74
+
75
+ const target = document.querySelector(activeStep.target);
76
+ setMissingTarget(target === null);
77
+ if (target instanceof HTMLElement) {
78
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
79
+ }
80
+ }, [activeStep]);
81
+
82
+ const value = useMemo(() => ({
83
+ activeStep,
84
+ activeStepIndex,
85
+ closeTour: () => setIsOpen(false),
86
+ hasTour: steps.length > 0,
87
+ missingTarget,
88
+ nextStep: () => setActiveStepIndex((current) => current + 1 >= steps.length ? 0 : current + 1),
89
+ startTour: () => {
90
+ if (steps.length === 0) return;
91
+ setActiveStepIndex(0);
92
+ setIsOpen(true);
93
+ },
94
+ stepCount: steps.length,
95
+ steps,
96
+ }), [activeStep, activeStepIndex, missingTarget, steps]);
97
+
98
+ return <WorkspaceTourContext.Provider value={value}>{children}</WorkspaceTourContext.Provider>;
99
+ }
100
+
101
+ export function useWorkspaceTour(): WorkspaceTourContextValue {
102
+ const context = useContext(WorkspaceTourContext);
103
+ if (!context) {
104
+ throw new Error("useWorkspaceTour must be used inside WorkspaceTourProvider");
105
+ }
106
+
107
+ return context;
108
+ }
@@ -1364,6 +1364,34 @@ code {
1364
1364
  min-width: 320px;
1365
1365
  }
1366
1366
 
1367
+ .signposts-tour {
1368
+ min-width: min(360px, 88vw);
1369
+ }
1370
+
1371
+ .signposts-heading,
1372
+ .signposts-actions {
1373
+ align-items: center;
1374
+ display: flex;
1375
+ justify-content: space-between;
1376
+ }
1377
+
1378
+ .signposts-heading span {
1379
+ background: var(--surface-muted);
1380
+ border-radius: 999px;
1381
+ color: var(--text-muted);
1382
+ font-size: 0.76rem;
1383
+ font-weight: 900;
1384
+ padding: 4px 8px;
1385
+ }
1386
+
1387
+ .signposts-tour code {
1388
+ overflow-wrap: anywhere;
1389
+ }
1390
+
1391
+ .tour-missing-target {
1392
+ color: var(--notification-warning-fg) !important;
1393
+ }
1394
+
1367
1395
  .notifications-menu-heading,
1368
1396
  .notification-preview {
1369
1397
  align-items: center;
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.31.13
15
+ # Version: 3.31.15
16
16
  #
17
17
  # Quick Install:
18
18
  # npm install -g aidevops && aidevops update (recommended)