aidevops 3.29.19 → 3.29.20
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-api/src/status-adapter-utils.ts +48 -4
- package/packages/gui-api/src/status-managed-apps.ts +16 -4
- package/packages/gui-desktop/scripts/install-macos-app.sh +15 -1
- package/packages/gui-web/src/InventorySurfaces.tsx +65 -8
- package/packages/gui-web/src/styles.css +26 -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.29.
|
|
1
|
+
3.29.20
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
export function readJsonObject(pathName: string): { health: "present" | "missing" | "invalid"; value: Record<string, unknown> } {
|
|
@@ -112,12 +112,34 @@ export function firstExistingPathRef(pathRefs: string[]): string | null {
|
|
|
112
112
|
return pathRefs.find((pathRef) => existsSync(expandHome(pathRef))) ?? null;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export function firstExecutablePathRef(pathRefs: string[]): string | null {
|
|
116
|
+
return pathRefs.find((pathRef) => isExecutable(expandHome(pathRef))) ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function isExecutable(pathName: string): boolean {
|
|
120
|
+
try {
|
|
121
|
+
if (!statSync(pathName).isFile()) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
accessSync(pathName, constants.X_OK);
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
export function resolveBinary(binary: string): string | null {
|
|
132
|
+
const candidatePath = binaryPathCandidates(binary).find((pathName) => isExecutable(pathName));
|
|
133
|
+
if (candidatePath !== undefined) {
|
|
134
|
+
return candidatePath;
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
try {
|
|
117
138
|
const output = execFileSync("which", [binary], {
|
|
139
|
+
env: { ...process.env, PATH: expandedPath() },
|
|
118
140
|
encoding: "utf8",
|
|
119
141
|
stdio: ["ignore", "pipe", "ignore"],
|
|
120
|
-
timeout:
|
|
142
|
+
timeout: 350,
|
|
121
143
|
}).trim();
|
|
122
144
|
return output.split("\n").find((line) => line.length > 0) ?? null;
|
|
123
145
|
} catch {
|
|
@@ -125,6 +147,27 @@ export function resolveBinary(binary: string): string | null {
|
|
|
125
147
|
}
|
|
126
148
|
}
|
|
127
149
|
|
|
150
|
+
function binaryPathCandidates(binary: string): string[] {
|
|
151
|
+
return expandedPath().split(":").filter(Boolean).map((dir) => join(dir, binary));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function expandedPath(): string {
|
|
155
|
+
const home = process.env.HOME ?? "";
|
|
156
|
+
const extraPaths = [
|
|
157
|
+
"/opt/homebrew/bin",
|
|
158
|
+
"/usr/local/bin",
|
|
159
|
+
"/usr/bin",
|
|
160
|
+
"/bin",
|
|
161
|
+
"/usr/sbin",
|
|
162
|
+
"/sbin",
|
|
163
|
+
join(home, ".bun/bin"),
|
|
164
|
+
join(home, ".local/bin"),
|
|
165
|
+
join(home, ".cursor/bin"),
|
|
166
|
+
join(home, ".qlty/bin"),
|
|
167
|
+
];
|
|
168
|
+
return [...new Set([...(process.env.PATH ?? "").split(":"), ...extraPaths].filter(Boolean))].join(":");
|
|
169
|
+
}
|
|
170
|
+
|
|
128
171
|
export function readBinaryVersion(binaryPath: string, args: string[]): string {
|
|
129
172
|
if (args.length === 0) {
|
|
130
173
|
return "unknown";
|
|
@@ -132,9 +175,10 @@ export function readBinaryVersion(binaryPath: string, args: string[]): string {
|
|
|
132
175
|
|
|
133
176
|
try {
|
|
134
177
|
const output = execFileSync(binaryPath, args, {
|
|
178
|
+
env: { ...process.env, PATH: expandedPath() },
|
|
135
179
|
encoding: "utf8",
|
|
136
|
-
stdio: ["ignore", "pipe", "
|
|
137
|
-
timeout:
|
|
180
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
181
|
+
timeout: 650,
|
|
138
182
|
}).trim();
|
|
139
183
|
return output.split("\n").find((line) => line.length > 0) ?? "unknown";
|
|
140
184
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import type { GuiAppActionId, GuiManagedAppSummary } from "../../gui-shared/src";
|
|
3
|
-
import { collapseHome, expandHome, firstExistingPathRef, readBinaryVersion, resolveBinary } from "./status-adapter-utils";
|
|
3
|
+
import { collapseHome, expandHome, firstExecutablePathRef, firstExistingPathRef, readBinaryVersion, resolveBinary } from "./status-adapter-utils";
|
|
4
4
|
|
|
5
5
|
type ManagedAppActionCommands = Partial<Record<GuiAppActionId, string>>;
|
|
6
6
|
|
|
@@ -180,7 +180,7 @@ function setupScopeActions(scope: string): ManagedAppActionCommands {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
function managedAppSummary(definition: ManagedAppDefinition, latestVersion: string, installedVersion: string): GuiManagedAppSummary {
|
|
183
|
-
const binaryPath = definition.binary === null ? null : resolveBinary(definition.binary);
|
|
183
|
+
const binaryPath = definition.binary === null ? null : executableInstallPath(definition.install_path_refs) ?? resolveBinary(definition.binary);
|
|
184
184
|
const installPathRef = firstExistingPathRef(definition.install_path_refs) ?? definition.install_path_refs[0] ?? "not found";
|
|
185
185
|
const installedVersionText = readManagedAppVersion(definition, binaryPath, installedVersion);
|
|
186
186
|
const latestVersionText = definition.latest_version_source === "aidevops"
|
|
@@ -231,7 +231,13 @@ function readManagedAppVersion(definition: ManagedAppDefinition, binaryPath: str
|
|
|
231
231
|
return readAppBundleVersion(existingPathRef) ?? "installed";
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
const versionText = readBinaryVersion(binaryPath, definition.version_args);
|
|
235
|
+
return versionText === "unknown" ? "installed" : versionText;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function executableInstallPath(pathRefs: string[]): string | null {
|
|
239
|
+
const executablePathRef = firstExecutablePathRef(pathRefs);
|
|
240
|
+
return executablePathRef === null ? null : expandHome(executablePathRef);
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
function readAppBundleVersion(pathRef: string): string | null {
|
|
@@ -239,5 +245,11 @@ function readAppBundleVersion(pathRef: string): string | null {
|
|
|
239
245
|
return null;
|
|
240
246
|
}
|
|
241
247
|
|
|
242
|
-
|
|
248
|
+
const version = readBinaryVersion("/usr/libexec/PlistBuddy", ["-c", "Print :CFBundleShortVersionString", `${expandHome(pathRef)}/Contents/Info.plist`]);
|
|
249
|
+
if (version !== "unknown") {
|
|
250
|
+
return version;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const bundleVersion = readBinaryVersion("/usr/libexec/PlistBuddy", ["-c", "Print :CFBundleVersion", `${expandHome(pathRef)}/Contents/Info.plist`]);
|
|
254
|
+
return bundleVersion === "unknown" ? null : bundleVersion;
|
|
243
255
|
}
|
|
@@ -338,7 +338,7 @@ final class DraggableTitlebarView: NSView {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate, WKScriptMessageHandler, NSMenuItemValidation, NSWindowDelegate {
|
|
341
|
+
final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, NSMenuItemValidation, NSWindowDelegate {
|
|
342
342
|
private var window: NSWindow!
|
|
343
343
|
private var aboutWindow: NSWindow?
|
|
344
344
|
private var titlebarToggleButton: NSButton?
|
|
@@ -416,6 +416,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate,
|
|
|
416
416
|
decisionHandler(.allow)
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
|
|
420
|
+
guard let url = navigationAction.request.url else {
|
|
421
|
+
return nil
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if url.scheme == "http" || url.scheme == "https" {
|
|
425
|
+
NSWorkspace.shared.open(url)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return nil
|
|
429
|
+
}
|
|
430
|
+
|
|
419
431
|
private func isLocalAppURL(_ url: URL) -> Bool {
|
|
420
432
|
let host = url.host ?? ""
|
|
421
433
|
return (host == "127.0.0.1" || host == "localhost") && url.port == Int(webPort)
|
|
@@ -677,11 +689,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate,
|
|
|
677
689
|
private func configureWindow() {
|
|
678
690
|
let configuration = WKWebViewConfiguration()
|
|
679
691
|
configuration.defaultWebpagePreferences.allowsContentJavaScript = true
|
|
692
|
+
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
|
|
680
693
|
let userContentController = WKUserContentController()
|
|
681
694
|
userContentController.add(self, name: "accentHue")
|
|
682
695
|
configuration.userContentController = userContentController
|
|
683
696
|
webView = WKWebView(frame: .zero, configuration: configuration)
|
|
684
697
|
webView.navigationDelegate = self
|
|
698
|
+
webView.uiDelegate = self
|
|
685
699
|
webView.translatesAutoresizingMaskIntoConstraints = false
|
|
686
700
|
|
|
687
701
|
let contentContainer = NSView()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GuiAppActionId, GuiAppActionJobSummary, GuiManagedAppSummary, GuiResponseEnvelope, GuiStatusData } from "@aidevops/gui-shared";
|
|
2
|
-
import { type Dispatch, type MouseEvent as ReactMouseEvent, type ReactElement, type ReactNode, type SetStateAction, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { type CSSProperties, type Dispatch, type FocusEvent as ReactFocusEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactElement, type ReactNode, type SetStateAction, useEffect, useRef, useState } from "react";
|
|
3
3
|
import type { IconType } from "react-icons";
|
|
4
4
|
import { FaApple, FaLinux, FaWindows } from "react-icons/fa";
|
|
5
5
|
import { FiChevronDown, FiCode, FiDownload, FiExternalLink, FiGlobe, FiMonitor, FiRefreshCw, FiRepeat, FiTerminal, FiTrash2, FiX } from "react-icons/fi";
|
|
@@ -24,18 +24,20 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
24
24
|
const [expandedAppId, setExpandedAppId] = useState<string | null>(() => sortedManagedApps(status.managed_apps).find((app) => managedCategoryForApp(app) === "core")?.id ?? null);
|
|
25
25
|
const [jobs, setJobs] = useState<Record<string, GuiAppActionJobSummary>>({});
|
|
26
26
|
const [dismissedJobIds, setDismissedJobIds] = useState<Set<string>>(() => new Set());
|
|
27
|
+
const [tooltip, setTooltip] = useState<AppTooltipState | null>(null);
|
|
27
28
|
const [policyJobs, setPolicyJobs] = useState<Record<string, GuiAppActionJobSummary[]>>({});
|
|
28
29
|
const [policyToggles, setPolicyToggles] = useState<ManagedPolicyToggleState>(() => readManagedPolicyToggles());
|
|
29
30
|
const selectedJob = selectedJobId === null ? null : jobs[selectedJobId] ?? null;
|
|
30
31
|
const managedApps = sortedManagedApps(status.managed_apps).map((app) => applyManagedPolicyToggles(app, policyToggles[app.id]));
|
|
31
32
|
const visibleManagedApps = managedApps.filter((app) => managedCategoryForApp(app) === managedCategory);
|
|
33
|
+
const firstVisibleManagedAppId = visibleManagedApps[0]?.id ?? null;
|
|
32
34
|
const visibleRecommendedApps = recommendedApps.filter((app) => recommendedAppMatchesFilters(app, recommendedPlatform, recommendedOs));
|
|
33
35
|
const selectRecommendedPlatform = (value: RecommendedPlatformFilterId) => setRecommendedPlatform((current) => nextRecommendedFilterValue(current, value, "all"));
|
|
34
36
|
const selectRecommendedOs = (value: RecommendedOsId) => setRecommendedOs((current) => nextRecommendedFilterValue(current, value, "all"));
|
|
35
37
|
|
|
36
38
|
useEffect(() => {
|
|
37
|
-
setExpandedAppId(
|
|
38
|
-
}, [
|
|
39
|
+
setExpandedAppId(firstVisibleManagedAppId);
|
|
40
|
+
}, [firstVisibleManagedAppId]);
|
|
39
41
|
|
|
40
42
|
const runningJobIdsKey = Object.values(jobs).filter((job) => job.status === "running").map((job) => job.id).sort().join("|");
|
|
41
43
|
|
|
@@ -57,7 +59,7 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
57
59
|
}, [runningJobIdsKey]);
|
|
58
60
|
|
|
59
61
|
return (
|
|
60
|
-
<section className="apps-surface" aria-label={text.apps}>
|
|
62
|
+
<section className="apps-surface" aria-label={text.apps} onBlur={() => setTooltip(null)} onFocus={showFocusedAppTooltip(setTooltip)} onPointerLeave={() => setTooltip(null)} onPointerMove={showPointedAppTooltip(setTooltip)}>
|
|
61
63
|
<div className="section-heading">
|
|
62
64
|
<p className="eyebrow">{text.infrastructure}</p>
|
|
63
65
|
<h2>{text.apps}</h2>
|
|
@@ -103,6 +105,7 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
103
105
|
</div>
|
|
104
106
|
<p className="empty-state compact-notice">Install/update actions run as allowlisted background jobs and stream inside each app panel. xterm.js with a node-pty bridge is the right next step for full TUI apps such as OpenCode; this view starts with non-interactive command logs.</p>
|
|
105
107
|
</> : <RecommendedAppsSurface apps={visibleRecommendedApps} os={recommendedOs} platform={recommendedPlatform} setOs={selectRecommendedOs} setPlatform={selectRecommendedPlatform} />}
|
|
108
|
+
{tooltip === null ? null : <AppTooltip tooltip={tooltip} />}
|
|
106
109
|
</section>
|
|
107
110
|
);
|
|
108
111
|
}
|
|
@@ -151,7 +154,8 @@ function ManagedAppPanel({ app, dismissedJobIds, expanded, job, onDismissJob, on
|
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
function SummaryChip({ label, value }: { label: string; value: string }): ReactElement {
|
|
154
|
-
|
|
157
|
+
const tooltip = `${label}: ${value}`;
|
|
158
|
+
return <span className="managed-summary-chip" data-tooltip={tooltip} title={tooltip}><small>{label}</small><span>{value}</span></span>;
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
function OriginLink({ href, label }: { href: string; label: string }): ReactElement {
|
|
@@ -159,7 +163,7 @@ function OriginLink({ href, label }: { href: string; label: string }): ReactElem
|
|
|
159
163
|
return <span className="origin-missing">{label}: source pending</span>;
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
return <a href={href} onClick={(event) => openExternalLink(event, href)} rel="noreferrer" target="_blank" title={href}>{label} <FiExternalLink aria-hidden="true" /></a>;
|
|
166
|
+
return <a data-tooltip={href} href={href} onClick={(event) => openExternalLink(event, href)} rel="noreferrer" target="_blank" title={href}>{label} <FiExternalLink aria-hidden="true" /></a>;
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
function ToggleSwitch({ checked, disabled = false, label, onChange }: { checked: boolean; disabled?: boolean; label: string; onChange: (checked: boolean) => void }): ReactElement {
|
|
@@ -218,7 +222,7 @@ function AppActionTerminal({ job, onDismiss }: { job: GuiAppActionJobSummary; on
|
|
|
218
222
|
if (outputRef.current !== null) {
|
|
219
223
|
outputRef.current.scrollTop = outputRef.current.scrollHeight;
|
|
220
224
|
}
|
|
221
|
-
}, [job.output]);
|
|
225
|
+
}, [job.output.length]);
|
|
222
226
|
|
|
223
227
|
return (
|
|
224
228
|
<section className="app-terminal" id={`app-job-${job.id}`} aria-label="App action terminal output">
|
|
@@ -594,17 +598,70 @@ function writeManagedPolicyToggles(policy: ManagedPolicyToggleState): void {
|
|
|
594
598
|
window.localStorage.setItem("aidevops-gui-managed-app-policy", JSON.stringify(policy));
|
|
595
599
|
}
|
|
596
600
|
|
|
601
|
+
interface AppTooltipState {
|
|
602
|
+
text: string;
|
|
603
|
+
x: number;
|
|
604
|
+
y: number;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function showPointedAppTooltip(setTooltip: Dispatch<SetStateAction<AppTooltipState | null>>) {
|
|
608
|
+
return (event: ReactPointerEvent<HTMLElement>) => {
|
|
609
|
+
const target = tooltipTarget(event.target);
|
|
610
|
+
setTooltip(target === null ? null : tooltipForElement(target));
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function showFocusedAppTooltip(setTooltip: Dispatch<SetStateAction<AppTooltipState | null>>) {
|
|
615
|
+
return (event: ReactFocusEvent<HTMLElement>) => {
|
|
616
|
+
const target = tooltipTarget(event.target);
|
|
617
|
+
if (target !== null) {
|
|
618
|
+
setTooltip(tooltipForElement(target));
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function tooltipTarget(target: EventTarget): HTMLElement | null {
|
|
624
|
+
return target instanceof HTMLElement ? target.closest<HTMLElement>("[data-tooltip]") : null;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function tooltipForElement(element: HTMLElement): AppTooltipState | null {
|
|
628
|
+
const textValue = element.dataset.tooltip;
|
|
629
|
+
if (textValue === undefined || textValue.length === 0) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const rect = element.getBoundingClientRect();
|
|
634
|
+
return { text: textValue, x: rect.left + (rect.width / 2), y: rect.top };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function AppTooltip({ tooltip }: { tooltip: AppTooltipState }): ReactElement {
|
|
638
|
+
const style = {
|
|
639
|
+
"--tooltip-x": `${tooltip.x}px`,
|
|
640
|
+
"--tooltip-y": `${tooltip.y}px`,
|
|
641
|
+
} as CSSProperties;
|
|
642
|
+
|
|
643
|
+
return <div className="app-global-tooltip" role="tooltip" style={style}>{tooltip.text}</div>;
|
|
644
|
+
}
|
|
645
|
+
|
|
597
646
|
function openExternalLink(event: ReactMouseEvent<HTMLAnchorElement>, href: string): void {
|
|
598
647
|
event.stopPropagation();
|
|
599
648
|
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
600
649
|
return;
|
|
601
650
|
}
|
|
602
651
|
|
|
652
|
+
event.preventDefault();
|
|
653
|
+
if (document.documentElement.dataset.desktopShell === "macos") {
|
|
654
|
+
window.location.assign(href);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
603
658
|
const opened = window.open(href, "_blank", "noopener,noreferrer");
|
|
604
659
|
if (opened !== null) {
|
|
605
|
-
event.preventDefault();
|
|
606
660
|
opened.opener = null;
|
|
661
|
+
return;
|
|
607
662
|
}
|
|
663
|
+
|
|
664
|
+
window.location.assign(href);
|
|
608
665
|
}
|
|
609
666
|
|
|
610
667
|
export function InstallationSurface(): ReactElement {
|
|
@@ -2558,6 +2558,32 @@ button.managed-toggle:focus-visible {
|
|
|
2558
2558
|
transform: translate(-50%, 0);
|
|
2559
2559
|
}
|
|
2560
2560
|
|
|
2561
|
+
.apps-surface [data-tooltip]::after {
|
|
2562
|
+
content: none;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
.app-global-tooltip {
|
|
2566
|
+
background: color-mix(in srgb, var(--surface-elevated) 94%, black);
|
|
2567
|
+
border: 1px solid color-mix(in srgb, var(--accent) 36%, var(--border));
|
|
2568
|
+
border-radius: 10px;
|
|
2569
|
+
box-shadow: 0 18px 46px rgb(0 0 0 / 38%);
|
|
2570
|
+
color: var(--text-primary);
|
|
2571
|
+
font-size: 0.72rem;
|
|
2572
|
+
font-weight: 700;
|
|
2573
|
+
left: clamp(12px, var(--tooltip-x), calc(100vw - 12px));
|
|
2574
|
+
line-height: 1.35;
|
|
2575
|
+
max-width: min(440px, 72vw);
|
|
2576
|
+
overflow-wrap: anywhere;
|
|
2577
|
+
padding: 8px 10px;
|
|
2578
|
+
pointer-events: none;
|
|
2579
|
+
position: fixed;
|
|
2580
|
+
text-align: left;
|
|
2581
|
+
top: var(--tooltip-y);
|
|
2582
|
+
transform: translate(-50%, calc(-100% - 10px));
|
|
2583
|
+
width: max-content;
|
|
2584
|
+
z-index: 10000;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2561
2587
|
.data-row,
|
|
2562
2588
|
.editable-row {
|
|
2563
2589
|
display: grid;
|
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.29.
|
|
15
|
+
# Version: 3.29.20
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|