Skip to content

Commit fd30400

Browse files
beautyfreeclaude
andauthored
v0.2.5: Linux-specific install commands + docs URL for agents (#13)
Problem: on the Dashboard "Install" modal, Linux users saw the macOS command (brew install --cask …) with no fallback. Five agents were affected — Antigravity, Qoder, Trae, Warp, Windsurf — whose install_command was macOS-specific. The UI copied the brew command verbatim, which fails on Linux, and linked to a formulae.brew.sh page that's meaningless there. Schema changes: - `install_command_linux?: string | null` - `install_docs_url_linux?: string | null` Added through the full stack: agents/*.toml → src/main/types.ts → src/main/registry.ts → src/main/skill-json.ts → src/shared/rpc-schema.ts → src/mainview/hooks/useAgents.ts → Dashboard.tsx. Renderer logic: - New `detectOS()` prefers `window.api.platform` from the Electron preload (reliable — Electron sets it to `darwin`/`win32`/`linux`) and falls back to userAgent sniffing for plain-Vite dev. - `getInstallCommand(agent)` returns the OS-specific command, falling back to `install_command` ONLY when it starts with a cross-platform prefix (npm/npx/pnpm/yarn/bun/pip/pipx/cargo/go/curl/wget). macOS- specific installers (brew, mas) never leak to Linux/Windows now. - `getInstallDocsUrl(agent)` checks for a Linux override before falling back to the shared `install_docs_url`. - `Dashboard.tsx::InstallGuideModal` replaces its inline `isWindows ? … : …` with these helpers so all three OSes are handled uniformly. TOML data: - qoder: `install_command_linux = "npm install -g @qoder-ai/qodercli"` (the Windows command happens to be the right one for Linux too). - antigravity, warp, windsurf, trae: no `install_command_linux` (Google and Warp distribute through their own apt/yum repos with per-distro one-liners; Trae has no native Linux build yet). All four get an `install_docs_url_linux` pointing at the official Linux download page so users click through to authoritative instructions instead of a useless brew-cask URL. Version bump to 0.2.5 so the change lands via electron-updater. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5de2086 commit fd30400

12 files changed

Lines changed: 118 additions & 22 deletions

File tree

agents/antigravity.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ skill_format = "skill-md"
66
cli_command = "antigravity"
77
install_command = "brew install --cask antigravity"
88
install_command_windows = "winget install Google.Antigravity"
9+
# Linux: Google ships .deb and .tar.gz from their official download page but
10+
# doesn't publish a stable one-liner install script we can hardcode. Leave the
11+
# command empty — the UI hides the "copy install command" block when null and
12+
# shows the docs-URL button instead, which points to the Linux download page.
913
install_docs_url = "https://formulae.brew.sh/cask/antigravity"
14+
install_docs_url_linux = "https://antigravity.google/download/linux"
1015
install_source_label = "homebrew-cask"
1116

1217
[[additional_readable_paths]]

agents/qoder.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ skill_format = "skill-md"
66
cli_command = "qodercli"
77
install_command = "brew install qoderai/qoder/qodercli --cask"
88
install_command_windows = "npm install -g @qoder-ai/qodercli"
9+
# qodercli is a cross-platform npm package — the Windows command works as-is
10+
# on Linux too.
11+
install_command_linux = "npm install -g @qoder-ai/qodercli"
912
install_docs_url = "https://docs.qoder.com/cli/quick-start"
1013
install_source_label = "official-docs"

agents/trae.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ skill_format = "skill-md"
66
cli_command = "trae"
77
install_command = "brew install --cask trae"
88
install_command_windows = "winget install ByteDance.Trae"
9+
# Trae has no native Linux desktop build as of 2026 (only macOS, Windows, and
10+
# the web-based Cloud IDE). No install_command_linux — the UI hides the copy-
11+
# command block and points the user at the web IDE via docs URL.
912
install_docs_url = "https://formulae.brew.sh/cask/trae"
13+
install_docs_url_linux = "https://www.trae.ai/download"
1014
install_source_label = "homebrew-cask"

agents/warp.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ global_paths = ["~/.warp/skills"]
55
skill_format = "skill-md"
66
install_command = "brew install --cask warp"
77
install_command_windows = "winget install Warp.Warp"
8+
# Linux: Warp publishes deb/rpm/pkg.tar.zst via their APT/YUM repos. The
9+
# docs page covers every distro's package-manager recipe — safer than
10+
# hardcoding a single distro's commands here.
811
install_docs_url = "https://www.warp.dev/"
12+
install_docs_url_linux = "https://docs.warp.dev/getting-started/installation-and-setup"
913
install_source_label = "official-site"
1014
detect_paths = ["/Applications/Warp.app", "~/AppData/Roaming/warp/Warp", "~/.local/share/warp-terminal"]
1115

agents/windsurf.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ skill_format = "skill-md"
66
cli_command = "windsurf"
77
install_command = "brew install --cask windsurf"
88
install_command_windows = "winget install Codeium.Windsurf"
9+
# Linux: Windsurf ships AppImage, deb, and tar.gz + GPG-signed apt/yum repos.
10+
# Their download page renders distro-specific one-liners (e.g. apt vs dnf vs
11+
# tarball), so we route the user there instead of hardcoding one flavor.
912
install_docs_url = "https://formulae.brew.sh/cask/windsurf"
13+
install_docs_url_linux = "https://windsurf.com/editor/download-linux"
1014
install_source_label = "homebrew-cask"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "skiller",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"description": "A desktop app for managing AI agent skills across Claude Code, Cursor, Copilot, Gemini CLI, and more",
55
"license": "MIT",
66
"author": {

src/main/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ export function loadAgentConfigs(dir: string): AgentConfig[] {
6666
cli_command: parsed.cli_command as string | null | undefined,
6767
install_command: parsed.install_command as string | null | undefined,
6868
install_command_windows: parsed.install_command_windows as string | null | undefined,
69+
install_command_linux: parsed.install_command_linux as string | null | undefined,
6970
install_docs_url: parsed.install_docs_url as string | null | undefined,
71+
install_docs_url_linux: parsed.install_docs_url_linux as string | null | undefined,
7072
install_source_label: parsed.install_source_label as string | null | undefined,
7173
detect_paths: (parsed.detect_paths as string[] | undefined)?.map((p) => expandHome(p)) ?? [],
7274
});

src/main/skill-json.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ export function agentConfigToJson(
6767
cli_command: a.cli_command ?? null,
6868
install_command: a.install_command ?? null,
6969
install_command_windows: a.install_command_windows ?? null,
70+
install_command_linux: a.install_command_linux ?? null,
7071
install_docs_url: a.install_docs_url ?? null,
72+
install_docs_url_linux: a.install_docs_url_linux ?? null,
7173
install_source_label: a.install_source_label ?? null,
7274
detect_paths: a.detect_paths,
7375
detected: a.detected,

src/main/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export interface AgentConfig {
3030
cli_command?: string | null;
3131
install_command?: string | null;
3232
install_command_windows?: string | null;
33+
install_command_linux?: string | null;
3334
install_docs_url?: string | null;
35+
install_docs_url_linux?: string | null;
3436
install_source_label?: string | null;
3537
detect_paths: string[];
3638
detected: boolean;

src/mainview/hooks/useAgents.ts

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,92 @@ export interface AgentConfig {
1010
cli_command: string | null;
1111
install_command: string | null;
1212
install_command_windows: string | null;
13+
install_command_linux: string | null;
1314
install_docs_url: string | null;
15+
install_docs_url_linux: string | null;
1416
install_source_label: string | null;
1517
detected: boolean;
1618
}
1719

18-
/** Get the platform-appropriate install command */
20+
type OS = "windows" | "linux" | "macos";
21+
22+
function detectOS(): OS {
23+
// window.api.platform comes from the Electron preload bridge. Fall back to
24+
// userAgent sniffing for plain-Vite dev (`vite dev` without the Electron
25+
// shell) so the install buttons still resolve to something sensible.
26+
const native = (globalThis as { api?: { platform?: string } }).api?.platform;
27+
if (native === "win32") return "windows";
28+
if (native === "linux") return "linux";
29+
if (native === "darwin") return "macos";
30+
if (typeof navigator === "undefined") return "macos";
31+
const ua = navigator.userAgent;
32+
if (ua.includes("Windows")) return "windows";
33+
if (ua.includes("Linux")) return "linux";
34+
return "macos";
35+
}
36+
37+
/**
38+
* Get the platform-appropriate install command. `install_command` is the
39+
* default (macOS-flavored — brew cask, curl | bash, npm -g). Windows and
40+
* Linux get their own optional fields; if null, we fall back to the default
41+
* ONLY when the default is plausibly cross-platform (starts with `npm `).
42+
* Otherwise return null so the UI can show "no install command for this OS".
43+
*/
1944
export function getInstallCommand(agent: AgentConfig): string | null {
20-
if (navigator.userAgent.includes("Windows")) {
21-
return agent.install_command_windows ?? agent.install_command;
45+
const os = detectOS();
46+
if (os === "windows") {
47+
return agent.install_command_windows ?? fallbackDefault(agent);
48+
}
49+
if (os === "linux") {
50+
return agent.install_command_linux ?? fallbackDefault(agent);
2251
}
2352
return agent.install_command;
2453
}
2554

55+
/**
56+
* When a platform-specific command isn't defined, we fall back to the default
57+
* `install_command` ONLY if it looks host-agnostic. Whitelist:
58+
* - node ecosystem (`npm`, `npx`, `pnpm`, `yarn`, `bun`)
59+
* - python (`pip`, `pipx`)
60+
* - rust/go (`cargo`, `go install`)
61+
* - `curl | bash` style installers — these are almost always cross-platform
62+
* in practice (the script inside branches on `uname`)
63+
* Blacklist (implicit — anything not matched returns null): `brew`, `winget`,
64+
* `choco`, `scoop`, `apt`, `snap`, `flatpak`, etc. — all OS-specific.
65+
*/
66+
function fallbackDefault(agent: AgentConfig): string | null {
67+
const cmd = agent.install_command;
68+
if (!cmd) return null;
69+
const trimmed = cmd.trim();
70+
const head = trimmed.split(/\s+/)[0];
71+
const CROSS_PLATFORM_PREFIXES = [
72+
"npm",
73+
"npx",
74+
"pnpm",
75+
"yarn",
76+
"bun",
77+
"bunx",
78+
"pip",
79+
"pipx",
80+
"cargo",
81+
"go",
82+
"curl", // curl|bash installers branch on uname internally
83+
"wget",
84+
];
85+
if (CROSS_PLATFORM_PREFIXES.includes(head)) return cmd;
86+
return null;
87+
}
88+
89+
/** Get the platform-appropriate docs URL. Linux can override the default
90+
* since the default often points at Homebrew-cask pages that are useless
91+
* for Linux users. */
92+
export function getInstallDocsUrl(agent: AgentConfig): string | null {
93+
if (detectOS() === "linux" && agent.install_docs_url_linux) {
94+
return agent.install_docs_url_linux;
95+
}
96+
return agent.install_docs_url;
97+
}
98+
2699
export function useAgents() {
27100
return useQuery<AgentConfig[]>({
28101
queryKey: ["agents"],

0 commit comments

Comments
 (0)