diff --git a/src/renderer/components/CopilotStatusCard.tsx b/src/renderer/components/CopilotStatusCard.tsx
index fc3763a..d3651d2 100644
--- a/src/renderer/components/CopilotStatusCard.tsx
+++ b/src/renderer/components/CopilotStatusCard.tsx
@@ -16,6 +16,7 @@ import {
Loader2,
} from 'lucide-react';
+import { CliInstallGuide } from '@renderer/components/settings/CliInstallGuide';
import type {
SidecarConnectionDiagnostics,
SidecarConnectionStatus,
@@ -391,8 +392,15 @@ export function CopilotStatusCard({
)}
- {/* Action hint for non-ready states */}
- {!isHealthy && config.actionLabel && (
+ {/* Installation guide for missing CLI */}
+ {connection.status === 'copilot-cli-missing' && (
+
{config.actionIcon}
{config.actionLabel}
diff --git a/src/renderer/components/WelcomePane.tsx b/src/renderer/components/WelcomePane.tsx
index 9367615..252fcb8 100644
--- a/src/renderer/components/WelcomePane.tsx
+++ b/src/renderer/components/WelcomePane.tsx
@@ -1,7 +1,9 @@
-import { CheckCircle2, Circle, FolderPlus, MessageSquarePlus, Settings, Zap } from 'lucide-react';
+import { CheckCircle2, Circle, Download, FolderPlus, MessageSquarePlus, Settings, Zap } from 'lucide-react';
import { motion } from 'motion/react';
import type { SidecarConnectionStatus } from '@shared/contracts/sidecar';
+import { detectedPlatform } from '@renderer/lib/platform';
+import { getInstallInfoForPlatform } from '@renderer/lib/cliInstallInstructions';
import appIconUrl from '../../../assets/icons/icon.png';
interface WelcomePaneProps {
@@ -71,6 +73,34 @@ function SetupStep({ label, done, active }: SetupStepProps) {
);
}
+function CliMissingCard({ onOpenSettings }: { onOpenSettings: () => void }) {
+ const info = getInstallInfoForPlatform(detectedPlatform);
+ const recommended = info.methods.find((m) => m.recommended) ?? info.methods[0];
+
+ return (
+
+ );
+}
+
export function WelcomePane({
hasProjects,
connectionStatus,
@@ -165,7 +195,10 @@ export function WelcomePane({
{/* Action cards */}
{/* Primary CTA adapts to state */}
- {!isConnected && (
+ {connectionStatus === 'copilot-cli-missing' && (
+
+ )}
+ {!isConnected && connectionStatus !== 'copilot-cli-missing' && (
}
title="Connect GitHub Copilot"
diff --git a/src/renderer/components/settings/CliInstallGuide.tsx b/src/renderer/components/settings/CliInstallGuide.tsx
new file mode 100644
index 0000000..5a60b8e
--- /dev/null
+++ b/src/renderer/components/settings/CliInstallGuide.tsx
@@ -0,0 +1,209 @@
+import { useState, useCallback } from 'react';
+import { Check, Copy, ChevronRight, KeyRound, Sparkles } from 'lucide-react';
+
+import { detectedPlatform, type DetectedPlatform } from '@renderer/lib/platform';
+import {
+ installInstructions,
+ authCommand,
+ type PlatformInstallInfo,
+ type InstallMethod,
+} from '@renderer/lib/cliInstallInstructions';
+
+interface CliInstallGuideProps {
+ onRefresh: () => void;
+ isRefreshing: boolean;
+}
+
+function PlatformTab({
+ info,
+ active,
+ onClick,
+}: {
+ info: PlatformInstallInfo;
+ active: boolean;
+ onClick: () => void;
+}) {
+ return (
+
+ );
+}
+
+function CommandBlock({
+ method,
+ index,
+}: {
+ method: InstallMethod;
+ index: number;
+}) {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = useCallback(() => {
+ void navigator.clipboard.writeText(method.command);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1800);
+ }, [method.command]);
+
+ return (
+
+ {/* Method label row */}
+
+
+ {method.label}
+
+ {method.recommended && (
+
+
+ Recommended
+
+ )}
+
+
+ {/* Command block */}
+
+
+
+
+ {method.command}
+
+
+
+
+
+ );
+}
+
+export function CliInstallGuide({ onRefresh, isRefreshing }: CliInstallGuideProps) {
+ const [activePlatform, setActivePlatform] = useState(detectedPlatform);
+
+ const activeInfo = installInstructions.find((i) => i.platform === activePlatform)!;
+
+ return (
+
+ {/* Step 1: Install */}
+
+
+
+ 1
+
+
+ Install the Copilot CLI
+
+
+
+ {/* Platform tabs */}
+
+ {installInstructions.map((info) => (
+
setActivePlatform(info.platform)}
+ />
+ ))}
+
+
+ {/* Commands for active platform */}
+
+ {activeInfo.methods.map((method, i) => (
+
+ ))}
+
+
+
+ {/* Step 2: Authenticate */}
+
+
+
+ 2
+
+
+ Sign in to GitHub
+
+
+
+
+
+
+ {/* Step 3: Refresh */}
+
+
+
+ 3
+
+
+ Refresh connection
+
+
+
+
+
+
+ );
+}
+
+function AuthCommandBlock() {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = useCallback(() => {
+ void navigator.clipboard.writeText(authCommand);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1800);
+ }, []);
+
+ return (
+
+
+
+
+ {authCommand}
+
+
+
+
+ );
+}
diff --git a/src/renderer/lib/cliInstallInstructions.ts b/src/renderer/lib/cliInstallInstructions.ts
new file mode 100644
index 0000000..b3198c9
--- /dev/null
+++ b/src/renderer/lib/cliInstallInstructions.ts
@@ -0,0 +1,48 @@
+import type { DetectedPlatform } from './platform';
+
+export interface InstallMethod {
+ label: string;
+ command: string;
+ recommended?: boolean;
+}
+
+export interface PlatformInstallInfo {
+ platform: DetectedPlatform;
+ displayName: string;
+ methods: InstallMethod[];
+}
+
+export const installInstructions: PlatformInstallInfo[] = [
+ {
+ platform: 'macos',
+ displayName: 'macOS',
+ methods: [
+ { label: 'Homebrew', command: 'brew install copilot-cli', recommended: true },
+ { label: 'Install script', command: 'curl -fsSL https://gh.io/copilot-install | bash' },
+ { label: 'npm', command: 'npm install -g @github/copilot' },
+ ],
+ },
+ {
+ platform: 'windows',
+ displayName: 'Windows',
+ methods: [
+ { label: 'WinGet', command: 'winget install GitHub.Copilot', recommended: true },
+ { label: 'npm', command: 'npm install -g @github/copilot' },
+ ],
+ },
+ {
+ platform: 'linux',
+ displayName: 'Linux',
+ methods: [
+ { label: 'Install script', command: 'curl -fsSL https://gh.io/copilot-install | bash', recommended: true },
+ { label: 'Homebrew', command: 'brew install copilot-cli' },
+ { label: 'npm', command: 'npm install -g @github/copilot' },
+ ],
+ },
+];
+
+export const authCommand = 'copilot auth login';
+
+export function getInstallInfoForPlatform(platform: DetectedPlatform): PlatformInstallInfo {
+ return installInstructions.find((i) => i.platform === platform)!;
+}
diff --git a/src/renderer/lib/platform.ts b/src/renderer/lib/platform.ts
index 954617b..edcc96d 100644
--- a/src/renderer/lib/platform.ts
+++ b/src/renderer/lib/platform.ts
@@ -1 +1,8 @@
+export type DetectedPlatform = 'macos' | 'windows' | 'linux';
+
export const isMac = navigator.platform.startsWith('Mac');
+export const isWindows = navigator.platform.startsWith('Win');
+export const isLinux = !isMac && !isWindows;
+
+export const detectedPlatform: DetectedPlatform =
+ isMac ? 'macos' : isWindows ? 'windows' : 'linux';