mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: add PR build check workflow and enhance feature management
- Introduced a new GitHub Actions workflow for PR build checks to ensure code quality and consistency. - Updated `analysis-view.tsx`, `interview-view.tsx`, and `setup-view.tsx` to incorporate a new `Feature` type for better feature management. - Refactored various components to improve code readability and maintainability. - Adjusted type imports in `delete-project-dialog.tsx` and `settings-navigation.tsx` for consistency. - Enhanced project initialization logic in `project-init.ts` to ensure proper type handling. - Updated Electron API types in `electron.d.ts` for better clarity and functionality.
This commit is contained in:
33
.github/workflows/pr-check.yml
vendored
Normal file
33
.github/workflows/pr-check.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: PR Build Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: app/package-lock.json
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: ./app
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run build:electron
|
||||||
|
working-directory: ./app
|
||||||
|
run: npm run build:electron
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useAppStore, FileTreeNode, ProjectAnalysis } from "@/store/app-store";
|
import {
|
||||||
|
useAppStore,
|
||||||
|
FileTreeNode,
|
||||||
|
ProjectAnalysis,
|
||||||
|
Feature,
|
||||||
|
} from "@/store/app-store";
|
||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -763,7 +768,17 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
|||||||
throw new Error("Features API not available");
|
throw new Error("Features API not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const feature of detectedFeatures) {
|
// Convert DetectedFeature to Feature by adding required id and status
|
||||||
|
for (const detectedFeature of detectedFeatures) {
|
||||||
|
const feature: Feature = {
|
||||||
|
id: `feature-${Date.now()}-${Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 9)}`,
|
||||||
|
category: detectedFeature.category,
|
||||||
|
description: detectedFeature.description,
|
||||||
|
steps: detectedFeature.steps,
|
||||||
|
status: "backlog" as const,
|
||||||
|
};
|
||||||
await api.features.create(currentProject.path, feature);
|
await api.features.create(currentProject.path, feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback, useRef, useEffect } from "react";
|
import { useState, useCallback, useRef, useEffect } from "react";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore, Feature } from "@/store/app-store";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -313,11 +313,11 @@ export function InterviewView() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create initial feature in the features folder
|
// Create initial feature in the features folder
|
||||||
const initialFeature = {
|
const initialFeature: Feature = {
|
||||||
id: `feature-${Date.now()}-0`,
|
id: `feature-${Date.now()}-0`,
|
||||||
category: "Core",
|
category: "Core",
|
||||||
description: "Initial project setup",
|
description: "Initial project setup",
|
||||||
status: "backlog",
|
status: "backlog" as const,
|
||||||
steps: [
|
steps: [
|
||||||
"Step 1: Review app_spec.txt",
|
"Step 1: Review app_spec.txt",
|
||||||
"Step 2: Set up development environment",
|
"Step 2: Set up development environment",
|
||||||
@@ -325,6 +325,9 @@ export function InterviewView() {
|
|||||||
],
|
],
|
||||||
skipTests: true,
|
skipTests: true,
|
||||||
};
|
};
|
||||||
|
if (!api.features) {
|
||||||
|
throw new Error("Features API not available");
|
||||||
|
}
|
||||||
await api.features.create(fullProjectPath, initialFeature);
|
await api.features.create(fullProjectPath, initialFeature);
|
||||||
|
|
||||||
const project = {
|
const project = {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import type { Project } from "@/store/app-store";
|
import type { Project } from "@/lib/electron";
|
||||||
|
|
||||||
interface DeleteProjectDialogProps {
|
interface DeleteProjectDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -49,14 +49,19 @@ export function DeleteProjectDialog({
|
|||||||
<Folder className="w-5 h-5 text-brand-500" />
|
<Folder className="w-5 h-5 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="font-medium text-foreground truncate">{project.name}</p>
|
<p className="font-medium text-foreground truncate">
|
||||||
<p className="text-xs text-muted-foreground truncate">{project.path}</p>
|
{project.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground truncate">
|
||||||
|
{project.path}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
The folder will remain on disk until you permanently delete it from Trash.
|
The folder will remain on disk until you permanently delete it from
|
||||||
|
Trash.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Project } from "@/store/app-store";
|
import type { Project } from "@/lib/electron";
|
||||||
import type { NavigationItem } from "../config/navigation";
|
import type { NavigationItem } from "../config/navigation";
|
||||||
|
|
||||||
interface SettingsNavigationProps {
|
interface SettingsNavigationProps {
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ function StatusBadge({
|
|||||||
status,
|
status,
|
||||||
label,
|
label,
|
||||||
}: {
|
}: {
|
||||||
status: "installed" | "not_installed" | "checking" | "authenticated" | "not_authenticated";
|
status:
|
||||||
|
| "installed"
|
||||||
|
| "not_installed"
|
||||||
|
| "checking"
|
||||||
|
| "authenticated"
|
||||||
|
| "not_authenticated";
|
||||||
label: string;
|
label: string;
|
||||||
}) {
|
}) {
|
||||||
const getStatusConfig = () => {
|
const getStatusConfig = () => {
|
||||||
@@ -128,8 +133,8 @@ function WelcomeStep({ onNext }: { onNext: () => void }) {
|
|||||||
Welcome to Automaker
|
Welcome to Automaker
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground max-w-md mx-auto">
|
<p className="text-muted-foreground max-w-md mx-auto">
|
||||||
Let's set up your development environment. We'll check for required
|
Let's set up your development environment. We'll check for
|
||||||
CLI tools and help you configure them.
|
required CLI tools and help you configure them.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -143,7 +148,8 @@ function WelcomeStep({ onNext }: { onNext: () => void }) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Anthropic's powerful AI assistant for code generation and analysis
|
Anthropic's powerful AI assistant for code generation and
|
||||||
|
analysis
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -200,7 +206,9 @@ function ClaudeSetupStep({
|
|||||||
|
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
const [isInstalling, setIsInstalling] = useState(false);
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
const [authMethod, setAuthMethod] = useState<"token" | "api_key" | null>(null);
|
const [authMethod, setAuthMethod] = useState<"token" | "api_key" | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [oauthToken, setOAuthToken] = useState("");
|
const [oauthToken, setOAuthToken] = useState("");
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
@@ -213,9 +221,18 @@ function ClaudeSetupStep({
|
|||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
// Debug: Check what's available
|
// Debug: Check what's available
|
||||||
console.log("[Claude Setup] isElectron:", typeof window !== "undefined" && (window as any).isElectron);
|
console.log(
|
||||||
console.log("[Claude Setup] electronAPI exists:", typeof window !== "undefined" && !!(window as any).electronAPI);
|
"[Claude Setup] isElectron:",
|
||||||
console.log("[Claude Setup] electronAPI.setup exists:", typeof window !== "undefined" && !!(window as any).electronAPI?.setup);
|
typeof window !== "undefined" && (window as any).isElectron
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[Claude Setup] electronAPI exists:",
|
||||||
|
typeof window !== "undefined" && !!(window as any).electronAPI
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[Claude Setup] electronAPI.setup exists:",
|
||||||
|
typeof window !== "undefined" && !!(window as any).electronAPI?.setup
|
||||||
|
);
|
||||||
console.log("[Claude Setup] Setup API available:", !!setupApi);
|
console.log("[Claude Setup] Setup API available:", !!setupApi);
|
||||||
|
|
||||||
if (setupApi?.getClaudeStatus) {
|
if (setupApi?.getClaudeStatus) {
|
||||||
@@ -224,7 +241,7 @@ function ClaudeSetupStep({
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const cliStatus = {
|
const cliStatus = {
|
||||||
installed: result.installed || result.status === "installed",
|
installed: result.status === "installed",
|
||||||
path: result.path || null,
|
path: result.path || null,
|
||||||
version: result.version || null,
|
version: result.version || null,
|
||||||
method: result.method || "none",
|
method: result.method || "none",
|
||||||
@@ -235,14 +252,16 @@ function ClaudeSetupStep({
|
|||||||
if (result.auth) {
|
if (result.auth) {
|
||||||
const authStatus = {
|
const authStatus = {
|
||||||
authenticated: result.auth.authenticated,
|
authenticated: result.auth.authenticated,
|
||||||
method: result.auth.method === "oauth_token"
|
method:
|
||||||
? "oauth"
|
result.auth.method === "oauth_token"
|
||||||
: result.auth.method?.includes("api_key")
|
? "oauth"
|
||||||
? "api_key"
|
: result.auth.method?.includes("api_key")
|
||||||
: "none",
|
? "api_key"
|
||||||
|
: "none",
|
||||||
hasCredentialsFile: false,
|
hasCredentialsFile: false,
|
||||||
oauthTokenValid: result.auth.hasStoredOAuthToken,
|
oauthTokenValid: result.auth.hasStoredOAuthToken,
|
||||||
apiKeyValid: result.auth.hasStoredApiKey || result.auth.hasEnvApiKey,
|
apiKeyValid:
|
||||||
|
result.auth.hasStoredApiKey || result.auth.hasEnvApiKey,
|
||||||
};
|
};
|
||||||
console.log("[Claude Setup] Auth Status:", authStatus);
|
console.log("[Claude Setup] Auth Status:", authStatus);
|
||||||
setClaudeAuthStatus(authStatus as any);
|
setClaudeAuthStatus(authStatus as any);
|
||||||
@@ -274,13 +293,18 @@ function ClaudeSetupStep({
|
|||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
if (setupApi?.installClaude) {
|
if (setupApi?.installClaude) {
|
||||||
const unsubscribe = setupApi.onInstallProgress?.((progress: { cli?: string; data?: string; type?: string }) => {
|
const unsubscribe = setupApi.onInstallProgress?.(
|
||||||
if (progress.cli === "claude") {
|
(progress: { cli?: string; data?: string; type?: string }) => {
|
||||||
setClaudeInstallProgress({
|
if (progress.cli === "claude") {
|
||||||
output: [...claudeInstallProgress.output, progress.data || progress.type || ""],
|
setClaudeInstallProgress({
|
||||||
});
|
output: [
|
||||||
|
...claudeInstallProgress.output,
|
||||||
|
progress.data || progress.type || "",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const result = await setupApi.installClaude();
|
const result = await setupApi.installClaude();
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
@@ -290,17 +314,17 @@ function ClaudeSetupStep({
|
|||||||
// Wait a bit for installation to complete and PATH to update, then retry status check
|
// Wait a bit for installation to complete and PATH to update, then retry status check
|
||||||
let retries = 5;
|
let retries = 5;
|
||||||
let detected = false;
|
let detected = false;
|
||||||
|
|
||||||
// Initial delay to let the installation script finish setting up
|
// Initial delay to let the installation script finish setting up
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
for (let i = 0; i < retries; i++) {
|
for (let i = 0; i < retries; i++) {
|
||||||
// Check status
|
// Check status
|
||||||
await checkStatus();
|
await checkStatus();
|
||||||
|
|
||||||
// Small delay to let state update
|
// Small delay to let state update
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
// Check if CLI is now detected by re-reading from store
|
// Check if CLI is now detected by re-reading from store
|
||||||
const currentStatus = useSetupStore.getState().claudeCliStatus;
|
const currentStatus = useSetupStore.getState().claudeCliStatus;
|
||||||
if (currentStatus?.installed) {
|
if (currentStatus?.installed) {
|
||||||
@@ -308,18 +332,21 @@ function ClaudeSetupStep({
|
|||||||
toast.success("Claude CLI installed and detected successfully");
|
toast.success("Claude CLI installed and detected successfully");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait before next retry (longer delays for later retries)
|
// Wait before next retry (longer delays for later retries)
|
||||||
if (i < retries - 1) {
|
if (i < retries - 1) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000 + (i * 500)));
|
await new Promise((resolve) =>
|
||||||
|
setTimeout(resolve, 2000 + i * 500)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show appropriate message based on detection
|
// Show appropriate message based on detection
|
||||||
if (!detected) {
|
if (!detected) {
|
||||||
// Installation completed but CLI not detected - this is common if PATH wasn't updated in current process
|
// Installation completed but CLI not detected - this is common if PATH wasn't updated in current process
|
||||||
toast.success("Claude CLI installation completed", {
|
toast.success("Claude CLI installation completed", {
|
||||||
description: "The CLI was installed but may need a terminal restart to be detected. You can continue with authentication if you have a token.",
|
description:
|
||||||
|
"The CLI was installed but may need a terminal restart to be detected. You can continue with authentication if you have a token.",
|
||||||
duration: 7000,
|
duration: 7000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -349,7 +376,10 @@ function ClaudeSetupStep({
|
|||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
if (setupApi?.storeApiKey) {
|
if (setupApi?.storeApiKey) {
|
||||||
const result = await setupApi.storeApiKey("anthropic_oauth_token", oauthToken);
|
const result = await setupApi.storeApiKey(
|
||||||
|
"anthropic_oauth_token",
|
||||||
|
oauthToken
|
||||||
|
);
|
||||||
console.log("[Claude Setup] Store OAuth token result:", result);
|
console.log("[Claude Setup] Store OAuth token result:", result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -434,7 +464,8 @@ function ClaudeSetupStep({
|
|||||||
const getAuthMethodLabel = () => {
|
const getAuthMethodLabel = () => {
|
||||||
if (!isAuthenticated) return null;
|
if (!isAuthenticated) return null;
|
||||||
if (claudeAuthStatus?.method === "oauth") return "Subscription Token";
|
if (claudeAuthStatus?.method === "oauth") return "Subscription Token";
|
||||||
if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key") return "API Key";
|
if (apiKeys.anthropic || claudeAuthStatus?.method === "api_key")
|
||||||
|
return "API Key";
|
||||||
return "Authenticated";
|
return "Authenticated";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -457,8 +488,15 @@ function ClaudeSetupStep({
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-lg">Status</CardTitle>
|
<CardTitle className="text-lg">Status</CardTitle>
|
||||||
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
|
<Button
|
||||||
<RefreshCw className={`w-4 h-4 ${isChecking ? "animate-spin" : ""}`} />
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={checkStatus}
|
||||||
|
disabled={isChecking}
|
||||||
|
>
|
||||||
|
<RefreshCw
|
||||||
|
className={`w-4 h-4 ${isChecking ? "animate-spin" : ""}`}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -477,7 +515,9 @@ function ClaudeSetupStep({
|
|||||||
{claudeCliStatus?.version && (
|
{claudeCliStatus?.version && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm text-muted-foreground">Version</span>
|
<span className="text-sm text-muted-foreground">Version</span>
|
||||||
<span className="text-sm font-mono text-foreground">{claudeCliStatus.version}</span>
|
<span className="text-sm font-mono text-foreground">
|
||||||
|
{claudeCliStatus.version}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -487,11 +527,16 @@ function ClaudeSetupStep({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StatusBadge status="authenticated" label="Authenticated" />
|
<StatusBadge status="authenticated" label="Authenticated" />
|
||||||
{getAuthMethodLabel() && (
|
{getAuthMethodLabel() && (
|
||||||
<span className="text-xs text-muted-foreground">({getAuthMethodLabel()})</span>
|
<span className="text-xs text-muted-foreground">
|
||||||
|
({getAuthMethodLabel()})
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<StatusBadge status="not_authenticated" label="Not Authenticated" />
|
<StatusBadge
|
||||||
|
status="not_authenticated"
|
||||||
|
label="Not Authenticated"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -505,16 +550,28 @@ function ClaudeSetupStep({
|
|||||||
<Download className="w-5 h-5" />
|
<Download className="w-5 h-5" />
|
||||||
Install Claude CLI
|
Install Claude CLI
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Required for subscription-based authentication</CardDescription>
|
<CardDescription>
|
||||||
|
Required for subscription-based authentication
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm text-muted-foreground">macOS / Linux</Label>
|
<Label className="text-sm text-muted-foreground">
|
||||||
|
macOS / Linux
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
||||||
curl -fsSL https://claude.ai/install.sh | bash
|
curl -fsSL https://claude.ai/install.sh | bash
|
||||||
</code>
|
</code>
|
||||||
<Button variant="ghost" size="icon" onClick={() => copyCommand("curl -fsSL https://claude.ai/install.sh | bash")}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() =>
|
||||||
|
copyCommand(
|
||||||
|
"curl -fsSL https://claude.ai/install.sh | bash"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<Copy className="w-4 h-4" />
|
<Copy className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -526,13 +583,21 @@ function ClaudeSetupStep({
|
|||||||
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
||||||
irm https://claude.ai/install.ps1 | iex
|
irm https://claude.ai/install.ps1 | iex
|
||||||
</code>
|
</code>
|
||||||
<Button variant="ghost" size="icon" onClick={() => copyCommand("irm https://claude.ai/install.ps1 | iex")}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() =>
|
||||||
|
copyCommand("irm https://claude.ai/install.ps1 | iex")
|
||||||
|
}
|
||||||
|
>
|
||||||
<Copy className="w-4 h-4" />
|
<Copy className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{claudeInstallProgress.isInstalling && <TerminalOutput lines={claudeInstallProgress.output} />}
|
{claudeInstallProgress.isInstalling && (
|
||||||
|
<TerminalOutput lines={claudeInstallProgress.output} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleInstall}
|
onClick={handleInstall}
|
||||||
@@ -573,25 +638,37 @@ function ClaudeSetupStep({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Shield className="w-5 h-5 text-brand-500 mt-0.5" />
|
<Shield className="w-5 h-5 text-brand-500 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-foreground">Subscription Token</p>
|
<p className="font-medium text-foreground">
|
||||||
<p className="text-sm text-muted-foreground mb-3">Use your Claude subscription (no API charges)</p>
|
Subscription Token
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
|
Use your Claude subscription (no API charges)
|
||||||
|
</p>
|
||||||
|
|
||||||
{claudeCliStatus?.installed ? (
|
{claudeCliStatus?.installed ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<p className="text-sm text-muted-foreground mb-2">1. Run this command in your terminal:</p>
|
<p className="text-sm text-muted-foreground mb-2">
|
||||||
|
1. Run this command in your terminal:
|
||||||
|
</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm font-mono text-foreground">
|
||||||
claude setup-token
|
claude setup-token
|
||||||
</code>
|
</code>
|
||||||
<Button variant="ghost" size="icon" onClick={() => copyCommand("claude setup-token")}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => copyCommand("claude setup-token")}
|
||||||
|
>
|
||||||
<Copy className="w-4 h-4" />
|
<Copy className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-foreground">2. Paste the token here:</Label>
|
<Label className="text-foreground">
|
||||||
|
2. Paste the token here:
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Paste token from claude setup-token..."
|
placeholder="Paste token from claude setup-token..."
|
||||||
@@ -603,7 +680,11 @@ function ClaudeSetupStep({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 mt-3">
|
<div className="flex gap-2 mt-3">
|
||||||
<Button variant="outline" onClick={() => setAuthMethod(null)} className="border-border">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setAuthMethod(null)}
|
||||||
|
className="border-border"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -612,7 +693,11 @@ function ClaudeSetupStep({
|
|||||||
className="flex-1 bg-brand-500 hover:bg-brand-600 text-white"
|
className="flex-1 bg-brand-500 hover:bg-brand-600 text-white"
|
||||||
data-testid="save-oauth-token-button"
|
data-testid="save-oauth-token-button"
|
||||||
>
|
>
|
||||||
{isSaving ? <Loader2 className="w-4 h-4 animate-spin" /> : "Save Token"}
|
{isSaving ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Save Token"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -620,7 +705,8 @@ function ClaudeSetupStep({
|
|||||||
<div className="p-3 rounded bg-yellow-500/10 border border-yellow-500/20">
|
<div className="p-3 rounded bg-yellow-500/10 border border-yellow-500/20">
|
||||||
<p className="text-sm text-yellow-600">
|
<p className="text-sm text-yellow-600">
|
||||||
<AlertCircle className="w-4 h-4 inline mr-1" />
|
<AlertCircle className="w-4 h-4 inline mr-1" />
|
||||||
Install Claude CLI first to use subscription authentication
|
Install Claude CLI first to use subscription
|
||||||
|
authentication
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -634,10 +720,17 @@ function ClaudeSetupStep({
|
|||||||
<Key className="w-5 h-5 text-green-500 mt-0.5" />
|
<Key className="w-5 h-5 text-green-500 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-foreground">API Key</p>
|
<p className="font-medium text-foreground">API Key</p>
|
||||||
<p className="text-sm text-muted-foreground mb-3">Pay-per-use with your Anthropic API key</p>
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
|
Pay-per-use with your Anthropic API key
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="anthropic-key" className="text-foreground">Anthropic API Key</Label>
|
<Label
|
||||||
|
htmlFor="anthropic-key"
|
||||||
|
className="text-foreground"
|
||||||
|
>
|
||||||
|
Anthropic API Key
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="anthropic-key"
|
id="anthropic-key"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -662,7 +755,11 @@ function ClaudeSetupStep({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 mt-3">
|
<div className="flex gap-2 mt-3">
|
||||||
<Button variant="outline" onClick={() => setAuthMethod(null)} className="border-border">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setAuthMethod(null)}
|
||||||
|
className="border-border"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -671,7 +768,11 @@ function ClaudeSetupStep({
|
|||||||
className="flex-1 bg-green-500 hover:bg-green-600 text-white"
|
className="flex-1 bg-green-500 hover:bg-green-600 text-white"
|
||||||
data-testid="save-anthropic-key-button"
|
data-testid="save-anthropic-key-button"
|
||||||
>
|
>
|
||||||
{isSaving ? <Loader2 className="w-4 h-4 animate-spin" /> : "Save API Key"}
|
{isSaving ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Save API Key"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -688,9 +789,15 @@ function ClaudeSetupStep({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Shield className="w-6 h-6 text-brand-500" />
|
<Shield className="w-6 h-6 text-brand-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">Subscription</p>
|
<p className="font-medium text-foreground">
|
||||||
<p className="text-sm text-muted-foreground mt-1">Use your Claude subscription</p>
|
Subscription
|
||||||
<p className="text-xs text-brand-500 mt-2">No API charges</p>
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Use your Claude subscription
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-brand-500 mt-2">
|
||||||
|
No API charges
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -704,7 +811,9 @@ function ClaudeSetupStep({
|
|||||||
<Key className="w-6 h-6 text-green-500" />
|
<Key className="w-6 h-6 text-green-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">API Key</p>
|
<p className="font-medium text-foreground">API Key</p>
|
||||||
<p className="text-sm text-muted-foreground mt-1">Use Anthropic API key</p>
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Use Anthropic API key
|
||||||
|
</p>
|
||||||
<p className="text-xs text-green-500 mt-2">Pay-per-use</p>
|
<p className="text-xs text-green-500 mt-2">Pay-per-use</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -724,9 +833,12 @@ function ClaudeSetupStep({
|
|||||||
<CheckCircle2 className="w-6 h-6 text-green-500" />
|
<CheckCircle2 className="w-6 h-6 text-green-500" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-foreground">Claude is ready to use!</p>
|
<p className="font-medium text-foreground">
|
||||||
|
Claude is ready to use!
|
||||||
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{getAuthMethodLabel() && `Using ${getAuthMethodLabel()}. `}You can proceed to the next step
|
{getAuthMethodLabel() && `Using ${getAuthMethodLabel()}. `}You
|
||||||
|
can proceed to the next step
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -736,15 +848,27 @@ function ClaudeSetupStep({
|
|||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<div className="flex justify-between pt-4">
|
<div className="flex justify-between pt-4">
|
||||||
<Button variant="ghost" onClick={onBack} className="text-muted-foreground">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onBack}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
>
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="ghost" onClick={onSkip} className="text-muted-foreground">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onSkip}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
>
|
||||||
Skip for now
|
Skip for now
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onNext} className="bg-brand-500 hover:bg-brand-600 text-white" data-testid="claude-next-button">
|
<Button
|
||||||
|
onClick={onNext}
|
||||||
|
className="bg-brand-500 hover:bg-brand-600 text-white"
|
||||||
|
data-testid="claude-next-button"
|
||||||
|
>
|
||||||
Continue
|
Continue
|
||||||
<ArrowRight className="w-4 h-4 ml-2" />
|
<ArrowRight className="w-4 h-4 ml-2" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -804,7 +928,10 @@ function CodexSetupStep({
|
|||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
console.log("[Codex Setup] Setup API available:", !!setupApi);
|
console.log("[Codex Setup] Setup API available:", !!setupApi);
|
||||||
console.log("[Codex Setup] getCodexStatus available:", !!setupApi?.getCodexStatus);
|
console.log(
|
||||||
|
"[Codex Setup] getCodexStatus available:",
|
||||||
|
!!setupApi?.getCodexStatus
|
||||||
|
);
|
||||||
|
|
||||||
if (setupApi?.getCodexStatus) {
|
if (setupApi?.getCodexStatus) {
|
||||||
const result = await setupApi.getCodexStatus();
|
const result = await setupApi.getCodexStatus();
|
||||||
@@ -822,12 +949,15 @@ function CodexSetupStep({
|
|||||||
|
|
||||||
if (result.auth) {
|
if (result.auth) {
|
||||||
const method = mapAuthMethod(result.auth.method);
|
const method = mapAuthMethod(result.auth.method);
|
||||||
|
|
||||||
const authStatus: CodexAuthStatus = {
|
const authStatus: CodexAuthStatus = {
|
||||||
authenticated: result.auth.authenticated,
|
authenticated: result.auth.authenticated,
|
||||||
method,
|
method,
|
||||||
// Only set apiKeyValid for actual API key methods, not CLI login
|
// Only set apiKeyValid for actual API key methods, not CLI login
|
||||||
apiKeyValid: method === "cli_verified" || method === "cli_tokens" ? undefined : result.auth.authenticated,
|
apiKeyValid:
|
||||||
|
method === "cli_verified" || method === "cli_tokens"
|
||||||
|
? undefined
|
||||||
|
: result.auth.authenticated,
|
||||||
};
|
};
|
||||||
console.log("[Codex Setup] Auth Status:", authStatus);
|
console.log("[Codex Setup] Auth Status:", authStatus);
|
||||||
setCodexAuthStatus(authStatus);
|
setCodexAuthStatus(authStatus);
|
||||||
@@ -866,16 +996,18 @@ function CodexSetupStep({
|
|||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
if (setupApi?.installCodex) {
|
if (setupApi?.installCodex) {
|
||||||
const unsubscribe = setupApi.onInstallProgress?.((progress: { cli?: string; data?: string; type?: string }) => {
|
const unsubscribe = setupApi.onInstallProgress?.(
|
||||||
if (progress.cli === "codex") {
|
(progress: { cli?: string; data?: string; type?: string }) => {
|
||||||
setCodexInstallProgress({
|
if (progress.cli === "codex") {
|
||||||
output: [
|
setCodexInstallProgress({
|
||||||
...codexInstallProgress.output,
|
output: [
|
||||||
progress.data || progress.type || "",
|
...codexInstallProgress.output,
|
||||||
],
|
progress.data || progress.type || "",
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const result = await setupApi.installCodex();
|
const result = await setupApi.installCodex();
|
||||||
|
|
||||||
@@ -912,7 +1044,10 @@ function CodexSetupStep({
|
|||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
const setupApi = api.setup;
|
const setupApi = api.setup;
|
||||||
|
|
||||||
console.log("[Codex Setup] storeApiKey available:", !!setupApi?.storeApiKey);
|
console.log(
|
||||||
|
"[Codex Setup] storeApiKey available:",
|
||||||
|
!!setupApi?.storeApiKey
|
||||||
|
);
|
||||||
|
|
||||||
if (setupApi?.storeApiKey) {
|
if (setupApi?.storeApiKey) {
|
||||||
console.log("[Codex Setup] Calling storeApiKey for openai...");
|
console.log("[Codex Setup] Calling storeApiKey for openai...");
|
||||||
@@ -920,7 +1055,9 @@ function CodexSetupStep({
|
|||||||
console.log("[Codex Setup] storeApiKey result:", result);
|
console.log("[Codex Setup] storeApiKey result:", result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log("[Codex Setup] API key stored successfully, updating state...");
|
console.log(
|
||||||
|
"[Codex Setup] API key stored successfully, updating state..."
|
||||||
|
);
|
||||||
setApiKeys({ ...apiKeys, openai: apiKey });
|
setApiKeys({ ...apiKeys, openai: apiKey });
|
||||||
setCodexAuthStatus({
|
setCodexAuthStatus({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
@@ -933,7 +1070,9 @@ function CodexSetupStep({
|
|||||||
console.log("[Codex Setup] Failed to store API key:", result.error);
|
console.log("[Codex Setup] Failed to store API key:", result.error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("[Codex Setup] Web mode - storing API key in app state only");
|
console.log(
|
||||||
|
"[Codex Setup] Web mode - storing API key in app state only"
|
||||||
|
);
|
||||||
setApiKeys({ ...apiKeys, openai: apiKey });
|
setApiKeys({ ...apiKeys, openai: apiKey });
|
||||||
setCodexAuthStatus({
|
setCodexAuthStatus({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
@@ -957,13 +1096,14 @@ function CodexSetupStep({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isAuthenticated = codexAuthStatus?.authenticated || apiKeys.openai;
|
const isAuthenticated = codexAuthStatus?.authenticated || apiKeys.openai;
|
||||||
|
|
||||||
const getAuthMethodLabel = () => {
|
const getAuthMethodLabel = () => {
|
||||||
if (!isAuthenticated) return null;
|
if (!isAuthenticated) return null;
|
||||||
if (apiKeys.openai) return "API Key (Manual)";
|
if (apiKeys.openai) return "API Key (Manual)";
|
||||||
if (codexAuthStatus?.method === "api_key") return "API Key (Auth File)";
|
if (codexAuthStatus?.method === "api_key") return "API Key (Auth File)";
|
||||||
if (codexAuthStatus?.method === "env") return "API Key (Environment)";
|
if (codexAuthStatus?.method === "env") return "API Key (Environment)";
|
||||||
if (codexAuthStatus?.method === "cli_verified") return "CLI Login (ChatGPT)";
|
if (codexAuthStatus?.method === "cli_verified")
|
||||||
|
return "CLI Login (ChatGPT)";
|
||||||
return "Authenticated";
|
return "Authenticated";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1031,7 +1171,10 @@ function CodexSetupStep({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<StatusBadge status="not_authenticated" label="Not Authenticated" />
|
<StatusBadge
|
||||||
|
status="not_authenticated"
|
||||||
|
label="Not Authenticated"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -1114,9 +1257,7 @@ function CodexSetupStep({
|
|||||||
<Key className="w-5 h-5" />
|
<Key className="w-5 h-5" />
|
||||||
Authentication
|
Authentication
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>Codex requires an OpenAI API key</CardDescription>
|
||||||
Codex requires an OpenAI API key
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{codexCliStatus?.installed && (
|
{codexCliStatus?.installed && (
|
||||||
@@ -1236,7 +1377,8 @@ function CodexSetupStep({
|
|||||||
Codex is ready to use!
|
Codex is ready to use!
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{getAuthMethodLabel() && `Authenticated via ${getAuthMethodLabel()}. `}
|
{getAuthMethodLabel() &&
|
||||||
|
`Authenticated via ${getAuthMethodLabel()}. `}
|
||||||
You can proceed to complete setup
|
You can proceed to complete setup
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1381,22 +1523,34 @@ function CompleteStep({ onFinish }: { onFinish: () => void }) {
|
|||||||
|
|
||||||
// Main Setup View
|
// Main Setup View
|
||||||
export function SetupView() {
|
export function SetupView() {
|
||||||
const { currentStep, setCurrentStep, completeSetup, setSkipClaudeSetup, setSkipCodexSetup } =
|
const {
|
||||||
useSetupStore();
|
currentStep,
|
||||||
|
setCurrentStep,
|
||||||
|
completeSetup,
|
||||||
|
setSkipClaudeSetup,
|
||||||
|
setSkipCodexSetup,
|
||||||
|
} = useSetupStore();
|
||||||
const { setCurrentView } = useAppStore();
|
const { setCurrentView } = useAppStore();
|
||||||
|
|
||||||
const steps = ["welcome", "claude", "codex", "complete"] as const;
|
const steps = ["welcome", "claude", "codex", "complete"] as const;
|
||||||
type StepName = typeof steps[number];
|
type StepName = (typeof steps)[number];
|
||||||
const getStepName = (): StepName => {
|
const getStepName = (): StepName => {
|
||||||
if (currentStep === "claude_detect" || currentStep === "claude_auth") return "claude";
|
if (currentStep === "claude_detect" || currentStep === "claude_auth")
|
||||||
if (currentStep === "codex_detect" || currentStep === "codex_auth") return "codex";
|
return "claude";
|
||||||
|
if (currentStep === "codex_detect" || currentStep === "codex_auth")
|
||||||
|
return "codex";
|
||||||
if (currentStep === "welcome") return "welcome";
|
if (currentStep === "welcome") return "welcome";
|
||||||
return "complete";
|
return "complete";
|
||||||
};
|
};
|
||||||
const currentIndex = steps.indexOf(getStepName());
|
const currentIndex = steps.indexOf(getStepName());
|
||||||
|
|
||||||
const handleNext = (from: string) => {
|
const handleNext = (from: string) => {
|
||||||
console.log("[Setup Flow] handleNext called from:", from, "currentStep:", currentStep);
|
console.log(
|
||||||
|
"[Setup Flow] handleNext called from:",
|
||||||
|
from,
|
||||||
|
"currentStep:",
|
||||||
|
currentStep
|
||||||
|
);
|
||||||
switch (from) {
|
switch (from) {
|
||||||
case "welcome":
|
case "welcome":
|
||||||
console.log("[Setup Flow] Moving to claude_detect step");
|
console.log("[Setup Flow] Moving to claude_detect step");
|
||||||
@@ -1445,10 +1599,7 @@ export function SetupView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="h-full flex flex-col content-bg" data-testid="setup-view">
|
||||||
className="h-full flex flex-col content-bg"
|
|
||||||
data-testid="setup-view"
|
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex-shrink-0 border-b border-border bg-glass backdrop-blur-md titlebar-drag-region">
|
<div className="flex-shrink-0 border-b border-border bg-glass backdrop-blur-md titlebar-drag-region">
|
||||||
<div className="px-8 py-4">
|
<div className="px-8 py-4">
|
||||||
@@ -1467,7 +1618,10 @@ export function SetupView() {
|
|||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="w-full max-w-2xl mx-auto">
|
<div className="w-full max-w-2xl mx-auto">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<StepIndicator currentStep={currentIndex} totalSteps={steps.length} />
|
<StepIndicator
|
||||||
|
currentStep={currentIndex}
|
||||||
|
totalSteps={steps.length}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-8">
|
<div className="py-8">
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export async function initializeProject(
|
|||||||
const exists = await api.exists(fullPath);
|
const exists = await api.exists(fullPath);
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await api.writeFile(fullPath, defaultContent);
|
await api.writeFile(fullPath, defaultContent as string);
|
||||||
createdFiles.push(relativePath);
|
createdFiles.push(relativePath);
|
||||||
} else {
|
} else {
|
||||||
existingFiles.push(relativePath);
|
existingFiles.push(relativePath);
|
||||||
|
|||||||
91
app/src/types/electron.d.ts
vendored
91
app/src/types/electron.d.ts
vendored
@@ -272,7 +272,10 @@ export interface SpecRegenerationAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoModeAPI {
|
export interface AutoModeAPI {
|
||||||
start: (projectPath: string, maxConcurrency?: number) => Promise<{
|
start: (
|
||||||
|
projectPath: string,
|
||||||
|
maxConcurrency?: number
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -299,25 +302,38 @@ export interface AutoModeAPI {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
runFeature: (projectPath: string, featureId: string, useWorktrees?: boolean) => Promise<{
|
runFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string,
|
||||||
|
useWorktrees?: boolean
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
passes?: boolean;
|
passes?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
verifyFeature: (projectPath: string, featureId: string) => Promise<{
|
verifyFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
passes?: boolean;
|
passes?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
resumeFeature: (projectPath: string, featureId: string) => Promise<{
|
resumeFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
passes?: boolean;
|
passes?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
contextExists: (projectPath: string, featureId: string) => Promise<{
|
contextExists: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
exists?: boolean;
|
exists?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -329,13 +345,21 @@ export interface AutoModeAPI {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
followUpFeature: (projectPath: string, featureId: string, prompt: string, imagePaths?: string[]) => Promise<{
|
followUpFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string,
|
||||||
|
prompt: string,
|
||||||
|
imagePaths?: string[]
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
passes?: boolean;
|
passes?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
commitFeature: (projectPath: string, featureId: string) => Promise<{
|
commitFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -345,6 +369,9 @@ export interface AutoModeAPI {
|
|||||||
|
|
||||||
export interface ElectronAPI {
|
export interface ElectronAPI {
|
||||||
ping: () => Promise<string>;
|
ping: () => Promise<string>;
|
||||||
|
openExternalLink: (
|
||||||
|
url: string
|
||||||
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
|
|
||||||
// Dialog APIs
|
// Dialog APIs
|
||||||
openDirectory: () => Promise<{
|
openDirectory: () => Promise<{
|
||||||
@@ -362,7 +389,10 @@ export interface ElectronAPI {
|
|||||||
content?: string;
|
content?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
writeFile: (filePath: string, content: string) => Promise<{
|
writeFile: (
|
||||||
|
filePath: string,
|
||||||
|
content: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -525,25 +555,35 @@ export interface FileDiffResult {
|
|||||||
|
|
||||||
export interface WorktreeAPI {
|
export interface WorktreeAPI {
|
||||||
// Revert feature changes by removing the worktree
|
// Revert feature changes by removing the worktree
|
||||||
revertFeature: (projectPath: string, featureId: string) => Promise<{
|
revertFeature: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
removedPath?: string;
|
removedPath?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Merge feature worktree changes back to main branch
|
// Merge feature worktree changes back to main branch
|
||||||
mergeFeature: (projectPath: string, featureId: string, options?: {
|
mergeFeature: (
|
||||||
squash?: boolean;
|
projectPath: string,
|
||||||
commitMessage?: string;
|
featureId: string,
|
||||||
squashMessage?: string;
|
options?: {
|
||||||
}) => Promise<{
|
squash?: boolean;
|
||||||
|
commitMessage?: string;
|
||||||
|
squashMessage?: string;
|
||||||
|
}
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
mergedBranch?: string;
|
mergedBranch?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Get worktree info for a feature
|
// Get worktree info for a feature
|
||||||
getInfo: (projectPath: string, featureId: string) => Promise<{
|
getInfo: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
worktreePath?: string;
|
worktreePath?: string;
|
||||||
branchName?: string;
|
branchName?: string;
|
||||||
@@ -552,7 +592,10 @@ export interface WorktreeAPI {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Get worktree status (changed files, commits)
|
// Get worktree status (changed files, commits)
|
||||||
getStatus: (projectPath: string, featureId: string) => Promise<WorktreeStatus>;
|
getStatus: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<WorktreeStatus>;
|
||||||
|
|
||||||
// List all feature worktrees
|
// List all feature worktrees
|
||||||
list: (projectPath: string) => Promise<{
|
list: (projectPath: string) => Promise<{
|
||||||
@@ -562,10 +605,17 @@ export interface WorktreeAPI {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Get file diffs for a feature worktree
|
// Get file diffs for a feature worktree
|
||||||
getDiffs: (projectPath: string, featureId: string) => Promise<FileDiffsResult>;
|
getDiffs: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string
|
||||||
|
) => Promise<FileDiffsResult>;
|
||||||
|
|
||||||
// Get diff for a specific file in a worktree
|
// Get diff for a specific file in a worktree
|
||||||
getFileDiff: (projectPath: string, featureId: string, filePath: string) => Promise<FileDiffResult>;
|
getFileDiff: (
|
||||||
|
projectPath: string,
|
||||||
|
featureId: string,
|
||||||
|
filePath: string
|
||||||
|
) => Promise<FileDiffResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitAPI {
|
export interface GitAPI {
|
||||||
@@ -573,7 +623,10 @@ export interface GitAPI {
|
|||||||
getDiffs: (projectPath: string) => Promise<FileDiffsResult>;
|
getDiffs: (projectPath: string) => Promise<FileDiffsResult>;
|
||||||
|
|
||||||
// Get diff for a specific file in the main project
|
// Get diff for a specific file in the main project
|
||||||
getFileDiff: (projectPath: string, filePath: string) => Promise<FileDiffResult>;
|
getFileDiff: (
|
||||||
|
projectPath: string,
|
||||||
|
filePath: string
|
||||||
|
) => Promise<FileDiffResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model definition type
|
// Model definition type
|
||||||
|
|||||||
Reference in New Issue
Block a user