aidevops 3.29.16 → 3.29.18
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/app.ts +1 -1
- package/packages/gui-api/src/status-managed-apps.ts +3 -3
- package/packages/gui-desktop/scripts/install-macos-app.sh +25 -0
- package/packages/gui-web/src/InventorySurfaces.tsx +103 -27
- package/packages/gui-web/src/app-model.ts +1 -1
- package/packages/gui-web/src/styles.css +55 -2
- 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.18
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -148,7 +148,7 @@ function startAppActionJob(appId: string, action: GuiAppActionId, command: strin
|
|
|
148
148
|
|
|
149
149
|
const child = spawn(command[0], command.slice(1), {
|
|
150
150
|
cwd: process.cwd(),
|
|
151
|
-
env: { ...process.env, AIDEVOPS_NON_INTERACTIVE: "true" },
|
|
151
|
+
env: { ...process.env, AIDEVOPS_NON_INTERACTIVE: "true", CLICOLOR_FORCE: "1", FORCE_COLOR: "1", TERM: process.env.TERM ?? "xterm-256color" },
|
|
152
152
|
stdio: ["ignore", "pipe", "pipe"],
|
|
153
153
|
});
|
|
154
154
|
if (child.stdout !== null) {
|
|
@@ -119,7 +119,7 @@ const MANAGED_APP_DEFINITIONS: ManagedAppDefinition[] = [
|
|
|
119
119
|
category: "automation",
|
|
120
120
|
binary: null,
|
|
121
121
|
version_args: [],
|
|
122
|
-
install_path_refs: ["~/Library/LaunchAgents/sh.aidevops.pulse.plist", "~/.aidevops/.agent-workspace/pulse"],
|
|
122
|
+
install_path_refs: ["~/Library/LaunchAgents/sh.aidevops.pulse.plist", "~/Library/LaunchAgents/com.aidevops.aidevops-supervisor-pulse.plist", "~/Library/LaunchAgents/sh.aidevops.supervisor-pulse.plist", "~/.aidevops/.agent-workspace/pulse"],
|
|
123
123
|
origin_website_url: "https://aidevops.sh",
|
|
124
124
|
origin_repo_url: "https://github.com/marcusquinn/aidevops.git",
|
|
125
125
|
aidevops_install: true,
|
|
@@ -143,7 +143,7 @@ const MANAGED_APP_DEFINITIONS: ManagedAppDefinition[] = [
|
|
|
143
143
|
binaryApp({ id: "cursor", name: "Cursor CLI", description: "Cursor command-line integration and config targets.", category: "cli", binary: "cursor", version_args: ["--version"], install_path_refs: ["~/.cursor/bin/cursor", "/Applications/Cursor.app"], origin_website_url: "https://cursor.com/install", origin_repo_url: "" }),
|
|
144
144
|
binaryApp({ id: "zed", name: "Zed", description: "Optional editor installed by setup when selected.", category: "editor", binary: "zed", version_args: ["--version"], install_path_refs: ["/Applications/Zed.app", "/opt/homebrew/bin/zed", "/usr/local/bin/zed"], origin_website_url: "https://zed.dev/download", origin_repo_url: "", aidevops_install: false, aidevops_update: false }),
|
|
145
145
|
binaryApp({ id: "orbstack", name: "OrbStack", description: "Optional local container/VM runtime for macOS development.", category: "runtime", binary: "orb", version_args: ["version"], install_path_refs: ["/Applications/OrbStack.app", "/opt/homebrew/bin/orb"], origin_website_url: "https://orbstack.dev/", origin_repo_url: "", aidevops_install: false, aidevops_update: false }),
|
|
146
|
-
binaryApp({ id: "ollama", name: "Ollama", description: "Optional local model runtime for local AI workflows.", category: "ai runtime", binary: "ollama", version_args: ["--version"], install_path_refs: ["/Applications/Ollama.app", "/opt/homebrew/bin/ollama", "/usr/local/bin/ollama"], origin_website_url: "https://ollama.com", origin_repo_url: "" }),
|
|
146
|
+
binaryApp({ id: "ollama", name: "Ollama", description: "Optional local model runtime for local AI workflows.", category: "ai runtime", binary: "ollama", version_args: ["--version"], install_path_refs: ["~/Applications/Ollama.app", "/Applications/Ollama.app", "/opt/homebrew/bin/ollama", "/usr/local/bin/ollama"], origin_website_url: "https://ollama.com", origin_repo_url: "" }),
|
|
147
147
|
];
|
|
148
148
|
|
|
149
149
|
export function readManagedApps(latestVersion: string, installedVersion: string): GuiManagedAppSummary[] {
|
|
@@ -200,7 +200,7 @@ function managedAppSummary(definition: ManagedAppDefinition, latestVersion: stri
|
|
|
200
200
|
aidevops_update: definition.aidevops_update,
|
|
201
201
|
installed_version: installedVersionText,
|
|
202
202
|
latest_version: latestVersionText,
|
|
203
|
-
install_path_ref: binaryPath
|
|
203
|
+
install_path_ref: collapseHome(binaryPath ?? expandHome(installPathRef)),
|
|
204
204
|
status: binaryPath !== null || definition.install_path_refs.some((pathRef) => existsSync(expandHome(pathRef))) ? "found" : "missing",
|
|
205
205
|
actions: (["install", "update", "reinstall", "remove"] as const).map((action) => managedAppAction(definition.action_commands, action)),
|
|
206
206
|
};
|
|
@@ -396,6 +396,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate,
|
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
400
|
+
guard let url = navigationAction.request.url else {
|
|
401
|
+
decisionHandler(.allow)
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if isLocalAppURL(url) {
|
|
406
|
+
decisionHandler(.allow)
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if url.scheme == "http" || url.scheme == "https" {
|
|
411
|
+
NSWorkspace.shared.open(url)
|
|
412
|
+
decisionHandler(.cancel)
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
decisionHandler(.allow)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private func isLocalAppURL(_ url: URL) -> Bool {
|
|
420
|
+
let host = url.host ?? ""
|
|
421
|
+
return (host == "127.0.0.1" || host == "localhost") && url.port == Int(webPort)
|
|
422
|
+
}
|
|
423
|
+
|
|
399
424
|
private func configureMenu() {
|
|
400
425
|
let appName = "aidevops"
|
|
401
426
|
let mainMenu = NSMenu(title: "Main Menu")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { GuiAppActionId, GuiAppActionJobSummary, GuiManagedAppSummary, GuiResponseEnvelope, GuiStatusData } from "@aidevops/gui-shared";
|
|
2
|
-
import { type Dispatch, type MouseEvent as ReactMouseEvent, type ReactElement, type SetStateAction, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { type Dispatch, type MouseEvent as ReactMouseEvent, 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
|
-
import { FiChevronDown, FiCode, FiDownload, FiExternalLink, FiGlobe, FiMonitor, FiRefreshCw, FiRepeat, FiTerminal, FiTrash2 } from "react-icons/fi";
|
|
5
|
+
import { FiChevronDown, FiCode, FiDownload, FiExternalLink, FiGlobe, FiMonitor, FiRefreshCw, FiRepeat, FiTerminal, FiTrash2, FiX } from "react-icons/fi";
|
|
6
6
|
import { IoLogoAndroid } from "react-icons/io";
|
|
7
7
|
import { SiIos } from "react-icons/si";
|
|
8
8
|
import type { InventoryColumn } from "./app-model";
|
|
@@ -23,6 +23,7 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
23
23
|
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
|
|
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
|
+
const [dismissedJobIds, setDismissedJobIds] = useState<Set<string>>(() => new Set());
|
|
26
27
|
const [policyJobs, setPolicyJobs] = useState<Record<string, GuiAppActionJobSummary[]>>({});
|
|
27
28
|
const [policyToggles, setPolicyToggles] = useState<ManagedPolicyToggleState>(() => readManagedPolicyToggles());
|
|
28
29
|
const selectedJob = selectedJobId === null ? null : jobs[selectedJobId] ?? null;
|
|
@@ -36,17 +37,24 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
36
37
|
setExpandedAppId(visibleManagedApps[0]?.id ?? null);
|
|
37
38
|
}, [managedCategory, status.managed_apps]);
|
|
38
39
|
|
|
40
|
+
const runningJobIdsKey = Object.values(jobs).filter((job) => job.status === "running").map((job) => job.id).sort().join("|");
|
|
41
|
+
|
|
39
42
|
useEffect(() => {
|
|
40
|
-
|
|
43
|
+
const runningJobIds = runningJobIdsKey.split("|").filter(Boolean);
|
|
44
|
+
if (runningJobIds.length === 0) {
|
|
41
45
|
return undefined;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
const refreshRunningJobs = () => {
|
|
49
|
+
for (const jobId of runningJobIds) {
|
|
50
|
+
void refreshJob(jobId, setJobs);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
refreshRunningJobs();
|
|
54
|
+
const timer = window.setInterval(refreshRunningJobs, 1_500);
|
|
47
55
|
|
|
48
56
|
return () => window.clearInterval(timer);
|
|
49
|
-
}, [
|
|
57
|
+
}, [runningJobIdsKey]);
|
|
50
58
|
|
|
51
59
|
return (
|
|
52
60
|
<section className="apps-surface" aria-label={text.apps}>
|
|
@@ -65,8 +73,15 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
65
73
|
expanded={expandedAppId === app.id}
|
|
66
74
|
job={jobForApp(app.id, jobs, selectedJob)}
|
|
67
75
|
key={app.id}
|
|
76
|
+
dismissedJobIds={dismissedJobIds}
|
|
68
77
|
policyJobs={policyJobs[app.id] ?? []}
|
|
78
|
+
onDismissJob={(jobId) => setDismissedJobIds((current) => new Set([...current, jobId]))}
|
|
69
79
|
onJob={(job) => {
|
|
80
|
+
setDismissedJobIds((current) => {
|
|
81
|
+
const next = new Set(current);
|
|
82
|
+
next.delete(job.id);
|
|
83
|
+
return next;
|
|
84
|
+
});
|
|
70
85
|
setJobs((current) => ({ ...current, [job.id]: job }));
|
|
71
86
|
setSelectedJobId(job.id);
|
|
72
87
|
setExpandedAppId(app.id);
|
|
@@ -92,8 +107,10 @@ export function AppsSurface({ status }: { status: GuiStatusData }): ReactElement
|
|
|
92
107
|
);
|
|
93
108
|
}
|
|
94
109
|
|
|
95
|
-
function ManagedAppPanel({ app, expanded, job, onJob, onPolicyToggle, onToggle, policyJobs }: { app: GuiManagedAppSummary; expanded: boolean; job: GuiAppActionJobSummary | null; onJob: (job: GuiAppActionJobSummary) => void; onPolicyToggle: (policy: ManagedPolicyId, value: boolean) => void; onToggle: () => void; policyJobs: GuiAppActionJobSummary[] }): ReactElement {
|
|
110
|
+
function ManagedAppPanel({ app, dismissedJobIds, expanded, job, onDismissJob, onJob, onPolicyToggle, onToggle, policyJobs }: { app: GuiManagedAppSummary; dismissedJobIds: Set<string>; expanded: boolean; job: GuiAppActionJobSummary | null; onDismissJob: (jobId: string) => void; onJob: (job: GuiAppActionJobSummary) => void; onPolicyToggle: (policy: ManagedPolicyId, value: boolean) => void; onToggle: () => void; policyJobs: GuiAppActionJobSummary[] }): ReactElement {
|
|
96
111
|
const lockedPolicy = isEssentialManagedApp(app);
|
|
112
|
+
const visibleJob = job === null || dismissedJobIds.has(job.id) ? null : job;
|
|
113
|
+
const visiblePolicyJobs = policyJobs.filter((policyJob) => !dismissedJobIds.has(policyJob.id));
|
|
97
114
|
|
|
98
115
|
return (
|
|
99
116
|
<article className={expanded ? "managed-app-card expanded" : "managed-app-card"}>
|
|
@@ -122,12 +139,12 @@ function ManagedAppPanel({ app, expanded, job, onJob, onPolicyToggle, onToggle,
|
|
|
122
139
|
<div className="managed-app-details">
|
|
123
140
|
<ToggleSwitch checked={app.aidevops_install} disabled={lockedPolicy} label="setup installs" onChange={(value) => onPolicyToggle("setup", value)} />
|
|
124
141
|
<ToggleSwitch checked={app.aidevops_update} disabled={lockedPolicy} label="update maintains" onChange={(value) => onPolicyToggle("update", value)} />
|
|
125
|
-
{
|
|
142
|
+
{visibleJob ? <AppLogLink job={visibleJob} /> : null}
|
|
126
143
|
</div>
|
|
127
144
|
<AppMeta className="managed-app-path" label={text.path} value={app.install_path_ref} />
|
|
128
|
-
{
|
|
129
|
-
{
|
|
130
|
-
{
|
|
145
|
+
{visibleJob ? <AppActionTerminal job={visibleJob} onDismiss={onDismissJob} /> : null}
|
|
146
|
+
{visiblePolicyJobs.map((policyJob) => <AppActionTerminal job={policyJob} key={policyJob.id} onDismiss={onDismissJob} />)}
|
|
147
|
+
{visibleJob === null && visiblePolicyJobs.length === 0 ? <p className="empty-state compact-notice">No recent command output for this app. Run an action or change a policy toggle to open this app's terminal log.</p> : null}
|
|
131
148
|
</div> : null}
|
|
132
149
|
</article>
|
|
133
150
|
);
|
|
@@ -194,7 +211,7 @@ function ConfirmActionModal({ action, app, close, commandPreview, confirm }: { a
|
|
|
194
211
|
);
|
|
195
212
|
}
|
|
196
213
|
|
|
197
|
-
function AppActionTerminal({ job }: { job: GuiAppActionJobSummary }): ReactElement {
|
|
214
|
+
function AppActionTerminal({ job, onDismiss }: { job: GuiAppActionJobSummary; onDismiss: (jobId: string) => void }): ReactElement {
|
|
198
215
|
const outputRef = useRef<HTMLPreElement | null>(null);
|
|
199
216
|
|
|
200
217
|
useEffect(() => {
|
|
@@ -205,12 +222,71 @@ function AppActionTerminal({ job }: { job: GuiAppActionJobSummary }): ReactEleme
|
|
|
205
222
|
|
|
206
223
|
return (
|
|
207
224
|
<section className="app-terminal" id={`app-job-${job.id}`} aria-label="App action terminal output">
|
|
208
|
-
<header><strong>{job.app_id} {job.action}</strong><span>{job.
|
|
209
|
-
<pre ref={outputRef}>{job.output
|
|
225
|
+
<header><strong>{job.app_id} {job.action}</strong><span>{terminalStatusLabel(job)}</span><button aria-label={`Dismiss ${job.app_id} ${job.action} terminal output`} className="terminal-close-button" onClick={() => onDismiss(job.id)} title="Dismiss terminal output" type="button"><FiX aria-hidden="true" /></button></header>
|
|
226
|
+
<pre ref={outputRef}>{renderTerminalOutput(job.output)}</pre>
|
|
210
227
|
</section>
|
|
211
228
|
);
|
|
212
229
|
}
|
|
213
230
|
|
|
231
|
+
function terminalStatusLabel(job: GuiAppActionJobSummary): string {
|
|
232
|
+
if (job.status === "running") {
|
|
233
|
+
return "running";
|
|
234
|
+
}
|
|
235
|
+
if (job.exit_code === null) {
|
|
236
|
+
return job.status;
|
|
237
|
+
}
|
|
238
|
+
return `${job.status} · exit ${job.exit_code}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renderTerminalOutput(lines: string[]): ReactNode[] {
|
|
242
|
+
return lines.flatMap((line, index) => [
|
|
243
|
+
...renderAnsiLine(line, `line-${index}`),
|
|
244
|
+
index === lines.length - 1 ? null : "\n",
|
|
245
|
+
]).filter((node): node is ReactNode => node !== null);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function renderAnsiLine(line: string, keyPrefix: string): ReactNode[] {
|
|
249
|
+
const nodes: ReactNode[] = [];
|
|
250
|
+
const ansiPattern = new RegExp("\\x1b\\[([0-9;]*)m", "g");
|
|
251
|
+
let activeClass = "";
|
|
252
|
+
let lastIndex = 0;
|
|
253
|
+
let match = ansiPattern.exec(line);
|
|
254
|
+
|
|
255
|
+
while (match !== null) {
|
|
256
|
+
if (match.index > lastIndex) {
|
|
257
|
+
nodes.push(terminalSpan(line.slice(lastIndex, match.index), activeClass, `${keyPrefix}-${nodes.length}`));
|
|
258
|
+
}
|
|
259
|
+
activeClass = ansiClassForCodes(match[1]);
|
|
260
|
+
lastIndex = ansiPattern.lastIndex;
|
|
261
|
+
match = ansiPattern.exec(line);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (lastIndex < line.length) {
|
|
265
|
+
nodes.push(terminalSpan(line.slice(lastIndex), activeClass, `${keyPrefix}-${nodes.length}`));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (nodes.length === 0) {
|
|
269
|
+
nodes.push(terminalSpan(line, activeClass, `${keyPrefix}-0`));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return nodes;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function terminalSpan(textValue: string, className: string, key: string): ReactNode {
|
|
276
|
+
const promptClass = textValue.startsWith("$ ") ? "ansi-prompt" : "";
|
|
277
|
+
const combinedClass = [className, promptClass].filter(Boolean).join(" ");
|
|
278
|
+
return combinedClass.length > 0 ? <span className={combinedClass} key={key}>{textValue}</span> : textValue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function ansiClassForCodes(rawCodes: string): string {
|
|
282
|
+
const codes = rawCodes.length === 0 ? [0] : rawCodes.split(";").map((code) => Number.parseInt(code, 10));
|
|
283
|
+
const colorCode = [...codes].reverse().find((code) => (code >= 30 && code <= 37) || (code >= 90 && code <= 97));
|
|
284
|
+
if (colorCode === undefined) {
|
|
285
|
+
return "";
|
|
286
|
+
}
|
|
287
|
+
return `ansi-fg-${colorCode}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
214
290
|
async function refreshJob(jobId: string, setJobs: Dispatch<SetStateAction<Record<string, GuiAppActionJobSummary>>>): Promise<void> {
|
|
215
291
|
const response = await fetch(`/api/apps/jobs/${encodeURIComponent(jobId)}`);
|
|
216
292
|
if (!response.ok) {
|
|
@@ -321,16 +397,16 @@ const recommendedOsTabs: TabOption<RecommendedOsId>[] = [
|
|
|
321
397
|
];
|
|
322
398
|
|
|
323
399
|
const recommendedApps = ([
|
|
324
|
-
{ name: "Affinity Studio", description: "Creative design suite.", websiteUrl: "https://www.affinity.studio/", alternativeToUrl: "https://alternativeto.net/software/affinity-1/", os: ["macos", "windows"
|
|
325
|
-
{ name: "Bitwarden", description: "Open source password manager.", websiteUrl: "https://bitwarden.com", alternativeToUrl: "https://alternativeto.net/software/bitwarden--free-password-manager/", repoUrl: "https://github.com/bitwarden", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "cli", "api"] },
|
|
326
|
-
{ name: "Brave Browser", description: "Privacy-focused web browser.", websiteUrl: "https://brave.com/", alternativeToUrl: "https://alternativeto.net/software/brave/", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
400
|
+
{ name: "Affinity Studio", description: "Creative design suite.", websiteUrl: "https://www.affinity.studio/", alternativeToUrl: "https://alternativeto.net/software/affinity-1/", os: ["macos", "windows"], platforms: [] },
|
|
401
|
+
{ name: "Bitwarden", description: "Open source password manager.", websiteUrl: "https://bitwarden.com", alternativeToUrl: "https://alternativeto.net/software/bitwarden--free-password-manager/", repoUrl: "https://github.com/bitwarden", iosUrl: "https://itunes.apple.com/app/bitwarden-free-password-manager/id1137397744?mt=8", androidUrl: "https://play.google.com/store/apps/details?id=com.x8bit.bitwarden", fdroidUrl: "https://mobileapp.bitwarden.com/fdroid/repo", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "cli", "api"] },
|
|
402
|
+
{ name: "Brave Browser", description: "Privacy-focused web browser.", websiteUrl: "https://brave.com/", alternativeToUrl: "https://alternativeto.net/software/brave/", iosUrl: "https://apps.apple.com/app/brave-private-web-browser-vpn/id1052879175?mt=8", androidUrl: "https://play.google.com/store/apps/details?id=com.brave.browser", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
327
403
|
{ name: "Cloudron", description: "Self-hosted app platform.", websiteUrl: "https://www.cloudron.io/", alternativeToUrl: "https://alternativeto.net/software/cloudron/", os: ["linux"], platforms: ["webapp", "cli", "api"] },
|
|
328
404
|
{ name: "Collabora Online", description: "Online office collaboration.", websiteUrl: "https://www.collaboraonline.com/collabora-online/", alternativeToUrl: "https://alternativeto.net/software/collabora-online/", os: ["linux"], platforms: ["webapp", "api"] },
|
|
329
405
|
{ name: "Cometly", description: "Marketing attribution analytics.", websiteUrl: "https://www.cometly.com/", os: [], platforms: ["saas", "api"] },
|
|
330
406
|
{ name: "DaVinci Resolve", description: "Professional video editing, color, VFX, and audio post-production.", websiteUrl: "https://www.blackmagicdesign.com/products/davinciresolve/", alternativeToUrl: "https://alternativeto.net/software/davinci-resolve/", os: ["macos", "linux", "windows"], platforms: [] },
|
|
331
407
|
{ name: "DocuSeal", description: "Open source document signing.", websiteUrl: "https://www.docuseal.com/", alternativeToUrl: "https://alternativeto.net/software/docuseal/", os: ["linux"], platforms: ["webapp", "saas", "api"] },
|
|
332
|
-
{ name: "Element", description: "Matrix messaging client.", websiteUrl: "https://element.io/", alternativeToUrl: "https://alternativeto.net/software/element-app/", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp"] },
|
|
333
|
-
{ name: "Enpass", description: "Offline-first password manager.", websiteUrl: "https://www.enpass.io/", alternativeToUrl: "https://alternativeto.net/software/enpass/", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
408
|
+
{ name: "Element", description: "Matrix messaging client.", websiteUrl: "https://element.io/", alternativeToUrl: "https://alternativeto.net/software/element-app/", iosUrl: "https://apps.apple.com/app/id1631335820", androidUrl: "https://play.google.com/store/apps/details?id=io.element.android.x", fdroidUrl: "https://f-droid.org/en/packages/io.element.android.x/", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp"] },
|
|
409
|
+
{ name: "Enpass", description: "Offline-first password manager.", websiteUrl: "https://www.enpass.io/", alternativeToUrl: "https://alternativeto.net/software/enpass/", iosUrl: "https://apps.apple.com/app/apple-store/id455566716?pt=637991", androidUrl: "https://play.google.com/store/apps/details?id=io.enpass.app", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
334
410
|
{ name: "EspoCRM", description: "Open source CRM.", websiteUrl: "https://www.espocrm.com/", alternativeToUrl: "https://alternativeto.net/software/espocrm/", os: ["linux"], platforms: ["webapp", "saas", "api"] },
|
|
335
411
|
{ name: "Fathom Analytics", description: "Privacy-first analytics.", websiteUrl: "https://usefathom.com/", alternativeToUrl: "https://alternativeto.net/software/fathom-analytics/", os: [], platforms: ["saas", "api"] },
|
|
336
412
|
{ name: "FontBase", description: "Font management and reference tool.", websiteUrl: "https://fontba.se/", alternativeToUrl: "https://alternativeto.net/software/fontbase/", os: ["macos"], platforms: [] },
|
|
@@ -341,10 +417,10 @@ const recommendedApps = ([
|
|
|
341
417
|
{ name: "LibreOffice", description: "Open source office suite.", websiteUrl: "https://www.libreoffice.org/", alternativeToUrl: "https://alternativeto.net/software/libreoffice/", os: ["macos", "linux", "windows"], platforms: [] },
|
|
342
418
|
{ name: "LocalWP", description: "Local WordPress development.", websiteUrl: "https://localwp.com/", alternativeToUrl: "https://alternativeto.net/software/local-by-flywheel/", os: ["macos", "linux", "windows"], platforms: [] },
|
|
343
419
|
{ name: "Matomo", description: "Open analytics platform.", websiteUrl: "https://matomo.org/", alternativeToUrl: "https://alternativeto.net/software/piwik/", os: ["linux"], platforms: ["webapp", "saas", "api"] },
|
|
344
|
-
{ name: "Nextcloud", description: "Open source content collaboration platform.", websiteUrl: "https://nextcloud.com/", alternativeToUrl: "https://alternativeto.net/software/nextcloud/", repoUrl: "https://github.com/nextcloud", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "api"] },
|
|
420
|
+
{ name: "Nextcloud", description: "Open source content collaboration platform.", websiteUrl: "https://nextcloud.com/", alternativeToUrl: "https://alternativeto.net/software/nextcloud/", repoUrl: "https://github.com/nextcloud", iosUrl: "https://itunes.apple.com/us/app/nextcloud/id1125420102?mt=8", androidUrl: "https://play.google.com/store/apps/details?id=com.nextcloud.client", fdroidUrl: "https://f-droid.org/packages/com.nextcloud.client/", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "api"] },
|
|
345
421
|
{ name: "Nextcloud Talk", description: "Open source video calls, chat, and collaboration for Nextcloud.", websiteUrl: "https://nextcloud.com/talk/", repoUrl: "https://github.com/nextcloud/spreed", iosUrl: "https://apps.apple.com/us/app/nextcloud-talk/id1296825574", androidUrl: "https://play.google.com/store/apps/details?id=com.nextcloud.talk2&hl=en", os: ["ios", "android"], platforms: ["webapp"] },
|
|
346
422
|
{ name: "OBS Studio", description: "Open source video recording and live streaming.", websiteUrl: "https://obsproject.com/", alternativeToUrl: "https://alternativeto.net/software/open-broadcaster-software/", repoUrl: "https://github.com/obsproject/obs-studio", os: ["macos", "linux", "windows"], platforms: [] },
|
|
347
|
-
{ name: "ONLYOFFICE", description: "Office and document collaboration.", websiteUrl: "https://www.onlyoffice.com/", alternativeToUrl: "https://alternativeto.net/software/onlyoffice/", repoUrl: "https://github.com/ONLYOFFICE/", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "api"] },
|
|
423
|
+
{ name: "ONLYOFFICE", description: "Office and document collaboration.", websiteUrl: "https://www.onlyoffice.com/", alternativeToUrl: "https://alternativeto.net/software/onlyoffice/", repoUrl: "https://github.com/ONLYOFFICE/", iosUrl: "https://apps.apple.com/us/app/onlyoffice-documents/id944896972", androidUrl: "https://play.google.com/store/apps/details?id=com.onlyoffice.documents", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "saas", "api"] },
|
|
348
424
|
{ name: "OpenScreen", description: "Open screen-sharing project.", websiteUrl: "https://github.com/getopenscreen/openscreen/releases", alternativeToUrl: "https://alternativeto.net/software/openscreen/", repoUrl: "https://github.com/getopenscreen/openscreen", os: ["macos", "linux", "windows"], platforms: [] },
|
|
349
425
|
{ name: "Osaurus", description: "AI app workspace.", websiteUrl: "https://osaurus.ai/", alternativeToUrl: "https://alternativeto.net/software/osaurus/", os: ["macos"], platforms: [] },
|
|
350
426
|
{ name: "Parallels Desktop", description: "Run Windows, Linux, and virtual machines on macOS.", websiteUrl: "https://www.parallels.com/products/desktop/", alternativeToUrl: "https://alternativeto.net/software/parallels-desktop/about/", os: ["macos"], platforms: [] },
|
|
@@ -357,13 +433,13 @@ const recommendedApps = ([
|
|
|
357
433
|
{ name: "QuickFile", description: "Accounting platform.", websiteUrl: "https://www.quickfile.co.uk/", os: [], platforms: ["saas", "api"] },
|
|
358
434
|
{ name: "Reframed", description: "Creative framing utility.", websiteUrl: "https://www.reframed.dev/", alternativeToUrl: "https://alternativeto.net/software/reframed/", os: ["macos"], platforms: [] },
|
|
359
435
|
{ name: "Rybbit", description: "Web analytics.", websiteUrl: "https://rybbit.com/", alternativeToUrl: "https://alternativeto.net/software/rybbit/", os: ["linux"], platforms: ["webapp", "saas", "api"] },
|
|
360
|
-
{ name: "SchildiChat", description: "Matrix messaging client.", websiteUrl: "https://schildi.chat/", alternativeToUrl: "https://alternativeto.net/software/schildichat/", os: ["macos", "linux", "windows", "android"], platforms: ["webapp"] },
|
|
361
|
-
{ name: "ScreenFlow", description: "Screen recording and editing.", websiteUrl: "https://www.telestream.net/screenflow/", alternativeToUrl: "https://alternativeto.net/software/screenflow/", os: ["macos"
|
|
436
|
+
{ name: "SchildiChat", description: "Matrix messaging client.", websiteUrl: "https://schildi.chat/", alternativeToUrl: "https://alternativeto.net/software/schildichat/", androidUrl: "https://play.google.com/store/apps/details?id=chat.schildi.android", fdroidUrl: "https://schildi.chat/next/install-from-sc-fdroid", os: ["macos", "linux", "windows", "android"], platforms: ["webapp"] },
|
|
437
|
+
{ name: "ScreenFlow", description: "Screen recording and editing.", websiteUrl: "https://www.telestream.net/screenflow/", alternativeToUrl: "https://alternativeto.net/software/screenflow/", os: ["macos"], platforms: [] },
|
|
362
438
|
{ name: "SEO Utils", description: "Desktop SEO tools.", websiteUrl: "https://seoutils.app/", os: ["macos", "linux", "windows"], platforms: [] },
|
|
363
439
|
{ name: "Shottr", description: "macOS screenshot utility.", websiteUrl: "https://shottr.cc/", alternativeToUrl: "https://alternativeto.net/software/shottr/", os: ["macos"], platforms: [] },
|
|
364
|
-
{ name: "Signal", description: "Private messenger.", websiteUrl: "https://signal.org/", alternativeToUrl: "https://alternativeto.net/software/signal-private-messenger/", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
365
|
-
{ name: "SimpleX Chat", description: "Private messenger with no user IDs.", websiteUrl: "https://simplex.chat/", alternativeToUrl: "https://alternativeto.net/software/simplex-chat/about/", repoUrl: "https://github.com/simplex-chat", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["cli"] },
|
|
366
|
-
{ name: "Telegram", description: "Cloud-based mobile and desktop messaging app.", websiteUrl: "https://telegram.org/", iosUrl: "https://
|
|
440
|
+
{ name: "Signal", description: "Private messenger.", websiteUrl: "https://signal.org/", alternativeToUrl: "https://alternativeto.net/software/signal-private-messenger/", iosUrl: "https://apps.apple.com/us/app/signal-private-messenger/id874139669", androidUrl: "https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms", os: ["macos", "linux", "windows", "ios", "android"], platforms: [] },
|
|
441
|
+
{ name: "SimpleX Chat", description: "Private messenger with no user IDs.", websiteUrl: "https://simplex.chat/", alternativeToUrl: "https://alternativeto.net/software/simplex-chat/about/", repoUrl: "https://github.com/simplex-chat", iosUrl: "https://apps.apple.com/us/app/simplex-chat/id1605771084", androidUrl: "https://play.google.com/store/apps/details?id=chat.simplex.app", fdroidUrl: "https://simplex.chat/fdroid", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["cli"] },
|
|
442
|
+
{ name: "Telegram", description: "Cloud-based mobile and desktop messaging app.", websiteUrl: "https://telegram.org/", iosUrl: "https://apps.apple.com/us/app/telegram-messenger/id686449807", androidUrl: "https://play.google.com/store/apps/details?id=org.telegram.messenger", os: ["macos", "linux", "windows", "ios", "android"], platforms: ["webapp", "api"] },
|
|
367
443
|
{ name: "Thunderbird", description: "Email and calendar app.", websiteUrl: "https://www.thunderbird.net/", alternativeToUrl: "https://alternativeto.net/software/mozilla-thunderbird/about/", os: ["macos", "linux", "windows"], platforms: [] },
|
|
368
444
|
{ name: "Ubicloud", description: "Open cloud platform.", websiteUrl: "https://www.ubicloud.com/", alternativeToUrl: "https://alternativeto.net/software/ubicloud/about/", repoUrl: "https://github.com/ubicloud/ubicloud", os: ["linux"], platforms: ["webapp", "saas", "cli", "api"] },
|
|
369
445
|
{ name: "Vaultwarden", description: "Alternative Bitwarden server implementation.", websiteUrl: "https://github.com/dani-garcia/vaultwarden", alternativeToUrl: "https://alternativeto.net/software/vaultwarden/", repoUrl: "https://github.com/dani-garcia/vaultwarden", os: ["linux"], platforms: ["webapp", "api"] },
|
|
@@ -147,7 +147,7 @@ export const text = {
|
|
|
147
147
|
aidevops: "aidevops",
|
|
148
148
|
appShell: "App shell",
|
|
149
149
|
apps: "Apps",
|
|
150
|
-
appsIntro: "
|
|
150
|
+
appsIntro: "These are the apps we use and recommend from our tried & tested toolkit — enabling all the things we can do with AI, development, and operations to create and share our work. Opinionated preferences based on open-source-first, lowest-costs, highest-quality UI & UX, teamwork, data security, privacy, and AI-usage capabilities.",
|
|
151
151
|
appStatusPending: "adapter pending",
|
|
152
152
|
brands: "Brands",
|
|
153
153
|
brandsIntro: "Draft brand inventory for names and website references.",
|
|
@@ -1316,7 +1316,8 @@ code {
|
|
|
1316
1316
|
.workspace-scroll {
|
|
1317
1317
|
display: grid;
|
|
1318
1318
|
gap: 16px;
|
|
1319
|
-
overflow:
|
|
1319
|
+
overflow-x: hidden;
|
|
1320
|
+
overflow-y: auto;
|
|
1320
1321
|
padding: 18px;
|
|
1321
1322
|
}
|
|
1322
1323
|
|
|
@@ -2053,7 +2054,8 @@ code {
|
|
|
2053
2054
|
border: 1px solid var(--border);
|
|
2054
2055
|
border-radius: 18px;
|
|
2055
2056
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 7%);
|
|
2056
|
-
|
|
2057
|
+
min-width: 0;
|
|
2058
|
+
overflow: hidden;
|
|
2057
2059
|
transition: background 140ms ease, border-color 140ms ease;
|
|
2058
2060
|
}
|
|
2059
2061
|
|
|
@@ -2174,6 +2176,7 @@ code {
|
|
|
2174
2176
|
gap: 16px;
|
|
2175
2177
|
padding: 0 16px 16px;
|
|
2176
2178
|
position: relative;
|
|
2179
|
+
min-width: 0;
|
|
2177
2180
|
}
|
|
2178
2181
|
|
|
2179
2182
|
.managed-app-toolbar {
|
|
@@ -2182,6 +2185,7 @@ code {
|
|
|
2182
2185
|
gap: 16px;
|
|
2183
2186
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
2184
2187
|
padding-top: 16px;
|
|
2188
|
+
min-width: 0;
|
|
2185
2189
|
}
|
|
2186
2190
|
|
|
2187
2191
|
.managed-app-links,
|
|
@@ -2194,6 +2198,7 @@ code {
|
|
|
2194
2198
|
|
|
2195
2199
|
.managed-app-actions {
|
|
2196
2200
|
justify-content: end;
|
|
2201
|
+
min-width: 0;
|
|
2197
2202
|
}
|
|
2198
2203
|
|
|
2199
2204
|
.managed-app-links a,
|
|
@@ -2393,6 +2398,7 @@ button.managed-toggle:focus-visible {
|
|
|
2393
2398
|
border: 1px solid var(--border);
|
|
2394
2399
|
border-radius: 16px;
|
|
2395
2400
|
color: color-mix(in srgb, var(--accent) 78%, white);
|
|
2401
|
+
min-width: 0;
|
|
2396
2402
|
overflow: hidden;
|
|
2397
2403
|
}
|
|
2398
2404
|
|
|
@@ -2405,11 +2411,40 @@ button.managed-toggle:focus-visible {
|
|
|
2405
2411
|
padding: 10px 12px;
|
|
2406
2412
|
}
|
|
2407
2413
|
|
|
2414
|
+
.app-terminal header strong {
|
|
2415
|
+
min-width: 0;
|
|
2416
|
+
overflow: hidden;
|
|
2417
|
+
text-overflow: ellipsis;
|
|
2418
|
+
white-space: nowrap;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2408
2421
|
.app-terminal header span {
|
|
2422
|
+
flex: 0 0 auto;
|
|
2409
2423
|
color: var(--text-muted);
|
|
2410
2424
|
text-align: right;
|
|
2411
2425
|
}
|
|
2412
2426
|
|
|
2427
|
+
.terminal-close-button {
|
|
2428
|
+
align-items: center;
|
|
2429
|
+
background: transparent;
|
|
2430
|
+
border: 1px solid transparent;
|
|
2431
|
+
border-radius: 999px;
|
|
2432
|
+
color: var(--text-muted);
|
|
2433
|
+
display: inline-flex;
|
|
2434
|
+
flex: 0 0 auto;
|
|
2435
|
+
height: 28px;
|
|
2436
|
+
justify-content: center;
|
|
2437
|
+
padding: 0;
|
|
2438
|
+
width: 28px;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
.terminal-close-button:hover,
|
|
2442
|
+
.terminal-close-button:focus-visible {
|
|
2443
|
+
border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
|
|
2444
|
+
color: var(--accent);
|
|
2445
|
+
outline: none;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2413
2448
|
.app-terminal pre {
|
|
2414
2449
|
margin: 0;
|
|
2415
2450
|
max-height: 240px;
|
|
@@ -2419,6 +2454,24 @@ button.managed-toggle:focus-visible {
|
|
|
2419
2454
|
word-break: break-word;
|
|
2420
2455
|
}
|
|
2421
2456
|
|
|
2457
|
+
.ansi-prompt { color: #7ee787; }
|
|
2458
|
+
.ansi-fg-30,
|
|
2459
|
+
.ansi-fg-90 { color: #8b949e; }
|
|
2460
|
+
.ansi-fg-31,
|
|
2461
|
+
.ansi-fg-91 { color: #ff7b72; }
|
|
2462
|
+
.ansi-fg-32,
|
|
2463
|
+
.ansi-fg-92 { color: #7ee787; }
|
|
2464
|
+
.ansi-fg-33,
|
|
2465
|
+
.ansi-fg-93 { color: #f2cc60; }
|
|
2466
|
+
.ansi-fg-34,
|
|
2467
|
+
.ansi-fg-94 { color: #79c0ff; }
|
|
2468
|
+
.ansi-fg-35,
|
|
2469
|
+
.ansi-fg-95 { color: #d2a8ff; }
|
|
2470
|
+
.ansi-fg-36,
|
|
2471
|
+
.ansi-fg-96 { color: #76e3ea; }
|
|
2472
|
+
.ansi-fg-37,
|
|
2473
|
+
.ansi-fg-97 { color: #f0f6fc; }
|
|
2474
|
+
|
|
2422
2475
|
.modal-backdrop {
|
|
2423
2476
|
align-items: center;
|
|
2424
2477
|
background: rgb(0 0 0 / 62%);
|
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.18
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|